$search
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 <QTimer> 00031 #include <QHBoxLayout> 00032 #include <QVBoxLayout> 00033 #include <QPushButton> 00034 #include <QInputDialog> 00035 00036 #include <set> 00037 00038 #include <boost/bind.hpp> 00039 00040 #include "displays_panel.h" 00041 #include "visualization_manager.h" 00042 #include "display.h" 00043 #include "display_wrapper.h" 00044 #include "new_display_dialog.h" 00045 #include "properties/property.h" 00046 #include "properties/property_manager.h" 00047 #include "properties/property_tree_with_help.h" 00048 #include "properties/property_tree_widget.h" 00049 #include "properties/property_widget_item.h" 00050 #include "config.h" 00051 00052 static const std::string PROPERTY_GRID_CONFIG("Property Grid State"); 00053 static const std::string PROPERTY_GRID_SPLITTER("Property Grid Splitter"); 00054 00055 namespace rviz 00056 { 00057 00058 DisplaysPanel::DisplaysPanel( QWidget* parent ) 00059 : QWidget( parent ) 00060 , manager_( NULL ) 00061 { 00062 tree_with_help_ = new PropertyTreeWithHelp; 00063 property_grid_ = tree_with_help_->getTree(); 00064 property_grid_->setDragEnabled( true ); 00065 property_grid_->setAcceptDrops( true ); 00066 00067 QPushButton* add_button = new QPushButton( "Add" ); 00068 add_button->setShortcut( QKeySequence( QString( "Ctrl+N" ))); 00069 add_button->setToolTip( "Add a new display, Ctrl+N" ); 00070 remove_button_ = new QPushButton( "Remove" ); 00071 remove_button_->setShortcut( QKeySequence( QString( "Ctrl+X" ))); 00072 remove_button_->setToolTip( "Remove displays, Ctrl+X" ); 00073 remove_button_->setEnabled( false ); 00074 rename_button_ = new QPushButton( "Rename" ); 00075 rename_button_->setShortcut( QKeySequence( QString( "Ctrl+R" ))); 00076 rename_button_->setToolTip( "Rename a display, Ctrl+R" ); 00077 rename_button_->setEnabled( false ); 00078 00079 QHBoxLayout* button_layout = new QHBoxLayout; 00080 button_layout->addWidget( add_button ); 00081 button_layout->addWidget( remove_button_ ); 00082 button_layout->addWidget( rename_button_ ); 00083 00084 QVBoxLayout* layout = new QVBoxLayout; 00085 layout->addWidget( tree_with_help_ ); 00086 layout->addLayout( button_layout ); 00087 00088 setLayout( layout ); 00089 00090 connect( add_button, SIGNAL( clicked( bool )), this, SLOT( onNewDisplay() )); 00091 connect( remove_button_, SIGNAL( clicked( bool )), this, SLOT( onDeleteDisplay() )); 00092 connect( rename_button_, SIGNAL( clicked( bool )), this, SLOT( onRenameDisplay() )); 00093 connect( property_grid_, SIGNAL( itemSelectionChanged() ), this, SLOT( onSelectionChanged() )); 00094 connect( property_grid_, SIGNAL( orderChanged() ), this, SLOT( renumberDisplays() )); 00095 00096 QTimer* timer = new QTimer( this ); 00097 connect( timer, SIGNAL( timeout() ), this, SLOT( onStateChangedTimer() )); 00098 timer->start( 200 ); 00099 } 00100 00101 DisplaysPanel::~DisplaysPanel() 00102 { 00103 } 00104 00105 void DisplaysPanel::initialize( VisualizationManager* manager ) 00106 { 00107 manager_ = manager; 00108 connect( manager_, SIGNAL( displayAdding( DisplayWrapper* )), this, SLOT( onDisplayAdding( DisplayWrapper* ))); 00109 connect( manager_, SIGNAL( displayAdded( DisplayWrapper* )), this, SLOT( onDisplayAdded( DisplayWrapper* ))); 00110 connect( manager_, SIGNAL( displayRemoved( DisplayWrapper* )), this, SLOT( onDisplayRemoved( DisplayWrapper* ))); 00111 connect( manager_, SIGNAL( displaysConfigLoaded( const boost::shared_ptr<Config>& )), 00112 this, SLOT( readFromConfig( const boost::shared_ptr<Config>& ))); 00113 connect( manager_, SIGNAL( displaysConfigSaved( const boost::shared_ptr<Config>& )), 00114 this, SLOT( writeToConfig( const boost::shared_ptr<Config>& ))); 00115 00116 manager_->getPropertyManager()->setPropertyTreeWidget( property_grid_ ); 00117 00118 sortDisplays(); 00119 } 00120 00121 void DisplaysPanel::sortDisplays() 00122 { 00123 property_grid_->sortItems( 0, Qt::AscendingOrder ); 00124 } 00125 00126 DisplayWrapper* DisplaysPanel::displayWrapperFromItem( QTreeWidgetItem* selected_item ) 00127 { 00128 DisplayWrapper* selected_display = 0; 00129 00130 PropertyWidgetItem* pwi = dynamic_cast<PropertyWidgetItem*>( selected_item ); 00131 if( pwi ) 00132 { 00133 PropertyBase* property = pwi->getProperty(); 00134 00135 void* user_data = property->getUserData(); 00136 if( user_data ) 00137 { 00138 DisplayWrapper* wrapper = reinterpret_cast<DisplayWrapper*>( user_data ); 00139 00140 if( manager_->isValidDisplay( wrapper )) 00141 { 00142 selected_display = manager_->getDisplayWrapper( wrapper->getName() ); 00143 } 00144 else 00145 { 00146 DisplayWrapper* wrapper = manager_->getDisplayWrapper( reinterpret_cast<Display*>( user_data )); 00147 00148 if( wrapper ) 00149 { 00150 selected_display = wrapper; 00151 } 00152 } 00153 } 00154 } 00155 return selected_display; 00156 } 00157 00158 void DisplaysPanel::onNewDisplay() 00159 { 00160 // Get the list of current display names, so we can enforce that the 00161 // new display has a unique name. 00162 S_string current_display_names; 00163 manager_->getDisplayNames(current_display_names); 00164 00165 std::string lookup_name; 00166 std::string display_name; 00167 00168 NewDisplayDialog* dialog = new NewDisplayDialog( manager_->getDisplayClassLoader(), 00169 current_display_names, 00170 &lookup_name, 00171 &display_name ); 00172 if( dialog->exec() == QDialog::Accepted ) 00173 { 00174 manager_->createDisplay( lookup_name, display_name, true ); 00175 } 00176 } 00177 00178 void DisplaysPanel::onDeleteDisplay() 00179 { 00180 std::set<DisplayWrapper*> displays_to_delete = getSelectedDisplays(); 00181 00182 std::set<DisplayWrapper*>::iterator di; 00183 for( di = displays_to_delete.begin(); di != displays_to_delete.end(); di++ ) 00184 { 00185 manager_->removeDisplay( *di ); 00186 } 00187 } 00188 00189 std::set<DisplayWrapper*> DisplaysPanel::getSelectedDisplays() 00190 { 00191 std::set<DisplayWrapper*> displays; 00192 00193 QList<QTreeWidgetItem*> selection = property_grid_->selectedItems(); 00194 QList<QTreeWidgetItem*>::iterator si; 00195 for( si = selection.begin(); si != selection.end(); si++ ) 00196 { 00197 DisplayWrapper* selected = displayWrapperFromItem( *si ); 00198 if( selected ) 00199 { 00200 displays.insert( selected ); 00201 } 00202 } 00203 return displays; 00204 } 00205 00206 void DisplaysPanel::onSelectionChanged() 00207 { 00208 std::set<DisplayWrapper*> displays = getSelectedDisplays(); 00209 00210 int num_displays_selected = displays.size(); 00211 00212 remove_button_->setEnabled( num_displays_selected > 0 ); 00213 rename_button_->setEnabled( num_displays_selected == 1 ); 00214 } 00215 00216 void DisplaysPanel::onRenameDisplay() 00217 { 00218 std::set<DisplayWrapper*> displays = getSelectedDisplays(); 00219 if( displays.size() == 0 ) 00220 { 00221 return; 00222 } 00223 DisplayWrapper* display_to_rename = *(displays.begin()); 00224 00225 if( !display_to_rename ) 00226 { 00227 return; 00228 } 00229 00230 bool ok = true; 00231 QString new_name; 00232 std::string new_name_std; 00233 QString old_name = QString::fromStdString( display_to_rename->getName() ); 00234 do 00235 { 00236 QString prompt; 00237 00238 if (!ok) 00239 { 00240 prompt = "That name is already taken. Please try another."; 00241 } 00242 else 00243 { 00244 prompt = "New Name?"; 00245 } 00246 new_name = QInputDialog::getText( this, "Rename Display", prompt, QLineEdit::Normal, old_name ); 00247 00248 ok = true; 00249 if( new_name.isEmpty() || new_name == old_name ) 00250 { 00251 return; 00252 } 00253 00254 new_name_std = new_name.toStdString(); 00255 00256 // Make sure the new name is not already taken 00257 M_DisplayToIndex::iterator it = display_map_.begin(); 00258 M_DisplayToIndex::iterator end = display_map_.end(); 00259 for (; it != end; ++it) 00260 { 00261 DisplayWrapper* wrapper = (*it).first; 00262 if( wrapper->getName() == new_name_std ) 00263 { 00264 ok = false; 00265 break; 00266 } 00267 } 00268 } while (!ok); 00269 00270 display_to_rename->setName( new_name_std ); 00271 renumberDisplays(); 00272 } 00273 00274 void DisplaysPanel::setDisplayCategoryLabel(const DisplayWrapper* wrapper, int index) 00275 { 00276 char buf[1024]; 00277 snprintf( buf, 1024, "%02d. %s (%s)", index + 1, wrapper->getName().c_str(), wrapper->getClassDisplayName().c_str()); 00278 wrapper->getCategory().lock()->setLabel(buf); 00279 } 00280 00281 void DisplaysPanel::setDisplayCategoryColor(const DisplayWrapper* wrapper) 00282 { 00283 CategoryPropertyPtr cat = wrapper->getCategory().lock(); 00284 00285 if (!wrapper->isLoaded()) 00286 { 00287 cat->setToError(); 00288 } 00289 else if ( wrapper->getDisplay()->isEnabled() ) 00290 { 00291 switch (wrapper->getDisplay()->getStatus()) 00292 { 00293 case status_levels::Ok: 00294 cat->setToOK(); 00295 break; 00296 case status_levels::Warn: 00297 cat->setToWarn(); 00298 break; 00299 case status_levels::Error: 00300 cat->setToError(); 00301 break; 00302 } 00303 } 00304 else 00305 { 00306 cat->setToDisabled(); 00307 } 00308 00309 PropertyWidgetItem* item = cat->getWidgetItem(); 00310 if( item ) 00311 { 00312 bool ign = property_grid_->setIgnoreChanges( true ); 00313 item->setFlags( item->flags() | Qt::ItemIsDragEnabled ); 00314 property_grid_->setIgnoreChanges( ign ); 00315 } 00316 } 00317 00318 void DisplaysPanel::onStateChangedTimer() 00319 { 00320 S_Display local_displays; 00321 { 00322 boost::mutex::scoped_lock lock(state_changed_displays_mutex_); 00323 local_displays.swap(state_changed_displays_); 00324 } 00325 00326 S_Display::iterator it = local_displays.begin(); 00327 S_Display::iterator end = local_displays.end(); 00328 for (; it != end; ++it) 00329 { 00330 Display* display = *it; 00331 DisplayWrapper* wrapper = manager_->getDisplayWrapper(display); 00332 if (!wrapper) 00333 { 00334 continue; 00335 } 00336 00337 M_DisplayToIndex::iterator it = display_map_.find(wrapper); 00338 if (it == display_map_.end()) 00339 { 00340 continue; 00341 } 00342 00343 int index = it->second; 00344 setDisplayCategoryColor(wrapper); 00345 setDisplayCategoryLabel(wrapper, index); 00346 } 00347 } 00348 00349 void DisplaysPanel::onDisplayStateChanged( Display* display ) 00350 { 00351 // This can be called from different threads, so we have to push this to the GUI update thread 00352 boost::mutex::scoped_lock lock(state_changed_displays_mutex_); 00353 state_changed_displays_.insert(display); 00354 } 00355 00356 void DisplaysPanel::onDisplayCreated( DisplayWrapper* wrapper ) 00357 { 00358 connect( wrapper->getDisplay(), SIGNAL( stateChanged( Display* )), this, SLOT( onDisplayStateChanged( Display* ))); 00359 00360 setDisplayCategoryColor(wrapper); 00361 00362 update(); 00363 } 00364 00365 void DisplaysPanel::onDisplayDestroyed( DisplayWrapper* wrapper ) 00366 { 00367 M_DisplayToIndex::iterator it = display_map_.find(wrapper); 00368 if (it == display_map_.end()) 00369 { 00370 return; 00371 } 00372 00373 setDisplayCategoryColor(wrapper); 00374 00375 00376 int index = it->second; 00377 setDisplayCategoryLabel(wrapper, index); 00378 00379 update(); 00380 } 00381 00382 void DisplaysPanel::onDisplayAdding( DisplayWrapper* wrapper ) 00383 { 00384 connect( wrapper, SIGNAL( displayCreated( DisplayWrapper* )), this, SLOT( onDisplayCreated( DisplayWrapper* ))); 00385 connect( wrapper, SIGNAL( displayDestroyed( DisplayWrapper* )), this, SLOT( onDisplayDestroyed( DisplayWrapper* ))); 00386 } 00387 00388 void DisplaysPanel::onDisplayAdded( DisplayWrapper* wrapper ) 00389 { 00390 int index = display_map_.size(); 00391 bool inserted = display_map_.insert(std::make_pair(wrapper, index)).second; 00392 ROS_ASSERT(inserted); 00393 setDisplayCategoryLabel(wrapper, index); 00394 setDisplayCategoryColor(wrapper); 00395 } 00396 00397 void DisplaysPanel::onDisplayRemoved( DisplayWrapper* wrapper ) 00398 { 00399 M_DisplayToIndex::iterator it = display_map_.find(wrapper); 00400 ROS_ASSERT(it != display_map_.end()); 00401 00402 uint32_t index = it->second; 00403 00404 display_map_.erase(it); 00405 00406 it = display_map_.begin(); 00407 M_DisplayToIndex::iterator end = display_map_.end(); 00408 for (;it != end; ++it) 00409 { 00410 if (it->second > index) 00411 { 00412 --it->second; 00413 setDisplayCategoryLabel(it->first, it->second); 00414 } 00415 } 00416 00417 sortDisplays(); 00418 } 00419 00420 void DisplaysPanel::readFromConfig(const boost::shared_ptr<Config>& config) 00421 { 00422 std::string grid_state; 00423 if ( config->get( PROPERTY_GRID_CONFIG, &grid_state ) ) 00424 { 00425 property_grid_->restoreEditableState( grid_state ); 00426 } 00427 00428 std::string sizes_string; 00429 if ( config->get( PROPERTY_GRID_SPLITTER, &sizes_string ) ) 00430 { 00431 QList<int> sizes; 00432 00433 std::istringstream iss( sizes_string ); 00434 int size; 00435 iss >> size; 00436 sizes.push_back( size ); 00437 char c; 00438 iss >> c; // skip the ',' 00439 iss >> size; 00440 sizes.push_back( size ); 00441 tree_with_help_->setSizes( sizes ); 00442 } 00443 } 00444 00445 void DisplaysPanel::writeToConfig(const boost::shared_ptr<Config>& config) 00446 { 00447 config->set( PROPERTY_GRID_CONFIG, property_grid_->saveEditableState() ); 00448 QList<int> sizes = tree_with_help_->sizes(); 00449 std::ostringstream sizes_stream; 00450 sizes_stream << sizes.at( 0 ) << ',' << sizes.at( 1 ); 00451 config->set( PROPERTY_GRID_SPLITTER, sizes_stream.str() ); 00452 } 00453 00454 void DisplaysPanel::renumberDisplays() 00455 { 00456 int display_number = 0; 00457 display_map_.clear(); 00458 for( int i = 0; i < property_grid_->topLevelItemCount(); i++ ) 00459 { 00460 DisplayWrapper* wrapper = displayWrapperFromItem( property_grid_->topLevelItem( i )); 00461 if( wrapper ) 00462 { 00463 setDisplayCategoryLabel( wrapper, display_number ); 00464 display_map_[ wrapper ] = display_number; 00465 display_number++; 00466 } 00467 } 00468 sortDisplays(); 00469 } 00470 00471 } // namespace rviz