Skip to content

Commit

Permalink
Refactor URL source callbacks and add mapping data (#91)
Browse files Browse the repository at this point in the history
* Refactor URL source callbacks and add mapping data (#91)

* Refactor URL source callbacks and fix error messages

* Refactor URL source thread and remove unused variables
  • Loading branch information
royshil committed Apr 9, 2024
1 parent 71cc597 commit 79852bd
Show file tree
Hide file tree
Showing 11 changed files with 703 additions and 307 deletions.
12 changes: 7 additions & 5 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -73,14 +73,16 @@ target_include_directories(${CMAKE_PROJECT_NAME} PRIVATE vendor/nlohmann-json)
target_sources(
${CMAKE_PROJECT_NAME}
PRIVATE src/plugin-main.c
src/url-source.cpp
src/ui/RequestBuilder.cpp
src/obs-source-util.cpp
src/mapping-data.cpp
src/request-data.cpp
src/ui/RequestBuilder.cpp
src/ui/text-render-helper.cpp
src/url-source-info.c
src/obs-source-util.cpp
src/ui/outputmapping.cpp
src/url-source-callbacks.cpp
src/url-source-thread.cpp)
src/url-source-info.c
src/url-source-thread.cpp
src/url-source.cpp)
add_subdirectory(src/parsers)

set_target_properties_plugin(${CMAKE_PROJECT_NAME} PROPERTIES OUTPUT_NAME ${_name})
34 changes: 34 additions & 0 deletions src/mapping-data.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@

#include "mapping-data.h"
#include <nlohmann/json.hpp>

std::string serialize_output_mapping_data(const output_mapping_data &data)
{
nlohmann::json j;
for (const auto &mapping : data.mappings) {
nlohmann::json j_mapping;
j_mapping["name"] = mapping.name;
j_mapping["output_source"] = mapping.output_source;
j_mapping["template_string"] = mapping.template_string;
j_mapping["css_props"] = mapping.css_props;
j_mapping["unhide_output_source"] = mapping.unhide_output_source;
j.push_back(j_mapping);
}
return j.dump();
}

output_mapping_data deserialize_output_mapping_data(const std::string &data)
{
output_mapping_data result;
nlohmann::json j = nlohmann::json::parse(data);
for (const auto &j_mapping : j) {
output_mapping mapping;
mapping.name = j_mapping.value("name", "");
mapping.output_source = j_mapping.value("output_source", "");
mapping.template_string = j_mapping.value("template_string", "");
mapping.css_props = j_mapping.value("css_props", "");
mapping.unhide_output_source = j_mapping.value("unhide_output_source", false);
result.mappings.push_back(mapping);
}
return result;
}
24 changes: 24 additions & 0 deletions src/mapping-data.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#ifndef MAPPING_DATA_H
#define MAPPING_DATA_H

#include <string>
#include <vector>

const std::string none_internal_rendering = "None / Internal rendering";

struct output_mapping {
std::string name;
std::string output_source;
std::string template_string;
std::string css_props;
bool unhide_output_source = false;
};

struct output_mapping_data {
std::vector<output_mapping> mappings;
};

std::string serialize_output_mapping_data(const output_mapping_data &data);
output_mapping_data deserialize_output_mapping_data(const std::string &data);

#endif // MAPPING_DATA_H
204 changes: 204 additions & 0 deletions src/ui/outputmapping.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
#include "outputmapping.h"
#include "ui_outputmapping.h"

#include <QComboBox>
#include <QHeaderView>
#include <QStandardItem>

#include <obs.h>
#include <obs-module.h>

namespace {
// add_sources_to_list is a helper function that adds all text and media sources to the list
bool add_sources_to_list(void *list_property, obs_source_t *source)
{
// add all text and media sources to the list
auto source_id = obs_source_get_id(source);
if (strcmp(source_id, "text_ft2_source_v2") != 0 &&
strcmp(source_id, "text_gdiplus_v2") != 0 && strcmp(source_id, "ffmpeg_source") != 0 &&
strcmp(source_id, "image_source") != 0) {
return true;
}

QComboBox *sources = static_cast<QComboBox *>(list_property);
const char *name = obs_source_get_name(source);
std::string name_with_prefix;
// add a prefix to the name to indicate the source type
if (strcmp(source_id, "text_ft2_source_v2") == 0 ||
strcmp(source_id, "text_gdiplus_v2") == 0) {
name_with_prefix = std::string("(Text) ").append(name);
} else if (strcmp(source_id, "image_source") == 0) {
name_with_prefix = std::string("(Image) ").append(name);
} else if (strcmp(source_id, "ffmpeg_source") == 0) {
name_with_prefix = std::string("(Media) ").append(name);
}
sources->addItem(name_with_prefix.c_str());
return true;
}

const std::string default_css_props = R"(background-color: transparent;
color: #FFFFFF;
font-size: 48px;
)";
const std::string default_template_string = R"({{output}})";
} // namespace

