Add source properties window (very preliminary)

- Add a properties window for sources so that you can now actually edit
   the settings for sources.  Also, display the source by itself in the
   window (Note: not working on mac, and possibly not working on linux).

   When changing the settings for a source, it will call
   obs_source_update on that source when you have modified any values
   automatically.

 - Add a properties 'widget', eventually I want to turn this in to a
   regular nice properties view like you'd see in the designer, but
   right now it just uses a form layout in a QScrollArea with regular
   controls to display the properties.  It's clunky but works for the
   time being.

 - Make it so that swap chains and the main graphics subsystem will
   automatically use at least one backbuffer if none was specified

 - Fix bug where displays weren't added to the main display array

 - Make it so that you can get the properties of a source via the actual
   pointer of a source/encoder/output in addition to being able to look
   up properties via identifier.

 - When registering source types, check for required functions (wasn't
   doing it before).  getheight/getwidth should not be optional if it's
   a video source as well.

 - Add an RAII OBSObj wrapper to obs.hpp for non-reference-counted
   libobs pointers

 - Add an RAII OBSSignal wrapper to obs.hpp for libobs signals to
   automatically disconnect them on destruction

 - Move the "scale and center" calculation in window-basic-main.cpp to
   its own function and in its own source file

 - Add an 'update' callback to WASAPI audio sources
This commit is contained in:
jp9000
2014-03-23 01:07:54 -07:00
parent 6c904650b3
commit d9251f9e87
26 changed files with 991 additions and 74 deletions

View File

@@ -51,7 +51,9 @@ set(obs_SOURCES
obs-app.cpp
window-basic-main.cpp
window-basic-settings.cpp
window-basic-properties.cpp
window-namedialog.cpp
properties-view.cpp
qt-wrappers.cpp)
set(obs_HEADERS
@@ -60,14 +62,18 @@ set(obs_HEADERS
window-main.hpp
window-basic-main.hpp
window-basic-settings.hpp
window-basic-properties.hpp
window-namedialog.hpp
properties-view.hpp
display-helpers.hpp
qt-display.hpp
qt-wrappers.hpp)
set(obs_UI
forms/NameDialog.ui
forms/OBSBasic.ui
forms/OBSBasicSettings.ui)
forms/OBSBasicSettings.ui
forms/OBSBasicProperties.ui)
set(obs_QRC
forms/obs.qrc)

42
obs/display-helpers.hpp Normal file
View File

@@ -0,0 +1,42 @@
/******************************************************************************
Copyright (C) 2014 by Hugh Bailey <obs.jim@gmail.com>
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/>.
******************************************************************************/
#pragma once
static inline void GetScaleAndCenterPos(
int baseCX, int baseCY, int windowCX, int windowCY,
int &x, int &y, float &scale)
{
double windowAspect, baseAspect;
int newCX, newCY;
windowAspect = double(windowCX) / double(windowCY);
baseAspect = double(baseCX) / double(baseCY);
if (windowAspect > baseAspect) {
scale = float(windowCY) / float(baseCY);
newCX = int(double(windowCY) * baseAspect);
newCY = windowCY;
} else {
scale = float(windowCX) / float(baseCX);
newCX = windowCX;
newCY = int(float(windowCX) / baseAspect);
}
x = windowCX/2 - newCX/2;
y = windowCY/2 - newCY/2;
}

View File

@@ -433,7 +433,7 @@
</action>
<action name="actionSourceProperties">
<property name="enabled">
<bool>false</bool>
<bool>true</bool>
</property>
<property name="icon">
<iconset resource="obs.qrc">

View File

@@ -0,0 +1,48 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>OBSBasicProperties</class>
<widget class="QDialog" name="OBSBasicProperties">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>841</width>
<height>479</height>
</rect>
</property>
<property name="windowTitle">
<string>Dialog</string>
</property>
<property name="sizeGripEnabled">
<bool>true</bool>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="OBSQTDisplay" name="preview" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>20</width>
<height>20</height>
</size>
</property>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>OBSQTDisplay</class>
<extends>QWidget</extends>
<header>qt-display.hpp</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>

244
obs/properties-view.cpp Normal file
View File

