NJointControllersWidget.cpp
Go to the documentation of this file.
1/*
2 * This file is part of ArmarX.
3 *
4 * ArmarX is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License version 2 as
6 * published by the Free Software Foundation.
7 *
8 * ArmarX is distributed in the hope that it will be useful, but
9 * WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 *
16 * \package RobotAPI::gui-plugins::RobotUnitPlugin
17 * \author Raphael Grimm ( raphael dot grimm at kit dot edu )
18 * \date 2017
19 * \copyright http://www.gnu.org/licenses/gpl-2.0.txt
20 * GNU General Public License
21 */
23
25
26#include "StyleSheets.h"
27
28namespace armarx
29{
31 RobotUnitWidgetTemplateBase("NJointControllersWidget", parent)
32 {
33 ui->treeWidget->setColumnCount(11);
34 QTreeWidgetItem* head = ui->treeWidget->headerItem();
35
36 head->setText(idxName, "Name");
37 head->setText(idxClass, "Class");
38 head->setText(idxActive, "Act");
39 head->setText(idxRequested, "Req");
40 head->setText(idxError, "Err");
41 head->setText(idxInternal, "Int");
42 head->setText(idxCtrlDev, "Devices");
43 head->setText(idxCtrlMode, "Modes");
44 head->setText(idxActivate, "");
45 head->setText(idxDeactivate, "");
46 head->setText(idxDelete, "");
47
48 head->setToolTip(idxName,
49 "The controllers instance name (green: ok, red: active and requested "
50 "state missmatch or there is an error)");
51 head->setToolTip(idxClass, "The controllers class name");
52 head->setToolTip(idxActive,
53 "Whether the controller is active (green: ok, red: active and requested "
54 "state missmatch)");
55 head->setToolTip(idxRequested,
56 "Whether the controller is requested (green: ok, red: active and "
57 "requested state missmatch)");
58 head->setToolTip(idxError,
59 "Whether the controller is deactivated due to an error (green: ok, red: "
60 "there is an error)");
61 head->setToolTip(idxInternal,
62 "Whether the controller is internal (e.g. used by the kinematic unit)");
63 head->setToolTip(idxCtrlDev, "The control devices used by this controller");
64 head->setToolTip(idxCtrlMode,
65 "The controll mode for the control devices used by this controller");
66 head->setToolTip(idxActivate, "Button to activate the Controller");
67 head->setToolTip(idxDeactivate, "Button to deactivate the Controller");
68 head->setToolTip(idxDelete, "Button to delete the Controller");
69
70 ui->treeWidget->setColumnWidth(idxName, 400);
71 ui->treeWidget->setColumnWidth(idxClass, 200);
72 ui->treeWidget->setColumnWidth(idxCtrlDev, 130);
73 ui->treeWidget->setColumnWidth(idxCtrlMode, 150);
74 ui->treeWidget->setColumnWidth(idxActivate, 40);
75 ui->treeWidget->setColumnWidth(idxDeactivate, 40);
76 ui->treeWidget->setColumnWidth(idxDelete, 40);
77 ui->treeWidget->setColumnWidth(idxRequested, 40);
78 ui->treeWidget->setColumnWidth(idxActive, 40);
79 ui->treeWidget->setColumnWidth(idxError, 40);
80 ui->treeWidget->setColumnWidth(idxInternal, 40);
81
82 ui->treeWidget->header()->setResizeMode(idxActivate, QHeaderView::Fixed);
83 ui->treeWidget->header()->setResizeMode(idxDeactivate, QHeaderView::Fixed);
84 ui->treeWidget->header()->setResizeMode(idxDelete, QHeaderView::Fixed);
85 ui->treeWidget->header()->setResizeMode(idxRequested, QHeaderView::Fixed);
86 ui->treeWidget->header()->setResizeMode(idxActive, QHeaderView::Fixed);
87 ui->treeWidget->header()->setResizeMode(idxError, QHeaderView::Fixed);
88 ui->treeWidget->header()->setResizeMode(idxInternal, QHeaderView::Fixed);
89
90 ui->pushButtonStopAll->setIcon(QIcon{QString{":/icons/media-playback-pause.ico"}});
91 ui->pushButtonRemoveAll->setIcon(QIcon{QString{":/icons/Trash.svg"}});
92 connect(
93 ui->pushButtonStopAll, SIGNAL(clicked()), this, SLOT(onPushButtonStopAll_clicked()));
94 connect(ui->pushButtonRemoveAll,
95 SIGNAL(clicked()),
96 this,
97 SLOT(onPushButtonRemoveAll_clicked()));
98 }
99
104
105 void
107 const NJointControllerStatusSeq& allStatus)
108 {
109 std::unique_lock<std::recursive_timed_mutex> guard{mutex, std::defer_lock};
110 if (!guard.try_lock_for(std::chrono::microseconds(100)))
111 {
112 return;
113 }
114 for (const auto& status : allStatus)
115 {
116 if (statusUpdates[status.instanceName].timestampUSec < status.timestampUSec)
117 {
118 statusUpdates[status.instanceName] = status;
119 if (doMetaCall)
120 {
121 QMetaObject::invokeMethod(this, "updateContent", Qt::QueuedConnection);
122 }
123 }
124 }
125 }
126
127 void
128 NJointControllersWidget::add(const NJointControllerDescriptionWithStatus& ds)
129 {
130 if (!entries.count(ds.description.instanceName))
131 {
132 entries[ds.description.instanceName] =
133 new NJointControllersWidgetEntry(*this, *(ui->treeWidget), ds.description);
134 }
135 entries.at(ds.description.instanceName)->update(ds.status);
136 }
137
138 void
139 NJointControllersWidget::onPushButtonStopAll_clicked()
140 {
141 if (robotUnit)
142 {
143 robotUnit->switchNJointControllerSetup({});
144 }
145 }
146
147 void
148 NJointControllersWidget::onPushButtonRemoveAll_clicked()
149 {
150 for (auto& pair : entries)
151 {
152 try
153 {
154 if (pair.second->deletable)
155 {
156 pair.second->deleteController();
157 }
158 }
159 catch (...)
160 {
161 }
162 }
163 }
164
165 void
167 {
169 std::unique_lock<std::recursive_timed_mutex> guard{mutex};
170 if (isResetting || !robotUnit || entries.count(name))
171 {
172 return;
173 }
174 ru = robotUnit;
175 guard.unlock();
176 auto data = ru->getNJointControllerDescriptionWithStatus(name);
177 guard.lock();
178 if (!entries.count(name))
179 {
180 controllersCreated[name] = std::move(data);
181 if (doMetaCall)
182 {
183 QMetaObject::invokeMethod(this, "updateContent", Qt::QueuedConnection);
184 }
185 }
186 }
187
188 void
190 {
191 std::unique_lock<std::recursive_timed_mutex> guard{mutex};
192 controllersDeleted.emplace(name);
193 if (doMetaCall)
194 {
195 QMetaObject::invokeMethod(this, "updateContent", Qt::QueuedConnection);
196 }
197 }
198
199 void
201 {
202 ui->treeWidget->setColumnWidth(idxName, settings->value("ctrlColWName", 400).toInt());
203 ui->treeWidget->setColumnWidth(idxClass, settings->value("ctrlColWClass", 200).toInt());
204 ui->treeWidget->setColumnWidth(idxCtrlDev, settings->value("ctrlColWCDen", 130).toInt());
205 ui->treeWidget->setColumnWidth(idxCtrlMode, settings->value("ctrlColWCMod", 150).toInt());
206 }
207
208 void
210 {
211 settings->setValue("ctrlColWName", ui->treeWidget->columnWidth(idxName));
212 settings->setValue("ctrlColWClass", ui->treeWidget->columnWidth(idxClass));
213 settings->setValue("ctrlColWCDen", ui->treeWidget->columnWidth(idxCtrlDev));
214 settings->setValue("ctrlColWCMod", ui->treeWidget->columnWidth(idxCtrlMode));
215 }
216
217 void
219 {
220 entries.clear();
221 controllersCreated.clear();
222 statusUpdates.clear();
223 controllersDeleted.clear();
224 }
225
226 void
228 {
229 for (const auto& pair : controllersCreated)
230 {
231 add(pair.second);
232 }
233 controllersCreated.clear();
234 for (const auto& pair : statusUpdates)
235 {
236 const auto& name = pair.second.instanceName;
237 if (controllersDeleted.count(name))
238 {
239 continue;
240 }
241 auto it = entries.find(name);
242 if (it != entries.end())
243 {
244 it->second->update(pair.second);
245 }
246 else
247 {
248 NJointControllerDescriptionWithStatus stat;
249 stat.status = pair.second;
250 ARMARX_INFO << "loding description for the missing entry '" << name << "'";
251 stat.description = robotUnit->getNJointControllerDescription(name);
252 add(stat);
253 }
254 }
255 statusUpdates.clear();
256 for (const auto& name : controllersDeleted)
257 {
258 auto it = entries.find(name);
259 if (it != entries.end())
260 {
261 it->second->deleteContent();
262 it->second->deleteLater();
263 entries.erase(it);
264 }
265 }
266 controllersDeleted.clear();
267 }
268
269 void
271 {
272 auto temp = robotUnit->getNJointControllerDescriptionsWithStatuses();
273 {
274 std::unique_lock<std::recursive_timed_mutex> guard{mutex};
275 for (NJointControllerDescriptionWithStatus& ds : temp)
276 {
277 controllersCreated[ds.description.instanceName] = std::move(ds);
278 }
279 }
280 }
281
282 bool
284 {
285 if (controllersCreated.empty())
286 {
287 return true;
288 }
289 add(controllersCreated.begin()->second);
290 controllersCreated.erase(controllersCreated.begin());
291 return false;
292 }
293
296 QTreeWidget& treeWidget,
297 const NJointControllerDescription& desc) :
298 QObject(&parent), deletable{desc.deletable}, controller{desc.controller}
299 {
301 header = new QTreeWidgetItem;
302 treeWidget.addTopLevelItem(header);
303 header->setText(NJointControllersWidget::idxName,
304 QString::fromStdString(desc.instanceName));
305 header->setText(NJointControllersWidget::idxClass, QString::fromStdString(desc.className));
306 header->setText(NJointControllersWidget::idxActive, "?");
307 header->setText(NJointControllersWidget::idxRequested, "?");
308 header->setText(NJointControllersWidget::idxError, "?");
309 header->setText(NJointControllersWidget::idxInternal, desc.internal ? "X" : " ");
310
311 const std::size_t numDev = desc.controlModeAssignment.size();
312 const std::size_t numMod =
313 getMapValues<StringStringDictionary, std::set>(desc.controlModeAssignment).size();
314 //device list
315 {
316 //buttons
317 QWidget* widgDev = new QWidget{&parent};
318 QWidget* widgMod = new QWidget{&parent};
319 treeWidget.setItemWidget(header, NJointControllersWidget::idxCtrlDev, widgDev);
320 treeWidget.setItemWidget(header, NJointControllersWidget::idxCtrlMode, widgMod);
321 QVBoxLayout* layDev = new QVBoxLayout;
322 QVBoxLayout* layMod = new QVBoxLayout;
323 layDev->setContentsMargins(0, 0, 0, 0);
324 layMod->setContentsMargins(0, 0, 0, 0);
325 widgDev->setLayout(layDev);
326 widgMod->setLayout(layMod);
327 boxDev = new QCheckBox{QString::number(numDev) + " Device" +
328 (numDev > 1 ? QString{"s"} : QString{""})};
329 boxMod = new QCheckBox{QString::number(numMod) + " Mode" +
330 (numMod > 1 ? QString{"s"} : QString{""})};
331 layDev->addWidget(boxDev);
332 layMod->addWidget(boxMod);
333 boxDev->setStyleSheet(checkboxStyleSheet());
334 boxMod->setStyleSheet(checkboxStyleSheet());
335 boxDev->setChecked(false);
336 boxMod->setChecked(false);
337 connect(boxDev, SIGNAL(clicked(bool)), this, SLOT(hideDeviceList()));
338 connect(boxMod, SIGNAL(clicked(bool)), this, SLOT(hideDeviceList()));
339 //list
340 devsToModes.reserve(desc.controlModeAssignment.size());
341 for (const auto& pair : desc.controlModeAssignment)
342 {
343 QTreeWidgetItem* child = new QTreeWidgetItem;
344 header->addChild(child);
346 QString::fromStdString(pair.first));
348 QString::fromStdString(pair.second));
349 devsToModes.emplace_back(child);
350 child->setHidden(true);
351 }
352 }
353 QPushButton* act = new QPushButton;
354 QPushButton* dec = new QPushButton;
355 act->setToolTip("Activate the Controller");
356 dec->setToolTip("Deactivate the Controller");
357 treeWidget.setItemWidget(header, NJointControllersWidget::idxActivate, act);
358 treeWidget.setItemWidget(header, NJointControllersWidget::idxDeactivate, dec);
359 act->setIcon(QIcon{QString{":/icons/media-playback-start.ico"}});
360 dec->setIcon(QIcon{QString{":/icons/media-playback-pause.ico"}});
361 act->setFixedWidth(40);
362 dec->setFixedWidth(40);
363 act->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Minimum);
364 dec->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Minimum);
365 connect(act, SIGNAL(clicked(bool)), this, SLOT(activateController()));
366 connect(dec, SIGNAL(clicked(bool)), this, SLOT(deactivateController()));
367 if (desc.deletable)
368 {
369 QPushButton* del = new QPushButton;
370 treeWidget.setItemWidget(header, NJointControllersWidget::idxDelete, del);
371 del->setToolTip("Delete the Controller");
372 del->setIcon(QIcon{QString{":/icons/Trash.svg"}});
373 del->setFixedWidth(40);
374 del->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Minimum);
375 connect(del, SIGNAL(clicked(bool)), this, SLOT(deleteController()));
376 }
377
378 //remote functions
379 {
380 for (const auto& descr : controller->getFunctionDescriptions())
381 {
383 treeWidget,
384 *header,
385 descr.first,
387 descr.second}; //adds it self to the widget
388 }
389 }
390 }
391
392 void
393 NJointControllersWidgetEntry::update(const NJointControllerStatus& status)
394 {
395 header->setText(NJointControllersWidget::idxActive, status.active ? "X" : " ");
396 header->setText(NJointControllersWidget::idxRequested, status.requested ? "X" : " ");
397 header->setText(NJointControllersWidget::idxError, status.error ? "X" : " ");
398 auto acReqColor = (status.active != status.requested) ? red() : green();
399 header->setBackground(NJointControllersWidget::idxActive, {acReqColor});
400 header->setBackground(NJointControllersWidget::idxRequested, {acReqColor});
401 header->setBackground(NJointControllersWidget::idxError, {status.error ? red() : green()});
402 //name
403 header->setBackground(NJointControllersWidget::idxName,
404 {status.error ? red() : acReqColor});
405 }
406
407 bool
409 {
410 return header->text(NJointControllersWidget::idxName).contains(name, Qt::CaseInsensitive);
411 }
412
413 bool
415 {
416 return header->text(NJointControllersWidget::idxClass).contains(name, Qt::CaseInsensitive);
417 }
418
419 bool
421 {
422 return header->text(NJointControllersWidget::idxActive)
423 .contains(state, Qt::CaseInsensitive);
424 }
425
426 bool
428 {
429 return header->text(NJointControllersWidget::idxRequested)
430 .contains(state, Qt::CaseInsensitive);
431 }
432
433 bool
435 {
436 return header->text(NJointControllersWidget::idxError).contains(state, Qt::CaseInsensitive);
437 }
438
439 bool
441 {
442 for (const auto& elem : devsToModes)
443 {
444 if (elem->text(NJointControllersWidget::idxCtrlDev).contains(dev, Qt::CaseInsensitive))
445 {
446 return true;
447 }
448 }
449 return false;
450 }
451
452 bool
454 {
455 for (const auto& elem : devsToModes)
456 {
458 .contains(mode, Qt::CaseInsensitive))
459 {
460 return true;
461 }
462 }
463 return false;
464 }
465
466 void
468 {
469 for (auto item : devsToModes)
470 {
471 delete item;
472 }
473 delete header;
474 }
475
476 void
478 {
479 setDeviceListVisible(vis);
480 header->setHidden(!vis);
481 }
482
483 void
484 NJointControllersWidgetEntry::activateController()
485 {
487 controller->activateController();
488 }
489
490 void
491 NJointControllersWidgetEntry::deactivateController()
492 {
494 controller->deactivateController();
495 }
496
497 void
498 NJointControllersWidgetEntry::deleteController()
499 {
500 ARMARX_CHECK_EXPRESSION(controller);
501 controller->deleteController();
502 }
503
504 void
505 NJointControllersWidgetEntry::hideDeviceList()
506 {
507 QCheckBox* box = dynamic_cast<QCheckBox*>(sender());
508 if (box)
509 {
510 bool checked = box->isChecked();
511 boxDev->setChecked(checked);
512 boxMod->setChecked(checked);
513 for (QTreeWidgetItem* dev : devsToModes)
514 {
515 dev->setHidden(!checked);
516 }
517 if (checked)
518 {
519 header->setExpanded(true);
520 }
521 }
522 }
523
524 void
525 NJointControllersWidgetEntry::setDeviceListVisible(bool vis)
526 {
527 boxDev->setChecked(vis);
528 boxMod->setChecked(vis);
529 for (QTreeWidgetItem* dev : devsToModes)
530 {
531 dev->setHidden(!vis);
532 }
533 }
534
536 QTreeWidget& treeWidget,
537 QTreeWidgetItem& header,
538 const std::string& name,
539 const NJointControllerInterfacePrx& ctrl,
541 functionName{name}, ctrl{ctrl}
542 {
543 //tree widget item
544 {
545 setObjectName(QString::fromStdString(ctrl->getInstanceName() + "_" + name));
546 functionHeader = new QTreeWidgetItem;
547 header.addChild(functionHeader);
548 functionHeader->setFirstColumnSpanned(true);
549 treeWidget.setItemWidget(functionHeader, 0, this);
550
551 QHBoxLayout* l = new QHBoxLayout;
552 setLayout(l);
553 l->setContentsMargins(0, 0, 0, 0);
554 //exec button
555 {
556 QPushButton* execute = new QPushButton;
557 execute->setIcon(QIcon{QString{":/icons/media-playback-start.ico"}});
558 execute->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Minimum);
559 execute->setFixedWidth(40);
560 execute->setToolTip("Execute '" + QString::fromStdString(name) + "' function");
561 connect(execute, SIGNAL(clicked(bool)), this, SLOT(execFunction()));
562 l->addWidget(execute);
563 }
564 //param change box
565 execOnParamChange = new QCheckBox{"Call function on parameter changes"};
566 execOnParamChange->setToolTip("Execute '" + QString::fromStdString(name) +
567 "' function whenever a parameter changes");
568 l->addWidget(execOnParamChange);
569 if (!w)
570 {
571 //this way we always have the correct horizontal spacing
572 execOnParamChange->setFixedHeight(0);
573 execOnParamChange->setSizePolicy(execOnParamChange->sizePolicy().horizontalPolicy(),
574 QSizePolicy::Fixed);
575 }
576 //name
577 l->addWidget(new QLabel{"Remote function: " + QString::fromStdString(name)});
578 //spacer
579 l->addSpacerItem(new QSpacerItem{0, 0, QSizePolicy::Expanding});
580 }
581 //add widget as child
582 if (w)
583 {
584 QTreeWidgetItem* fnitem = new QTreeWidgetItem;
585 functionHeader->addChild(fnitem);
586 fnitem->setFirstColumnSpanned(true);
587 QWidget* container = new QWidget;
588 treeWidget.setItemWidget(fnitem, 0, container);
589 QHBoxLayout* compress = new QHBoxLayout;
590 compress->setContentsMargins(0, 0, 0, 0);
591 container->setLayout(compress);
592 //params
594 paramValues = params->getVariants();
595 params->setContentsMargins(0, 0, 0, 0);
596 compress->addWidget(params);
597 //add spacer
598 compress->addItem(new QSpacerItem{
599 0, 0, QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding});
600 }
601 }
602
603 void
604 NJointControllersWidgetRemoteFunction::execFunction()
605 {
606 if (paramValues.empty())
607 {
608 ARMARX_INFO << deactivateSpam(1, functionName) << "calling function " << functionName
609 << " with no parameters\n";
610 }
611 else
612 {
613 std::stringstream ss;
614 ss << "calling function " << functionName << " with parameters:\n";
615 for (const auto& pair : paramValues)
616 {
617 if (pair.second)
618 {
619
620 if (pair.second->data)
621 {
622 ss << " '" << pair.first << "' of type " << pair.second->data->ice_id()
623 << "\n";
624 }
625 else
626 {
627 ss << " '" << pair.first << "' nullptr data \n";
628 }
629 }
630 else
631 {
632 ss << " '" << pair.first << "' nullptr\n";
633 }
634 }
635 ARMARX_INFO << deactivateSpam(1, functionName) << ss.str();
636 }
637 ctrl->callDescribedFunction(functionName, paramValues);
638 }
639
640 void
642 {
643 paramValues[std::move(name)] = std::move(value);
644 if (execOnParamChange->isChecked())
645 {
646 execFunction();
647 }
648 }
649} // namespace armarx
SpamFilterDataPtr deactivateSpam(SpamFilterDataPtr const &spamFilter, float deactivationDurationSec, const std::string &identifier, bool deactivate)
Definition Logging.cpp:75
void update(const NJointControllerStatus &status)
NJointControllersWidgetEntry(NJointControllersWidget &parent, QTreeWidget &treeWidget, const NJointControllerDescription &desc)
void valueChangedSlot(std::string name, VariantBasePtr value) override
NJointControllersWidgetRemoteFunction(QTreeWidget &treeWidget, QTreeWidgetItem &header, const std::string &functionName, const NJointControllerInterfacePrx &ctrl, const WidgetDescription::WidgetPtr &w)
virtual void nJointControllerDeleted(std::string name)
void loadSettings(QSettings *settings) override
void saveSettings(QSettings *settings) override
virtual void nJointControllerCreated(std::string name)
void nJointControllerStatusChanged(const NJointControllerStatusSeq &allStatus)
RobotUnitInterfacePrx robotUnit
std::recursive_timed_mutex mutex
#define ARMARX_CHECK_EXPRESSION(expression)
This macro evaluates the expression and if it turns out to be false it will throw an ExpressionExcept...
#define ARMARX_INFO
The normal logging level.
Definition Logging.h:181
DescribedWidgetBase * makeDescribedWidget(const WidgetPtr &p, ValueChangedListenerInterface *listener)
::IceInternal::Handle<::armarx::WidgetDescription::Widget > WidgetPtr
This file offers overloads of toIce() and fromIce() functions for STL container types.
::IceInternal::ProxyHandle<::IceProxy::armarx::RobotUnitInterface > RobotUnitInterfacePrx
QColor green()
Definition StyleSheets.h:72
QString checkboxStyleSheet()
Definition StyleSheets.h:31
QColor red()
Definition StyleSheets.h:78
void getMapValues(const MapType &map, OutputIteratorType it)
Definition algorithm.h:194
::IceInternal::Handle<::armarx::VariantBase > VariantBasePtr