OutputMapping::OutputMapping(output_mapping_data *mapping_data_in,
std::function<void()> update_handler_in, QWidget *parent)
: QDialog(parent),
ui(new Ui::OutputMapping),
mapping_data(mapping_data_in),
update_handler(update_handler_in)
{
ui->setupUi(this);

model.setHorizontalHeaderLabels(QStringList() << "Mapping Name"
<< "Output");
ui->tableView->setModel(&model);
ui->tableView->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);

// if an item is selected in the tableView, enable the remove button
connect(ui->tableView->selectionModel(), &QItemSelectionModel::selectionChanged,
[this](const QItemSelection &selected) {
const bool enable = !selected.indexes().isEmpty();
ui->toolButton_removeMapping->setEnabled(enable);
ui->plainTextEdit_template->setEnabled(enable);
ui->plainTextEdit_cssProps->setEnabled(enable);

if (enable) {
// get the selected row
const auto row = selected.indexes().first().row();
// get the data and output_source of the selected row
const auto data = model.item(row, 0)->text();
const auto output_source = model.item(row, 1)->text();
ui->plainTextEdit_template->blockSignals(true);
ui->plainTextEdit_cssProps->blockSignals(true);
ui->checkBox_unhide_Source->blockSignals(true);
// set the plainTextEdit_template and plainTextEdit_cssProps to the template_string and css_props of the selected row
ui->plainTextEdit_template->setPlainText(
this->mapping_data->mappings[row].template_string.c_str());
ui->plainTextEdit_cssProps->setPlainText(
this->mapping_data->mappings[row].css_props.c_str());
ui->checkBox_unhide_Source->setChecked(
this->mapping_data->mappings[row].unhide_output_source);
ui->plainTextEdit_template->blockSignals(false);
ui->plainTextEdit_cssProps->blockSignals(false);
ui->checkBox_unhide_Source->blockSignals(false);
}
});

// connect toolButton_addMapping to addMapping
connect(ui->toolButton_addMapping, &QToolButton::clicked, this, &OutputMapping::addMapping);
// connect toolButton_removeMapping to removeMapping
connect(ui->toolButton_removeMapping, &QToolButton::clicked, this,
&OutputMapping::removeMapping);

// connect plainTextEdit_template textChanged to update the mapping template_string
connect(ui->plainTextEdit_template, &QPlainTextEdit::textChanged, [this]() {
// get the selected row
const auto row = ui->tableView->currentIndex().row();
// set the template_string of the selected row to the plainTextEdit_template text
this->mapping_data->mappings[row].template_string =
ui->plainTextEdit_template->toPlainText().toStdString();
// call update_handler
this->update_handler();
});
connect(ui->plainTextEdit_cssProps, &QPlainTextEdit::textChanged, [this]() {
// get the selected row
const auto row = ui->tableView->currentIndex().row();
// set the css_props of the selected row to the plainTextEdit_cssProps text
this->mapping_data->mappings[row].css_props =
ui->plainTextEdit_cssProps->toPlainText().toStdString();
// call update_handler
this->update_handler();
});

