add_display_dialog.cpp
Go to the documentation of this file.
1 /*
2  * Copyright (c) 2008, Willow Garage, Inc.
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions are met:
7  *
8  * * Redistributions of source code must retain the above copyright
9  * notice, this list of conditions and the following disclaimer.
10  * * Redistributions in binary form must reproduce the above copyright
11  * notice, this list of conditions and the following disclaimer in the
12  * documentation and/or other materials provided with the distribution.
13  * * Neither the name of the Willow Garage, Inc. nor the names of its
14  * contributors may be used to endorse or promote products derived from
15  * this software without specific prior written permission.
16  *
17  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
18  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
21  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
22  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
25  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27  * POSSIBILITY OF SUCH DAMAGE.
28  */
29 
30 #include <map>
31 
32 #include <boost/filesystem.hpp>
33 
34 #include <ros/package.h>
35 #include <ros/ros.h>
36 
37 #include <QGroupBox>
38 #include <QLabel>
39 #include <QLineEdit>
40 #include <QTextBrowser>
41 #include <QVBoxLayout>
42 #include <QDialogButtonBox>
43 #include <QPushButton>
44 #include <QTabWidget>
45 #include <QCheckBox>
46 #include <QComboBox>
47 #include <QHeaderView>
48 
49 #include "add_display_dialog.h"
50 #include "rviz/load_resource.h"
51 
52 #include "display_factory.h"
53 
54 namespace rviz
55 {
56 // Utilities for grouping topics together
57 
59 {
61  {
62  return a.name < b.name;
63  }
64 };
65 
80 bool isSubtopic(const std::string& base, const std::string& topic)
81 {
82  std::string error;
83  if (!ros::names::validate(base, error))
84  {
85  ROS_ERROR_STREAM("isSubtopic() Invalid basename: " << error);
86  return false;
87  }
88  if (!ros::names::validate(topic, error))
89  {
90  ROS_ERROR_STREAM("isSubtopic() Invalid topic: " << error);
91  return false;
92  }
93 
94  std::string query = topic;
95  while (query != "/")
96  {
97  if (query == base)
98  {
99  return true;
100  }
101  query = ros::names::parentNamespace(query);
102  }
103  return false;
104 }
105 
107 {
108  struct Info
109  {
110  QStringList topic_suffixes;
111  QStringList datatypes;
112  };
113 
114  QString base_topic;
115  // Map from plugin name to plugin data
116  QMap<QString, Info> plugins;
117 };
118 
119 void getPluginGroups(const QMap<QString, QString>& datatype_plugins,
120  QList<PluginGroup>* groups,
121  QList<ros::master::TopicInfo>* unvisualizable)
122 {
123  ros::master::V_TopicInfo all_topics;
124  ros::master::getTopics(all_topics);
125  std::sort(all_topics.begin(), all_topics.end(), LexicalTopicInfo());
126  ros::master::V_TopicInfo::iterator topic_it;
127 
128  for (topic_it = all_topics.begin(); topic_it != all_topics.end(); ++topic_it)
129  {
130  QString topic = QString::fromStdString(topic_it->name);
131  QString datatype = QString::fromStdString(topic_it->datatype);
132 
133  if (datatype_plugins.contains(datatype))
134  {
135  if (groups->empty() || !isSubtopic(groups->back().base_topic.toStdString(), topic.toStdString()))
136  {
137  PluginGroup pi;
138  pi.base_topic = topic;
139  groups->append(pi);
140  }
141 
142  PluginGroup& group = groups->back();
143  QString topic_suffix("raw");
144  if (topic != group.base_topic)
145  {
146  // Remove base_topic and leading slash
147  topic_suffix = topic.right(topic.size() - group.base_topic.size() - 1);
148  }
149 
150  const QList<QString>& plugin_names = datatype_plugins.values(datatype);
151  for (int i = 0; i < plugin_names.size(); ++i)
152  {
153  const QString& name = plugin_names[i];
154  PluginGroup::Info& info = group.plugins[name];
155  info.topic_suffixes.append(topic_suffix);
156  info.datatypes.append(datatype);
157  }
158  }
159  else
160  {
161  unvisualizable->append(*topic_it);
162  }
163  }
164 }
165 
166 // Dialog implementation
168  const QString& /*object_type*/,
169  const QStringList& disallowed_display_names,
170  const QStringList& disallowed_class_lookup_names,
171  QString* lookup_name_output,
172  QString* display_name_output,
173  QString* topic_output,
174  QString* datatype_output,
175  QWidget* parent)
176  : QDialog(parent)
177  , factory_(factory)
178  , disallowed_display_names_(disallowed_display_names)
179  , disallowed_class_lookup_names_(disallowed_class_lookup_names)
180  , lookup_name_output_(lookup_name_output)
181  , display_name_output_(display_name_output)
182  , topic_output_(topic_output)
183  , datatype_output_(datatype_output)
184 {
185  //***** Layout
186 
187  // Display Type group
188  QGroupBox* type_box = new QGroupBox("Create visualization");
189 
190  QLabel* description_label = new QLabel("Description:");
191  description_ = new QTextBrowser;
192  description_->setMaximumHeight(100);
193  description_->setOpenExternalLinks(true);
194 
195  DisplayTypeTree* display_tree = new DisplayTypeTree;
196  display_tree->fillTree(factory);
197 
198  TopicDisplayWidget* topic_widget = new TopicDisplayWidget;
199  topic_widget->fill(factory);
200 
201  tab_widget_ = new QTabWidget;
202  display_tab_ = tab_widget_->addTab(display_tree, tr("By display type"));
203  topic_tab_ = tab_widget_->addTab(topic_widget, tr("By topic"));
204 
205  QVBoxLayout* type_layout = new QVBoxLayout;
206  type_layout->addWidget(tab_widget_);
207  type_layout->addWidget(description_label);
208  type_layout->addWidget(description_);
209 
210  type_box->setLayout(type_layout);
211 
212  // Display Name group
213  QGroupBox* name_box = nullptr;
215  {
216  name_box = new QGroupBox("Display Name");
217  name_editor_ = new QLineEdit;
218  QVBoxLayout* name_layout = new QVBoxLayout;
219  name_layout->addWidget(name_editor_);
220  name_box->setLayout(name_layout);
221  }
222 
223  // Buttons
224  button_box_ = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, Qt::Horizontal);
225 
226  QVBoxLayout* main_layout = new QVBoxLayout;
227  main_layout->addWidget(type_box);
229  {
230  main_layout->addWidget(name_box);
231  }
232  main_layout->addWidget(button_box_);
233  setLayout(main_layout);
234 
235  //***** Connections
236  connect(display_tree, SIGNAL(itemChanged(SelectionData*)), this,
238  connect(display_tree, SIGNAL(itemActivated(QTreeWidgetItem*, int)), this, SLOT(accept()));
239 
240  connect(topic_widget, SIGNAL(itemChanged(SelectionData*)), this, SLOT(onTopicSelected(SelectionData*)));
241  connect(topic_widget, SIGNAL(itemActivated(QTreeWidgetItem*, int)), this, SLOT(accept()));
242 
243  connect(button_box_, SIGNAL(accepted()), this, SLOT(accept()));
244  connect(button_box_, SIGNAL(rejected()), this, SLOT(reject()));
245 
246  connect(tab_widget_, SIGNAL(currentChanged(int)), this, SLOT(onTabChanged(int)));
248  {
249  connect(name_editor_, SIGNAL(textEdited(const QString&)), this, SLOT(onNameChanged()));
250  }
251 
252  button_box_->button(QDialogButtonBox::Ok)->setEnabled(isValid());
253 }
254 
256 {
257  return (QSize(500, 660));
258 }
259 
261 {
262  updateDisplay();
263 }
264 
266 {
267  display_data_ = *data;
268  updateDisplay();
269 }
270 
272 {
273  topic_data_ = *data;
274  updateDisplay();
275 }
276 
278 {
279  SelectionData* data = nullptr;
280  if (tab_widget_->currentIndex() == topic_tab_)
281  {
282  data = &topic_data_;
283  }
284  else if (tab_widget_->currentIndex() == display_tab_)
285  {
286  data = &display_data_;
287  }
288  else
289  {
290  ROS_WARN("Unknown tab index: %i", tab_widget_->currentIndex());
291  return;
292  }
293 
294  QString html = "<html><body>" + data->whats_this + "</body></html>";
295  description_->setHtml(html);
296 
297  lookup_name_ = data->lookup_name;
299  {
300  name_editor_->setText(data->display_name);
301  }
302 
303  *topic_output_ = data->topic;
304  *datatype_output_ = data->datatype;
305 
306  button_box_->button(QDialogButtonBox::Ok)->setEnabled(isValid());
307 }
308 
310 {
311  if (lookup_name_.size() == 0)
312  {
313  setError("Select a Display type.");
314  return false;
315  }
317  {
318  QString display_name = name_editor_->text();
319  if (display_name.size() == 0)
320  {
321  setError("Enter a name for the display.");
322  return false;
323  }
324  if (disallowed_display_names_.contains(display_name))
325  {
326  setError("Name in use. Display names must be unique.");
327  return false;
328  }
329  }
330  setError("");
331  return true;
332 }
333 
334 void AddDisplayDialog::setError(const QString& error_text)
335 {
336  button_box_->button(QDialogButtonBox::Ok)->setToolTip(error_text);
337 }
338 
340 {
341  button_box_->button(QDialogButtonBox::Ok)->setEnabled(isValid());
342 }
343 
345 {
346  if (isValid())
347  {
350  {
352  }
353  QDialog::accept();
354  }
355 }
356 
358 {
359  setHeaderHidden(true);
360 
361  connect(this, SIGNAL(currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)), this,
362  SLOT(onCurrentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)));
363 }
364 
365 void DisplayTypeTree::onCurrentItemChanged(QTreeWidgetItem* curr, QTreeWidgetItem* /*prev*/)
366 {
367  // If display is selected, populate selection data. Otherwise, clear data.
368  SelectionData sd;
369  if (curr->parent() != nullptr)
370  {
371  // Leave topic and datatype blank
372  sd.whats_this = curr->whatsThis(0);
373  sd.lookup_name = curr->data(0, Qt::UserRole).toString();
374  sd.display_name = curr->text(0);
375  }
376  Q_EMIT itemChanged(&sd);
377 }
378 
380 {
381  QIcon default_package_icon = loadPixmap("package://rviz/icons/default_package_icon.png");
382 
383  QStringList classes = factory->getDeclaredClassIds();
384  classes.sort();
385 
386  // Map from package names to the corresponding top-level tree widget items.
387  std::map<QString, QTreeWidgetItem*> package_items;
388 
389  for (int i = 0; i < classes.size(); i++)
390  {
391  QString lookup_name = classes[i];
392  QString package = factory->getClassPackage(lookup_name);
393  QString description = factory->getClassDescription(lookup_name);
394  QString name = factory->getClassName(lookup_name);
395 
396  QTreeWidgetItem* package_item;
397 
398  std::map<QString, QTreeWidgetItem*>::iterator mi;
399  mi = package_items.find(package);
400  if (mi == package_items.end())
401  {
402  package_item = new QTreeWidgetItem(this);
403  package_item->setText(0, package);
404  package_item->setIcon(0, default_package_icon);
405 
406  package_item->setExpanded(true);
407  package_items[package] = package_item;
408  }
409  else
410  {
411  package_item = (*mi).second;
412  }
413  QTreeWidgetItem* class_item = new QTreeWidgetItem(package_item);
414 
415  class_item->setIcon(0, factory->getIcon(lookup_name));
416 
417  class_item->setText(0, name);
418  class_item->setWhatsThis(0, description);
419  // Store the lookup name for each class in the UserRole of the item.
420  class_item->setData(0, Qt::UserRole, lookup_name);
421  }
422 }
423 
425 {
426  tree_ = new QTreeWidget;
427  tree_->setHeaderHidden(true);
428  tree_->setColumnCount(2);
429 
430  tree_->header()->setStretchLastSection(false);
431 #if QT_VERSION < QT_VERSION_CHECK(5, 0, 0)
432  tree_->header()->setResizeMode(0, QHeaderView::Stretch);
433 #else
434  tree_->header()->setSectionResizeMode(0, QHeaderView::Stretch);
435 #endif
436 
437  enable_hidden_box_ = new QCheckBox("Show unvisualizable topics");
438  enable_hidden_box_->setCheckState(Qt::Unchecked);
439 
440  QVBoxLayout* layout = new QVBoxLayout;
441  layout->setContentsMargins(QMargins(0, 0, 0, 0));
442 
443  layout->addWidget(tree_);
444  layout->addWidget(enable_hidden_box_);
445 
446  connect(tree_, SIGNAL(currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)), this,
447  SLOT(onCurrentItemChanged(QTreeWidgetItem*)));
448  // Forward signals from tree_
449  connect(tree_, SIGNAL(itemActivated(QTreeWidgetItem*, int)), this,
450  SIGNAL(itemActivated(QTreeWidgetItem*, int)));
451 
452  // Connect signal from checkbox
453  connect(enable_hidden_box_, SIGNAL(stateChanged(int)), this, SLOT(stateChanged(int)));
454 
455  setLayout(layout);
456 }
457 
458 void TopicDisplayWidget::onCurrentItemChanged(QTreeWidgetItem* curr)
459 {
460  // If plugin is selected, populate selection data. Otherwise, clear data.
461  SelectionData sd;
462  if (curr->data(1, Qt::UserRole).isValid())
463  {
464  QTreeWidgetItem* parent = curr->parent();
465  sd.whats_this = curr->whatsThis(0);
466 
467  sd.topic = parent->data(0, Qt::UserRole).toString();
468  sd.lookup_name = curr->data(0, Qt::UserRole).toString();
469  sd.display_name = curr->text(0);
470 
471  QComboBox* combo = qobject_cast<QComboBox*>(tree_->itemWidget(curr, 1));
472  if (combo != nullptr)
473  {
474  QString combo_text = combo->currentText();
475  if (combo_text != "raw")
476  {
477  sd.topic += "/" + combo_text;
478  }
479  sd.datatype = combo->itemData(combo->currentIndex()).toString();
480  }
481  else
482  {
483  sd.datatype = curr->data(1, Qt::UserRole).toString();
484  }
485  }
486  Q_EMIT itemChanged(&sd);
487 }
488 
489 void TopicDisplayWidget::onComboBoxClicked(QTreeWidgetItem* curr)
490 {
491  tree_->setCurrentItem(curr);
492 }
493 
495 {
496  bool hide_disabled = state == Qt::Unchecked;
497  QTreeWidgetItemIterator it(tree_, QTreeWidgetItemIterator::Disabled);
498  for (; *it; ++it)
499  {
500  QTreeWidgetItem* item = *it;
501  item->setHidden(hide_disabled);
502  }
503 }
504 
506 {
507  findPlugins(factory);
508 
509  QList<PluginGroup> groups;
510  QList<ros::master::TopicInfo> unvisualizable;
511  getPluginGroups(datatype_plugins_, &groups, &unvisualizable);
512 
513  // Insert visualizable topics along with their plugins
514  QList<PluginGroup>::const_iterator pg_it;
515  for (pg_it = groups.begin(); pg_it < groups.end(); ++pg_it)
516  {
517  const PluginGroup& pg = *pg_it;
518 
519  QTreeWidgetItem* item = insertItem(pg.base_topic, false);
520  item->setData(0, Qt::UserRole, pg.base_topic);
521 
522  QMap<QString, PluginGroup::Info>::const_iterator it;
523  for (it = pg.plugins.begin(); it != pg.plugins.end(); ++it)
524  {
525  const QString plugin_name = it.key();
526  const PluginGroup::Info& info = it.value();
527  QTreeWidgetItem* row = new QTreeWidgetItem(item);
528 
529  row->setText(0, factory->getClassName(plugin_name));
530  row->setIcon(0, factory->getIcon(plugin_name));
531  row->setWhatsThis(0, factory->getClassDescription(plugin_name));
532  row->setData(0, Qt::UserRole, plugin_name);
533  row->setData(1, Qt::UserRole, info.datatypes[0]);
534 
535  if (info.topic_suffixes.size() > 1)
536  {
537  EmbeddableComboBox* box = new EmbeddableComboBox(row, 1);
538  connect(box, SIGNAL(itemClicked(QTreeWidgetItem*, int)), this,
539  SLOT(onComboBoxClicked(QTreeWidgetItem*)));
540  for (int i = 0; i < info.topic_suffixes.size(); ++i)
541  {
542  box->addItem(info.topic_suffixes[i], info.datatypes[i]);
543  }
544  tree_->setItemWidget(row, 1, box);
545  tree_->setColumnWidth(1, std::max(tree_->columnWidth(1), box->width()));
546  }
547  }
548  }
549 
550  // Insert unvisualizable topics
551  for (int i = 0; i < unvisualizable.size(); ++i)
552  {
553  const ros::master::TopicInfo& ti = unvisualizable.at(i);
554  insertItem(QString::fromStdString(ti.name), true);
555  }
556 
557  // Hide unvisualizable topics if necessary
558  stateChanged(enable_hidden_box_->isChecked());
559 }
560 
562 {
563  // Build map from topic type to plugin by instantiating every plugin we have.
564  QStringList lookup_names = factory->getDeclaredClassIds();
565 
566  QStringList::iterator it;
567  for (it = lookup_names.begin(); it != lookup_names.end(); ++it)
568  {
569  const QString& lookup_name = *it;
570  // ROS_INFO("Class: %s", lookup_name.toStdString().c_str());
571 
572  QSet<QString> topic_types = factory->getMessageTypes(lookup_name);
573  Q_FOREACH (QString topic_type, topic_types)
574  {
575  // ROS_INFO("Type: %s", topic_type.toStdString().c_str());
576  datatype_plugins_.insertMulti(topic_type, lookup_name);
577  }
578  }
579 }
580 
581 QTreeWidgetItem* TopicDisplayWidget::insertItem(const QString& topic, bool disabled)
582 {
583  QTreeWidgetItem* current = tree_->invisibleRootItem();
584  ;
585  QStringList parts = topic.split("/");
586 
587  for (int part_ind = 1; part_ind < parts.size(); ++part_ind)
588  {
589  QString part = "/" + parts[part_ind];
590  // If any child matches, use that one.
591  bool match = false;
592  for (int c = 0; c < current->childCount(); ++c)
593  {
594  QTreeWidgetItem* child = current->child(c);
595  if (child->text(0) == part && !child->data(1, Qt::UserRole).isValid())
596  {
597  match = true;
598  current = child;
599  break;
600  }
601  }
602  // If no match, create a new child.
603  if (!match)
604  {
605  QTreeWidgetItem* new_child = new QTreeWidgetItem(current);
606  // Only expand first few levels of the tree
607  new_child->setExpanded(3 > part_ind);
608  new_child->setText(0, part);
609  new_child->setDisabled(disabled);
610  current = new_child;
611  }
612  }
613  return current;
614 }
615 
616 } // namespace rviz
void onComboBoxClicked(QTreeWidgetItem *curr)
QMap< QString, Info > plugins
void onCurrentItemChanged(QTreeWidgetItem *curr)
string package
QStringList getDeclaredClassIds() override
virtual QString getClassName(const QString &class_id) const =0
ROSCPP_DECL std::string parentNamespace(const std::string &name)
void fillTree(Factory *factory)
QString getClassDescription(const QString &class_id) const override
void onDisplaySelected(SelectionData *data)
ROSCPP_DECL bool getTopics(V_TopicInfo &topics)
ROSCPP_DECL bool validate(const std::string &name, std::string &error)
std::vector< TopicInfo > V_TopicInfo
#define ROS_WARN(...)
QSize sizeHint() const override
virtual QSet< QString > getMessageTypes(const QString &class_id)
Get all supported message types for the given class id.
bool isSubtopic(const std::string &base, const std::string &topic)
void fill(DisplayFactory *factory)
description
const char * datatype()
AddDisplayDialog(DisplayFactory *factory, const QString &object_type, const QStringList &disallowed_display_names, const QStringList &disallowed_class_lookup_names, QString *lookup_name_output, QString *display_name_output=nullptr, QString *topic_output=nullptr, QString *datatype_output=nullptr, QWidget *parent=nullptr)
virtual QString getClassDescription(const QString &class_id) const =0
const QStringList & disallowed_display_names_
Widget for selecting a display by topic.
QString getClassName(const QString &class_id) const override
void onCurrentItemChanged(QTreeWidgetItem *curr, QTreeWidgetItem *prev)
void onTopicSelected(SelectionData *data)
Abstract superclass representing the ability to get a list of class IDs and the ability to get name...
Definition: factory.h:42
void findPlugins(DisplayFactory *factory)
void setError(const QString &error_text)
virtual QIcon getIcon(const QString &class_id) const =0
void getPluginGroups(const QMap< QString, QString > &datatype_plugins, QList< PluginGroup > *groups, QList< ros::master::TopicInfo > *unvisualizable)
QTreeWidgetItem * insertItem(const QString &topic, bool disabled)
#define ROS_ERROR_STREAM(args)
virtual QStringList getDeclaredClassIds()=0
QPixmap loadPixmap(QString url, bool fill_cache)
QIcon getIcon(const QString &class_id) const override
Widget for selecting a display by display type.
QDialogButtonBox * button_box_
bool operator()(const ros::master::TopicInfo &a, const ros::master::TopicInfo &b)


rviz
Author(s): Dave Hershberger, David Gossow, Josh Faust
autogenerated on Sat May 27 2023 02:06:24