property_tree_widget.cpp
Go to the documentation of this file.
00001 /*
00002  * Copyright (c) 2011, 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 <QTimer>
00031 #include <QStyledItemDelegate>
00032 #include <QLineEdit>
00033 #include <QPainter>
00034 #include <QMouseEvent>
00035 
00036 #include "rviz/properties/property_tree_widget.h"
00037 #include "rviz/properties/property_widget_item.h"
00038 #include "rviz/properties/property.h"
00039 #include "rviz/properties/topic_info_variant.h"
00040 #include "rviz/properties/ros_topic_editor.h"
00041 #include "rviz/properties/color_editor.h"
00042 #include "rviz/uniform_string_stream.h"
00043 
00044 namespace rviz
00045 {
00046 
00048 // Delegate for handling items.
00050 class PropertyTreeDelegate: public QStyledItemDelegate
00051 {
00052 private:
00053   PropertyTreeWidget* tree_widget_;
00054 
00055 public:
00056   PropertyTreeDelegate( PropertyTreeWidget* tree_widget )
00057     : QStyledItemDelegate( tree_widget )
00058     , tree_widget_( tree_widget )
00059   {
00060   }
00061 
00062   virtual void paint( QPainter * painter,
00063                       const QStyleOptionViewItem & option,
00064                       const QModelIndex & index ) const
00065   {
00066     PropertyWidgetItem* item = tree_widget_->getItem( index );
00067     if( index.column() == 0 || !item->paint( painter, option ))
00068     {
00069       QStyledItemDelegate::paint( painter, option, index );
00070     }
00071   }
00072 
00073   virtual QWidget *createEditor( QWidget *parent,
00074                                  const QStyleOptionViewItem & option,
00075                                  const QModelIndex &index ) const
00076   {
00077     if( index.column() != 1 ) // only right column (#1) is editable.
00078     {
00079       return 0;
00080     }
00081     PropertyWidgetItem* item = tree_widget_->getItem( index );
00082     if( !item )
00083     {
00084       return 0;
00085     }
00086     QWidget* editor = item->createEditor( parent, option );
00087     if( editor != 0 )
00088     {
00089       if( LineEditWithButton* lewb = qobject_cast<LineEditWithButton*>( editor ))
00090       {
00091         tree_widget_->connect( lewb, SIGNAL( startPersistence() ), tree_widget_, SLOT( startPersistCurrent() ));
00092         tree_widget_->connect( lewb, SIGNAL( endPersistence() ), tree_widget_, SLOT( endPersistCurrent() ));
00093       }
00094       return editor;
00095     }
00096 
00097     // TODO: clean up the rest of this function.  I think ideally the
00098     // rest of this function would go out to subclasses of
00099     // PropertyWidgetItem, as is done with EnumItems and
00100     // EditEnumItems.  Same for setEditorData() and setModelData().
00101 
00102     QVariant originalValue = index.model()->data(index, Qt::UserRole);
00103 
00104     if( originalValue.canConvert<ros::master::TopicInfo>() )
00105     {
00106       RosTopicEditor* editor = new RosTopicEditor( parent );
00107       tree_widget_->connect( editor, SIGNAL( startPersistence() ), tree_widget_, SLOT( startPersistCurrent() ));
00108       tree_widget_->connect( editor, SIGNAL( endPersistence() ), tree_widget_, SLOT( endPersistCurrent() ));
00109       editor->setFrame( false );
00110       return editor;
00111     }
00112     else
00113     {
00114       QValidator *validator;
00115       QLineEdit *lineEdit = new QLineEdit(parent);
00116 
00117       switch (originalValue.type()) {
00118       case QVariant::Int:
00119       {
00120         validator = new QIntValidator( (int) item->min_, (int) item->max_, lineEdit );
00121         break;
00122       }
00123       case QMetaType::Float:
00124       {
00125         validator = new QDoubleValidator( item->min_, item->max_, 1000, lineEdit );
00126         break;
00127       }
00128       default:
00129         validator = NULL;
00130       }
00131       lineEdit->setValidator( validator );
00132       lineEdit->setFrame( false );
00133       return lineEdit;
00134     }
00135   }
00136   // see qt-examples/tools/settingseditor/variantdelegate.cpp for a more elaborate example.
00137 
00138   // Called to transfer data from model to editor
00139   void setEditorData(QWidget *editor, const QModelIndex &index) const
00140   {
00141     if( index.column() == 1 )
00142     {
00143       PropertyWidgetItem* item = tree_widget_->getItem( index );
00144       if( !item || item->setEditorData( editor ))
00145       {
00146         return;
00147       }
00148     }
00149     
00150     QVariant value = index.model()->data(index, Qt::UserRole);
00151 
00152     if( RosTopicEditor* topic_editor = qobject_cast<RosTopicEditor*>( editor ))
00153     {
00154       topic_editor->setTopic( value.value<ros::master::TopicInfo>() );
00155     }
00156     else if( QLineEdit *lineEdit = qobject_cast<QLineEdit *>( editor ))
00157     {
00158       lineEdit->setText(value.toString());
00159     }
00160   }
00161 
00162   // Called to transfer data from editor to model.
00163   void setModelData( QWidget *editor, QAbstractItemModel *model,
00164                      const QModelIndex &index ) const
00165   {
00166     PropertyWidgetItem* item = tree_widget_->getItem( index );
00167     if( !item || item->setModelData( editor ))
00168     {
00169       return;
00170     }
00171 
00172     QVariant originalValue = index.model()->data( index, Qt::UserRole );
00173     QVariant value;
00174     QString display_string;
00175 
00176     if( RosTopicEditor* topic_editor = qobject_cast<RosTopicEditor*>( editor ))
00177     {
00178       if( !topic_editor->isModified() )
00179       {
00180         return;
00181       }
00182       ros::master::TopicInfo topic = topic_editor->getTopic();
00183       value = QVariant::fromValue( topic );
00184       display_string = QString::fromStdString( topic.name );
00185     }
00186     else if( QLineEdit* lineEdit = qobject_cast<QLineEdit *>( editor ))
00187     {
00188       if( !lineEdit->isModified() )
00189       {
00190         return;
00191       }
00192 
00193       QString text = lineEdit->text();
00194       const QValidator *validator = lineEdit->validator();
00195       if( validator ) {
00196         int pos;
00197         if( validator->validate( text, pos ) != QValidator::Acceptable )
00198         {
00199           return;
00200         }
00201       }
00202 
00203       switch (originalValue.type()) {
00204 //    case QVariant::Char:
00205 //      value = text.at(0);
00206 //      break;
00207 //    case QVariant::Color:
00208 //      colorExp.exactMatch(text);
00209 //      value = QColor(qMin(colorExp.cap(1).toInt(), 255),
00210 //                     qMin(colorExp.cap(2).toInt(), 255),
00211 //                     qMin(colorExp.cap(3).toInt(), 255),
00212 //                     qMin(colorExp.cap(4).toInt(), 255));
00213 //      break;
00214 //    case QVariant::Date:
00215 //    {
00216 //      QDate date = QDate::fromString(text, Qt::ISODate);
00217 //      if (!date.isValid())
00218 //        return;
00219 //      value = date;
00220 //    }
00221 //    break;
00222 //    case QVariant::DateTime:
00223 //    {
00224 //      QDateTime dateTime = QDateTime::fromString(text, Qt::ISODate);
00225 //      if (!dateTime.isValid())
00226 //        return;
00227 //      value = dateTime;
00228 //    }
00229 //    break;
00230 //    case QVariant::Point:
00231 //      pointExp.exactMatch(text);
00232 //      value = QPoint(pointExp.cap(1).toInt(), pointExp.cap(2).toInt());
00233 //      break;
00234 //    case QVariant::Rect:
00235 //      rectExp.exactMatch(text);
00236 //      value = QRect(rectExp.cap(1).toInt(), rectExp.cap(2).toInt(),
00237 //                    rectExp.cap(3).toInt(), rectExp.cap(4).toInt());
00238 //      break;
00239 //    case QVariant::Size:
00240 //      sizeExp.exactMatch(text);
00241 //      value = QSize(sizeExp.cap(1).toInt(), sizeExp.cap(2).toInt());
00242 //      break;
00243 //    case QVariant::StringList:
00244 //      value = text.split(",");
00245 //      break;
00246 //    case QVariant::Time:
00247 //    {
00248 //      QTime time = QTime::fromString(text, Qt::ISODate);
00249 //      if (!time.isValid())
00250 //        return;
00251 //      value = time;
00252 //    }
00253 //    break;
00254       default: // int, float, string, etc which are parsed nicely by QVariant.
00255         value = text;
00256         value.convert( originalValue.type() );
00257       }
00258       display_string = value.toString();
00259     }
00260 
00261     // Store the final value variant in the model's UserRole.
00262     model->setData(index, value, Qt::UserRole);
00263 
00264     // Copy the display string into the DisplayRole so the default painter shows the right stuff.
00265     bool ign = tree_widget_->setIgnoreChanges( true );
00266     model->setData(index, display_string, Qt::DisplayRole);
00267     tree_widget_->setIgnoreChanges( ign );
00268   }
00269 };
00270 
00272 // Splitter widget class
00274 class SplitterHandle: public QWidget
00275 {
00276 public:
00277   SplitterHandle( PropertyTreeWidget* parent = 0 )
00278   : QWidget( parent )
00279   , parent_( parent )
00280   , first_column_size_ratio_( 0.5f )
00281   {
00282     setCursor( Qt::SplitHCursor );
00283     int w = 7;
00284     setGeometry( parent_->width() / 2 - w/2, 0, w, parent_->height() );
00285   }
00286 
00287   void onParentResized()
00288   {
00289     int new_column_width = int( first_column_size_ratio_ * parent_->width() );
00290     parent_->setColumnWidth( 0, new_column_width );
00291     setGeometry( new_column_width - width() / 2, 0, width(), parent_->height() );
00292   }
00293 
00294   void setRatio( float ratio )
00295   {
00296     first_column_size_ratio_ = ratio;
00297     onParentResized();
00298   }
00299 
00300   float getRatio()
00301   {
00302     return first_column_size_ratio_;
00303   }
00304 
00305 protected:
00306   void mousePressEvent( QMouseEvent* event )
00307   {
00308     if( event->button() == Qt::LeftButton )
00309     {
00310       x_press_offset_ = event->x();
00311     }
00312   }
00313 
00314   void mouseMoveEvent( QMouseEvent* event )
00315   {
00316     int padding = 55;
00317 
00318     if( event->buttons() & Qt::LeftButton )
00319     {
00320       QPoint pos_rel_parent = parent_->mapFromGlobal( event->globalPos() );
00321 
00322       int new_x = pos_rel_parent.x() - x_press_offset_;
00323 
00324       if( new_x > parent_->width() - width() - padding )
00325       {
00326         new_x = parent_->width() - width() - padding;
00327       }
00328 
00329       if( new_x < padding )
00330       {
00331         new_x = padding;
00332       }
00333 
00334       if( new_x != x() )
00335       {
00336         move( new_x, 0 );
00337 
00338         int new_column_width = new_x + width() / 2;
00339         parent_->setColumnWidth( 0, new_column_width );
00340 
00341         first_column_size_ratio_ = new_column_width / (float) parent_->width();
00342       }
00343     }
00344   }
00345 
00346 private:
00347   PropertyTreeWidget* parent_;
00348   int x_press_offset_;
00349 
00352   float first_column_size_ratio_;
00353 };
00354 
00356 // Main class functions
00358 PropertyTreeWidget::PropertyTreeWidget( QWidget* parent )
00359   : QTreeWidget( parent )
00360   , ignore_changes_( false )
00361   , splitter_handle_( new SplitterHandle( this ))
00362   , persisted_item_( 0 )
00363 {
00364   setHorizontalScrollBarPolicy( Qt::ScrollBarAlwaysOff );
00365   setHeaderHidden( true );
00366   setUniformRowHeights( true );
00367   setItemDelegate( new PropertyTreeDelegate( this ));
00368   setEditTriggers( QAbstractItemView::AllEditTriggers );
00369   setColumnCount( 2 );
00370   setSelectionMode( QAbstractItemView::ExtendedSelection );
00371 
00372   connect( this, SIGNAL( itemChanged( QTreeWidgetItem *, int )),
00373            this, SLOT( onItemChanged( QTreeWidgetItem *, int )));
00374 }
00375 
00376 void PropertyTreeWidget::onItemChanged( QTreeWidgetItem* item, int column_number )
00377 {
00378   if( !ignore_changes_ )
00379   {
00380     PropertyWidgetItem* pwi = dynamic_cast<PropertyWidgetItem*>( item );
00381     if( pwi )
00382     {
00383       pwi->getProperty()->readFromGrid();
00384     }
00385   }
00386 }
00387 
00388 PropertyWidgetItem* PropertyTreeWidget::getItem( const QModelIndex & index )
00389 {
00390   return dynamic_cast<PropertyWidgetItem*>( itemFromIndex( index ));
00391 }
00392 
00393 void PropertyTreeWidget::resizeEvent( QResizeEvent* event )
00394 {
00395   splitter_handle_->onParentResized();
00396 }
00397 
00398 void PropertyTreeWidget::dropEvent( QDropEvent* event )
00399 {
00400   QTreeWidget::dropEvent( event );
00401   Q_EMIT orderChanged();
00402 }
00403 
00406 std::string PropertyTreeWidget::saveEditableState()
00407 {
00408   UniformStringStream output;
00409   
00410   bool first = true;
00411   saveExpandedState( output, invisibleRootItem(), first );
00412 
00413   output << ";splitterratio=" << splitter_handle_->getRatio();
00414 
00415   return output.str();
00416 
00417 // Here's what the old rviz saved here:
00418 //
00419 // Property\ Grid\ State=selection=Fixed Frame;expanded=.Global Options,TF.Enabled.TF.StatusTopStatus,TF.Enabled.TF.Tree,MarkerArray.Enabled,MarkerArray.Enabled.MarkerArray.Namespaces;scrollpos=0,0;splitterpos=142,301;ispageselected=1
00420 //
00421 // I'm explicitly not saving what is selected - I don't think it is
00422 // important, and I'm not attempting to be compatible with old files.
00423 }
00424 
00425 void PropertyTreeWidget::saveExpandedState( std::ostream& output,
00426                                             QTreeWidgetItem* parent_item,
00427                                             bool& first )
00428 {
00429   for( int child_index = 0; child_index < parent_item->childCount(); child_index++ )
00430   {
00431     QTreeWidgetItem* item = parent_item->child( child_index );
00432     if( item->isExpanded() && item->childCount() > 0 )
00433     {
00434       if( first )
00435       {
00436         output << "expanded=";
00437         first = false;
00438       }
00439       else
00440       {
00441         output << ',';
00442       }
00443       PropertyWidgetItem* pwi = dynamic_cast<PropertyWidgetItem*>( item );
00444       if( pwi )
00445       {
00446         output << pwi->getProperty()->getPrefix() << pwi->getProperty()->getName();
00447         saveExpandedState( output, item, first );
00448       }
00449     }
00450   }
00451 }
00452 
00454 void PropertyTreeWidget::restoreEditableState( const std::string& state )
00455 {
00456   UniformStringStream iss( state );
00457   std::string assignment;
00458   while( std::getline( iss, assignment, ';' ))
00459   {
00460     size_t equal_pos = assignment.find( '=' );
00461     if( equal_pos != std::string::npos )
00462     {
00463       UniformStringStream value_stream( assignment.substr( equal_pos + 1 ));
00464       if( 0 == assignment.compare( 0, equal_pos, "splitterratio" ))
00465       {
00466         float ratio = 0.5;
00467         value_stream.parseFloat( ratio );
00468         splitter_handle_->setRatio( ratio );
00469       }
00470       else if( 0 == assignment.compare( 0, equal_pos, "expanded" ))
00471       {
00472         std::set<std::string> expanded_entries;
00473         std::string entry;
00474         while( std::getline( value_stream, entry, ',' ))
00475         {
00476           expanded_entries.insert( entry );
00477         }
00478 
00479         restoreExpandedState( expanded_entries, invisibleRootItem() );
00480       }
00481     }
00482   }
00483 }
00484 
00487 void PropertyTreeWidget::restoreExpandedState( const std::set<std::string>& expanded_entries,
00488                                                QTreeWidgetItem* parent_item )
00489 {
00490   // Caveat: this runs before data comes in from subscriptions which
00491   // actually sets up some of the properties (like TF frame entries),
00492   // so the expanded state of those are not properly restored.
00493   for( int child_index = 0; child_index < parent_item->childCount(); child_index++ )
00494   {
00495     QTreeWidgetItem* item = parent_item->child( child_index );
00496     PropertyWidgetItem* pwi = dynamic_cast<PropertyWidgetItem*>( item );
00497     if( pwi )
00498     {
00499       std::string entry_name = pwi->getProperty()->getPrefix() + pwi->getProperty()->getName();
00500       if( expanded_entries.find( entry_name ) != expanded_entries.end() )
00501       {
00502         item->setExpanded( true );
00503         if( item->childCount() > 0 )
00504         {
00505           restoreExpandedState( expanded_entries, item );
00506         }
00507       }
00508       else
00509       {
00510         item->setExpanded( false );
00511       }
00512     }
00513   }
00514 }
00515 
00516 void PropertyTreeWidget::startPersistCurrent()
00517 {
00518   persisted_item_ = currentItem();
00519   openPersistentEditor( persisted_item_, 1 );
00520 }
00521 
00522 void PropertyTreeWidget::endPersistCurrent()
00523 {
00524   if( persisted_item_ )
00525   {
00526     QWidget* editor = itemWidget( persisted_item_, 1 );
00527     if( editor )
00528     {
00529       commitData( editor );
00530     }
00531     closePersistentEditor( persisted_item_, 1 );
00532 
00533     // Not sure why this is necessary.  Without this, the
00534     // QAbstractItemView (an ancestor class of this) stays in its
00535     // "Editing" state after the above closePersistentEditor() call,
00536     // leading to a broken state where clicking on editable items does
00537     // not open their editors.
00538     //
00539     // QAbstractItemView::dragLeaveEvent() does an unconditional
00540     // setState(NoState) internally, which fixes the problem.
00541     dragLeaveEvent( NULL );
00542   }
00543 }
00544 
00545 } // end namespace rviz


rviz
Author(s): Dave Hershberger, Josh Faust
autogenerated on Mon Jan 6 2014 11:54:32