SkillDetailsTreeWidget.cpp
Go to the documentation of this file.
2
3#include <optional>
4
5#include <QApplication>
6#include <QClipboard>
7#include <QHeaderView>
8#include <QResizeEvent>
9#include <QVBoxLayout>
10#include <qchar.h>
11#include <qcheckbox.h>
12#include <qmessagebox.h>
13#include <qpushbutton.h>
14#include <qtreewidget.h>
15
17
22
23namespace armarx::skills::gui
24{
25 SkillDetailsTreeWidget::SkillDetailsTreeWidget(std::shared_ptr<SkillManagerWrapper> _memory,
26 QWidget* parent) :
27 QTreeWidget(parent), MemoryCommunicatorBase(_memory)
28 {
29 setupUi();
30 }
31
32 std::optional<SkillID>
34 {
35 if (shownSkill.has_value())
36 {
37 return shownSkill.value().skillId;
38 }
39 else
40 {
41 return std::nullopt;
42 }
43 }
44
45 void
47 {
48 // dont touch the widget if the skill id didn't change
49 // note: this is only relevant when periodic updates are enabled
50 /*
51 if (shownSkill.has_value() && skillId == shownSkill.value().skillId)
52 return;
53 */
54
55 // check the parameters: did they change?
56 if (shownSkill.has_value())
57 {
58 auto remDesc = shownSkill.value().descr;
59 if (descr.rootProfileDefaults != remDesc.rootProfileDefaults)
60 {
61 // TODO: ask the user if they want to reset to defaults
62 // for now, we just overwrite without asking... (and do nothing)
63 }
64 // other cases: if the parameter types change, we *have* to reset; else the skill
65 // cannot be started with the parameters.
66 // same goes for the result type.
67 }
68
69 this->resetWidget();
70
71 auto aron_args = descr.parametersType;
72 auto default_args_of_profile = descr.rootProfileDefaults;
73 // ideally we want to use the invisible root item, but aron expects a parent...
74 QTreeWidgetItem* aronTreeWidgetItem = new QTreeWidgetItem(this);
75 aronTreeWidgetItem->setText(0, QString::fromStdString("Parameters"));
76
77 aronTreeWidgetController = std::make_shared<AronTreeWidgetController>(
78 this, aronTreeWidgetItem, aron_args, default_args_of_profile);
79
80
81 this->expandAll();
83
84 emit updated(skillId);
85
86 // update the ShownSkill
87 shownSkill = {skillId, descr, aronTreeWidgetController->convertToAron()};
88 }
89
90 void
92 {
93 this->aronTreeWidgetController = nullptr;
94 }
95
96 void
98 {
99 if (not shownSkill.has_value())
100 {
101 return;
102 }
103 skills::SkillID sid = shownSkill.value().skillId;
104
105 // we assume the id to be fully specified, as it is checked while constructing
106 // sanity check
108
109 // maybe the search is empty?
110 auto skillsMap = update.skills;
111 if (skillsMap.count(sid.providerId.value()) == 0 ||
112 skillsMap.at(sid.providerId.value()).count(sid) == 0)
113 {
114 this->resetWidget();
115 return;
116 }
117
118 auto descr = update.skills.at(sid.providerId.value()).at(sid);
119
120 // only triggers if the description does not match
121 // TODO: for some reason the == operator does not work for the aron ObjectPtrs (data and type) in the description.
122 // We exclude them here, but it should be adressed elsewhere...
123 if (descr.skillId != shownSkill->descr.skillId ||
124 descr.timeout != shownSkill->descr.timeout ||
125 descr.description != shownSkill->descr.description)
126 {
127 ARMARX_WARNING << "The skill description of the currently shown skill has changed. "
128 "Resetting the widget...";
129 this->updateContents(sid, descr);
130 }
131 }
132
133 void
134 SkillDetailsTreeWidget::setupUi()
135 {
136 this->setColumnCount(3);
137
138 this->setContextMenuPolicy(Qt::CustomContextMenu);
139 this->setEditTriggers(QAbstractItemView::DoubleClicked | QAbstractItemView::EditKeyPressed);
140
141 QTreeWidgetItem* qtreewidgetitem2 = this->headerItem();
142 qtreewidgetitem2->setText(3, "defaultValue (hidden in GUI)");
143 qtreewidgetitem2->setText(2, "Type");
144 qtreewidgetitem2->setText(1, "Value");
145 qtreewidgetitem2->setText(0, "Key");
146
147 setColumnHidden(3, true);
148 }
149
150 bool
151 SkillDetailsTreeWidget::askUserToConfirmWidgetReset()
152 {
153 std::string msg =
154 "The skill details widget will be reset. All changes will be lost, unless saved.";
155 QMessageBox msgBox;
156 msgBox.setText(QString::fromStdString(msg));
157 QCheckBox* ignoreCheckbox = new QCheckBox("Do not ask again in this session.");
158 msgBox.setCheckBox(ignoreCheckbox);
159 QPushButton* copyParamsButton =
160 msgBox.addButton(tr("Copy Parameters && Close"), QMessageBox::ActionRole);
161 // QPushButton* copySkillIdButton =
162 // msgBox.addButton(tr("Copy SkillID && Close"), QMessageBox::ActionRole);
163 QPushButton* closeWithoutSavingButton =
164 msgBox.addButton(tr("Close"), QMessageBox::ActionRole);
165
166 msgBox.setDefaultButton(closeWithoutSavingButton);
167 QObject::connect(ignoreCheckbox,
168 &QCheckBox::stateChanged,
169 [this](int state)
170 {
171 if (static_cast<Qt::CheckState>(state) == Qt::CheckState::Checked)
172 {
173 this->showWidgetResetConfirmation_ = false;
174 }
175 });
176
177 connect(copyParamsButton,
178 &QPushButton::pressed,
179 this,
181 msgBox.exec();
182
183 return true;
184 }
185
186 bool
187 SkillDetailsTreeWidget::checkIfParametersAreModified()
188 {
189 if (not shownSkill.has_value())
190 {
191 return false;
192 }
193
194 auto defaults = shownSkill->originalParameters;
195
196 if (this->aronTreeWidgetController.get() == nullptr)
197 {
198 return false;
199 }
200
201 auto params = this->aronTreeWidgetController->convertToAron();
202
203 if (defaults.get() == nullptr and params.get() == nullptr)
204 {
205 return false;
206 }
207
208 bool modified = not(*defaults == *params);
209 if (modified)
210 {
211 ARMARX_DEBUG << "The parameters have been modified.";
212 }
213 return modified;
214 }
215
216 /**
217 * Problem: columns 0 and 1 have arbitrary size; so we want to limit their size, to make sure
218 * everything is visible without scrolling. Column 2 is limited by design, as it only contains
219 * type information.
220 */
221 void
223 {
224 // take remainder of width (which we want to assign to dynamic columns)
225 const int widthRemainder = this->width() - typeWidth;
226
227 // we want to assign half of it to each dynamic column
228 const int dynamicColumnSize = widthRemainder / 2;
229
230 // set width...
231
232 this->setColumnWidth(0, dynamicColumnSize);
233
234 this->setColumnWidth(1, dynamicColumnSize);
235 }
236
237 void
239 {
240 if (not shownSkill.has_value())
241 {
242 return;
243 }
244
245 auto params = memory->getLatestParametersForSkill(shownSkill->skillId);
246
247 if (not params.has_value())
248 {
249 return;
250 }
251
252 ARMARX_INFO << "Reloaded parameters from the last execution";
253 aronTreeWidgetController->setFromAron(params.value());
254 }
255
256 void
258 {
259 auto executions = memory->getExecutions();
260 if (executions.empty() || not shownSkill.has_value())
261 {
262 return;
263 }
264
265
266 // find the most recent execution with a matching skill id
267
268 std::optional<armarx::skills::SkillStatusUpdate> found = std::nullopt;
269 for (auto& execution : executions)
270 {
271 if (execution.first.skillId == shownSkill->skillId)
272 {
273 if (not found.has_value())
274 {
275 found = execution.second;
276 }
277 else
278 {
279 if (found->executionId.executionStartedTime <
280 execution.first.executionStartedTime)
281 {
282 // in this case, we found a more recent execution.
283 found = execution.second;
284 }
285 }
286 }
287 }
288
289 if (not found.has_value())
290 {
291 // we didn't find an entry for the execution id
292 ARMARX_INFO << "No execution for the skill " << shownSkill->skillId.toString()
293 << " has been found in the memory. The parametrization cannot be reloaded.";
294 return;
295 }
296 auto params = found->parameters;
297
298 ARMARX_INFO << "Reloading parameters of skill " << found->executionId.skillId;
299 this->aronTreeWidgetController->setFromAron(params);
300 }
301
304 {
305 // create argument aron (if there is an accepted type set)
306 if (aronTreeWidgetController)
307 {
308 return aronTreeWidgetController->convertToAron();
309 }
310 return nullptr;
311 }
312
313 void
315 {
316 auto data = getConfigAsAron();
317 if (!data)
318 {
319 return;
320 }
321
323 QClipboard* clipboard = QApplication::clipboard();
324 clipboard->setText(QString::fromStdString(json.dump(2)));
325 }
326
327 void
329 {
330 if (not shownSkill.has_value())
331 {
332 return;
333 }
334
335 auto data = getConfigAsAron();
336 if (!data)
337 {
338 aron::data::DictPtr placeholderDict = std::make_shared<aron::data::Dict>();
339 placeholderDict->addElement("skill_args");
340 data = placeholderDict;
341 }
342
343
344 auto skillArgsJson =
346 auto skillID = getShownId();
347
348 if (not skillID.has_value())
349 {
350 return;
351 }
352
353 nlohmann::json j;
354
355
356 j["shortcuts"].push_back(
357 {{"skill_id", skillID->toString()}, {"skill_args", skillArgsJson}});
358
359 QClipboard* clipboard = QApplication::clipboard();
360 clipboard->setText(QString::fromStdString(j.dump(2)));
361 }
362
363 void
365 {
366 QClipboard* clipboard = QApplication::clipboard();
367 std::string s = clipboard->text().toStdString();
368 nlohmann::json json = nlohmann::json::parse(s);
369 auto data =
371
372 if (!aronTreeWidgetController)
373 {
374 return;
375 }
376
377 aronTreeWidgetController->setFromAron(data);
378 }
379
380 void
382 {
383 // this will always reset the args to the root profile
384 // good while there is only the root, not good when profiles are properly implemented
385
386
387 if (!shownSkill.has_value())
388 {
389 return;
390 }
391 skills::SkillID& skillId = shownSkill.value().skillId;
392 const auto skills = memory->getSkills();
394
395 // find description
396 // did the provider die?
397 if (skills.count(skillId.providerId.value()) == 0 ||
398 skills.at(skillId.providerId.value()).count(skillId) == 0)
399 return;
400 skills::SkillDescription descr = skills.at(skillId.providerId.value()).at(skillId);
401 this->updateContents(skillId, descr);
402 }
403
404 void
406 {
407 if (checkIfParametersAreModified())
408 {
409 if (shownSkill.has_value())
410 {
412 << "A skill parametrization has been lost in the GUI. It can now be reloaded.";
413 memory->addParametersToHistory(shownSkill->skillId,
414 aronTreeWidgetController->convertToAron());
415 emit updated(shownSkill->skillId);
416 }
417 }
418 this->clear();
419 this->shownSkill = std::nullopt;
420 aronTreeWidgetController = nullptr;
421 }
422
423
424} // namespace armarx::skills::gui
static data::DictPtr ConvertFromNlohmannJSONObject(const nlohmann::json &, const armarx::aron::Path &p={})
static nlohmann::json ConvertToNlohmannJSON(const data::VariantPtr &)
bool isProviderSpecified() const
Definition SkillID.cpp:90
std::optional< ProviderID > providerId
Definition SkillID.h:40
bool isFullySpecified() const
Definition SkillID.cpp:78
MemoryCommunicatorBase(std::shared_ptr< SkillManagerWrapper > _memory)
std::shared_ptr< SkillManagerWrapper > memory
void resizeContents()
Problem: columns 0 and 1 have arbitrary size; so we want to limit their size, to make sure everything...
void updated(const skills::SkillID shownSkill)
void updateContents(skills::SkillID const &skillId, skills::SkillDescription const &descr)
SkillDetailsTreeWidget(std::shared_ptr< SkillManagerWrapper > _memory, QWidget *parent=nullptr)
void updateGui(SkillManagerWrapper::Snapshot update)
std::optional< skills::SkillID > getShownId()
#define ARMARX_CHECK(expression)
Shortcut for ARMARX_CHECK_EXPRESSION.
#define ARMARX_INFO
The normal logging level.
Definition Logging.h:181
#define ARMARX_IMPORTANT
The logging level for always important information, but expected behaviour (in contrast to ARMARX_WAR...
Definition Logging.h:190
#define ARMARX_DEBUG
The logging level for output that is only interesting while debugging.
Definition Logging.h:184
#define ARMARX_WARNING
The logging level for unexpected behaviour, but not a serious problem.
Definition Logging.h:193
std::shared_ptr< Dict > DictPtr
Definition Dict.h:42
This file is part of ArmarX.
aron::data::DictPtr rootProfileDefaults
aron::type::ObjectPtr parametersType