00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00029
00030 #include <map>
00031
00032 #include <boost/filesystem.hpp>
00033
00034 #include <ros/package.h>
00035 #include <ros/ros.h>
00036
00037 #include <QGroupBox>
00038 #include <QLabel>
00039 #include <QLineEdit>
00040 #include <QTextBrowser>
00041 #include <QVBoxLayout>
00042 #include <QDialogButtonBox>
00043 #include <QPushButton>
00044 #include <QTabWidget>
00045 #include <QCheckBox>
00046 #include <QComboBox>
00047 #include <QHeaderView>
00048
00049 #include "add_display_dialog.h"
00050 #include "rviz/load_resource.h"
00051
00052 #include "display_factory.h"
00053
00054 namespace rviz
00055 {
00056
00057
00058
00059 struct LexicalTopicInfo {
00060 bool operator()(const ros::master::TopicInfo &a, const ros::master::TopicInfo &b) {
00061 return a.name < b.name;
00062 }
00063 };
00064
00079 bool isSubtopic( const std::string &base, const std::string &topic )
00080 {
00081 std::string error;
00082 if ( !ros::names::validate(base, error) )
00083 {
00084 ROS_ERROR_STREAM("isSubtopic() Invalid basename: " << error);
00085 return false;
00086 }
00087 if ( !ros::names::validate(topic, error) )
00088 {
00089 ROS_ERROR_STREAM("isSubtopic() Invalid topic: " << error);
00090 return false;
00091 }
00092
00093 std::string query = topic;
00094 while ( query != "/" )
00095 {
00096 if ( query == base )
00097 {
00098 return true;
00099 }
00100 query = ros::names::parentNamespace( query );
00101 }
00102 return false;
00103 }
00104
00105 struct PluginGroup {
00106 struct Info {
00107 QStringList topic_suffixes;
00108 QStringList datatypes;
00109 };
00110
00111 QString base_topic;
00112
00113 QMap<QString, Info> plugins;
00114 };
00115
00116 void getPluginGroups( const QMap<QString, QString> &datatype_plugins,
00117 QList<PluginGroup> *groups,
00118 QList<ros::master::TopicInfo> *unvisualizable )
00119 {
00120 ros::master::V_TopicInfo all_topics;
00121 ros::master::getTopics( all_topics );
00122 std::sort( all_topics.begin(), all_topics.end(), LexicalTopicInfo() );
00123 ros::master::V_TopicInfo::iterator topic_it;
00124
00125 for ( topic_it = all_topics.begin(); topic_it != all_topics.end(); ++topic_it )
00126 {
00127 QString topic = QString::fromStdString( topic_it->name );
00128 QString datatype = QString::fromStdString( topic_it->datatype );
00129
00130 if ( datatype_plugins.contains( datatype ) )
00131 {
00132 if ( groups->size() == 0 ||
00133 !isSubtopic(groups->back().base_topic.toStdString(),
00134 topic.toStdString()) )
00135 {
00136 PluginGroup pi;
00137 pi.base_topic = topic;
00138 groups->append( pi );
00139 }
00140
00141 PluginGroup &group = groups->back();
00142 QString topic_suffix( "raw" );
00143 if ( topic != group.base_topic )
00144 {
00145
00146 topic_suffix = topic.right( topic.size() - group.base_topic.size() - 1 );
00147 }
00148
00149 const QList<QString> &plugin_names =
00150 datatype_plugins.values( datatype );
00151 for ( int i = 0; i < plugin_names.size(); ++i )
00152 {
00153 const QString &name = plugin_names[i];
00154 PluginGroup::Info &info = group.plugins[name];
00155 info.topic_suffixes.append( topic_suffix );
00156 info.datatypes.append( datatype );
00157 }
00158 }
00159 else
00160 {
00161 unvisualizable->append( *topic_it );
00162 }
00163 }
00164 }
00165
00166
00167 AddDisplayDialog::AddDisplayDialog( DisplayFactory* factory,
00168 const QString& object_type,
00169 const QStringList& disallowed_display_names,
00170 const QStringList& disallowed_class_lookup_names,
00171 QString* lookup_name_output,
00172 QString* display_name_output,
00173 QString* topic_output,
00174 QString* datatype_output,
00175 QWidget* parent )
00176 : QDialog( parent )
00177 , factory_( factory )
00178 , disallowed_display_names_( disallowed_display_names )
00179 , disallowed_class_lookup_names_( disallowed_class_lookup_names )
00180 , lookup_name_output_( lookup_name_output )
00181 , display_name_output_( display_name_output )
00182 , topic_output_( topic_output )
00183 , datatype_output_( datatype_output )
00184 {
00185
00186
00187
00188 QGroupBox* type_box = new QGroupBox( "Create visualization" );
00189
00190 QLabel* description_label = new QLabel( "Description:" );
00191 description_ = new QTextBrowser;
00192 description_->setMaximumHeight( 100 );
00193 description_->setOpenExternalLinks( true );
00194
00195 DisplayTypeTree *display_tree = new DisplayTypeTree;
00196 display_tree->fillTree(factory);
00197
00198 TopicDisplayWidget *topic_widget = new TopicDisplayWidget;
00199 topic_widget->fill(factory);
00200
00201 tab_widget_ = new QTabWidget;
00202 display_tab_ = tab_widget_->addTab( display_tree, tr("By display type") );
00203 topic_tab_ = tab_widget_->addTab( topic_widget, tr("By topic") );
00204
00205 QVBoxLayout *type_layout = new QVBoxLayout;
00206 type_layout->addWidget( tab_widget_ );
00207 type_layout->addWidget( description_label );
00208 type_layout->addWidget( description_ );
00209
00210 type_box->setLayout( type_layout );
00211
00212
00213 QGroupBox* name_box;
00214 if( display_name_output_ )
00215 {
00216 name_box = new QGroupBox( "Display Name" );
00217 name_editor_ = new QLineEdit;
00218 QVBoxLayout* name_layout = new QVBoxLayout;
00219 name_layout->addWidget( name_editor_ );
00220 name_box->setLayout( name_layout );
00221 }
00222
00223
00224 button_box_ = new QDialogButtonBox( QDialogButtonBox::Ok | QDialogButtonBox::Cancel,
00225 Qt::Horizontal );
00226
00227 QVBoxLayout* main_layout = new QVBoxLayout;
00228 main_layout->addWidget( type_box );
00229 if( display_name_output_ )
00230 {
00231 main_layout->addWidget( name_box );
00232 }
00233 main_layout->addWidget( button_box_ );
00234 setLayout( main_layout );
00235
00236
00237 connect( display_tree, SIGNAL( itemChanged( SelectionData* )),
00238 this, SLOT( onDisplaySelected( SelectionData* )));
00239 connect( display_tree, SIGNAL( itemActivated( QTreeWidgetItem*, int )),
00240 this, SLOT( accept() ));
00241
00242 connect( topic_widget, SIGNAL( itemChanged( SelectionData* )),
00243 this, SLOT( onTopicSelected( SelectionData* )));
00244 connect( topic_widget, SIGNAL( itemActivated( QTreeWidgetItem*, int )),
00245 this, SLOT( accept() ));
00246
00247 connect( button_box_, SIGNAL( accepted() ), this, SLOT( accept() ));
00248 connect( button_box_, SIGNAL( rejected() ), this, SLOT( reject() ));
00249
00250 connect( tab_widget_, SIGNAL( currentChanged( int ) ),
00251 this, SLOT( onTabChanged( int ) ));
00252 if( display_name_output_ )
00253 {
00254 connect( name_editor_, SIGNAL( textEdited( const QString& )),
00255 this, SLOT( onNameChanged() ));
00256 }
00257
00258 button_box_->button( QDialogButtonBox::Ok )->setEnabled( isValid() );
00259 }
00260
00261 QSize AddDisplayDialog::sizeHint () const
00262 {
00263 return( QSize(500,660) );
00264 }
00265
00266 void AddDisplayDialog::onTabChanged( int index )
00267 {
00268 updateDisplay();
00269 }
00270
00271 void AddDisplayDialog::onDisplaySelected( SelectionData *data)
00272 {
00273 display_data_ = *data;
00274 updateDisplay();
00275 }
00276
00277 void AddDisplayDialog::onTopicSelected( SelectionData *data )
00278 {
00279 topic_data_ = *data;
00280 updateDisplay();
00281 }
00282
00283 void AddDisplayDialog::updateDisplay()
00284 {
00285 SelectionData *data = NULL;
00286 if ( tab_widget_->currentIndex() == topic_tab_ )
00287 {
00288 data = &topic_data_;
00289 }
00290 else if ( tab_widget_->currentIndex() == display_tab_ )
00291 {
00292 data = &display_data_;
00293 }
00294 else
00295 {
00296 ROS_WARN("Unknown tab index: %i", tab_widget_->currentIndex());
00297 return;
00298 }
00299
00300 QString html = "<html><body>" + data->whats_this + "</body></html>";
00301 description_->setHtml( html );
00302
00303 lookup_name_ = data->lookup_name;
00304 if( display_name_output_ )
00305 {
00306 name_editor_->setText( data->display_name );
00307 }
00308
00309 *topic_output_ = data->topic;
00310 *datatype_output_ = data->datatype;
00311
00312 button_box_->button( QDialogButtonBox::Ok )->setEnabled( isValid() );
00313 }
00314
00315 bool AddDisplayDialog::isValid()
00316 {
00317 if( lookup_name_.size() == 0 )
00318 {
00319 setError( "Select a Display type." );
00320 return false;
00321 }
00322 if( display_name_output_ )
00323 {
00324 QString display_name = name_editor_->text();
00325 if( display_name.size() == 0 )
00326 {
00327 setError( "Enter a name for the display." );
00328 return false;
00329 }
00330 if( disallowed_display_names_.contains( display_name ))
00331 {
00332 setError( "Name in use. Display names must be unique." );
00333 return false;
00334 }
00335 }
00336 setError( "" );
00337 return true;
00338 }
00339
00340 void AddDisplayDialog::setError( const QString& error_text )
00341 {
00342 button_box_->button( QDialogButtonBox::Ok )->setToolTip( error_text );
00343 }
00344
00345 void AddDisplayDialog::onNameChanged()
00346 {
00347 button_box_->button( QDialogButtonBox::Ok )->setEnabled( isValid() );
00348 }
00349
00350 void AddDisplayDialog::accept()
00351 {
00352 if( isValid() )
00353 {
00354 *lookup_name_output_ = lookup_name_;
00355 if( display_name_output_ )
00356 {
00357 *display_name_output_ = name_editor_->text();
00358 }
00359 QDialog::accept();
00360 }
00361 }
00362
00363 DisplayTypeTree::DisplayTypeTree()
00364 {
00365 setHeaderHidden( true );
00366
00367 connect(this, SIGNAL( currentItemChanged( QTreeWidgetItem*, QTreeWidgetItem* )),
00368 this, SLOT( onCurrentItemChanged( QTreeWidgetItem*, QTreeWidgetItem* )));
00369 }
00370
00371 void DisplayTypeTree::onCurrentItemChanged(QTreeWidgetItem *curr,
00372 QTreeWidgetItem *prev)
00373 {
00374
00375 SelectionData sd;
00376 if ( curr->parent() != NULL )
00377 {
00378
00379 sd.whats_this = curr->whatsThis( 0 );
00380 sd.lookup_name = curr->data( 0, Qt::UserRole).toString();
00381 sd.display_name = curr->text( 0 );
00382 }
00383 Q_EMIT itemChanged( &sd );
00384 }
00385
00386 void DisplayTypeTree::fillTree( Factory *factory )
00387 {
00388 QIcon default_package_icon = loadPixmap( "package://rviz/icons/default_package_icon.png" );
00389
00390 QStringList classes = factory->getDeclaredClassIds();
00391 classes.sort();
00392
00393
00394 std::map<QString, QTreeWidgetItem*> package_items;
00395
00396 for( int i = 0; i < classes.size(); i++ )
00397 {
00398 QString lookup_name = classes[ i ];
00399 QString package = factory->getClassPackage( lookup_name );
00400 QString description = factory->getClassDescription( lookup_name );
00401 QString name = factory->getClassName( lookup_name );
00402
00403 QTreeWidgetItem* package_item;
00404
00405 std::map<QString, QTreeWidgetItem*>::iterator mi;
00406 mi = package_items.find( package );
00407 if( mi == package_items.end() )
00408 {
00409 package_item = new QTreeWidgetItem( this );
00410 package_item->setText( 0, package );
00411 package_item->setIcon( 0, default_package_icon );
00412
00413 package_item->setExpanded( true );
00414 package_items[ package ] = package_item;
00415 }
00416 else
00417 {
00418 package_item = (*mi).second;
00419 }
00420 QTreeWidgetItem* class_item = new QTreeWidgetItem( package_item );
00421
00422 class_item->setIcon( 0, factory->getIcon( lookup_name ) );
00423
00424 class_item->setText( 0, name );
00425 class_item->setWhatsThis( 0, description );
00426
00427 class_item->setData( 0, Qt::UserRole, lookup_name );
00428 }
00429 }
00430
00431 TopicDisplayWidget::TopicDisplayWidget()
00432 {
00433 tree_ = new QTreeWidget;
00434 tree_->setHeaderHidden( true );
00435 tree_->setColumnCount( 2 );
00436
00437 tree_->header()->setStretchLastSection( false );
00438 #if QT_VERSION < QT_VERSION_CHECK(5, 0, 0)
00439 tree_->header()->setResizeMode( 0, QHeaderView::Stretch );
00440 #else
00441 tree_->header()->setSectionResizeMode(0, QHeaderView::Stretch);
00442 #endif
00443
00444 enable_hidden_box_ = new QCheckBox( "Show unvisualizable topics" );
00445 enable_hidden_box_->setCheckState( Qt::Unchecked );
00446
00447 QVBoxLayout *layout = new QVBoxLayout;
00448 layout->setContentsMargins( QMargins( 0, 0, 0, 0 ) );
00449
00450 layout->addWidget( tree_ );
00451 layout->addWidget( enable_hidden_box_ );
00452
00453 connect( tree_, SIGNAL(currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)),
00454 this, SLOT(onCurrentItemChanged(QTreeWidgetItem*)));
00455
00456 connect( tree_, SIGNAL(itemActivated(QTreeWidgetItem*, int)),
00457 this, SIGNAL(itemActivated(QTreeWidgetItem*, int)) );
00458
00459
00460 connect( enable_hidden_box_, SIGNAL(stateChanged(int)),
00461 this, SLOT(stateChanged(int)) );
00462
00463 setLayout( layout );
00464 }
00465
00466 void TopicDisplayWidget::onCurrentItemChanged( QTreeWidgetItem* curr )
00467 {
00468
00469 SelectionData sd;
00470 if ( curr->data( 1, Qt::UserRole ).isValid() )
00471 {
00472 QTreeWidgetItem *parent = curr->parent();
00473 sd.whats_this = curr->whatsThis( 0 );
00474
00475 sd.topic = parent->data( 0, Qt::UserRole ).toString();
00476 sd.lookup_name = curr->data( 0, Qt::UserRole ).toString();
00477 sd.display_name = curr->text( 0 );
00478
00479 QComboBox *combo = qobject_cast<QComboBox*>( tree_->itemWidget( curr, 1 ) );
00480 if ( combo != NULL )
00481 {
00482 QString combo_text = combo->currentText();
00483 if ( combo_text != "raw" )
00484 {
00485 sd.topic += "/" + combo_text;
00486 }
00487 sd.datatype = combo->itemData( combo->currentIndex() ).toString();
00488 }
00489 else
00490 {
00491 sd.datatype = curr->data( 1, Qt::UserRole ).toString();
00492 }
00493 }
00494 Q_EMIT itemChanged( &sd );
00495 }
00496
00497 void TopicDisplayWidget::onComboBoxClicked( QTreeWidgetItem *curr )
00498 {
00499 tree_->setCurrentItem( curr );
00500 }
00501
00502 void TopicDisplayWidget::stateChanged( int state )
00503 {
00504 bool hide_disabled = state == Qt::Unchecked;
00505 QTreeWidgetItemIterator it( tree_, QTreeWidgetItemIterator::Disabled );
00506 for ( ; *it; ++it )
00507 {
00508 QTreeWidgetItem *item = *it;
00509 item->setHidden( hide_disabled );
00510 }
00511 }
00512
00513 void TopicDisplayWidget::fill( DisplayFactory *factory )
00514 {
00515 findPlugins( factory );
00516
00517 QList<PluginGroup> groups;
00518 QList<ros::master::TopicInfo> unvisualizable;
00519 getPluginGroups( datatype_plugins_, &groups, &unvisualizable );
00520
00521
00522 QList<PluginGroup>::const_iterator pg_it;
00523 for( pg_it = groups.begin(); pg_it < groups.end(); ++pg_it)
00524 {
00525 const PluginGroup &pg = *pg_it;
00526
00527 QTreeWidgetItem *item = insertItem( pg.base_topic, false );
00528 item->setData( 0, Qt::UserRole, pg.base_topic );
00529
00530 QMap<QString, PluginGroup::Info>::const_iterator it;
00531 for (it = pg.plugins.begin(); it != pg.plugins.end(); ++it)
00532 {
00533 const QString plugin_name = it.key();
00534 const PluginGroup::Info &info = it.value();
00535 QTreeWidgetItem *row = new QTreeWidgetItem( item );
00536
00537 row->setText( 0, factory->getClassName( plugin_name ) );
00538 row->setIcon( 0, factory->getIcon( plugin_name ) );
00539 row->setWhatsThis( 0, factory->getClassDescription( plugin_name ) );
00540 row->setData( 0, Qt::UserRole, plugin_name );
00541 row->setData( 1, Qt::UserRole, info.datatypes[0] );
00542
00543 if ( info.topic_suffixes.size() > 1 )
00544 {
00545 EmbeddableComboBox *box = new EmbeddableComboBox( row, 1 );
00546 connect( box, SIGNAL( itemClicked( QTreeWidgetItem*, int )),
00547 this, SLOT( onComboBoxClicked( QTreeWidgetItem* )));
00548 for ( int i = 0; i < info.topic_suffixes.size(); ++i)
00549 {
00550 box->addItem( info.topic_suffixes[i], info.datatypes[i] );
00551 }
00552 tree_->setItemWidget( row, 1, box );
00553 tree_->setColumnWidth( 1, std::max( tree_->columnWidth( 1 ), box->width() ));
00554 }
00555 }
00556 }
00557
00558
00559 for ( int i = 0; i < unvisualizable.size(); ++i )
00560 {
00561 const ros::master::TopicInfo &ti = unvisualizable.at( i );
00562 QTreeWidgetItem *item = insertItem( QString::fromStdString( ti.name ),
00563 true );
00564 }
00565
00566
00567 stateChanged( enable_hidden_box_->isChecked() );
00568 }
00569
00570 void TopicDisplayWidget::findPlugins( DisplayFactory *factory )
00571 {
00572
00573 QStringList lookup_names = factory->getDeclaredClassIds();
00574
00575 QStringList::iterator it;
00576 for (it = lookup_names.begin(); it != lookup_names.end(); ++it)
00577 {
00578 const QString &lookup_name = *it;
00579
00580
00581 QSet<QString> topic_types = factory->getMessageTypes( lookup_name );
00582 Q_FOREACH( QString topic_type, topic_types )
00583 {
00584
00585 datatype_plugins_.insertMulti( topic_type, lookup_name );
00586 }
00587 }
00588 }
00589
00590 QTreeWidgetItem* TopicDisplayWidget::insertItem( const QString &topic,
00591 bool disabled )
00592 {
00593 QTreeWidgetItem *current = tree_->invisibleRootItem();;
00594 QStringList parts = topic.split( "/" );
00595
00596 for ( int part_ind = 1; part_ind < parts.size(); ++part_ind )
00597 {
00598 QString part = "/" + parts[part_ind];
00599
00600 bool match = false;
00601 for ( int c = 0; c < current->childCount(); ++c )
00602 {
00603 QTreeWidgetItem *child = current->child( c );
00604 if ( child->text( 0 ) == part && !child->data( 1, Qt::UserRole ).isValid() )
00605 {
00606 match = true;
00607 current = child;
00608 break;
00609 }
00610 }
00611
00612 if ( !match )
00613 {
00614 QTreeWidgetItem *new_child = new QTreeWidgetItem( current );
00615
00616 new_child->setExpanded( 3 > part_ind );
00617 new_child->setText( 0, part );
00618 new_child->setDisabled( disabled );
00619 current = new_child;
00620
00621 }
00622 }
00623 return current;
00624 }
00625
00626 }