@@ -0,0 +1,244 @@
#include <QFormLayout>
#include <QLabel>
#include <QCheckBox>
#include <QLineEdit>
#include <QSpinBox>
#include <QDoubleSpinBox>
#include <QComboBox>
#include "qt-wrappers.hpp"
#include "properties-view.hpp"
#include <string>
using namespace std;
OBSPropertiesView::OBSPropertiesView(OBSData settings_,
obs_properties_t properties_, void *obj_,
PropertiesUpdateCallback callback_)
: QScrollArea (nullptr),
properties (properties_),
settings (settings_),
obj (obj_),
callback (callback_)
{
widget = new QWidget();
QFormLayout *layout = new QFormLayout;
widget->setLayout(layout);
QSizePolicy policy(QSizePolicy::Expanding, QSizePolicy::Preferred);
widget->setSizePolicy(policy);
layout->setSizeConstraint(QLayout::SetMaximumSize);
layout->setLabelAlignment(Qt::AlignRight);
obs_property_t property = obs_properties_first(properties);
while (property) {
AddProperty(property, layout);
obs_property_next(&property);
}
setWidget(widget);
setSizePolicy(policy);
}
QWidget *OBSPropertiesView::NewWidget(obs_property_t prop, QWidget *widget,
const char *signal)
{
WidgetInfo *info = new WidgetInfo(this, prop, widget);
connect(widget, signal, info, SLOT(ControlChanged()));
children.push_back(std::move(unique_ptr<WidgetInfo>(info)));
return widget;
}
QWidget *OBSPropertiesView::AddCheckbox(obs_property_t prop)
{
const char *desc = obs_property_description(prop);
return NewWidget(prop, new QCheckBox(QT_UTF8(desc)),
SIGNAL(stateChanged(int)));
}
QWidget *OBSPropertiesView::AddText(obs_property_t prop)
{
return NewWidget(prop, new QLineEdit(),
SIGNAL(textEdited(const QString &)));
}
void OBSPropertiesView::AddPath(obs_property_t prop, QFormLayout *layout)
{
/* TODO */
UNUSED_PARAMETER(prop);
UNUSED_PARAMETER(layout);
}
QWidget *OBSPropertiesView::AddInt(obs_property_t prop)
{
QSpinBox *spin = new QSpinBox();
spin->setMinimum(obs_property_int_min(prop));
spin->setMaximum(obs_property_int_max(prop));
spin->setSingleStep(obs_property_int_step(prop));
return NewWidget(prop, spin, SIGNAL(valueChanged(int)));
}
QWidget *OBSPropertiesView::AddFloat(obs_property_t prop)
{
QDoubleSpinBox *spin = new QDoubleSpinBox();
spin->setMinimum(obs_property_float_min(prop));
spin->setMaximum(obs_property_float_max(prop));
spin->setSingleStep(obs_property_float_step(prop));
return NewWidget(prop, spin, SIGNAL(valueChanged(double)));
}
QWidget *OBSPropertiesView::AddList(obs_property_t prop)
{
QComboBox *combo = new QComboBox();
obs_combo_type type = obs_property_list_type(prop);
size_t count = obs_property_list_item_count(prop);
for (size_t i = 0; i < count; i++) {
const char *name = obs_property_list_item_name(prop, i);
const char *val = obs_property_list_item_value(prop, i);
combo->addItem(QT_UTF8(name), QT_UTF8(val));
}
if (type == OBS_COMBO_TYPE_EDITABLE) {
combo->setEditable(true);
return NewWidget(prop, combo,
SLOT(editTextChanged(const QString &)));
}
return NewWidget(prop, combo, SIGNAL(currentIndexChanged(int)));
}
void OBSPropertiesView::AddProperty(obs_property_t property,
QFormLayout *layout)
{
obs_property_type type = obs_property_get_type(property);
QWidget *widget = nullptr;
switch (type) {
case OBS_PROPERTY_INVALID:
return;
case OBS_PROPERTY_BOOL:
widget = AddCheckbox(property);
break;
case OBS_PROPERTY_INT:
widget = AddInt(property);
break;
case OBS_PROPERTY_FLOAT:
widget = AddFloat(property);
break;
case OBS_PROPERTY_TEXT:
widget = AddText(property);
break;
case OBS_PROPERTY_PATH:
AddPath(property, layout);
break;
case OBS_PROPERTY_LIST:
widget = AddList(property);
break;
case OBS_PROPERTY_COLOR:
/* TODO */
break;
}
if (!widget)
return;
QLabel *label = nullptr;
if (type != OBS_PROPERTY_BOOL)
label = new QLabel(QT_UTF8(obs_property_description(property)));
layout->addRow(label, widget);
}
void WidgetInfo::BoolChanged(const char *setting)
{
QCheckBox *checkbox = static_cast<QCheckBox*>(widget);
obs_data_setbool(view->settings, setting,
checkbox->checkState() == Qt::Checked);
}
void WidgetInfo::IntChanged(const char *setting)
{
QSpinBox *spin = static_cast<QSpinBox*>(widget);
obs_data_setint(view->settings, setting, spin->value());
}
void WidgetInfo::FloatChanged(const char *setting)
{
QDoubleSpinBox *spin = static_cast<QDoubleSpinBox*>(widget);
obs_data_setdouble(view->settings, setting, spin->value());
}
void WidgetInfo::TextChanged(const char *setting)
{
QLineEdit *edit = static_cast<QLineEdit*>(widget);
obs_data_setstring(view->settings, setting, QT_TO_UTF8(edit->text()));
}
void WidgetInfo::PathChanged(const char *setting)
{
/* TODO */
UNUSED_PARAMETER(setting);
}
void WidgetInfo::ListChanged(const char *setting)
{
QComboBox *combo = static_cast<QComboBox*>(widget);
obs_combo_format format = obs_property_list_format(property);
obs_combo_type type = obs_property_list_type(property);
string val;
if (type == OBS_COMBO_TYPE_EDITABLE) {
val = QT_TO_UTF8(combo->currentText());
} else {
int index = combo->currentIndex();
if (index != -1) {
QVariant variant = combo->itemData(index);
val = QT_TO_UTF8(variant.toString());
}
}
switch (format) {
case OBS_COMBO_FORMAT_INVALID:
return;
case OBS_COMBO_FORMAT_INT:
obs_data_setint(view->settings, setting, stol(val));
break;
case OBS_COMBO_FORMAT_FLOAT:
obs_data_setdouble(view->settings, setting, stod(val));
break;
case OBS_COMBO_FORMAT_STRING:
obs_data_setstring(view->settings, setting, val.c_str());
break;
}
}
void WidgetInfo::ColorChanged(const char *setting)
{
/* TODO */
UNUSED_PARAMETER(setting);
}
void WidgetInfo::ControlChanged()
{
const char *setting = obs_property_name(property);
obs_property_type type = obs_property_get_type(property);
switch (type) {
case OBS_PROPERTY_INVALID: return;
case OBS_PROPERTY_BOOL: BoolChanged(setting); break;
case OBS_PROPERTY_INT: IntChanged(setting); break;
case OBS_PROPERTY_FLOAT: FloatChanged(setting); break;
case OBS_PROPERTY_TEXT: TextChanged(setting); break;
case OBS_PROPERTY_PATH: PathChanged(setting); break;
case OBS_PROPERTY_LIST: ListChanged(setting); break;
case OBS_PROPERTY_COLOR: ColorChanged(setting); break;
}
view->callback(view->obj, view->settings);
}

