LayerInfoTree.cpp
Go to the documentation of this file.
1 #include "LayerInfoTree.h"
2 
5 
6 #include <SimoxUtility/algorithm/string.h>
7 
8 #include <QCheckBox>
9 #include <QDoubleSpinBox>
10 #include <QLineEdit>
11 #include <QPushButton>
12 #include <QSpinBox>
13 
18 
19 
20 namespace armarx
21 {
23  {
24  }
25 
26  void LayerInfoTree::setWidget(QTreeWidget* widget)
27  {
28  if (!widgetConnections.empty())
29  {
30  // Disconnect connections to prior widget.
31  for (const auto& conn : widgetConnections)
32  {
33  disconnect(conn);
34  }
35  widgetConnections.clear();
36  }
37 
38  this->widget = widget;
39 
40  // Update layer element properties when expanded.
41  widgetConnections.append(connect(widget, &QTreeWidget::itemExpanded, [this](QTreeWidgetItem * item)
42  {
43  if (item->parent() || this->layers == nullptr)
44  {
45  return;
46  }
47 
48  std::string id = item->text(0).toStdString();
49  auto* layer = this->layers->findLayer(this->layerID);
50  if (layer != nullptr)
51  {
52  viz::CoinLayerElement const* element = layer->findElement(id);
53  if (element != nullptr)
54  {
55  this->updateLayerElementProperties(item, element->data);
56  }
57  }
58  }));
59 
60  widget->setColumnCount(2);
61  widget->setHeaderLabels({"Property", "Value"});
62  widget->setHeaderHidden(false);
63  }
64 
66  {
67  visualizer.layerUpdatedCallbacks.emplace_back(
68  [this](const viz::CoinLayerID & layerID, const viz::CoinLayer & layer)
69  {
70  if (layerID == this->layerID)
71  {
72  this->update();
73  }
74  });
75  }
76 
78  {
79  this->maxElementCountDefault = max;
80  }
81 
83  {
84  if (this->enabled != value)
85  {
86  this->enabled = value;
87  if (value)
88  {
89  update();
90  }
91  else
92  {
93  clearLayerElements();
94  }
95  }
96  }
97 
99  {
100  this->layerID = id;
101  this->layers = layers;
102 
103  showMoreItem = nullptr;
104  maxElementCount = maxElementCountDefault;
105 
106  widget->clear();
107 
108  update();
109  }
110 
111 
113  {
114  if (enabled && layers)
115  {
116  auto* layer = layers->findLayer(layerID);
117  if (layer != nullptr)
118  {
119  updateLayerElements(layer->elements);
120  }
121  }
122  }
123 
124 
125  void LayerInfoTree::clearLayerElements()
126  {
127  if (widget)
128  {
129  widget->clear();
130  showMoreItem = nullptr;
131  }
132  }
133 
134 
135  void LayerInfoTree::updateLayerElements(const std::vector<viz::CoinLayerElement>& elements)
136  {
137  if (showMoreItem)
138  {
139  // We always delete the item up-front. If we still need it, it will be added again.
140  // This way, we don't have to check for the showMoreItem while we update the entries.
141  int index = widget->indexOfTopLevelItem(showMoreItem);
142  ARMARX_CHECK_NONNEGATIVE(index) << "Invariant: If showMoreItem != nullptr, then the widget contains it.";
143  delete widget->takeTopLevelItem(index);
144  showMoreItem = nullptr;
145  }
146  ARMARX_CHECK_IS_NULL(showMoreItem);
147 
148  int currentIndex = 0;
149  for (viz::CoinLayerElement const& element : elements)
150  {
151  std::string const& name = element.data->id;
152 
153  if (maxElementCount >= 0 && currentIndex >= maxElementCount)
154  {
155  // Stop adding more.
156  addShowMoreItem();
157  break;
158  }
159 
160  if (currentIndex >= widget->topLevelItemCount())
161  {
162  // Add elements to the end of the list.
163  QTreeWidgetItem* item = insertLayerElementItem(currentIndex, name, element.data);
164  updateLayerElementProperties(item, element.data);
165  ++currentIndex;
166  continue;
167  }
168 
169  // Get current item.
170  QTreeWidgetItem* currentItem = widget->topLevelItem(currentIndex);
171 
172  // Compare current item to name.
173 
174  while (currentItem != nullptr && name.c_str() > currentItem->text(0))
175  {
176  // Delete items before current item.
177  delete widget->takeTopLevelItem(currentIndex);
178  currentItem = widget->topLevelItem(currentIndex);
179  }
180 
181  if (currentItem != nullptr && name.c_str() < currentItem->text(0))
182  {
183  // Insert new item before child.
184  currentItem = insertLayerElementItem(currentIndex, name, element.data);
185  updateLayerElementProperties(currentItem, element.data);
186  ++currentIndex;
187  continue;
188  }
189 
190  if (currentItem != nullptr && name.c_str() == currentItem->text(0))
191  {
192  // Already existing.
193  updateLayerElementItem(currentItem, element.data);
194  if (currentItem->isExpanded())
195  {
196  updateLayerElementProperties(currentItem, element.data);
197  }
198  ++currentIndex;
199  continue;
200  }
201  }
202  }
203 
204 
205  QTreeWidgetItem* LayerInfoTree::insertLayerElementItem(
206  int index, const std::string& name, const viz::data::ElementPtr& element)
207  {
208  QTreeWidgetItem* item = new QTreeWidgetItem(QStringList { name.c_str(), getTypeName(element).c_str() });
209  // To be used when we can hide specific elements.
210  // item->setCheckState(0, Qt::CheckState::Unchecked);
211  widget->insertTopLevelItem(index, item);
212  return item;
213  }
214 
215  void LayerInfoTree::updateLayerElementItem(QTreeWidgetItem* item, const viz::data::ElementPtr& element)
216  {
217  // Update type name.
218  item->setText(1, getTypeName(element).c_str());
219  }
220 
221 
222  void LayerInfoTree::updateLayerElementProperties(QTreeWidgetItem* item, const viz::data::ElementPtr& element)
223  {
224  bool recursive = true;
225  updateJsonChildren(item, serializeElement(element), recursive);
226  }
227 
228 
229  std::string LayerInfoTree::getTypeName(const viz::data::ElementPtr& element) const
230  {
231  const std::string typeName = simox::meta::get_type_name(*element);
233  ? typeName
234  : "<unknown type '" + typeName + "'>";
235  }
236 
237 
238  nlohmann::json LayerInfoTree::serializeElement(const viz::data::ElementPtr& element) const
239  {
240  try
241  {
242  return nlohmann::json(element);
243  }
244  catch (const viz::error::NoSerializerForType&)
245  {
246  return nlohmann::json::object();
247  }
248  }
249 
250 
251  void LayerInfoTree::updateJsonChildren(QTreeWidgetItem* parent, const nlohmann::json& json, bool recursive)
252  {
253  if (json.is_array())
254  {
255  nlohmann::json jmeta = nlohmann::json::object();
256 
257  int iItem = 0;
258  for (auto j : json)
259  {
260  const std::string key = std::to_string(iItem);
261 
262  if (iItem >= parent->childCount())
263  {
264  QTreeWidgetItem* item = addJsonChild(parent, key);
265  updateJsonItem(item, key, j, jmeta, recursive);
266  }
267  else // iItem < parent->childCount()
268  {
269  QTreeWidgetItem* currentItem = parent->child(iItem);
270  updateJsonItem(currentItem, key, j, jmeta, recursive);
271  }
272  ++iItem;
273  }
274  while (parent->childCount() > int(json.size()))
275  {
276  parent->removeChild(parent->child(parent->childCount() - 1));
277  }
278  }
279  else if (json.is_object())
280  {
281  namespace meta = viz::json::meta;
282  const nlohmann::json& jmeta = json.count(meta::KEY) > 0 ? json.at(meta::KEY)
283  : nlohmann::json::object();
284 
285  int iItem = 0;
286  for (auto it : json.items())
287  {
288  // Hide meta entries.
289  if (isMetaKey(it.key()))
290  {
291  continue;
292  }
293 
294  // Check meta info.
295  if (jmeta.count(it.key()) > 0 && jmeta.at(it.key()) == meta::HIDE)
296  {
297  continue;
298  }
299 
300  if (iItem >= parent->childCount())
301  {
302  QTreeWidgetItem* item = addJsonChild(parent, it.key());
303  updateJsonItem(item, it.key(), it.value(), jmeta, recursive);
304  ++iItem;
305  continue;
306  }
307 
308  QTreeWidgetItem* currentItem = parent->child(iItem);
309 
310  while (it.key().c_str() > currentItem->text(0))
311  {
312  // Delete items before current item.
313  parent->removeChild(currentItem);
314  currentItem = parent->child(iItem);
315  }
316 
317  if (it.key().c_str() < currentItem->text(0))
318  {
319  // Insert new item before child.
320  currentItem = addJsonChild(parent, it.key());
321  updateJsonItem(currentItem, it.key(), it.value(), jmeta, recursive);
322  ++iItem;
323  continue;
324  }
325 
326  if (it.key().c_str() == currentItem->text(0))
327  {
328  // Already existing.
329  updateJsonItem(currentItem, it.key(), it.value(), jmeta, recursive);
330  ++iItem;
331  continue;
332  }
333  }
334  }
335  }
336 
337 
338  QTreeWidgetItem* LayerInfoTree::addJsonChild(QTreeWidgetItem* parent, const std::string& key)
339  {
340  QTreeWidgetItem* child = new QTreeWidgetItem(QStringList { key.c_str(), "" });
341  // To be used when we can actually change the values (enabled = use value from GUI).
342  // child->setCheckState(0, Qt::CheckState::Unchecked);
343  parent->addChild(child);
344 
345  return child;
346  }
347 
348 
349  void LayerInfoTree::updateJsonItem(
350  QTreeWidgetItem* item, const std::string& key, const nlohmann::json& value,
351  const nlohmann::json& parentMeta,
352  bool recursive)
353  {
354  const int columnKey = 0;
355 
356  // Update key.
357  item->setText(columnKey, key.c_str());
358  // Update value.
359  updateJsonItemValue(item, key, value, parentMeta);
360 
361  // Recursion.
362  if (recursive)
363  {
364  updateJsonChildren(item, value, recursive);
365  }
366  }
367 
368 
369  void LayerInfoTree::updateJsonItemValue(
370  QTreeWidgetItem* item, const std::string& key, const nlohmann::json& value,
371  const nlohmann::json& parentMeta)
372  {
373  const int columnValue = 1;
374 
375  if (parentMeta.count(key) > 0)
376  {
377  const nlohmann::json& metaValue = parentMeta.at(key);
378  if (metaValue.is_string())
379  {
380  const std::string metaString = metaValue.get<std::string>();
381  if (metaString == viz::json::meta::READ_ONLY)
382  {
383  item->setText(columnValue, getValuePreview(value).toString());
384  // Hide check box.
385  item->setData(0, Qt::CheckStateRole, QVariant());
386  return;
387  }
388  }
389  }
390 
391  // No handled meta info.
392  if (QWidget* w = widget->itemWidget(item, columnValue))
393  {
394  updateValueWidget(value, w);
395  }
396  else if (QWidget* w = getValueWidget(item, value); w)
397  {
398  widget->setItemWidget(item, columnValue, w);
399  }
400  else
401  {
402  // No widget.
403  item->setText(columnValue, getValuePreview(value).toString());
404  }
405  }
406 
407 
408  QVariant LayerInfoTree::getValuePreview(const nlohmann::json& json) const
409  {
410  std::stringstream ss;
411 
412  switch (json.type())
413  {
414  case nlohmann::json::value_t::null:
415  return QVariant("null");
416 
417  case nlohmann::json::value_t::object:
418  {
419  std::vector<std::string> keys;
420  for (auto it : json.items())
421  {
422  if (!isMetaKey(it.key()))
423  {
424  keys.push_back(it.key());
425  }
426  }
427  ss << "object [" << simox::alg::join(keys, ", ") << "]";
428  return QVariant(ss.str().c_str());
429  }
430 
431  case nlohmann::json::value_t::array:
432  ss << "array of size " << json.size();
433  return QVariant(ss.str().c_str());
434 
435  case nlohmann::json::value_t::string:
436  return QVariant(json.get<std::string>().c_str());
437  case nlohmann::json::value_t::boolean:
438  return QVariant(json.get<bool>());
439  case nlohmann::json::value_t::number_integer:
440  return QVariant(json.get<int>());
441  case nlohmann::json::value_t::number_unsigned:
442  return QVariant(json.get<unsigned int>());
443  case nlohmann::json::value_t::number_float:
444  return QVariant(json.get<float>());
445 
446  default:
447  return QVariant("n/a");
448  }
449  }
450 
451 
452  QWidget* LayerInfoTree::getValueWidget(QTreeWidgetItem* item, const nlohmann::json& json) const
453  {
454  auto setChecked = [item]()
455  {
456  item->setCheckState(0, Qt::CheckState::Checked);
457  };
458 
459  switch (json.type())
460  {
461  case nlohmann::json::value_t::string:
462  {
463 
464  QLineEdit* w = new QLineEdit(json.get<std::string>().c_str());
465  connect(w, &QLineEdit::editingFinished, this, setChecked);
466  return w;
467  }
468  case nlohmann::json::value_t::boolean:
469  {
470  QCheckBox* w = new QCheckBox("Enabled");
471  w->setChecked(json.get<bool>());
472  connect(w, &QCheckBox::stateChanged, this, setChecked);
473  return w;
474  }
475  case nlohmann::json::value_t::number_integer:
476  case nlohmann::json::value_t::number_unsigned:
477  {
478  QSpinBox* w = new QSpinBox();
479  w->setMinimum(std::numeric_limits<int>::lowest());
480  w->setMaximum(std::numeric_limits<int>::max());
481  w->setValue(json.get<int>());
482  connect(w, qOverload<int>(&QSpinBox::valueChanged), this, setChecked);
483  return w;
484  }
485  case nlohmann::json::value_t::number_float:
486  {
487  QDoubleSpinBox* w = new QDoubleSpinBox();
488  w->setMinimum(std::numeric_limits<double>::lowest());
489  w->setMaximum(std::numeric_limits<double>::max());
490  w->setValue(json.get<double>());
491  connect(w, qOverload<double>(&QDoubleSpinBox::valueChanged), this, setChecked);
492  return w;
493  }
494  case nlohmann::json::value_t::null:
495  case nlohmann::json::value_t::object:
496  case nlohmann::json::value_t::array:
497  return nullptr;
498 
499  default:
500  return nullptr;
501  }
502  }
503 
504 
505  void LayerInfoTree::updateValueWidget(const nlohmann::json& json, QWidget* widget) const
506  {
507  switch (json.type())
508  {
509  case nlohmann::json::value_t::string:
510  {
511  QLineEdit* w = dynamic_cast<QLineEdit*>(widget);
513  w->setText(json.get<std::string>().c_str());
514  }
515  break;
516  case nlohmann::json::value_t::boolean:
517  {
518  QCheckBox* w = dynamic_cast<QCheckBox*>(widget);
520  w->setChecked(json.get<bool>());
521  }
522  break;
523  case nlohmann::json::value_t::number_integer:
524  case nlohmann::json::value_t::number_unsigned:
525  {
526  QSpinBox* w = dynamic_cast<QSpinBox*>(widget);
528  w->setValue(json.get<int>());
529  }
530  break;
531  case nlohmann::json::value_t::number_float:
532  {
533  QDoubleSpinBox* w = dynamic_cast<QDoubleSpinBox*>(widget);
535  w->setValue(json.get<double>());
536  }
537  break;
538  case nlohmann::json::value_t::null:
539  case nlohmann::json::value_t::object:
540  case nlohmann::json::value_t::array:
541  default:
542  break;
543  }
544  }
545 
546 
547  bool LayerInfoTree::isMetaKey(const std::string& key)
548  {
549  return key.substr(0, 2) == "__" && key.substr(key.size() - 2) == "__";
550  }
551 
552  void LayerInfoTree::addShowMoreItem()
553  {
554  ARMARX_CHECK_NULL(showMoreItem) << "There should not be a showMoreItem when this function is called.";
555 
556  auto* layer = this->layers->findLayer(this->layerID);
557  if (layer == nullptr)
558  {
559  ARMARX_WARNING << "No layer with ID '" << this->layerID.first
560  << "/" << this->layerID.second << "' found";
561  return;
562  }
563 
564  std::stringstream ss;
565  ss << "Showing " << widget->topLevelItemCount() << " of " << layer->elements.size() << " elements.";
566 
567  // Add a new item.
568  QTreeWidgetItem* item = new QTreeWidgetItem(QStringList { "", ss.str().c_str() });
569  widget->addTopLevelItem(item);
570 
571  QPushButton* button = new QPushButton("Show more");
572  widget->setItemWidget(item, 0, button);
573 
574  connect(button, &QPushButton::pressed, [this, layer]()
575  {
576  maxElementCount += maxElementCountDefault;
577  this->updateLayerElements(layer->elements);
578  });
579 
580  this->showMoreItem = item;
581  }
582 
583 }
armarx::viz::CoinLayer
Definition: Visualizer.h:36
armarx::LayerInfoTree::registerVisualizerCallbacks
void registerVisualizerCallbacks(viz::CoinVisualizer &visualizer)
Definition: LayerInfoTree.cpp:65
armarx::viz::CoinLayerMap::findLayer
CoinLayer * findLayer(CoinLayerID const &id)
Definition: Visualizer.h:118
ARMARX_CHECK_NONNEGATIVE
#define ARMARX_CHECK_NONNEGATIVE(number)
Check whether number is nonnegative (>= 0). If it is not, throw an ExpressionException with the expre...
Definition: ExpressionException.h:152
index
uint8_t index
Definition: EtherCATFrame.h:59
armarx::viz::json::meta::KEY
const std::string KEY
Definition: json_base.cpp:77
armarx::LayerInfoTree::setWidget
void setWidget(QTreeWidget *treeWidget)
Definition: LayerInfoTree.cpp:26
armarx::LayerInfoTree::LayerInfoTree
LayerInfoTree()
Definition: LayerInfoTree.cpp:22
armarx::viz::json::meta::HIDE
const std::string HIDE
Do not show.
Definition: json_base.cpp:79
armarx::viz::CoinVisualizer::layerUpdatedCallbacks
std::vector< std::function< void(CoinLayerID const &layerID, CoinLayer const &layer)> > layerUpdatedCallbacks
A layer's data has changed.
Definition: Visualizer.h:283
ARMARX_CHECK_NOT_NULL
#define ARMARX_CHECK_NOT_NULL(ptr)
This macro evaluates whether ptr is not null and if it turns out to be false it will throw an Express...
Definition: ExpressionException.h:206
armarx::viz::json::ElementJsonSerializers::isTypeRegistered
static bool isTypeRegistered(const std::string &typeName)
Indicates whether there is a serializer registered for the given type name.
Definition: ElementJsonSerializers.cpp:104
armarx::max
std::vector< T > max(const std::vector< T > &v1, const std::vector< T > &v2)
Definition: VectorHelpers.h:267
armarx::LayerInfoTree::setSelectedLayer
void setSelectedLayer(viz::CoinLayerID id, viz::CoinLayerMap *layers)
Set the selected layer.
Definition: LayerInfoTree.cpp:98
armarx::LayerInfoTree::update
void update()
Update the currently expanded layer elements.
Definition: LayerInfoTree.cpp:112
armarx::LayerInfoTree::setEnabled
void setEnabled(bool enabled)
Enable or disable the layer info tree.
Definition: LayerInfoTree.cpp:82
ElementJsonSerializers.h
LayerInfoTree.h
ARMARX_CHECK_IS_NULL
#define ARMARX_CHECK_IS_NULL(ptr)
This macro evaluates whether ptr is null and if it turns out to be false it will throw an ExpressionE...
Definition: ExpressionException.h:194
cxxopts::value
std::shared_ptr< Value > value()
Definition: cxxopts.hpp:926
json_elements.h
if
if(!yyvaluep)
Definition: Grammar.cpp:724
armarx::viz::json::meta::READ_ONLY
const std::string READ_ONLY
Make read-only.
Definition: json_base.cpp:80
armarx::viz::CoinLayerElement::data
data::ElementPtr data
Definition: Visualizer.h:26
armarx::viz::CoinLayerElement
Definition: Visualizer.h:24
json_layer.h
json_base.h
max
T max(T t1, T t2)
Definition: gdiam.h:48
armarx::to_string
const std::string & to_string(const std::string &s)
Definition: StringHelpers.h:40
armarx::viz::CoinVisualizer
Definition: Visualizer.h:198
ExpressionException.h
armarx::viz::toString
const char * toString(InteractionFeedbackType type)
Definition: Interaction.h:27
ARMARX_CHECK_NULL
#define ARMARX_CHECK_NULL(ptr)
Definition: ExpressionException.h:212
Logging.h
ARMARX_WARNING
#define ARMARX_WARNING
Definition: Logging.h:186
armarx::LayerInfoTree::setMaxElementCountDefault
void setMaxElementCountDefault(int max)
Definition: LayerInfoTree.cpp:77
armarx::viz::CoinLayerMap
Definition: Visualizer.h:110
armarx::viz::CoinLayerID
std::pair< std::string, std::string > CoinLayerID
Definition: Visualizer.h:21
armarx
This file offers overloads of toIce() and fromIce() functions for STL container types.
Definition: ArmarXTimeserver.cpp:28