RemoteGuiWidgetController.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 ArmarXGui::gui-plugins::RemoteGuiWidgetController
17 * \author Fabian Paus ( fabian dot paus at kit dot edu )
18 * \date 2018
19 * \copyright http://www.gnu.org/licenses/gpl-2.0.txt
20 * GNU General Public License
21 */
22
24
25#include <QGridLayout>
26#include <QLayout>
27#include <QScrollArea>
28#include <QSpacerItem>
29#include <QStyledItemDelegate>
30
32
35
36namespace armarx
37{
38 void
39 clearLayout(QLayout* layout)
40 {
41 QLayoutItem* item;
42 while ((item = layout->takeAt(0)))
43 {
44 if (item->layout())
45 {
46 clearLayout(item->layout());
47 delete item->layout();
48 }
49 if (item->widget())
50 {
51 delete item->widget();
52 }
53 delete item;
54 }
55 }
56
57 namespace
58 {
59
60 const char* const REMOTE_TAB_ID = "remote.tab_id";
61 const char* const REMOTE_WIDGET_NAME = "remote.widget_name";
62
63 struct InternalUpdateGuard
64 {
65 InternalUpdateGuard(std::atomic<bool>* enabled) : enabled(enabled)
66 {
67 *enabled = true;
68 }
69
70 ~InternalUpdateGuard()
71 {
72 *enabled = false;
73 }
74
75 std::atomic<bool>* enabled;
76 };
77 } // namespace
78
80 {
81 widget.setupUi(getWidget());
82 widget.remoteTabWidget->clear();
83
84 widget.comboBoxTabGroups->setVisible(false);
85 widget.comboBoxTabGroups->addItem(groupAll);
86 widget.comboBoxTabGroups->addItem(groupUngrouped);
87
88 connect(widget.sendRemoteValuesButton,
89 &QPushButton::clicked,
90 this,
92 connect(widget.checkBoxGroupTabs,
93 &QCheckBox::clicked,
94 this,
96 connect(widget.comboBoxTabGroups,
97 QOverload<const QString&>::of(&QComboBox::currentIndexChanged),
98 this,
100
101 connect(this,
103 this,
105 Qt::QueuedConnection);
106 connect(this,
108 this,
110 Qt::QueuedConnection);
111 connect(this,
113 this,
115 Qt::QueuedConnection);
116 connect(this,
118 this,
120 Qt::QueuedConnection);
121
122 widget.comboBoxTabGroups->setCurrentIndex(0);
123 }
124
128
129 QPointer<QDialog>
131 {
132 if (!dialog)
133 {
134 dialog = new SimpleConfigDialog(parent);
135 dialog->addProxyFinder<RemoteGuiInterfacePrx>({"RemoteGuiProvider", "", "RemoteGui*"});
136 }
137 return qobject_cast<SimpleConfigDialog*>(dialog);
138 }
139
140 void
142 {
143 remoteGuiProviderName = dialog->getProxyName("RemoteGuiProvider");
144 }
145
146 void
148 {
149 remoteGuiProviderName =
150 settings->value("remoteGuiProviderName", "RemoteGuiProvider").toString().toStdString();
151 }
152
153 void
155 {
156 settings->setValue("remoteGuiProviderName", QString::fromStdString(remoteGuiProviderName));
157 }
158
159 void
161 {
162 usingProxy(remoteGuiProviderName);
163 }
164
165 void
167 {
168 remoteGuiProvider = getProxy<RemoteGuiInterfacePrx>(remoteGuiProviderName);
169
170 // Setup data structures
171 {
172 std::unique_lock<std::mutex> lock(stateMutex);
173 tabs = remoteGuiProvider->getTabs();
174 tabWidgetStates = remoteGuiProvider->getTabStates();
175 tabValueMap = remoteGuiProvider->getValuesForAllTabs();
176 }
177
178 // Load initial values into GUI
179 emit tabsChanged();
180
181 // Subscribe to updates
182 usingTopic(remoteGuiProvider->getTopicName());
183 }
184
185 void
186 RemoteGuiWidgetController::reportTabChanged(const std::string& tab, const Ice::Current&)
187 {
188 {
189 std::unique_lock<std::mutex> lock(stateMutex);
190 tabs = remoteGuiProvider->getTabs();
191 tabWidgetStates = remoteGuiProvider->getTabStates();
192 tabValueMap = remoteGuiProvider->getValuesForAllTabs();
193 }
194 QString qTabId = QString::fromStdString(tab);
195 emit tabChanged(qTabId);
196 }
197
198 void
200 {
201 // Setup data structures
202 {
203 std::unique_lock<std::mutex> lock(stateMutex);
204 tabs = remoteGuiProvider->getTabs();
205 tabWidgetStates = remoteGuiProvider->getTabStates();
206 tabValueMap = remoteGuiProvider->getValuesForAllTabs();
207 }
208 emit tabsChanged();
209 }
210
211 void
213 const RemoteGui::WidgetStateMap& widgetState,
214 const Ice::Current&)
215 {
216 {
217 std::unique_lock<std::mutex> lock(stateMutex);
218 tabWidgetStates[tab] = widgetState;
219 }
220 QString qTabId = QString::fromStdString(tab);
221 emit widgetChanged(qTabId);
222 }
223
224 void
226 const RemoteGui::ValueMap& delta,
227 const Ice::Current&)
228 {
229 {
230 std::unique_lock<std::mutex> lock(stateMutex);
231 for (const auto& pair : delta)
232 {
233 tabValueMap[tab][pair.first] = pair.second;
234 tabDirtyMap[tab][pair.first] = pair.second;
235 }
236 }
237 QString qTabId = QString::fromStdString(tab);
238 emit stateChanged(qTabId);
239 }
240
241 void
243 {
244 // The root widget of a tab was changed, so we have to create everything from scratch
245 std::string id = RemoteGui::toUtf8(qid);
246
247 std::unique_lock<std::mutex> lock(stateMutex);
248
249 createTab(id);
250 updateWidgets(id);
251
252 removeObsoleteTabs();
253 }
254
255 void
257 {
258 std::unique_lock<std::mutex> lock(stateMutex);
259
260 // Add or update existing tabs
261 for (auto& tabWidget : tabs)
262 {
263 QString qid = QString::fromStdString(tabWidget.first);
264 std::string id = RemoteGui::toUtf8(qid);
265 createTab(id);
266 updateWidgets(id);
267 }
268
269 removeObsoleteTabs();
270 }
271
272 void
274 {
275 // The display options of some widgets changed (e.g. enabled, hidden)
276 std::string id = RemoteGui::toUtf8(qid);
277
278 std::unique_lock<std::mutex> lock(stateMutex);
279
280 updateWidgets(id);
281 }
282
283 void
285 {
286 // The remotely stored values changed => update the displayed values
287 std::string id = RemoteGui::toUtf8(qid);
288 updateState(id);
289 }
290
291 void
293 {
294 if (internalUpdate)
295 {
296 return;
297 }
298
299 QWidget* widget = qobject_cast<QWidget*>(sender());
300 if (!widget)
301 {
302 ARMARX_WARNING << "Expected a widget as sender of onGuiStateChanged()";
303 return;
304 }
305
306 std::string id = widget->property(REMOTE_TAB_ID).toString().toStdString();
307 std::string name = widget->property(REMOTE_WIDGET_NAME).toString().toStdString();
308
309 std::unique_lock<std::mutex> lock(stateMutex);
310
311 auto varinfodump = ARMARX_STREAM_PRINTER
312 {
313 out << '\n'
314 << VAROUT(id) << '\n'
315 << VAROUT(name) << '\n'
316 << VAROUT(GetTypeString(*widget));
317 };
318
319 ARMARX_CHECK_EXPRESSION(tabValueMap.count(id)) << varinfodump;
320 ARMARX_CHECK_EXPRESSION(tabValueMap.at(id).count(name)) << varinfodump;
321 ARMARX_CHECK_EXPRESSION(guiDescriptions.count(id)) << varinfodump;
322 ARMARX_CHECK_EXPRESSION(guiDescriptions.at(id).count(name)) << varinfodump;
323
324 RemoteGui::ValueVariant& currentValue = tabValueMap.at(id).at(name);
325
326 RemoteGui::WidgetPtr const& desc = guiDescriptions.at(id).at(name);
327 RemoteGui::WidgetHandler const& widgetHandler = RemoteGui::getWidgetHandler(desc);
328 RemoteGui::ValueVariant newValue = widgetHandler.handleGuiChange(*desc, widget);
329
330 if (newValue != currentValue)
331 {
332 currentValue = newValue;
333 doAutoUpdate(id);
334 }
335 }
336
337 void
339 {
340 const int index = widget.remoteTabWidget->currentIndex();
341 QString currentTabText = widget.remoteTabWidget->tabText(index);
342 std::string tabId = RemoteGui::toUtf8(currentTabText);
343
344 remoteGuiProvider->setValues(tabId, tabValueMap[tabId]);
345 }
346
347 void
348 RemoteGuiWidgetController::createTab(std::string const& tabId)
349 {
350 RemoteGui::WidgetPtr widgetP = tabs.at(tabId);
352
353 ARMARX_INFO << "Updating GUI for tab '" << tabId << "'";
354
355 // Build UI elements
356 QString qTabId = QString::fromStdString(tabId);
357 QWidget* tabHolder = nullptr;
358 for (const auto& pair : qtTabs)
359 {
360 TabData const& data = pair.second;
361 if (data.fullName() == tabId)
362 {
363 tabHolder = data.w;
364 break;
365 }
366 }
367 QGridLayout* layout = nullptr;
368 if (tabHolder)
369 {
370 // Delete all the children
371 QScrollArea* scrollArea = qobject_cast<QScrollArea*>(tabHolder);
372 if (scrollArea && scrollArea->widget())
373 {
374 layout = qobject_cast<QGridLayout*>(scrollArea->widget()->layout());
375 if (layout)
376 {
377 clearLayout(layout);
378 }
379 }
380 }
381 else
382 {
383 {
384 QScrollArea* scrollArea = new QScrollArea(widget.remoteTabWidget);
385 scrollArea->setObjectName(qTabId);
386 scrollArea->setWidgetResizable(true);
387 layout = new QGridLayout(scrollArea);
388 layout->setContentsMargins(0, 0, 0, 0);
389 QWidget* scrollAreaWidgetContents = new QWidget;
390 scrollArea->setWidget(scrollAreaWidgetContents);
391 scrollAreaWidgetContents->setLayout(layout);
392 tabHolder = scrollArea;
393 }
394
395 auto& data = qtTabs[tabId];
396 data.w = tabHolder;
397 // extract groups
398 {
399 const auto fname = data.fullName();
400 std::size_t idx = fname.find('_');
401 while (idx != std::string::npos)
402 {
403 data.groups.emplace(fname.substr(0, idx));
404 idx = fname.find('_', idx + 1);
405 }
406 }
407 // maybe add tab
408 if (activeGroup == groupAll || (data.groups.empty() && activeGroup == groupUngrouped))
409 {
410 //ungrouped / all group uses full names
411 widget.remoteTabWidget->addTab(tabHolder, qTabId);
412 }
413 else if (data.groups.count(activeGroup))
414 {
415 widget.remoteTabWidget->addTab(tabHolder,
416 QString::fromStdString(data.name(activeGroup)));
417 }
418 // inc group count
419 for (const auto& group : data.groups)
420 {
421 if (tabsPerGroup.count(group))
422 {
423 ++tabsPerGroup.at(group);
424 }
425 else
426 {
427 tabsPerGroup[group] = 1;
428 const auto box = widget.comboBoxTabGroups;
429 box->blockSignals(true);
430 box->addItem(QString::fromStdString(group));
431 box->blockSignals(false);
432 }
433 ARMARX_DEBUG << VAROUT(tabsPerGroup);
434 ARMARX_DEBUG << VAROUT(widget.comboBoxTabGroups->maxVisibleItems());
435 }
436 }
437
438 guiWidgets[tabId].clear();
439 QWidget* newTab = createWidgetFromDescription(tabId, widgetP);
440
441 newTab->setParent(tabHolder);
442 layout->addWidget(newTab, 0, 0);
443 layout->addItem(new QSpacerItem(1, 1, QSizePolicy::Minimum, QSizePolicy::Expanding), 1, 1);
444 layout->addItem(new QSpacerItem(1, 1, QSizePolicy::Expanding, QSizePolicy::Minimum), 0, 1);
445 }
446
447 void
448 RemoteGuiWidgetController::removeObsoleteTabs()
449 {
450 for (auto iter = qtTabs.begin(); iter != qtTabs.end();)
451 {
452 std::string const& tabId = iter->first;
453 TabData const& data = iter->second;
454
455 if (tabs.count(tabId) > 0)
456 {
457 ++iter;
458 continue;
459 }
460
461 ARMARX_INFO << "Removing tab: " << tabId;
462 QString qTabId = QString::fromStdString(tabId);
463
464 QWidget* tabHolder = data.w;
465 tabHolder->deleteLater();
466 QTabWidget* tab = widget.remoteTabWidget;
467 int i = data.findTabIndex(tab);
468 if (i > 0)
469 {
470 tab->removeTab(i);
471 }
472
473 for (const std::string& group : data.groups)
474 {
475 std::size_t& tabCount = tabsPerGroup.at(group);
476 tabCount -= 1;
477 if (tabCount == 0)
478 {
479 tabsPerGroup.erase(group);
480 QComboBox* box = widget.comboBoxTabGroups;
481 int boxIdx = box->findText(QString::fromStdString(group));
482 ARMARX_DEBUG << group << ' ' << VAROUT(boxIdx);
483 if (boxIdx > 1)
484 {
485 box->blockSignals(true);
486 box->setCurrentIndex(0);
487 box->removeItem(boxIdx);
488 box->blockSignals(false);
489 ARMARX_DEBUG << "REMOVING entry " << group << ' ' << VAROUT(boxIdx);
490 }
491 box->setCurrentIndex(0);
492 }
493 }
494
495 iter = qtTabs.erase(iter);
496 guiWidgets.erase(tabId);
497
498 ARMARX_DEBUG << VAROUT(tabsPerGroup);
499 }
500 }
501
502 void
503 RemoteGuiWidgetController::updateWidgets(std::string const& tabId)
504 {
505 // External widget state update received
506 RemoteGui::WidgetStateMap const& states = tabWidgetStates.at(tabId);
507 std::map<std::string, QWidget*> const& widgets = guiWidgets.at(tabId);
508
509 for (auto& nameState : states)
510 {
511 std::string const& name = nameState.first;
512 RemoteGui::WidgetState const& state = nameState.second;
513 QWidget* widget = widgets.at(name);
514
515 widget->setHidden(state.hidden);
516 widget->setDisabled(state.disabled);
517 }
518 }
519
520 void
521 RemoteGuiWidgetController::updateState(std::string const& tabId)
522 {
523 // External state update received
524 std::unique_lock<std::mutex> lock(stateMutex);
525
526 // Do not handle GUI updates triggered by this method (internal update only)
527 InternalUpdateGuard guard(&internalUpdate);
528
529 RemoteGui::ValueMap& values = tabDirtyMap.at(tabId);
530 std::map<std::string, QWidget*> const& widgets = guiWidgets.at(tabId);
531 RemoteGui::WidgetMap const& widgetDesc = guiDescriptions.at(tabId);
532
533 for (auto& pair : values)
534 {
535 std::string const& name = pair.first;
536 RemoteGui::ValueVariant const& value = pair.second;
537 RemoteGui::WidgetPtr const& desc = widgetDesc.at(name);
538 QWidget* widget = widgets.at(name);
539
540 auto& widgetHandler = RemoteGui::getWidgetHandler(desc);
541 widgetHandler.updateGui(*desc, widget, value);
542 }
543 tabDirtyMap.at(tabId).clear();
544 }
545
546 static void
547 setNameProp(QWidget* w, QString const& name, QString const& id)
548 {
549 if (w->property(REMOTE_WIDGET_NAME).toString() == "")
550 {
551 w->setProperty(REMOTE_WIDGET_NAME, name);
552 w->setProperty(REMOTE_TAB_ID, id);
553 for (auto child : w->findChildren<QWidget*>(QString{}, Qt::FindDirectChildrenOnly))
554 {
555 setNameProp(child, name, id);
556 }
557 }
558 }
559
560 QWidget*
561 RemoteGuiWidgetController::createWidgetFromDescription(const std::string& tabId,
562 const RemoteGui::WidgetPtr& desc)
563 {
565 QWidget* widget = nullptr;
566
567 RemoteGui::ValueMap& values = tabValueMap[tabId];
568 bool useDefaultValue = values.count(desc->name) == 0;
569 RemoteGui::ValueVariant const& initialValue =
570 useDefaultValue ? desc->defaultValue : values[desc->name];
571
573 [this, tabId](RemoteGui::WidgetPtr const& desc) -> QWidget*
574 {
576 return this->createWidgetFromDescription(tabId, desc);
577 };
578
579 {
581 // Do not trigger external updates when initial values are set
582 InternalUpdateGuard guard(&internalUpdate);
583
584 auto& widgetHandler = RemoteGui::getWidgetHandler(desc);
585 widget = widgetHandler.createWidget(
586 *desc, initialValue, createChild, this, SLOT(onGuiStateChanged()));
587 }
588
589 ARMARX_CHECK_EXPRESSION(widget != nullptr);
590
591 setNameProp(widget, QString::fromStdString(desc->name), QString::fromStdString(tabId));
592
593 // Widgets without a name cannot be referred to later
594 if (!desc->name.empty())
595 {
597 auto result = guiWidgets[tabId].emplace(desc->name, widget);
598 bool inserted = result.second;
599 if (inserted)
600 {
601 guiDescriptions[tabId][desc->name] = desc;
602 values[desc->name] = initialValue;
603 }
604 else
605 {
607 ARMARX_WARNING << "Tried to add a widget with duplicate name '" << desc->name
608 << "' to remote tab ID '" << tabId << "'";
609 }
610 }
611
612 return widget;
613 }
614
615 void
617 {
618 widget.widgetTopBar->setVisible(false);
619 widget.remoteTabWidget->tabBar()->setVisible(false);
620 }
621
622 void
624 {
625 widget.widgetTopBar->setVisible(true);
626 widget.remoteTabWidget->tabBar()->setVisible(true);
627 }
628
629 void
630 RemoteGuiWidgetController::doAutoUpdate(std::string const& id)
631 {
632 if (widget.autoUpdateCheckBox->checkState() == Qt::Checked)
633 {
634 remoteGuiProvider->setValues(id, tabValueMap[id]);
635 }
636 }
637
638 void
640 {
641 widget.comboBoxTabGroups->setEnabled(checked);
642 widget.comboBoxTabGroups->setVisible(checked);
643 widget.comboBoxTabGroups->setCurrentIndex(0);
644 }
645
646 void
648 {
649 activeGroup = group.toStdString();
650 auto tab = widget.remoteTabWidget;
651 // Remove all without running their destructor
652 while (tab->count())
653 {
654 tab->removeTab(0);
655 }
656 // Readd tabs
657 for (const auto& pair : qtTabs)
658 {
659 TabData const& data = pair.second;
660
661 if (activeGroup == groupAll || (data.groups.empty() && activeGroup == groupUngrouped))
662 {
663 // Ungrouped / all group uses full names
664 tab->addTab(data.w, QString::fromStdString(data.fullName()));
665 }
666 else if (!data.groups.empty() && data.groups.count(activeGroup))
667 {
668 tab->addTab(data.w, QString::fromStdString(data.name(activeGroup)));
669 }
670 }
671 }
672
673 std::string
674 RemoteGuiWidgetController::TabData::fullName() const
675 {
676 return w->objectName().toStdString();
677 }
678
679 std::string
680 RemoteGuiWidgetController::TabData::name(const std::string& pre) const
681 {
682 return fullName().substr(pre.size() + 1);
683 }
684
685 int
686 RemoteGuiWidgetController::TabData::findTabIndex(QTabWidget* tab) const
687 {
688 for (auto idx = 0; idx < tab->count(); ++idx)
689 {
690 if (w == tab->widget(idx))
691 {
692 return idx;
693 }
694 }
695 return -1;
696 }
697} // namespace armarx
uint8_t data[1]
uint8_t index
#define ARMARX_STREAM_PRINTER
use this macro to write output code that is executed when printed and thus not executed if the debug ...
Definition Logging.h:310
#define VAROUT(x)
virtual QPointer< QWidget > getWidget()
getWidget returns a pointer to the a widget of this controller.
bool usingProxy(const std::string &name, const std::string &endpoints="")
Registers a proxy for retrieval after initialization and adds it to the dependency list.
void usingTopic(const std::string &name, bool orderedPublishing=false)
Registers a proxy for subscription after initialization.
Ice::ObjectPrx getProxy(long timeoutMs=0, bool waitForScheduler=true) const
Returns the proxy of this object (optionally it waits for the proxy)
void onInitComponent() override
Pure virtual hook for the subclass.
void reportTabsRemoved(const Ice::Current &) override
void loadSettings(QSettings *settings) override
void saveSettings(QSettings *settings) override
void reportWidgetChanged(const std::string &tab, const RemoteGui::WidgetStateMap &widgetState, const Ice::Current &) override
virtual ~RemoteGuiWidgetController()
Controller destructor.
QPointer< QDialog > getConfigDialog(QWidget *parent=0) override
getConfigDialog returns a pointer to the a configuration widget of this controller.
void onConnectComponent() override
Pure virtual hook for the subclass.
void configured() override
This function must be implemented by the user, if he supplies a config dialog.
void onComboBoxTabGroupsCurrentIndexChanged(const QString &group)
void reportTabChanged(const std::string &tab, const Ice::Current &) override
void reportStateChanged(const std::string &tab, const RemoteGui::ValueMap &delta, const Ice::Current &) override
A config-dialog containing one (or multiple) proxy finders.
#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
#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::function< QWidgetPtr(WidgetPtr const &)> CreateWidgetCallback
std::map< std::string, ValueVariant > ValueMap
std::string toUtf8(QString const &qstring)
const WidgetHandler & getWidgetHandler(WidgetPtr const &desc)
This file offers overloads of toIce() and fromIce() functions for STL container types.
void clearLayout(QLayout *layout)
std::string GetTypeString(const std::type_info &tinf, bool withoutNamespaceSpecifier=false)
std::shared_ptr< Value > value()
Definition cxxopts.hpp:855
virtual RemoteGui::ValueVariant handleGuiChange(Widget const &desc, QWidget *widget) const =0
#define ARMARX_TRACE
Definition trace.h:77