From fc29bde83854311eaef276adb04ffa753aa31ee3 Mon Sep 17 00:00:00 2001
From: Fabian Paus <fabian.paus@kit.edu>
Date: Mon, 14 Oct 2019 14:14:52 +0200
Subject: [PATCH] Implement tree view to select layers to show

---
 .../components/ArViz/ArVizStorage.cpp         |  4 +-
 .../components/ArViz/Coin/Visualizer.cpp      | 58 ++++++++++++-
 .../components/ArViz/Coin/Visualizer.h        | 38 ++++++++
 .../RobotAPI/gui-plugins/ArViz/ArVizWidget.ui | 22 ++++-
 .../ArViz/ArVizWidgetController.cpp           | 86 +++++++++++++++++++
 .../gui-plugins/ArViz/ArVizWidgetController.h |  4 +
 6 files changed, 205 insertions(+), 7 deletions(-)

diff --git a/source/RobotAPI/components/ArViz/ArVizStorage.cpp b/source/RobotAPI/components/ArViz/ArVizStorage.cpp
index f52f5cfc5..610ed669a 100644
--- a/source/RobotAPI/components/ArViz/ArVizStorage.cpp
+++ b/source/RobotAPI/components/ArViz/ArVizStorage.cpp
@@ -179,8 +179,8 @@ void ArVizStorage::compressHistory()
         updateHistory.push_back(std::move(layer));
     }
 
-    ARMARX_INFO << "Compressed history from " << currentHistorySize
-                << " to " << newHistorySize;
+    ARMARX_VERBOSE << "Compressed history from " << currentHistorySize
+                   << " to " << newHistorySize;
 
     long updateCounterAfterCompression = updateCounterBase + newHistorySize;
 
diff --git a/source/RobotAPI/components/ArViz/Coin/Visualizer.cpp b/source/RobotAPI/components/ArViz/Coin/Visualizer.cpp
index 834e43bd8..25c01e3c0 100644
--- a/source/RobotAPI/components/ArViz/Coin/Visualizer.cpp
+++ b/source/RobotAPI/components/ArViz/Coin/Visualizer.cpp
@@ -101,9 +101,7 @@ namespace armarx
                 coinNode->ref();
                 root->addChild(coinNode);
 
-                CoinLayer newLayer;
-                newLayer.node = coinNode;
-                layerIt = layers.emplace(layerID, std::move(newLayer)).first;
+                layerIt = layers.emplace(layerID, CoinLayer(coinNode)).first;
             }
 
             CoinLayer& layer = layerIt->second;
@@ -232,12 +230,20 @@ namespace armarx
                     // We never block the GUI thread with Ice calls: Only get result when call is completed
                     if (pullUpdateResult->isCompleted())
                     {
+                        auto layerIDsBefore = getLayerIDs();
+
                         LayerUpdates pulledUpdates = storage->end_pullUpdatesSince(pullUpdateResult);
                         for (LayerUpdate const& update : pulledUpdates.updates)
                         {
                             apply(update);
                         }
                         updateCounter = pulledUpdates.updateCounter;
+
+                        auto layerIDsAfter = getLayerIDs();
+                        if (layerIDsAfter != layerIDsBefore)
+                        {
+                            emitLayersChanged(layerIDsAfter);
+                        }
                     }
                     else
                     {
@@ -258,5 +264,51 @@ namespace armarx
             }
         }
 
+        void CoinVisualizer::showLayer(CoinLayerID const& id, bool visible)
+        {
+            auto iter = layers.find(id);
+            if (iter == layers.end())
+            {
+                return;
+            }
+
+            viz::CoinLayer& layer = iter->second;
+            int childIndex = root->findChild(layer.node);
+            if (childIndex < 0)
+            {
+                // Layer is currently not visible
+                if (visible)
+                {
+                    root->addChild(layer.node);
+                }
+            }
+            else
+            {
+                // Layer is currently visible
+                if (!visible)
+                {
+                    root->removeChild(childIndex);
+                }
+            }
+        }
+
+        std::vector<CoinLayerID> CoinVisualizer::getLayerIDs()
+        {
+            std::vector<CoinLayerID> result;
+            for (auto& entry : layers)
+            {
+                result.push_back(entry.first);
+            }
+            return result;
+        }
+
+        void CoinVisualizer::emitLayersChanged(std::vector<CoinLayerID> const& layerIDs)
+        {
+            if (layersChangedCallback)
+            {
+                layersChangedCallback(layerIDs);
+            }
+        }
+
     }
 }
