From f54b3c4fc62805480fee9b874e695ef9fdb680e3 Mon Sep 17 00:00:00 2001
From: Meixner <andre.meixner@kit.edu>
Date: Tue, 26 Nov 2024 20:34:00 +0100
Subject: [PATCH] Added first implemntation of ArvizProfile

---
 .../ArViz/ArVizWidgetController.cpp           |  48 +++++-
 .../gui-plugins/ArViz/ArVizWidgetController.h |   6 +
 .../RobotAPI/gui-plugins/ArViz/CMakeLists.txt |   2 +
 .../ArViz/HiddenCoinLayerManagerWidget.cpp    | 153 ++++++++++++++++++
 .../ArViz/HiddenCoinLayerManagerWidget.h      |  92 +++++++++++
 5 files changed, 295 insertions(+), 6 deletions(-)
 create mode 100644 source/RobotAPI/gui-plugins/ArViz/HiddenCoinLayerManagerWidget.cpp
 create mode 100644 source/RobotAPI/gui-plugins/ArViz/HiddenCoinLayerManagerWidget.h

diff --git a/source/RobotAPI/gui-plugins/ArViz/ArVizWidgetController.cpp b/source/RobotAPI/gui-plugins/ArViz/ArVizWidgetController.cpp
index 945013a99..1633da37a 100644
--- a/source/RobotAPI/gui-plugins/ArViz/ArVizWidgetController.cpp
+++ b/source/RobotAPI/gui-plugins/ArViz/ArVizWidgetController.cpp
@@ -30,6 +30,7 @@
 #include <QMessageBox>
 #include <QTimer>
 #include <QFileDialog>
+#include <qnamespace.h>
 
 
 namespace armarx
@@ -92,6 +93,10 @@ namespace armarx
         connect(this, &This::connectGui, this, &This::onConnectGui, Qt::QueuedConnection);
         connect(this, &This::disconnectGui, this, &This::onDisconnectGui, Qt::QueuedConnection);
 
+        hiddenCoinLayerWidget = new HiddenCoinLayerManagerWidget();
+        widget.verticalLayout->addWidget(hiddenCoinLayerWidget);
+        connect(hiddenCoinLayerWidget, &HiddenCoinLayerManagerWidget::hiddenLayersReset, this, &This::updateHiddenLayers);
+
         // Layer info tree.
 
         connect(widget.layerTree, &QTreeWidget::currentItemChanged, this, &This::updateSelectedLayer);
@@ -105,7 +110,6 @@ namespace armarx
         layerInfoTree.setEnabled(widget.layerInfoTreeGroupBox->isChecked());
         layerInfoTree.registerVisualizerCallbacks(visualizer);
 
-
         // We need a callback from the visualizer, when the layers have changed
         // So we can update the tree accordingly
         visualizer.layersChangedCallback = [this](std::vector<viz::CoinLayerID> const & layers)
@@ -167,6 +171,32 @@ namespace armarx
         emit disconnectGui();
     }
 
+    void ArVizWidgetController::updateHiddenLayers()
+    {
+        {
+            QSignalBlocker blocker(widget.layerTree);
+
+            // Iterate over all items and activate/deactivate layers accordingly
+            for (int compIndex = 0; compIndex < widget.layerTree->topLevelItemCount(); ++compIndex)
+            {
+                QTreeWidgetItem* componentItem = widget.layerTree->topLevelItem(compIndex);
+                bool parentChecked = true;
+                const std::string component = componentItem->text(0).toStdString();
+                for (int layerIndex = 0; layerIndex < componentItem->childCount(); ++layerIndex)
+                {
+                    QTreeWidgetItem* layerItem = componentItem->child(layerIndex);
+                    const std::string layer = layerItem->text(0).toStdString();
+                    const viz::CoinLayerID layerID(component, layer);
+                    const bool visible = not hiddenCoinLayerWidget->isHidden(layerID);
+                    layerItem->setCheckState(0, visible ? Qt::CheckState::Checked : Qt::CheckState::Unchecked);
+                    parentChecked &= visible;
+                    visualizer.showLayer(layerID, visible);
+                }
+                componentItem->setCheckState(0, parentChecked ? Qt::CheckState::Checked : Qt::CheckState::Unchecked);
+            }
+        }
+    }
+
     void ArVizWidgetController::onConnectGui()
     {
         onRefreshRecordings();
@@ -205,6 +235,7 @@ namespace armarx
                     {
                         layerItem->setCheckState(0, componentCheck);
                     }
+                    hiddenCoinLayerWidget->update(component, componentCheck == Qt::Checked);
                 }
                 return;
             }