// populate the model with the mapping data
for (const auto &mapping : mapping_data->mappings) {
model.appendRow(QList<QStandardItem *>() << new QStandardItem(mapping.name.c_str())
<< new QStandardItem(""));
QComboBox *comboBox = createSourcesComboBox();
if (!mapping.output_source.empty()) {
// select the output_source of the mapping in the comboBox
comboBox->blockSignals(true);
comboBox->setCurrentText(mapping.output_source.c_str());
comboBox->blockSignals(false);
}
// add a row to the model with the data and output_source of the mapping
// set comboBox as the index widget of the last item in the model
ui->tableView->setIndexWidget(model.index(model.rowCount() - 1, 1), comboBox);
}

// connect item edit on tableView
connect(&model, &QStandardItemModel::itemChanged, [this](QStandardItem *item) {
// update mapping name
if (item->column() == 0) {
this->mapping_data->mappings[item->row()].name = item->text().toStdString();
}
});
}

OutputMapping::~OutputMapping()
{
delete ui;
}

/// @brief a helper function that creates a comboBox and adds all text and media sources to the list.
/// The sources are added with a prefix to indicate the source type (Text, Image, or Media).
/// @return a pointer to the comboBox
QComboBox *OutputMapping::createSourcesComboBox()
{
QComboBox *comboBox = new QComboBox(this);
// add "Internal Renderer" to the comboBox
comboBox->addItem(QString::fromStdString(none_internal_rendering));
// add all text and media sources to the comboBox
obs_enum_sources(add_sources_to_list, comboBox);
// connect comboBox to update_handler
connect(comboBox, &QComboBox::currentIndexChanged, [this, comboBox]() {
// get the selected row
const auto row = ui->tableView->currentIndex().row();
// get the output_name of the selected item in the comboBox
const auto output_name = comboBox->currentText().toStdString();
// remove the prefix from the output_name if it exists
std::string output_name_without_prefix = output_name;
if (output_name.find("(Text) ") == 0) {
output_name_without_prefix = output_name.substr(7);
} else if (output_name.find("(Image) ") == 0) {
output_name_without_prefix = output_name.substr(8);
} else if (output_name.find("(Media) ") == 0) {
output_name_without_prefix = output_name.substr(8);
}
// set the css_props of the selected row to the plainTextEdit_cssProps text
this->mapping_data->mappings[row].output_source = output_name_without_prefix;
// call update_handler
this->update_handler();
});

return comboBox;
}

void OutputMapping::addMapping()
{
// add row to model
model.appendRow(QList<QStandardItem *>()
<< new QStandardItem("Mapping") << new QStandardItem("Output"));
QComboBox *comboBox = createSourcesComboBox();
// set comboBox as the index widget of the last item in the model
ui->tableView->setIndexWidget(model.index(model.rowCount() - 1, 1), comboBox);
// add a new mapping to the mapping_data
this->mapping_data->mappings.push_back(output_mapping{
"Mapping", none_internal_rendering, default_template_string, default_css_props});
// call update_handler
this->update_handler();
}

void OutputMapping::removeMapping()
{
// remove the mapping from the mapping_data
this->mapping_data->mappings.erase(this->mapping_data->mappings.begin() +
ui->tableView->currentIndex().row());
// remove row from model
model.removeRow(ui->tableView->currentIndex().row());
// call update_handler
this->update_handler();
}
38 changes: 38 additions & 0 deletions src/ui/outputmapping.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#ifndef OUTPUTMAPPING_H
#define OUTPUTMAPPING_H

#include <QDialog>
#include <QStandardItemModel>
#include <QComboBox>
#include <functional>

#include "mapping-data.h"

namespace Ui {
class OutputMapping;
}

class OutputMapping : public QDialog {
Q_OBJECT

public:
explicit OutputMapping(output_mapping_data *mapping_data_in,
std::function<void()> update_handler, QWidget *parent = nullptr);
~OutputMapping();

OutputMapping(const OutputMapping &) = delete;
OutputMapping &operator=(const OutputMapping &) = delete;

private:
Ui::OutputMapping *ui;
QStandardItemModel model;
output_mapping_data *mapping_data;
QComboBox *createSourcesComboBox();
std::function<void()> update_handler;

private slots:
void addMapping();
void removeMapping();
};

#endif // OUTPUTMAPPING_H

0 comments on commit 79852bd

Please sign in to comment.