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


rviz_qt
Author(s): Dave Hershberger
autogenerated on Fri Dec 6 2013 20:56:53