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 tree_->header()->setResizeMode( 0, QHeaderView::Stretch );
00439
00440 enable_hidden_box_ = new QCheckBox( "Show unvisualizable topics" );
00441 enable_hidden_box_->setCheckState( Qt::Unchecked );
00442
00443 QVBoxLayout *layout = new QVBoxLayout;
00444 layout->setContentsMargins( QMargins( 0, 0, 0, 0 ) );
00445
00446 layout->addWidget( tree_ );
00447 layout->addWidget( enable_hidden_box_ );
00448
00449 connect( tree_, SIGNAL(currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)),
00450 this, SLOT(onCurrentItemChanged(QTreeWidgetItem*)));
00451
00452 connect( tree_, SIGNAL(itemActivated(QTreeWidgetItem*, int)),
00453 this, SIGNAL(itemActivated(QTreeWidgetItem*, int)) );
00454
00455
00456 connect( enable_hidden_box_, SIGNAL(stateChanged(int)),
00457 this, SLOT(stateChanged(int)) );
00458
00459 setLayout( layout );
00460 }
00461
00462 void TopicDisplayWidget::onCurrentItemChanged( QTreeWidgetItem* curr )
00463 {
00464
00465 SelectionData sd;
00466 if ( curr->data( 1, Qt::UserRole ).isValid() )
00467 {
00468 QTreeWidgetItem *parent = curr->parent();
00469 sd.whats_this = curr->whatsThis( 0 );
00470
00471 sd.topic = parent->data( 0, Qt::UserRole ).toString();
00472 sd.lookup_name = curr->data( 0, Qt::UserRole ).toString();
00473 sd.display_name = curr->text( 0 );
00474
00475 QComboBox *combo = qobject_cast<QComboBox*>( tree_->itemWidget( curr, 1 ) );
00476 if ( combo != NULL )
00477 {
00478 QString combo_text = combo->currentText();
00479 if ( combo_text != "raw" )
00480 {
00481 sd.topic += "/" + combo_text;
00482 }
00483 sd.datatype = combo->itemData( combo->currentIndex() ).toString();
00484 }
00485 else
00486 {
00487 sd.datatype = curr->data( 1, Qt::UserRole ).toString();
00488 }
00489 }
00490 Q_EMIT itemChanged( &sd );
00491 }
00492
00493 void TopicDisplayWidget::onComboBoxClicked( QTreeWidgetItem *curr )
00494 {
00495 tree_->setCurrentItem( curr );
00496 }
00497
00498 void TopicDisplayWidget::stateChanged( int state )
00499 {
00500 bool hide_disabled = state == Qt::Unchecked;
00501 QTreeWidgetItemIterator it( tree_, QTreeWidgetItemIterator::Disabled );
00502 for ( ; *it; ++it )
00503 {
00504 QTreeWidgetItem *item = *it;
00505 item->setHidden( hide_disabled );
00506 }
00507 }
00508
00509 void TopicDisplayWidget::fill( DisplayFactory *factory )
00510 {
00511 findPlugins( factory );
00512
00513 QList<PluginGroup> groups;
00514 QList<ros::master::TopicInfo> unvisualizable;
00515 getPluginGroups( datatype_plugins_, &groups, &unvisualizable );
00516
00517
00518 QList<PluginGroup>::const_iterator pg_it;
00519 for( pg_it = groups.begin(); pg_it < groups.end(); ++pg_it)
00520 {
00521 const PluginGroup &pg = *pg_it;
00522
00523 QTreeWidgetItem *item = insertItem( pg.base_topic, false );
00524 item->setData( 0, Qt::UserRole, pg.base_topic );
00525
00526 QMap<QString, PluginGroup::Info>::const_iterator it;
00527 for (it = pg.plugins.begin(); it != pg.plugins.end(); ++it)
00528 {
00529 const QString plugin_name = it.key();
00530 const PluginGroup::Info &info = it.value();
00531 QTreeWidgetItem *row = new QTreeWidgetItem( item );
00532
00533 row->setText( 0, factory->getClassName( plugin_name ) );
00534 row->setIcon( 0, factory->getIcon( plugin_name ) );
00535 row->setWhatsThis( 0, factory->getClassDescription( plugin_name ) );
00536 row->setData( 0, Qt::UserRole, plugin_name );
00537 row->setData( 1, Qt::UserRole, info.datatypes[0] );
00538
00539 if ( info.topic_suffixes.size() > 1 )
00540 {
00541 EmbeddableComboBox *box = new EmbeddableComboBox( row, 1 );
00542 connect( box, SIGNAL( itemClicked( QTreeWidgetItem*, int )),
00543 this, SLOT( onComboBoxClicked( QTreeWidgetItem* )));
00544 for ( int i = 0; i < info.topic_suffixes.size(); ++i)
00545 {
00546 box->addItem( info.topic_suffixes[i], info.datatypes[i] );
00547 }
00548 tree_->setItemWidget( row, 1, box );
00549 tree_->setColumnWidth( 1, std::max( tree_->columnWidth( 1 ), box->width() ));
00550 }
00551 }
00552 }
00553
00554
00555 for ( int i = 0; i < unvisualizable.size(); ++i )
00556 {
00557 const ros::master::TopicInfo &ti = unvisualizable.at( i );
00558 QTreeWidgetItem *item = insertItem( QString::fromStdString( ti.name ),
00559 true );
00560 }
00561
00562
00563 stateChanged( enable_hidden_box_->isChecked() );
00564 }
00565
00566 void TopicDisplayWidget::findPlugins( DisplayFactory *factory )
00567 {
00568
00569 QStringList lookup_names = factory->getDeclaredClassIds();
00570
00571 QStringList::iterator it;
00572 for (it = lookup_names.begin(); it != lookup_names.end(); ++it)
00573 {
00574 const QString &lookup_name = *it;
00575
00576
00577 QSet<QString> topic_types = factory->getMessageTypes( lookup_name );
00578 Q_FOREACH( QString topic_type, topic_types )
00579 {
00580
00581 datatype_plugins_.insertMulti( topic_type, lookup_name );
00582 }
00583 }
00584 }
00585
00586 QTreeWidgetItem* TopicDisplayWidget::insertItem( const QString &topic,
00587 bool disabled )
00588 {
00589 QTreeWidgetItem *current = tree_->invisibleRootItem();;
00590 QStringList parts = topic.split( "/" );
00591
00592 for ( int part_ind = 1; part_ind < parts.size(); ++part_ind )
00593 {
00594 QString part = "/" + parts[part_ind];
00595
00596 bool match = false;
00597 for ( int c = 0; c < current->childCount(); ++c )
00598 {
00599 QTreeWidgetItem *child = current->child( c );
00600 if ( child->text( 0 ) == part && !child->data( 1, Qt::UserRole ).isValid() )
00601 {
00602 match = true;
00603 current = child;
00604 break;
00605 }
00606 }
00607
00608 if ( !match )
00609 {
00610 QTreeWidgetItem *new_child = new QTreeWidgetItem( current );
00611
00612 new_child->setExpanded( 3 > part_ind );
00613 new_child->setText( 0, part );
00614 new_child->setDisabled( disabled );
00615 current = new_child;
00616
00617 }
00618 }
00619 return current;
00620 }
00621
00622 }