SkillExecutionTreeWidget.cpp
Go to the documentation of this file.
2
3#include <mutex>
4
5#include <functional>
6
7#include <QApplication>
8#include <QClipboard>
9#include <QDialog>
10#include <QFormLayout>
11#include <QLabel>
12#include <QMenu>
13#include <QPushButton>
14#include <QTreeWidgetItem>
15#include <QVBoxLayout>
16
18
23
27
28namespace armarx::skills::gui
29{
30 namespace
31 {
32 // same format as SkillDetailsTreeWidget::copyCurrentConfig(), so the copied
33 // parameters can be pasted via "set params from clipboard"
34 void
35 copyParametersJsonToClipboard(const aron::data::DictPtr& parameters)
36 {
37 if (!parameters)
38 {
39 ARMARX_INFO << "The execution has no parameters to copy.";
40 return;
41 }
42 auto json =
44 QApplication::clipboard()->setText(QString::fromStdString(json.dump(2)));
45 }
46 } // namespace
47
48 void
49 SkillExecutionTreeWidget::runContextMenu(const QPoint& pos)
50 {
51 // sanity check
52 ARMARX_CHECK(selectionValid());
53
54 QMenu* menu = new QMenu();
55
56 // Stop skill
57 QAction* stopSkillAction = new QAction("Stop execution", this);
58 const auto& executions = memory->getExecutions();
59 if (executions.count(selectedExecution.skillExecutionId) == 0)
60 return;
61 skills::SkillStatus currentStatus =
62 memory->getExecutions().at(selectedExecution.skillExecutionId).status;
63 stopSkillAction->setDisabled(currentStatus == skills::SkillStatus::Aborted ||
64 currentStatus == skills::SkillStatus::Failed ||
65 currentStatus == skills::SkillStatus::Succeeded);
66
67 QAction* rerunSkillAction = new QAction("Re-execute with similar parameters", this);
68 QAction* copyParametersAction = new QAction("Copy parameters to clipboard", this);
69 QAction* showDetailsAction = new QAction("Show Execution Details", this);
70 menu->addAction(stopSkillAction);
71 menu->addAction(rerunSkillAction);
72 menu->addAction(copyParametersAction);
73 menu->addAction(showDetailsAction);
74 connect(stopSkillAction,
75 &QAction::triggered,
76 this,
77 &SkillExecutionTreeWidget::stopSelectedExecution);
78 connect(rerunSkillAction,
79 &QAction::triggered,
80 this,
81 &SkillExecutionTreeWidget::rerunSkillWithSimilarParams);
82 connect(copyParametersAction,
83 &QAction::triggered,
84 this,
85 &SkillExecutionTreeWidget::copyParametersToClipboard);
86 connect(showDetailsAction,
87 &QAction::triggered,
88 this,
89 &SkillExecutionTreeWidget::showExecutionDetails);
90
91 // open menu
92 menu->popup(this->viewport()->mapToGlobal(pos));
93 }
94
95 void
96 SkillExecutionTreeWidget::stopSelectedExecution()
97 {
98 if (!selectionValid())
99 return;
100 memory->stopExecution(this->selectedExecution.skillExecutionId);
101 }
102
103 void
104 SkillExecutionTreeWidget::rerunSkillWithSimilarParams()
105 {
106 if (!selectionValid())
107 return;
108 // we don't want to hold state in the gui, so we need to get the parameters from memory:
109 skills::SkillExecutionID currentExecutionId = this->selectedExecution.skillExecutionId;
110 auto executions = memory->getExecutions();
111 if (executions.empty())
112 return;
113
114 if (executions.count(currentExecutionId) == 0)
115 {
116 // we didn't find an entry for the execution id
117 ARMARX_IMPORTANT << "The selected execution was not found in memory. The GUI is unable "
118 "to determine the parametrization for this execution.";
119 return;
120 }
121 auto params = executions[currentExecutionId].parameters;
122
123 ARMARX_INFO << "Re-executing the skill " << currentExecutionId.skillId
124 << " with previous parameters.";
125
126 // give all information to manager
127 this->memory->startExecutionWithParams(currentExecutionId.skillId, params);
128 }
129
130 void
131 SkillExecutionTreeWidget::copyParametersToClipboard()
132 {
133 if (!selectionValid())
134 return;
135
136 auto executions = memory->getExecutions();
137 auto executionIt = executions.find(selectedExecution.skillExecutionId);
138 if (executionIt == executions.end())
139 {
140 ARMARX_IMPORTANT << "The selected execution was not found in memory. The GUI is unable "
141 "to determine the parametrization for this execution.";
142 return;
143 }
144 copyParametersJsonToClipboard(executionIt->second.parameters);
145 }
146
147 void
148 SkillExecutionTreeWidget::showExecutionDetails()
149 {
150 if (!selectionValid())
151 return;
152
153 skills::SkillExecutionID executionId = this->selectedExecution.skillExecutionId;
154 auto executions = memory->getExecutions();
155 if (executions.count(executionId) == 0)
156 {
157 ARMARX_IMPORTANT << "The selected execution was not found in memory. The GUI is unable "
158 "to determine the details for this execution.";
159 return;
160 }
161 const auto& update = executions.at(executionId);
162
163 std::string statusStr = "Unknown";
164 for (const auto& [status, name] : EXECUTION_STATUS_TO_STRING)
165 {
166 if (status == update.status)
167 {
168 statusStr = name;
169 }
170 }
171
172 auto* dialog = new QDialog(this);
173 dialog->setAttribute(Qt::WA_DeleteOnClose);
174 dialog->setWindowTitle("Execution Details");
175 dialog->resize(600, 500);
176
177 auto* layout = new QVBoxLayout(dialog);
178 auto* infoLayout = new QFormLayout();
179 infoLayout->addRow("Skill:",
180 new QLabel(QString::fromStdString(executionId.skillId.toString())));
181 infoLayout->addRow("Executor:",
182 new QLabel(QString::fromStdString(executionId.executorName)));
183 infoLayout->addRow(
184 "Started:",
185 new QLabel(QString::fromStdString(executionId.executionStartedTime.toDateTimeString())));
186 infoLayout->addRow("Status:", new QLabel(QString::fromStdString(statusStr)));
187 layout->addLayout(infoLayout);
188
189 // look up the skill description to get the parameter/result types, so the
190 // values can be rendered like in the skill description tab
191 std::optional<skills::SkillDescription> description;
192 if (executionId.skillId.providerId.has_value())
193 {
194 const auto skillsMap = memory->getSkills();
195 auto providerIt = skillsMap.find(executionId.skillId.providerId.value());
196 if (providerIt != skillsMap.end())
197 {
198 auto skillIt = providerIt->second.find(executionId.skillId);
199 if (skillIt != providerIt->second.end())
200 {
201 description = skillIt->second;
202 }
203 }
204 }
205
206 // same rendering as the skill description tab, but read-only;
207 // if defaults are given, parameters differing from them are highlighted
208 const auto makeAronTree = [dialog](const std::string& title,
209 const aron::type::ObjectPtr& type,
211 const aron::data::DictPtr& defaults) -> QTreeWidget*
212 {
213 auto* tree = new QTreeWidget(dialog);
214 tree->setColumnCount(3);
215 tree->setHeaderLabels({"Key", "Value", "Type"});
216
217 auto* rootItem = new QTreeWidgetItem(tree);
218 rootItem->setText(0, QString::fromStdString(title));
219
220 auto* controller = new AronTreeWidgetController(tree, rootItem, type, data);
221 // the controller only handles editing; dropping it makes the tree static
222 delete controller;
223
224 // int enums are shown as an editable combo box holding only the numeric
225 // value; replace it with a static "Name (value)" text
226 std::function<void(QTreeWidgetItem*)> showEnumNames = [&](QTreeWidgetItem* item)
227 {
228 if (auto* aronItem = AronTreeWidgetItem::DynamicCast(item))
229 {
230 auto enumType = aron::type::IntEnum::DynamicCast(aronItem->aronType);
231 auto* enumWidget = IntEnumWidget::DynamicCast(tree->itemWidget(item, 1));
232 if (enumType && enumWidget)
233 {
234 const std::string text = enumWidget->getText().toStdString();
235 QString display = QString::fromStdString(text);
236 for (const auto& [name, value] : enumType->getAcceptedValueMap())
237 {
238 if (name == text || std::to_string(value) == text)
239 {
240 display = QString::fromStdString(name + " (" +
241 std::to_string(value) + ")");
242 break;
243 }
244 }
245 tree->removeItemWidget(item, 1);
246 item->setText(1, display);
247 }
248 }
249 for (int i = 0; i < item->childCount(); ++i)
250 {
251 showEnumNames(item->child(i));
252 }
253 };
254 showEnumNames(rootItem);
255
256 // highlight the specific rows that deviate from the profile defaults,
257 // i.e. that were actively set for this execution
258 const auto markChanged = [](QTreeWidgetItem* item)
259 {
260 const QBrush highlight(QColor(255, 200, 100, 90));
261 QFont font = item->font(0);
262 font.setBold(true);
263 for (int column = 0; column < 3; ++column)
264 {
265 item->setBackground(column, highlight);
266 item->setToolTip(column,
267 "This value differs from the profile default, i.e. it "
268 "was actively set for this execution.");
269 }
270 item->setFont(0, font);
271 };
272 // ancestors of a changed row only get a bold key, so the change can be
273 // located when the tree is collapsed without drowning it in highlights
274 const auto markContainsChange = [](QTreeWidgetItem* item)
275 {
276 QFont font = item->font(0);
277 font.setBold(true);
278 item->setFont(0, font);
279 };
280
281 // walks the tree and both data dicts in parallel; returns whether
282 // anything in the subtree differs
283 std::function<bool(QTreeWidgetItem*,
286 markDifferences = [&](QTreeWidgetItem* item,
288 const aron::data::VariantPtr& defaultValue) -> bool
289 {
290 if (!value && !defaultValue)
291 {
292 return false;
293 }
294 if (!value || !defaultValue)
295 {
296 // set but no default (or vice versa) -> the whole entry was actively set
297 markChanged(item);
298 return true;
299 }
300
301 if (auto valueDict = aron::data::Dict::DynamicCast(value))
302 {
303 auto defaultDict = aron::data::Dict::DynamicCast(defaultValue);
304 if (!defaultDict)
305 {
306 markChanged(item);
307 return true;
308 }
309 bool anyChildChanged = false;
310 for (int i = 0; i < item->childCount(); ++i)
311 {
312 QTreeWidgetItem* childItem = item->child(i);
313 const std::string key = childItem->text(0).toStdString();
314 const auto childValue =
315 valueDict->hasElement(key) ? valueDict->getElement(key) : nullptr;
316 const auto childDefault = defaultDict->hasElement(key)
317 ? defaultDict->getElement(key)
318 : nullptr;
319 anyChildChanged |= markDifferences(childItem, childValue, childDefault);
320 }
321 if (anyChildChanged)
322 {
323 markContainsChange(item);
324 }
325 return anyChildChanged;
326 }
327
328 if (auto valueList = aron::data::List::DynamicCast(value))
329 {
330 auto defaultList = aron::data::List::DynamicCast(defaultValue);
331 if (!defaultList)
332 {
333 markChanged(item);
334 return true;
335 }
336 const auto valueElements = valueList->getElements();
337 const auto defaultElements = defaultList->getElements();
338 bool anyChildChanged = false;
339 for (int i = 0; i < item->childCount(); ++i)
340 {
341 QTreeWidgetItem* childItem = item->child(i);
342 const auto childValue = static_cast<size_t>(i) < valueElements.size()
343 ? valueElements[i]
344 : nullptr;
345 const auto childDefault = static_cast<size_t>(i) < defaultElements.size()
346 ? defaultElements[i]
347 : nullptr;
348 anyChildChanged |= markDifferences(childItem, childValue, childDefault);
349 }
350 if (valueElements.size() != defaultElements.size())
351 {
352 // fewer elements than the default -> visible only on the list itself
353 markChanged(item);
354 anyChildChanged = true;
355 }
356 else if (anyChildChanged)
357 {
358 markContainsChange(item);
359 }
360 return anyChildChanged;
361 }
362
363 // leaf value (int, float, string, bool, matrix, ...)
364 bool equal = false;
365 try
366 {
367 equal = (*value == defaultValue);
368 }
369 catch (...)
370 {
371 // type mismatch between value and default -> treat as changed
372 }
373 if (not equal)
374 {
375 markChanged(item);
376 return true;
377 }
378 return false;
379 };
380
381 if (data && defaults)
382 {
383 if (QTreeWidgetItem* objectItem = rootItem->child(0))
384 {
385 markDifferences(objectItem, data, defaults);
386 }
387 }
388
389 tree->setEditTriggers(QAbstractItemView::NoEditTriggers);
390 std::function<void(QTreeWidgetItem*)> makeReadOnly =
391 [&](QTreeWidgetItem* item)
392 {
393 item->setFlags(item->flags() & ~Qt::ItemIsEditable);
394 if (QWidget* itemWidget = tree->itemWidget(item, 1))
395 {
396 itemWidget->setEnabled(false);
397 }
398 for (int i = 0; i < item->childCount(); ++i)
399 {
400 makeReadOnly(item->child(i));
401 }
402 };
403 makeReadOnly(rootItem);
404
405 tree->expandAll();
406 tree->resizeColumnToContents(0);
407 return tree;
408 };
409
410 if (description.has_value())
411 {
412 layout->addWidget(makeAronTree("Parameters",
413 description->parametersType,
414 update.parameters,
415 description->rootProfileDefaults));
416
417 if (update.result)
418 {
419 layout->addWidget(
420 makeAronTree("Result", description->resultType, update.result, nullptr));
421 }
422 }
423 else
424 {
425 layout->addWidget(
426 new QLabel("The skill description is no longer available; the parameters "
427 "cannot be displayed."));
428 }
429
430 auto* copyButton = new QPushButton("Copy parameters to clipboard", dialog);
431 connect(copyButton,
432 &QPushButton::clicked,
433 [parameters = update.parameters]() { copyParametersJsonToClipboard(parameters); });
434 layout->addWidget(copyButton);
435
436 dialog->show();
437 }
438
439 void
441 {
442 this->selectedExecution = SelectedExecution();
443 this->clear();
444 }
445
446 void
447 SkillExecutionTreeWidget::setupUi()
448 {
449 this->setColumnCount(6);
450
451 this->setContextMenuPolicy(Qt::CustomContextMenu);
452
453 QTreeWidgetItem* qtreewidgetitem = this->headerItem();
454 qtreewidgetitem->setText(5, "");
455 qtreewidgetitem->setText(4, "");
456 qtreewidgetitem->setText(3, "Status");
457 qtreewidgetitem->setText(2, "SkillID");
458 qtreewidgetitem->setText(1, "Executor");
459 qtreewidgetitem->setText(0, "Timestamp");
460
461 this->setColumnWidth(4, 30);
462
463 connectSignals();
464 }
465
466 void
467 SkillExecutionTreeWidget::connectSignals()
468 {
469 connect(this,
470 &QTreeWidget::customContextMenuRequested,
471 this,
472 &SkillExecutionTreeWidget::runContextMenu);
473 connect(this,
474 &QTreeWidget::currentItemChanged,
475 this,
476 &SkillExecutionTreeWidget::executionSelectionChanged);
477 }
478
479 inline bool
480 SkillExecutionTreeWidget::selectionValid()
481 {
483 }
484
485 void
486 SkillExecutionTreeWidget::executionSelectionChanged(QTreeWidgetItem* current,
487 QTreeWidgetItem* previous)
488 {
489 // update internal state
490 SkillExecutionTreeWidgetItem* selected =
491 dynamic_cast<SkillExecutionTreeWidgetItem*>(current);
492 if (selected)
493 {
494 this->selectedExecution.skillExecutionId = selected->getExecutionId();
495 }
496 }
497
498 void
500 {
501 if (update.statuses.empty())
502 {
503 return;
504 }
505
506 for (const auto& [k, v] : update.statuses)
507 {
508 skills::SkillExecutionID executionId = k;
509 skills::SkillStatusUpdate statusUpdate = v;
510
511 SkillExecutionTreeWidgetItem* found = nullptr;
512 for (int i = 0; i < this->topLevelItemCount(); ++i)
513 {
514 auto c = dynamic_cast<SkillExecutionTreeWidgetItem*>(topLevelItem(i));
515 if (!c)
516 {
517 // the item is probably not the correct type, skip...
518 continue;
519 }
520
522
523 if (found)
524 {
525 found->updateItem(statusUpdate.status);
526
527 break;
528 }
529 }
530
531 if (!found)
532 {
533 // TODO: Sort to executor!
534 auto item = new SkillExecutionTreeWidgetItem(executionId, memory, this);
535
536 item->updateItem(statusUpdate.status);
537 }
538 }
539 }
540} // namespace armarx::skills::gui
uint8_t data[1]
constexpr T c
static nlohmann::json ConvertToNlohmannJSON(const data::VariantPtr &)
static const constexpr char * UNKNOWN
Definition SkillID.h:18
std::string skillName
Definition SkillID.h:41
static AronTreeWidgetItem * DynamicCast(QTreeWidgetItem *)
static IntEnumWidget * DynamicCast(QWidget *)
std::shared_ptr< SkillManagerWrapper > memory
static SkillExecutionTreeWidgetItem * SearchRecursiveForMatch(SkillExecutionTreeWidgetItem *haystack, const skills::SkillExecutionID &needle)
void updateGui(SkillManagerWrapper::Snapshot update)
#define ARMARX_CHECK(expression)
Shortcut for ARMARX_CHECK_EXPRESSION.
#define ARMARX_INFO
The normal logging level.
Definition Logging.h:179
#define ARMARX_IMPORTANT
The logging level for always important information, but expected behaviour (in contrast to ARMARX_WAR...
Definition Logging.h:188
bool update(mongocxx::collection &coll, const nlohmann::json &query, const nlohmann::json &update)
Definition mongodb.cpp:68
std::shared_ptr< Dict > DictPtr
Definition Dict.h:42
std::shared_ptr< Variant > VariantPtr
std::shared_ptr< Object > ObjectPtr
Definition Object.h:36
std::shared_ptr< Value > value()
Definition cxxopts.hpp:855
bool equal(const std::string &a, const std::string &b)
Definition httplib.h:370