add_display_dialog.cpp
Go to the documentation of this file.
00001 /*
00002  * Copyright (c) 2008, Willow Garage, Inc.
00003  * All rights reserved.
00004  *
00005  * Redistribution and use in source and binary forms, with or without
00006  * modification, are permitted provided that the following conditions are met:
00007  *
00008  *     * Redistributions of source code must retain the above copyright
00009  *       notice, this list of conditions and the following disclaimer.
00010  *     * Redistributions in binary form must reproduce the above copyright
00011  *       notice, this list of conditions and the following disclaimer in the
00012  *       documentation and/or other materials provided with the distribution.
00013  *     * Neither the name of the Willow Garage, Inc. nor the names of its
00014  *       contributors may be used to endorse or promote products derived from
00015  *       this software without specific prior written permission.
00016  *
00017  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
00018  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
00019  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
00020  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
00021  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
00022  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
00023  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
00024  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
00025  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
00026  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
00027  * POSSIBILITY OF SUCH DAMAGE.
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 // Utilities for grouping topics together
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   // Map from plugin name to plugin data
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         // Remove base_topic and leading slash
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 // Dialog implementation
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   //***** Layout
00186 
00187   // Display Type group
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   // Display Name group
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   // Buttons
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   //***** Connections
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   // If display is selected, populate selection data.  Otherwise, clear data.
00375   SelectionData sd;
00376   if ( curr->parent() != NULL )
00377   {
00378     // Leave topic and datatype blank
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     // Map from package names to the corresponding top-level tree widget items.
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       // Store the lookup name for each class in the UserRole of the item.
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   // Forward signals from tree_
00456   connect( tree_, SIGNAL(itemActivated(QTreeWidgetItem*, int)),
00457            this, SIGNAL(itemActivated(QTreeWidgetItem*, int)) );
00458 
00459   // Connect signal from checkbox
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   // If plugin is selected, populate selection data.  Otherwise, clear data.
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   // Insert visualizable topics along with their plugins
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   // Insert unvisualizable topics
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   // Hide unvisualizable topics if necessary
00567   stateChanged( enable_hidden_box_->isChecked() );
00568 }
00569 
00570 void TopicDisplayWidget::findPlugins( DisplayFactory *factory )
00571 {
00572   // Build map from topic type to plugin by instantiating every plugin we have.
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     // ROS_INFO("Class: %s", lookup_name.toStdString().c_str());
00580 
00581     QSet<QString> topic_types = factory->getMessageTypes( lookup_name );
00582     Q_FOREACH( QString topic_type, topic_types )
00583     {
00584       // ROS_INFO("Type: %s", topic_type.toStdString().c_str());
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     // If any child matches, use that one.
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     // If no match, create a new child.
00612     if ( !match )
00613     {
00614       QTreeWidgetItem *new_child = new QTreeWidgetItem( current );
00615       // Only expand first few levels of the tree
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 } // rviz


rviz
Author(s): Dave Hershberger, David Gossow, Josh Faust
autogenerated on Thu Jun 6 2019 18:02:15