diff --git a/source/RobotAPI/components/ArViz/Coin/Visualizer.h b/source/RobotAPI/components/ArViz/Coin/Visualizer.h
index 25906d711..24aa055ce 100644
--- a/source/RobotAPI/components/ArViz/Coin/Visualizer.h
+++ b/source/RobotAPI/components/ArViz/Coin/Visualizer.h
@@ -18,6 +18,37 @@ namespace armarx::viz
 
     struct CoinLayer
     {
+        CoinLayer(SoSeparator* node)
+            : node(node)
+        {
+            // Increase ref-count, so we can add/remove this node without it being deleted
+            // This is needed for showing/hiding layers
+            node->ref();
+        }
+
+        CoinLayer(CoinLayer&& other)
+            : node(other.node)
+        {
+            other.node = nullptr;
+        }
+
+        ~CoinLayer()
+        {
+            if (node)
+            {
+                node->unref();
+            }
+        }
+
+        CoinLayer(CoinLayer const&) = delete;
+        void operator= (CoinLayer const&) = delete;
+
+        void operator= (CoinLayer&& other)
+        {
+            node = other.node;
+            other.node = nullptr;
+        }
+
         SoSeparator* node = nullptr;
         std::map<std::string, std::unique_ptr<coin::ElementVisualization>> elements;
     };
@@ -56,6 +87,11 @@ namespace armarx::viz
             elementVisualizers.emplace_back(typeid(ElementT), std::make_unique<VisualizerT>());
         }
 
+        void showLayer(CoinLayerID const& id, bool visible);
+
+        std::vector<CoinLayerID> getLayerIDs();
+        void emitLayersChanged(std::vector<CoinLayerID> const& layerIDs);
+
         std::mutex storageMutex;
         viz::StorageInterfacePrx storage;
         Ice::AsyncResultPtr pullUpdateResult;
@@ -72,5 +108,7 @@ namespace armarx::viz
         std::atomic<CoinVisualizerState> state{CoinVisualizerState::STOPPED};
         viz::StorageInterfacePrx stateStorage;
 
+        std::function<void(std::vector<CoinLayerID> const&)> layersChangedCallback;
+
     };
 }
diff --git a/source/RobotAPI/gui-plugins/ArViz/ArVizWidget.ui b/source/RobotAPI/gui-plugins/ArViz/ArVizWidget.ui
index 855fb9993..75deb8dc0 100644
--- a/source/RobotAPI/gui-plugins/ArViz/ArVizWidget.ui
+++ b/source/RobotAPI/gui-plugins/ArViz/ArVizWidget.ui
@@ -6,13 +6,31 @@
    <rect>
     <x>0</x>
     <y>0</y>
-    <width>400</width>
-    <height>300</height>
+    <width>576</width>
+    <height>400</height>
    </rect>
   </property>
   <property name="windowTitle">
    <string>ArVizWidget</string>
   </property>
+  <layout class="QHBoxLayout" name="horizontalLayout">
+   <item>
+    <widget class="QTreeWidget" name="layerTree">
+     <column>
+      <property name="text">
+       <string>Layer</string>
+      </property>
+     </column>
+    </widget>
+   </item>
+   <item>
+    <widget class="QGroupBox" name="groupBox">
+     <property name="title">
+      <string>Layer Information</string>
+     </property>
+    </widget>
+   </item>
+  </layout>
  </widget>
  <resources/>
  <connections/>
diff --git a/source/RobotAPI/gui-plugins/ArViz/ArVizWidgetController.cpp b/source/RobotAPI/gui-plugins/ArViz/ArVizWidgetController.cpp
index 78611085d..a8716ed3f 100644
--- a/source/RobotAPI/gui-plugins/ArViz/ArVizWidgetController.cpp
+++ b/source/RobotAPI/gui-plugins/ArViz/ArVizWidgetController.cpp
@@ -31,6 +31,15 @@ using namespace armarx;
 ArVizWidgetController::ArVizWidgetController()
 {
     widget.setupUi(getWidget());
+
+    connect(widget.layerTree, &QTreeWidget::itemChanged, this, &ArVizWidgetController::layerTreeChanged);
+
+    // 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)
+    {
+        layersChanged(layers);
+    };
 }
 
 ArVizWidgetController::~ArVizWidgetController()
