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