$search
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