@@ -217,6 +248,9 @@ namespace armarx
                 ARMARX_VERBOSE << "Layer: " << layer << ", Visible: " << layerVisible;
 
                 viz::CoinLayerID layerID(component, layer);
+
+                hiddenCoinLayerWidget->update(layerID, layerVisible);
+
                 visualizer.showLayer(layerID, layerVisible);
             }
         }
@@ -263,8 +297,7 @@ namespace armarx
                     // Create a new item
                     currentItem = new QTreeWidgetItem(tree);
                     currentItem->setText(0, QString::fromStdString(component));
-                    // A new item is visible by default
-                    currentItem->setCheckState(0, Qt::Checked);
+                    currentItem->setCheckState(0, hiddenCoinLayerWidget->isHidden(entry) ? Qt::Unchecked : Qt::Checked);
 
                     componentWasNew = true;
                 }
@@ -286,8 +319,7 @@ namespace armarx
                 std::string const& layer = entry.second;
                 QTreeWidgetItem* layerItem = new QTreeWidgetItem;
                 layerItem->setText(0, QString::fromStdString(layer));
-                // A new item is visible by default
-                layerItem->setCheckState(0, Qt::Checked);
+                layerItem->setCheckState(0, hiddenCoinLayerWidget->isHidden(entry) ? Qt::Unchecked : Qt::Checked);
 
                 if (currentItem)
                 {
@@ -308,7 +340,11 @@ namespace armarx
             }
             else
             {
-                // Item exists already ==> nothing to be done
+                // Item exists already ==> make sure that it is not shown if necessary
+                if (hiddenCoinLayerWidget->isHidden(iter->first) or iter->second->checkState(0) == Qt::Unchecked)
+                {
+                    visualizer.showLayer(iter->first, false);
+                }
             }
         }
     }
diff --git a/source/RobotAPI/gui-plugins/ArViz/ArVizWidgetController.h b/source/RobotAPI/gui-plugins/ArViz/ArVizWidgetController.h
index cb10e285b..d81dc69bc 100644
--- a/source/RobotAPI/gui-plugins/ArViz/ArVizWidgetController.h
+++ b/source/RobotAPI/gui-plugins/ArViz/ArVizWidgetController.h
@@ -21,6 +21,8 @@
  */
 #pragma once
 
+#include <unordered_set>
+
 #include <RobotAPI/gui-plugins/ArViz/ui_ArVizWidget.h>
 
 #include <RobotAPI/components/ArViz/Coin/Visualizer.h>
@@ -34,6 +36,7 @@
 #include <ArmarXCore/core/system/ImportExportComponent.h>
 
 #include "LayerInfoTree.h"
+#include "HiddenCoinLayerManagerWidget.h"
 
 
 namespace armarx
@@ -108,6 +111,7 @@ namespace armarx
 
     public slots:
         /* QT slot declarations */
+        void updateHiddenLayers();
 
     signals:
         /* QT signal declarations */
@@ -224,6 +228,8 @@ namespace armarx
 
         std::vector<double> timings;
 