@@ -44,6 +53,7 @@ void ArVizWidgetController::onInitComponent()
     {
         usingProxy(storageName);
     }
+    ARMARX_IMPORTANT << "OnInit: " << storageName;
 }
 
 void armarx::ArVizWidgetController::onExitComponent()
@@ -54,11 +64,87 @@ void ArVizWidgetController::onConnectComponent()
 {
     getProxy(storage, storageName);
     visualizer.startAsync(storage);
+
+    ARMARX_IMPORTANT << "OnConnect: " << storageName;
 }
 
 void armarx::ArVizWidgetController::onDisconnectComponent()
 {
     visualizer.stop();
+
+    ARMARX_IMPORTANT << "OnDisconnect: " << storageName;
+}
+
+void ArVizWidgetController::layerTreeChanged(QTreeWidgetItem* item, int /*column*/)
+{
+    // Iterate over all items and activate/deactivate layers accordingly
+    for (QTreeWidgetItemIterator compIter(widget.layerTree); *compIter; ++compIter)
+    {
+        QTreeWidgetItem* componentItem = *compIter;
+        std::string component = componentItem->text(0).toStdString();
+        Qt::CheckState componentCheck = componentItem->checkState(0);
+
+        if (componentItem == item)
+        {
+            // The parent was selected or deselected, so all children should be set accordingly
+            for (QTreeWidgetItemIterator layerIter(componentItem); *layerIter; ++layerIter)
+            {
+                QTreeWidgetItem* layerItem = *layerIter;
+                if (layerItem->checkState(0) != componentCheck)
+                {
+                    layerItem->setCheckState(0, componentCheck);
+                }
+            }
+            return;
+        }
+
+        for (QTreeWidgetItemIterator layerIter(componentItem); *layerIter; ++layerIter)
+        {
+            QTreeWidgetItem* layerItem = *layerIter;
+            std::string layer = layerItem->text(0).toStdString();
+            bool layerVisible = (layerItem->checkState(0) == Qt::Checked);
+
+            bool visible = layerVisible;
+
+            viz::CoinLayerID layerID(component, layer);
+            visualizer.showLayer(layerID, visible);
+        }
+    }
+}
+
+void ArVizWidgetController::layersChanged(std::vector<viz::CoinLayerID> const& layers)
+{
+    QTreeWidget* tree = widget.layerTree;
+    QTreeWidgetItem* currentItem = nullptr;
+
+    // TODO: We need to keep the state of the old tree items
+    // HACK: Just remove all the children before adding them again
+    tree->clear();
+
+    std::string currentComponent;
+    for (auto& entry : layers)
+    {
+        std::string const& component = entry.first;
+        if (component != currentComponent)
+        {
+            // 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);
+
+            currentComponent = component;
+        }
+
+        std::string const& layer = entry.second;
+        QTreeWidgetItem* layerItem = new QTreeWidgetItem;
+        layerItem->setText(0, QString::fromStdString(layer));
+        // A new item is visible by default
+        // TODO: Take into account the old state!
+        layerItem->setCheckState(0, Qt::Checked);
+
+        currentItem->addChild(layerItem);
+    }
 }
 
 SoNode* ArVizWidgetController::getScene()
diff --git a/source/RobotAPI/gui-plugins/ArViz/ArVizWidgetController.h b/source/RobotAPI/gui-plugins/ArViz/ArVizWidgetController.h
index a596a42da..b1d4e3c3a 100644
--- a/source/RobotAPI/gui-plugins/ArViz/ArVizWidgetController.h
+++ b/source/RobotAPI/gui-plugins/ArViz/ArVizWidgetController.h
@@ -100,10 +100,14 @@ namespace armarx
 
     public slots:
         /* QT slot declarations */
+        void layerTreeChanged(QTreeWidgetItem* item, int column);
 
     signals:
         /* QT signal declarations */
 
+    private:
+        void layersChanged(std::vector<viz::CoinLayerID> const& layers);
+
     private:
         Ui::ArVizWidget widget;
 
-- 
GitLab