77
obs/properties-view.hpp Normal file
View File

@@ -0,0 +1,77 @@
#pragma once
#include <QScrollArea>
#include <obs.hpp>
#include <vector>
#include <memory>
class QFormLayout;
class OBSPropertiesView;
typedef void (*PropertiesUpdateCallback)(void *obj, obs_data_t settings);
/* ------------------------------------------------------------------------- */
class WidgetInfo : public QObject {
Q_OBJECT
private:
OBSPropertiesView *view;
obs_property_t property;
QWidget *widget;
void BoolChanged(const char *setting);
void IntChanged(const char *setting);
void FloatChanged(const char *setting);
void TextChanged(const char *setting);
void PathChanged(const char *setting);
void ListChanged(const char *setting);
void ColorChanged(const char *setting);
public:
inline WidgetInfo(OBSPropertiesView *view_, obs_property_t prop,
QWidget *widget_)
: view(view_), property(prop), widget(widget_)
{}
public slots:
void ControlChanged();
};
/* ------------------------------------------------------------------------- */
class OBSPropertiesView : public QScrollArea {
Q_OBJECT
friend class WidgetInfo;
private:
QWidget *widget;
obs_properties_t properties;
OBSData settings;
void *obj;
PropertiesUpdateCallback callback;
std::vector<std::unique_ptr<WidgetInfo>> children;
QWidget *NewWidget(obs_property_t prop, QWidget *widget,
const char *signal);
QWidget *AddCheckbox(obs_property_t prop);
QWidget *AddText(obs_property_t prop);
void AddPath(obs_property_t prop, QFormLayout *layout);
QWidget *AddInt(obs_property_t prop);
QWidget *AddFloat(obs_property_t prop);
QWidget *AddList(obs_property_t prop);
void AddProperty(obs_property_t property, QFormLayout *layout);
public:
OBSPropertiesView(OBSData settings,
obs_properties_t properties,
void *obj, PropertiesUpdateCallback callback);
inline ~OBSPropertiesView()
{
obs_properties_destroy(properties);
}
};