+        HiddenCoinLayerManagerWidget* hiddenCoinLayerWidget;
+
     public:
         static QIcon GetWidgetIcon()
         {
diff --git a/source/RobotAPI/gui-plugins/ArViz/CMakeLists.txt b/source/RobotAPI/gui-plugins/ArViz/CMakeLists.txt
index c9f30ffe5..318b4d9bd 100644
--- a/source/RobotAPI/gui-plugins/ArViz/CMakeLists.txt
+++ b/source/RobotAPI/gui-plugins/ArViz/CMakeLists.txt
@@ -10,6 +10,7 @@ armarx_build_if(ArmarXGui_FOUND "ArmarXGui not available")
 set(SOURCES
     ArVizGuiPlugin.cpp
     ArVizWidgetController.cpp
+    HiddenCoinLayerManagerWidget.cpp
 
     LayerInfoTree.cpp
 )
@@ -18,6 +19,7 @@ set(SOURCES
 set(HEADERS
     ArVizGuiPlugin.h
     ArVizWidgetController.h
+    HiddenCoinLayerManagerWidget.h
 
     LayerInfoTree.h
 )
diff --git a/source/RobotAPI/gui-plugins/ArViz/HiddenCoinLayerManagerWidget.cpp b/source/RobotAPI/gui-plugins/ArViz/HiddenCoinLayerManagerWidget.cpp
new file mode 100644
index 000000000..95ca8de3d
--- /dev/null
+++ b/source/RobotAPI/gui-plugins/ArViz/HiddenCoinLayerManagerWidget.cpp
@@ -0,0 +1,153 @@
+#include "HiddenCoinLayerManagerWidget.h"
+
+#include <ArmarXCore/core/logging/Logging.h>
+#include <QVBoxLayout>
+#include <QMessageBox>
+
+#include <ArmarXCore/core/system/ArmarXDataPath.h>
+#include <qboxlayout.h>
+#include <qlabel.h>
+
+namespace armarx
+{
+
+HiddenCoinLayerManagerWidget::HiddenCoinLayerManagerWidget(QWidget* parent) :
+    QWidget(parent), 
+    settings(QString((armarx::ArmarXDataPath::GetDefaultUserConfigPath() + "/ArvizHiddenCoinLayerManager.conf").c_str()),
+             QSettings::NativeFormat),
+    hiddenLayers(HiddenLayers{})
+{
+    QHBoxLayout* layout = new QHBoxLayout(this);
+
+    // Setup UI elements
+    hiddenLayersComboBox = new QComboBox(this);
+    hiddenLayersComboBox->addItem("default");
+    connect(hiddenLayersComboBox, QOverload<int>::of(&QComboBox::currentIndexChanged), 
+           this, &HiddenCoinLayerManagerWidget::onHiddenLayersSelected);
+
+    nameEdit = new QLineEdit(this);
+    saveButton = new QPushButton("Save", this);
+    deleteButton = new QPushButton("Delete", this);
+
+    layout->addWidget(new QLabel("Arviz Layer Saves: "));
+    layout->addWidget(hiddenLayersComboBox);
+    layout->addWidget(nameEdit);
+    layout->addWidget(saveButton);
+    layout->addWidget(deleteButton);
+    setLayout(layout);
+
+    connect(saveButton, &QPushButton::clicked, this, &HiddenCoinLayerManagerWidget::saveCurrentHiddenLayers);
+    connect(deleteButton, &QPushButton::clicked, this, &HiddenCoinLayerManagerWidget::deleteCurrentHiddenLayersSave);
+
+    loadHiddenLayerNames();
+    loadDefaultHiddenLayer();
+}
+
+void HiddenCoinLayerManagerWidget::onHiddenLayersSelected(int index) 
+{
+    const QString name = hiddenLayersComboBox->currentText();
+
+    loadHiddenLayers(name);
+    emit hiddenLayersReset();
+}
+
+void HiddenCoinLayerManagerWidget::saveCurrentHiddenLayers() 
+{
+    const QString name = nameEdit->text();
+    if (name.isEmpty() or name == "default") 
+    {
+        QMessageBox::warning(this, "Warning", "Please enter a valid name.");
+        return;
+    }
+
+    if (saveHiddenLayers(name, hiddenLayers)) 
+    {
+        if (hiddenLayersComboBox->findText(name) == -1) 
+        {
+            hiddenLayersComboBox->addItem(name);
+        }
+        hiddenLayersComboBox->setCurrentText(name);
+    }
+}
+
+void HiddenCoinLayerManagerWidget::deleteCurrentHiddenLayersSave() 
+{
+    const QString name = hiddenLayersComboBox->currentText();
+    if (name == "default") 
+    {
+        QMessageBox::warning(this, "Warning", "Cannot delete the default.");
+        return;
+    }
+
+    settings.remove(name);
+
+    int index = hiddenLayersComboBox->currentIndex();
+    hiddenLayersComboBox->removeItem(index);
+    loadDefaultHiddenLayer();
+    emit hiddenLayersReset();
+}
+
+void HiddenCoinLayerManagerWidget::loadHiddenLayerNames() 
+{
+    for (const QString& groups : settings.childGroups()) 
+    {
+        hiddenLayersComboBox->addItem(groups);
+    }
+}
+
+void HiddenCoinLayerManagerWidget::loadDefaultHiddenLayer() 
+{
+    hiddenLayersComboBox->setCurrentText("default");
+    hiddenLayers.clear();
+}
+
+void HiddenCoinLayerManagerWidget::loadHiddenLayers(const QString& name)
+{
+    std::scoped_lock lock(mtx);
+    hiddenLayers.clear();
+    
+    if (name != "default")
+    {
+        settings.beginGroup(name);
+        const int size = settings.beginReadArray("layers");
+        for (int i = 0; i < size; ++i) 
+        {
+            settings.setArrayIndex(i);
+            QString first = settings.value("component").toString();
+            QString second = settings.value("layer").toString();
+            hiddenLayers.insert({first.toStdString(), second.toStdString()});
+        }
+        settings.endArray();
+        settings.endGroup();
+
+        nameEdit->setText(name);
+        deleteButton->setEnabled(true);
+    }
+    else 
+    {
+        deleteButton->setEnabled(false);
+    }
+}
+
+bool HiddenCoinLayerManagerWidget::saveHiddenLayers(const QString& name, const HiddenLayers& hiddenLayers) 
+{
+    if (name != "default")
+    {
+        settings.beginGroup(name);
+        settings.remove("layers");
+        settings.beginWriteArray("layers", hiddenLayers.size());
+        int i = 0;
+        for (const auto& pair : hiddenLayers)
+        {
+            settings.setArrayIndex(i++);
+            settings.setValue("component", QString::fromStdString(pair.first));
+            settings.setValue("layer", QString::fromStdString(pair.second));
+        }
+        settings.endArray();
+        settings.endGroup();
+        return true;
+    }
+    return false;
+}
+
+}
\ No newline at end of file
diff --git a/source/RobotAPI/gui-plugins/ArViz/HiddenCoinLayerManagerWidget.h b/source/RobotAPI/gui-plugins/ArViz/HiddenCoinLayerManagerWidget.h
new file mode 100644
index 000000000..f10140d09
--- /dev/null
+++ b/source/RobotAPI/gui-plugins/ArViz/HiddenCoinLayerManagerWidget.h
@@ -0,0 +1,92 @@
+#pragma once
+
+#include <QWidget>
+#include <QComboBox>
+#include <QPushButton>
+#include <QLineEdit>
+#include <QSettings>
+#include <QString>
+
+#include <mutex>
+#include <qobjectdefs.h>
+#include <shared_mutex>
+#include <unordered_set>
+#include <string>
+#include <utility>
+
+#include <RobotAPI/components/ArViz/Coin/Visualizer.h>
+
+namespace armarx
+{
+    struct pair_hash 
+    {
+        template <typename T1, typename T2>
+        std::size_t operator ()(const std::pair<T1, T2>& p) const 
+        {
+            auto h1 = std::hash<T1>{}(p.first);
+            auto h2 = std::hash<T2>{}(p.second);
+            return h1 ^ (h2 << 1);
+        }
+    };
+
+    using HiddenLayers = std::unordered_set<viz::CoinLayerID, pair_hash>;
+
+    class HiddenCoinLayerManagerWidget : public QWidget 
+    {
+        Q_OBJECT
+
+    public:
+        explicit HiddenCoinLayerManagerWidget(QWidget* parent = nullptr);
+
+        inline void update(const viz::CoinLayerID& layerID, bool visible)
+        {
+            std::scoped_lock lock(mtx);
+
+            if (visible and hiddenLayers.count(layerID) > 0)
+            {
+                hiddenLayers.erase(layerID);
+                hiddenLayers.erase({layerID.first, "*"});
+            }
+            else if (not visible)
+            {
+                hiddenLayers.insert(layerID);
+            }
+        }
+
+        inline void update(const std::string& componentName, bool visible)
+        {
+            update({componentName, "*"}, visible);
+        }
+
+        inline bool isHidden(const viz::CoinLayerID& layerID) const
+        {
+            std::scoped_lock lock(mtx);
+
+            return hiddenLayers.count(layerID) > 0 or hiddenLayers.count({layerID.first, "*"}) > 0;
+        }
+
+    signals:
+        // Signal emitted when the saveHiddenLayer is reset (reselected)
+        void hiddenLayersReset();
+
+    private slots:
+        void onHiddenLayersSelected(int index);
+        void saveCurrentHiddenLayers();
+        void deleteCurrentHiddenLayersSave();
+
+    private:
+        void loadHiddenLayerNames();
+        void loadDefaultHiddenLayer();
+        void loadHiddenLayers(const QString& name);
+        bool saveHiddenLayers(const QString& name, const HiddenLayers& hiddenLayers);
+
+        QComboBox* hiddenLayersComboBox;
+        QLineEdit* nameEdit;
+        QPushButton* saveButton;
+        QPushButton* deleteButton;
+        QSettings settings;
+        mutable std::mutex mtx;
+        HiddenLayers hiddenLayers;
+    };
+
+}
\ No newline at end of file
-- 
GitLab