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 
57 // Utilities for grouping topics together
58 
61  return a.name < b.name;
62  }
63 };
64 
79 bool isSubtopic( const std::string &base, const std::string &topic )
80 {
81  std::string error;
82  if ( !ros::names::validate(base, error) )
83  {
84  ROS_ERROR_STREAM("isSubtopic() Invalid basename: " << error);
85  return false;
86  }
87  if ( !ros::names::validate(topic, error) )
88  {
89  ROS_ERROR_STREAM("isSubtopic() Invalid topic: " << error);
90  return false;
91  }
92 
93  std::string query = topic;
94  while ( query != "/" )
95  {
96  if ( query == base )
97  {
98  return true;
99  }
100  query = ros::names::parentNamespace( query );
101  }
102  return false;
103 }
104 
105 struct PluginGroup {
106  struct Info {
107  QStringList topic_suffixes;
108  QStringList datatypes;
109  };
110 
111  QString base_topic;
112  // Map from plugin name to plugin data
113  QMap<QString, Info> plugins;
114 };
115 
116 void getPluginGroups( const QMap<QString, QString> &datatype_plugins,
117  QList<PluginGroup> *groups,
118  QList<ros::master::TopicInfo> *unvisualizable )
119 {
120  ros::master::V_TopicInfo all_topics;
121  ros::master::getTopics( all_topics );
122  std::sort( all_topics.begin(), all_topics.end(), LexicalTopicInfo() );
123  ros::master::V_TopicInfo::iterator topic_it;
124 
125  for ( topic_it = all_topics.begin(); topic_it != all_topics.end(); ++topic_it )
126  {
127  QString topic = QString::fromStdString( topic_it->name );
128  QString datatype = QString::fromStdString( topic_it->datatype );
129 
130  if ( datatype_plugins.contains( datatype ) )
131  {
132  if ( groups->size() == 0 ||
133  !isSubtopic(groups->back().base_topic.toStdString(),
134  topic.toStdString()) )
135  {
136  PluginGroup pi;
137  pi.base_topic = topic;
138  groups->append( pi );
139  }
140 
141  PluginGroup &group = groups->back();
142  QString topic_suffix( "raw" );
143  if ( topic != group.base_topic )
144  {
145  // Remove base_topic and leading slash
146  topic_suffix = topic.right( topic.size() - group.base_topic.size() - 1 );
147  }
148 
149  const QList<QString> &plugin_names =
150  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;
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,
225  Qt::Horizontal );
226 
227  QVBoxLayout* main_layout = new QVBoxLayout;
228  main_layout->addWidget( type_box );
230  {
231  main_layout->addWidget( name_box );
232  }
233  main_layout->addWidget( button_box_ );
234  setLayout( main_layout );
235 
236  //***** Connections
237  connect( display_tree, SIGNAL( itemChanged( SelectionData* )),
238  this, SLOT( onDisplaySelected( SelectionData* )));
239  connect( display_tree, SIGNAL( itemActivated( QTreeWidgetItem*, int )),
240  this, SLOT( accept() ));
241 
242  connect( topic_widget, SIGNAL( itemChanged( SelectionData* )),
243  this, SLOT( onTopicSelected( SelectionData* )));
244  connect( topic_widget, SIGNAL( itemActivated( QTreeWidgetItem*, int )),
245  this, SLOT( accept() ));
246 
247  connect( button_box_, SIGNAL( accepted() ), this, SLOT( accept() ));
248  connect( button_box_, SIGNAL( rejected() ), this, SLOT( reject() ));
249 
250  connect( tab_widget_, SIGNAL( currentChanged( int ) ),
251  this, SLOT( onTabChanged( int ) ));
253  {
254  connect( name_editor_, SIGNAL( textEdited( const QString& )),
255  this, SLOT( onNameChanged() ));
256  }
257 
258  button_box_->button( QDialogButtonBox::Ok )->setEnabled( isValid() );
259 }
260 
262 {
263  return( QSize(500,660) );
264 }
265 
267 {
268  updateDisplay();
269 }
270 
272 {
273  display_data_ = *data;
274  updateDisplay();
275 }
276 
278 {
279  topic_data_ = *data;
280  updateDisplay();
281 }
282 
284 {
285  SelectionData *data = NULL;
286  if ( tab_widget_->currentIndex() == topic_tab_ )
287  {
288  data = &topic_data_;
289  }
290  else if ( tab_widget_->currentIndex() == display_tab_ )
291  {
292  data = &display_data_;
293  }
294  else
295  {
296  ROS_WARN("Unknown tab index: %i", tab_widget_->currentIndex());
297  return;
298  }
299 
300  QString html = "<html><body>" + data->whats_this + "</body></html>";
301  description_->setHtml( html );
302 
303  lookup_name_ = data->lookup_name;
305  {
306  name_editor_->setText( data->display_name );
307  }
308 
309  *topic_output_ = data->topic;
310  *datatype_output_ = data->datatype;
311 
312  button_box_->button( QDialogButtonBox::Ok )->setEnabled( isValid() );
313 }
314 
316 {
317  if( lookup_name_.size() == 0 )
318  {
319  setError( "Select a Display type." );
320  return false;
321  }
323  {
324  QString display_name = name_editor_->text();
325  if( display_name.size() == 0 )
326  {
327  setError( "Enter a name for the display." );
328  return false;
329  }
330  if( disallowed_display_names_.contains( display_name ))
331  {
332  setError( "Name in use. Display names must be unique." );
333  return false;
334  }
335  }
336  setError( "" );
337  return true;
338 }
339 
340 void AddDisplayDialog::setError( const QString& error_text )
341 {
342  button_box_->button( QDialogButtonBox::Ok )->setToolTip( error_text );
343 }
344 
346 {
347  button_box_->button( QDialogButtonBox::Ok )->setEnabled( isValid() );
348 }
349 
351 {
352  if( isValid() )
353  {
356  {
358  }
359  QDialog::accept();
360  }
361 }
362 
364 {
365  setHeaderHidden( true );
366 
367  connect(this, SIGNAL( currentItemChanged( QTreeWidgetItem*, QTreeWidgetItem* )),
368  this, SLOT( onCurrentItemChanged( QTreeWidgetItem*, QTreeWidgetItem* )));
369 }
370 
371 void DisplayTypeTree::onCurrentItemChanged(QTreeWidgetItem *curr,
372  QTreeWidgetItem *prev)
373 {
374  // If display is selected, populate selection data. Otherwise, clear data.
375  SelectionData sd;
376  if ( curr->parent() != NULL )
377  {
378  // Leave topic and datatype blank
379  sd.whats_this = curr->whatsThis( 0 );
380  sd.lookup_name = curr->data( 0, Qt::UserRole).toString();
381  sd.display_name = curr->text( 0 );
382  }
383  Q_EMIT itemChanged( &sd );
384 }
385 
387 {
388  QIcon default_package_icon = loadPixmap( "package://rviz/icons/default_package_icon.png" );
389 
390  QStringList classes = factory->getDeclaredClassIds();
391  classes.sort();
392 
393  // Map from package names to the corresponding top-level tree widget items.
394  std::map<QString, QTreeWidgetItem*> package_items;
395 
396  for( int i = 0; i < classes.size(); i++ )
397  {
398  QString lookup_name = classes[ i ];
399  QString package = factory->getClassPackage( lookup_name );
400  QString description = factory->getClassDescription( lookup_name );
401  QString name = factory->getClassName( lookup_name );
402 
403  QTreeWidgetItem* package_item;
404 
405  std::map<QString, QTreeWidgetItem*>::iterator mi;
406  mi = package_items.find( package );
407  if( mi == package_items.end() )
408  {
409  package_item = new QTreeWidgetItem( this );
410  package_item->setText( 0, package );
411  package_item->setIcon( 0, default_package_icon );
412 
413  package_item->setExpanded( true );
414  package_items[ package ] = package_item;
415  }
416  else
417  {
418  package_item = (*mi).second;
419  }
420  QTreeWidgetItem* class_item = new QTreeWidgetItem( package_item );
421 
422  class_item->setIcon( 0, factory->getIcon( lookup_name ) );
423 
424  class_item->setText( 0, name );
425  class_item->setWhatsThis( 0, description );
426  // Store the lookup name for each class in the UserRole of the item.
427  class_item->setData( 0, Qt::UserRole, lookup_name );
428  }
429 }
430 
432 {
433  tree_ = new QTreeWidget;
434  tree_->setHeaderHidden( true );
435  tree_->setColumnCount( 2 );
436 
437  tree_->header()->setStretchLastSection( false );
438 #if QT_VERSION < QT_VERSION_CHECK(5, 0, 0)
439  tree_->header()->setResizeMode( 0, QHeaderView::Stretch );
440 #else
441  tree_->header()->setSectionResizeMode(0, QHeaderView::Stretch);
442 #endif
443 
444  enable_hidden_box_ = new QCheckBox( "Show unvisualizable topics" );
445  enable_hidden_box_->setCheckState( Qt::Unchecked );
446 
447  QVBoxLayout *layout = new QVBoxLayout;
448  layout->setContentsMargins( QMargins( 0, 0, 0, 0 ) );
449 
450  layout->addWidget( tree_ );
451  layout->addWidget( enable_hidden_box_ );
452 
453  connect( tree_, SIGNAL(currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)),
454  this, SLOT(onCurrentItemChanged(QTreeWidgetItem*)));
455  // Forward signals from tree_
456  connect( tree_, SIGNAL(itemActivated(QTreeWidgetItem*, int)),
457  this, SIGNAL(itemActivated(QTreeWidgetItem*, int)) );
458 
459  // Connect signal from checkbox
460  connect( enable_hidden_box_, SIGNAL(stateChanged(int)),
461  this, SLOT(stateChanged(int)) );
462 
463  setLayout( layout );
464 }
465 
466 void TopicDisplayWidget::onCurrentItemChanged( QTreeWidgetItem* curr )
467 {
468  // If plugin is selected, populate selection data. Otherwise, clear data.
469  SelectionData sd;
470  if ( curr->data( 1, Qt::UserRole ).isValid() )
471  {
472  QTreeWidgetItem *parent = curr->parent();
473  sd.whats_this = curr->whatsThis( 0 );
474 
475  sd.topic = parent->data( 0, Qt::UserRole ).toString();
476  sd.lookup_name = curr->data( 0, Qt::UserRole ).toString();
477  sd.display_name = curr->text( 0 );
478 
479  QComboBox *combo = qobject_cast<QComboBox*>( tree_->itemWidget( curr, 1 ) );
480  if ( combo != NULL )
481  {
482  QString combo_text = combo->currentText();
483  if ( combo_text != "raw" )
484  {
485  sd.topic += "/" + combo_text;
486  }
487  sd.datatype = combo->itemData( combo->currentIndex() ).toString();
488  }
489  else
490  {
491  sd.datatype = curr->data( 1, Qt::UserRole ).toString();
492  }
493  }
494  Q_EMIT itemChanged( &sd );
495 }
496 
497 void TopicDisplayWidget::onComboBoxClicked( QTreeWidgetItem *curr )
498 {
499  tree_->setCurrentItem( curr );
500 }
501 
503 {
504  bool hide_disabled = state == Qt::Unchecked;
505  QTreeWidgetItemIterator it( tree_, QTreeWidgetItemIterator::Disabled );
506  for ( ; *it; ++it )
507  {
508  QTreeWidgetItem *item = *it;
509  item->setHidden( hide_disabled );
510  }
511 }
512 
514 {
515  findPlugins( factory );
516 
517  QList<PluginGroup> groups;
518  QList<ros::master::TopicInfo> unvisualizable;
519  getPluginGroups( datatype_plugins_, &groups, &unvisualizable );
520 
521  // Insert visualizable topics along with their plugins
522  QList<PluginGroup>::const_iterator pg_it;
523  for( pg_it = groups.begin(); pg_it < groups.end(); ++pg_it)
524  {
525  const PluginGroup &pg = *pg_it;
526 
527  QTreeWidgetItem *item = insertItem( pg.base_topic, false );
528  item->setData( 0, Qt::UserRole, pg.base_topic );
529 
530  QMap<QString, PluginGroup::Info>::const_iterator it;
531  for (it = pg.plugins.begin(); it != pg.plugins.end(); ++it)
532  {
533  const QString plugin_name = it.key();
534  const PluginGroup::Info &info = it.value();
535  QTreeWidgetItem *row = new QTreeWidgetItem( item );
536 
537  row->setText( 0, factory->getClassName( plugin_name ) );
538  row->setIcon( 0, factory->getIcon( plugin_name ) );
539  row->setWhatsThis( 0, factory->getClassDescription( plugin_name ) );
540  row->setData( 0, Qt::UserRole, plugin_name );
541  row->setData( 1, Qt::UserRole, info.datatypes[0] );
542 
543  if ( info.topic_suffixes.size() > 1 )
544  {
545  EmbeddableComboBox *box = new EmbeddableComboBox( row, 1 );
546  connect( box, SIGNAL( itemClicked( QTreeWidgetItem*, int )),
547  this, SLOT( onComboBoxClicked( QTreeWidgetItem* )));
548  for ( int i = 0; i < info.topic_suffixes.size(); ++i)
549  {
550  box->addItem( info.topic_suffixes[i], info.datatypes[i] );
551  }
552  tree_->setItemWidget( row, 1, box );
553  tree_->setColumnWidth( 1, std::max( tree_->columnWidth( 1 ), box->width() ));
554  }
555  }
556  }
557 
558  // Insert unvisualizable topics
559  for ( int i = 0; i < unvisualizable.size(); ++i )
560  {
561  const ros::master::TopicInfo &ti = unvisualizable.at( i );
562  QTreeWidgetItem *item = insertItem( QString::fromStdString( ti.name ),
563  true );
564  }
565 
566  // Hide unvisualizable topics if necessary
567  stateChanged( enable_hidden_box_->isChecked() );
568 }
569 
571 {
572  // Build map from topic type to plugin by instantiating every plugin we have.
573  QStringList lookup_names = factory->getDeclaredClassIds();
574 
575  QStringList::iterator it;
576  for (it = lookup_names.begin(); it != lookup_names.end(); ++it)
577  {
578  const QString &lookup_name = *it;
579  // ROS_INFO("Class: %s", lookup_name.toStdString().c_str());
580 
581  QSet<QString> topic_types = factory->getMessageTypes( lookup_name );
582  Q_FOREACH( QString topic_type, topic_types )
583  {
584  // ROS_INFO("Type: %s", topic_type.toStdString().c_str());
585  datatype_plugins_.insertMulti( topic_type, lookup_name );
586  }
587  }
588 }
589 
590 QTreeWidgetItem* TopicDisplayWidget::insertItem( const QString &topic,
591  bool disabled )
592 {
593  QTreeWidgetItem *current = tree_->invisibleRootItem();;
594  QStringList parts = topic.split( "/" );
595 
596  for ( int part_ind = 1; part_ind < parts.size(); ++part_ind )
597  {
598  QString part = "/" + parts[part_ind];
599  // If any child matches, use that one.
600  bool match = false;
601  for ( int c = 0; c < current->childCount(); ++c )
602  {
603  QTreeWidgetItem *child = current->child( c );
604  if ( child->text( 0 ) == part && !child->data( 1, Qt::UserRole ).isValid() )
605  {
606  match = true;
607  current = child;
608  break;
609  }
610  }
611  // If no match, create a new child.
612  if ( !match )
613  {
614  QTreeWidgetItem *new_child = new QTreeWidgetItem( current );
615  // Only expand first few levels of the tree
616  new_child->setExpanded( 3 > part_ind );
617  new_child->setText( 0, part );
618  new_child->setDisabled( disabled );
619  current = new_child;
620 
621  }
622  }
623  return current;
624 }
625 
626 } // rviz
void onComboBoxClicked(QTreeWidgetItem *curr)
#define NULL
Definition: global.h:37
QMap< QString, Info > plugins
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=0, QString *topic_output=0, QString *datatype_output=0, QWidget *parent=0)
void onCurrentItemChanged(QTreeWidgetItem *curr)
string package
virtual QString getClassName(const QString &class_id) const =0
ROSCPP_DECL std::string parentNamespace(const std::string &name)
void fillTree(Factory *factory)
void onDisplaySelected(SelectionData *data)
ROSCPP_DECL bool getTopics(V_TopicInfo &topics)
ROSCPP_DECL bool validate(const std::string &name, std::string &error)
virtual QString getClassName(const QString &class_id) const
virtual QIcon getIcon(const QString &class_id) const
std::vector< TopicInfo > V_TopicInfo
virtual QStringList getDeclaredClassIds()
#define ROS_WARN(...)
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
virtual QString getClassDescription(const QString &class_id) const =0
const QStringList & disallowed_display_names_
Widget for selecting a display by topic.
void onCurrentItemChanged(QTreeWidgetItem *curr, QTreeWidgetItem *prev)
void onTopicSelected(SelectionData *data)
virtual QSize sizeHint() const
void findPlugins(DisplayFactory *)
Abstract superclass representing the ability to get a list of class IDs and the ability to get name...
Definition: factory.h:43
virtual QString getClassDescription(const QString &class_id) const
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)
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 Apr 27 2019 02:33:41