mirror of
https://github.com/WarmUpTill/SceneSwitcher.git
synced 2026-03-21 17:34:57 -05:00
Add OSC action
This commit is contained in:
parent
b71f633fac
commit
a1b5fbb1fe
|
|
@ -107,6 +107,8 @@ target_sources(
|
|||
src/macro-core/macro-action-macro.hpp
|
||||
src/macro-core/macro-action-media.cpp
|
||||
src/macro-core/macro-action-media.hpp
|
||||
src/macro-core/macro-action-osc.cpp
|
||||
src/macro-core/macro-action-osc.hpp
|
||||
src/macro-core/macro-action-plugin-state.cpp
|
||||
src/macro-core/macro-action-plugin-state.hpp
|
||||
src/macro-core/macro-action-profile.cpp
|
||||
|
|
@ -266,6 +268,8 @@ target_sources(
|
|||
src/utils/obs-dock.hpp
|
||||
src/utils/obs-module-helper.cpp
|
||||
src/utils/obs-module-helper.hpp
|
||||
src/utils/osc-helpers.cpp
|
||||
src/utils/osc-helpers.hpp
|
||||
src/utils/priority-helper.cpp
|
||||
src/utils/priority-helper.hpp
|
||||
src/utils/process-config.cpp
|
||||
|
|
|
|||
|
|
@ -673,6 +673,7 @@ AdvSceneSwitcher.action.projector.entry.monitor="on{{monitors}}"
|
|||
AdvSceneSwitcher.action.midi="MIDI"
|
||||
AdvSceneSwitcher.action.midi.entry="Send message to {{device}}:"
|
||||
AdvSceneSwitcher.action.midi.entry.listen="Set MIDI message selection to messages incoming on {{listenDevices}}: {{listenButton}}"
|
||||
AdvSceneSwitcher.action.osc="Open Sound Control"
|
||||
|
||||
; Transition Tab
|
||||
AdvSceneSwitcher.transitionTab.title="Transition"
|
||||
|
|
@ -1013,6 +1014,21 @@ AdvSceneSwitcher.midi.stopListen="Stop listening"
|
|||
AdvSceneSwitcher.midi.startListenFail="Device is busy!\nSomething else is already listening!"
|
||||
AdvSceneSwitcher.midi.deviceOpenFail="Failed to initialize MIDI device!"
|
||||
|
||||
AdvSceneSwitcher.osc.network="Network"
|
||||
AdvSceneSwitcher.osc.network.protocol="Protocol:"
|
||||
AdvSceneSwitcher.osc.network.address="Address:"
|
||||
AdvSceneSwitcher.osc.network.port="Port:"
|
||||
AdvSceneSwitcher.osc.message="Message"
|
||||
AdvSceneSwitcher.osc.message.type.none="None"
|
||||
AdvSceneSwitcher.osc.message.type.float="Float"
|
||||
AdvSceneSwitcher.osc.message.type.int="Integer"
|
||||
AdvSceneSwitcher.osc.message.type.string="String"
|
||||
AdvSceneSwitcher.osc.message.type.binaryBlob="Binary blob"
|
||||
AdvSceneSwitcher.osc.message.type.true="True"
|
||||
AdvSceneSwitcher.osc.message.type.false="False"
|
||||
AdvSceneSwitcher.osc.message.type.infinity="Infinitum"
|
||||
AdvSceneSwitcher.osc.message.type.null="Nil"
|
||||
|
||||
AdvSceneSwitcher.selectScene="--select scene--"
|
||||
AdvSceneSwitcher.selectPreviousScene="Previous Scene"
|
||||
AdvSceneSwitcher.selectCurrentScene="Current Scene"
|
||||
|
|
|
|||
318
src/macro-core/macro-action-osc.cpp
Normal file
318
src/macro-core/macro-action-osc.cpp
Normal file
|
|
@ -0,0 +1,318 @@
|
|||
#include "macro-action-osc.hpp"
|
||||
#include "utility.hpp"
|
||||
|
||||
#include <system_error>
|
||||
#include <QGroupBox>
|
||||
|
||||
namespace advss {
|
||||
|
||||
const std::string MacroActionOSC::id = "osc";
|
||||
|
||||
bool MacroActionOSC::_registered = MacroActionFactory::Register(
|
||||
MacroActionOSC::id, {MacroActionOSC::Create, MacroActionOSCEdit::Create,
|
||||
"AdvSceneSwitcher.action.osc"});
|
||||
|
||||
MacroActionOSC::MacroActionOSC(Macro *m)
|
||||
: MacroAction(m),
|
||||
_ctx(asio::io_context()),
|
||||
_tcpSocket(asio::ip::tcp::socket(_ctx)),
|
||||
_udpSocket(asio::ip::udp::socket(_ctx))
|
||||
{
|
||||
}
|
||||
|
||||
void MacroActionOSC::SendOSCTCPMessage(const asio::mutable_buffer &buffer)
|
||||
{
|
||||
try {
|
||||
asio::write(_tcpSocket, asio::buffer(buffer));
|
||||
} catch (const std::exception &e) {
|
||||
blog(LOG_WARNING,
|
||||
"failed to send OSC message \"%s\" via TCP %s %d: %s",
|
||||
_message.ToString().c_str(), _ip.c_str(), _port.GetValue(),
|
||||
e.what());
|
||||
}
|
||||
}
|
||||
|
||||
void MacroActionOSC::SendOSCUDPMessage(const asio::mutable_buffer &buffer)
|
||||
{
|
||||
try {
|
||||
_udpSocket.send_to(buffer, _updEndpoint);
|
||||
} catch (const std::exception &e) {
|
||||
blog(LOG_WARNING,
|
||||
"failed to send OSC message \"%s\" via UDP %s %d: %s",
|
||||
_message.ToString().c_str(), _ip.c_str(), _port.GetValue(),
|
||||
e.what());
|
||||
}
|
||||
}
|
||||
|
||||
void MacroActionOSC::UDPReconnect()
|
||||
{
|
||||
asio::error_code ec;
|
||||
asio::ip::udp::resolver resolver(_ctx);
|
||||
auto endpoints = resolver.resolve(asio::ip::udp::v4(), _ip.c_str(),
|
||||
std::to_string(_port.GetValue()), ec);
|
||||
if (ec) {
|
||||
endpoints = resolver.resolve(asio::ip::udp::v6(), _ip.c_str(),
|
||||
std::to_string(_port.GetValue()),
|
||||
ec);
|
||||
}
|
||||
if (ec) {
|
||||
blog(LOG_WARNING, "failed to get IP for \"%s\": %s",
|
||||
_ip.c_str(), ec.message().c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
_updEndpoint = endpoints.begin()->endpoint();
|
||||
_udpSocket = asio::ip::udp::socket(_ctx);
|
||||
_udpSocket.open(endpoints.begin()->endpoint().protocol());
|
||||
} catch (const std::exception &e) {
|
||||
blog(LOG_WARNING, "failed to connect to UDP %s %d: %s",
|
||||
_ip.c_str(), _port.GetValue(), e.what());
|
||||
}
|
||||
}
|
||||
|
||||
void MacroActionOSC::TCPReconnect()
|
||||
{
|
||||
asio::error_code ec;
|
||||
asio::ip::tcp::resolver resolver(_ctx);
|
||||
auto endpoints = resolver.resolve(asio::ip::tcp::v4(), _ip.c_str(),
|
||||
std::to_string(_port.GetValue()), ec);
|
||||
if (ec) {
|
||||
endpoints = resolver.resolve(asio::ip::tcp::v6(), _ip.c_str(),
|
||||
std::to_string(_port.GetValue()),
|
||||
ec);
|
||||
}
|
||||
if (ec) {
|
||||
blog(LOG_WARNING, "failed to get IP for \"%s\": %s",
|
||||
_ip.c_str(), ec.message().c_str());
|
||||
return;
|
||||
}
|
||||
try {
|
||||
_tcpSocket = asio::ip::tcp::socket(_ctx);
|
||||
_tcpSocket.connect(endpoints.begin()->endpoint());
|
||||
} catch (const std::exception &e) {
|
||||
blog(LOG_WARNING, "failed to connect to TCP %s %d: %s",
|
||||
_ip.c_str(), _port.GetValue(), e.what());
|
||||
}
|
||||
}
|
||||
|
||||
void MacroActionOSC::CheckReconnect()
|
||||
{
|
||||
if (_protocol == Protocol::TCP &&
|
||||
(_reconnect || !_tcpSocket.is_open())) {
|
||||
TCPReconnect();
|
||||
}
|
||||
|
||||
if (_protocol == Protocol::UDP &&
|
||||
(_reconnect || !_udpSocket.is_open())) {
|
||||
UDPReconnect();
|
||||
}
|
||||
}
|
||||
|
||||
bool MacroActionOSC::PerformAction()
|
||||
{
|
||||
auto buffer = _message.GetBuffer();
|
||||
if (!buffer.has_value()) {
|
||||
blog(LOG_WARNING, "failed to create or fill OSC buffer!");
|
||||
return true;
|
||||
}
|
||||
|
||||
CheckReconnect();
|
||||
|
||||
if (_protocol == Protocol::TCP &&
|
||||
(_reconnect || !_tcpSocket.is_open())) {
|
||||
TCPReconnect();
|
||||
}
|
||||
|
||||
if (_protocol == Protocol::UDP &&
|
||||
(_reconnect || !_udpSocket.is_open())) {
|
||||
UDPReconnect();
|
||||
}
|
||||
|
||||
auto rawMessage = asio::buffer(*buffer);
|
||||
|
||||
switch (_protocol) {
|
||||
case MacroActionOSC::Protocol::TCP:
|
||||
SendOSCTCPMessage(rawMessage);
|
||||
break;
|
||||
case MacroActionOSC::Protocol::UDP:
|
||||
SendOSCUDPMessage(rawMessage);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void MacroActionOSC::LogAction() const
|
||||
{
|
||||
vblog(LOG_INFO, "sending OSC message '%s' to %s %s %d",
|
||||
_message.ToString().c_str(),
|
||||
_protocol == Protocol::UDP ? "UDP" : "TCP", _ip.c_str(),
|
||||
_port.GetValue());
|
||||
}
|
||||
|
||||
bool MacroActionOSC::Save(obs_data_t *obj) const
|
||||
{
|
||||
MacroAction::Save(obj);
|
||||
obs_data_set_int(obj, "protocol", static_cast<int>(_protocol));
|
||||
_ip.Save(obj, "ip");
|
||||
_port.Save(obj, "port");
|
||||
_message.Save(obj);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MacroActionOSC::Load(obs_data_t *obj)
|
||||
{
|
||||
MacroAction::Load(obj);
|
||||
_protocol = static_cast<Protocol>(obs_data_get_int(obj, "protocol"));
|
||||
_ip.Load(obj, "ip");
|
||||
_port.Load(obj, "port");
|
||||
_message.Load(obj);
|
||||
return true;
|
||||
}
|
||||
|
||||
void MacroActionOSC::SetProtocol(Protocol p)
|
||||
{
|
||||
_protocol = p;
|
||||
_reconnect = true;
|
||||
}
|
||||
|
||||
void MacroActionOSC::SetIP(const std::string &ip)
|
||||
{
|
||||
_ip = ip;
|
||||
_reconnect = true;
|
||||
}
|
||||
|
||||
void MacroActionOSC::SetPortNr(IntVariable port)
|
||||
{
|
||||
_port = port;
|
||||
_reconnect = true;
|
||||
}
|
||||
|
||||
static void populateProtocolSelection(QComboBox *list)
|
||||
{
|
||||
list->addItem("TCP");
|
||||
list->addItem("UDP");
|
||||
}
|
||||
|
||||
MacroActionOSCEdit::MacroActionOSCEdit(
|
||||
QWidget *parent, std::shared_ptr<MacroActionOSC> entryData)
|
||||
: QWidget(parent),
|
||||
_protocol(new QComboBox(this)),
|
||||
_ip(new VariableLineEdit(this)),
|
||||
_port(new VariableSpinBox(this)),
|
||||
_message(new OSCMessageEdit(this))
|
||||
{
|
||||
populateProtocolSelection(_protocol);
|
||||
_port->setMaximum(65535);
|
||||
|
||||
auto networkGroup =
|
||||
new QGroupBox(obs_module_text("AdvSceneSwitcher.osc.network"));
|
||||
auto networkLayout = new QGridLayout;
|
||||
int row = 0;
|
||||
networkLayout->addWidget(
|
||||
new QLabel(obs_module_text(
|
||||
"AdvSceneSwitcher.osc.network.protocol")),
|
||||
row, 0);
|
||||
networkLayout->addWidget(_protocol, row, 1);
|
||||
++row;
|
||||
networkLayout->addWidget(
|
||||
new QLabel(obs_module_text(
|
||||
"AdvSceneSwitcher.osc.network.address")),
|
||||
row, 0);
|
||||
networkLayout->addWidget(_ip, row, 1);
|
||||
++row;
|
||||
networkLayout->addWidget(new QLabel(obs_module_text(
|
||||
"AdvSceneSwitcher.osc.network.port")),
|
||||
row, 0);
|
||||
networkLayout->addWidget(_port, row, 1);
|
||||
networkGroup->setLayout(networkLayout);
|
||||
|
||||
auto messageGroup =
|
||||
new QGroupBox(obs_module_text("AdvSceneSwitcher.osc.message"));
|
||||
auto messageLayout = new QHBoxLayout();
|
||||
messageLayout->addWidget(_message);
|
||||
messageGroup->setLayout(messageLayout);
|
||||
|
||||
auto mainLayout = new QVBoxLayout;
|
||||
mainLayout->addWidget(networkGroup);
|
||||
mainLayout->addWidget(messageGroup);
|
||||
setLayout(mainLayout);
|
||||
|
||||
QWidget::connect(_ip, SIGNAL(editingFinished()), this,
|
||||
SLOT(IpChanged()));
|
||||
QWidget::connect(_protocol, SIGNAL(currentIndexChanged(int)), this,
|
||||
SLOT(ProtocolChanged(int)));
|
||||
QWidget::connect(
|
||||
_port,
|
||||
SIGNAL(NumberVariableChanged(const NumberVariable<int> &)),
|
||||
this, SLOT(PortChanged(const NumberVariable<int> &)));
|
||||
QWidget::connect(_message, SIGNAL(MessageChanged(const OSCMessage &)),
|
||||
this, SLOT(MessageChanged(const OSCMessage &)));
|
||||
|
||||
_entryData = entryData;
|
||||
UpdateEntryData();
|
||||
_loading = false;
|
||||
}
|
||||
|
||||
void MacroActionOSCEdit::UpdateEntryData()
|
||||
{
|
||||
if (!_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
_protocol->setCurrentIndex(static_cast<int>(_entryData->GetProtocol()));
|
||||
_ip->setText(_entryData->GetIP());
|
||||
_port->SetValue(_entryData->GetPortNr());
|
||||
_message->SetMessage(_entryData->_message);
|
||||
|
||||
adjustSize();
|
||||
updateGeometry();
|
||||
}
|
||||
|
||||
void MacroActionOSCEdit::IpChanged()
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->SetIP(_ip->text().toStdString());
|
||||
}
|
||||
|
||||
void MacroActionOSCEdit::ProtocolChanged(int value)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->SetProtocol(static_cast<MacroActionOSC::Protocol>(value));
|
||||
}
|
||||
|
||||
void MacroActionOSCEdit::PortChanged(const NumberVariable<int> &value)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->SetPortNr(value);
|
||||
}
|
||||
|
||||
void MacroActionOSCEdit::MessageChanged(const OSCMessage &m)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_message = m;
|
||||
|
||||
adjustSize();
|
||||
updateGeometry();
|
||||
}
|
||||
|
||||
} // namespace advss
|
||||
94
src/macro-core/macro-action-osc.hpp
Normal file
94
src/macro-core/macro-action-osc.hpp
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
#pragma once
|
||||
#include "macro-action-edit.hpp"
|
||||
#include "osc-helpers.hpp"
|
||||
|
||||
#include <memory>
|
||||
#include <asio.hpp>
|
||||
|
||||
namespace advss {
|
||||
|
||||
class MacroActionOSC : public MacroAction {
|
||||
public:
|
||||
MacroActionOSC(Macro *m);
|
||||
bool PerformAction();
|
||||
void LogAction() const;
|
||||
bool Save(obs_data_t *obj) const;
|
||||
bool Load(obs_data_t *obj);
|
||||
std::string GetId() const { return id; };
|
||||
static std::shared_ptr<MacroAction> Create(Macro *m)
|
||||
{
|
||||
return std::make_shared<MacroActionOSC>(m);
|
||||
}
|
||||
|
||||
enum class Protocol {
|
||||
TCP,
|
||||
UDP,
|
||||
};
|
||||
|
||||
void SetProtocol(Protocol);
|
||||
Protocol GetProtocol() const { return _protocol; }
|
||||
void SetIP(const std::string &);
|
||||
StringVariable GetIP() const { return _ip; }
|
||||
void SetPortNr(IntVariable);
|
||||
IntVariable GetPortNr() { return _port; }
|
||||
|
||||
OSCMessage _message;
|
||||
|
||||
private:
|
||||
void SendOSCTCPMessage(const asio::mutable_buffer &);
|
||||
void SendOSCUDPMessage(const asio::mutable_buffer &);
|
||||
|
||||
void CheckReconnect();
|
||||
void TCPReconnect();
|
||||
void UDPReconnect();
|
||||
|
||||
Protocol _protocol = Protocol::UDP;
|
||||
StringVariable _ip = "localhost";
|
||||
IntVariable _port = 12345;
|
||||
bool _reconnect = true;
|
||||
|
||||
asio::io_context _ctx;
|
||||
asio::ip::tcp::socket _tcpSocket;
|
||||
asio::ip::udp::socket _udpSocket;
|
||||
asio::ip::udp::endpoint _updEndpoint;
|
||||
|
||||
static bool _registered;
|
||||
static const std::string id;
|
||||
};
|
||||
|
||||
class MacroActionOSCEdit : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
MacroActionOSCEdit(QWidget *parent,
|
||||
std::shared_ptr<MacroActionOSC> entryData = nullptr);
|
||||
void UpdateEntryData();
|
||||
static QWidget *Create(QWidget *parent,
|
||||
std::shared_ptr<MacroAction> action)
|
||||
{
|
||||
return new MacroActionOSCEdit(
|
||||
parent,
|
||||
std::dynamic_pointer_cast<MacroActionOSC>(action));
|
||||
}
|
||||
|
||||
private slots:
|
||||
void IpChanged();
|
||||
void MessageChanged(const OSCMessage &);
|
||||
void ProtocolChanged(int);
|
||||
void PortChanged(const NumberVariable<int> &value);
|
||||
|
||||
signals:
|
||||
void HeaderInfoChanged(const QString &);
|
||||
|
||||
protected:
|
||||
std::shared_ptr<MacroActionOSC> _entryData;
|
||||
|
||||
private:
|
||||
QComboBox *_protocol;
|
||||
VariableLineEdit *_ip;
|
||||
VariableSpinBox *_port;
|
||||
OSCMessageEdit *_message;
|
||||
bool _loading = true;
|
||||
};
|
||||
|
||||
} // namespace advss
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
#include "mouse-wheel-guard.hpp"
|
||||
#include "osc-helpers.hpp"
|
||||
|
||||
#include <QEvent>
|
||||
#include <QScrollBar>
|
||||
|
|
@ -23,12 +24,15 @@ bool MouseWheelWidgetAdjustmentGuard::eventFilter(QObject *o, QEvent *e)
|
|||
|
||||
void PreventMouseWheelAdjustWithoutFocus(QWidget *w)
|
||||
{
|
||||
w->setFocusPolicy(Qt::StrongFocus);
|
||||
// Ignore QScrollBar as there is no danger of accidentally modifying anything
|
||||
// and long expanded QComboBox would be difficult to interact with otherwise.
|
||||
if (qobject_cast<QScrollBar *>(w)) {
|
||||
// Ignore OSCMessageElementEdit to allow OSCMessageEdit list to up update
|
||||
// current index correctly.
|
||||
if (qobject_cast<QScrollBar *>(w) ||
|
||||
qobject_cast<OSCMessageElementEdit *>(w)) {
|
||||
return;
|
||||
}
|
||||
w->setFocusPolicy(Qt::StrongFocus);
|
||||
w->installEventFilter(new MouseWheelWidgetAdjustmentGuard(w));
|
||||
}
|
||||
|
||||
|
|
|
|||
689
src/utils/osc-helpers.cpp
Normal file
689
src/utils/osc-helpers.cpp
Normal file
|
|
@ -0,0 +1,689 @@
|
|||
#include "osc-helpers.hpp"
|
||||
#include "obs-module-helper.hpp"
|
||||
#include "utility.hpp"
|
||||
|
||||
#include <string.h>
|
||||
#include <QGroupBox>
|
||||
|
||||
namespace advss {
|
||||
|
||||
std::unordered_map<size_t, OSCMessageElement::TypeInfo>
|
||||
OSCMessageElement::_typeNames = {
|
||||
{std::variant_npos,
|
||||
{"AdvSceneSwitcher.osc.message.type.none", "-"}},
|
||||
{0, {"AdvSceneSwitcher.osc.message.type.int", "i"}},
|
||||
{1, {"AdvSceneSwitcher.osc.message.type.float", "f"}},
|
||||
{2, {"AdvSceneSwitcher.osc.message.type.string", "s"}},
|
||||
{3, {"AdvSceneSwitcher.osc.message.type.binaryBlob", "b"}},
|
||||
{4, {"AdvSceneSwitcher.osc.message.type.true", "T"}},
|
||||
{5, {"AdvSceneSwitcher.osc.message.type.false", "F"}},
|
||||
{6, {"AdvSceneSwitcher.osc.message.type.infinity", "I"}},
|
||||
{7, {"AdvSceneSwitcher.osc.message.type.null", "N"}},
|
||||
};
|
||||
|
||||
// Based on https://github.com/mhroth/tinyosc
|
||||
struct FillMessageElementBufferVisitor {
|
||||
std::vector<char> &buffer;
|
||||
uint32_t &curOffset;
|
||||
|
||||
bool success = false;
|
||||
|
||||
void operator()(const StringVariable &value)
|
||||
{
|
||||
std::string string = value;
|
||||
int length = (int)strlen(string.c_str());
|
||||
if (curOffset + length >= buffer.size()) {
|
||||
buffer.resize(curOffset + length + 1);
|
||||
}
|
||||
strncpy(buffer.data() + curOffset, string.c_str(),
|
||||
buffer.size() - curOffset - length);
|
||||
curOffset = (curOffset + 4 + length) & ~0x3;
|
||||
success = true;
|
||||
}
|
||||
void operator()(const IntVariable &value)
|
||||
{
|
||||
if (curOffset + 4 > buffer.size()) {
|
||||
buffer.resize(curOffset + 4);
|
||||
}
|
||||
int32_t k = value;
|
||||
*((uint32_t *)(buffer.data() + curOffset)) = htonl(k);
|
||||
curOffset += 4;
|
||||
success = true;
|
||||
}
|
||||
void operator()(const DoubleVariable &value)
|
||||
{
|
||||
if (curOffset + 4 > buffer.size()) {
|
||||
buffer.resize(curOffset + 4);
|
||||
}
|
||||
const float f = value;
|
||||
*((uint32_t *)(buffer.data() + curOffset)) =
|
||||
htonl(*((uint32_t *)&f));
|
||||
curOffset += 4;
|
||||
success = true;
|
||||
}
|
||||
void operator()(const OSCBlob &value)
|
||||
{
|
||||
if (curOffset + 4 > buffer.size()) {
|
||||
buffer.resize(curOffset + 4);
|
||||
}
|
||||
auto blob = value.GetBinary();
|
||||
if (!blob.has_value()) {
|
||||
return;
|
||||
}
|
||||
if (curOffset + 4 + blob->size() > buffer.size()) {
|
||||
buffer.resize(curOffset + 4 + blob->size());
|
||||
}
|
||||
*((uint32_t *)(buffer.data() + curOffset)) =
|
||||
htonl(blob->size());
|
||||
curOffset += 4;
|
||||
memcpy(buffer.data() + curOffset, blob->data(), blob->size());
|
||||
curOffset = (curOffset + 3 + blob->size()) & ~0x3;
|
||||
success = true;
|
||||
}
|
||||
void operator()(const OSCTrue &) { success = true; }
|
||||
void operator()(const OSCFalse &) { success = true; }
|
||||
void operator()(const OSCInfinity &) { success = true; }
|
||||
void operator()(const OSCNull &) { success = true; }
|
||||
};
|
||||
|
||||
OSCBlob::OSCBlob(const std::string &stringRepresentation)
|
||||
: _stringRep(stringRepresentation)
|
||||
{
|
||||
}
|
||||
|
||||
void OSCBlob::SetStringRepresentation(const StringVariable &s)
|
||||
{
|
||||
_stringRep = s;
|
||||
}
|
||||
|
||||
std::string OSCBlob::GetStringRepresentation() const
|
||||
{
|
||||
return _stringRep;
|
||||
}
|
||||
|
||||
std::optional<std::vector<char>> OSCBlob::GetBinary() const
|
||||
{
|
||||
std::vector<char> bytes;
|
||||
std::string hexString = _stringRep;
|
||||
|
||||
for (std::size_t i = 2; i < hexString.size(); i += 4) {
|
||||
try {
|
||||
auto byteString = hexString.substr(i, 2);
|
||||
int value = std::stoi(byteString, nullptr, 16);
|
||||
bytes.push_back(static_cast<char>(value));
|
||||
} catch (const std::exception &e) {
|
||||
blog(LOG_WARNING,
|
||||
"failed to convert hex \"%s\" to binary: %s",
|
||||
hexString.c_str(), e.what());
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
return bytes;
|
||||
}
|
||||
|
||||
void OSCBlob::Save(obs_data_t *obj, const char *name) const
|
||||
{
|
||||
_stringRep.Save(obj, name);
|
||||
}
|
||||
|
||||
void OSCBlob::Load(obs_data_t *obj, const char *name)
|
||||
{
|
||||
_stringRep.Load(obj, name);
|
||||
}
|
||||
|
||||
void OSCTrue::Save(obs_data_t *obj, const char *name) const
|
||||
{
|
||||
obs_data_set_bool(obj, name, true);
|
||||
}
|
||||
|
||||
void OSCFalse::Save(obs_data_t *obj, const char *name) const
|
||||
{
|
||||
obs_data_set_bool(obj, name, true);
|
||||
}
|
||||
|
||||
void OSCInfinity::Save(obs_data_t *obj, const char *name) const
|
||||
{
|
||||
obs_data_set_bool(obj, name, true);
|
||||
}
|
||||
|
||||
void OSCNull::Save(obs_data_t *obj, const char *name) const
|
||||
{
|
||||
obs_data_set_bool(obj, name, true);
|
||||
}
|
||||
|
||||
std::optional<std::vector<char>> OSCMessage::GetBuffer() const
|
||||
{
|
||||
if (std::string(_address).empty()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
std::vector<char> buffer(128, 0);
|
||||
uint32_t currentOffset = (uint32_t)strlen(_address.c_str());
|
||||
if (currentOffset > buffer.size()) {
|
||||
buffer.resize(buffer.size() * 2);
|
||||
}
|
||||
strncpy(buffer.data(), _address.c_str(), buffer.size());
|
||||
currentOffset = (currentOffset + 4) & ~0x3;
|
||||
|
||||
std::string typeTags;
|
||||
for (const auto &e : _elements) {
|
||||
typeTags += e.GetTypeTag();
|
||||
}
|
||||
|
||||
buffer.at(currentOffset++) = ',';
|
||||
int length = (int)strlen(typeTags.c_str());
|
||||
if (currentOffset + length >= buffer.size()) {
|
||||
buffer.resize(buffer.size() * 2);
|
||||
}
|
||||
strncpy(buffer.data() + currentOffset, typeTags.c_str(),
|
||||
buffer.size() - currentOffset - length);
|
||||
currentOffset = (currentOffset + 4 + length) & ~0x3;
|
||||
|
||||
for (const auto &e : _elements) {
|
||||
const auto &value = e._value;
|
||||
FillMessageElementBufferVisitor visitor{buffer, currentOffset};
|
||||
std::visit(visitor, value);
|
||||
if (!visitor.success) {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
buffer.resize(currentOffset);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
const char *OSCMessageElement::GetTypeTag() const
|
||||
{
|
||||
return GetTypeTag(*this);
|
||||
}
|
||||
|
||||
const char *OSCMessageElement::GetTypeName() const
|
||||
{
|
||||
return GetTypeName(*this);
|
||||
}
|
||||
|
||||
const char *OSCMessageElement::GetTypeTag(const OSCMessageElement &element)
|
||||
{
|
||||
return _typeNames.at(element._value.index()).tag;
|
||||
}
|
||||
|
||||
const char *OSCMessageElement::GetTypeName(const OSCMessageElement &element)
|
||||
{
|
||||
return obs_module_text(
|
||||
_typeNames.at(element._value.index()).localizedName);
|
||||
}
|
||||
|
||||
void OSCMessageElement::Save(obs_data_t *obj) const
|
||||
{
|
||||
std::visit(
|
||||
[obj](auto &&arg) {
|
||||
using T = std::decay_t<decltype(arg)>;
|
||||
if constexpr (std::is_same_v<T, StringVariable>) {
|
||||
arg.Save(obj, "strValue");
|
||||
} else if constexpr (std::is_same_v<T, IntVariable>) {
|
||||
arg.Save(obj, "intValue");
|
||||
} else if constexpr (std::is_same_v<T, DoubleVariable>) {
|
||||
arg.Save(obj, "floatValue");
|
||||
} else if constexpr (std::is_same_v<T, OSCBlob>) {
|
||||
arg.Save(obj, "binaryValue");
|
||||
} else if constexpr (std::is_same_v<T, OSCTrue>) {
|
||||
arg.Save(obj, "trueValue");
|
||||
} else if constexpr (std::is_same_v<T, OSCFalse>) {
|
||||
arg.Save(obj, "falseValue");
|
||||
} else if constexpr (std::is_same_v<T, OSCInfinity>) {
|
||||
arg.Save(obj, "infiniteValue");
|
||||
} else if constexpr (std::is_same_v<T, OSCNull>) {
|
||||
arg.Save(obj, "nullValue");
|
||||
} else {
|
||||
blog(LOG_WARNING,
|
||||
"cannot save unkown OSCMessageElement");
|
||||
}
|
||||
},
|
||||
_value);
|
||||
}
|
||||
|
||||
void OSCMessageElement::Load(obs_data_t *obj)
|
||||
{
|
||||
if (obs_data_has_user_value(obj, "strValue")) {
|
||||
StringVariable string;
|
||||
string.Load(obj, "strValue");
|
||||
_value = string;
|
||||
} else if (obs_data_has_user_value(obj, "intValue")) {
|
||||
NumberVariable<int> intValue;
|
||||
intValue.Load(obj, "intValue");
|
||||
_value = intValue;
|
||||
} else if (obs_data_has_user_value(obj, "floatValue")) {
|
||||
NumberVariable<double> floatValue;
|
||||
floatValue.Load(obj, "floatValue");
|
||||
_value = floatValue;
|
||||
} else if (obs_data_has_user_value(obj, "binaryValue")) {
|
||||
OSCBlob binValue;
|
||||
binValue.Load(obj, "binaryValue");
|
||||
_value = binValue;
|
||||
} else if (obs_data_has_user_value(obj, "trueValue")) {
|
||||
_value = OSCTrue();
|
||||
} else if (obs_data_has_user_value(obj, "falseValue")) {
|
||||
_value = OSCFalse();
|
||||
} else if (obs_data_has_user_value(obj, "OSCInfinite")) {
|
||||
_value = OSCInfinity();
|
||||
} else if (obs_data_has_user_value(obj, "nullValue")) {
|
||||
_value = OSCNull();
|
||||
} else {
|
||||
blog(LOG_WARNING, "cannot load unkown OSCMessageElement");
|
||||
}
|
||||
}
|
||||
|
||||
std::string OSCMessageElement::ToString() const
|
||||
{
|
||||
return std::visit(
|
||||
[](auto &&arg) -> std::string {
|
||||
using T = std::decay_t<decltype(arg)>;
|
||||
if constexpr (std::is_same_v<T, StringVariable>) {
|
||||
return arg;
|
||||
} else if constexpr (std::is_same_v<T, OSCBlob> ||
|
||||
std::is_same_v<T, OSCTrue> ||
|
||||
std::is_same_v<T, OSCFalse> ||
|
||||
std::is_same_v<T, OSCInfinity> ||
|
||||
std::is_same_v<T, OSCNull>) {
|
||||
return arg.GetStringRepresentation();
|
||||
} else {
|
||||
return std::to_string(arg.GetValue());
|
||||
}
|
||||
},
|
||||
_value);
|
||||
}
|
||||
|
||||
void OSCMessage::Save(obs_data_t *obj) const
|
||||
{
|
||||
auto data = obs_data_create();
|
||||
_address.Save(data, "address");
|
||||
auto elements = obs_data_array_create();
|
||||
for (const auto &e : _elements) {
|
||||
auto array_obj = obs_data_create();
|
||||
e.Save(array_obj);
|
||||
obs_data_array_push_back(elements, array_obj);
|
||||
obs_data_release(array_obj);
|
||||
}
|
||||
obs_data_set_array(data, "elements", elements);
|
||||
obs_data_set_obj(obj, "oscMessage", data);
|
||||
obs_data_array_release(elements);
|
||||
obs_data_release(data);
|
||||
}
|
||||
|
||||
void OSCMessage::Load(obs_data_t *obj)
|
||||
{
|
||||
|
||||
auto data = obs_data_get_obj(obj, "oscMessage");
|
||||
_address.Load(data, "address");
|
||||
_elements.clear();
|
||||
auto elements = obs_data_get_array(data, "elements");
|
||||
size_t count = obs_data_array_count(elements);
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
auto array_obj = obs_data_array_item(elements, i);
|
||||
OSCMessageElement e;
|
||||
e.Load(array_obj);
|
||||
_elements.push_back(e);
|
||||
obs_data_release(array_obj);
|
||||
}
|
||||
obs_data_array_release(elements);
|
||||
obs_data_release(data);
|
||||
}
|
||||
|
||||
std::string OSCMessage::ToString() const
|
||||
{
|
||||
std::string res = "address: " + std::string(_address) + " message: ";
|
||||
for (const auto &e : _elements) {
|
||||
res += "[" + e.ToString() + "]";
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
OSCMessageElementEdit::OSCMessageElementEdit(QWidget *parent)
|
||||
: QWidget(parent),
|
||||
_type(new QComboBox(this)),
|
||||
_intValue(new VariableSpinBox(this)),
|
||||
_doubleValue(new VariableDoubleSpinBox(this)),
|
||||
_text(new VariableLineEdit(this)),
|
||||
_binaryText(new VariableLineEdit(this))
|
||||
{
|
||||
installEventFilter(this);
|
||||
|
||||
_intValue->setMinimum(INT_MIN);
|
||||
_intValue->setMaximum(INT_MAX);
|
||||
_doubleValue->setMinimum(-9999999999);
|
||||
_doubleValue->setMaximum(9999999999);
|
||||
_doubleValue->setDecimals(10);
|
||||
|
||||
_intValue->hide();
|
||||
_doubleValue->hide();
|
||||
_text->hide();
|
||||
_binaryText->hide();
|
||||
|
||||
for (int i = 0; i < OSCMessageElement::_typeNames.size() - 1; i++) {
|
||||
_type->addItem(obs_module_text(
|
||||
OSCMessageElement::_typeNames.at(i).localizedName));
|
||||
}
|
||||
_type->setCurrentIndex(0);
|
||||
|
||||
QWidget::connect(_type, SIGNAL(currentIndexChanged(int)), this,
|
||||
SLOT(TypeChanged(int)));
|
||||
QWidget::connect(
|
||||
_doubleValue,
|
||||
SIGNAL(NumberVariableChanged(const NumberVariable<double> &)),
|
||||
this, SLOT(DoubleChanged(const NumberVariable<double> &)));
|
||||
QWidget::connect(
|
||||
_intValue,
|
||||
SIGNAL(NumberVariableChanged(const NumberVariable<int> &)),
|
||||
this, SLOT(IntChanged(const NumberVariable<int> &)));
|
||||
QWidget::connect(_text, SIGNAL(editingFinished()), this,
|
||||
SLOT(TextChanged()));
|
||||
QWidget::connect(_binaryText, SIGNAL(editingFinished()), this,
|
||||
SLOT(BinaryTextChanged()));
|
||||
|
||||
auto layout = new QHBoxLayout();
|
||||
layout->setContentsMargins(0, 0, 0, 0);
|
||||
layout->addWidget(_type, 1);
|
||||
layout->addWidget(_intValue, 4);
|
||||
layout->addWidget(_doubleValue, 4);
|
||||
layout->addWidget(_text, 4);
|
||||
layout->addWidget(_binaryText, 4);
|
||||
setLayout(layout);
|
||||
}
|
||||
|
||||
void OSCMessageElementEdit::SetMessageElement(const OSCMessageElement &element)
|
||||
{
|
||||
const QSignalBlocker b(this);
|
||||
_type->setCurrentText(element.GetTypeName());
|
||||
SetVisibility(element);
|
||||
|
||||
if (std::holds_alternative<StringVariable>(element._value)) {
|
||||
_text->setText(std::get<StringVariable>(element._value));
|
||||
} else if (std::holds_alternative<IntVariable>(element._value)) {
|
||||
_intValue->SetValue(std::get<IntVariable>(element._value));
|
||||
} else if (std::holds_alternative<DoubleVariable>(element._value)) {
|
||||
_doubleValue->SetValue(
|
||||
std::get<DoubleVariable>(element._value));
|
||||
} else if (std::holds_alternative<OSCBlob>(element._value)) {
|
||||
_binaryText->setText(std::get<OSCBlob>(element._value)
|
||||
.GetStringRepresentation());
|
||||
}
|
||||
}
|
||||
|
||||
bool OSCMessageElementEdit::eventFilter(QObject *obj, QEvent *event)
|
||||
{
|
||||
if (event->type() == QEvent::MouseButtonPress ||
|
||||
event->type() == QEvent::MouseButtonDblClick) {
|
||||
emit Focussed();
|
||||
}
|
||||
|
||||
return QWidget::eventFilter(obj, event);
|
||||
}
|
||||
void OSCMessageElementEdit::showEvent(QShowEvent *event)
|
||||
{
|
||||
QWidget::showEvent(event);
|
||||
|
||||
QWidgetList childWidgets = findChildren<QWidget *>();
|
||||
for (QWidget *childWidget : childWidgets) {
|
||||
childWidget->installEventFilter(this);
|
||||
}
|
||||
}
|
||||
|
||||
void OSCMessageElementEdit::DoubleChanged(const DoubleVariable &value)
|
||||
{
|
||||
emit ElementValueChanged(OSCMessageElement(value));
|
||||
}
|
||||
|
||||
void OSCMessageElementEdit::IntChanged(const IntVariable &value)
|
||||
{
|
||||
emit ElementValueChanged(OSCMessageElement(value));
|
||||
}
|
||||
|
||||
void OSCMessageElementEdit::TextChanged()
|
||||
{
|
||||
emit ElementValueChanged(StringVariable(_text->text().toStdString()));
|
||||
}
|
||||
|
||||
void OSCMessageElementEdit::BinaryTextChanged()
|
||||
{
|
||||
emit ElementValueChanged(OSCBlob(_binaryText->text().toStdString()));
|
||||
}
|
||||
|
||||
void OSCMessageElementEdit::SetVisibility(const OSCMessageElement &element)
|
||||
{
|
||||
_intValue->hide();
|
||||
_doubleValue->hide();
|
||||
_text->hide();
|
||||
_binaryText->hide();
|
||||
|
||||
if (std::holds_alternative<StringVariable>(element._value)) {
|
||||
_text->show();
|
||||
} else if (std::holds_alternative<IntVariable>(element._value)) {
|
||||
_intValue->show();
|
||||
} else if (std::holds_alternative<DoubleVariable>(element._value)) {
|
||||
_doubleValue->show();
|
||||
} else if (std::holds_alternative<OSCBlob>(element._value)) {
|
||||
_binaryText->show();
|
||||
}
|
||||
}
|
||||
|
||||
void OSCMessageElementEdit::TypeChanged(int idx)
|
||||
{
|
||||
OSCMessageElement element;
|
||||
if (idx == 0) {
|
||||
element = OSCMessageElement(IntVariable(0));
|
||||
} else if (idx == 1) {
|
||||
element = OSCMessageElement(DoubleVariable(0.0));
|
||||
} else if (idx == 2) {
|
||||
element = OSCMessageElement("value");
|
||||
} else if (idx == 3) {
|
||||
element = OSCMessageElement(OSCBlob("\\x00\\x01\\x02\\x03"));
|
||||
} else if (idx == 4) {
|
||||
element = OSCMessageElement(OSCTrue());
|
||||
} else if (idx == 5) {
|
||||
element = OSCMessageElement(OSCFalse());
|
||||
} else if (idx == 6) {
|
||||
element = OSCMessageElement(OSCInfinity());
|
||||
} else if (idx == 7) {
|
||||
element = OSCMessageElement(OSCNull());
|
||||
}
|
||||
SetVisibility(element);
|
||||
SetMessageElement(element);
|
||||
emit ElementValueChanged(element);
|
||||
}
|
||||
|
||||
OSCMessageEdit::OSCMessageEdit(QWidget *parent)
|
||||
: QWidget(parent),
|
||||
_address(new VariableLineEdit(this)),
|
||||
_elements(new QListWidget()),
|
||||
_add(new QPushButton()),
|
||||
_remove(new QPushButton()),
|
||||
_up(new QPushButton()),
|
||||
_down(new QPushButton())
|
||||
{
|
||||
_elements->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
||||
_elements->setAutoScroll(false);
|
||||
|
||||
_add->setMaximumWidth(22);
|
||||
_add->setProperty("themeID",
|
||||
QVariant(QString::fromUtf8("addIconSmall")));
|
||||
_add->setFlat(true);
|
||||
_remove->setMaximumWidth(22);
|
||||
_remove->setProperty("themeID",
|
||||
QVariant(QString::fromUtf8("removeIconSmall")));
|
||||
_remove->setFlat(true);
|
||||
_up->setMaximumWidth(22);
|
||||
_up->setProperty("themeID",
|
||||
QVariant(QString::fromUtf8("upArrowIconSmall")));
|
||||
_up->setFlat(true);
|
||||
_down->setMaximumWidth(22);
|
||||
_down->setProperty("themeID",
|
||||
QVariant(QString::fromUtf8("downArrowIconSmall")));
|
||||
_down->setFlat(true);
|
||||
|
||||
QWidget::connect(_address, SIGNAL(editingFinished()), this,
|
||||
SLOT(AddressChanged()));
|
||||
QWidget::connect(_add, SIGNAL(clicked()), this, SLOT(Add()));
|
||||
QWidget::connect(_remove, SIGNAL(clicked()), this, SLOT(Remove()));
|
||||
QWidget::connect(_up, SIGNAL(clicked()), this, SLOT(Up()));
|
||||
QWidget::connect(_down, SIGNAL(clicked()), this, SLOT(Down()));
|
||||
|
||||
auto controlsLayout = new QHBoxLayout();
|
||||
controlsLayout->addWidget(_add);
|
||||
controlsLayout->addWidget(_remove);
|
||||
QFrame *line = new QFrame();
|
||||
line->setFrameShape(QFrame::VLine);
|
||||
line->setFrameShadow(QFrame::Sunken);
|
||||
controlsLayout->addWidget(line);
|
||||
controlsLayout->addWidget(_up);
|
||||
controlsLayout->addWidget(_down);
|
||||
controlsLayout->addStretch();
|
||||
|
||||
auto layout = new QVBoxLayout;
|
||||
layout->setContentsMargins(0, 0, 0, 0);
|
||||
layout->addWidget(_address);
|
||||
layout->addWidget(_elements);
|
||||
layout->addLayout(controlsLayout);
|
||||
setLayout(layout);
|
||||
}
|
||||
|
||||
void OSCMessageEdit::InsertElement(const OSCMessageElement &element)
|
||||
{
|
||||
auto item = new QListWidgetItem(_elements);
|
||||
_elements->addItem(item);
|
||||
auto elementEdit = new OSCMessageElementEdit(this);
|
||||
elementEdit->SetMessageElement(element);
|
||||
item->setSizeHint(elementEdit->minimumSizeHint());
|
||||
_elements->setItemWidget(item, elementEdit);
|
||||
QWidget::connect(elementEdit,
|
||||
SIGNAL(ElementValueChanged(const OSCMessageElement &)),
|
||||
this,
|
||||
SLOT(ElementValueChanged(const OSCMessageElement &)));
|
||||
QWidget::connect(elementEdit, SIGNAL(Focussed()), this,
|
||||
SLOT(ElementFocussed()));
|
||||
_currentSelection._elements.push_back(element);
|
||||
}
|
||||
|
||||
void OSCMessageEdit::SetMessage(const OSCMessage &message)
|
||||
{
|
||||
_address->setText(message._address);
|
||||
for (const auto &element : message._elements) {
|
||||
InsertElement(element);
|
||||
}
|
||||
_currentSelection = message;
|
||||
SetWidgetSize();
|
||||
}
|
||||
|
||||
void OSCMessageEdit::AddressChanged()
|
||||
{
|
||||
_currentSelection._address = _address->text().toStdString();
|
||||
emit MessageChanged(_currentSelection);
|
||||
}
|
||||
|
||||
void OSCMessageEdit::Add()
|
||||
{
|
||||
OSCMessageElement element;
|
||||
InsertElement(element);
|
||||
emit MessageChanged(_currentSelection);
|
||||
SetWidgetSize();
|
||||
}
|
||||
|
||||
void OSCMessageEdit::Remove()
|
||||
{
|
||||
auto item = _elements->currentItem();
|
||||
int idx = _elements->currentRow();
|
||||
if (!item || idx == -1) {
|
||||
return;
|
||||
}
|
||||
delete item;
|
||||
_currentSelection._elements.erase(_currentSelection._elements.begin() +
|
||||
idx);
|
||||
emit MessageChanged(_currentSelection);
|
||||
SetWidgetSize();
|
||||
}
|
||||
|
||||
void OSCMessageEdit::Up()
|
||||
{
|
||||
int idx = _elements->currentRow();
|
||||
if (!listMoveUp(_elements)) {
|
||||
return;
|
||||
}
|
||||
|
||||
iter_swap(_currentSelection._elements.begin() + idx,
|
||||
_currentSelection._elements.begin() + idx - 1);
|
||||
|
||||
emit MessageChanged(_currentSelection);
|
||||
SetWidgetSize();
|
||||
}
|
||||
|
||||
void OSCMessageEdit::Down()
|
||||
{
|
||||
int idx = _elements->currentRow();
|
||||
if (!listMoveDown(_elements)) {
|
||||
return;
|
||||
}
|
||||
|
||||
iter_swap(_currentSelection._elements.begin() + idx,
|
||||
_currentSelection._elements.begin() + idx + 1);
|
||||
|
||||
emit MessageChanged(_currentSelection);
|
||||
SetWidgetSize();
|
||||
}
|
||||
|
||||
static QListWidgetItem *getItemFromWidget(QListWidget *list, QWidget *widget)
|
||||
{
|
||||
for (int i = 0; i < list->count(); i++) {
|
||||
auto item = list->item(i);
|
||||
if (!item) {
|
||||
continue;
|
||||
}
|
||||
auto itemWidget = list->itemWidget(item);
|
||||
if (itemWidget == widget) {
|
||||
return item;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
int OSCMessageEdit::GetIndexOfSignal()
|
||||
{
|
||||
auto sender = this->sender();
|
||||
if (!sender) {
|
||||
return -1;
|
||||
}
|
||||
auto widget = qobject_cast<QWidget *>(sender);
|
||||
if (!widget) {
|
||||
return -1;
|
||||
}
|
||||
return _elements->row(getItemFromWidget(_elements, widget));
|
||||
}
|
||||
|
||||
void OSCMessageEdit::ElementFocussed()
|
||||
{
|
||||
int idx = GetIndexOfSignal();
|
||||
if (idx == -1) {
|
||||
return;
|
||||
}
|
||||
_elements->setCurrentRow(idx);
|
||||
}
|
||||
|
||||
void OSCMessageEdit::ElementValueChanged(const OSCMessageElement &element)
|
||||
{
|
||||
int idx = GetIndexOfSignal();
|
||||
if (idx == -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
_currentSelection._elements.at(idx) = element;
|
||||
_elements->setCurrentRow(idx);
|
||||
emit MessageChanged(_currentSelection);
|
||||
}
|
||||
|
||||
void OSCMessageEdit::SetWidgetSize()
|
||||
{
|
||||
SetHeightToContentHeight(_elements);
|
||||
adjustSize();
|
||||
updateGeometry();
|
||||
}
|
||||
|
||||
} // namespace advss
|
||||
168
src/utils/osc-helpers.hpp
Normal file
168
src/utils/osc-helpers.hpp
Normal file
|
|
@ -0,0 +1,168 @@
|
|||
#pragma once
|
||||
#include "variable-string.hpp"
|
||||
#include "variable-number.hpp"
|
||||
#include "variable-line-edit.hpp"
|
||||
#include "variable-spinbox.hpp"
|
||||
|
||||
#include <variant>
|
||||
#include <unordered_map>
|
||||
#include <QListWidget>
|
||||
|
||||
namespace advss {
|
||||
|
||||
class OSCBlob {
|
||||
public:
|
||||
OSCBlob() = default;
|
||||
OSCBlob(const std::string &stringRepresentation);
|
||||
void SetStringRepresentation(const StringVariable &);
|
||||
std::string GetStringRepresentation() const;
|
||||
std::optional<std::vector<char>> GetBinary() const;
|
||||
void Save(obs_data_t *obj, const char *name) const;
|
||||
void Load(obs_data_t *obj, const char *name);
|
||||
|
||||
private:
|
||||
StringVariable _stringRep;
|
||||
};
|
||||
|
||||
class OSCTrue {
|
||||
public:
|
||||
void Save(obs_data_t *obj, const char *name) const;
|
||||
std::string GetStringRepresentation() const { return "true"; }
|
||||
};
|
||||
|
||||
class OSCFalse {
|
||||
public:
|
||||
void Save(obs_data_t *obj, const char *name) const;
|
||||
std::string GetStringRepresentation() const { return "false"; }
|
||||
};
|
||||
|
||||
class OSCInfinity {
|
||||
public:
|
||||
void Save(obs_data_t *obj, const char *name) const;
|
||||
std::string GetStringRepresentation() const { return "infinity"; }
|
||||
};
|
||||
|
||||
class OSCNull {
|
||||
public:
|
||||
void Save(obs_data_t *obj, const char *name) const;
|
||||
std::string GetStringRepresentation() const { return "null"; }
|
||||
};
|
||||
|
||||
class OSCMessageElement {
|
||||
public:
|
||||
OSCMessageElement() = default;
|
||||
OSCMessageElement(const StringVariable &v) : _value(v) {}
|
||||
OSCMessageElement(const IntVariable &v) : _value(v) {}
|
||||
OSCMessageElement(const DoubleVariable &v) : _value(v) {}
|
||||
OSCMessageElement(const OSCBlob &v) : _value(v) {}
|
||||
OSCMessageElement(const OSCTrue &v) : _value(v) {}
|
||||
OSCMessageElement(const OSCFalse &v) : _value(v) {}
|
||||
OSCMessageElement(const OSCInfinity &v) : _value(v) {}
|
||||
OSCMessageElement(const OSCNull &v) : _value(v) {}
|
||||
|
||||
void Save(obs_data_t *obj) const;
|
||||
void Load(obs_data_t *obj);
|
||||
|
||||
std::string ToString() const;
|
||||
const char *GetTypeName() const;
|
||||
const char *GetTypeTag() const;
|
||||
static const char *GetTypeName(const OSCMessageElement &);
|
||||
static const char *GetTypeTag(const OSCMessageElement &);
|
||||
|
||||
private:
|
||||
struct TypeInfo {
|
||||
const char *localizedName, *tag;
|
||||
};
|
||||
static std::unordered_map<size_t, TypeInfo> _typeNames;
|
||||
|
||||
std::variant<IntVariable, DoubleVariable, StringVariable, OSCBlob,
|
||||
OSCTrue, OSCFalse, OSCInfinity, OSCNull>
|
||||
_value;
|
||||
|
||||
friend class OSCMessage;
|
||||
friend class OSCMessageElementEdit;
|
||||
};
|
||||
|
||||
class OSCMessage {
|
||||
public:
|
||||
void Save(obs_data_t *obj) const;
|
||||
void Load(obs_data_t *obj);
|
||||
|
||||
std::string ToString() const;
|
||||
std::optional<std::vector<char>> GetBuffer() const;
|
||||
|
||||
private:
|
||||
StringVariable _address = "/address";
|
||||
std::vector<OSCMessageElement> _elements = {
|
||||
OSCMessageElement("example"),
|
||||
OSCMessageElement(IntVariable(3))};
|
||||
|
||||
friend class OSCMessageEdit;
|
||||
};
|
||||
|
||||
class OSCMessageElementEdit : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
OSCMessageElementEdit(QWidget *);
|
||||
void SetMessageElement(const OSCMessageElement &);
|
||||
|
||||
protected:
|
||||
bool eventFilter(QObject *obj, QEvent *event) override;
|
||||
void showEvent(QShowEvent *event) override;
|
||||
|
||||
private slots:
|
||||
void TypeChanged(int);
|
||||
void DoubleChanged(const NumberVariable<double> &value);
|
||||
void IntChanged(const NumberVariable<int> &value);
|
||||
void TextChanged();
|
||||
void BinaryTextChanged();
|
||||
|
||||
signals:
|
||||
void ElementValueChanged(const OSCMessageElement &);
|
||||
void Focussed();
|
||||
|
||||
private:
|
||||
void SetVisibility(const OSCMessageElement &);
|
||||
|
||||
QComboBox *_type;
|
||||
VariableSpinBox *_intValue;
|
||||
VariableDoubleSpinBox *_doubleValue;
|
||||
VariableLineEdit *_text;
|
||||
VariableLineEdit *_binaryText;
|
||||
};
|
||||
|
||||
class OSCMessageEdit : public QWidget {
|
||||
Q_OBJECT
|
||||
public:
|
||||
OSCMessageEdit(QWidget *);
|
||||
void SetMessage(const OSCMessage &);
|
||||
|
||||
private slots:
|
||||
void ElementValueChanged(const OSCMessageElement &);
|
||||
void ElementFocussed();
|
||||
void AddressChanged();
|
||||
void Add();
|
||||
void Remove();
|
||||
void Up();
|
||||
void Down();
|
||||
|
||||
signals:
|
||||
void MessageChanged(const OSCMessage &);
|
||||
|
||||
private:
|
||||
void InsertElement(const OSCMessageElement &);
|
||||
void SetWidgetSize();
|
||||
int GetIndexOfSignal();
|
||||
|
||||
VariableLineEdit *_address;
|
||||
QListWidget *_elements;
|
||||
QPushButton *_add;
|
||||
QPushButton *_remove;
|
||||
QPushButton *_up;
|
||||
QPushButton *_down;
|
||||
|
||||
OSCMessage _currentSelection;
|
||||
};
|
||||
|
||||
} // namespace advss
|
||||
|
|
@ -1138,9 +1138,13 @@ void SetHeightToContentHeight(QListWidget *list)
|
|||
auto nrItems = list->count();
|
||||
if (nrItems == 0) {
|
||||
list->setMaximumHeight(0);
|
||||
list->setMinimumHeight(0);
|
||||
} else {
|
||||
list->setMaximumHeight(list->sizeHintForRow(0) * nrItems +
|
||||
2 * list->frameWidth());
|
||||
int height =
|
||||
(list->sizeHintForRow(0) + list->spacing()) * nrItems +
|
||||
2 * list->frameWidth();
|
||||
list->setMinimumHeight(height);
|
||||
list->setMaximumHeight(height);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user