View File

@@ -29,7 +29,9 @@
#include "window-basic-settings.hpp"
#include "window-namedialog.hpp"
#include "window-basic-main.hpp"
#include "window-basic-properties.hpp"
#include "qt-wrappers.hpp"
#include "display-helpers.hpp"
#include "ui_OBSBasic.h"
@@ -43,6 +45,7 @@ OBSBasic::OBSBasic(QWidget *parent)
outputTest (NULL),
sceneChanging (false),
resizeTimer (0),
properties (nullptr),
ui (new Ui::OBSBasic)
{
ui->setupUi(this);
@@ -51,7 +54,7 @@ OBSBasic::OBSBasic(QWidget *parent)
static inline bool HasAudioDevices(const char *source_id)
{
const char *output_id = source_id;
obs_properties_t props = obs_source_properties(
obs_properties_t props = obs_get_source_properties(
OBS_SOURCE_TYPE_INPUT, output_id, App()->GetLocale());
size_t count = 0;
@@ -182,6 +185,9 @@ void OBSBasic::OBSInit()
OBSBasic::~OBSBasic()
{
if (properties)
delete properties;
/* free the lists before shutting down to remove the scene/item
* references */
ui->sources->clear();
@@ -499,26 +505,13 @@ void OBSBasic::ResetAudioDevices()
void OBSBasic::ResizePreview(uint32_t cx, uint32_t cy)
{
double targetAspect, baseAspect;
QSize targetSize;
/* resize preview panel to fix to the top section of the window */
targetSize = ui->preview->size();
targetAspect = double(targetSize.width()) / double(targetSize.height());
baseAspect = double(cx) / double(cy);
if (targetAspect > baseAspect) {
previewScale = float(targetSize.height()) / float(cy);
cx = targetSize.height() * baseAspect;
cy = targetSize.height();
} else {
previewScale = float(targetSize.width()) / float(cx);
cx = targetSize.width();
cy = targetSize.width() / baseAspect;
}
previewX = targetSize.width() /2 - cx/2;
previewY = targetSize.height()/2 - cy/2;
targetSize = ui->preview->size();
GetScaleAndCenterPos(int(cx), int(cy),
targetSize.width(), targetSize.height(),
previewX, previewY, previewScale);
if (isVisible()) {
if (resizeTimer)
@@ -769,6 +762,14 @@ void OBSBasic::on_actionRemoveSource_triggered()
void OBSBasic::on_actionSourceProperties_triggered()
{
OBSSceneItem item = GetCurrentSceneItem();
OBSSource source = obs_sceneitem_getsource(item);
if (source) {
delete properties;
properties = new OBSBasicProperties(this, source);
properties->Init();
}
}
void OBSBasic::on_actionSourceUp_triggered()
@@ -915,3 +916,8 @@ config_t OBSBasic::Config() const
{
return basicConfig;
}
void OBSBasic::UnloadProperties()
{
properties = nullptr;
}

View File

@@ -21,6 +21,7 @@
#include <unordered_map>
#include <memory>
#include "window-main.hpp"
#include "window-basic-properties.hpp"
#include <util/util.hpp>
@@ -42,6 +43,8 @@ private:
ConfigFile basicConfig;
OBSBasicProperties *properties;
void GetFPSCommon(uint32_t &num, uint32_t &den) const;
void GetFPSInteger(uint32_t &num, uint32_t &den) const;
void GetFPSFraction(uint32_t &num, uint32_t &den) const;
@@ -93,6 +96,8 @@ public:
void SaveProject();
void LoadProject();
void UnloadProperties();
protected:
virtual void closeEvent(QCloseEvent *event) override;
virtual void changeEvent(QEvent *event) override;

View File

@@ -0,0 +1,127 @@
/******************************************************************************
Copyright (C) 2014 by Hugh Bailey <obs.jim@gmail.com>
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 "obs-app.hpp"
#include "window-basic-properties.hpp"
#include "window-basic-main.hpp"
#include "qt-wrappers.hpp"
#include "display-helpers.hpp"
using namespace std;
OBSBasicProperties::OBSBasicProperties(QWidget *parent, OBSSource source_)
: QDialog (parent),
main (qobject_cast<OBSBasic*>(parent)),
resizeTimer (0),
ui (new Ui::OBSBasicProperties),
source (source_),
removedSignal (obs_source_signalhandler(source), "remove",
OBSBasicProperties::SourceRemoved, this)
{
setAttribute(Qt::WA_DeleteOnClose);
ui->setupUi(this);
OBSData settings = obs_source_getsettings(source);
obs_data_release(settings);
view = new OBSPropertiesView(settings,
obs_source_properties(source, App()->GetLocale()),
source, (PropertiesUpdateCallback)obs_source_update);
layout()->addWidget(view);
layout()->setAlignment(view, Qt::AlignRight);
view->show();
}
void OBSBasicProperties::SourceRemoved(void *data, calldata_t params)
{
QMetaObject::invokeMethod(static_cast<OBSBasicProperties*>(data),
"close");
UNUSED_PARAMETER(params);
}
void OBSBasicProperties::DrawPreview(void *data, uint32_t cx, uint32_t cy)
{
OBSBasicProperties *window = static_cast<OBSBasicProperties*>(data);
if (!window->source)
return;
uint32_t sourceCX = obs_source_getwidth(window->source);
uint32_t sourceCY = obs_source_getheight(window->source);
int x, y;
float scale;
GetScaleAndCenterPos(sourceCX, sourceCY, cx, cy, x, y, scale);
gs_matrix_push();
gs_matrix_scale3f(scale, scale, 1.0f);
gs_matrix_translate3f(-x, -y, 0.0f);
obs_source_video_render(window->source);
gs_matrix_pop();
}
void OBSBasicProperties::closeEvent(QCloseEvent *event)
{
main->UnloadProperties();
UNUSED_PARAMETER(event);
}
void OBSBasicProperties::resizeEvent(QResizeEvent *event)
{
if (isVisible()) {
if (resizeTimer)
killTimer(resizeTimer);
resizeTimer = startTimer(100);
}
UNUSED_PARAMETER(event);
}
void OBSBasicProperties::timerEvent(QTimerEvent *event)
{
if (event->timerId() == resizeTimer) {
killTimer(resizeTimer);
resizeTimer = 0;
QSize size = ui->preview->size();
obs_display_resize(display, size.width(), size.height());
}
}
void OBSBasicProperties::Init()
{
gs_init_data init_data = {};
show();
App()->processEvents();
QSize previewSize = ui->preview->size();
init_data.cx = uint32_t(previewSize.width());
init_data.cy = uint32_t(previewSize.height());
init_data.format = GS_RGBA;
QTToGSWindow(ui->preview->winId(), init_data.window);
display = obs_display_create(&init_data);
if (display)
obs_display_add_draw_callback(display,
OBSBasicProperties::DrawPreview, this);
}

View File

@@ -0,0 +1,56 @@
/******************************************************************************
Copyright (C) 2014 by Hugh Bailey <obs.jim@gmail.com>
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/>.
******************************************************************************/
#pragma once
#include <QDialog>
#include <memory>
#include <obs.hpp>
#include "properties-view.hpp"
class OBSBasic;
#include "ui_OBSBasicProperties.h"
class OBSBasicProperties : public QDialog {
Q_OBJECT
private:
OBSBasic *main;
int resizeTimer;
std::unique_ptr<Ui::OBSBasicProperties> ui;
OBSSource source;
OBSDisplay display;
OBSSignal removedSignal;
OBSPropertiesView *view;
static void SourceRemoved(void *data, calldata_t params);
static void DrawPreview(void *data, uint32_t cx, uint32_t cy);
public:
OBSBasicProperties(QWidget *parent, OBSSource source_);
void Init();
protected:
virtual void closeEvent(QCloseEvent *event) override;
virtual void resizeEvent(QResizeEvent *event) override;
virtual void timerEvent(QTimerEvent *event) override;
};

View File

@@ -374,9 +374,9 @@ void OBSBasicSettings::LoadAudioDevices()
const char *input_id = App()->InputAudioSource();
const char *output_id = App()->OutputAudioSource();
obs_properties_t input_props = obs_source_properties(
obs_properties_t input_props = obs_get_source_properties(
OBS_SOURCE_TYPE_INPUT, input_id, App()->GetLocale());
obs_properties_t output_props = obs_source_properties(
obs_properties_t output_props = obs_get_source_properties(
OBS_SOURCE_TYPE_INPUT, output_id, App()->GetLocale());
if (input_props) {