From daba6d83796f2f6074719c6b39081ab27e82c82a Mon Sep 17 00:00:00 2001 From: swurl Date: Thu, 21 Nov 2024 18:22:53 -0500 Subject: [PATCH] CameraView overhaul; use NT only Signed-off-by: swurl --- CMakeLists.txt | 1 - dialogs/TopicView.qml | 54 ++------- include/models/CameraListModel.h | 60 ---------- items/MainScreen.qml | 36 +----- items/Tab.qml | 4 - main.cpp | 19 +--- src/models/CameraListModel.cpp | 186 ------------------------------- src/models/TopicListModel.cpp | 18 +-- widgets/misc/CameraView.qml | 45 +++++++- 9 files changed, 63 insertions(+), 360 deletions(-) delete mode 100644 include/models/CameraListModel.h delete mode 100644 src/models/CameraListModel.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 96a19913..f5dc494c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -117,7 +117,6 @@ set(ELEMENTS models/TabWidgetsModel models/TabListModel models/TopicListModel - models/CameraListModel models/MapModel models/AccentsListModel diff --git a/dialogs/TopicView.qml b/dialogs/TopicView.qml index 62be46bd..b4497606 100644 --- a/dialogs/TopicView.qml +++ b/dialogs/TopicView.qml @@ -10,10 +10,8 @@ Row { property alias menuAnim: menuAnim - property bool isCamera: false - - property string closedText: isCamera ? "<<" : ">>" - property string openText: isCamera ? ">>" : "<<" + property string closedText: ">>" + property string openText: "<<" width: (parent.width / 3) + 40 height: parent.height @@ -21,7 +19,7 @@ Row { SmoothedAnimation { id: menuAnim target: tv - property: "anchors." + (isCamera ? "right" : "left") + "Margin" + property: "anchors." + "left" + "Margin" duration: 500 } @@ -29,40 +27,15 @@ Row { signal close signal addWidget(string name, string topic, string type) - signal addCamera(string name, string source, var urls) signal dragging(point pos) signal dropped(point pos) function widgetAdd(name, topic, type) { - button2.text = closedText + button.text = closedText close() addWidget(name, topic, type) } - function cameraAdd(name, source, urls) { - button.text = (closedText) - close() - addCamera(name, source, urls) - } - - Button { - id: button - text: closedText - - width: isCamera ? 40 : 0 - height: isCamera ? 40 : 0 - - onClicked: { - if (text === closedText) { - open() - text = openText - } else { - close() - text = closedText - } - } - } - Rectangle { id: topicView radius: 10 @@ -87,7 +60,7 @@ Row { selectionModel: ItemSelectionModel {} - model: isCamera ? cameras : topics + model: topics delegate: Item { DragHandler { @@ -105,13 +78,8 @@ Row { let global = mapToItem(topicView, centroid.position) if (!topicView.contains(global)) { if (!ready) { - if (isCamera) { - cameraAdd(model.name, model.source, - model.urls) - } else { - widgetAdd(model.name, model.topic, - model.type) - } + widgetAdd(model.name, model.topic, + model.type) ready = true } @@ -210,7 +178,7 @@ Row { anchors.verticalCenter: parent.verticalCenter anchors.right: parent.right clip: true - text: isCamera ? "" : model.type + text: model.type color: Constants.palette.text @@ -221,11 +189,11 @@ Row { } Button { - id: button2 + id: button text: closedText - width: !isCamera ? 40 : 0 - height: !isCamera ? 40 : 0 + width: 40 + height: 40 onClicked: { if (text === closedText) { diff --git a/include/models/CameraListModel.h b/include/models/CameraListModel.h deleted file mode 100644 index 3ccfe4c6..00000000 --- a/include/models/CameraListModel.h +++ /dev/null @@ -1,60 +0,0 @@ -#ifndef CAMERALISTMODEL_H -#define CAMERALISTMODEL_H - -#include "TopicStore.h" -#include "networktables/NetworkTable.h" -#include -#include -#include - -typedef struct Camera -{ -public: - static struct Camera fromTable(std::shared_ptr table); - - QList urls; - QString name; - QString source; -} Camera; - -class CameraListModel : public QAbstractListModel -{ - Q_OBJECT - QML_ELEMENT - -public: - enum CLMRoleTypes { - NAME = Qt::UserRole, - URLS, - SOURCE - }; - - explicit CameraListModel(TopicStore &store, QObject *parent = nullptr); - - // Basic functionality: - int rowCount(const QModelIndex &parent = QModelIndex()) const override; - - QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; - - // Editable: - bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; - - Qt::ItemFlags flags(const QModelIndex &index) const override; - - // Add data: - Q_INVOKABLE void add(std::shared_ptr table); - - // Remove data: - void clear(); - Q_INVOKABLE bool remove(int row, const QModelIndex &parent = QModelIndex()); - -protected: - QHash roleNames() const override; - -private: - QList m_data; - - TopicStore *m_store; -}; - -#endif // CAMERALISTMODEL_H diff --git a/items/MainScreen.qml b/items/MainScreen.qml index bb9d6630..940d8f3a 100644 --- a/items/MainScreen.qml +++ b/items/MainScreen.qml @@ -122,40 +122,6 @@ Rectangle { onDropped: pos => drop(pos, true) } - TopicView { - id: cl - - isCamera: true - - onAddCamera: (name, source, urls) => { - currentTab().addCamera(name, source, urls) - } - - anchors { - right: parent.right - rightMargin: -(parent.width / 3) - - top: parent.top - bottom: parent.bottom - } - - onOpen: { - menuAnim.from = -(parent.width / 3) - menuAnim.to = 0 - menuAnim.start() - } - - onClose: { - menuAnim.to = -(parent.width / 3) - menuAnim.from = 0 - menuAnim.start() - } - - onDragging: pos => drag(pos, true) - - onDropped: pos => drop(pos, true) - } - TabNameDialog { id: tabNameDialog } @@ -394,7 +360,7 @@ Rectangle { anchors { top: parent.top left: tv.right - right: cl.left + right: parent.right leftMargin: 0 rightMargin: 0 diff --git a/items/Tab.qml b/items/Tab.qml index dd932a86..6a0268c9 100644 --- a/items/Tab.qml +++ b/items/Tab.qml @@ -82,10 +82,6 @@ Rectangle { twm.add(title, topic, type) } - function addCamera(name, source, urls) { - twm.addCamera(name, source, urls) - } - function setName(name) { model.title = name } diff --git a/main.cpp b/main.cpp index d38deeb2..12d3fb7e 100644 --- a/main.cpp +++ b/main.cpp @@ -5,7 +5,6 @@ #include "AccentsListModel.h" #include "BuildConfig.h" -#include "CameraListModel.h" #include "Flags.h" #include "Globals.h" #include "TitleManager.h" @@ -30,36 +29,21 @@ int main(int argc, char *argv[]) TabListModel *tlm = new TabListModel(settings, &app); - CameraListModel *clm = new CameraListModel(store, &app); - TitleManager *title = new TitleManager(&app); AccentsListModel *accents = new AccentsListModel(&app); accents->load(); - Globals::inst.AddConnectionListener(true, [topics, &store, clm, title] (const nt::Event &event) { + Globals::inst.AddConnectionListener(true, [topics, &store, title] (const nt::Event &event) { bool connected = event.Is(nt::EventFlags::kConnected); store.connect(connected); if (!connected) { QMetaObject::invokeMethod(topics, &TopicListModel::clear); - QMetaObject::invokeMethod(clm, &CameraListModel::clear); title->resetTitle(); } else { title->setTitle("Connected (" + QString::fromStdString(event.GetConnectionInfo()->remote_ip) + ")"); - QMetaObject::invokeMethod(clm, [=] { - // kind of hacky, but what works, works - QTimer::singleShot(1000, [=] { - clm->clear(); - - for (const std::string &st : Globals::inst.GetTable("/CameraPublisher")->GetSubTables()) { - std::shared_ptr subtable = Globals::inst.GetTable("/CameraPublisher")->GetSubTable(st); - - clm->add(subtable); - } - }); - }); } }); @@ -103,7 +87,6 @@ int main(int argc, char *argv[]) engine.rootContext()->setContextProperty("topics", topics); engine.rootContext()->setContextProperty("settings", settings); - engine.rootContext()->setContextProperty("cameras", clm); engine.rootContext()->setContextProperty("topicStore", &store); engine.rootContext()->setContextProperty("tlm", tlm); engine.rootContext()->setContextProperty("titleManager", title); diff --git a/src/models/CameraListModel.cpp b/src/models/CameraListModel.cpp deleted file mode 100644 index 48a8b63a..00000000 --- a/src/models/CameraListModel.cpp +++ /dev/null @@ -1,186 +0,0 @@ -#include "CameraListModel.h" -#include "Globals.h" - -#include -#include -#include - -CameraListModel::CameraListModel(TopicStore &store, QObject *parent) - : QAbstractListModel(parent) - , m_store(&store) -{} - -int CameraListModel::rowCount(const QModelIndex &parent) const -{ - return m_data.count(); -} - -QVariant CameraListModel::data(const QModelIndex &index, int role) const -{ - if (!index.isValid()) - return QVariant(); - - Camera c = m_data[index.row()]; - - switch (role) { - case NAME: - return c.name; - case URLS: - return QVariant::fromValue(c.urls); - case SOURCE: - return c.source; - default: - break; - } - - return QVariant(); -} - -bool CameraListModel::setData(const QModelIndex &index, const QVariant &value, int role) -{ - // if (data(index, role) != value) { - // Tab &t = m_data[index.row()]; - // switch (role) { - // case TITLE: - // t.title = value.toString(); - // case ROWS: - // t.rows = value.toInt(); - // case COLS: - // t.cols = value.toInt(); - // case WIDGETS: - // t.model = value.value(); - // default: - // break; - // } - // emit dataChanged(index, index, {role}); - // return true; - // } - return false; -} - -Qt::ItemFlags CameraListModel::flags(const QModelIndex &index) const -{ - if (!index.isValid()) - return Qt::NoItemFlags; - - return QAbstractItemModel::flags(index) | Qt::ItemIsEditable; -} - -void CameraListModel::add(std::shared_ptr table) -{ - Camera camera{}; - - camera.name = QString::fromStdString(table->GetEntry("description").GetString("")); - camera.source = QString::fromStdString(table->GetEntry("source").GetString("")); - - QString namePath = QString::fromStdString(std::string{table->GetPath()} + "/description"); - QString sourcePath = QString::fromStdString(std::string{table->GetPath()} + "/source"); - QString streamsPath = QString::fromStdString(std::string{table->GetPath()} + "/streams"); - - nt::NetworkTableEntry desc = Globals::inst.GetEntry(namePath.toStdString()); - nt::NetworkTableEntry src = Globals::inst.GetEntry(sourcePath.toStdString()); - nt::NetworkTableEntry str = Globals::inst.GetEntry(streamsPath.toStdString()); - - camera.name = QString::fromStdString(desc.GetString("invalid")); - camera.source = QString::fromStdString(src.GetString("invalid")); - - auto urls = TopicStore::toVariant(str.GetValue()).toList(); - - camera.urls = {}; - for (const QVariant &url : urls) { - static QRegularExpression re("^(mjpe?g|ip|usb):"); - QString newStream = url.toUrl().toString(); - newStream.replace(re, ""); - newStream.replace("/?action=stream", "/stream.mjpg?"); - - camera.urls.append(QUrl(newStream)); - } - - bool descriptionDone = (camera.name != "invalid"), - sourceDone = (camera.source != "invalid"), - streamsDone = (!camera.urls.empty()); - - if (sourceDone && descriptionDone && streamsDone) { - beginInsertRows(QModelIndex(), rowCount(), rowCount()); - m_data << camera; - endInsertRows(); - } else { - m_store->subscribe(namePath); - m_store->subscribe(sourcePath); - m_store->subscribe(streamsPath); - - QMetaObject::Connection *conn = new QMetaObject::Connection; - - *conn = connect(m_store, &TopicStore::topicUpdate, this, [=, this](QString topic, QVariant value) mutable { - if (!descriptionDone && topic == namePath) { - descriptionDone = true; - - camera.name = value.toString(); - - if (camera.name.isEmpty()) { - std::string path{table->GetPath()}; - QString qpath = QString::fromStdString(path); - camera.name = qpath.split("/").last(); - } - } else if (!sourceDone && topic == sourcePath) { - sourceDone = true; - - camera.source = value.toString(); - } else if (!streamsDone && topic == streamsPath) { - streamsDone = true; - - QStringList streams = value.toStringList(); - - for (const QString &stream : streams) { - static QRegularExpression re("^(mjpe?g|ip|usb):"); - QString newStream = stream; - newStream.replace(re, ""); - newStream.replace("/?action=stream", "/stream.mjpg?"); - - camera.urls.append(QUrl(newStream)); - } - } - - if ((topic == streamsPath || topic == sourcePath || topic == namePath) - && sourceDone && descriptionDone && streamsDone) { - - beginInsertRows(QModelIndex(), rowCount(), rowCount()); - m_data << camera; - endInsertRows(); - - m_store->unsubscribe(namePath); - m_store->unsubscribe(sourcePath); - m_store->unsubscribe(streamsPath); - - disconnect(*conn); - delete conn; - } - }); - } -} - -void CameraListModel::clear() -{ - beginResetModel(); - m_data.clear(); - endResetModel(); -} - -bool CameraListModel::remove(int row, const QModelIndex &parent) -{ - beginRemoveRows(parent, row, row); - m_data.remove(row); - endRemoveRows(); - - return true; -} - -QHash CameraListModel::roleNames() const -{ - QHash rez; - rez[NAME] = "name"; - rez[URLS] = "urls"; - rez[SOURCE] = "source"; - - return rez; -} diff --git a/src/models/TopicListModel.cpp b/src/models/TopicListModel.cpp index c7ea0c34..24518da7 100644 --- a/src/models/TopicListModel.cpp +++ b/src/models/TopicListModel.cpp @@ -56,7 +56,9 @@ void TopicListModel::add(const QString &toAdd) bool append = false; if (isLast) { - if (hasType) { + if (parentItem && parentItem->data(TOPIC).toString() != "") { + append = false; + } else if (hasType) { if (sub == ".type") { std::string value = type.GetString("invalid"); @@ -82,11 +84,8 @@ void TopicListModel::add(const QString &toAdd) QString typeStr = QString::fromStdString(value); parentItem->setData(typeStr, TYPE); } - - append = false; - } else { - append = false; } + append = false; } else { append = true; @@ -95,8 +94,13 @@ void TopicListModel::add(const QString &toAdd) } } else { append = true; - item->setData("", TLMRoleTypes::TOPIC); - item->setData("", TYPE); + if (parentItem && parentItem->text() == "CameraPublisher") { + item->setData("/CameraPublisher/" + sub, TLMRoleTypes::TOPIC); + item->setData("camera", TYPE); + } else { + item->setData("", TLMRoleTypes::TOPIC); + item->setData("", TYPE); + } } if (append) { diff --git a/widgets/misc/CameraView.qml b/widgets/misc/CameraView.qml index 8186fabe..ffb39307 100644 --- a/widgets/misc/CameraView.qml +++ b/widgets/misc/CameraView.qml @@ -6,10 +6,10 @@ import QtMultimedia import QFRCDashboard BaseWidget { - property string item_host: "127.0.0.1" + property string item_topic - property int item_port: 1181 - property int portMax: 65535 + property var item_url: "" + property list urlChoices property int item_quality: 0 property int qualityMax: 100 @@ -19,11 +19,10 @@ BaseWidget { property size item_resolution: Qt.size(0, 0) + onItem_urlChanged: player.resetSource() onItem_fpsChanged: player.resetSource() - onItem_portChanged: player.resetSource() onItem_qualityChanged: player.resetSource() onItem_resolutionChanged: player.resetSource() - onItem_hostChanged: player.resetSource() MenuItem { id: reconnItem @@ -33,8 +32,34 @@ BaseWidget { } } + function fixUrls(value) { + for (let i = 0; i < value.length; ++i) { + if (value[i].startsWith("mjpg:")) value[i] = value[i].substring(5) + } + } + + function updateTopic(ntTopic, ntValue) { + if (ntTopic === item_topic + "/streams") { + urlChoices = ntValue + fixUrls(urlChoices) + + if (urlChoices.length > 0 && item_url === "") item_url = urlChoices[0] + } + } + Component.onCompleted: { rcMenu.addItem(reconnItem) + + topicStore.topicUpdate.connect(updateTopic) + + item_topic = model.topic + } + + Component.onDestruction: { + if (topicStore !== null) { + topicStore.topicUpdate.disconnect(updateTopic) + topicStore.unsubscribe(item_topic) + } } Rectangle { @@ -62,7 +87,7 @@ BaseWidget { } function resetSource() { - source = Qt.url(item_host + ":" + item_port + "/stream.mjpg?" + (item_quality !== 0 ? "compression=" + item_quality + "&" : "") + (item_fps !== 0 ? "fps=" + item_fps + "&" : "") + (item_resolution !== Qt.size(0, 0) ? "resolution=" + item_resolution.width + "x" + item_resolution.height : "")) + source = Qt.url(item_url + (item_quality !== 0 ? "compression=" + item_quality + "&" : "") + (item_fps !== 0 ? "fps=" + item_fps + "&" : "") + (item_resolution !== Qt.size(0, 0) ? "resolution=" + item_resolution.width + "x" + item_resolution.height : "")) } function reconnect() { @@ -87,4 +112,12 @@ BaseWidget { anchors.fill: parent } } + + onItem_topicChanged: { + topicStore.unsubscribe(topic + "/streams") + topicStore.subscribe(item_topic + "/streams") + model.topic = item_topic + + updateTopic(item_topic + "/streams", topicStore.getValue(model.topic + "/streams")) + } }