diff --git a/README.md b/README.md new file mode 100644 index 0000000..56ebc85 --- /dev/null +++ b/README.md @@ -0,0 +1,62 @@ + +# OPC UA Browser + +The OPC UA Browser from basysKom is an OPC UA client with a wide range of application options for OPC UA technology. + +The OPC UA Browser can be used to establish an unencrypted connection to an OPC UA server as well as a connection encrypted via certificates. User authentication via user name and password is also supported. + +The OPC UA Browser can be used to monitor selected nodes in order to obtain a quick overview of important components. Individual nodes can be easily added to and removed from the dashboard. It is possible to create several dashboards at the same time and quickly switch back and forth between them. The dashboards created can be easily saved so that they can be quickly reloaded in a new session. + +The information model of the OPC UA server is displayed as a tree structure using the expert mode. Selecting a node displays its attributes and references. By clicking on a reference, you can quickly jump to the selected reference in the tree structure. + +## Features + +- Connecting to OPC UA Browser (uncrypted and encrypted via certificates, user authentication) +- Monitoring nodes in several dashboards +- Saving dashboards for reuse +- Browser for information model +- Windows, Linux, Android, iOS + +## Getting OPC UA Browser + +### Android + +coming soon in Google Play store + +### iOS + +coming soon in App Store + +## Compatibility +The application has been tested on: + +- Windows using the MinGW 64-Bit compiler and Qt 6.5+ + +- Linux (Ubuntu 22.04 LTS) and Qt 6.5+ + +- Android using clang for arm64-v8a, armeabi-v7a, x86, x86_64 + +- iOS + +## Dependencies + +- Qt 6.5+ +- Qt OPC UA (available at [https://doc.qt.io/qt-6/qtopcua-index.html)) +- OpenSSL >= 3.0 + +## Building + +To be done + +## Contributing + +Contributions are welcome! If you encounter any issues or would like to request a feature, feel free to open an issue or submit a pull request. + +## License + +This project is released under the GPLv3.0-or-later License. \ No newline at end of file diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index d35a652..e34ce97 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -4,7 +4,10 @@ SPDX-FileContributor: Karsten Herrler SPDX-License-Identifier: BSD-3-Clause ]] - cmake_minimum_required(VERSION 3.19) +cmake_minimum_required(VERSION 3.19) + +set(QT_QML_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/qml) +set(QML_IMPORT_PATH ${QT_QML_OUTPUT_DIRECTORY} CACHE STRING "Import paths for Qt Creator's code model" FORCE) qt_add_executable(appOPC_UA_Browser main.cpp @@ -16,7 +19,8 @@ set_source_files_properties(qml/style/Style.qml PROPERTIES qt_add_qml_module(appOPC_UA_Browser URI OPC_UA_Browser - VERSION 1.0.0 + VERSION 1.0 + DEPENDENCIES QtCore QtQuick QML_FILES qml/Main.qml qml/BrowserView.qml qml/ConnectionView.qml @@ -77,7 +81,6 @@ qt_add_qml_module(appOPC_UA_Browser reference.h reference.cpp referencemodel.h referencemodel.cpp treeitem.h treeitem.cpp - types.h uisettings.h uisettings.cpp x509certificate.h x509certificate.cpp ) diff --git a/src/backend.cpp b/src/backend.cpp index 1117d13..75299f5 100644 --- a/src/backend.cpp +++ b/src/backend.cpp @@ -14,9 +14,7 @@ #include #include "backend.h" -#include "dashboarditemmodel.h" #include "monitoreditemmodel.h" -#include "opcuamodel.h" #include "x509certificate.h" static QString defaultPkiPath() @@ -128,7 +126,7 @@ OpcUaModel *BackEnd::opcUaModel() const noexcept return mOpcUaModel; } -QAbstractItemModel *BackEnd::dashboardItemModel() const noexcept +DashboardItemModel *BackEnd::dashboardItemModel() const noexcept { return mDashboardItemModel; } @@ -281,11 +279,11 @@ void BackEnd::saveCurrentDashboard(const QString &name) QSettings settings; switch (mDashboardItemModel->getCurrentDashboardType()) { - case Types::DashboardType::Variables: + case DashboardItem::DashboardType::Variables: settings.setValue("dashboards/variables/" % name, nodeIds); addItemToStringListModel(mSavedVariableDashboardsModel, name); break; - case Types::DashboardType::Events: + case DashboardItem::DashboardType::Events: settings.setValue("dashboards/events/" % name, nodeIds); addItemToStringListModel(mSavedEventDashboardsModel, name); break; @@ -526,8 +524,8 @@ void BackEnd::loadLastDashboardsFromSettings() for (int i = 0; i < size; ++i) { settings.setArrayIndex(i); const QString name = settings.value("name").toString(); - const Types::DashboardType type = - static_cast(settings.value("type", 0).toInt()); + const DashboardItem::DashboardType type = + static_cast(settings.value("type", 0).toInt()); const int index = mDashboardItemModel->addItem(type, name); auto model = mDashboardItemModel->getMonitoredItemModel(index); diff --git a/src/backend.h b/src/backend.h index 027ac35..8319721 100644 --- a/src/backend.h +++ b/src/backend.h @@ -16,11 +16,19 @@ #include #include +#include "dashboarditemmodel.h" #include "opcuamodel.h" -class DashboardItemModel; class MonitoredItemModel; +// Workaround, otherwise qmllint doesn't recognise the QStringListModel +struct ThisIsAnnoying +{ + Q_GADGET + QML_FOREIGN(QStringListModel) + QML_ANONYMOUS +}; + class BackEnd : public QObject { Q_OBJECT @@ -35,8 +43,8 @@ class BackEnd : public QObject recentConnectionsChanged FINAL) Q_PROPERTY(QVector serverList READ serverList NOTIFY serverListChanged FINAL) Q_PROPERTY(QVector endpointList READ endpointList NOTIFY endpointListChanged FINAL) - Q_PROPERTY(QAbstractItemModel *opcUaModel READ opcUaModel NOTIFY opcUaModelChanged FINAL) - Q_PROPERTY(QAbstractItemModel *dashboardItemModel READ dashboardItemModel NOTIFY + Q_PROPERTY(OpcUaModel *opcUaModel READ opcUaModel NOTIFY opcUaModelChanged FINAL) + Q_PROPERTY(DashboardItemModel *dashboardItemModel READ dashboardItemModel NOTIFY opcUaModelChanged FINAL) Q_PROPERTY(QStringListModel *defaultVariableDashboards READ defaultVariableDashboards NOTIFY defaultVariableDashboardsChanged FINAL) @@ -58,7 +66,7 @@ class BackEnd : public QObject QVector serverList() const noexcept; QVector endpointList() const; OpcUaModel *opcUaModel() const noexcept; - QAbstractItemModel *dashboardItemModel() const noexcept; + DashboardItemModel *dashboardItemModel() const noexcept; QStringListModel *defaultVariableDashboards() const noexcept; QStringListModel *defaultEventDashboards() const noexcept; QStringListModel *savedVariableDashboards() const noexcept; diff --git a/src/dashboarditem.cpp b/src/dashboarditem.cpp index b8ecc86..cce77c2 100644 --- a/src/dashboarditem.cpp +++ b/src/dashboarditem.cpp @@ -10,14 +10,14 @@ #include "dashboarditem.h" #include "monitoreditemmodel.h" -QString getDefaultNameForType(Types::DashboardType type) +QString getDefaultNameForType(DashboardItem::DashboardType type) { switch (type) { - case Types::DashboardType::Variables: + case DashboardItem::DashboardType::Variables: return QCoreApplication::translate("OpcUaBrowser", "Dashboard"); - case Types::DashboardType::Events: + case DashboardItem::DashboardType::Events: return QCoreApplication::translate("OpcUaBrowser", "Event"); - case Types::DashboardType::Add: + case DashboardItem::DashboardType::Add: return QCoreApplication::translate("OpcUaBrowser", "Add"); default: break; @@ -27,14 +27,14 @@ QString getDefaultNameForType(Types::DashboardType type) return QString(); } -DashboardItem::DashboardItem(Types::DashboardType type, const QString &name) +DashboardItem::DashboardItem(DashboardItem::DashboardType type, const QString &name) : mName(name), mType(type) { if (mName.isEmpty()) { mName = getDefaultNameForType(type); } - if (type != Types::DashboardType::Add) { + if (type != DashboardItem::DashboardType::Add) { mMonitoredItemModel = new MonitoredItemModel(); } } @@ -58,7 +58,7 @@ void DashboardItem::setName(const QString &name) } } -Types::DashboardType DashboardItem::type() const noexcept +DashboardItem::DashboardType DashboardItem::type() const noexcept { return mType; } diff --git a/src/dashboarditem.h b/src/dashboarditem.h index 55d89e9..942a7a0 100644 --- a/src/dashboarditem.h +++ b/src/dashboarditem.h @@ -12,27 +12,32 @@ #include #include -#include "types.h" - class MonitoredItemModel; class QAbstractListModel; class DashboardItem : public QObject { + Q_OBJECT + QML_ELEMENT + QML_UNCREATABLE("DashboardItem is created by DashboardItemModel") + public: - explicit DashboardItem(Types::DashboardType type, const QString &name = QString()); + enum class DashboardType { Unknown = -1, Variables, Events, Add }; + Q_ENUM(DashboardType) + + explicit DashboardItem(DashboardItem::DashboardType type, const QString &name = QString()); ~DashboardItem(); const QString &name() const noexcept; void setName(const QString &name); - Types::DashboardType type() const noexcept; + DashboardItem::DashboardType type() const noexcept; QAbstractListModel *monitoredItemModel() const noexcept; QStringList getMonitoredNodeIds() const; private: QString mName; - Types::DashboardType mType; + DashboardItem::DashboardType mType; MonitoredItemModel *mMonitoredItemModel = nullptr; }; diff --git a/src/dashboarditemmodel.cpp b/src/dashboarditemmodel.cpp index 7b49d39..3e88aef 100644 --- a/src/dashboarditemmodel.cpp +++ b/src/dashboarditemmodel.cpp @@ -20,7 +20,7 @@ enum Roles : int { DashboardItemModel::DashboardItemModel(QObject *parent) : QAbstractListModel{ parent } { // Insert Add item - mItems.push_back(new DashboardItem(Types::DashboardType::Add)); + mItems.push_back(new DashboardItem(DashboardItem::DashboardType::Add)); } DashboardItemModel::~DashboardItemModel() @@ -59,7 +59,7 @@ QVariant DashboardItemModel::data(const QModelIndex &index, int role) const return QVariant(); } -int DashboardItemModel::addItem(Types::DashboardType type, const QString &name) +int DashboardItemModel::addItem(DashboardItem::DashboardType type, const QString &name) { const int pos = mItems.size() - 1; DashboardItem *item = new DashboardItem(type, name); @@ -90,7 +90,7 @@ void DashboardItemModel::clearItems() qDeleteAll(mItems); mItems.clear(); // Insert Add item - mItems.push_back(new DashboardItem(Types::DashboardType::Add)); + mItems.push_back(new DashboardItem(DashboardItem::DashboardType::Add)); endResetModel(); } @@ -107,10 +107,10 @@ MonitoredItemModel *DashboardItemModel::getCurrentMonitoredItemModel() const return getMonitoredItemModel(mCurrentIndex); } -Types::DashboardType DashboardItemModel::getCurrentDashboardType() const +DashboardItem::DashboardType DashboardItemModel::getCurrentDashboardType() const { if (mCurrentIndex >= mItems.size()) - return Types::DashboardType::Unknown; + return DashboardItem::DashboardType::Unknown; return mItems[mCurrentIndex]->type(); } @@ -131,7 +131,7 @@ bool DashboardItemModel::isAddItem(uint index) const if (index >= mItems.size()) return false; - return (mItems.at(index)->type() == Types::DashboardType::Add); + return (mItems.at(index)->type() == DashboardItem::DashboardType::Add); } void DashboardItemModel::setCurrentIndex(uint index) @@ -152,7 +152,7 @@ void DashboardItemModel::saveDashboardsToSettings() const settings.beginWriteArray("lastDashboards"); for (qsizetype i = 0; i < mItems.count(); ++i) { - if (mItems[i]->type() == Types::DashboardType::Add) + if (mItems[i]->type() == DashboardItem::DashboardType::Add) continue; settings.setArrayIndex(i); diff --git a/src/dashboarditemmodel.h b/src/dashboarditemmodel.h index df138e9..8f2c311 100644 --- a/src/dashboarditemmodel.h +++ b/src/dashboarditemmodel.h @@ -9,12 +9,17 @@ #define DASHBOARDITEMMODEL_H #include +#include #include "dashboarditem.h" +class MonitoredItemModel; + class DashboardItemModel : public QAbstractListModel { Q_OBJECT + QML_ANONYMOUS + public: explicit DashboardItemModel(QObject *parent = nullptr); ~DashboardItemModel(); @@ -24,13 +29,13 @@ class DashboardItemModel : public QAbstractListModel QVariant data(const QModelIndex &index, int role) const override; bool containsItem(const QString &name) const noexcept; - Q_INVOKABLE int addItem(Types::DashboardType type, const QString &name = QString()); + Q_INVOKABLE int addItem(DashboardItem::DashboardType type, const QString &name = QString()); Q_INVOKABLE void removeItem(int index); void clearItems(); MonitoredItemModel *getMonitoredItemModel(int index) const; MonitoredItemModel *getCurrentMonitoredItemModel() const; - Types::DashboardType getCurrentDashboardType() const; + DashboardItem::DashboardType getCurrentDashboardType() const; void setCurrentDashboardName(const QString &name); Q_INVOKABLE bool isAddItem(uint index) const; diff --git a/src/main.cpp b/src/main.cpp index dcb129b..b20d1a1 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -13,8 +13,6 @@ #include #include -#include "types.h" - static constexpr auto FontSwansea = "://font/Swansea.ttf"; static constexpr auto FontSwanseaBold = "://font/SwanseaBold.ttf"; @@ -49,8 +47,6 @@ int main(int argc, char *argv[]) &engine, &QQmlApplicationEngine::objectCreationFailed, &app, []() { QCoreApplication::exit(-1); }, Qt::QueuedConnection); - qmlRegisterUncreatableMetaObject(Types::staticMetaObject, "Types", 1, 0, "DashboardType", - "Error: only enums"); engine.loadFromModule("OPC_UA_Browser", "Main"); return app.exec(); diff --git a/src/opcuamodel.cpp b/src/opcuamodel.cpp index c85a83c..d667adb 100644 --- a/src/opcuamodel.cpp +++ b/src/opcuamodel.cpp @@ -29,7 +29,7 @@ enum Roles : int { QHash OpcUaModel::roleNames() const { auto names = QAbstractItemModel::roleNames(); - names[ColorRole] = "color"; + names[ColorRole] = "indicatorColor"; names[ValueRole] = "value"; names[NodeIdRole] = "nodeId"; names[AttributesRole] = "attributes"; diff --git a/src/opcuamodel.h b/src/opcuamodel.h index da58b14..53b061f 100644 --- a/src/opcuamodel.h +++ b/src/opcuamodel.h @@ -9,6 +9,7 @@ #define OPCUAMODEL_H #include +#include #include "treeitem.h" @@ -18,6 +19,7 @@ class QOpcUaNode; class OpcUaModel : public QAbstractItemModel { Q_OBJECT + QML_ANONYMOUS public: explicit OpcUaModel(QObject *parent = nullptr); diff --git a/src/qml/ContentView.qml b/src/qml/ContentView.qml index 0d2fde4..de65c07 100644 --- a/src/qml/ContentView.qml +++ b/src/qml/ContentView.qml @@ -7,7 +7,6 @@ import QtQml import QtQuick -import QtQuick.Controls import QtQuick.Layouts import OPC_UA_Browser diff --git a/src/qml/ContextMenu.qml b/src/qml/ContextMenu.qml index 4c6644d..37c5b23 100644 --- a/src/qml/ContextMenu.qml +++ b/src/qml/ContextMenu.qml @@ -5,6 +5,8 @@ * SPDX-License-Identifier: GPL-3.0-or-later */ +pragma ComponentBehavior: Bound + import QtQuick import QtQuick.Controls import QtQuick.Layouts @@ -12,7 +14,7 @@ import QtQuick.Layouts import OPC_UA_Browser Popup { - id: contextMenu + id: menu property alias listModel: popupListView.model property ThemeContextMenu theme: Style.contextMenu @@ -28,10 +30,10 @@ Popup { background: Rectangle { id: transparentBorderRect - width: contextMenu.width - height: contextMenu.height + width: menu.width + height: menu.height radius: 3 - color: theme.background + color: menu.theme.background } contentItem: Item { @@ -46,25 +48,31 @@ Popup { clip: true delegate: Item { + id: delegateItem + height: 36 width: 200 + required property int index + required property url imageSource + required property string name + MouseArea { anchors.fill: parent hoverEnabled: true - onEntered: popupListView.currentIndex = index + onEntered: popupListView.currentIndex = delegateItem.index onClicked: { - listItemClicked(index) - contextMenu.close() + menu.listItemClicked(delegateItem.index) + menu.close() } } Rectangle { anchors.fill: parent radius: transparentBorderRect.radius - color: theme.backgroundSelected - opacity: popupListView.currentIndex === index ? 0.8 : 0 + color: menu.theme.backgroundSelected + opacity: popupListView.currentIndex === delegateItem.index ? 0.8 : 0 } RowLayout { @@ -76,17 +84,17 @@ Popup { Layout.alignment: Qt.AlignVCenter sourceSize.width: 24 sourceSize.height: 24 - source: model.imageSource - color: theme.textColor + source: delegateItem.imageSource + color: menu.theme.textColor } Text { Layout.fillHeight: true Layout.fillWidth: true verticalAlignment: Text.AlignVCenter - horizontalAlignment: Text.AlignHLeft - color: theme.textColor + horizontalAlignment: Text.AlignLeft + color: menu.theme.textColor font.pointSize: 12 - text: model.name + text: delegateItem.name } } } diff --git a/src/qml/DashboardConfigurationView.qml b/src/qml/DashboardConfigurationView.qml index 0aee8f0..5278440 100644 --- a/src/qml/DashboardConfigurationView.qml +++ b/src/qml/DashboardConfigurationView.qml @@ -6,7 +6,6 @@ */ import QtQuick -import QtQuick.Controls import QtQuick.Layouts import OPC_UA_Browser @@ -57,7 +56,7 @@ Item { onClicked: { if ((BackEnd.defaultVariableDashboards.rowCount() === 0) && (BackEnd.savedVariableDashboards.rowCount() === 0)) { - addMonitoredItems() + view.addMonitoredItems() } else { view.type = DashboardConfigurationView.Type.SelectVariables } diff --git a/src/qml/DashboardView.qml b/src/qml/DashboardView.qml index 3681fbc..47ce69d 100644 --- a/src/qml/DashboardView.qml +++ b/src/qml/DashboardView.qml @@ -5,19 +5,19 @@ * SPDX-License-Identifier: GPL-3.0-or-later */ +pragma ComponentBehavior: Bound + import QtQuick import QtQuick.Controls import QtQuick.Layouts import OPC_UA_Browser -import Types Rectangle { id: view readonly property bool canSaveDashboard: repeater.count > 1 - readonly property string currentDashboardName: (tabBar.currentItem - === null) ? "" : tabBar.currentItem.text + readonly property string currentDashboardName: (tabBar.currentItem === null) ? "" : tabBar.currentText readonly property int itemWidth: { // Calculate content width of flow var contentWidth = flow.width - flow.leftPadding - flow.rightPadding @@ -37,12 +37,12 @@ Rectangle { signal addNewDashboard function addMonitoredItemsDashboard(name) { - tabRepeater.model.addItem(DashboardType.Variables, name) + tabRepeater.model.addItem(DashboardItem.DashboardType.Variables, name) tabBar.currentIndex = tabRepeater.count - 2 } function addEventsDashboard(name) { - tabRepeater.model.addItem(DashboardType.Events, name) + tabRepeater.model.addItem(DashboardItem.DashboardType.Events, name) tabBar.currentIndex = tabRepeater.count - 2 } @@ -97,7 +97,7 @@ Rectangle { onReleased: held = false onClicked: { if (dragArea.isAddItem) { - addMonitoredItems() + view.addMonitoredItems() } } @@ -112,7 +112,7 @@ Rectangle { width: view.itemWidth implicitHeight: Math.max(80, childrenRect.height) radius: 5 - color: dragArea.held ? theme.item.backgroundHeld : dragArea.hasError ? theme.item.backgroundError : theme.item.background + color: dragArea.held ? view.theme.item.backgroundHeld : dragArea.hasError ? view.theme.item.backgroundError : view.theme.item.background Behavior on color { ColorAnimation { @@ -126,7 +126,8 @@ Rectangle { ParentChange { target: content parent: view - width: view.itemWidth + // https://bugreports.qt.io/browse/QTBUG-101364 + width: view.itemWidth // qmllint disable incompatible-type } AnchorChanges { target: content @@ -148,7 +149,7 @@ Rectangle { Text { Layout.fillWidth: true - color: theme.item.textColor + color: view.theme.item.textColor text: dragArea.name font { pointSize: 12 @@ -162,7 +163,7 @@ Rectangle { sourceSize.width: 24 sourceSize.height: 24 source: "qrc:/icons/delete.svg" - color: theme.item.textColor + color: view.theme.item.textColor MouseArea { anchors.fill: parent @@ -176,7 +177,7 @@ Rectangle { Text { width: parent.width - 2 * parent.padding font.pointSize: 10 - color: theme.item.textColor + color: view.theme.item.textColor text: dragArea.value elide: Text.ElideRight clip: true @@ -186,7 +187,7 @@ Rectangle { visible: dragArea.hasError width: parent.width - 2 * parent.padding font.pointSize: 10 - color: theme.item.textColor + color: view.theme.item.textColor text: dragArea.status elide: Text.ElideRight clip: true @@ -199,7 +200,7 @@ Rectangle { sourceSize.height: 48 visible: dragArea.isAddItem source: "qrc:/icons/plus.svg" - color: theme.item.textColor + color: view.theme.item.textColor } } @@ -221,7 +222,7 @@ Rectangle { DelegateModel { id: visualModel - model: (tabBar.currentItem === null) ? null : tabBar.currentItem.monitoringModel + model: (tabBar.currentItem === null) ? null : tabBar.currentMonitoringModel delegate: dragDelegate } @@ -259,6 +260,8 @@ Rectangle { property int lastCurrentIndex: 0 property bool allowSelectingAddItem: false + property string currentText + property var currentMonitoringModel anchors.left: parent.left anchors.right: parent.right @@ -288,18 +291,34 @@ Rectangle { StyledIconTabButton { id: tabButton + required property var model + required property var monitoringModel + required property int index + type: model.type text: model.name - property var monitoringModel: model.monitoringModel onClicked: { - if (type === DashboardType.Add) { - addNewDashboard() + if (type === DashboardItem.DashboardType.Add) { + view.addNewDashboard() + } + } + + onIsCurrentTabChanged: { + if (isCurrentTab) { + tabBar.currentText = tabButton.text + tabBar.currentMonitoringModel = tabButton.monitoringModel + } + } + + onTextChanged: { + if (isCurrentTab) { + tabBar.currentText = tabButton.text } } onPressAndHold: { - if (type === DashboardType.Add) + if (type === DashboardItem.DashboardType.Add) return var pressedPoint = mapToItem(view, tabButton.pressX, @@ -310,7 +329,7 @@ Rectangle { view.width - contextMenu.width)) contextMenu.y = pressedPoint.y - contextMenu.height - contextMenu.selectedIndex = index + contextMenu.selectedIndex = tabButton.index contextMenu.open() } } diff --git a/src/qml/Main.qml b/src/qml/Main.qml index 2516b97..7ffba9b 100644 --- a/src/qml/Main.qml +++ b/src/qml/Main.qml @@ -17,10 +17,6 @@ ApplicationWindow { property ThemeMainWindow theme: Style.mainWindow property int themeIndex: 0 - onThemeIndexChanged: { - Style.currentThemeIndex = themeIndex - UiSettings.setStatusAndNavigationBarColor(theme.background) - } Connections { target: Qt.application @@ -31,6 +27,16 @@ ApplicationWindow { } } + Connections { + target: Style + function onCurrentThemeIndexChanged() { + if (window.themeIndex !== Style.currentThemeIndex) { + window.themeIndex = Style.currentThemeIndex + UiSettings.setStatusAndNavigationBarColor(window.theme.background) + } + } + } + Settings { property alias themeIndex: window.themeIndex } diff --git a/src/qml/NodeAttributeList.qml b/src/qml/NodeAttributeList.qml index 6162663..a9ba365 100644 --- a/src/qml/NodeAttributeList.qml +++ b/src/qml/NodeAttributeList.qml @@ -5,6 +5,8 @@ * SPDX-License-Identifier: GPL-3.0-or-later */ +pragma ComponentBehavior: Bound + import QtQuick import QtQuick.Controls import QtQuick.Layouts @@ -49,7 +51,7 @@ Rectangle { property bool wasOvershooted: false height: flickable.height - width: maxDelegateWidth() + width: root.maxDelegateWidth() model: root.attributes //boundsBehavior: Flickable.StopAtBounds @@ -66,11 +68,15 @@ Rectangle { delegate: Rectangle { id: listViewDelegate + required property int index + required property string attribute + required property string value + readonly property real padding: 5 - width: maxDelegateWidth() + width: root.maxDelegateWidth() implicitHeight: childrenRect.height - color: ((index % 1) == 0) ? theme.color1 : theme.color2 + color: ((listViewDelegate.index % 1) == 0) ? root.theme.color1 : root.theme.color2 ColumnLayout { spacing: 0 @@ -78,20 +84,20 @@ Rectangle { Rectangle { Layout.preferredWidth: flickable.width Layout.preferredHeight: 1 - visible: model.index > 0 - color: theme.divider + visible: listViewDelegate.index > 0 + color: root.theme.divider } Text { - id: attribute + id: attributeText Layout.topMargin: 5 Layout.leftMargin: 5 Layout.fillWidth: true verticalAlignment: Qt.AlignVCenter - text: model.attribute + text: listViewDelegate.attribute elide: Qt.ElideRight - color: theme.textColor + color: root.theme.textColor font { pointSize: 11 bold: true @@ -105,47 +111,10 @@ Rectangle { Layout.bottomMargin: 5 Layout.fillWidth: true verticalAlignment: Qt.AlignVCenter - text: model.value - color: attribute.color + text: listViewDelegate.value + color: attributeText.color } } - - - /*RowLayout { - spacing: 0 - height: Math.max(30, valueLabel.implicitHeight) - - Text { - id: attribute - - Layout.margins: 5 - Layout.fillHeight: true - Layout.preferredWidth: root.width / 3 - verticalAlignment: Qt.AlignVCenter - text: model.attribute - elide: Qt.ElideRight - color: theme.textColor - } - - Rectangle { - id: divider - - Layout.fillHeight: true - width: 1 - color: attribute.color - } - - Text { - id: valueLabel - - Layout.margins: 5 - Layout.fillWidth: true - Layout.fillHeight: true - verticalAlignment: Qt.AlignVCenter - text: model.value - color: attribute.color - } - }*/ } } } diff --git a/src/qml/NodeDetailView.qml b/src/qml/NodeDetailView.qml index 0e1b3a6..bd70e0e 100644 --- a/src/qml/NodeDetailView.qml +++ b/src/qml/NodeDetailView.qml @@ -5,6 +5,8 @@ * SPDX-License-Identifier: GPL-3.0-or-later */ +pragma ComponentBehavior: Bound + import QtQuick import QtQuick.Controls import QtQuick.Layouts @@ -35,6 +37,7 @@ Item { model: [qsTr("Attributes"), qsTr("References")] StyledTabButton { + required property string modelData text: modelData width: Math.max(100, root.width / repeater.count) } diff --git a/src/qml/NodeReferenceList.qml b/src/qml/NodeReferenceList.qml index 7396ac4..54bd359 100644 --- a/src/qml/NodeReferenceList.qml +++ b/src/qml/NodeReferenceList.qml @@ -5,6 +5,8 @@ * SPDX-License-Identifier: GPL-3.0-or-later */ +pragma ComponentBehavior: Bound + import QtQuick import QtQuick.Controls import QtQuick.Layouts @@ -48,7 +50,7 @@ Rectangle { id: referenceList height: flickable.height - width: maxDelegateWidth() + width: root.maxDelegateWidth() //boundsBehavior: Flickable.StopAtBounds ScrollBar.vertical: StyledScrollBar {} @@ -62,7 +64,7 @@ Rectangle { implicitHeight: childrenRect.height z: 2 - color: theme.headerBackground + color: root.theme.headerBackground RowLayout { spacing: 0 @@ -81,7 +83,7 @@ Rectangle { Rectangle { Layout.fillHeight: true - width: 1 + Layout.preferredWidth: 1 color: root.textColor } @@ -101,11 +103,17 @@ Rectangle { delegate: Rectangle { id: listViewDelegate + required property int index + required property string type + required property string typeNodeId + required property string target + required property string targetNodeId + readonly property real padding: 5 - width: maxDelegateWidth() + width: root.maxDelegateWidth() implicitHeight: childrenRect.height - color: ((index % 2) == 0) ? theme.color1 : theme.color2 + color: ((listViewDelegate.index % 2) == 0) ? root.theme.color1 : root.theme.color2 RowLayout { spacing: 0 @@ -116,7 +124,7 @@ Rectangle { Layout.alignment: Qt.AlignVCenter sourceSize.width: 15 sourceSize.height: 15 - source: model.isForward ? "qrc:/icons/forward.svg" : "qrc:/icons/inverse.svg" + source: referenceList.model.isForward ? "qrc:/icons/forward.svg" : "qrc:/icons/inverse.svg" color: root.textColor } @@ -125,21 +133,20 @@ Rectangle { Layout.fillHeight: true Layout.preferredWidth: root.width / 3 verticalAlignment: Qt.AlignVCenter - text: model.type + text: listViewDelegate.type elide: Qt.ElideRight color: root.textColor MouseArea { anchors.fill: parent cursorShape: Qt.PointingHandCursor - onClicked: BackEnd.opcUaModel.setCurrentNodeId( - model.typeNodeId) + onClicked: BackEnd.opcUaModel.setCurrentNodeId(listViewDelegate.typeNodeId) } } Rectangle { Layout.fillHeight: true - width: 1 + Layout.preferredWidth: 1 color: root.textColor } @@ -149,14 +156,13 @@ Rectangle { Layout.fillHeight: true Layout.minimumWidth: root.width - x verticalAlignment: Qt.AlignVCenter - text: model.target + text: listViewDelegate.target color: root.textColor MouseArea { anchors.fill: parent cursorShape: Qt.PointingHandCursor - onClicked: BackEnd.opcUaModel.setCurrentNodeId( - model.targetNodeId) + onClicked: BackEnd.opcUaModel.setCurrentNodeId(listViewDelegate.targetNodeId) } } } diff --git a/src/qml/NodesView.qml b/src/qml/NodesView.qml index 31a9bcc..2a6b4e2 100644 --- a/src/qml/NodesView.qml +++ b/src/qml/NodesView.qml @@ -5,6 +5,8 @@ * SPDX-License-Identifier: GPL-3.0-or-later */ +pragma ComponentBehavior: Bound + import QtQuick import QtQuick.Controls @@ -60,7 +62,6 @@ Item { delegate: Rectangle { id: treeDelegate - readonly property real isCurrentItem: model.isCurrentItem readonly property real indent: 25 readonly property real padding: 5 @@ -70,11 +71,25 @@ Item { required property bool expanded required property int hasChildren required property int depth + required property int row + required property int column + + // Assigned to by model: + required property bool isCurrentItem + required property bool isSelected + required property bool canMonitoring + required property bool hasEventNotifier + required property color indicatorColor + required property string display + required property string value + required property var attributes + required property var references + required property var model onIsCurrentItemChanged: { if (isCurrentItem) { - view.attributes = model.attributes - view.references = model.references + view.attributes = attributes + view.references = references } } @@ -88,15 +103,14 @@ Item { TapHandler { id: tapHandler - onTapped: { - treeView.toggleExpanded(row) - BackEnd.opcUaModel.setCurrentIndex(treeView.index(row, - column)) + // ToDo: fix qmllint warning + onTapped: function(point, button) { // qmllint disable signal-handler-parameters + treeView.toggleExpanded(treeDelegate.row) + BackEnd.opcUaModel.setCurrentIndex(treeView.index(treeDelegate.row, treeDelegate.column)) } onLongPressed: { - BackEnd.opcUaModel.setCurrentIndex(treeView.index(row, - column)) + BackEnd.opcUaModel.setCurrentIndex(treeView.index(treeDelegate.row, treeDelegate.column)) var xPos = tapHandler.point.position.x + treeDelegate.x - treeView.contentX var yPos = tapHandler.point.position.y + treeDelegate.y - treeView.contentY @@ -137,7 +151,7 @@ Item { anchors.verticalCenter: label.verticalCenter width: height height: label.height / 2 - color: model.color + color: treeDelegate.indicatorColor border.width: 1 } @@ -148,9 +162,9 @@ Item { anchors.verticalCenter: parent.verticalCenter width: treeDelegate.width - treeDelegate.padding - x clip: true - text: model.display + text: treeDelegate.display font.pointSize: 12 - color: model.isCurrentItem ? view.theme.textColorSelected : view.theme.textColor + color: treeDelegate.isCurrentItem ? view.theme.textColorSelected : view.theme.textColor } StyledItemSelector { @@ -158,11 +172,11 @@ Item { anchors.verticalCenter: treeDelegate.verticalCenter height: treeDelegate.height - 10 width: height - checkState: model.isSelected ? Qt.Checked : Qt.Unchecked - visible: (view.canSelectVariables && model.canMonitoring) - || (view.canSelectEvents && model.hasEventNotifier) + checkState: treeDelegate.isSelected ? Qt.Checked : Qt.Unchecked + visible: (view.canSelectVariables && treeDelegate.canMonitoring) + || (view.canSelectEvents && treeDelegate.hasEventNotifier) - onToggled: model.isSelected = !model.isSelected + onToggled: treeDelegate.model.isSelected = !treeDelegate.isSelected } } } diff --git a/src/qml/SettingsView.qml b/src/qml/SettingsView.qml index 35d9463..3a13b04 100644 --- a/src/qml/SettingsView.qml +++ b/src/qml/SettingsView.qml @@ -15,7 +15,7 @@ Rectangle { property ThemeSettingsView theme: Style.settingsView function setTheme(index) { - window.themeIndex = index + Style.currentThemeIndex = index if (index === 0) { darkItemSelector.checkState = Qt.Checked brightItemSelector.checkState = Qt.Unchecked diff --git a/src/qml/controls/SideMenu.qml b/src/qml/controls/SideMenu.qml index fc8786d..10dbd16 100644 --- a/src/qml/controls/SideMenu.qml +++ b/src/qml/controls/SideMenu.qml @@ -7,7 +7,6 @@ import QtQuick import QtQuick.Controls -import QtQuick.Layouts import OPC_UA_Browser diff --git a/src/qml/controls/StyledButton.qml b/src/qml/controls/StyledButton.qml index 8399377..efbce9b 100644 --- a/src/qml/controls/StyledButton.qml +++ b/src/qml/controls/StyledButton.qml @@ -11,6 +11,8 @@ import QtQuick.Controls import OPC_UA_Browser Button { + id: button + highlighted: true height: 36 @@ -20,8 +22,8 @@ Button { palette.buttonText: theme.textColor background: Rectangle { - color: highlighted ? theme.highlightedBackground : theme.background - border.color: highlighted ? theme.highlightedBorderColor : theme.borderColor + color: button.highlighted ? button.theme.highlightedBackground : button.theme.background + border.color: button.highlighted ? button.theme.highlightedBorderColor : button.theme.borderColor border.width: 1 radius: 5 } diff --git a/src/qml/controls/StyledComboBox.qml b/src/qml/controls/StyledComboBox.qml index 0174027..b419fed 100644 --- a/src/qml/controls/StyledComboBox.qml +++ b/src/qml/controls/StyledComboBox.qml @@ -30,7 +30,7 @@ ColumnLayout { Layout.preferredHeight: layout.textColumnHeight verticalAlignment: Qt.AlignVCenter - color: theme.captionTextColor + color: layout.theme.captionTextColor font.bold: true } @@ -39,6 +39,6 @@ ColumnLayout { Layout.fillWidth: true Layout.preferredHeight: layout.comboBoxColumnHeight - palette.button: theme.background + palette.button: layout.theme.background } } diff --git a/src/qml/controls/StyledEndpointComboBox.qml b/src/qml/controls/StyledEndpointComboBox.qml index 5eddcf8..43aec13 100644 --- a/src/qml/controls/StyledEndpointComboBox.qml +++ b/src/qml/controls/StyledEndpointComboBox.qml @@ -5,10 +5,14 @@ * SPDX-License-Identifier: GPL-3.0-or-later */ +pragma ComponentBehavior: Bound + import QtQuick import QtQuick.Controls StyledComboBox { + id: comboBox + comboBoxColumnHeight: _comboBox.contentItem.implicitHeight function getDisplayTextPart(str, part) { @@ -26,21 +30,21 @@ StyledComboBox { leftPadding: 12 Text { font.pointSize: 12 - text: getDisplayTextPart(_comboBox.displayText, 0) + text: comboBox.getDisplayTextPart(comboBox._comboBox.displayText, 0) verticalAlignment: Text.AlignVCenter } Text { leftPadding: 12 font.pointSize: 10 - text: getDisplayTextPart(_comboBox.displayText, 1) + text: comboBox.getDisplayTextPart(comboBox._comboBox.displayText, 1) verticalAlignment: Text.AlignVCenter } Text { leftPadding: 12 font.pointSize: 10 - text: getDisplayTextPart(_comboBox.displayText, 2) + text: comboBox.getDisplayTextPart(comboBox._comboBox.displayText, 2) verticalAlignment: Text.AlignVCenter } } @@ -52,29 +56,30 @@ StyledComboBox { required property var model required property int index - property string delegateText: model[_comboBox.textRole] - width: _comboBox.width - highlighted: _comboBox.highlightedIndex === index + property string delegateText: model[comboBox._comboBox.textRole] + + width: comboBox._comboBox.width + highlighted: comboBox._comboBox.highlightedIndex === delegate.index contentItem: Column { Text { font.pointSize: 12 - text: getDisplayTextPart(delegateText, 0) + text: comboBox.getDisplayTextPart(delegate.delegateText, 0) verticalAlignment: Text.AlignVCenter } Text { leftPadding: 12 font.pointSize: 10 - text: getDisplayTextPart(delegateText, 1) + text: comboBox.getDisplayTextPart(delegate.delegateText, 1) verticalAlignment: Text.AlignVCenter } Text { leftPadding: 12 font.pointSize: 10 - text: getDisplayTextPart(delegateText, 2) + text: comboBox.getDisplayTextPart(delegate.delegateText, 2) verticalAlignment: Text.AlignVCenter } } diff --git a/src/qml/controls/StyledIconTabButton.qml b/src/qml/controls/StyledIconTabButton.qml index 123dcd3..d686e23 100644 --- a/src/qml/controls/StyledIconTabButton.qml +++ b/src/qml/controls/StyledIconTabButton.qml @@ -10,22 +10,21 @@ import QtQuick.Controls import QtQuick.Layouts import OPC_UA_Browser -import Types TabButton { id: control readonly property bool isCurrentTab: (TabBar.tabBar.currentIndex == TabBar.index) - && (type !== DashboardType.Add) + && (type !== DashboardItem.DashboardType.Add) property ThemeIconTabButton theme: Style.iconTabButton - property int type: DashboardType.Variables + property int type: DashboardItem.DashboardType.Variables width: 80 background: Rectangle { - color: isCurrentTab ? theme.backgroundSelected : theme.background + color: control.isCurrentTab ? control.theme.backgroundSelected : control.theme.background } contentItem: ColumnLayout { @@ -37,7 +36,8 @@ TabButton { Layout.alignment: Qt.AlignCenter sourceSize.width: 40 sourceSize.height: 40 - source: (DashboardType.Add === type) ? "qrc:/icons/plus.svg" : (DashboardType.Events === type) ? "qrc:/icons/event.svg" : "qrc:/icons/dashboard.svg" + source: (DashboardItem.DashboardType.Add === control.type) ? "qrc:/icons/plus.svg" : + (DashboardItem.DashboardType.Events === control.type) ? "qrc:/icons/event.svg" : "qrc:/icons/dashboard.svg" color: label.color } @@ -48,7 +48,7 @@ TabButton { horizontalAlignment: Text.AlignHCenter font.pointSize: 8 text: control.text - color: isCurrentTab ? theme.textColorSelected : theme.textColor + color: control.isCurrentTab ? control.theme.textColorSelected : control.theme.textColor elide: Text.ElideRight } } diff --git a/src/qml/controls/StyledItemSelector.qml b/src/qml/controls/StyledItemSelector.qml index db5fa5b..8583af3 100644 --- a/src/qml/controls/StyledItemSelector.qml +++ b/src/qml/controls/StyledItemSelector.qml @@ -21,15 +21,15 @@ CheckBox { width: control.width height: width radius: width / 2 - color: theme.background - opacity: theme.backgroundOpacity + color: control.theme.background + opacity: control.theme.backgroundOpacity } Rectangle { anchors.fill: parent radius: background.radius border.width: 1 - border.color: theme.borderColor + border.color: control.theme.borderColor color: "transparent" IconImage { @@ -39,8 +39,8 @@ CheckBox { source: "qrc:/icons/checkmark.svg" sourceSize.width: parent.height * 0.65 sourceSize.height: parent.width * 0.65 - color: theme.checkMarkColor - visible: (checkState === Qt.Checked) + color: control.theme.checkMarkColor + visible: (control.checkState === Qt.Checked) } } } diff --git a/src/qml/controls/StyledMenuItem.qml b/src/qml/controls/StyledMenuItem.qml index 7114229..8341598 100644 --- a/src/qml/controls/StyledMenuItem.qml +++ b/src/qml/controls/StyledMenuItem.qml @@ -28,6 +28,6 @@ MenuItem { } background: Rectangle { - color: theme.background + color: control.theme.background } } diff --git a/src/qml/controls/StyledMenuSeparator.qml b/src/qml/controls/StyledMenuSeparator.qml index dc20b88..3457de1 100644 --- a/src/qml/controls/StyledMenuSeparator.qml +++ b/src/qml/controls/StyledMenuSeparator.qml @@ -11,12 +11,14 @@ import QtQuick.Controls import OPC_UA_Browser MenuSeparator { + id: separator + property ThemeSideMenu theme: Style.sideMenu height: enabled ? implicitHeight : 0 contentItem: Rectangle { implicitHeight: 1 - color: theme.iconColor + color: separator.theme.iconColor } } diff --git a/src/qml/controls/StyledScrollBar.qml b/src/qml/controls/StyledScrollBar.qml index 45f9c49..6301dba 100644 --- a/src/qml/controls/StyledScrollBar.qml +++ b/src/qml/controls/StyledScrollBar.qml @@ -19,7 +19,7 @@ ScrollBar { implicitWidth: 10 implicitHeight: 10 radius: 5 - color: theme.selector + color: control.theme.selector opacity: (control.policy === ScrollBar.AlwaysOn) || (control.active && control.size < 1.0) ? 0.75 : 0 @@ -30,6 +30,6 @@ ScrollBar { } background: Rectangle { - color: theme.background + color: control.theme.background } } diff --git a/src/qml/controls/StyledTabButton.qml b/src/qml/controls/StyledTabButton.qml index d053b40..5d791c8 100644 --- a/src/qml/controls/StyledTabButton.qml +++ b/src/qml/controls/StyledTabButton.qml @@ -11,14 +11,14 @@ import QtQuick.Controls import OPC_UA_Browser TabButton { - id: root + id: button readonly property bool isCurrentTab: (TabBar.tabBar.currentIndex == TabBar.index) property ThemeTabButton theme: Style.tabButton background: Rectangle { - color: theme.background + color: button.theme.background Rectangle { id: divider @@ -26,26 +26,26 @@ TabButton { anchors.right: parent.right anchors.bottom: parent.bottom height: 4 - color: theme.dividerColor + color: button.theme.dividerColor } Rectangle { anchors.centerIn: divider width: divider.width / 2 height: 4 - visible: isCurrentTab - color: theme.dividerColorSelected + visible: button.isCurrentTab + color: button.theme.dividerColorSelected } } contentItem: Text { - text: root.text + text: button.text font { pointSize: 11 bold: true capitalization: Font.AllUppercase } - color: theme.textColor + color: button.theme.textColor horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter elide: Text.ElideRight diff --git a/src/qml/style/ThemeBright.qml b/src/qml/style/ThemeBright.qml index 65ab3a7..a790c69 100644 --- a/src/qml/style/ThemeBright.qml +++ b/src/qml/style/ThemeBright.qml @@ -5,8 +5,6 @@ * SPDX-License-Identifier: GPL-3.0-or-later */ -import QtQuick - ThemeDefault { browserView.background: light diff --git a/src/qml/style/ThemeButton.qml b/src/qml/style/ThemeButton.qml index 7dbd350..d32540d 100644 --- a/src/qml/style/ThemeButton.qml +++ b/src/qml/style/ThemeButton.qml @@ -7,7 +7,7 @@ import QtQuick -Item { +StyleDefinitions { property color background: "transparent" property color borderColor: mediumLight property color textColor: mediumLight diff --git a/src/qml/style/ThemeComboBox.qml b/src/qml/style/ThemeComboBox.qml index ec2c309..4aac514 100644 --- a/src/qml/style/ThemeComboBox.qml +++ b/src/qml/style/ThemeComboBox.qml @@ -7,7 +7,7 @@ import QtQuick -Item { +StyleDefinitions { property color background: mediumLight property color captionTextColor: foreground } diff --git a/src/qml/style/ThemeDark.qml b/src/qml/style/ThemeDark.qml index c527b77..f18d898 100644 --- a/src/qml/style/ThemeDark.qml +++ b/src/qml/style/ThemeDark.qml @@ -5,6 +5,4 @@ * SPDX-License-Identifier: GPL-3.0-or-later */ -import QtQuick - ThemeDefault {} diff --git a/src/qml/style/ThemeHeader.qml b/src/qml/style/ThemeHeader.qml index 79997fc..5154cef 100644 --- a/src/qml/style/ThemeHeader.qml +++ b/src/qml/style/ThemeHeader.qml @@ -7,7 +7,7 @@ import QtQuick -Item { +StyleDefinitions { property color background: "transparent" property color iconColor: foreground property color dividerColor: dark diff --git a/src/qml/style/ThemeIconTabButton.qml b/src/qml/style/ThemeIconTabButton.qml index 00a881b..8e5ba27 100644 --- a/src/qml/style/ThemeIconTabButton.qml +++ b/src/qml/style/ThemeIconTabButton.qml @@ -7,7 +7,7 @@ import QtQuick -Item { +StyleDefinitions { property color background: "transparent" property color backgroundSelected: "transparent" property color textColor: foreground diff --git a/src/qml/style/ThemeItemSelector.qml b/src/qml/style/ThemeItemSelector.qml index 9a5f041..4bb0a1c 100644 --- a/src/qml/style/ThemeItemSelector.qml +++ b/src/qml/style/ThemeItemSelector.qml @@ -7,7 +7,7 @@ import QtQuick -Item { +StyleDefinitions { property color background: anthrazite property real backgroundOpacity: 0.8 property color borderColor: accent diff --git a/src/qml/style/ThemeTextField.qml b/src/qml/style/ThemeTextField.qml index 98b31f0..9348942 100644 --- a/src/qml/style/ThemeTextField.qml +++ b/src/qml/style/ThemeTextField.qml @@ -7,7 +7,7 @@ import QtQuick -Item { +StyleDefinitions { property color background: foreground property color captionTextColor: foreground } diff --git a/src/types.h b/src/types.h deleted file mode 100644 index 0a6ca28..0000000 --- a/src/types.h +++ /dev/null @@ -1,23 +0,0 @@ -/** - * SPDX-FileCopyrightText: 2024 basysKom GmbH - * SPDX-FileContributor: Karsten Herrler - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#ifndef TYPES_H -#define TYPES_H - -#include - -namespace Types { - -Q_NAMESPACE -enum class DashboardType { Unknown = -1, Variables, Events, Add }; -Q_ENUM_NS(DashboardType) - -} // namespace Types - -Q_DECLARE_METATYPE(Types::DashboardType) - -#endif // TYPES_H