From 8e32f8a29a797c56b8999e134848272d3c261af9 Mon Sep 17 00:00:00 2001 From: JesusPoderoso Date: Mon, 18 Sep 2023 15:44:35 +0200 Subject: [PATCH 01/36] Disable dragging elements out of their bounds Signed-off-by: JesusPoderoso --- qml/EntityList.qml | 3 +++ qml/LogicalView.qml | 2 ++ qml/PhysicalView.qml | 3 +++ 3 files changed, 8 insertions(+) diff --git a/qml/EntityList.qml b/qml/EntityList.qml index 3464ac98..0746d97b 100644 --- a/qml/EntityList.qml +++ b/qml/EntityList.qml @@ -47,6 +47,7 @@ Rectangle { width: parent.width height: parent.height spacing: verticalSpacing + boundsBehavior: Flickable.StopAtBounds ScrollBar.vertical: CustomScrollBar { id: scrollBar @@ -123,6 +124,7 @@ Rectangle { spacing: verticalSpacing topMargin: verticalSpacing delegate: endpointListDelegate + boundsBehavior: Flickable.StopAtBounds property int collapseHeightFlag: childrenRect.height + endpointList.topMargin } @@ -205,6 +207,7 @@ Rectangle { delegate: locatorListDelegate spacing: verticalSpacing topMargin: verticalSpacing + boundsBehavior: Flickable.StopAtBounds property int collapseHeightFlag: childrenRect.height + locatorList.topMargin } diff --git a/qml/LogicalView.qml b/qml/LogicalView.qml index 39350711..cbbfa464 100644 --- a/qml/LogicalView.qml +++ b/qml/LogicalView.qml @@ -47,6 +47,7 @@ Rectangle { width: parent.width height: parent.height spacing: verticalSpacing + boundsBehavior: Flickable.StopAtBounds ScrollBar.vertical: CustomScrollBar { id: scrollBar @@ -129,6 +130,7 @@ Rectangle { spacing: verticalSpacing topMargin: verticalSpacing delegate: topicListDelegate + boundsBehavior: Flickable.StopAtBounds property int collapseHeightFlag: childrenRect.height + topicList.topMargin } diff --git a/qml/PhysicalView.qml b/qml/PhysicalView.qml index 35e6065d..0fa8cb55 100644 --- a/qml/PhysicalView.qml +++ b/qml/PhysicalView.qml @@ -47,6 +47,7 @@ Rectangle { width: parent.width height: parent.height spacing: verticalSpacing + boundsBehavior: Flickable.StopAtBounds ScrollBar.vertical: CustomScrollBar { id: scrollBar @@ -123,6 +124,7 @@ Rectangle { spacing: verticalSpacing topMargin: verticalSpacing delegate: userListDelegate + boundsBehavior: Flickable.StopAtBounds property int collapseHeightFlag: childrenRect.height + userList.topMargin } @@ -206,6 +208,7 @@ Rectangle { delegate: processListDelegate spacing: verticalSpacing topMargin: verticalSpacing + boundsBehavior: Flickable.StopAtBounds property int collapseHeightFlag: childrenRect.height + processList.topMargin } From cd23b1383bbc9aa29652061e379a7b2ffd19b98e Mon Sep 17 00:00:00 2001 From: JesusPoderoso Date: Tue, 29 Aug 2023 09:33:08 +0200 Subject: [PATCH 02/36] Refs #19532: Implement domain graph view Signed-off-by: JesusPoderoso Refs #19308: Improve domain view Signed-off-by: JesusPoderoso Refs #19308: Adapt to new JSON and visual improvements Signed-off-by: JesusPoderoso Refs #19308: Add arrows to connections Signed-off-by: JesusPoderoso Refs #19308: Round elements Signed-off-by: JesusPoderoso Refs #19308: Fix entities height Signed-off-by: JesusPoderoso Refs #19308: Update tab name with view content Signed-off-by: JesusPoderoso Refs #19308: Fix connections Signed-off-by: JesusPoderoso Refs #19308: Add domain id selector for graph view Signed-off-by: JesusPoderoso Refs #19308: Add Refresh button and refresh feature Signed-off-by: JesusPoderoso --- imports/Theme/Theme.qml | 2 + qml.qrc | 2 + qml/DomainGraphLayout.qml | 1255 +++++++++++++++++++++++++++++++++++++ qml/GraphConnection.qml | 94 +++ qml/TabLayout.qml | 107 +++- 5 files changed, 1447 insertions(+), 13 deletions(-) create mode 100644 qml/DomainGraphLayout.qml create mode 100644 qml/GraphConnection.qml diff --git a/imports/Theme/Theme.qml b/imports/Theme/Theme.qml index eca68b50..8207f34c 100644 --- a/imports/Theme/Theme.qml +++ b/imports/Theme/Theme.qml @@ -11,8 +11,10 @@ QtObject { readonly property color grey: "#808080" readonly property color lightGrey: "#d3d3d3" + readonly property color darkGrey: "#3e3e3e" readonly property color x11Grey: "#BEBEBE" readonly property color whiteSmoke: "#f5f5f5" + readonly property color vulcanexusBlue: "#DAF0F6" // readonly property int smallSize: 10 // readonly property int largeSize: 16 diff --git a/qml.qrc b/qml.qrc index 9901bc52..22715c9e 100644 --- a/qml.qrc +++ b/qml.qrc @@ -21,6 +21,7 @@ qml/CustomLegend.qml qml/CustomScrollBar.qml qml/DifferClickMouseArea.qml + qml/DomainGraphLayout.qml qml/DoubleSpinBox.qml qml/DumpFileDialog.qml qml/DynamicDataKindDialog.qml @@ -30,6 +31,7 @@ qml/EntityList.qml qml/ErrorDialog.qml qml/ExportCSVFileDialog.qml + qml/GraphConnection.qml qml/HistoricDataKindDialog.qml qml/HistoricDisplayStatisticsDialog.qml qml/HistoricStatisticsChartView.qml diff --git a/qml/DomainGraphLayout.qml b/qml/DomainGraphLayout.qml new file mode 100644 index 00000000..2cef19c4 --- /dev/null +++ b/qml/DomainGraphLayout.qml @@ -0,0 +1,1255 @@ +// Copyright 2023 Proyectos y Sistemas de Mantenimiento SL (eProsima). +// +// This file is part of eProsima Fast DDS Monitor. +// +// eProsima Fast DDS Monitor is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// eProsima Fast DDS Monitor is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with eProsima Fast DDS Monitor. If not, see . + +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import QtQuick.Layouts 1.15 + +import Theme 1.0 + +Item +{ + id: domainGraphLayout + + // Public properties + property var model: {} + property int entity_id: 0 // entity id associated to the domain id + property int domain_id: 0 // domain id + + // Public signals + signal update_tab_name(string new_name) + + // Private properties + property var topic_locations_: {} + property var endpoint_topic_connections_: {} + property int max_host_width_: 0 + property int max_user_width_: 0 + property int max_process_width_: 0 + property int max_participant_width_: 0 + property int max_endpoint_width_: 0 + property var entity_painted_: [] + property var topic_painted_: [] + + // Private signals + signal resize_elements_() + signal topics_updated_() + signal endpoints_updated_() + signal participants_updated_() + signal processes_updated_() + signal users_updated_() + signal hosts_updated_() + + // Read only design properties + readonly property int radius_: 10 + readonly property int connection_thickness_: 5 + readonly property int elements_spacing_: 12 + readonly property int endpoint_height_: 40 + readonly property int first_indentation_: 5 + readonly property int icon_size_: 18 + readonly property int label_height_: 35 + readonly property int spacing_icon_label_: 8 + readonly property int scrollbar_min_size_: 8 + readonly property int scrollbar_max_size_: 12 + readonly property int topic_thickness_: 10 + readonly property string topic_color_: Theme.grey + readonly property string host_color_: Theme.darkGrey + readonly property string user_color_: Theme.eProsimaLightBlue + readonly property string process_color_: Theme.eProsimaDarkBlue + readonly property string participant_color_: Theme.vulcanexusBlue + readonly property string reader_color_: Theme.eProsimaYellow + readonly property string writer_color_: Theme.eProsimaGreen + + Component.onCompleted: + { + load_model() + } + + Flickable { + id: topicView + anchors.top: parent.top; anchors.bottom: parent.bottom + anchors.left: parent.left; anchors.leftMargin: max_host_width_ + elements_spacing_; + width: parent.width - max_host_width_ - elements_spacing_ + flickableDirection: Flickable.HorizontalFlick + boundsBehavior: Flickable.StopAtBounds + + contentWidth: topicsList.contentWidth + 100 + contentHeight: parent.height + + ScrollBar.vertical: ScrollBar { policy: ScrollBar.AlwaysOff } + ScrollBar.horizontal: ScrollBar { + id: horizontal_bar + anchors.left: parent.left; + anchors.bottom: parent.bottom + policy: ScrollBar.AlwaysOn + visible: topicView.contentWidth > topicView.width + hoverEnabled: true + + contentItem: Item { + implicitHeight: scrollbar_min_size_ + + Rectangle { + anchors.fill: parent + anchors.rightMargin: scrollbar_max_size_ + anchors.leftMargin: 1 + anchors.topMargin: 2 + anchors.bottomMargin: 2 + radius: height / 2 + color: horizontal_bar.pressed ? Theme.eProsimaLightBlue : Theme.lightGrey + } + } + + background: Item { + implicitHeight: scrollbar_max_size_ + + Rectangle { + anchors.fill: parent + color: horizontal_bar.pressed ? Theme.lightGrey : Theme.grey + } + } + } + + ListView + { + id: topicsList + model: domainGraphLayout.model["topics"] + anchors.left: parent.left; anchors.leftMargin: 2 * elements_spacing_ + anchors.top: parent.top; anchors.topMargin: elements_spacing_; + anchors.bottom: parent.bottom; anchors.bottomMargin: scrollbar_max_size_+ elements_spacing_ + contentWidth: contentItem.childrenRect.width + spacing: elements_spacing_ + orientation: ListView.Horizontal + interactive: false + + Connections + { + target: domainGraphLayout + + function onResize_elements_() + { + topicsList.onCountChanged() + } + } + + onCountChanged: + { + var listViewHeight = 0 + var listViewWidth = 0 + + // iterate over each element in the list item + for (var i = 0; i < topicsList.visibleChildren.length; i++) { + listViewHeight = topicsList.visibleChildren[i].height + listViewWidth += topicsList.visibleChildren[i].width + } + + for (var c = 0; c < topicsList.count; c++) + { + topicsList.currentIndex = c + var globalCoordinates = topicsList.currentItem.mapToItem(mainSpace, 0, 0) + topic_locations_[topicsList.currentItem.topic_id] = { + "id": topicsList.currentItem.topic_id, + "x" : globalCoordinates.x + (topicsList.currentItem.width/2) + } + } + topicsList.height = listViewHeight + topicsList.width = listViewWidth + 10* elements_spacing_ + topics_updated_() + } + + delegate: Rectangle + { + property string topic_id: modelData["id"] + implicitWidth: topic_tag.implicitWidth + height: topicsList.height + color: "transparent" + + + Rectangle + { + id: topic_tag + implicitWidth: topicRowLayout.implicitWidth + height: label_height_ + color: topic_color_ + radius: radius_ + + RowLayout { + id: topicRowLayout + spacing: spacing_icon_label_ + anchors.centerIn: parent + + IconSVG { + name: "topic" + color: "white" + size: icon_size_ + Layout.leftMargin: first_indentation_ + } + Label { + text: modelData["alias"] + Layout.rightMargin: first_indentation_ + color: "white" + } + } + MouseArea + { + anchors.fill: parent + onClicked: + { + console.log(modelData["alias"] + " clicked!") + } + } + } + + Rectangle + { + id: topic_down_bar + anchors.top: topic_tag.bottom + anchors.bottom: parent.bottom + anchors.horizontalCenter: topic_tag.horizontalCenter + width: topic_thickness_ + color: topic_color_ + } + } + } + + Flickable + { + id: topicSpace + anchors.top: parent.top; anchors.topMargin: label_height_ + 2* elements_spacing_ + anchors.left: parent.left + width: parent.width + height: parent.height - (label_height_ + 2* elements_spacing_) + interactive: false + clip: true + + contentWidth: topicsList.contentWidth + 100 + contentHeight: mainView.contentHeight + Rectangle {id: topic_connections; anchors.fill:parent; color: "transparent" } + + + ScrollBar.vertical: ScrollBar{ + id: custom_bar + width: 0 + + Connections { + target: vertical_bar + + function onPositionChanged(){ + custom_bar.position = vertical_bar.position + } + } + } + + MouseArea { + anchors.fill: parent + + onWheel: { + if (wheel.angleDelta.y > 0) { + topicView.contentX -= 30// topicView.scrollSpeed; + if (topicView.contentX < 0) { + topicView.contentX = 0; + } + } else { + topicView.contentX += 30// topicView.scrollSpeed; + if (topicView.contentX + topicView.width > topicView.contentWidth) { + topicView.contentX = topicView.contentWidth - topicView.width; + } + } + } + onClicked: mouse.accepted = false; + onPressed: mouse.accepted = false; + onReleased: mouse.accepted = false; + onDoubleClicked: mouse.accepted = false; + onPositionChanged: mouse.accepted = false; + onPressAndHold: mouse.accepted = false; + } + + } + + Connections + { + target: domainGraphLayout + + function onTopics_updated_() + { + topicView.create_connections() + } + + function onEndpoints_updated_() + { + topicView.create_connections() + } + + function onParticipants_updated_() + { + topicView.create_connections() + } + + function onProcesses_updated_() + { + topicView.create_connections() + } + + function onUsers_updated_() + { + topicView.create_connections() + } + + function onHosts_updated_() + { + topicView.create_connections() + } + } + + function create_connections() + { + for (var key in endpoint_topic_connections_) + { + var topic_id = endpoint_topic_connections_[key]["destination_id"] + if (topic_locations_[topic_id] != undefined) + { + if (!topic_painted_.includes(key)) + { + //console.log(key) + //console.log(endpoint_topic_connections_[key]["y"]) + var destination_x = topic_locations_[topic_id]["x"] + var input = {"x": 0 + ,"right_direction": endpoint_topic_connections_[key]["right_direction"] + ,"y": endpoint_topic_connections_[key]["y"] - (connection_thickness_ / 2) + ,"width": destination_x - endpoint_topic_connections_[key]["x"] - 4*elements_spacing_ + ,"height":connection_thickness_, "z":200, "left_margin": 2*elements_spacing_ + ,"arrow_color": topic_color_, "background_color": background_color.color } + var connection_bar = arrow_component.createObject(topic_connections, input) + topic_painted_[topic_painted_.length] = key; + } + } + } + } + } + + Flickable { + id: mainView + anchors.left: parent.left ; anchors.top: parent.top; anchors.bottom: parent.bottom + width: max_host_width_ + elements_spacing_ + anchors.topMargin: 2* elements_spacing_ + label_height_ + flickableDirection: Flickable.VerticalFlick + boundsBehavior: Flickable.StopAtBounds + z: 10 + + contentWidth: mainSpace.width + contentHeight: mainSpace.height + + ScrollBar.horizontal: ScrollBar { policy: ScrollBar.AlwaysOff } + ScrollBar.vertical: ScrollBar { + id: vertical_bar + policy: ScrollBar.AlwaysOn + visible: mainView.contentHeight > mainView.height + anchors.top: parent.top; anchors.topMargin: -elements_spacing_ + anchors.right: parent.right; anchors.rightMargin: parent.width - domainGraphLayout.width + hoverEnabled: true + z: 20 + + contentItem: Item { + implicitWidth: scrollbar_min_size_ + + Rectangle { + anchors.fill: parent + anchors.topMargin: 1 + anchors.rightMargin: 2 + anchors.leftMargin: 2 + radius: width / 2 + color: vertical_bar.pressed ? Theme.eProsimaLightBlue : Theme.lightGrey + } + } + + background: Item { + implicitWidth: scrollbar_max_size_ + + Rectangle { + anchors.fill: parent + color: vertical_bar.pressed ? Theme.lightGrey : Theme.grey + } + } + + Rectangle { + anchors.top: parent.top + height: 1 + width: parent.width + color: vertical_bar.pressed ? Theme.lightGrey : Theme.grey + } + } + + + Rectangle + { + id: mainSpace + anchors.top: parent.top + + width: hostsList.width + height: hostsList.height < domainGraphLayout.height - (label_height_ + 2*elements_spacing_) + ? domainGraphLayout.height - (label_height_ + 2*elements_spacing_) : hostsList.height + + Rectangle { + id: background_color + anchors.fill: parent + color: "white" + } + + Component { + id: arrow_component + GraphConnection{ + + } + } + + ListView + { + id: hostsList + model: domainGraphLayout.model["hosts"] + anchors.top: parent.top + anchors.left: parent.left; anchors.leftMargin: elements_spacing_ + interactive: false + spacing: elements_spacing_ + z: 20 + + Connections + { + target: domainGraphLayout + + function onUsers_updated_() + { + hostsList.onCountChanged() + } + } + + onCountChanged: + { + var listViewHeight = 0 + var listViewWidth = 0 + + // iterate over each element in the list item + for (var i = 0; i < hostsList.visibleChildren.length; i++) { + var min_width = hostsList.visibleChildren[i].width + for (var j = 0; j < hostsList.visibleChildren[i].visibleChildren.length; j++) + { + min_width = Math.max(min_width, hostsList.visibleChildren[i].visibleChildren[j].width) + } + listViewWidth = Math.max(listViewWidth, min_width) + max_host_width_ = Math.max(max_host_width_, listViewWidth) + max_host_width_ = Math.max(max_host_width_, (2*elements_spacing_)+max_user_width_) + hostsList.visibleChildren[i].width = max_host_width_ + } + + for (var c = 0; c < hostsList.count; c++) + { + hostsList.currentIndex = c + if (hostsList.currentItem != null) + { + listViewHeight += hostsList.currentItem.height + elements_spacing_ + } + } + + hostsList.height = listViewHeight + elements_spacing_ + hostsList.width = max_host_width_ + hosts_updated_() + } + + delegate: Item + { + height: host_tag.height + usersList.height + width: hostRowLayout.implicitWidth > max_host_width_ + ? hostRowLayout.implicitWidth + : max_host_width_ == 0 + ? hostRowLayout.implicitWidth + : max_host_width_ + + Rectangle + { + id: host_background + height: parent.height + width: parent.width + color: host_color_ + radius: radius_ + } + + Rectangle + { + id: host_tag + anchors.horizontalCenter: parent.horizontalCenter + implicitWidth: hostRowLayout.implicitWidth > max_host_width_ + ? hostRowLayout.implicitWidth + : max_host_width_ == 0 + ? hostRowLayout.implicitWidth + : max_host_width_ + height: label_height_ + color: host_color_ + radius: radius_ + + RowLayout { + id: hostRowLayout + anchors.centerIn: parent + + IconSVG { + visible: modelData["status"] != "ok" + name: "issues" + color: "white" + size: icon_size_ + Layout.leftMargin: first_indentation_ + } + IconSVG { + name: "host" + color: "white" + size: icon_size_ + Layout.leftMargin: modelData["status"] != "ok" ? 0 : first_indentation_ + } + Label { + text: modelData["alias"] + Layout.rightMargin: first_indentation_ + color: "white" + } + } + Rectangle { + visible: host_tag.implicitWidth < max_host_width_ + anchors.left: host_tag.right + anchors.verticalCenter: parent.verticalCenter + height: host_tag.height + width: max_host_width_ - host_tag.implicitWidth + color: host_background.color + radius: radius_ + } + MouseArea + { + anchors.fill: parent + onClicked: + { + console.log(modelData["alias"] + " clicked!") + } + } + } + + ListView + { + id: usersList + model: modelData["users"] + anchors.top: host_tag.bottom; anchors.topMargin: elements_spacing_ + anchors.left: parent.left; anchors.leftMargin: elements_spacing_ + anchors.right: parent.right; anchors.rightMargin: elements_spacing_ + interactive: false + spacing: elements_spacing_ + + Connections + { + target: domainGraphLayout + + function onProcesses_updated_() + { + usersList.onCountChanged() + } + } + + onCountChanged: + { + var listViewHeight = 0 + var listViewWidth = 0 + + // iterate over each element in the list item + for (var i = 0; i < usersList.visibleChildren.length; i++) { + var min_width = usersList.visibleChildren[i].width + for (var j = 0; j < usersList.visibleChildren[i].visibleChildren.length; j++) + { + min_width = Math.max(min_width, usersList.visibleChildren[i].visibleChildren[j].width) + } + listViewWidth = Math.max(listViewWidth, min_width) + max_user_width_ = Math.max(max_user_width_, listViewWidth) + max_user_width_ = Math.max(max_user_width_, (2*elements_spacing_)+max_process_width_) + usersList.visibleChildren[i].width = max_user_width_ + } + + for (var c = 0; c < usersList.count; c++) + { + usersList.currentIndex = c + if (usersList.currentItem != null) + { + listViewHeight += usersList.currentItem.height + elements_spacing_ + } + } + + usersList.height = listViewHeight + elements_spacing_ + usersList.width = max_user_width_ + users_updated_() + } + + delegate: Item + { + height: user_tag.height + processesList.height + width: userRowLayout.implicitWidth > max_user_width_ + ? userRowLayout.implicitWidth + : max_user_width_ == 0 + ? userRowLayout.implicitWidth + : max_user_width_ + + Rectangle + { + id: user_background + height: parent.height + width: parent.width + color: user_color_ + radius: radius_ + } + + Rectangle + { + id: user_tag + anchors.top: parent.top + anchors.horizontalCenter: parent.horizontalCenter + implicitWidth: userRowLayout.implicitWidth > max_user_width_ + ? userRowLayout.implicitWidth + : max_user_width_ == 0 + ? userRowLayout.implicitWidth + : max_user_width_ + height: label_height_ + color: user_color_ + radius: radius_ + + RowLayout { + id: userRowLayout + anchors.centerIn: parent + + IconSVG { + visible: modelData["status"] != "ok" + name: "issues" + color: "white" + size: icon_size_ + Layout.leftMargin: first_indentation_ + } + IconSVG { + name: "user" + color: "white" + size: icon_size_ + Layout.leftMargin: modelData["status"] != "ok" ? 0 : first_indentation_ + } + Label { + text: modelData["alias"] + Layout.rightMargin: first_indentation_ + color: "white" + } + } + Rectangle { + visible: user_tag.implicitWidth < max_user_width_ + anchors.left: user_tag.right + anchors.verticalCenter: parent.verticalCenter + height: user_tag.height + width: max_user_width_ - user_tag.implicitWidth + color: user_background.color + radius: radius_ + } + MouseArea + { + anchors.fill: parent + onClicked: + { + console.log(modelData["alias"] + " clicked!") + } + } + } + + ListView + { + id: processesList + model: modelData["processes"] + anchors.top: user_tag.bottom; anchors.topMargin: elements_spacing_ + anchors.left: parent.left; anchors.leftMargin: elements_spacing_ + anchors.right: parent.right; anchors.rightMargin: elements_spacing_ + interactive: false + spacing: elements_spacing_ + + Connections + { + target: domainGraphLayout + + function onParticipants_updated_() + { + processesList.onCountChanged() + } + } + + onCountChanged: + { + var listViewHeight = 0 + var listViewWidth = 0 + + // iterate over each element in the list item + for (var i = 0; i < processesList.visibleChildren.length; i++) { + listViewHeight += processesList.visibleChildren[i].height + elements_spacing_ + var min_width = processesList.visibleChildren[i].width + for (var j = 0; j < processesList.visibleChildren[i].visibleChildren.length; j++) + { + min_width = Math.max(min_width, processesList.visibleChildren[i].visibleChildren[j].width) + } + listViewWidth = Math.max(listViewWidth, min_width) + max_process_width_ = Math.max(max_process_width_, listViewWidth) + max_process_width_ = Math.max(max_process_width_, (2*elements_spacing_)+max_participant_width_) + processesList.visibleChildren[i].width = max_process_width_ + } + + processesList.height = listViewHeight + elements_spacing_ + processesList.width = max_process_width_ + processes_updated_() + } + + delegate: Item + { + height: process_tag.height + participantsList.height + width: processRowLayout.implicitWidth > max_process_width_ + ? processRowLayout.implicitWidth + : max_process_width_ == 0 + ? processRowLayout.implicitWidth + : max_process_width_ + + Rectangle + { + id: process_background + height: parent.height + width: parent.width + color: process_color_ + radius: radius_ + } + + Rectangle + { + id: process_tag + anchors.top: parent.top + anchors.horizontalCenter: parent.horizontalCenter + implicitWidth: processRowLayout.implicitWidth > max_process_width_ + ? processRowLayout.implicitWidth + : max_process_width_ == 0 + ? processRowLayout.implicitWidth + : max_process_width_ + height: label_height_ + color: process_color_ + radius: radius_ + + RowLayout { + id: processRowLayout + anchors.centerIn: parent + + IconSVG { + visible: modelData["status"] != "ok" + name: "issues" + color: "white" + size: icon_size_ + Layout.leftMargin: first_indentation_ + } + IconSVG { + name: "process" + color: "white" + size: icon_size_ + Layout.leftMargin: modelData["status"] != "ok" ? 0 : first_indentation_ + } + Label { + text: modelData["alias"] + Layout.rightMargin: first_indentation_ + color: "white" + } + } + Rectangle { + visible: process_tag.implicitWidth < max_process_width_ + anchors.left: process_tag.right + anchors.verticalCenter: parent.verticalCenter + height: process_tag.height + width: max_process_width_ - process_tag.implicitWidth + color: process_background.color + radius: radius_ + } + MouseArea + { + anchors.fill: parent + onClicked: + { + console.log(modelData["alias"] + " clicked!") + } + } + } + ListView + { + id: participantsList + model: modelData["participants"] + anchors.top: process_tag.bottom; anchors.topMargin: elements_spacing_ + anchors.left: parent.left; anchors.leftMargin: elements_spacing_ + anchors.right: parent.right; anchors.rightMargin: elements_spacing_ + interactive: false + spacing: elements_spacing_ + + Connections + { + target: domainGraphLayout + + function onEndpoints_updated_() + { + participantsList.onCountChanged() + } + } + + onCountChanged: + { + var listViewHeight = 0 + var listViewWidth = 0 + + // iterate over each element in the list item + for (var i = 0; i < participantsList.visibleChildren.length; i++) { + listViewHeight += participantsList.visibleChildren[i].height + elements_spacing_ + var min_width = participantsList.visibleChildren[i].width + for (var j = 0; j < participantsList.visibleChildren[i].visibleChildren.length; j++) + { + min_width = Math.max(min_width, participantsList.visibleChildren[i].visibleChildren[j].width) + } + listViewWidth = Math.max(listViewWidth, min_width) + max_participant_width_ = Math.max(max_participant_width_, listViewWidth) + max_participant_width_ = Math.max(max_participant_width_, (2*elements_spacing_)+max_endpoint_width_) + participantsList.visibleChildren[i].width = max_participant_width_ + } + + participantsList.height = listViewHeight + elements_spacing_ + participantsList.width = max_participant_width_ + participants_updated_() + } + + delegate: Item + { + height: participant_tag.height + endpointsList.height + width: participantRowLayout.implicitWidth > max_participant_width_ + ? participantRowLayout.implicitWidth + : max_participant_width_ == 0 + ? participantRowLayout.implicitWidth + : max_participant_width_ + + Rectangle + { + id: participant_background + height: parent.height + width: parent.width + color: participant_color_ + radius: radius_ + } + Rectangle + { + id: participant_tag + anchors.top: parent.top + anchors.horizontalCenter: parent.horizontalCenter + implicitWidth: participantRowLayout.implicitWidth > max_participant_width_ + ? participantRowLayout.implicitWidth + : max_participant_width_ == 0 + ? participantRowLayout.implicitWidth + : max_participant_width_ + height: label_height_ + color: participant_color_ + radius: radius_ + + RowLayout { + id: participantRowLayout + anchors.centerIn: parent + + IconSVG { + visible: modelData["status"] != "ok" + name: "issues" + size: modelData["status"] != "ok"? icon_size_ : 0 + Layout.leftMargin: modelData["status"] != "ok" ? first_indentation_ : 0 + } + IconSVG { + name: modelData["kind"] + size: icon_size_ + } + Label { + text: modelData["alias"] + Layout.rightMargin: spacing_icon_label_ + first_indentation_ + } + } + Rectangle { + visible: participant_tag.implicitWidth < max_participant_width_ + anchors.left: participant_tag.right + anchors.verticalCenter: parent.verticalCenter + height: participant_tag.height + width: max_participant_width_ - participant_tag.implicitWidth + color: participant_background.color + radius: radius_ + } + MouseArea + { + anchors.fill: parent + onClicked: + { + console.log(modelData["alias"] + " clicked!") + } + } + } + + ListView + { + id: endpointsList + model: modelData["endpoints"] + anchors.top: participant_tag.bottom; anchors.topMargin: elements_spacing_ + anchors.left: parent.left; anchors.leftMargin: elements_spacing_ + anchors.right: parent.right; anchors.rightMargin: elements_spacing_ + interactive: false + spacing: elements_spacing_ + + Connections + { + target: domainGraphLayout + + function onTopics_updated_() + { + endpointsList.onCountChanged() + } + } + + onCountChanged: + { + var listViewHeight = 0 + + // iterate over each element in the list item + for (var i = 0; i < endpointsList.visibleChildren.length; i++) { + listViewHeight += endpointsList.visibleChildren[i].height + elements_spacing_ + var min_width = endpointsList.visibleChildren[i].width + for (var j = 0; j < endpointsList.visibleChildren[i].visibleChildren.length; j++) + { + min_width = Math.max(min_width, endpointsList.visibleChildren[i].visibleChildren[j].width) + } + max_endpoint_width_ = Math.max(max_endpoint_width_, min_width) + endpointsList.visibleChildren[i].width = max_endpoint_width_ + } + + for (var c = 0; c < endpointsList.count; c++) + { + endpointsList.currentIndex = c + endpointsList.currentItem.record_connection() + } + + endpointsList.height = listViewHeight + elements_spacing_ + endpointsList.width = max_endpoint_width_ + endpoints_updated_() + } + + delegate: Item + { + id: endpointComponent + width: endpointRowLayout.implicitWidth > max_endpoint_width_ + ? endpointRowLayout.implicitWidth + : max_endpoint_width_ == 0 + ? endpointRowLayout.implicitWidth + : max_endpoint_width_ + height: endpoint_height_ + + function record_connection() + { + var globalCoordinates = endpointComponent.mapToItem(mainSpace, 0, 0) + var src_x = globalCoordinates.x + endpointComponent.width + var src_y = modelData["accum_y"] + (endpointComponent.height / 2) + var left_direction = modelData["kind"] == "datareader" + var right_direction = modelData["kind"] == "datawriter" + + endpoint_topic_connections_[modelData["id"]] = { + "id": modelData["id"], "left_direction": left_direction, + "right_direction": right_direction, "x": src_x, "y": src_y, + "destination_id": modelData["topic"] + } + } + + Rectangle + { + id: endpoint_background + width: parent.width + height: endpoint_height_ + color: modelData["kind"] == "datareader" ? reader_color_ : writer_color_ + radius: radius_ + } + Rectangle + { + id: endpoint_tag + anchors.top: parent.top + anchors.horizontalCenter: parent.horizontalCenter + implicitWidth: endpointRowLayout.implicitWidth > max_endpoint_width_ + ? endpointRowLayout.implicitWidth + : max_endpoint_width_ == 0 + ? endpointRowLayout.implicitWidth + : max_endpoint_width_ + height: endpoint_height_ + color: endpoint_background.color + radius: radius_ + + RowLayout { + id: endpointRowLayout + anchors.centerIn: parent + + IconSVG { + visible: modelData["status"] != "ok" + name: "issues" + size: modelData["status"] != "ok"? icon_size_ : 0 + Layout.leftMargin: modelData["status"] != "ok" ? first_indentation_ : 0 + } + IconSVG { + name: modelData["kind"] + size: icon_size_ + } + Label { + text: modelData["alias"] + Layout.rightMargin: spacing_icon_label_ + first_indentation_ + } + } + MouseArea + { + anchors.fill: parent + onClicked: + { + console.log(modelData["alias"] + " clicked!") + } + } + } + Rectangle { + visible: endpoint_tag.implicitWidth < max_endpoint_width_ + anchors.left: endpoint_tag.right + anchors.verticalCenter: parent.verticalCenter + height: endpoint_tag.height + width: max_endpoint_width_ - endpoint_tag.implicitWidth + color: endpoint_background.color + radius: radius_ + } + } + } + } + } + } + } + } + } + } + } + + Connections + { + target: domainGraphLayout + + function onTopics_updated_() + { + mainSpace.create_connections() + } + + function onEndpoints_updated_() + { + mainSpace.create_connections() + } + + function onParticipants_updated_() + { + mainSpace.create_connections() + } + + function onProcesses_updated_() + { + mainSpace.create_connections() + } + + function onUsers_updated_() + { + mainSpace.create_connections() + } + + function onHosts_updated_() + { + mainSpace.create_connections() + } + } + + function create_connections() + { + for (var key in endpoint_topic_connections_) + { + var topic_id = endpoint_topic_connections_[key]["destination_id"] + if (topic_locations_[topic_id] != undefined) + { + if (!entity_painted_.includes(key)) + { + //console.log(key) + //console.log(JSON.stringify(endpoint_topic_connections_[key], null, 2)) + var destination_x = topic_locations_[topic_id]["x"] + var input = {"x": endpoint_topic_connections_[key]["x"] + ,"left_direction": endpoint_topic_connections_[key]["left_direction"] + ,"y": endpoint_topic_connections_[key]["y"] - (connection_thickness_ / 2) + ,"width": 4*elements_spacing_ + ,"height":connection_thickness_, "z":200 + ,"arrow_color": topic_color_, "background_color": background_color.color } + var connection_bar = arrow_component.createObject(mainSpace, input) + entity_painted_[entity_painted_.length] = key + } + } + } + } + } + } + + Rectangle { + anchors.top: parent.top + anchors.left: parent.left + height: 2* elements_spacing_ + label_height_ + width: max_host_width_ +2* elements_spacing_ + color: "white" + z: 12 + + Button{ + width: parent.width /2 + height: label_height_ + anchors.centerIn: parent + text: "Refresh" + + onClicked:{ + load_model() + } + } + } + + Rectangle { + anchors.bottom: parent.bottom + anchors.left: parent.left + height: elements_spacing_ + width: max_host_width_ + elements_spacing_ + color: "white" + z: 14 + } + + + function load_model(new_model) + { + // remove drawn connections + for (var i = 0; i < mainSpace.children.length; i++) + { + if (mainSpace.children[i].left_margin != undefined) + { + mainSpace.children[i].destroy() + } + } + for (var i = 0; i < topic_connections.children.length; i++) + { + if (topic_connections.children[i].left_margin != undefined) + { + topic_connections.children[i].destroy() + } + } + // clear internal models + topic_locations_ = {} + endpoint_topic_connections_ = {} + entity_painted_ = [] + topic_painted_ = [] + + // Obtain model from backend, and parse from string to JSON + var model_string = controller.get_domain_view_graph(entity_id) + var new_model = JSON.parse(model_string) + + // transform indexed model to array model (arrays required for the listviews) + var new_topics = [] + for (var topic in new_model["topics"]) + { + new_topics[new_topics.length] = { + "id":topic, + "kind":new_model["topics"][topic]["kind"], + "alias":new_model["topics"][topic]["alias"],} + } + var accum_y = 0 + var new_hosts = [] + for (var host in new_model["hosts"]) + { + accum_y += label_height_ + elements_spacing_ + var new_users = [] + for (var user in new_model["hosts"][host]["users"]) + { + accum_y += label_height_ + elements_spacing_ + var new_processes = [] + for (var process in new_model["hosts"][host]["users"][user]["processes"]) + { + accum_y += label_height_ + elements_spacing_ + var new_participants = [] + for (var participant in new_model["hosts"][host]["users"][user]["processes"][process]["participants"]) + { + accum_y += label_height_ + elements_spacing_ + var new_endpoints = [] + for (var endpoint in new_model["hosts"][host]["users"][user]["processes"][process]["participants"][participant]["endpoints"]) + { + new_endpoints[new_endpoints.length] = { + "id":endpoint, + "kind":new_model["hosts"][host]["users"][user]["processes"][process]["participants"][participant]["endpoints"][endpoint]["kind"], + "alias":new_model["hosts"][host]["users"][user]["processes"][process]["participants"][participant]["endpoints"][endpoint]["alias"], + "status":new_model["hosts"][host]["users"][user]["processes"][process]["participants"][participant]["endpoints"][endpoint]["status"], + "topic":new_model["hosts"][host]["users"][user]["processes"][process]["participants"][participant]["endpoints"][endpoint]["topic"], + "accum_y":accum_y + } + accum_y += endpoint_height_ + elements_spacing_ + } + new_participants[new_participants.length] = { + "id":participant, + "kind":new_model["hosts"][host]["users"][user]["processes"][process]["participants"][participant]["kind"], + "alias":new_model["hosts"][host]["users"][user]["processes"][process]["participants"][participant]["alias"], + "status":new_model["hosts"][host]["users"][user]["processes"][process]["participants"][participant]["status"], + "app_id":new_model["hosts"][host]["users"][user]["processes"][process]["participants"][participant]["app_id"], + "app_metadata":new_model["hosts"][host]["users"][user]["processes"][process]["participants"][participant]["app_metadata"], + "endpoints":new_endpoints + } + accum_y += elements_spacing_ + } + new_processes[new_processes.length] = { + "id":process, + "kind":new_model["hosts"][host]["users"][user]["processes"][process]["kind"], + "alias":new_model["hosts"][host]["users"][user]["processes"][process]["alias"], + "pid": new_model["hosts"][host]["users"][user]["processes"][process]["pid"], + "status":new_model["hosts"][host]["users"][user]["processes"][process]["status"], + "participants":new_participants + } + accum_y += elements_spacing_ + } + new_users[new_users.length] = { + "id":user, + "kind":new_model["hosts"][host]["users"][user]["kind"], + "alias":new_model["hosts"][host]["users"][user]["alias"], + "status":new_model["hosts"][host]["users"][user]["status"], + "processes":new_processes + } + accum_y += elements_spacing_ + } + new_hosts[new_hosts.length] = { + "id":host, + "kind":new_model["hosts"][host]["kind"], + "alias":new_model["hosts"][host]["alias"], + "status":new_model["hosts"][host]["status"], + "users":new_users + } + accum_y += elements_spacing_ + } + model = { + "kind": new_model["kind"], + "domain": new_model["domain"], + "topics": new_topics, + "hosts": new_hosts, + } + + // Update tab name with selected domain id + domainGraphLayout.update_tab_name("Domain " + new_model["domain"] + " View") + + // Update visual elements + resize_elements_() + } + + function remove_connections() + { + + } +} diff --git a/qml/GraphConnection.qml b/qml/GraphConnection.qml new file mode 100644 index 00000000..e09c4eed --- /dev/null +++ b/qml/GraphConnection.qml @@ -0,0 +1,94 @@ +import QtQuick 2.0 + +Item { + // public property + property bool left_direction: false + property bool right_direction: false + property int left_margin: 0 + property string arrow_color: Theme.grey + property string background_color: "white" + + // The index of corner for the triangle to be attached + property int corner: 0 + + Rectangle { + id: background_arrow + visible: left_margin != 0 + anchors.top: parent.top; anchors.bottom: parent.bottom + anchors.topMargin: -2; anchors.bottomMargin: -2 + anchors.left: parent.left; anchors.right: parent.right + anchors.leftMargin: left_margin; anchors.rightMargin: left_margin; + color: background_color + } + + Rectangle { + id: base_arrow + anchors.fill: parent + color: arrow_color + } + + + Item { + id: left_arrow + visible: left_direction + height: 30 + width: 30 + anchors.verticalCenter: parent.verticalCenter + anchors.left: parent.left; anchors.leftMargin: -parent.height /2 + + Canvas { + id: left_canvas + + anchors.centerIn: parent + + height: parent.height / 2 + width: parent.width + + antialiasing:true; smooth:true + + onPaint: { + var ctx = left_canvas.getContext('2d') + + ctx.strokeStyle = arrow_color + ctx.lineWidth = left_canvas.width * 0.1 + ctx.beginPath() + ctx.moveTo(left_canvas.width, left_canvas.height * 0.05) + ctx.lineTo(0, left_canvas.height / 2) + ctx.lineTo(left_canvas.width, left_canvas.height * 0.95) + ctx.stroke() + } + } + } + + Item { + id: right_arrow + visible: right_direction + height: 30 + width: 30 + anchors.verticalCenter: parent.verticalCenter + anchors.right: parent.right; anchors.rightMargin: parent.height /2 + + Canvas { + id: right_canvas + + anchors.centerIn: parent + + height: parent.height / 2 + width: parent.width + + antialiasing:true; smooth:true + + onPaint: { + var ctx = right_canvas.getContext('2d') + + ctx.strokeStyle = arrow_color + ctx.lineWidth = right_canvas.width * 0.1 + ctx.beginPath() + ctx.moveTo(0, right_canvas.height * 0.05) + ctx.lineTo(right_canvas.width, right_canvas.height / 2) + ctx.lineTo(0, right_canvas.height * 0.95) + ctx.stroke() + } + } + } +} diff --git a/qml/TabLayout.qml b/qml/TabLayout.qml index aa1aaf52..2bead0e1 100644 --- a/qml/TabLayout.qml +++ b/qml/TabLayout.qml @@ -30,8 +30,9 @@ Item { property int last_index_: 1 // force unique idx on QML components property var tab_model_: [{"idx":0, "title":"New Tab", "stack_id": 0}] // tab model for tab bad and tab management property bool disable_chart_selection: false // flag to disable multiple chart view tabs + signal open_domain_view(int entity_id, int domain_id) - // Read only design properties + // Read only design properties readonly property int max_tabs_: 15 readonly property int max_tab_size_: 180 readonly property int min_tab_size_: 120 @@ -52,6 +53,8 @@ Item { } ChartsLayout { + z: 1 + visible: disable_chart_selection id: chartsLayout anchors.fill: stack_layout onFullScreenChanged: { @@ -59,6 +62,19 @@ Item { } } + Component { + id: domainGraphLayout_component + + DomainGraphLayout + { + id: domainGraphLayout + + onUpdate_tab_name: { + tabLayout.tab_model_[current_]["title"] = new_name + } + } + } + ListView { id: tab_list anchors.top: parent.top @@ -143,6 +159,7 @@ Item { // Add new tab button Rectangle { id: add_new_tab_button + z: 99 visible: tabLayout.tab_model_.length < max_tabs_ anchors.right: remain_width_rect.left anchors.verticalCenter: tab_list.verticalCenter @@ -175,6 +192,7 @@ Item { // remain space in tab bar handled by this component Rectangle { id: remain_width_rect + z: 98 width: tabLayout.width - add_new_tab_button.width - tab_list.width; height: tabs_height_ anchors.right: tabLayout.right anchors.verticalCenter: tab_list.verticalCenter @@ -248,29 +266,92 @@ Item { anchors.verticalCenter: parent.verticalCenter text: "Domain View" onClicked: { - tabLayout.tab_model_[current_]["title"]="Domain View" - if (stack.deep > 1) - { - stack.pop() - } - stack.push(domainViewLayout) - refresh_layout(current_) + domain_id_dialog.open() } } } + + Connections { + target: tabLayout + + function onOpen_domain_view(entity_id, domain_id) { + if (stack.deep > 1) + { + stack.pop() + } + + var new_domain_graph = domainGraphLayout_component.createObject(null, + {"id": tabLayout.tab_model_[current_]["stack_id"], + "entity_id":entity_id , "domain_id":domain_id }) + stack.push(new_domain_graph) + refresh_layout(current_) + } + } } } } } } - Component { - id: domainViewLayout + Dialog { + id: domain_id_dialog + + property bool enable_ok_button: false // disable OK button until user selects domain id - Rectangle{ - Text{ - text: "Here would be the domain view" + x: (parent.width - width) / 2 + y: (parent.height - height) / 2 + + width: 300 + + modal: true + title: "Select DDS Domain" + + footer: DialogButtonBox { + id: buttons + standardButtons: Dialog.Ok | Dialog.Cancel + } + + Component.onCompleted: { + controller.update_available_entity_ids("Domain", "getDataDialogSourceEntityId") + } + + onAboutToShow: { + custom_combobox.currentIndex = -1 + controller.update_available_entity_ids("Domain", "getDataDialogSourceEntityId") + custom_combobox.recalculateWidth() + enable_ok_button = false + buttons.standardButton(Dialog.Ok).enabled = false + } + + onEnable_ok_buttonChanged: { + buttons.standardButton(Dialog.Ok).enabled = domain_id_dialog.enable_ok_button + } + + AdaptiveComboBox { + id: custom_combobox + textRole: "nameId" + valueRole: "id" + displayText: currentIndex === -1 + ? ("Please choose a Domain ID") + : currentText + model: entityModelFirst + + Component.onCompleted: + { + controller.update_available_entity_ids("Domain", "getDataDialogSourceEntityId") + currentIndex = -1 + custom_combobox.recalculateWidth() } + + onActivated: { + domain_id_dialog.enable_ok_button = true + custom_combobox.recalculateWidth() + } + } + + onAccepted: + { + open_domain_view(0, 0) } } From 476037cce23aab6ebd857a51e87d58006fa02e6a Mon Sep 17 00:00:00 2001 From: JesusPoderoso Date: Mon, 11 Sep 2023 10:43:59 +0200 Subject: [PATCH 03/36] Refs #19532: Implement Monitor <---> Backend connection Signed-off-by: JesusPoderoso Refs #19308: Establish connection Signed-off-by: JesusPoderoso Refs #19308: Add try-catch in SyncBackendConnection call Signed-off-by: JesusPoderoso Refs #19308: Catch Exception when no graph Signed-off-by: JesusPoderoso --- include/fastdds_monitor/Controller.h | 5 + include/fastdds_monitor/Engine.h | 4 + .../backend/SyncBackendConnection.h | 4 + qml/DomainGraphLayout.qml | 225 +++++++++++------- qml/TabLayout.qml | 4 +- src/Controller.cpp | 12 + src/Engine.cpp | 6 + src/backend/SyncBackendConnection.cpp | 13 + 8 files changed, 180 insertions(+), 93 deletions(-) diff --git a/include/fastdds_monitor/Controller.h b/include/fastdds_monitor/Controller.h index c47017a8..ab8ee25b 100644 --- a/include/fastdds_monitor/Controller.h +++ b/include/fastdds_monitor/Controller.h @@ -260,6 +260,11 @@ public slots: quint64 series_id, quint64 new_max_point); + + //! Request to backend the latest domain view JSON to build the graph + QString get_domain_view_graph ( + QString domain_id); + signals: //! Signal to show the Error Dialog diff --git a/include/fastdds_monitor/Engine.h b/include/fastdds_monitor/Engine.h index f194cdc5..8f534938 100644 --- a/include/fastdds_monitor/Engine.h +++ b/include/fastdds_monitor/Engine.h @@ -495,6 +495,10 @@ class Engine : public QQmlApplicationEngine quint64 series_id, quint64 new_max_point); + //! Request to backend the latest domain view JSON to build the graph + backend::Graph get_domain_view_graph ( + const backend::EntityId& domain_id); + signals: /** diff --git a/include/fastdds_monitor/backend/SyncBackendConnection.h b/include/fastdds_monitor/backend/SyncBackendConnection.h index bfda3e27..ee4ab907 100644 --- a/include/fastdds_monitor/backend/SyncBackendConnection.h +++ b/include/fastdds_monitor/backend/SyncBackendConnection.h @@ -257,6 +257,10 @@ class SyncBackendConnection std::vector& source_ids, std::vector& target_ids); + //! Request to backend the latest domain view JSON to build the graph + Graph get_domain_view_graph ( + const EntityId& domain_id); + protected: void change_unit_magnitude( diff --git a/qml/DomainGraphLayout.qml b/qml/DomainGraphLayout.qml index 2cef19c4..7741fe80 100644 --- a/qml/DomainGraphLayout.qml +++ b/qml/DomainGraphLayout.qml @@ -125,7 +125,7 @@ Item ListView { id: topicsList - model: domainGraphLayout.model["topics"] + model: domainGraphLayout.model ? domainGraphLayout.model["topics"] : undefined anchors.left: parent.left; anchors.leftMargin: 2 * elements_spacing_ anchors.top: parent.top; anchors.topMargin: elements_spacing_; anchors.bottom: parent.bottom; anchors.bottomMargin: scrollbar_max_size_+ elements_spacing_ @@ -322,8 +322,6 @@ Item { if (!topic_painted_.includes(key)) { - //console.log(key) - //console.log(endpoint_topic_connections_[key]["y"]) var destination_x = topic_locations_[topic_id]["x"] var input = {"x": 0 ,"right_direction": endpoint_topic_connections_[key]["right_direction"] @@ -417,7 +415,7 @@ Item ListView { id: hostsList - model: domainGraphLayout.model["hosts"] + model: domainGraphLayout.model ? domainGraphLayout.model["hosts"] : undefined anchors.top: parent.top anchors.left: parent.left; anchors.leftMargin: elements_spacing_ interactive: false @@ -1081,8 +1079,6 @@ Item { if (!entity_painted_.includes(key)) { - //console.log(key) - //console.log(JSON.stringify(endpoint_topic_connections_[key], null, 2)) var destination_x = topic_locations_[topic_id]["x"] var input = {"x": endpoint_topic_connections_[key]["x"] ,"left_direction": endpoint_topic_connections_[key]["left_direction"] @@ -1108,9 +1104,12 @@ Item z: 12 Button{ - width: parent.width /2 + width: parent.width /2 < 150 ? 150 : parent.width /2 height: label_height_ - anchors.centerIn: parent + anchors.top: parent.top; anchors.topMargin: elements_spacing_ + anchors.left: parent.left + anchors.leftMargin: max_host_width_/2 + elements_spacing_ - width /2 < 100 ? 5* elements_spacing_ + : max_host_width_/2 + elements_spacing_ - width /2 text: "Refresh" onClicked:{ @@ -1128,128 +1127,170 @@ Item z: 14 } + Rectangle { + anchors.fill: parent + color: "transparent" - function load_model(new_model) - { - // remove drawn connections - for (var i = 0; i < mainSpace.children.length; i++) - { - if (mainSpace.children[i].left_margin != undefined) - { - mainSpace.children[i].destroy() - } - } - for (var i = 0; i < topic_connections.children.length; i++) - { - if (topic_connections.children[i].left_margin != undefined) - { - topic_connections.children[i].destroy() - } + Text { + id: emptyScreenLabel + visible: true + width: parent.width + height: parent.height + anchors.verticalCenter: parent.verticalCenter + anchors.horizontalCenter: parent.horizontalCenter + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + fontSizeMode: Text.Fit + minimumPixelSize: 10 + font.pointSize: 20 + font.bold: true + color: Theme.x11Grey + text: "Oops... no data to display" } + } + + + function load_model() + { // clear internal models topic_locations_ = {} endpoint_topic_connections_ = {} entity_painted_ = [] topic_painted_ = [] + remove_connections() // Obtain model from backend, and parse from string to JSON var model_string = controller.get_domain_view_graph(entity_id) - var new_model = JSON.parse(model_string) - // transform indexed model to array model (arrays required for the listviews) + // obtained hosts and topics var new_topics = [] - for (var topic in new_model["topics"]) - { - new_topics[new_topics.length] = { - "id":topic, - "kind":new_model["topics"][topic]["kind"], - "alias":new_model["topics"][topic]["alias"],} - } - var accum_y = 0 var new_hosts = [] - for (var host in new_model["hosts"]) + + // Check if obtained graph is not empty + if (model_string.length !== 0) { - accum_y += label_height_ + elements_spacing_ - var new_users = [] - for (var user in new_model["hosts"][host]["users"]) + // Parse model from string to JSON + var new_model = JSON.parse(model_string) + + // transform indexed model to array model (arrays required for the listviews) + for (var topic in new_model["topics"]) + { + new_topics[new_topics.length] = { + "id":topic, + "kind":new_model["topics"][topic]["kind"], + "alias":new_model["topics"][topic]["alias"],} + } + var accum_y = 0 + for (var host in new_model["hosts"]) { accum_y += label_height_ + elements_spacing_ - var new_processes = [] - for (var process in new_model["hosts"][host]["users"][user]["processes"]) + var new_users = [] + for (var user in new_model["hosts"][host]["users"]) { accum_y += label_height_ + elements_spacing_ - var new_participants = [] - for (var participant in new_model["hosts"][host]["users"][user]["processes"][process]["participants"]) + var new_processes = [] + for (var process in new_model["hosts"][host]["users"][user]["processes"]) { accum_y += label_height_ + elements_spacing_ - var new_endpoints = [] - for (var endpoint in new_model["hosts"][host]["users"][user]["processes"][process]["participants"][participant]["endpoints"]) + var new_participants = [] + for (var participant in new_model["hosts"][host]["users"][user]["processes"][process]["participants"]) { - new_endpoints[new_endpoints.length] = { - "id":endpoint, - "kind":new_model["hosts"][host]["users"][user]["processes"][process]["participants"][participant]["endpoints"][endpoint]["kind"], - "alias":new_model["hosts"][host]["users"][user]["processes"][process]["participants"][participant]["endpoints"][endpoint]["alias"], - "status":new_model["hosts"][host]["users"][user]["processes"][process]["participants"][participant]["endpoints"][endpoint]["status"], - "topic":new_model["hosts"][host]["users"][user]["processes"][process]["participants"][participant]["endpoints"][endpoint]["topic"], - "accum_y":accum_y + accum_y += label_height_ + elements_spacing_ + var new_endpoints = [] + for (var endpoint in new_model["hosts"][host]["users"][user]["processes"][process]["participants"][participant]["endpoints"]) + { + new_endpoints[new_endpoints.length] = { + "id":endpoint, + "kind":new_model["hosts"][host]["users"][user]["processes"][process]["participants"][participant]["endpoints"][endpoint]["kind"], + "alias":new_model["hosts"][host]["users"][user]["processes"][process]["participants"][participant]["endpoints"][endpoint]["alias"], + "status":new_model["hosts"][host]["users"][user]["processes"][process]["participants"][participant]["endpoints"][endpoint]["status"], + "topic":new_model["hosts"][host]["users"][user]["processes"][process]["participants"][participant]["endpoints"][endpoint]["topic"], + "accum_y":accum_y + } + accum_y += endpoint_height_ + elements_spacing_ + } + new_participants[new_participants.length] = { + "id":participant, + "kind":new_model["hosts"][host]["users"][user]["processes"][process]["participants"][participant]["kind"], + "alias":new_model["hosts"][host]["users"][user]["processes"][process]["participants"][participant]["alias"], + "status":new_model["hosts"][host]["users"][user]["processes"][process]["participants"][participant]["status"], + "app_id":new_model["hosts"][host]["users"][user]["processes"][process]["participants"][participant]["app_id"], + "app_metadata":new_model["hosts"][host]["users"][user]["processes"][process]["participants"][participant]["app_metadata"], + "endpoints":new_endpoints } - accum_y += endpoint_height_ + elements_spacing_ + accum_y += elements_spacing_ } - new_participants[new_participants.length] = { - "id":participant, - "kind":new_model["hosts"][host]["users"][user]["processes"][process]["participants"][participant]["kind"], - "alias":new_model["hosts"][host]["users"][user]["processes"][process]["participants"][participant]["alias"], - "status":new_model["hosts"][host]["users"][user]["processes"][process]["participants"][participant]["status"], - "app_id":new_model["hosts"][host]["users"][user]["processes"][process]["participants"][participant]["app_id"], - "app_metadata":new_model["hosts"][host]["users"][user]["processes"][process]["participants"][participant]["app_metadata"], - "endpoints":new_endpoints + new_processes[new_processes.length] = { + "id":process, + "kind":new_model["hosts"][host]["users"][user]["processes"][process]["kind"], + "alias":new_model["hosts"][host]["users"][user]["processes"][process]["alias"], + "pid": new_model["hosts"][host]["users"][user]["processes"][process]["pid"], + "status":new_model["hosts"][host]["users"][user]["processes"][process]["status"], + "participants":new_participants } accum_y += elements_spacing_ } - new_processes[new_processes.length] = { - "id":process, - "kind":new_model["hosts"][host]["users"][user]["processes"][process]["kind"], - "alias":new_model["hosts"][host]["users"][user]["processes"][process]["alias"], - "pid": new_model["hosts"][host]["users"][user]["processes"][process]["pid"], - "status":new_model["hosts"][host]["users"][user]["processes"][process]["status"], - "participants":new_participants + new_users[new_users.length] = { + "id":user, + "kind":new_model["hosts"][host]["users"][user]["kind"], + "alias":new_model["hosts"][host]["users"][user]["alias"], + "status":new_model["hosts"][host]["users"][user]["status"], + "processes":new_processes } accum_y += elements_spacing_ } - new_users[new_users.length] = { - "id":user, - "kind":new_model["hosts"][host]["users"][user]["kind"], - "alias":new_model["hosts"][host]["users"][user]["alias"], - "status":new_model["hosts"][host]["users"][user]["status"], - "processes":new_processes + new_hosts[new_hosts.length] = { + "id":host, + "kind":new_model["hosts"][host]["kind"], + "alias":new_model["hosts"][host]["alias"], + "status":new_model["hosts"][host]["status"], + "users":new_users } accum_y += elements_spacing_ } - new_hosts[new_hosts.length] = { - "id":host, - "kind":new_model["hosts"][host]["kind"], - "alias":new_model["hosts"][host]["alias"], - "status":new_model["hosts"][host]["status"], - "users":new_users + model = { + "kind": new_model["kind"], + "domain": new_model["domain"], + "topics": new_topics, + "hosts": new_hosts, } - accum_y += elements_spacing_ - } - model = { - "kind": new_model["kind"], - "domain": new_model["domain"], - "topics": new_topics, - "hosts": new_hosts, - } - // Update tab name with selected domain id - domainGraphLayout.update_tab_name("Domain " + new_model["domain"] + " View") + // Update tab name with selected domain id + domainGraphLayout.update_tab_name("Domain " + new_model["domain"] + " View") + + // Update visual elements + resize_elements_() - // Update visual elements - resize_elements_() + // hide empty screen label + emptyScreenLabel.visible = false + } + // print error message + if (new_topics.length === 0 && new_hosts.length === 0) + { + // Update tab name with selected domain id + domainGraphLayout.update_tab_name("Domain " + domain_id + " View") + + // display empty screen label + emptyScreenLabel.visible = true + } } + // remove drawn connections function remove_connections() { - + for (var i = 0; i < mainSpace.children.length; i++) + { + if (mainSpace.children[i].left_margin != undefined) + { + mainSpace.children[i].destroy() + } + } + for (var i = 0; i < topic_connections.children.length; i++) + { + if (topic_connections.children[i].left_margin != undefined) + { + topic_connections.children[i].destroy() + } + } } } diff --git a/qml/TabLayout.qml b/qml/TabLayout.qml index 2bead0e1..0d07e5cd 100644 --- a/qml/TabLayout.qml +++ b/qml/TabLayout.qml @@ -351,7 +351,9 @@ Item { onAccepted: { - open_domain_view(0, 0) + open_domain_view( + entityModelFirst.get(custom_combobox.currentIndex).id, + entityModelFirst.get(custom_combobox.currentIndex).name) } } diff --git a/src/Controller.cpp b/src/Controller.cpp index 4549e0a2..27ce9de4 100644 --- a/src/Controller.cpp +++ b/src/Controller.cpp @@ -21,6 +21,7 @@ #include #include +#include #include #include @@ -316,3 +317,14 @@ void Controller::change_max_points( { return engine_->change_max_points(chartbox_id, series_id, new_max_point); } + +QString Controller::get_domain_view_graph( + QString entity_id) +{ + backend::Graph domain_view = engine_->get_domain_view_graph(backend::models_id_to_backend_id(entity_id)); + if (domain_view != nullptr) + { + return QString::fromUtf8(domain_view.dump().data(), int(domain_view.dump().size())); + } + return ""; +} diff --git a/src/Engine.cpp b/src/Engine.cpp index 73eef257..873d9461 100644 --- a/src/Engine.cpp +++ b/src/Engine.cpp @@ -1286,6 +1286,12 @@ bool Engine::data_kind_has_target( return backend_connection_.data_kind_has_target(backend::string_to_data_kind(data_kind)); } +backend::Graph Engine::get_domain_view_graph ( + const backend::EntityId& domain_id) +{ + return backend_connection_.get_domain_view_graph(domain_id); +} + bool EntityClicked::is_set() const { return kind != backend::EntityKind::INVALID; diff --git a/src/backend/SyncBackendConnection.cpp b/src/backend/SyncBackendConnection.cpp index b8b7362c..3b56a5de 100644 --- a/src/backend/SyncBackendConnection.cpp +++ b/src/backend/SyncBackendConnection.cpp @@ -765,6 +765,19 @@ bool SyncBackendConnection::build_source_target_entities_vectors( return two_entities_data; } +Graph SyncBackendConnection::get_domain_view_graph ( + const EntityId& domain_id) +{ + try + { + return StatisticsBackend::get_domain_view_graph(domain_id); + } + catch (BadParameter) + { + return nullptr; + } +} + void SyncBackendConnection::change_unit_magnitude( std::vector& data, DataKind data_kind) From 4b10f7aa103a98b40b58a990592cd181e683e7b5 Mon Sep 17 00:00:00 2001 From: JesusPoderoso Date: Mon, 11 Sep 2023 16:40:40 +0200 Subject: [PATCH 04/36] Refs #19532: Fix issues, visual bugs and improvements Signed-off-by: JesusPoderoso Refs #19308: Fix communication arrows visual bugs Signed-off-by: JesusPoderoso Refs #19308: Fix icons and text spacing in entity boxes Signed-off-by: JesusPoderoso Refs: Remove vulcanexus blue from color palette Signed-off-by: JesusPoderoso Refs #19308: Fix entity order generation to optimize resizing Signed-off-by: JesusPoderoso Refs #19308: Add comments to QML code Signed-off-by: JesusPoderoso Refs #19308: Improve backend calls Signed-off-by: JesusPoderoso Refs #19308: Fix overlapping visual bugs Signed-off-by: JesusPoderoso Refs #19308: Fix resizing with large alias Signed-off-by: JesusPoderoso Refs #19308: Fix single topic (and no entities) data representation Signed-off-by: JesusPoderoso Refs #19308: Fix tab issue with multiple domains Signed-off-by: JesusPoderoso Refs #19308: Display domain info when navigating through different domain views in tabs Signed-off-by: JesusPoderoso Refs #19308: Display entity and topic data when clicked in the graph Signed-off-by: JesusPoderoso Refs #19513: Improve Domain ID Dialog values Signed-off-by: JesusPoderoso Refs #19513: Add metatraffic filter in the graph view Signed-off-by: JesusPoderoso Refs #19533: Improve arrow drawing Signed-off-by: JesusPoderoso --- imports/Theme/Theme.qml | 1 - qml/DomainGraphLayout.qml | 742 ++++++++++++++++---------- qml/GraphConnection.qml | 90 +++- qml/TabLayout.qml | 121 +++-- src/Controller.cpp | 6 +- src/backend/SyncBackendConnection.cpp | 7 +- 6 files changed, 619 insertions(+), 348 deletions(-) diff --git a/imports/Theme/Theme.qml b/imports/Theme/Theme.qml index 8207f34c..3c5655c2 100644 --- a/imports/Theme/Theme.qml +++ b/imports/Theme/Theme.qml @@ -14,7 +14,6 @@ QtObject { readonly property color darkGrey: "#3e3e3e" readonly property color x11Grey: "#BEBEBE" readonly property color whiteSmoke: "#f5f5f5" - readonly property color vulcanexusBlue: "#DAF0F6" // readonly property int smallSize: 10 // readonly property int largeSize: 16 diff --git a/qml/DomainGraphLayout.qml b/qml/DomainGraphLayout.qml index 7741fe80..5d6513a3 100644 --- a/qml/DomainGraphLayout.qml +++ b/qml/DomainGraphLayout.qml @@ -26,26 +26,37 @@ Item id: domainGraphLayout // Public properties - property var model: {} - property int entity_id: 0 // entity id associated to the domain id - property int domain_id: 0 // domain id + property var model: {} // domain view graph JSON model + property int entity_id // entity id associated to the domain id + property int domain_id // domain id + required property string component_id // mandatory to be included when object created // Public signals - signal update_tab_name(string new_name) + signal update_tab_name(string new_name) // Update tab name based on selected domain id // Private properties - property var topic_locations_: {} - property var endpoint_topic_connections_: {} - property int max_host_width_: 0 - property int max_user_width_: 0 - property int max_process_width_: 0 - property int max_participant_width_: 0 - property int max_endpoint_width_: 0 - property var entity_painted_: [] - property var topic_painted_: [] - - // Private signals + property var topic_locations_: {} // topic information needed for connection representation + property var endpoint_topic_connections_: {} // endpoint information needed for connection representation + property var topic_painted_: [] // already painted topic connection references + property var endpoint_painted_: [] // already painted endpoint connection references + property int max_host_width_: 0 // host entity box width management + property int max_user_width_: 0 // user entity box width management + property int max_process_width_: 0 // process entity box width management + property int max_participant_width_: 0 // participant entity box width management + property int max_endpoint_width_: 0 // endpoint entity box width management + + // Private (resize) signals resize_elements_ will trigger a bunch of resize methods per entities and + // HOST TOPIC ─┐ topics, when 2 iterations are performed. The first one is aimed to + // 3↓ ... ↑2 1│ ↑ 5└─>CONNECTIONS resize "parents" based on max "child" size, and then the second one takes + // ENDPOINT <───┘ │ place to ensure the well format if a "parent" has a size longer than a + // 4└─────────────┘ "child". After that, with the final results, connections between topics + // and endpoints are generated signal resize_elements_() + signal update_endpoints_() + signal update_participants_() + signal update_processes_() + signal update_users_() + signal update_hosts_() signal topics_updated_() signal endpoints_updated_() signal participants_updated_() @@ -53,7 +64,7 @@ Item signal users_updated_() signal hosts_updated_() - // Read only design properties + // Read only design properties (sizes and colors) readonly property int radius_: 10 readonly property int connection_thickness_: 5 readonly property int elements_spacing_: 12 @@ -69,20 +80,23 @@ Item readonly property string host_color_: Theme.darkGrey readonly property string user_color_: Theme.eProsimaLightBlue readonly property string process_color_: Theme.eProsimaDarkBlue - readonly property string participant_color_: Theme.vulcanexusBlue + readonly property string participant_color_: Theme.whiteSmoke readonly property string reader_color_: Theme.eProsimaYellow readonly property string writer_color_: Theme.eProsimaGreen + // Obtain given domain id graph Component.onCompleted: { load_model() } + // Horizontal scroll view for topics section. This will contain also a Flickable that replicates entities height + // and will move accordingly to display the connections Flickable { id: topicView anchors.top: parent.top; anchors.bottom: parent.bottom anchors.left: parent.left; anchors.leftMargin: max_host_width_ + elements_spacing_; - width: parent.width - max_host_width_ - elements_spacing_ + width: parent.width - max_host_width_ - 2*elements_spacing_ flickableDirection: Flickable.HorizontalFlick boundsBehavior: Flickable.StopAtBounds @@ -92,7 +106,7 @@ Item ScrollBar.vertical: ScrollBar { policy: ScrollBar.AlwaysOff } ScrollBar.horizontal: ScrollBar { id: horizontal_bar - anchors.left: parent.left; + anchors.left: parent.left; anchors.leftMargin: elements_spacing_ anchors.bottom: parent.bottom policy: ScrollBar.AlwaysOn visible: topicView.contentWidth > topicView.width @@ -122,53 +136,78 @@ Item } } + // List view of topics model ListView { id: topicsList model: domainGraphLayout.model ? domainGraphLayout.model["topics"] : undefined anchors.left: parent.left; anchors.leftMargin: 2 * elements_spacing_ anchors.top: parent.top; anchors.topMargin: elements_spacing_; - anchors.bottom: parent.bottom; anchors.bottomMargin: scrollbar_max_size_+ elements_spacing_ + anchors.bottom: parent.bottom contentWidth: contentItem.childrenRect.width spacing: elements_spacing_ orientation: ListView.Horizontal interactive: false + // Resizing management connections Connections { target: domainGraphLayout function onResize_elements_() { - topicsList.onCountChanged() + topicsList.resize() + update_endpoints_() + } + + function onEndpoints_updated_() + { + topicsList.resize() + topicsList.create_connections() + topics_updated_() } } + // Resize performed also when new element included in the model onCountChanged: + { + topicsList.resize() + } + + // Calculates the list height based on the number of contained entities, and width based on their widths + function resize() { var listViewHeight = 0 var listViewWidth = 0 // iterate over each element in the list item - for (var i = 0; i < topicsList.visibleChildren.length; i++) { - listViewHeight = topicsList.visibleChildren[i].height - listViewWidth += topicsList.visibleChildren[i].width + for (var c = 0; c < topicsList.count; c++) + { + topicsList.currentIndex = c + listViewHeight = topicsList.currentItem.height + listViewWidth += topicsList.currentItem.width + elements_spacing_ } + topicsList.height = listViewHeight + topicsList.width = listViewWidth + } + + function create_connections() + { + var draw_width = 2*elements_spacing_ + // iterate over each element in the list item for (var c = 0; c < topicsList.count; c++) { topicsList.currentIndex = c - var globalCoordinates = topicsList.currentItem.mapToItem(mainSpace, 0, 0) topic_locations_[topicsList.currentItem.topic_id] = { "id": topicsList.currentItem.topic_id, - "x" : globalCoordinates.x + (topicsList.currentItem.width/2) + "width" : draw_width + topicsList.currentItem.width/2 } + draw_width += topicsList.currentItem.width + elements_spacing_ } - topicsList.height = listViewHeight - topicsList.width = listViewWidth + 10* elements_spacing_ - topics_updated_() } + // Topic delegated item box with vertical line delegate: Rectangle { property string topic_id: modelData["id"] @@ -176,7 +215,7 @@ Item height: topicsList.height color: "transparent" - + // Topic name and icon Rectangle { id: topic_tag @@ -198,7 +237,7 @@ Item } Label { text: modelData["alias"] - Layout.rightMargin: first_indentation_ + Layout.rightMargin: 2* first_indentation_ color: "white" } } @@ -207,11 +246,12 @@ Item anchors.fill: parent onClicked: { - console.log(modelData["alias"] + " clicked!") + controller.topic_click(modelData["id"]) } } } + // Topic vertical line Rectangle { id: topic_down_bar @@ -224,6 +264,7 @@ Item } } + // Section where connections are represented Flickable { id: topicSpace @@ -238,11 +279,13 @@ Item contentHeight: mainView.contentHeight Rectangle {id: topic_connections; anchors.fill:parent; color: "transparent" } - + // Not visible scroll bar ScrollBar.vertical: ScrollBar{ id: custom_bar width: 0 + interactive: false + // connection to move vertically the view when entities view moves vertically Connections { target: vertical_bar @@ -252,19 +295,24 @@ Item } } + // Overriding mouse area to scroll horizontally on wheel event MouseArea { anchors.fill: parent + preventStealing: true onWheel: { - if (wheel.angleDelta.y > 0) { - topicView.contentX -= 30// topicView.scrollSpeed; - if (topicView.contentX < 0) { - topicView.contentX = 0; - } - } else { - topicView.contentX += 30// topicView.scrollSpeed; - if (topicView.contentX + topicView.width > topicView.contentWidth) { - topicView.contentX = topicView.contentWidth - topicView.width; + if (topicView.contentWidth > topicView.width) + { + if (wheel.angleDelta.y > 0) { + topicView.contentX -= 30 + if (topicView.contentX < 0) { + topicView.contentX = 0; + } + } else { + topicView.contentX += 30 + if (topicView.contentX + topicView.width > topicView.contentWidth) { + topicView.contentX = topicView.contentWidth - topicView.width; + } } } } @@ -275,9 +323,9 @@ Item onPositionChanged: mouse.accepted = false; onPressAndHold: mouse.accepted = false; } - } + // Resizing management connections Connections { target: domainGraphLayout @@ -286,33 +334,9 @@ Item { topicView.create_connections() } - - function onEndpoints_updated_() - { - topicView.create_connections() - } - - function onParticipants_updated_() - { - topicView.create_connections() - } - - function onProcesses_updated_() - { - topicView.create_connections() - } - - function onUsers_updated_() - { - topicView.create_connections() - } - - function onHosts_updated_() - { - topicView.create_connections() - } } + // Generate connections in topic side function create_connections() { for (var key in endpoint_topic_connections_) @@ -322,11 +346,10 @@ Item { if (!topic_painted_.includes(key)) { - var destination_x = topic_locations_[topic_id]["x"] var input = {"x": 0 ,"right_direction": endpoint_topic_connections_[key]["right_direction"] ,"y": endpoint_topic_connections_[key]["y"] - (connection_thickness_ / 2) - ,"width": destination_x - endpoint_topic_connections_[key]["x"] - 4*elements_spacing_ + ,"width": topic_locations_[topic_id]["width"] ,"height":connection_thickness_, "z":200, "left_margin": 2*elements_spacing_ ,"arrow_color": topic_color_, "background_color": background_color.color } var connection_bar = arrow_component.createObject(topic_connections, input) @@ -337,6 +360,16 @@ Item } } + // middle section to cut topics layout + Rectangle { + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.left: mainView.right + width: elements_spacing_ + color: "white" + } + + // Entities vertical flickable (left section) Flickable { id: mainView anchors.left: parent.left ; anchors.top: parent.top; anchors.bottom: parent.bottom @@ -389,22 +422,24 @@ Item } } - + // Scpace where entities will be represented Rectangle { id: mainSpace anchors.top: parent.top - width: hostsList.width + width: hostsList.width + 2*elements_spacing_ height: hostsList.height < domainGraphLayout.height - (label_height_ + 2*elements_spacing_) ? domainGraphLayout.height - (label_height_ + 2*elements_spacing_) : hostsList.height + // Entities background Rectangle { id: background_color anchors.fill: parent color: "white" } + // Graph connection component definition for object creation purposes Component { id: arrow_component GraphConnection{ @@ -412,6 +447,7 @@ Item } } + // List view of hosts model (which would contain remain entities nested) ListView { id: hostsList @@ -422,48 +458,46 @@ Item spacing: elements_spacing_ z: 20 + // Resizing management connections Connections { target: domainGraphLayout - function onUsers_updated_() + function onUpdate_hosts_() { - hostsList.onCountChanged() + hostsList.resize() + hosts_updated_() } } + // Resize performed also when new element included in the model onCountChanged: { - var listViewHeight = 0 - var listViewWidth = 0 + hostsList.resize() + } + // Calculates the list height based on the number of contained entities, and width based on their widths + function resize() + { + var listViewHeight = 0 // iterate over each element in the list item - for (var i = 0; i < hostsList.visibleChildren.length; i++) { - var min_width = hostsList.visibleChildren[i].width - for (var j = 0; j < hostsList.visibleChildren[i].visibleChildren.length; j++) - { - min_width = Math.max(min_width, hostsList.visibleChildren[i].visibleChildren[j].width) - } - listViewWidth = Math.max(listViewWidth, min_width) - max_host_width_ = Math.max(max_host_width_, listViewWidth) - max_host_width_ = Math.max(max_host_width_, (2*elements_spacing_)+max_user_width_) - hostsList.visibleChildren[i].width = max_host_width_ - } - for (var c = 0; c < hostsList.count; c++) { hostsList.currentIndex = c if (hostsList.currentItem != null) { listViewHeight += hostsList.currentItem.height + elements_spacing_ + max_host_width_ = Math.max(max_host_width_, hostsList.currentItem.width) + max_host_width_ = Math.max(max_host_width_, (2*elements_spacing_)+max_user_width_) + hostsList.currentItem.width = max_host_width_ } } - hostsList.height = listViewHeight + elements_spacing_ + hostsList.height = listViewHeight hostsList.width = max_host_width_ - hosts_updated_() } + // Host delegated item box delegate: Item { height: host_tag.height + usersList.height @@ -473,6 +507,7 @@ Item ? hostRowLayout.implicitWidth : max_host_width_ + // background Rectangle { id: host_background @@ -482,6 +517,7 @@ Item radius: radius_ } + // host name and icons Rectangle { id: host_tag @@ -497,20 +533,28 @@ Item RowLayout { id: hostRowLayout + spacing: spacing_icon_label_ anchors.centerIn: parent + Rectangle { + color: "transparent" + width: modelData["status"] != "ok" + ? first_indentation_ : 0 + } IconSVG { visible: modelData["status"] != "ok" name: "issues" color: "white" - size: icon_size_ - Layout.leftMargin: first_indentation_ + size: modelData["status"] != "ok"? icon_size_ : 0 + } + Rectangle { + color: "transparent" + width: first_indentation_ /2 } IconSVG { name: "host" color: "white" size: icon_size_ - Layout.leftMargin: modelData["status"] != "ok" ? 0 : first_indentation_ } Label { text: modelData["alias"] @@ -532,11 +576,12 @@ Item anchors.fill: parent onClicked: { - console.log(modelData["alias"] + " clicked!") + controller.host_click(modelData["id"]) } } } + // List view of users model (which would contain remain entities nested) ListView { id: usersList @@ -547,48 +592,54 @@ Item interactive: false spacing: elements_spacing_ + // Resizing management connections Connections { target: domainGraphLayout - function onProcesses_updated_() + function onUpdate_users_() + { + usersList.resize() + update_hosts_() + } + function onHosts_updated_() { - usersList.onCountChanged() + usersList.resize() + users_updated_() } } + // Resize performed also when new element included in the model onCountChanged: + { + usersList.resize() + } + + // Calculates the list height based on the number of contained entities, and width based on + // their widths + function resize() { var listViewHeight = 0 - var listViewWidth = 0 // iterate over each element in the list item - for (var i = 0; i < usersList.visibleChildren.length; i++) { - var min_width = usersList.visibleChildren[i].width - for (var j = 0; j < usersList.visibleChildren[i].visibleChildren.length; j++) - { - min_width = Math.max(min_width, usersList.visibleChildren[i].visibleChildren[j].width) - } - listViewWidth = Math.max(listViewWidth, min_width) - max_user_width_ = Math.max(max_user_width_, listViewWidth) - max_user_width_ = Math.max(max_user_width_, (2*elements_spacing_)+max_process_width_) - usersList.visibleChildren[i].width = max_user_width_ - } - for (var c = 0; c < usersList.count; c++) { usersList.currentIndex = c if (usersList.currentItem != null) { listViewHeight += usersList.currentItem.height + elements_spacing_ + max_user_width_ = Math.max(max_user_width_, usersList.currentItem.width) + max_user_width_ = Math.max(max_user_width_, max_process_width_+(2*elements_spacing_)) + max_user_width_ = Math.max(max_user_width_, max_host_width_-(2*elements_spacing_)) + usersList.currentItem.width = max_user_width_ } } usersList.height = listViewHeight + elements_spacing_ usersList.width = max_user_width_ - users_updated_() } + // User delegated item box delegate: Item { height: user_tag.height + processesList.height @@ -598,6 +649,7 @@ Item ? userRowLayout.implicitWidth : max_user_width_ + // background Rectangle { id: user_background @@ -607,6 +659,7 @@ Item radius: radius_ } + // user name and icons Rectangle { id: user_tag @@ -623,20 +676,28 @@ Item RowLayout { id: userRowLayout + spacing: spacing_icon_label_ anchors.centerIn: parent + Rectangle { + color: "transparent" + width: modelData["status"] != "ok" + ? first_indentation_ : 0 + } IconSVG { visible: modelData["status"] != "ok" name: "issues" color: "white" - size: icon_size_ - Layout.leftMargin: first_indentation_ + size: modelData["status"] != "ok"? icon_size_ : 0 + } + Rectangle { + color: "transparent" + width: first_indentation_ /2 } IconSVG { name: "user" color: "white" size: icon_size_ - Layout.leftMargin: modelData["status"] != "ok" ? 0 : first_indentation_ } Label { text: modelData["alias"] @@ -658,11 +719,12 @@ Item anchors.fill: parent onClicked: { - console.log(modelData["alias"] + " clicked!") + controller.user_click(modelData["id"]) } } } + // List view of processes model (which would contain remain entities nested) ListView { id: processesList @@ -673,40 +735,52 @@ Item interactive: false spacing: elements_spacing_ + // Resizing management connections Connections { target: domainGraphLayout - function onParticipants_updated_() + function onUpdate_processes_() + { + processesList.resize() + update_users_() + } + + function onUsers_updated_() { - processesList.onCountChanged() + processesList.resize() + processes_updated_() } } + // Resize performed also when new element included in the model onCountChanged: + { + processesList.resize() + } + + // Calculates the list height based on the number of contained entities, + // and width based on their widths + function resize() { var listViewHeight = 0 - var listViewWidth = 0 // iterate over each element in the list item - for (var i = 0; i < processesList.visibleChildren.length; i++) { - listViewHeight += processesList.visibleChildren[i].height + elements_spacing_ - var min_width = processesList.visibleChildren[i].width - for (var j = 0; j < processesList.visibleChildren[i].visibleChildren.length; j++) - { - min_width = Math.max(min_width, processesList.visibleChildren[i].visibleChildren[j].width) - } - listViewWidth = Math.max(listViewWidth, min_width) - max_process_width_ = Math.max(max_process_width_, listViewWidth) - max_process_width_ = Math.max(max_process_width_, (2*elements_spacing_)+max_participant_width_) - processesList.visibleChildren[i].width = max_process_width_ + for (var c = 0; c < processesList.count; c++) + { + processesList.currentIndex = c + listViewHeight += processesList.currentItem.height + elements_spacing_ + max_process_width_ = Math.max(max_process_width_, processesList.currentItem.width) + max_process_width_ = Math.max(max_process_width_, max_participant_width_+(2*elements_spacing_)) + max_process_width_ = Math.max(max_process_width_, max_user_width_-(2*elements_spacing_)) + processesList.currentItem.width = max_process_width_ } processesList.height = listViewHeight + elements_spacing_ processesList.width = max_process_width_ - processes_updated_() } + // Process delegated item box delegate: Item { height: process_tag.height + participantsList.height @@ -716,6 +790,7 @@ Item ? processRowLayout.implicitWidth : max_process_width_ + // background Rectangle { id: process_background @@ -725,6 +800,7 @@ Item radius: radius_ } + // process name and icons Rectangle { id: process_tag @@ -741,20 +817,28 @@ Item RowLayout { id: processRowLayout + spacing: spacing_icon_label_ anchors.centerIn: parent + Rectangle { + color: "transparent" + width: modelData["status"] != "ok" + ? first_indentation_ : 0 + } IconSVG { visible: modelData["status"] != "ok" name: "issues" color: "white" - size: icon_size_ - Layout.leftMargin: first_indentation_ + size: modelData["status"] != "ok"? icon_size_ : 0 + } + Rectangle { + color: "transparent" + width: first_indentation_ /2 } IconSVG { name: "process" color: "white" size: icon_size_ - Layout.leftMargin: modelData["status"] != "ok" ? 0 : first_indentation_ } Label { text: modelData["alias"] @@ -776,10 +860,12 @@ Item anchors.fill: parent onClicked: { - console.log(modelData["alias"] + " clicked!") + controller.process_click(modelData["id"]) } } } + + // List view of participants model (which would contain remain endpoints nested) ListView { id: participantsList @@ -790,40 +876,51 @@ Item interactive: false spacing: elements_spacing_ + // Resizing management connections Connections { target: domainGraphLayout - function onEndpoints_updated_() + function onUpdate_participants_() { - participantsList.onCountChanged() + participantsList.resize() + update_processes_() + } + function onProcesses_updated_() + { + participantsList.resize() + participants_updated_() } } + // Resize performed also when new element included in the model onCountChanged: + { + participantsList.resize() + } + + // Calculates the list height based on the number of contained entities, + // and width based on their widths + function resize() { var listViewHeight = 0 - var listViewWidth = 0 // iterate over each element in the list item - for (var i = 0; i < participantsList.visibleChildren.length; i++) { - listViewHeight += participantsList.visibleChildren[i].height + elements_spacing_ - var min_width = participantsList.visibleChildren[i].width - for (var j = 0; j < participantsList.visibleChildren[i].visibleChildren.length; j++) - { - min_width = Math.max(min_width, participantsList.visibleChildren[i].visibleChildren[j].width) - } - listViewWidth = Math.max(listViewWidth, min_width) - max_participant_width_ = Math.max(max_participant_width_, listViewWidth) - max_participant_width_ = Math.max(max_participant_width_, (2*elements_spacing_)+max_endpoint_width_) - participantsList.visibleChildren[i].width = max_participant_width_ + for (var c = 0; c < participantsList.count; c++) + { + participantsList.currentIndex = c + listViewHeight += participantsList.currentItem.height + elements_spacing_ + max_participant_width_ = Math.max(max_participant_width_, participantsList.currentItem.width) + max_participant_width_ = Math.max(max_participant_width_, max_endpoint_width_+(2*elements_spacing_)) + max_participant_width_ = Math.max(max_participant_width_, max_process_width_-(2*elements_spacing_)) + participantsList.currentItem.width = max_participant_width_ } participantsList.height = listViewHeight + elements_spacing_ participantsList.width = max_participant_width_ - participants_updated_() } + // Participant delegated item box delegate: Item { height: participant_tag.height + endpointsList.height @@ -833,6 +930,7 @@ Item ? participantRowLayout.implicitWidth : max_participant_width_ + // background Rectangle { id: participant_background @@ -841,6 +939,8 @@ Item color: participant_color_ radius: radius_ } + + // participant name and icons Rectangle { id: participant_tag @@ -857,13 +957,22 @@ Item RowLayout { id: participantRowLayout + spacing: spacing_icon_label_ anchors.centerIn: parent + Rectangle { + color: "transparent" + width: modelData["status"] != "ok" + ? first_indentation_ : 0 + } IconSVG { visible: modelData["status"] != "ok" name: "issues" size: modelData["status"] != "ok"? icon_size_ : 0 - Layout.leftMargin: modelData["status"] != "ok" ? first_indentation_ : 0 + } + Rectangle { + color: "transparent" + width: first_indentation_ /2 } IconSVG { name: modelData["kind"] @@ -888,11 +997,12 @@ Item anchors.fill: parent onClicked: { - console.log(modelData["alias"] + " clicked!") + controller.participant_click(modelData["id"]) } } } + // List view of endpoint model ListView { id: endpointsList @@ -903,43 +1013,61 @@ Item interactive: false spacing: elements_spacing_ + // Resizing management connections Connections { target: domainGraphLayout - function onTopics_updated_() + function onUpdate_endpoints_() + { + endpointsList.resize() + update_participants_() + } + + function onParticipants_updated_() { - endpointsList.onCountChanged() + endpointsList.resize() + endpointsList.record_connections() + endpoints_updated_() } } + // Resize performed also when new element included in the model onCountChanged: + { + endpointsList.resize() + } + + // Calculates the list height based on the number of contained entities, + // and width based on their widths + function resize() { var listViewHeight = 0 // iterate over each element in the list item - for (var i = 0; i < endpointsList.visibleChildren.length; i++) { - listViewHeight += endpointsList.visibleChildren[i].height + elements_spacing_ - var min_width = endpointsList.visibleChildren[i].width - for (var j = 0; j < endpointsList.visibleChildren[i].visibleChildren.length; j++) - { - min_width = Math.max(min_width, endpointsList.visibleChildren[i].visibleChildren[j].width) - } - max_endpoint_width_ = Math.max(max_endpoint_width_, min_width) - endpointsList.visibleChildren[i].width = max_endpoint_width_ - } - for (var c = 0; c < endpointsList.count; c++) { endpointsList.currentIndex = c - endpointsList.currentItem.record_connection() + listViewHeight += endpointsList.currentItem.height + elements_spacing_ + max_endpoint_width_ = Math.max(max_endpoint_width_, endpointsList.currentItem.width) + max_endpoint_width_ = Math.max(max_endpoint_width_, max_participant_width_-(2*elements_spacing_)) + endpointsList.currentItem.width = max_endpoint_width_ } endpointsList.height = listViewHeight + elements_spacing_ endpointsList.width = max_endpoint_width_ - endpoints_updated_() } + function record_connections() + { + for (var c = 0; c < endpointsList.count; c++) + { + endpointsList.currentIndex = c + endpointsList.currentItem.record_connection() + } + } + + // Endpoint delegated item box delegate: Item { id: endpointComponent @@ -950,6 +1078,7 @@ Item : max_endpoint_width_ height: endpoint_height_ + // Saves the endpoint needed info for connection representation function record_connection() { var globalCoordinates = endpointComponent.mapToItem(mainSpace, 0, 0) @@ -965,6 +1094,7 @@ Item } } + // background Rectangle { id: endpoint_background @@ -973,6 +1103,8 @@ Item color: modelData["kind"] == "datareader" ? reader_color_ : writer_color_ radius: radius_ } + + // endpoint name and icons Rectangle { id: endpoint_tag @@ -989,13 +1121,22 @@ Item RowLayout { id: endpointRowLayout + spacing: spacing_icon_label_ anchors.centerIn: parent + Rectangle { + color: "transparent" + width: modelData["status"] != "ok" + ? first_indentation_ : 0 + } IconSVG { visible: modelData["status"] != "ok" name: "issues" size: modelData["status"] != "ok"? icon_size_ : 0 - Layout.leftMargin: modelData["status"] != "ok" ? first_indentation_ : 0 + } + Rectangle { + color: "transparent" + width: first_indentation_ /2 } IconSVG { name: modelData["kind"] @@ -1011,7 +1152,7 @@ Item anchors.fill: parent onClicked: { - console.log(modelData["alias"] + " clicked!") + controller.endpoint_click(modelData["id"]) } } } @@ -1035,6 +1176,7 @@ Item } } + // Resizing management connections Connections { target: domainGraphLayout @@ -1043,33 +1185,9 @@ Item { mainSpace.create_connections() } - - function onEndpoints_updated_() - { - mainSpace.create_connections() - } - - function onParticipants_updated_() - { - mainSpace.create_connections() - } - - function onProcesses_updated_() - { - mainSpace.create_connections() - } - - function onUsers_updated_() - { - mainSpace.create_connections() - } - - function onHosts_updated_() - { - mainSpace.create_connections() - } } + // Saves the topic needed info for connection representation function create_connections() { for (var key in endpoint_topic_connections_) @@ -1077,17 +1195,16 @@ Item var topic_id = endpoint_topic_connections_[key]["destination_id"] if (topic_locations_[topic_id] != undefined) { - if (!entity_painted_.includes(key)) + if (!endpoint_painted_.includes(key)) { - var destination_x = topic_locations_[topic_id]["x"] var input = {"x": endpoint_topic_connections_[key]["x"] ,"left_direction": endpoint_topic_connections_[key]["left_direction"] ,"y": endpoint_topic_connections_[key]["y"] - (connection_thickness_ / 2) - ,"width": 4*elements_spacing_ + ,"width": 5*elements_spacing_ ,"height":connection_thickness_, "z":200 ,"arrow_color": topic_color_, "background_color": background_color.color } var connection_bar = arrow_component.createObject(mainSpace, input) - entity_painted_[entity_painted_.length] = key + endpoint_painted_[endpoint_painted_.length] = key } } } @@ -1095,6 +1212,7 @@ Item } } + // top section to cut entities layout and display the REFRESH butotn Rectangle { anchors.top: parent.top anchors.left: parent.left @@ -1103,13 +1221,15 @@ Item color: "white" z: 12 + // Refresh button Button{ + id: refresh_button width: parent.width /2 < 150 ? 150 : parent.width /2 height: label_height_ anchors.top: parent.top; anchors.topMargin: elements_spacing_ - anchors.left: parent.left - anchors.leftMargin: max_host_width_/2 + elements_spacing_ - width /2 < 100 ? 5* elements_spacing_ - : max_host_width_/2 + elements_spacing_ - width /2 + anchors.left: parent.left + anchors.leftMargin: max_host_width_/2 + elements_spacing_ - refresh_button.width /2 < 40 + ? 5* elements_spacing_ : (max_host_width_/2) + elements_spacing_ - (refresh_button.width /2) text: "Refresh" onClicked:{ @@ -1118,15 +1238,17 @@ Item } } + // footer section to cut entities layout Rectangle { anchors.bottom: parent.bottom anchors.left: parent.left height: elements_spacing_ - width: max_host_width_ + elements_spacing_ + width: max_host_width_ + 2*elements_spacing_ color: "white" z: 14 } + // Empty screen message Rectangle { anchors.fill: parent color: "transparent" @@ -1149,135 +1271,179 @@ Item } } - + // Obtain given domain id graph JSON model function load_model() { // clear internal models - topic_locations_ = {} - endpoint_topic_connections_ = {} - entity_painted_ = [] - topic_painted_ = [] - remove_connections() + clear_graph() - // Obtain model from backend, and parse from string to JSON + // Obtain model from backend var model_string = controller.get_domain_view_graph(entity_id) - // obtained hosts and topics + // declare obtained hosts and topics variables var new_topics = [] var new_hosts = [] // Check if obtained graph is not empty - if (model_string.length !== 0) + if (model_string.length !== 0 && model_string !== "null") { // Parse model from string to JSON var new_model = JSON.parse(model_string) - // transform indexed model to array model (arrays required for the listviews) - for (var topic in new_model["topics"]) - { - new_topics[new_topics.length] = { - "id":topic, - "kind":new_model["topics"][topic]["kind"], - "alias":new_model["topics"][topic]["alias"],} - } - var accum_y = 0 - for (var host in new_model["hosts"]) + // Ensure expected graph was received + if (new_model["domain"] == domain_id) { - accum_y += label_height_ + elements_spacing_ - var new_users = [] - for (var user in new_model["hosts"][host]["users"]) + var is_metatraffic_visible_ = controller.metatraffic_visible(); + + // transform indexed model to array model (arrays required for the listviews) + for (var topic in new_model["topics"]) + { + var metatraffic_ = new_model["topics"][topic]["metatraffic"] + if (metatraffic_ != true || is_metatraffic_visible_) + { + new_topics[new_topics.length] = { + "id":topic, + "kind":new_model["topics"][topic]["kind"], + "alias":new_model["topics"][topic]["alias"] + } + } + } + var accum_y = 0 + for (var host in new_model["hosts"]) { - accum_y += label_height_ + elements_spacing_ - var new_processes = [] - for (var process in new_model["hosts"][host]["users"][user]["processes"]) + var metatraffic_ = new_model["hosts"][host]["metatraffic"] + if (metatraffic_ != true || is_metatraffic_visible_) { accum_y += label_height_ + elements_spacing_ - var new_participants = [] - for (var participant in new_model["hosts"][host]["users"][user]["processes"][process]["participants"]) + var new_users = [] + for (var user in new_model["hosts"][host]["users"]) { - accum_y += label_height_ + elements_spacing_ - var new_endpoints = [] - for (var endpoint in new_model["hosts"][host]["users"][user]["processes"][process]["participants"][participant]["endpoints"]) + var metatraffic_ = new_model["hosts"][host]["users"][user]["metatraffic"] + if (metatraffic_ != true || is_metatraffic_visible_) { - new_endpoints[new_endpoints.length] = { - "id":endpoint, - "kind":new_model["hosts"][host]["users"][user]["processes"][process]["participants"][participant]["endpoints"][endpoint]["kind"], - "alias":new_model["hosts"][host]["users"][user]["processes"][process]["participants"][participant]["endpoints"][endpoint]["alias"], - "status":new_model["hosts"][host]["users"][user]["processes"][process]["participants"][participant]["endpoints"][endpoint]["status"], - "topic":new_model["hosts"][host]["users"][user]["processes"][process]["participants"][participant]["endpoints"][endpoint]["topic"], - "accum_y":accum_y + accum_y += label_height_ + elements_spacing_ + var new_processes = [] + for (var process in new_model["hosts"][host]["users"][user]["processes"]) + { + var metatraffic_ = new_model["hosts"][host]["users"][user]["processes"][process]["metatraffic"] + if (metatraffic_ != true || is_metatraffic_visible_) + { + accum_y += label_height_ + elements_spacing_ + var new_participants = [] + for (var participant in new_model["hosts"][host]["users"][user]["processes"][process]["participants"]) + { + var metatraffic_ = new_model["hosts"][host]["users"][user]["processes"][process]["participants"][participant]["metatraffic"] + if (metatraffic_ != true || is_metatraffic_visible_) + { + accum_y += label_height_ + elements_spacing_ + var new_endpoints = [] + for (var endpoint in new_model["hosts"][host]["users"][user]["processes"][process]["participants"][participant]["endpoints"]) + { + var metatraffic_ = new_model["hosts"][host]["users"][user]["processes"][process]["participants"][participant]["endpoints"][endpoint]["metatraffic"] + if (metatraffic_ != true || is_metatraffic_visible_) + { + new_endpoints[new_endpoints.length] = { + "id":endpoint, + "kind":new_model["hosts"][host]["users"][user]["processes"][process]["participants"][participant]["endpoints"][endpoint]["kind"], + "alias":new_model["hosts"][host]["users"][user]["processes"][process]["participants"][participant]["endpoints"][endpoint]["alias"], + "status":new_model["hosts"][host]["users"][user]["processes"][process]["participants"][participant]["endpoints"][endpoint]["status"], + "topic":new_model["hosts"][host]["users"][user]["processes"][process]["participants"][participant]["endpoints"][endpoint]["topic"], + "accum_y":accum_y + } + accum_y += endpoint_height_ + elements_spacing_ + } + } + new_participants[new_participants.length] = { + "id":participant, + "kind":new_model["hosts"][host]["users"][user]["processes"][process]["participants"][participant]["kind"], + "alias":new_model["hosts"][host]["users"][user]["processes"][process]["participants"][participant]["alias"], + "status":new_model["hosts"][host]["users"][user]["processes"][process]["participants"][participant]["status"], + "app_id":new_model["hosts"][host]["users"][user]["processes"][process]["participants"][participant]["app_id"], + "app_metadata":new_model["hosts"][host]["users"][user]["processes"][process]["participants"][participant]["app_metadata"], + "endpoints":new_endpoints + } + accum_y += elements_spacing_ + } + } + new_processes[new_processes.length] = { + "id":process, + "kind":new_model["hosts"][host]["users"][user]["processes"][process]["kind"], + "alias":new_model["hosts"][host]["users"][user]["processes"][process]["alias"], + "pid": new_model["hosts"][host]["users"][user]["processes"][process]["pid"], + "status":new_model["hosts"][host]["users"][user]["processes"][process]["status"], + "participants":new_participants + } + accum_y += elements_spacing_ + } } - accum_y += endpoint_height_ + elements_spacing_ - } - new_participants[new_participants.length] = { - "id":participant, - "kind":new_model["hosts"][host]["users"][user]["processes"][process]["participants"][participant]["kind"], - "alias":new_model["hosts"][host]["users"][user]["processes"][process]["participants"][participant]["alias"], - "status":new_model["hosts"][host]["users"][user]["processes"][process]["participants"][participant]["status"], - "app_id":new_model["hosts"][host]["users"][user]["processes"][process]["participants"][participant]["app_id"], - "app_metadata":new_model["hosts"][host]["users"][user]["processes"][process]["participants"][participant]["app_metadata"], - "endpoints":new_endpoints + new_users[new_users.length] = { + "id":user, + "kind":new_model["hosts"][host]["users"][user]["kind"], + "alias":new_model["hosts"][host]["users"][user]["alias"], + "status":new_model["hosts"][host]["users"][user]["status"], + "processes":new_processes + } + accum_y += elements_spacing_ } - accum_y += elements_spacing_ } - new_processes[new_processes.length] = { - "id":process, - "kind":new_model["hosts"][host]["users"][user]["processes"][process]["kind"], - "alias":new_model["hosts"][host]["users"][user]["processes"][process]["alias"], - "pid": new_model["hosts"][host]["users"][user]["processes"][process]["pid"], - "status":new_model["hosts"][host]["users"][user]["processes"][process]["status"], - "participants":new_participants + new_hosts[new_hosts.length] = { + "id":host, + "kind":new_model["hosts"][host]["kind"], + "alias":new_model["hosts"][host]["alias"], + "status":new_model["hosts"][host]["status"], + "users":new_users } accum_y += elements_spacing_ } - new_users[new_users.length] = { - "id":user, - "kind":new_model["hosts"][host]["users"][user]["kind"], - "alias":new_model["hosts"][host]["users"][user]["alias"], - "status":new_model["hosts"][host]["users"][user]["status"], - "processes":new_processes - } - accum_y += elements_spacing_ } - new_hosts[new_hosts.length] = { - "id":host, - "kind":new_model["hosts"][host]["kind"], - "alias":new_model["hosts"][host]["alias"], - "status":new_model["hosts"][host]["status"], - "users":new_users + model = { + "kind": new_model["kind"], + "domain": new_model["domain"], + "topics": new_topics, + "hosts": new_hosts, } - accum_y += elements_spacing_ - } - model = { - "kind": new_model["kind"], - "domain": new_model["domain"], - "topics": new_topics, - "hosts": new_hosts, - } - - // Update tab name with selected domain id - domainGraphLayout.update_tab_name("Domain " + new_model["domain"] + " View") - // Update visual elements - resize_elements_() + // Update visual elements by re-calculating their sizes + resize_elements_() - // hide empty screen label - emptyScreenLabel.visible = false + // hide empty screen label + emptyScreenLabel.visible = false + } } // print error message - if (new_topics.length === 0 && new_hosts.length === 0) + if (new_topics.length === 0 || new_hosts.length === 0) { - // Update tab name with selected domain id - domainGraphLayout.update_tab_name("Domain " + domain_id + " View") + // Discard any possible data received + model = { + "kind": "domain_view", + "domain": domain_id, + "topics": [], + "hosts": [], + } // display empty screen label emptyScreenLabel.visible = true } + + // Update tab name with selected domain id + domainGraphLayout.update_tab_name("Domain " + domain_id + " View") } // remove drawn connections - function remove_connections() + function clear_graph() { + topic_locations_ = {} + endpoint_topic_connections_ = {} + endpoint_painted_ = [] + topic_painted_ = [] + vertical_bar.position = 0 + horizontal_bar.position = 0 + max_host_width_ = 0; + max_user_width_ = 0; + max_process_width_ = 0; + max_participant_width_ = 0; + max_endpoint_width_ = 0; + for (var i = 0; i < mainSpace.children.length; i++) { if (mainSpace.children[i].left_margin != undefined) diff --git a/qml/GraphConnection.qml b/qml/GraphConnection.qml index e09c4eed..5464d3fc 100644 --- a/qml/GraphConnection.qml +++ b/qml/GraphConnection.qml @@ -2,39 +2,82 @@ import QtQuick 2.0 Item { // public property - property bool left_direction: false - property bool right_direction: false - property int left_margin: 0 - property string arrow_color: Theme.grey - property string background_color: "white" + property bool left_direction: false // defines if the represented connection must draw a left arrow + property bool right_direction: false // defines if the represented connection must draw a right arrow + property int left_margin: 0 // left margin to be applied + property string arrow_color: Theme.grey // connection color + property string background_color: "white" // background color - // The index of corner for the triangle to be attached - property int corner: 0 + // readonly private design properties + readonly property int arrow_margin_: -4 // margins for background + readonly property int arrow_size_: 30 // arrow size + // background to make connection overlap nicely with previous topics (looks like connection goes OVER the topic) Rectangle { id: background_arrow visible: left_margin != 0 anchors.top: parent.top; anchors.bottom: parent.bottom - anchors.topMargin: -2; anchors.bottomMargin: -2 + anchors.topMargin: arrow_margin_; anchors.bottomMargin: arrow_margin_ anchors.left: parent.left; anchors.right: parent.right anchors.leftMargin: left_margin; anchors.rightMargin: left_margin; color: background_color } Rectangle { - id: base_arrow - anchors.fill: parent - color: arrow_color + id: left_background + opacity: 0.70 + anchors.top: parent.top; anchors.bottom: parent.bottom + anchors.topMargin: -2; anchors.bottomMargin: -2 + anchors.left: parent.left; anchors.right: parent.right + anchors.leftMargin: parent.height /2; anchors.rightMargin: 5; + color: background_color + } + + + + // left arrow if visible + Item { + id: left_arrow_background + visible: left_direction + height: arrow_size_ + 8 + width: arrow_size_ + 2 + opacity: 0.7 + anchors.verticalCenter: parent.verticalCenter + anchors.left: parent.left + + Canvas { + id: left_canvas_background + + anchors.centerIn: parent + + height: parent.height / 2 + width: parent.width + + antialiasing:true; smooth:true + + onPaint: { + var ctx = left_canvas_background.getContext('2d') + + ctx.strokeStyle = "white" + ctx.lineWidth = left_canvas_background.width * 0.1 + ctx.beginPath() + ctx.moveTo(left_canvas_background.width, left_canvas_background.height * 0.001) + ctx.lineTo(12, left_canvas_background.height / 2 - 6) + ctx.lineTo(12, left_canvas_background.height / 2 + 6) + ctx.lineTo(left_canvas_background.width, left_canvas_background.height * 0.999) + ctx.stroke() + } + } } Item { id: left_arrow visible: left_direction - height: 30 - width: 30 + height: arrow_size_ + width: arrow_size_ anchors.verticalCenter: parent.verticalCenter - anchors.left: parent.left; anchors.leftMargin: -parent.height /2 + anchors.left: parent.left Canvas { id: left_canvas @@ -60,13 +103,24 @@ Item { } } + // main connection + Rectangle { + id: base_arrow + anchors.top: parent.top; anchors.bottom: parent.bottom + anchors.left: parent.left; anchors.right: parent.right + anchors.leftMargin: left_direction ? 8 : 0 + anchors.rightMargin: right_direction ? 8 : 0 + color: arrow_color + } + + // right arrow if visible Item { id: right_arrow visible: right_direction - height: 30 - width: 30 + height: arrow_size_ + width: arrow_size_ anchors.verticalCenter: parent.verticalCenter - anchors.right: parent.right; anchors.rightMargin: parent.height /2 + anchors.right: parent.right; anchors.rightMargin: parent.height /2 + 2 Canvas { id: right_canvas @@ -85,7 +139,7 @@ Item { ctx.lineWidth = right_canvas.width * 0.1 ctx.beginPath() ctx.moveTo(0, right_canvas.height * 0.05) - ctx.lineTo(right_canvas.width, right_canvas.height / 2) + ctx.lineTo(right_canvas.width, right_canvas.height / 2 -1) ctx.lineTo(0, right_canvas.height * 0.95) ctx.stroke() } diff --git a/qml/TabLayout.qml b/qml/TabLayout.qml index 0d07e5cd..1c72238f 100644 --- a/qml/TabLayout.qml +++ b/qml/TabLayout.qml @@ -29,8 +29,11 @@ Item { property int current_: 0 // current tab displayed property int last_index_: 1 // force unique idx on QML components property var tab_model_: [{"idx":0, "title":"New Tab", "stack_id": 0}] // tab model for tab bad and tab management - property bool disable_chart_selection: false // flag to disable multiple chart view tabs - signal open_domain_view(int entity_id, int domain_id) + property bool disable_chart_selection_: false // flag to disable multiple chart view tabs + + // private signals + signal open_domain_view_(int stack_id, int entity_id, int domain_id) + signal initialize_domain_view_(int stack_id, int entity_id, int domain_id) // Read only design properties readonly property int max_tabs_: 15 @@ -47,14 +50,14 @@ Item { // initialize first element in the tab Component.onCompleted:{ - var new_stack = stack_component.createObject(null, {"id": 0, "anchors.fill": "parent"}) + var new_stack = stack_component.createObject(null, {"stack_id": 0, "anchors.fill": "parent"}) stack_layout.children.push(new_stack) refresh_layout(current_) } ChartsLayout { z: 1 - visible: disable_chart_selection + visible: disable_chart_selection_ id: chartsLayout anchors.fill: stack_layout onFullScreenChanged: { @@ -62,19 +65,6 @@ Item { } } - Component { - id: domainGraphLayout_component - - DomainGraphLayout - { - id: domainGraphLayout - - onUpdate_tab_name: { - tabLayout.tab_model_[current_]["title"] = new_name - } - } - } - ListView { id: tab_list anchors.top: parent.top @@ -224,6 +214,7 @@ Item { // view with the different views available in a tab StackView { id: stack + property int stack_id: 0 anchors.fill: parent initialItem: view_selector @@ -245,10 +236,10 @@ Item { Button { width: 400; height: 400 anchors.verticalCenter: parent.verticalCenter - enabled: !disable_chart_selection + enabled: !disable_chart_selection_ text: "Chart View" onClicked: { - if (!disable_chart_selection) + if (!disable_chart_selection_) { tabLayout.tab_model_[current_]["title"] = "Chart View" if (stack.deep > 1) @@ -256,7 +247,7 @@ Item { stack.pop() } stack.push(chartsLayout) - disable_chart_selection = true + disable_chart_selection_ = true refresh_layout(current_) } } @@ -266,26 +257,71 @@ Item { anchors.verticalCenter: parent.verticalCenter text: "Domain View" onClicked: { - domain_id_dialog.open() + if (mainApplicationView.monitors == 0) + { + dialogInitMonitor.open() + } + else if (mainApplicationView.monitors == 1) + { + controller.update_available_entity_ids("Domain", "getDataDialogSourceEntityId") + open_domain_view_( + tabLayout.tab_model_[current_]["stack_id"], + entityModelFirst.get(0).id, + entityModelFirst.get(0).name) + } + else + { + domain_id_dialog.open() + } } } } + } + } + + Component { + id: domainGraphLayout_component + + DomainGraphLayout + { + id: domainGraphLayout + component_id: stack.stack_id + + onUpdate_tab_name: { + tabLayout.tab_model_[current_]["title"] = new_name + refresh_layout(current_) + } Connections { target: tabLayout - function onOpen_domain_view(entity_id, domain_id) { - if (stack.deep > 1) + function onInitialize_domain_view_(stack_id, entity_id, domain_id) { + if (domainGraphLayout.component_id == stack_id) { - stack.pop() + domainGraphLayout.entity_id = entity_id + domainGraphLayout.domain_id = domain_id + domainGraphLayout.load_model() } + } + } - var new_domain_graph = domainGraphLayout_component.createObject(null, - {"id": tabLayout.tab_model_[current_]["stack_id"], - "entity_id":entity_id , "domain_id":domain_id }) - stack.push(new_domain_graph) - refresh_layout(current_) + } + } + + Connections { + target: tabLayout + + function onOpen_domain_view_(stack_id, entity_id, domain_id) { + if (stack.stack_id == stack_id) + { + if (stack.deep > 1) + { + stack.pop() } + + stack.push(domainGraphLayout_component) + refresh_layout(current_) + initialize_domain_view_(stack_id, entity_id, domain_id) } } } @@ -329,11 +365,11 @@ Item { AdaptiveComboBox { id: custom_combobox - textRole: "nameId" + textRole: "name" valueRole: "id" displayText: currentIndex === -1 ? ("Please choose a Domain ID") - : currentText + : ("DDS Domain " + currentText) model: entityModelFirst Component.onCompleted: @@ -351,7 +387,8 @@ Item { onAccepted: { - open_domain_view( + open_domain_view_( + tabLayout.tab_model_[current_]["stack_id"], entityModelFirst.get(custom_combobox.currentIndex).id, entityModelFirst.get(custom_combobox.currentIndex).name) } @@ -361,7 +398,8 @@ Item { { var idx = tabLayout.tab_model_.length tabLayout.tab_model_[idx] = {"idx" : idx, "title": "New Tab", "stack_id":last_index_} - var new_stack = stack_component.createObject(null, {"id": last_index_, "anchors.fill": "parent"}) + var new_stack = stack_component.createObject(null, {"stack_id": tabLayout.tab_model_[idx]["stack_id"], + "anchors.fill": "parent"}) last_index_++ stack_layout.children.push(new_stack) refresh_layout(idx) @@ -376,8 +414,23 @@ Item { if (idx != current_) { current_ = idx + // move to the idx tab in the stack stack_layout.currentIndex = tabLayout.tab_model_[idx]["stack_id"] + + // check if domain info has changed + if (tabLayout.tab_model_[idx]["title"].includes("Domain")) + { + for (var i=0; iget_domain_view_graph(backend::models_id_to_backend_id(entity_id)); - if (domain_view != nullptr) - { - return QString::fromUtf8(domain_view.dump().data(), int(domain_view.dump().size())); - } - return ""; + return QString::fromUtf8(domain_view.dump().data(), int(domain_view.dump().size())); } diff --git a/src/backend/SyncBackendConnection.cpp b/src/backend/SyncBackendConnection.cpp index 3b56a5de..87e2f2eb 100644 --- a/src/backend/SyncBackendConnection.cpp +++ b/src/backend/SyncBackendConnection.cpp @@ -772,9 +772,12 @@ Graph SyncBackendConnection::get_domain_view_graph ( { return StatisticsBackend::get_domain_view_graph(domain_id); } - catch (BadParameter) + catch (const Exception& e) { - return nullptr; + qWarning() << "Fail getting the domain view JSON graph for entity id " + << domain_id.value() << ":" << e.what(); + static_cast(e); // In release qWarning does not compile and so e is not used + return Graph(); } } From 20676123e8de37cc07d615b47ecb05e52040f831 Mon Sep 17 00:00:00 2001 From: JesusPoderoso Date: Wed, 20 Sep 2023 16:30:10 +0200 Subject: [PATCH 05/36] Refs #19532: Please linters Signed-off-by: JesusPoderoso --- include/fastdds_monitor/Controller.h | 2 +- include/fastdds_monitor/Engine.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/include/fastdds_monitor/Controller.h b/include/fastdds_monitor/Controller.h index ab8ee25b..17f61ace 100644 --- a/include/fastdds_monitor/Controller.h +++ b/include/fastdds_monitor/Controller.h @@ -263,7 +263,7 @@ public slots: //! Request to backend the latest domain view JSON to build the graph QString get_domain_view_graph ( - QString domain_id); + QString domain_id); signals: diff --git a/include/fastdds_monitor/Engine.h b/include/fastdds_monitor/Engine.h index 8f534938..129b6d95 100644 --- a/include/fastdds_monitor/Engine.h +++ b/include/fastdds_monitor/Engine.h @@ -497,7 +497,7 @@ class Engine : public QQmlApplicationEngine //! Request to backend the latest domain view JSON to build the graph backend::Graph get_domain_view_graph ( - const backend::EntityId& domain_id); + const backend::EntityId& domain_id); signals: From ae9b78b05c75cf19b99f6aa2fbeede9ae560bcd2 Mon Sep 17 00:00:00 2001 From: JesusPoderoso Date: Wed, 23 Aug 2023 12:48:34 +0200 Subject: [PATCH 06/36] Initial empty commit Signed-off-by: JesusPoderoso From 9c73b177ab05846ff73aed27e37f77dd22052409 Mon Sep 17 00:00:00 2001 From: JesusPoderoso Date: Tue, 24 Oct 2023 08:17:06 +0200 Subject: [PATCH 07/36] Refs #19532: [ARS] Remove unnecessary include Signed-off-by: JesusPoderoso --- src/Controller.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Controller.cpp b/src/Controller.cpp index 543dab3f..10fa491c 100644 --- a/src/Controller.cpp +++ b/src/Controller.cpp @@ -21,7 +21,6 @@ #include #include -#include #include #include From f066e624bf0564fe8a94f07333b6e25386ba9e1f Mon Sep 17 00:00:00 2001 From: JesusPoderoso Date: Tue, 24 Oct 2023 08:19:15 +0200 Subject: [PATCH 08/36] Refs #19532: [ARS] Remove unnecessary eol ';' Signed-off-by: JesusPoderoso --- qml/DomainGraphLayout.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qml/DomainGraphLayout.qml b/qml/DomainGraphLayout.qml index 5d6513a3..d0c077b6 100644 --- a/qml/DomainGraphLayout.qml +++ b/qml/DomainGraphLayout.qml @@ -95,7 +95,7 @@ Item Flickable { id: topicView anchors.top: parent.top; anchors.bottom: parent.bottom - anchors.left: parent.left; anchors.leftMargin: max_host_width_ + elements_spacing_; + anchors.left: parent.left; anchors.leftMargin: max_host_width_ + elements_spacing_ width: parent.width - max_host_width_ - 2*elements_spacing_ flickableDirection: Flickable.HorizontalFlick boundsBehavior: Flickable.StopAtBounds From bbf0a8b45f9ca44254c52ac161cce39dbff7eb73 Mon Sep 17 00:00:00 2001 From: JesusPoderoso Date: Tue, 24 Oct 2023 08:21:22 +0200 Subject: [PATCH 09/36] Refs #19532: [ARS] Remove 'magic numbers' Signed-off-by: JesusPoderoso Refs #19532: [ARS] Remove wheel displacement 'magic number' Signed-off-by: JesusPoderoso --- qml/DomainGraphLayout.qml | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/qml/DomainGraphLayout.qml b/qml/DomainGraphLayout.qml index d0c077b6..b26d1e14 100644 --- a/qml/DomainGraphLayout.qml +++ b/qml/DomainGraphLayout.qml @@ -68,6 +68,7 @@ Item readonly property int radius_: 10 readonly property int connection_thickness_: 5 readonly property int elements_spacing_: 12 + readonly property int containers_spacing_: 100 readonly property int endpoint_height_: 40 readonly property int first_indentation_: 5 readonly property int icon_size_: 18 @@ -76,6 +77,7 @@ Item readonly property int scrollbar_min_size_: 8 readonly property int scrollbar_max_size_: 12 readonly property int topic_thickness_: 10 + readonly property int wheel_displacement_: 30 readonly property string topic_color_: Theme.grey readonly property string host_color_: Theme.darkGrey readonly property string user_color_: Theme.eProsimaLightBlue @@ -100,7 +102,7 @@ Item flickableDirection: Flickable.HorizontalFlick boundsBehavior: Flickable.StopAtBounds - contentWidth: topicsList.contentWidth + 100 + contentWidth: topicsList.contentWidth + containers_spacing_ contentHeight: parent.height ScrollBar.vertical: ScrollBar { policy: ScrollBar.AlwaysOff } @@ -275,7 +277,7 @@ Item interactive: false clip: true - contentWidth: topicsList.contentWidth + 100 + contentWidth: topicsList.contentWidth + containers_spacing_ contentHeight: mainView.contentHeight Rectangle {id: topic_connections; anchors.fill:parent; color: "transparent" } @@ -304,12 +306,12 @@ Item if (topicView.contentWidth > topicView.width) { if (wheel.angleDelta.y > 0) { - topicView.contentX -= 30 + topicView.contentX -= wheel_displacement_ if (topicView.contentX < 0) { topicView.contentX = 0; } } else { - topicView.contentX += 30 + topicView.contentX += wheel_displacement_ if (topicView.contentX + topicView.width > topicView.contentWidth) { topicView.contentX = topicView.contentWidth - topicView.width; } From d0ff1706e151d5dfff453feea29febbeb8b180e9 Mon Sep 17 00:00:00 2001 From: JesusPoderoso Date: Tue, 24 Oct 2023 08:25:56 +0200 Subject: [PATCH 10/36] Refs #19532: [ARS] Simplify hidden scrollbar Signed-off-by: JesusPoderoso --- qml/DomainGraphLayout.qml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/qml/DomainGraphLayout.qml b/qml/DomainGraphLayout.qml index b26d1e14..bdd049ac 100644 --- a/qml/DomainGraphLayout.qml +++ b/qml/DomainGraphLayout.qml @@ -284,8 +284,7 @@ Item // Not visible scroll bar ScrollBar.vertical: ScrollBar{ id: custom_bar - width: 0 - interactive: false + visible: false // connection to move vertically the view when entities view moves vertically Connections { From 31c0b3bccf6ae7b2a6c7c5eb7744b96db85e3323 Mon Sep 17 00:00:00 2001 From: JesusPoderoso Date: Tue, 24 Oct 2023 08:56:23 +0200 Subject: [PATCH 11/36] Refs #19532: [ARS] Remove unnecesary item Signed-off-by: JesusPoderoso --- qml/DomainGraphLayout.qml | 9 --------- 1 file changed, 9 deletions(-) diff --git a/qml/DomainGraphLayout.qml b/qml/DomainGraphLayout.qml index bdd049ac..bc0fe039 100644 --- a/qml/DomainGraphLayout.qml +++ b/qml/DomainGraphLayout.qml @@ -361,15 +361,6 @@ Item } } - // middle section to cut topics layout - Rectangle { - anchors.top: parent.top - anchors.bottom: parent.bottom - anchors.left: mainView.right - width: elements_spacing_ - color: "white" - } - // Entities vertical flickable (left section) Flickable { id: mainView From 51fe41862f9b6a6f7b0a3d10f5e9794c25590248 Mon Sep 17 00:00:00 2001 From: JesusPoderoso Date: Tue, 24 Oct 2023 08:57:25 +0200 Subject: [PATCH 12/36] Refs #19532: [ARS] Update status comparision to uppercase Signed-off-by: JesusPoderoso --- qml/DomainGraphLayout.qml | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/qml/DomainGraphLayout.qml b/qml/DomainGraphLayout.qml index bc0fe039..f1062082 100644 --- a/qml/DomainGraphLayout.qml +++ b/qml/DomainGraphLayout.qml @@ -530,14 +530,14 @@ Item Rectangle { color: "transparent" - width: modelData["status"] != "ok" + width: modelData["status"] != "OK" ? first_indentation_ : 0 } IconSVG { - visible: modelData["status"] != "ok" + visible: modelData["status"] != "OK" name: "issues" color: "white" - size: modelData["status"] != "ok"? icon_size_ : 0 + size: modelData["status"] != "OK"? icon_size_ : 0 } Rectangle { color: "transparent" @@ -673,14 +673,14 @@ Item Rectangle { color: "transparent" - width: modelData["status"] != "ok" + width: modelData["status"] != "OK" ? first_indentation_ : 0 } IconSVG { - visible: modelData["status"] != "ok" + visible: modelData["status"] != "OK" name: "issues" color: "white" - size: modelData["status"] != "ok"? icon_size_ : 0 + size: modelData["status"] != "OK"? icon_size_ : 0 } Rectangle { color: "transparent" @@ -814,14 +814,14 @@ Item Rectangle { color: "transparent" - width: modelData["status"] != "ok" + width: modelData["status"] != "OK" ? first_indentation_ : 0 } IconSVG { - visible: modelData["status"] != "ok" + visible: modelData["status"] != "OK" name: "issues" color: "white" - size: modelData["status"] != "ok"? icon_size_ : 0 + size: modelData["status"] != "OK"? icon_size_ : 0 } Rectangle { color: "transparent" @@ -954,13 +954,13 @@ Item Rectangle { color: "transparent" - width: modelData["status"] != "ok" + width: modelData["status"] != "OK" ? first_indentation_ : 0 } IconSVG { - visible: modelData["status"] != "ok" + visible: modelData["status"] != "OK" name: "issues" - size: modelData["status"] != "ok"? icon_size_ : 0 + size: modelData["status"] != "OK"? icon_size_ : 0 } Rectangle { color: "transparent" @@ -1118,13 +1118,13 @@ Item Rectangle { color: "transparent" - width: modelData["status"] != "ok" + width: modelData["status"] != "OK" ? first_indentation_ : 0 } IconSVG { - visible: modelData["status"] != "ok" + visible: modelData["status"] != "OK" name: "issues" - size: modelData["status"] != "ok"? icon_size_ : 0 + size: modelData["status"] != "OK"? icon_size_ : 0 } Rectangle { color: "transparent" From 74d2818177c7298702786ce72942475959a8a7bf Mon Sep 17 00:00:00 2001 From: JesusPoderoso Date: Tue, 24 Oct 2023 09:07:40 +0200 Subject: [PATCH 13/36] Refs #19532: [ARS] Remove unnecessary width conditions Signed-off-by: JesusPoderoso --- qml/DomainGraphLayout.qml | 40 ++++++++++----------------------------- 1 file changed, 10 insertions(+), 30 deletions(-) diff --git a/qml/DomainGraphLayout.qml b/qml/DomainGraphLayout.qml index f1062082..4e8fe866 100644 --- a/qml/DomainGraphLayout.qml +++ b/qml/DomainGraphLayout.qml @@ -495,9 +495,7 @@ Item height: host_tag.height + usersList.height width: hostRowLayout.implicitWidth > max_host_width_ ? hostRowLayout.implicitWidth - : max_host_width_ == 0 - ? hostRowLayout.implicitWidth - : max_host_width_ + : max_host_width_ // background Rectangle @@ -516,9 +514,7 @@ Item anchors.horizontalCenter: parent.horizontalCenter implicitWidth: hostRowLayout.implicitWidth > max_host_width_ ? hostRowLayout.implicitWidth - : max_host_width_ == 0 - ? hostRowLayout.implicitWidth - : max_host_width_ + : max_host_width_ height: label_height_ color: host_color_ radius: radius_ @@ -637,9 +633,7 @@ Item height: user_tag.height + processesList.height width: userRowLayout.implicitWidth > max_user_width_ ? userRowLayout.implicitWidth - : max_user_width_ == 0 - ? userRowLayout.implicitWidth - : max_user_width_ + : max_user_width_ // background Rectangle @@ -659,9 +653,7 @@ Item anchors.horizontalCenter: parent.horizontalCenter implicitWidth: userRowLayout.implicitWidth > max_user_width_ ? userRowLayout.implicitWidth - : max_user_width_ == 0 - ? userRowLayout.implicitWidth - : max_user_width_ + : max_user_width_ height: label_height_ color: user_color_ radius: radius_ @@ -778,9 +770,7 @@ Item height: process_tag.height + participantsList.height width: processRowLayout.implicitWidth > max_process_width_ ? processRowLayout.implicitWidth - : max_process_width_ == 0 - ? processRowLayout.implicitWidth - : max_process_width_ + : max_process_width_ // background Rectangle @@ -800,9 +790,7 @@ Item anchors.horizontalCenter: parent.horizontalCenter implicitWidth: processRowLayout.implicitWidth > max_process_width_ ? processRowLayout.implicitWidth - : max_process_width_ == 0 - ? processRowLayout.implicitWidth - : max_process_width_ + : max_process_width_ height: label_height_ color: process_color_ radius: radius_ @@ -918,9 +906,7 @@ Item height: participant_tag.height + endpointsList.height width: participantRowLayout.implicitWidth > max_participant_width_ ? participantRowLayout.implicitWidth - : max_participant_width_ == 0 - ? participantRowLayout.implicitWidth - : max_participant_width_ + : max_participant_width_ // background Rectangle @@ -940,9 +926,7 @@ Item anchors.horizontalCenter: parent.horizontalCenter implicitWidth: participantRowLayout.implicitWidth > max_participant_width_ ? participantRowLayout.implicitWidth - : max_participant_width_ == 0 - ? participantRowLayout.implicitWidth - : max_participant_width_ + : max_participant_width_ height: label_height_ color: participant_color_ radius: radius_ @@ -1065,9 +1049,7 @@ Item id: endpointComponent width: endpointRowLayout.implicitWidth > max_endpoint_width_ ? endpointRowLayout.implicitWidth - : max_endpoint_width_ == 0 - ? endpointRowLayout.implicitWidth - : max_endpoint_width_ + : max_endpoint_width_ height: endpoint_height_ // Saves the endpoint needed info for connection representation @@ -1104,9 +1086,7 @@ Item anchors.horizontalCenter: parent.horizontalCenter implicitWidth: endpointRowLayout.implicitWidth > max_endpoint_width_ ? endpointRowLayout.implicitWidth - : max_endpoint_width_ == 0 - ? endpointRowLayout.implicitWidth - : max_endpoint_width_ + : max_endpoint_width_ height: endpoint_height_ color: endpoint_background.color radius: radius_ From 79a6fc4c13344271f7765fe1537db89d825abd18 Mon Sep 17 00:00:00 2001 From: JesusPoderoso Date: Tue, 24 Oct 2023 09:14:23 +0200 Subject: [PATCH 14/36] Refs #19532: [ARS] Remove unnecessary filling rect Signed-off-by: JesusPoderoso --- qml/DomainGraphLayout.qml | 38 +------------------------------------- 1 file changed, 1 insertion(+), 37 deletions(-) diff --git a/qml/DomainGraphLayout.qml b/qml/DomainGraphLayout.qml index 4e8fe866..93243dea 100644 --- a/qml/DomainGraphLayout.qml +++ b/qml/DomainGraphLayout.qml @@ -550,15 +550,6 @@ Item color: "white" } } - Rectangle { - visible: host_tag.implicitWidth < max_host_width_ - anchors.left: host_tag.right - anchors.verticalCenter: parent.verticalCenter - height: host_tag.height - width: max_host_width_ - host_tag.implicitWidth - color: host_background.color - radius: radius_ - } MouseArea { anchors.fill: parent @@ -689,15 +680,6 @@ Item color: "white" } } - Rectangle { - visible: user_tag.implicitWidth < max_user_width_ - anchors.left: user_tag.right - anchors.verticalCenter: parent.verticalCenter - height: user_tag.height - width: max_user_width_ - user_tag.implicitWidth - color: user_background.color - radius: radius_ - } MouseArea { anchors.fill: parent @@ -826,15 +808,6 @@ Item color: "white" } } - Rectangle { - visible: process_tag.implicitWidth < max_process_width_ - anchors.left: process_tag.right - anchors.verticalCenter: parent.verticalCenter - height: process_tag.height - width: max_process_width_ - process_tag.implicitWidth - color: process_background.color - radius: radius_ - } MouseArea { anchors.fill: parent @@ -959,15 +932,6 @@ Item Layout.rightMargin: spacing_icon_label_ + first_indentation_ } } - Rectangle { - visible: participant_tag.implicitWidth < max_participant_width_ - anchors.left: participant_tag.right - anchors.verticalCenter: parent.verticalCenter - height: participant_tag.height - width: max_participant_width_ - participant_tag.implicitWidth - color: participant_background.color - radius: radius_ - } MouseArea { anchors.fill: parent @@ -1086,7 +1050,7 @@ Item anchors.horizontalCenter: parent.horizontalCenter implicitWidth: endpointRowLayout.implicitWidth > max_endpoint_width_ ? endpointRowLayout.implicitWidth - : max_endpoint_width_ + : max_endpoint_width_ height: endpoint_height_ color: endpoint_background.color radius: radius_ From 0a362f04eaa6a9d72a6c2df060f9ce9e2bc8919b Mon Sep 17 00:00:00 2001 From: JesusPoderoso Date: Tue, 24 Oct 2023 09:16:43 +0200 Subject: [PATCH 15/36] Refs #19532: [ARS] Fix comment Signed-off-by: JesusPoderoso --- qml/TabLayout.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qml/TabLayout.qml b/qml/TabLayout.qml index 1c72238f..1771b71a 100644 --- a/qml/TabLayout.qml +++ b/qml/TabLayout.qml @@ -29,7 +29,7 @@ Item { property int current_: 0 // current tab displayed property int last_index_: 1 // force unique idx on QML components property var tab_model_: [{"idx":0, "title":"New Tab", "stack_id": 0}] // tab model for tab bad and tab management - property bool disable_chart_selection_: false // flag to disable multiple chart view tabs + property bool disable_chart_selection_: false // flag to disable multiple chart view tabs // private signals signal open_domain_view_(int stack_id, int entity_id, int domain_id) From cac17463035c46f14bcc8d94e9cbc2afaefa279e Mon Sep 17 00:00:00 2001 From: JesusPoderoso Date: Tue, 24 Oct 2023 09:18:55 +0200 Subject: [PATCH 16/36] Refs #19532: [ARS] Remove unnecessary recursive call Signed-off-by: JesusPoderoso --- qml/TabLayout.qml | 2 -- 1 file changed, 2 deletions(-) diff --git a/qml/TabLayout.qml b/qml/TabLayout.qml index 1771b71a..4b980fd1 100644 --- a/qml/TabLayout.qml +++ b/qml/TabLayout.qml @@ -430,8 +430,6 @@ Item { } } } - - refresh_layout(current_) } // update idx model tab_list.model = tabLayout.tab_model_ From 2773f37d0b9aafe22cd18f1468c63bf71dcded2d Mon Sep 17 00:00:00 2001 From: JesusPoderoso Date: Tue, 24 Oct 2023 09:23:31 +0200 Subject: [PATCH 17/36] Refs #19532: [ARS] Fix refresh domain info call Signed-off-by: JesusPoderoso --- qml/TabLayout.qml | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/qml/TabLayout.qml b/qml/TabLayout.qml index 4b980fd1..0bf44bdc 100644 --- a/qml/TabLayout.qml +++ b/qml/TabLayout.qml @@ -289,7 +289,20 @@ Item { onUpdate_tab_name: { tabLayout.tab_model_[current_]["title"] = new_name - refresh_layout(current_) + + // update model to set the visual change + tab_list.model = tabLayout.tab_model_ + + // update left panel information + for (var i=0; i 0) + { + controller.domain_click(stack_layout.children[i].currentItem.entity_id) + break; + } + } } Connections { From 6ede22e524c8704d699838c656ac52340f51d1ca Mon Sep 17 00:00:00 2001 From: JesusPoderoso Date: Tue, 24 Oct 2023 09:49:49 +0200 Subject: [PATCH 18/36] Refs #19532: [ARS] Remove unnecessary stack anchoring Signed-off-by: JesusPoderoso --- qml/TabLayout.qml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/qml/TabLayout.qml b/qml/TabLayout.qml index 0bf44bdc..a172d778 100644 --- a/qml/TabLayout.qml +++ b/qml/TabLayout.qml @@ -50,7 +50,7 @@ Item { // initialize first element in the tab Component.onCompleted:{ - var new_stack = stack_component.createObject(null, {"stack_id": 0, "anchors.fill": "parent"}) + var new_stack = stack_component.createObject(null, {"stack_id": 0}) stack_layout.children.push(new_stack) refresh_layout(current_) } @@ -215,7 +215,6 @@ Item { StackView { id: stack property int stack_id: 0 - anchors.fill: parent initialItem: view_selector // override push transition to none @@ -411,8 +410,7 @@ Item { { var idx = tabLayout.tab_model_.length tabLayout.tab_model_[idx] = {"idx" : idx, "title": "New Tab", "stack_id":last_index_} - var new_stack = stack_component.createObject(null, {"stack_id": tabLayout.tab_model_[idx]["stack_id"], - "anchors.fill": "parent"}) + var new_stack = stack_component.createObject(null, {"stack_id": tabLayout.tab_model_[idx]["stack_id"]}) last_index_++ stack_layout.children.push(new_stack) refresh_layout(idx) From 0c6a37bd057f835f21d98137711c68d29e3faf6f Mon Sep 17 00:00:00 2001 From: JesusPoderoso Date: Tue, 24 Oct 2023 10:05:04 +0200 Subject: [PATCH 19/36] Refs #19532: [ARS] Remove unnecessary 'z' ordering Signed-off-by: JesusPoderoso Refs #19532: [ARS] Remove unnecessary 'z' ordering Signed-off-by: JesusPoderoso --- qml/DomainGraphLayout.qml | 4 ---- qml/TabLayout.qml | 5 ----- 2 files changed, 9 deletions(-) diff --git a/qml/DomainGraphLayout.qml b/qml/DomainGraphLayout.qml index 93243dea..6d3533ee 100644 --- a/qml/DomainGraphLayout.qml +++ b/qml/DomainGraphLayout.qml @@ -369,7 +369,6 @@ Item anchors.topMargin: 2* elements_spacing_ + label_height_ flickableDirection: Flickable.VerticalFlick boundsBehavior: Flickable.StopAtBounds - z: 10 contentWidth: mainSpace.width contentHeight: mainSpace.height @@ -382,7 +381,6 @@ Item anchors.top: parent.top; anchors.topMargin: -elements_spacing_ anchors.right: parent.right; anchors.rightMargin: parent.width - domainGraphLayout.width hoverEnabled: true - z: 20 contentItem: Item { implicitWidth: scrollbar_min_size_ @@ -448,7 +446,6 @@ Item anchors.left: parent.left; anchors.leftMargin: elements_spacing_ interactive: false spacing: elements_spacing_ - z: 20 // Resizing management connections Connections @@ -1155,7 +1152,6 @@ Item height: 2* elements_spacing_ + label_height_ width: max_host_width_ +2* elements_spacing_ color: "white" - z: 12 // Refresh button Button{ diff --git a/qml/TabLayout.qml b/qml/TabLayout.qml index a172d778..425a1577 100644 --- a/qml/TabLayout.qml +++ b/qml/TabLayout.qml @@ -56,7 +56,6 @@ Item { } ChartsLayout { - z: 1 visible: disable_chart_selection_ id: chartsLayout anchors.fill: stack_layout @@ -71,7 +70,6 @@ Item { anchors.left: parent.left width: contentWidth height: tabs_height_ - z: 100 // z is the front-back order. The tab bar must always be on top of any StackView component orientation: ListView.Horizontal model: tabLayout.tab_model_ interactive: false @@ -149,7 +147,6 @@ Item { // Add new tab button Rectangle { id: add_new_tab_button - z: 99 visible: tabLayout.tab_model_.length < max_tabs_ anchors.right: remain_width_rect.left anchors.verticalCenter: tab_list.verticalCenter @@ -182,7 +179,6 @@ Item { // remain space in tab bar handled by this component Rectangle { id: remain_width_rect - z: 98 width: tabLayout.width - add_new_tab_button.width - tab_list.width; height: tabs_height_ anchors.right: tabLayout.right anchors.verticalCenter: tab_list.verticalCenter @@ -204,7 +200,6 @@ Item { // stack layout (where idx referred to the tab, which would contain different views) StackLayout { id: stack_layout - z: 1 // z is the front-back order. The tab bar must always be on top of any stackview component width: tabLayout.width anchors.top: tab_list.bottom; anchors.bottom: tabLayout.bottom From 69232134a3cb426a2c8c1ee833fabdce6dc4f0ee Mon Sep 17 00:00:00 2001 From: JesusPoderoso Date: Tue, 24 Oct 2023 10:21:51 +0200 Subject: [PATCH 20/36] Refs #19532: [ARS] Remove unnecessary load model calls Signed-off-by: JesusPoderoso --- qml/TabLayout.qml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/qml/TabLayout.qml b/qml/TabLayout.qml index 425a1577..c3b37e59 100644 --- a/qml/TabLayout.qml +++ b/qml/TabLayout.qml @@ -354,10 +354,6 @@ Item { standardButtons: Dialog.Ok | Dialog.Cancel } - Component.onCompleted: { - controller.update_available_entity_ids("Domain", "getDataDialogSourceEntityId") - } - onAboutToShow: { custom_combobox.currentIndex = -1 controller.update_available_entity_ids("Domain", "getDataDialogSourceEntityId") @@ -381,7 +377,6 @@ Item { Component.onCompleted: { - controller.update_available_entity_ids("Domain", "getDataDialogSourceEntityId") currentIndex = -1 custom_combobox.recalculateWidth() } From da67edfa3329bd22ad46bc48dc92dda054bfc896 Mon Sep 17 00:00:00 2001 From: JesusPoderoso Date: Tue, 24 Oct 2023 11:31:18 +0200 Subject: [PATCH 21/36] Refs #19532: Reorder elements to fix tabs overlapping Signed-off-by: JesusPoderoso --- qml/TabLayout.qml | 265 +++++++++++++++++++++++----------------------- 1 file changed, 132 insertions(+), 133 deletions(-) diff --git a/qml/TabLayout.qml b/qml/TabLayout.qml index c3b37e59..8de5cfaf 100644 --- a/qml/TabLayout.qml +++ b/qml/TabLayout.qml @@ -64,139 +64,6 @@ Item { } } - ListView { - id: tab_list - anchors.top: parent.top - anchors.left: parent.left - width: contentWidth - height: tabs_height_ - orientation: ListView.Horizontal - model: tabLayout.tab_model_ - interactive: false - - // tab design - delegate: Rectangle { - id: delegated_rect - height: tabs_height_ - width: tabLayout.tab_model_.length == max_tabs_ - ? tabLayout.width / tabLayout.tab_model_.length < tab_icons_size_+ (4*tabs_margins_) - ? current_ == modelData["idx"] ? tab_icons_size_+ (2 * tabs_margins_) - : tabLayout.width / tabLayout.tab_model_.length : tabLayout.width / tabLayout.tab_model_.length - : (tabLayout.width - add_new_tab_button.width) / tabLayout.tab_model_.length > max_tab_size_ ? max_tab_size_ - : (tabLayout.width - add_new_tab_button.width) / tabLayout.tab_model_.length < tab_icons_size_+ (4*tabs_margins_) - ? current_ == modelData["idx"] ? tab_icons_size_+ (2 * tabs_margins_) - : (tabLayout.width - add_new_tab_button.width) / tabLayout.tab_model_.length - : (tabLayout.width - add_new_tab_button.width) / tabLayout.tab_model_.length - color: current_ == modelData["idx"] ? selected_tab_color_ : not_selected_tab_color_ - property string shadow_color: current_ == modelData["idx"] ? selected_shadow_tab_color_ : not_selected_shadow_tab_color_ - gradient: Gradient { - orientation: Gradient.Horizontal - GradientStop { position: 0.0; color: modelData["idx"] == 0 || current_ == modelData["idx"] ? delegated_rect.color : shadow_color} - GradientStop { position: 0.04; color: delegated_rect.color } - GradientStop { position: 0.96; color: delegated_rect.color } - GradientStop { position: 1.0; color: current_ == modelData["idx"] + 1 ? shadow_color : delegated_rect.color} - } - Text { - horizontalAlignment: Qt.AlignLeft; verticalAlignment: Qt.AlignVCenter - anchors.left: parent.left - anchors.leftMargin: tabs_margins_ - anchors.right: close_icon.visible ? close_icon.left : parent.right - anchors.rightMargin: tabs_margins_ - anchors.verticalCenter: parent.verticalCenter - text: modelData["title"] - elide: Text.ElideRight - } - // close tab icon - IconSVG { - id: close_icon - visible: modelData["idx"] == current_ ? true : parent.width > min_tab_size_ - anchors.right: parent.right - anchors.rightMargin: tabs_margins_ - anchors.verticalCenter: parent.verticalCenter - name: "cross" - size: tab_icons_size_ - } - // tab selection action - MouseArea { - anchors.top: parent.top; anchors.bottom: parent.bottom; anchors.left: parent.left; - anchors.right: close_icon.left; anchors.rightMargin: - tabs_margins_ - onClicked: { - refresh_layout(modelData["idx"]) - } - } - // close tab action - MouseArea { - anchors.top: parent.top; anchors.bottom: parent.bottom; anchors.right: parent.right - anchors.left: close_icon.left; anchors.leftMargin: - tabs_margins_ - onClicked: { - // act as close is close icon shown (same expression as in close_icon visible attribute) - if (modelData["idx"] == current_ || parent.width > min_tab_size_) - { - remove_idx(modelData["idx"]) - } - // if not, act as open tab action - else - { - refresh_layout(modelData["idx"]) - } - } - } - } - } - - // Add new tab button - Rectangle { - id: add_new_tab_button - visible: tabLayout.tab_model_.length < max_tabs_ - anchors.right: remain_width_rect.left - anchors.verticalCenter: tab_list.verticalCenter - height: tabs_height_ - width: tabLayout.tab_model_.length == max_tabs_ ? 0 : add_tab_width_ - color: not_selected_tab_color_ - gradient: Gradient { - orientation: Gradient.Horizontal - GradientStop { position: 0.0; color: not_selected_shadow_tab_color_} - GradientStop { position: 0.08; color: add_new_tab_button.color } - GradientStop { position: 1.0; color: add_new_tab_button.color } - } - // add new tab icon - IconSVG { - visible: tabLayout.tab_model_.length < max_tabs_ - anchors.centerIn: parent - name: "plus" - size: tab_icons_size_ - } - // add new tab action - MouseArea { - anchors.fill: parent - onClicked: { - if (tabLayout.tab_model_.length < max_tabs_) - tabLayout.create_new_tab() - } - } - } - - // remain space in tab bar handled by this component - Rectangle { - id: remain_width_rect - width: tabLayout.width - add_new_tab_button.width - tab_list.width; height: tabs_height_ - anchors.right: tabLayout.right - anchors.verticalCenter: tab_list.verticalCenter - color: not_selected_tab_color_ - - Rectangle { - width: parent.width >= 80 ? 80 : parent.width; height: parent.height - color: parent.color - gradient: Gradient { - orientation: Gradient.Horizontal - GradientStop { position: 0.0; color: not_selected_shadow_tab_color_} - GradientStop { position: 0.08; color: not_selected_tab_color_ } - GradientStop { position: 1.0; color: not_selected_tab_color_ } - } - } - } - - // stack layout (where idx referred to the tab, which would contain different views) StackLayout { id: stack_layout @@ -336,6 +203,138 @@ Item { } } + ListView { + id: tab_list + anchors.top: parent.top + anchors.left: parent.left + width: contentWidth + height: tabs_height_ + orientation: ListView.Horizontal + model: tabLayout.tab_model_ + interactive: false + + // tab design + delegate: Rectangle { + id: delegated_rect + height: tabs_height_ + width: tabLayout.tab_model_.length == max_tabs_ + ? tabLayout.width / tabLayout.tab_model_.length < tab_icons_size_+ (4*tabs_margins_) + ? current_ == modelData["idx"] ? tab_icons_size_+ (2 * tabs_margins_) + : tabLayout.width / tabLayout.tab_model_.length : tabLayout.width / tabLayout.tab_model_.length + : (tabLayout.width - add_new_tab_button.width) / tabLayout.tab_model_.length > max_tab_size_ ? max_tab_size_ + : (tabLayout.width - add_new_tab_button.width) / tabLayout.tab_model_.length < tab_icons_size_+ (4*tabs_margins_) + ? current_ == modelData["idx"] ? tab_icons_size_+ (2 * tabs_margins_) + : (tabLayout.width - add_new_tab_button.width) / tabLayout.tab_model_.length + : (tabLayout.width - add_new_tab_button.width) / tabLayout.tab_model_.length + color: current_ == modelData["idx"] ? selected_tab_color_ : not_selected_tab_color_ + property string shadow_color: current_ == modelData["idx"] ? selected_shadow_tab_color_ : not_selected_shadow_tab_color_ + gradient: Gradient { + orientation: Gradient.Horizontal + GradientStop { position: 0.0; color: modelData["idx"] == 0 || current_ == modelData["idx"] ? delegated_rect.color : shadow_color} + GradientStop { position: 0.04; color: delegated_rect.color } + GradientStop { position: 0.96; color: delegated_rect.color } + GradientStop { position: 1.0; color: current_ == modelData["idx"] + 1 ? shadow_color : delegated_rect.color} + } + Text { + horizontalAlignment: Qt.AlignLeft; verticalAlignment: Qt.AlignVCenter + anchors.left: parent.left + anchors.leftMargin: tabs_margins_ + anchors.right: close_icon.visible ? close_icon.left : parent.right + anchors.rightMargin: tabs_margins_ + anchors.verticalCenter: parent.verticalCenter + text: modelData["title"] + elide: Text.ElideRight + } + // close tab icon + IconSVG { + id: close_icon + visible: modelData["idx"] == current_ ? true : parent.width > min_tab_size_ + anchors.right: parent.right + anchors.rightMargin: tabs_margins_ + anchors.verticalCenter: parent.verticalCenter + name: "cross" + size: tab_icons_size_ + } + // tab selection action + MouseArea { + anchors.top: parent.top; anchors.bottom: parent.bottom; anchors.left: parent.left; + anchors.right: close_icon.left; anchors.rightMargin: - tabs_margins_ + onClicked: { + refresh_layout(modelData["idx"]) + } + } + // close tab action + MouseArea { + anchors.top: parent.top; anchors.bottom: parent.bottom; anchors.right: parent.right + anchors.left: close_icon.left; anchors.leftMargin: - tabs_margins_ + onClicked: { + // act as close is close icon shown (same expression as in close_icon visible attribute) + if (modelData["idx"] == current_ || parent.width > min_tab_size_) + { + remove_idx(modelData["idx"]) + } + // if not, act as open tab action + else + { + refresh_layout(modelData["idx"]) + } + } + } + } + } + + // Add new tab button + Rectangle { + id: add_new_tab_button + visible: tabLayout.tab_model_.length < max_tabs_ + anchors.right: remain_width_rect.left + anchors.verticalCenter: tab_list.verticalCenter + height: tabs_height_ + width: tabLayout.tab_model_.length == max_tabs_ ? 0 : add_tab_width_ + color: not_selected_tab_color_ + gradient: Gradient { + orientation: Gradient.Horizontal + GradientStop { position: 0.0; color: not_selected_shadow_tab_color_} + GradientStop { position: 0.08; color: add_new_tab_button.color } + GradientStop { position: 1.0; color: add_new_tab_button.color } + } + // add new tab icon + IconSVG { + visible: tabLayout.tab_model_.length < max_tabs_ + anchors.centerIn: parent + name: "plus" + size: tab_icons_size_ + } + // add new tab action + MouseArea { + anchors.fill: parent + onClicked: { + if (tabLayout.tab_model_.length < max_tabs_) + tabLayout.create_new_tab() + } + } + } + + // remain space in tab bar handled by this component + Rectangle { + id: remain_width_rect + width: tabLayout.width - add_new_tab_button.width - tab_list.width; height: tabs_height_ + anchors.right: tabLayout.right + anchors.verticalCenter: tab_list.verticalCenter + color: not_selected_tab_color_ + + Rectangle { + width: parent.width >= 80 ? 80 : parent.width; height: parent.height + color: parent.color + gradient: Gradient { + orientation: Gradient.Horizontal + GradientStop { position: 0.0; color: not_selected_shadow_tab_color_} + GradientStop { position: 0.08; color: not_selected_tab_color_ } + GradientStop { position: 1.0; color: not_selected_tab_color_ } + } + } + } + Dialog { id: domain_id_dialog From 3380b871cb336a4df25f176b5974ad29b06279f0 Mon Sep 17 00:00:00 2001 From: JesusPoderoso Date: Tue, 24 Oct 2023 11:36:19 +0200 Subject: [PATCH 22/36] Refs #19532: Fix warning displayed when showing metatraffic Signed-off-by: JesusPoderoso --- qml/DomainGraphLayout.qml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/qml/DomainGraphLayout.qml b/qml/DomainGraphLayout.qml index 6d3533ee..f75e0931 100644 --- a/qml/DomainGraphLayout.qml +++ b/qml/DomainGraphLayout.qml @@ -186,8 +186,11 @@ Item for (var c = 0; c < topicsList.count; c++) { topicsList.currentIndex = c - listViewHeight = topicsList.currentItem.height - listViewWidth += topicsList.currentItem.width + elements_spacing_ + if (topicsList.currentItem != null) + { + listViewHeight = topicsList.currentItem.height + listViewWidth += topicsList.currentItem.width + elements_spacing_ + } } topicsList.height = listViewHeight topicsList.width = listViewWidth From a10f6ceb28800266e3649ac2401bf8ef6ebcdf86 Mon Sep 17 00:00:00 2001 From: JesusPoderoso Date: Tue, 24 Oct 2023 16:23:17 +0200 Subject: [PATCH 23/36] Refs #19533: [ARS] Add parenthesys to conditional assignations Signed-off-by: JesusPoderoso --- qml/DomainGraphLayout.qml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/qml/DomainGraphLayout.qml b/qml/DomainGraphLayout.qml index f75e0931..1a864bb4 100644 --- a/qml/DomainGraphLayout.qml +++ b/qml/DomainGraphLayout.qml @@ -622,7 +622,7 @@ Item delegate: Item { height: user_tag.height + processesList.height - width: userRowLayout.implicitWidth > max_user_width_ + width: userRowLayout.implicitWidth > (entity_box_width_-(2*elements_spacing_)) ? userRowLayout.implicitWidth : max_user_width_ @@ -642,7 +642,7 @@ Item id: user_tag anchors.top: parent.top anchors.horizontalCenter: parent.horizontalCenter - implicitWidth: userRowLayout.implicitWidth > max_user_width_ + implicitWidth: userRowLayout.implicitWidth > (entity_box_width_-(2*elements_spacing_)) ? userRowLayout.implicitWidth : max_user_width_ height: label_height_ @@ -750,7 +750,7 @@ Item delegate: Item { height: process_tag.height + participantsList.height - width: processRowLayout.implicitWidth > max_process_width_ + width: processRowLayout.implicitWidth > (entity_box_width_-(4*elements_spacing_)) ? processRowLayout.implicitWidth : max_process_width_ @@ -770,7 +770,7 @@ Item id: process_tag anchors.top: parent.top anchors.horizontalCenter: parent.horizontalCenter - implicitWidth: processRowLayout.implicitWidth > max_process_width_ + implicitWidth: processRowLayout.implicitWidth > (entity_box_width_-(4*elements_spacing_)) ? processRowLayout.implicitWidth : max_process_width_ height: label_height_ @@ -877,7 +877,7 @@ Item delegate: Item { height: participant_tag.height + endpointsList.height - width: participantRowLayout.implicitWidth > max_participant_width_ + width: participantRowLayout.implicitWidth > (entity_box_width_-(6*elements_spacing_)) ? participantRowLayout.implicitWidth : max_participant_width_ @@ -897,7 +897,7 @@ Item id: participant_tag anchors.top: parent.top anchors.horizontalCenter: parent.horizontalCenter - implicitWidth: participantRowLayout.implicitWidth > max_participant_width_ + implicitWidth: participantRowLayout.implicitWidth > (entity_box_width_-(6*elements_spacing_)) ? participantRowLayout.implicitWidth : max_participant_width_ height: label_height_ @@ -1011,7 +1011,7 @@ Item delegate: Item { id: endpointComponent - width: endpointRowLayout.implicitWidth > max_endpoint_width_ + width: endpointRowLayout.implicitWidth > (entity_box_width_-(8*elements_spacing_)) ? endpointRowLayout.implicitWidth : max_endpoint_width_ height: endpoint_height_ @@ -1048,7 +1048,7 @@ Item id: endpoint_tag anchors.top: parent.top anchors.horizontalCenter: parent.horizontalCenter - implicitWidth: endpointRowLayout.implicitWidth > max_endpoint_width_ + implicitWidth: endpointRowLayout.implicitWidth > (entity_box_width_-(8*elements_spacing_)) ? endpointRowLayout.implicitWidth : max_endpoint_width_ height: endpoint_height_ From e7da8bf74763db9c63884ed3f55e148d925aff86 Mon Sep 17 00:00:00 2001 From: JesusPoderoso Date: Tue, 24 Oct 2023 16:25:29 +0200 Subject: [PATCH 24/36] Refs #19533: [ARS] Add error check in missing entities Signed-off-by: JesusPoderoso --- qml/DomainGraphLayout.qml | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/qml/DomainGraphLayout.qml b/qml/DomainGraphLayout.qml index 1a864bb4..2c794588 100644 --- a/qml/DomainGraphLayout.qml +++ b/qml/DomainGraphLayout.qml @@ -735,11 +735,11 @@ Item for (var c = 0; c < processesList.count; c++) { processesList.currentIndex = c - listViewHeight += processesList.currentItem.height + elements_spacing_ - max_process_width_ = Math.max(max_process_width_, processesList.currentItem.width) - max_process_width_ = Math.max(max_process_width_, max_participant_width_+(2*elements_spacing_)) - max_process_width_ = Math.max(max_process_width_, max_user_width_-(2*elements_spacing_)) - processesList.currentItem.width = max_process_width_ + if (processesList.currentItem != null) + { + listViewHeight += processesList.currentItem.height + elements_spacing_ + entity_box_width_ = Math.max(entity_box_width_, processesList.currentItem.width+(4*elements_spacing_)) + } } processesList.height = listViewHeight + elements_spacing_ @@ -862,11 +862,11 @@ Item for (var c = 0; c < participantsList.count; c++) { participantsList.currentIndex = c - listViewHeight += participantsList.currentItem.height + elements_spacing_ - max_participant_width_ = Math.max(max_participant_width_, participantsList.currentItem.width) - max_participant_width_ = Math.max(max_participant_width_, max_endpoint_width_+(2*elements_spacing_)) - max_participant_width_ = Math.max(max_participant_width_, max_process_width_-(2*elements_spacing_)) - participantsList.currentItem.width = max_participant_width_ + if (participantsList.currentItem != null) + { + listViewHeight += participantsList.currentItem.height + elements_spacing_ + entity_box_width_ = Math.max(entity_box_width_, participantsList.currentItem.width+(6*elements_spacing_)) + } } participantsList.height = listViewHeight + elements_spacing_ @@ -988,10 +988,11 @@ Item for (var c = 0; c < endpointsList.count; c++) { endpointsList.currentIndex = c - listViewHeight += endpointsList.currentItem.height + elements_spacing_ - max_endpoint_width_ = Math.max(max_endpoint_width_, endpointsList.currentItem.width) - max_endpoint_width_ = Math.max(max_endpoint_width_, max_participant_width_-(2*elements_spacing_)) - endpointsList.currentItem.width = max_endpoint_width_ + if (endpointsList.currentItem != null) + { + listViewHeight += endpointsList.currentItem.height + elements_spacing_ + entity_box_width_ = Math.max(entity_box_width_, endpointsList.currentItem.width+(8*elements_spacing_)) + } } endpointsList.height = listViewHeight + elements_spacing_ From e4475784b6ae2ea49aed49aa3c4f0ee70f24605f Mon Sep 17 00:00:00 2001 From: JesusPoderoso Date: Tue, 24 Oct 2023 16:26:06 +0200 Subject: [PATCH 25/36] Refs #19533: [ARS] Remove unnecessary rect Signed-off-by: JesusPoderoso --- qml/DomainGraphLayout.qml | 9 --------- 1 file changed, 9 deletions(-) diff --git a/qml/DomainGraphLayout.qml b/qml/DomainGraphLayout.qml index 2c794588..35cf3a0e 100644 --- a/qml/DomainGraphLayout.qml +++ b/qml/DomainGraphLayout.qml @@ -1093,15 +1093,6 @@ Item } } } - Rectangle { - visible: endpoint_tag.implicitWidth < max_endpoint_width_ - anchors.left: endpoint_tag.right - anchors.verticalCenter: parent.verticalCenter - height: endpoint_tag.height - width: max_endpoint_width_ - endpoint_tag.implicitWidth - color: endpoint_background.color - radius: radius_ - } } } } From 300c1341db60882f7ecb6153225f41140de12414 Mon Sep 17 00:00:00 2001 From: JesusPoderoso Date: Tue, 24 Oct 2023 16:27:12 +0200 Subject: [PATCH 26/36] Refs #19533: [ARS] Add missing space Signed-off-by: JesusPoderoso --- qml/DomainGraphLayout.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qml/DomainGraphLayout.qml b/qml/DomainGraphLayout.qml index 35cf3a0e..b36e021c 100644 --- a/qml/DomainGraphLayout.qml +++ b/qml/DomainGraphLayout.qml @@ -1145,7 +1145,7 @@ Item anchors.top: parent.top anchors.left: parent.left height: 2* elements_spacing_ + label_height_ - width: max_host_width_ +2* elements_spacing_ + width: entity_box_width_ + 2*elements_spacing_ color: "white" // Refresh button From 54cffcb5bdd47af4b198ce9d7af69d0a4db87c07 Mon Sep 17 00:00:00 2001 From: JesusPoderoso Date: Tue, 24 Oct 2023 16:59:30 +0200 Subject: [PATCH 27/36] Refs #19533: [ARS] Improve entities width management Signed-off-by: JesusPoderoso --- qml/DomainGraphLayout.qml | 56 ++++++++++++++++++++++++++++----------- 1 file changed, 41 insertions(+), 15 deletions(-) diff --git a/qml/DomainGraphLayout.qml b/qml/DomainGraphLayout.qml index b36e021c..880d34c9 100644 --- a/qml/DomainGraphLayout.qml +++ b/qml/DomainGraphLayout.qml @@ -472,6 +472,8 @@ Item function resize() { var listViewHeight = 0 + var aux_width = entity_box_width_ + // iterate over each element in the list item for (var c = 0; c < hostsList.count; c++) { @@ -479,14 +481,17 @@ Item if (hostsList.currentItem != null) { listViewHeight += hostsList.currentItem.height + elements_spacing_ - max_host_width_ = Math.max(max_host_width_, hostsList.currentItem.width) - max_host_width_ = Math.max(max_host_width_, (2*elements_spacing_)+max_user_width_) - hostsList.currentItem.width = max_host_width_ + aux_width = Math.max(aux_width, hostsList.currentItem.width) } } hostsList.height = listViewHeight - hostsList.width = max_host_width_ + + // update if necessary + if (aux_width > entity_box_width_) + { + entity_box_width_ = aux_width + } } // Host delegated item box @@ -599,6 +604,7 @@ Item function resize() { var listViewHeight = 0 + var aux_width = entity_box_width_ // iterate over each element in the list item for (var c = 0; c < usersList.count; c++) @@ -607,15 +613,17 @@ Item if (usersList.currentItem != null) { listViewHeight += usersList.currentItem.height + elements_spacing_ - max_user_width_ = Math.max(max_user_width_, usersList.currentItem.width) - max_user_width_ = Math.max(max_user_width_, max_process_width_+(2*elements_spacing_)) - max_user_width_ = Math.max(max_user_width_, max_host_width_-(2*elements_spacing_)) - usersList.currentItem.width = max_user_width_ + aux_width = Math.max(aux_width, usersList.currentItem.width+(2*elements_spacing_)) } } usersList.height = listViewHeight + elements_spacing_ - usersList.width = max_user_width_ + + // update if necessary + if (aux_width > entity_box_width_) + { + entity_box_width_ = aux_width + } } // User delegated item box @@ -730,6 +738,7 @@ Item function resize() { var listViewHeight = 0 + var aux_width = entity_box_width_ // iterate over each element in the list item for (var c = 0; c < processesList.count; c++) @@ -738,12 +747,17 @@ Item if (processesList.currentItem != null) { listViewHeight += processesList.currentItem.height + elements_spacing_ - entity_box_width_ = Math.max(entity_box_width_, processesList.currentItem.width+(4*elements_spacing_)) + aux_width = Math.max(aux_width, processesList.currentItem.width+(4*elements_spacing_)) } } processesList.height = listViewHeight + elements_spacing_ - processesList.width = max_process_width_ + + // update if necessary + if (aux_width > entity_box_width_) + { + entity_box_width_ = aux_width + } } // Process delegated item box @@ -857,6 +871,7 @@ Item function resize() { var listViewHeight = 0 + var aux_width = entity_box_width_ // iterate over each element in the list item for (var c = 0; c < participantsList.count; c++) @@ -865,12 +880,17 @@ Item if (participantsList.currentItem != null) { listViewHeight += participantsList.currentItem.height + elements_spacing_ - entity_box_width_ = Math.max(entity_box_width_, participantsList.currentItem.width+(6*elements_spacing_)) + aux_width = Math.max(aux_width, participantsList.currentItem.width+(6*elements_spacing_)) } } participantsList.height = listViewHeight + elements_spacing_ - participantsList.width = max_participant_width_ + + // update if necessary + if (aux_width > entity_box_width_) + { + entity_box_width_ = aux_width + } } // Participant delegated item box @@ -983,6 +1003,7 @@ Item function resize() { var listViewHeight = 0 + var aux_width = entity_box_width_ // iterate over each element in the list item for (var c = 0; c < endpointsList.count; c++) @@ -991,12 +1012,17 @@ Item if (endpointsList.currentItem != null) { listViewHeight += endpointsList.currentItem.height + elements_spacing_ - entity_box_width_ = Math.max(entity_box_width_, endpointsList.currentItem.width+(8*elements_spacing_)) + aux_width = Math.max(aux_width, endpointsList.currentItem.width+(8*elements_spacing_)) } } endpointsList.height = listViewHeight + elements_spacing_ - endpointsList.width = max_endpoint_width_ + + // update if necessary + if (aux_width > entity_box_width_) + { + entity_box_width_ = aux_width + } } function record_connections() From 7f87234d1f6a1109b25399c2e57f1b7b5051befd Mon Sep 17 00:00:00 2001 From: JesusPoderoso Date: Wed, 25 Oct 2023 10:15:15 +0200 Subject: [PATCH 28/36] Refs #19533: Fix connections Signed-off-by: JesusPoderoso --- qml/DomainGraphLayout.qml | 85 ++++++++++++++++++++++----------------- 1 file changed, 48 insertions(+), 37 deletions(-) diff --git a/qml/DomainGraphLayout.qml b/qml/DomainGraphLayout.qml index 880d34c9..3e72efcc 100644 --- a/qml/DomainGraphLayout.qml +++ b/qml/DomainGraphLayout.qml @@ -39,11 +39,7 @@ Item property var endpoint_topic_connections_: {} // endpoint information needed for connection representation property var topic_painted_: [] // already painted topic connection references property var endpoint_painted_: [] // already painted endpoint connection references - property int max_host_width_: 0 // host entity box width management - property int max_user_width_: 0 // user entity box width management - property int max_process_width_: 0 // process entity box width management - property int max_participant_width_: 0 // participant entity box width management - property int max_endpoint_width_: 0 // endpoint entity box width management + property int entity_box_width_: 0 // entities box width management // Private (resize) signals resize_elements_ will trigger a bunch of resize methods per entities and // HOST TOPIC ─┐ topics, when 2 iterations are performed. The first one is aimed to @@ -63,6 +59,7 @@ Item signal processes_updated_() signal users_updated_() signal hosts_updated_() + signal generate_connections_() // Read only design properties (sizes and colors) readonly property int radius_: 10 @@ -97,8 +94,8 @@ Item Flickable { id: topicView anchors.top: parent.top; anchors.bottom: parent.bottom - anchors.left: parent.left; anchors.leftMargin: max_host_width_ + elements_spacing_ - width: parent.width - max_host_width_ - 2*elements_spacing_ + anchors.left: parent.left; anchors.leftMargin: entity_box_width_ + elements_spacing_ + width: parent.width - entity_box_width_ - 2*elements_spacing_ flickableDirection: Flickable.HorizontalFlick boundsBehavior: Flickable.StopAtBounds @@ -164,9 +161,7 @@ Item function onEndpoints_updated_() { - topicsList.resize() topicsList.create_connections() - topics_updated_() } } @@ -200,6 +195,9 @@ Item { var draw_width = 2*elements_spacing_ + // load topic sizes + topicsList.resize() + // iterate over each element in the list item for (var c = 0; c < topicsList.count; c++) { @@ -210,6 +208,9 @@ Item } draw_width += topicsList.currentItem.width + elements_spacing_ } + + // announce topics are ready + topics_updated_() } // Topic delegated item box with vertical line @@ -368,7 +369,7 @@ Item Flickable { id: mainView anchors.left: parent.left ; anchors.top: parent.top; anchors.bottom: parent.bottom - width: max_host_width_ + elements_spacing_ + width: entity_box_width_ + elements_spacing_ anchors.topMargin: 2* elements_spacing_ + label_height_ flickableDirection: Flickable.VerticalFlick boundsBehavior: Flickable.StopAtBounds @@ -490,7 +491,9 @@ Item // update if necessary if (aux_width > entity_box_width_) { + clear_graph() entity_box_width_ = aux_width + resize_elements_() } } @@ -498,9 +501,9 @@ Item delegate: Item { height: host_tag.height + usersList.height - width: hostRowLayout.implicitWidth > max_host_width_ + width: hostRowLayout.implicitWidth > entity_box_width_ ? hostRowLayout.implicitWidth - : max_host_width_ + : entity_box_width_ // background Rectangle @@ -517,9 +520,9 @@ Item { id: host_tag anchors.horizontalCenter: parent.horizontalCenter - implicitWidth: hostRowLayout.implicitWidth > max_host_width_ + implicitWidth: hostRowLayout.implicitWidth > entity_box_width_ ? hostRowLayout.implicitWidth - : max_host_width_ + : entity_box_width_ height: label_height_ color: host_color_ radius: radius_ @@ -622,7 +625,9 @@ Item // update if necessary if (aux_width > entity_box_width_) { + clear_graph() entity_box_width_ = aux_width + resize_elements_() } } @@ -632,7 +637,7 @@ Item height: user_tag.height + processesList.height width: userRowLayout.implicitWidth > (entity_box_width_-(2*elements_spacing_)) ? userRowLayout.implicitWidth - : max_user_width_ + : entity_box_width_-(2*elements_spacing_) // background Rectangle @@ -652,7 +657,7 @@ Item anchors.horizontalCenter: parent.horizontalCenter implicitWidth: userRowLayout.implicitWidth > (entity_box_width_-(2*elements_spacing_)) ? userRowLayout.implicitWidth - : max_user_width_ + : entity_box_width_-(2*elements_spacing_) height: label_height_ color: user_color_ radius: radius_ @@ -756,7 +761,9 @@ Item // update if necessary if (aux_width > entity_box_width_) { + clear_graph() entity_box_width_ = aux_width + resize_elements_() } } @@ -766,7 +773,7 @@ Item height: process_tag.height + participantsList.height width: processRowLayout.implicitWidth > (entity_box_width_-(4*elements_spacing_)) ? processRowLayout.implicitWidth - : max_process_width_ + : entity_box_width_-(4*elements_spacing_) // background Rectangle @@ -786,7 +793,7 @@ Item anchors.horizontalCenter: parent.horizontalCenter implicitWidth: processRowLayout.implicitWidth > (entity_box_width_-(4*elements_spacing_)) ? processRowLayout.implicitWidth - : max_process_width_ + : entity_box_width_-(4*elements_spacing_) height: label_height_ color: process_color_ radius: radius_ @@ -889,7 +896,9 @@ Item // update if necessary if (aux_width > entity_box_width_) { + clear_graph() entity_box_width_ = aux_width + resize_elements_() } } @@ -899,7 +908,7 @@ Item height: participant_tag.height + endpointsList.height width: participantRowLayout.implicitWidth > (entity_box_width_-(6*elements_spacing_)) ? participantRowLayout.implicitWidth - : max_participant_width_ + : entity_box_width_-(6*elements_spacing_) // background Rectangle @@ -919,7 +928,7 @@ Item anchors.horizontalCenter: parent.horizontalCenter implicitWidth: participantRowLayout.implicitWidth > (entity_box_width_-(6*elements_spacing_)) ? participantRowLayout.implicitWidth - : max_participant_width_ + : entity_box_width_-(6*elements_spacing_) height: label_height_ color: participant_color_ radius: radius_ @@ -987,9 +996,13 @@ Item function onParticipants_updated_() { endpointsList.resize() - endpointsList.record_connections() endpoints_updated_() } + + function onTopics_updated_() + { + endpointsList.record_connections() + } } // Resize performed also when new element included in the model @@ -1021,7 +1034,9 @@ Item // update if necessary if (aux_width > entity_box_width_) { + clear_graph() entity_box_width_ = aux_width + resize_elements_() } } @@ -1032,6 +1047,7 @@ Item endpointsList.currentIndex = c endpointsList.currentItem.record_connection() } + generate_connections_() } // Endpoint delegated item box @@ -1040,14 +1056,14 @@ Item id: endpointComponent width: endpointRowLayout.implicitWidth > (entity_box_width_-(8*elements_spacing_)) ? endpointRowLayout.implicitWidth - : max_endpoint_width_ + : entity_box_width_-(8*elements_spacing_) height: endpoint_height_ // Saves the endpoint needed info for connection representation function record_connection() { var globalCoordinates = endpointComponent.mapToItem(mainSpace, 0, 0) - var src_x = globalCoordinates.x + endpointComponent.width + var src_x = globalCoordinates.x + entity_box_width_-(8*elements_spacing_) var src_y = modelData["accum_y"] + (endpointComponent.height / 2) var left_direction = modelData["kind"] == "datareader" var right_direction = modelData["kind"] == "datawriter" @@ -1077,7 +1093,7 @@ Item anchors.horizontalCenter: parent.horizontalCenter implicitWidth: endpointRowLayout.implicitWidth > (entity_box_width_-(8*elements_spacing_)) ? endpointRowLayout.implicitWidth - : max_endpoint_width_ + : entity_box_width_-(8*elements_spacing_) height: endpoint_height_ color: endpoint_background.color radius: radius_ @@ -1135,14 +1151,14 @@ Item { target: domainGraphLayout - function onTopics_updated_() + function onGenerate_connections_() { - mainSpace.create_connections() + mainSpace.generate_connections() } } // Saves the topic needed info for connection representation - function create_connections() + function generate_connections() { for (var key in endpoint_topic_connections_) { @@ -1152,8 +1168,8 @@ Item if (!endpoint_painted_.includes(key)) { var input = {"x": endpoint_topic_connections_[key]["x"] - ,"left_direction": endpoint_topic_connections_[key]["left_direction"] ,"y": endpoint_topic_connections_[key]["y"] - (connection_thickness_ / 2) + ,"left_direction": endpoint_topic_connections_[key]["left_direction"] ,"width": 5*elements_spacing_ ,"height":connection_thickness_, "z":200 ,"arrow_color": topic_color_, "background_color": background_color.color } @@ -1181,8 +1197,8 @@ Item height: label_height_ anchors.top: parent.top; anchors.topMargin: elements_spacing_ anchors.left: parent.left - anchors.leftMargin: max_host_width_/2 + elements_spacing_ - refresh_button.width /2 < 40 - ? 5* elements_spacing_ : (max_host_width_/2) + elements_spacing_ - (refresh_button.width /2) + anchors.leftMargin: entity_box_width_/2 + elements_spacing_ - refresh_button.width /2 < 40 + ? 5* elements_spacing_ : (entity_box_width_/2) + elements_spacing_ - (refresh_button.width /2) text: "Refresh" onClicked:{ @@ -1196,7 +1212,7 @@ Item anchors.bottom: parent.bottom anchors.left: parent.left height: elements_spacing_ - width: max_host_width_ + 2*elements_spacing_ + width: entity_box_width_ + 2*elements_spacing_ color: "white" z: 14 } @@ -1391,12 +1407,7 @@ Item topic_painted_ = [] vertical_bar.position = 0 horizontal_bar.position = 0 - max_host_width_ = 0; - max_user_width_ = 0; - max_process_width_ = 0; - max_participant_width_ = 0; - max_endpoint_width_ = 0; - + entity_box_width_ = 0; for (var i = 0; i < mainSpace.children.length; i++) { if (mainSpace.children[i].left_margin != undefined) From 0db8f67c1c74abe1391b3ad08fa1958d18a41c85 Mon Sep 17 00:00:00 2001 From: JesusPoderoso Date: Thu, 26 Oct 2023 15:29:59 +0200 Subject: [PATCH 29/36] Refs #19743: [ARS] Improve comparasion conditions Signed-off-by: JesusPoderoso --- qml/DomainGraphLayout.qml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/qml/DomainGraphLayout.qml b/qml/DomainGraphLayout.qml index 3e72efcc..d260eca8 100644 --- a/qml/DomainGraphLayout.qml +++ b/qml/DomainGraphLayout.qml @@ -315,7 +315,7 @@ Item } } else { topicView.contentX += wheel_displacement_ - if (topicView.contentX + topicView.width > topicView.contentWidth) { + if ((topicView.contentX + topicView.width) > topicView.contentWidth) { topicView.contentX = topicView.contentWidth - topicView.width; } } @@ -423,7 +423,7 @@ Item anchors.top: parent.top width: hostsList.width + 2*elements_spacing_ - height: hostsList.height < domainGraphLayout.height - (label_height_ + 2*elements_spacing_) + height: hostsList.height < (domainGraphLayout.height - (label_height_ + 2*elements_spacing_)) ? domainGraphLayout.height - (label_height_ + 2*elements_spacing_) : hostsList.height // Entities background @@ -1193,11 +1193,11 @@ Item // Refresh button Button{ id: refresh_button - width: parent.width /2 < 150 ? 150 : parent.width /2 + width: (parent.width /2) < 150 ? 150 : parent.width /2 height: label_height_ anchors.top: parent.top; anchors.topMargin: elements_spacing_ anchors.left: parent.left - anchors.leftMargin: entity_box_width_/2 + elements_spacing_ - refresh_button.width /2 < 40 + anchors.leftMargin: ((entity_box_width_/2) + elements_spacing_ - (refresh_button.width /2)) < 40 ? 5* elements_spacing_ : (entity_box_width_/2) + elements_spacing_ - (refresh_button.width /2) text: "Refresh" From 22460161403cc5b44e38942420c2447de6eb2cac Mon Sep 17 00:00:00 2001 From: JesusPoderoso Date: Mon, 30 Oct 2023 09:00:22 +0100 Subject: [PATCH 30/36] Refs #19743: [ARS] Run resize call only once Signed-off-by: JesusPoderoso --- qml/DomainGraphLayout.qml | 45 +++++---------------------------------- 1 file changed, 5 insertions(+), 40 deletions(-) diff --git a/qml/DomainGraphLayout.qml b/qml/DomainGraphLayout.qml index d260eca8..58819cfe 100644 --- a/qml/DomainGraphLayout.qml +++ b/qml/DomainGraphLayout.qml @@ -41,18 +41,11 @@ Item property var endpoint_painted_: [] // already painted endpoint connection references property int entity_box_width_: 0 // entities box width management - // Private (resize) signals resize_elements_ will trigger a bunch of resize methods per entities and - // HOST TOPIC ─┐ topics, when 2 iterations are performed. The first one is aimed to - // 3↓ ... ↑2 1│ ↑ 5└─>CONNECTIONS resize "parents" based on max "child" size, and then the second one takes - // ENDPOINT <───┘ │ place to ensure the well format if a "parent" has a size longer than a - // 4└─────────────┘ "child". After that, with the final results, connections between topics - // and endpoints are generated + // Private (resize) signals The signal resize_elements_ will trigger all entities resize methods in + // HOST TOPIC ─┐ the order displayed in the left figure. All entities width value are + // 1↓ ... ↓4 5↑ 6└─>CONNECTIONS based on the var entity_box_width_wich would be updated with the max + // ENDPOINT ────┘ width. After that, connections between endpoints and topics are generated. signal resize_elements_() - signal update_endpoints_() - signal update_participants_() - signal update_processes_() - signal update_users_() - signal update_hosts_() signal topics_updated_() signal endpoints_updated_() signal participants_updated_() @@ -153,12 +146,6 @@ Item { target: domainGraphLayout - function onResize_elements_() - { - topicsList.resize() - update_endpoints_() - } - function onEndpoints_updated_() { topicsList.create_connections() @@ -456,7 +443,7 @@ Item { target: domainGraphLayout - function onUpdate_hosts_() + function onResize_elements_() { hostsList.resize() hosts_updated_() @@ -584,11 +571,6 @@ Item { target: domainGraphLayout - function onUpdate_users_() - { - usersList.resize() - update_hosts_() - } function onHosts_updated_() { usersList.resize() @@ -719,12 +701,6 @@ Item { target: domainGraphLayout - function onUpdate_processes_() - { - processesList.resize() - update_users_() - } - function onUsers_updated_() { processesList.resize() @@ -855,11 +831,6 @@ Item { target: domainGraphLayout - function onUpdate_participants_() - { - participantsList.resize() - update_processes_() - } function onProcesses_updated_() { participantsList.resize() @@ -987,12 +958,6 @@ Item { target: domainGraphLayout - function onUpdate_endpoints_() - { - endpointsList.resize() - update_participants_() - } - function onParticipants_updated_() { endpointsList.resize() From ac6ffa81ae5f1b03dc5dc9bc8faaf9dd5128ba9a Mon Sep 17 00:00:00 2001 From: JesusPoderoso Date: Mon, 30 Oct 2023 10:29:40 +0100 Subject: [PATCH 31/36] Refs #19743: [ARS] Improve connection generation Signed-off-by: JesusPoderoso --- qml/DomainGraphLayout.qml | 46 +++++++++++++++++++++++++++++---------- 1 file changed, 35 insertions(+), 11 deletions(-) diff --git a/qml/DomainGraphLayout.qml b/qml/DomainGraphLayout.qml index 58819cfe..6050b2cf 100644 --- a/qml/DomainGraphLayout.qml +++ b/qml/DomainGraphLayout.qml @@ -39,6 +39,7 @@ Item property var endpoint_topic_connections_: {} // endpoint information needed for connection representation property var topic_painted_: [] // already painted topic connection references property var endpoint_painted_: [] // already painted endpoint connection references + property var pending_endpoints_: [] // pending endpoints references that have not been resized yet property int entity_box_width_: 0 // entities box width management // Private (resize) signals The signal resize_elements_ will trigger all entities resize methods in @@ -478,9 +479,7 @@ Item // update if necessary if (aux_width > entity_box_width_) { - clear_graph() entity_box_width_ = aux_width - resize_elements_() } } @@ -607,9 +606,7 @@ Item // update if necessary if (aux_width > entity_box_width_) { - clear_graph() entity_box_width_ = aux_width - resize_elements_() } } @@ -737,9 +734,7 @@ Item // update if necessary if (aux_width > entity_box_width_) { - clear_graph() entity_box_width_ = aux_width - resize_elements_() } } @@ -867,9 +862,7 @@ Item // update if necessary if (aux_width > entity_box_width_) { - clear_graph() entity_box_width_ = aux_width - resize_elements_() } } @@ -961,12 +954,29 @@ Item function onParticipants_updated_() { endpointsList.resize() + + // remove current endpoints from pending queue + for (var c = 0; c < endpointsList.count; c++) + { + endpointsList.currentIndex = c + if (endpointsList.currentItem != null) + { + if (pending_endpoints_.includes(endpointsList.currentItem.get_endpoint_id())) + { + pending_endpoints_.splice(pending_endpoints_.indexOf(endpointsList.currentItem.get_endpoint_id()), 1) + } + } + } endpoints_updated_() } function onTopics_updated_() { - endpointsList.record_connections() + if (pending_endpoints_.length == 0) + { + stop_timer() + endpointsList.record_connections() + } } } @@ -999,9 +1009,7 @@ Item // update if necessary if (aux_width > entity_box_width_) { - clear_graph() entity_box_width_ = aux_width - resize_elements_() } } @@ -1040,6 +1048,11 @@ Item } } + function get_endpoint_id() + { + return modelData["id"] + } + // background Rectangle { @@ -1205,6 +1218,12 @@ Item } } + Timer { + id: safety_timer + interval: 200; running: false + onTriggered: { if (interval < 500) interval = 500; load_model() } + } function stop_timer() { safety_timer.stop() } + // Obtain given domain id graph JSON model function load_model() { @@ -1285,6 +1304,7 @@ Item "accum_y":accum_y } accum_y += endpoint_height_ + elements_spacing_ + pending_endpoints_[pending_endpoints_.length] = endpoint } } new_participants[new_participants.length] = { @@ -1337,6 +1357,9 @@ Item "hosts": new_hosts, } + // recovery timer starts + safety_timer.start() + // Update visual elements by re-calculating their sizes resize_elements_() @@ -1370,6 +1393,7 @@ Item endpoint_topic_connections_ = {} endpoint_painted_ = [] topic_painted_ = [] + pending_endpoints_ = [] vertical_bar.position = 0 horizontal_bar.position = 0 entity_box_width_ = 0; From 4a5a467dbf480ba45bad9d29169737066bdc7600 Mon Sep 17 00:00:00 2001 From: JesusPoderoso Date: Mon, 6 Nov 2023 11:47:44 +0100 Subject: [PATCH 32/36] Refs #19743: Remove unnecessary load model call Signed-off-by: JesusPoderoso --- qml/DomainGraphLayout.qml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/qml/DomainGraphLayout.qml b/qml/DomainGraphLayout.qml index 6050b2cf..6cb45747 100644 --- a/qml/DomainGraphLayout.qml +++ b/qml/DomainGraphLayout.qml @@ -77,12 +77,6 @@ Item readonly property string reader_color_: Theme.eProsimaYellow readonly property string writer_color_: Theme.eProsimaGreen - // Obtain given domain id graph - Component.onCompleted: - { - load_model() - } - // Horizontal scroll view for topics section. This will contain also a Flickable that replicates entities height // and will move accordingly to display the connections Flickable { From 9069e49aeb156a8e00ed6fc46d8d82da74bfb1dd Mon Sep 17 00:00:00 2001 From: JesusPoderoso Date: Tue, 7 Nov 2023 10:13:34 +0100 Subject: [PATCH 33/36] Refs #19743: Reduce amount of painting iterations Signed-off-by: JesusPoderoso --- qml/DomainGraphLayout.qml | 193 ++++++++++++++++++++++++-------------- 1 file changed, 125 insertions(+), 68 deletions(-) diff --git a/qml/DomainGraphLayout.qml b/qml/DomainGraphLayout.qml index 6cb45747..0acd213c 100644 --- a/qml/DomainGraphLayout.qml +++ b/qml/DomainGraphLayout.qml @@ -40,6 +40,7 @@ Item property var topic_painted_: [] // already painted topic connection references property var endpoint_painted_: [] // already painted endpoint connection references property var pending_endpoints_: [] // pending endpoints references that have not been resized yet + property var pending_connections_: [] // pending connections references that have not been generated yet property int entity_box_width_: 0 // entities box width management // Private (resize) signals The signal resize_elements_ will trigger all entities resize methods in @@ -53,6 +54,7 @@ Item signal processes_updated_() signal users_updated_() signal hosts_updated_() + signal create_endpoint_connections_(); signal generate_connections_() // Read only design properties (sizes and colors) @@ -441,7 +443,7 @@ Item function onResize_elements_() { hostsList.resize() - hosts_updated_() + hostsList.resize_elements() } } @@ -477,6 +479,20 @@ Item } } + // Makes each list element to be resized + function resize_elements() + { + // iterate over each element in the list item + for (var c = 0; c < hostsList.count; c++) + { + hostsList.currentIndex = c + if (hostsList.currentItem != null) + { + hostsList.currentItem.resize() + } + } + } + // Host delegated item box delegate: Item { @@ -485,6 +501,12 @@ Item ? hostRowLayout.implicitWidth : entity_box_width_ + function resize() + { + usersList.resize() + usersList.resize_elements() + } + // background Rectangle { @@ -559,18 +581,6 @@ Item interactive: false spacing: elements_spacing_ - // Resizing management connections - Connections - { - target: domainGraphLayout - - function onHosts_updated_() - { - usersList.resize() - users_updated_() - } - } - // Resize performed also when new element included in the model onCountChanged: { @@ -604,6 +614,21 @@ Item } } + // Makes each list element to be resized + function resize_elements() + { + // iterate over each element in the list item + for (var c = 0; c < usersList.count; c++) + { + usersList.currentIndex = c + if (usersList.currentItem != null) + { + usersList.currentItem.resize() + } + } + } + + // User delegated item box delegate: Item { @@ -612,6 +637,12 @@ Item ? userRowLayout.implicitWidth : entity_box_width_-(2*elements_spacing_) + function resize() + { + processesList.resize() + processesList.resize_elements() + } + // background Rectangle { @@ -687,19 +718,7 @@ Item interactive: false spacing: elements_spacing_ - // Resizing management connections - Connections - { - target: domainGraphLayout - - function onUsers_updated_() - { - processesList.resize() - processes_updated_() - } - } - - // Resize performed also when new element included in the model + // Resize performed also when new element included in the model onCountChanged: { processesList.resize() @@ -732,6 +751,20 @@ Item } } + // Makes each list element to be resized + function resize_elements() + { + // iterate over each element in the list item + for (var c = 0; c < processesList.count; c++) + { + processesList.currentIndex = c + if (processesList.currentItem != null) + { + processesList.currentItem.resize() + } + } + } + // Process delegated item box delegate: Item { @@ -740,6 +773,12 @@ Item ? processRowLayout.implicitWidth : entity_box_width_-(4*elements_spacing_) + function resize() + { + participantsList.resize() + participantsList.resize_elements() + } + // background Rectangle { @@ -815,18 +854,6 @@ Item interactive: false spacing: elements_spacing_ - // Resizing management connections - Connections - { - target: domainGraphLayout - - function onProcesses_updated_() - { - participantsList.resize() - participants_updated_() - } - } - // Resize performed also when new element included in the model onCountChanged: { @@ -860,6 +887,20 @@ Item } } + // Makes each list element to be resized + function resize_elements() + { + // iterate over each element in the list item + for (var c = 0; c < participantsList.count; c++) + { + participantsList.currentIndex = c + if (participantsList.currentItem != null) + { + participantsList.currentItem.resize() + } + } + } + // Participant delegated item box delegate: Item { @@ -868,6 +909,12 @@ Item ? participantRowLayout.implicitWidth : entity_box_width_-(6*elements_spacing_) + function resize() + { + endpointsList.resize() + endpointsList.resize_elements() + } + // background Rectangle { @@ -940,37 +987,14 @@ Item interactive: false spacing: elements_spacing_ - // Resizing management connections + // Connection management Connections { target: domainGraphLayout - function onParticipants_updated_() + function onCreate_endpoint_connections_() { - endpointsList.resize() - - // remove current endpoints from pending queue - for (var c = 0; c < endpointsList.count; c++) - { - endpointsList.currentIndex = c - if (endpointsList.currentItem != null) - { - if (pending_endpoints_.includes(endpointsList.currentItem.get_endpoint_id())) - { - pending_endpoints_.splice(pending_endpoints_.indexOf(endpointsList.currentItem.get_endpoint_id()), 1) - } - } - } - endpoints_updated_() - } - - function onTopics_updated_() - { - if (pending_endpoints_.length == 0) - { - stop_timer() - endpointsList.record_connections() - } + endpointsList.record_connections() } } @@ -1007,14 +1031,46 @@ Item } } + // Makes each list element to be resized + function resize_elements() + { + // remove current endpoints from pending queue + for (var c = 0; c < endpointsList.count; c++) + { + endpointsList.currentIndex = c + if (endpointsList.currentItem != null) + { + if (pending_endpoints_.includes(endpointsList.currentItem.get_endpoint_id())) + { + pending_connections_[pending_connections_.length] = endpointsList.currentItem.get_endpoint_id() + pending_endpoints_.splice(pending_endpoints_.indexOf(endpointsList.currentItem.get_endpoint_id()), 1) + } + } + } + + if (pending_endpoints_.length == 0) + { + domainGraphLayout.create_endpoint_connections_() + } + } + function record_connections() { for (var c = 0; c < endpointsList.count; c++) { endpointsList.currentIndex = c - endpointsList.currentItem.record_connection() + if (pending_connections_.includes(endpointsList.currentItem.get_endpoint_id())) + { + pending_connections_.splice(pending_connections_.indexOf(endpointsList.currentItem.get_endpoint_id()), 1) + endpointsList.currentItem.record_connection() + } + } + + if (pending_connections_.length == 0) + { + stop_timer() + endpoints_updated_() } - generate_connections_() } // Endpoint delegated item box @@ -1123,7 +1179,7 @@ Item { target: domainGraphLayout - function onGenerate_connections_() + function onTopics_updated_() { mainSpace.generate_connections() } @@ -1215,7 +1271,7 @@ Item Timer { id: safety_timer interval: 200; running: false - onTriggered: { if (interval < 500) interval = 500; load_model() } + onTriggered: { interval += interval; load_model() } } function stop_timer() { safety_timer.stop() } // Obtain given domain id graph JSON model @@ -1388,6 +1444,7 @@ Item endpoint_painted_ = [] topic_painted_ = [] pending_endpoints_ = [] + pending_connections_ = [] vertical_bar.position = 0 horizontal_bar.position = 0 entity_box_width_ = 0; From 8c82a9e1612f01b985af794f5dcad224ec915c92 Mon Sep 17 00:00:00 2001 From: JesusPoderoso Date: Tue, 7 Nov 2023 10:16:19 +0100 Subject: [PATCH 34/36] Refs #19743: Fix typo Signed-off-by: JesusPoderoso --- qml/DomainGraphLayout.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qml/DomainGraphLayout.qml b/qml/DomainGraphLayout.qml index 0acd213c..bf100292 100644 --- a/qml/DomainGraphLayout.qml +++ b/qml/DomainGraphLayout.qml @@ -45,7 +45,7 @@ Item // Private (resize) signals The signal resize_elements_ will trigger all entities resize methods in // HOST TOPIC ─┐ the order displayed in the left figure. All entities width value are - // 1↓ ... ↓4 5↑ 6└─>CONNECTIONS based on the var entity_box_width_wich would be updated with the max + // 1↓ ... ↓4 5↑ 6└─>CONNECTIONS based on the var entity_box_width which would be updated with the max // ENDPOINT ────┘ width. After that, connections between endpoints and topics are generated. signal resize_elements_() signal topics_updated_() From dec6ebd72b8059292a3de4c0c4f9aa58ef38531d Mon Sep 17 00:00:00 2001 From: JesusPoderoso Date: Tue, 7 Nov 2023 10:17:58 +0100 Subject: [PATCH 35/36] Refs #19743: Remove unnecessary signal declaration Signed-off-by: JesusPoderoso --- qml/DomainGraphLayout.qml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/qml/DomainGraphLayout.qml b/qml/DomainGraphLayout.qml index bf100292..3f483c38 100644 --- a/qml/DomainGraphLayout.qml +++ b/qml/DomainGraphLayout.qml @@ -50,10 +50,6 @@ Item signal resize_elements_() signal topics_updated_() signal endpoints_updated_() - signal participants_updated_() - signal processes_updated_() - signal users_updated_() - signal hosts_updated_() signal create_endpoint_connections_(); signal generate_connections_() From ea488cf61f87f1c7a26925d76d7943047a5eab8e Mon Sep 17 00:00:00 2001 From: JesusPoderoso Date: Tue, 7 Nov 2023 11:34:36 +0100 Subject: [PATCH 36/36] Refs #19743: Apply latest rev suggestions Signed-off-by: JesusPoderoso --- qml/DomainGraphLayout.qml | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/qml/DomainGraphLayout.qml b/qml/DomainGraphLayout.qml index 3f483c38..5aadb1fa 100644 --- a/qml/DomainGraphLayout.qml +++ b/qml/DomainGraphLayout.qml @@ -50,8 +50,7 @@ Item signal resize_elements_() signal topics_updated_() signal endpoints_updated_() - signal create_endpoint_connections_(); - signal generate_connections_() + signal record_connections_() // Read only design properties (sizes and colors) readonly property int radius_: 10 @@ -141,7 +140,7 @@ Item function onEndpoints_updated_() { - topicsList.create_connections() + topicsList.record_connections() } } @@ -171,7 +170,7 @@ Item topicsList.width = listViewWidth } - function create_connections() + function record_connections() { var draw_width = 2*elements_spacing_ @@ -317,12 +316,12 @@ Item function onTopics_updated_() { - topicView.create_connections() + topicView.generate_connections() } } // Generate connections in topic side - function create_connections() + function generate_connections() { for (var key in endpoint_topic_connections_) { @@ -714,7 +713,7 @@ Item interactive: false spacing: elements_spacing_ - // Resize performed also when new element included in the model + // Resize performed also when new element included in the model onCountChanged: { processesList.resize() @@ -988,7 +987,7 @@ Item { target: domainGraphLayout - function onCreate_endpoint_connections_() + function onRecord_connections_() { endpointsList.record_connections() } @@ -1046,7 +1045,7 @@ Item if (pending_endpoints_.length == 0) { - domainGraphLayout.create_endpoint_connections_() + domainGraphLayout.record_connections_() } }