$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 <QSplashScreen> 00031 #include <QDockWidget> 00032 #include <QDir> 00033 #include <QCloseEvent> 00034 #include <QToolBar> 00035 #include <QMenuBar> 00036 #include <QMenu> 00037 #include <QMessageBox> 00038 #include <QFileDialog> 00039 #include <QDesktopServices> 00040 #include <QUrl> 00041 00042 #include <boost/filesystem.hpp> 00043 #include <boost/bind.hpp> 00044 #include <boost/algorithm/string/split.hpp> 00045 #include <boost/algorithm/string/trim.hpp> 00046 00047 #include <ros/package.h> 00048 #include <ros/console.h> 00049 00050 #include <ogre_tools/initialization.h> 00051 #include <ogre_tools/render_system.h> 00052 00053 #include "visualization_frame.h" 00054 #include "render_panel.h" 00055 #include "displays_panel.h" 00056 #include "views_panel.h" 00057 #include "time_panel.h" 00058 #include "selection_panel.h" 00059 #include "tool_properties_panel.h" 00060 #include "visualization_manager.h" 00061 #include "tools/tool.h" 00062 #include "loading_dialog.h" 00063 #include "config.h" 00064 #include "panel_dock_widget.h" 00065 00067 // #include <gdk/gdk.h> 00068 // #include <gdk/gdkx.h> 00069 00070 namespace fs = boost::filesystem; 00071 00072 #define CONFIG_WINDOW_X "/Window/X" 00073 #define CONFIG_WINDOW_Y "/Window/Y" 00074 #define CONFIG_WINDOW_WIDTH "/Window/Width" 00075 #define CONFIG_WINDOW_HEIGHT "/Window/Height" 00076 // I am not trying to preserve peoples' window layouts from wx to Qt, 00077 // just saving the Qt layout in a new config tag. 00078 #define CONFIG_QMAINWINDOW "/QMainWindow" 00079 #define CONFIG_AUIMANAGER_PERSPECTIVE "/AuiManagerPerspective" 00080 #define CONFIG_AUIMANAGER_PERSPECTIVE_VERSION "/AuiManagerPerspectiveVersion" 00081 #define CONFIG_RECENT_CONFIGS "/RecentConfigs" 00082 #define CONFIG_LAST_DIR "/LastConfigDir" 00083 00084 #define CONFIG_EXTENSION "vcg" 00085 #define CONFIG_EXTENSION_WILDCARD "*."CONFIG_EXTENSION 00086 #define PERSPECTIVE_VERSION 2 00087 00088 #define RECENT_CONFIG_COUNT 10 00089 00090 namespace rviz 00091 { 00092 00093 VisualizationFrame::VisualizationFrame( QWidget* parent ) 00094 : QMainWindow( parent ) 00095 , render_panel_(NULL) 00096 , displays_panel_(NULL) 00097 , views_panel_(NULL) 00098 , time_panel_(NULL) 00099 , selection_panel_(NULL) 00100 , tool_properties_panel_(NULL) 00101 , file_menu_(NULL) 00102 , recent_configs_menu_(NULL) 00103 , toolbar_(NULL) 00104 , manager_(NULL) 00105 , position_correction_( 0, 0 ) 00106 , num_move_events_( 0 ) 00107 , toolbar_actions_( NULL ) 00108 { 00109 setWindowTitle( "RViz" ); 00110 } 00111 00112 VisualizationFrame::~VisualizationFrame() 00113 { 00114 if( manager_ ) 00115 { 00116 manager_->removeAllDisplays(); 00117 } 00118 00119 delete render_panel_; 00120 delete manager_; 00121 } 00122 00123 void VisualizationFrame::closeEvent( QCloseEvent* event ) 00124 { 00125 if( general_config_ ) 00126 { 00127 saveConfigs(); 00128 } 00129 event->accept(); 00130 } 00131 00132 void VisualizationFrame::onSplashLoadStatus( const std::string& status ) 00133 { 00134 splash_->showMessage( QString::fromStdString( status )); 00135 } 00136 00137 void VisualizationFrame::initialize(const std::string& display_config_file, 00138 const std::string& fixed_frame, 00139 const std::string& target_frame, 00140 const std::string& splash_path, 00141 bool verbose ) 00142 { 00143 initConfigs(); 00144 00145 int new_x, new_y, new_width, new_height; 00146 general_config_->get( CONFIG_WINDOW_X, &new_x, x() ); 00147 general_config_->get( CONFIG_WINDOW_Y, &new_y, y() ); 00148 general_config_->get( CONFIG_WINDOW_WIDTH, &new_width, width() ); 00149 general_config_->get( CONFIG_WINDOW_HEIGHT, &new_height, height() ); 00150 00151 { 00152 std::string recent; 00153 if( general_config_->get( CONFIG_RECENT_CONFIGS, &recent )) 00154 { 00155 boost::trim( recent ); 00156 boost::split( recent_configs_, recent, boost::is_any_of (":"), boost::token_compress_on ); 00157 } 00158 00159 general_config_->get( CONFIG_LAST_DIR, &last_config_dir_ ); 00160 } 00161 00162 move( new_x, new_y ); 00163 resize( new_width, new_height ); 00164 00165 package_path_ = ros::package::getPath("rviz_qt"); 00166 00167 std::string final_splash_path = splash_path; 00168 00169 if ( splash_path.empty() ) 00170 { 00171 #if BOOST_FILESYSTEM_VERSION == 3 00172 final_splash_path = (fs::path(package_path_) / "images/splash.png").string(); 00173 #else 00174 final_splash_path = (fs::path(package_path_) / "images/splash.png").file_string(); 00175 #endif 00176 } 00177 QPixmap splash_image( QString::fromStdString( final_splash_path )); 00178 splash_ = new QSplashScreen( splash_image ); 00179 splash_->show(); 00180 splash_->showMessage( "Initializing" ); 00181 00182 if( !ros::isInitialized() ) 00183 { 00184 int argc = 0; 00185 ros::init( argc, 0, "rviz", ros::init_options::AnonymousName ); 00186 } 00187 00188 render_panel_ = new RenderPanel( ogre_tools::RenderSystem::get(), 0, this ); 00189 displays_panel_ = new DisplaysPanel( this ); 00190 views_panel_ = new ViewsPanel( this ); 00191 time_panel_ = new TimePanel( this ); 00192 selection_panel_ = new SelectionPanel( this ); 00193 tool_properties_panel_ = new ToolPropertiesPanel( this ); 00194 00195 splash_->showMessage( "Initializing OGRE resources" ); 00196 ogre_tools::V_string paths; 00197 paths.push_back( package_path_ + "/ogre_media/textures" ); 00198 ogre_tools::initializeResources( paths ); 00199 00200 initMenus(); 00201 toolbar_ = addToolBar( "Tools" ); 00202 toolbar_->setObjectName( "Tools" ); 00203 toolbar_actions_ = new QActionGroup( this ); 00204 connect( toolbar_actions_, SIGNAL( triggered( QAction* )), this, SLOT( onToolbarActionTriggered( QAction* ))); 00205 view_menu_->addAction( toolbar_->toggleViewAction() ); 00206 00207 setCentralWidget( render_panel_ ); 00208 00209 addPane( "Displays", displays_panel_, Qt::LeftDockWidgetArea, false ); 00210 addPane( "Tool Properties", tool_properties_panel_, Qt::RightDockWidgetArea, false ); 00211 addPane( "Views", views_panel_, Qt::RightDockWidgetArea, false ); 00212 addPane( "Selection", selection_panel_, Qt::RightDockWidgetArea, false ); 00213 addPane( "Time", time_panel_, Qt::BottomDockWidgetArea, false ); 00214 00215 manager_ = new VisualizationManager( render_panel_, this ); 00216 render_panel_->initialize( manager_->getSceneManager(), manager_ ); 00217 displays_panel_->initialize( manager_ ); 00218 views_panel_->initialize( manager_ ); 00219 time_panel_->initialize(manager_); 00220 selection_panel_->initialize( manager_ ); 00221 tool_properties_panel_->initialize( manager_ ); 00222 00223 connect( manager_, SIGNAL( toolAdded( Tool* )), this, SLOT( addTool( Tool* ))); 00224 connect( manager_, SIGNAL( toolChanged( Tool* )), this, SLOT( indicateToolIsCurrent( Tool* ))); 00225 00226 manager_->initialize( StatusCallback(), verbose ); 00227 manager_->loadGeneralConfig(general_config_, boost::bind( &VisualizationFrame::onSplashLoadStatus, this, _1 )); 00228 00229 bool display_config_valid = !display_config_file.empty(); 00230 if( display_config_valid && !fs::exists( display_config_file )) 00231 { 00232 ROS_ERROR("File [%s] does not exist", display_config_file.c_str()); 00233 display_config_valid = false; 00234 } 00235 00236 if( !display_config_valid ) 00237 { 00238 manager_->loadDisplayConfig( display_config_, boost::bind( &VisualizationFrame::onSplashLoadStatus, this, _1 )); 00239 } 00240 else 00241 { 00242 boost::shared_ptr<Config> config( new Config ); 00243 config->readFromFile( display_config_file ); 00244 manager_->loadDisplayConfig( config, boost::bind( &VisualizationFrame::onSplashLoadStatus, this, _1 )); 00245 } 00246 00247 if( !fixed_frame.empty() ) 00248 { 00249 manager_->setFixedFrame( fixed_frame ); 00250 } 00251 00252 if( !target_frame.empty() ) 00253 { 00254 manager_->setTargetFrame( target_frame ); 00255 } 00256 00257 splash_->showMessage( "Loading perspective" ); 00258 00259 std::string main_window_config; 00260 if( general_config_->get( CONFIG_QMAINWINDOW, &main_window_config )) 00261 { 00262 restoreState( QByteArray::fromHex( main_window_config.c_str() )); 00263 } 00264 00265 updateRecentConfigMenu(); 00266 if( display_config_valid ) 00267 { 00268 markRecentConfig( display_config_file ); 00269 } 00270 00271 delete splash_; 00272 splash_ = 0; 00273 00274 manager_->startUpdate(); 00275 } 00276 00277 void VisualizationFrame::initConfigs() 00278 { 00279 config_dir_ = QDir::toNativeSeparators( QDir::homePath() ).toStdString(); 00280 #if BOOST_FILESYSTEM_VERSION == 3 00281 std::string old_dir = (fs::path(config_dir_) / ".standalone_visualizer").string(); 00282 config_dir_ = (fs::path(config_dir_) / ".rviz_qt").string(); 00283 general_config_file_ = (fs::path(config_dir_) / "config").string(); 00284 display_config_file_ = (fs::path(config_dir_) / "display_config").string(); 00285 #else 00286 std::string old_dir = (fs::path(config_dir_) / ".standalone_visualizer").file_string(); 00287 config_dir_ = (fs::path(config_dir_) / ".rviz_qt").file_string(); 00288 general_config_file_ = (fs::path(config_dir_) / "config").file_string(); 00289 display_config_file_ = (fs::path(config_dir_) / "display_config").file_string(); 00290 #endif 00291 00292 if( fs::exists( old_dir ) && !fs::exists( config_dir_ )) 00293 { 00294 ROS_INFO("Migrating old config directory to new location ([%s] to [%s])", old_dir.c_str(), config_dir_.c_str()); 00295 fs::rename( old_dir, config_dir_ ); 00296 } 00297 00298 if( fs::is_regular_file( config_dir_ )) 00299 { 00300 ROS_INFO("Migrating old config file to new location ([%s] to [%s])", config_dir_.c_str(), general_config_file_.c_str()); 00301 std::string backup_file = config_dir_ + "bak"; 00302 00303 fs::rename(config_dir_, backup_file); 00304 fs::create_directory(config_dir_); 00305 fs::rename(backup_file, general_config_file_); 00306 } 00307 else if (!fs::exists(config_dir_)) 00308 { 00309 fs::create_directory(config_dir_); 00310 } 00311 00312 if (fs::exists(general_config_file_) && !fs::exists(display_config_file_)) 00313 { 00314 ROS_INFO("Creating display config from general config"); 00315 fs::copy_file(general_config_file_, display_config_file_); 00316 } 00317 00318 ROS_INFO("Loading general config from [%s]", general_config_file_.c_str()); 00319 general_config_.reset( new Config ); 00320 general_config_->readFromFile( general_config_file_ ); 00321 00322 ROS_INFO("Loading display config from [%s]", display_config_file_.c_str()); 00323 display_config_.reset( new Config ); 00324 display_config_->readFromFile( display_config_file_ ); 00325 } 00326 00327 void VisualizationFrame::initMenus() 00328 { 00329 file_menu_ = menuBar()->addMenu( "&File" ); 00330 file_menu_->addAction( "&Open Config", this, SLOT( onOpen() ), QKeySequence( "Ctrl+O" )); 00331 file_menu_->addAction( "&Save Config", this, SLOT( onSave() ), QKeySequence( "Ctrl+S" )); 00332 recent_configs_menu_ = file_menu_->addMenu( "&Recent Configs" ); 00333 file_menu_->addSeparator(); 00334 file_menu_->addAction( "&Quit", this, SLOT( close() ), QKeySequence( "Ctrl+Q" )); 00335 00336 view_menu_ = menuBar()->addMenu( "&View" ); 00337 00343 00344 QMenu* help_menu = menuBar()->addMenu( "&Help" ); 00345 help_menu->addAction( "Wiki", this, SLOT( onHelpWiki() )); 00346 } 00347 00348 void VisualizationFrame::updateRecentConfigMenu() 00349 { 00350 recent_configs_menu_->clear(); 00351 00352 D_string::iterator it = recent_configs_.begin(); 00353 D_string::iterator end = recent_configs_.end(); 00354 for (; it != end; ++it) 00355 { 00356 if( *it != "" ) 00357 { 00358 recent_configs_menu_->addAction( QString::fromStdString( *it ), this, SLOT( onRecentConfigSelected() )); 00359 } 00360 } 00361 } 00362 00363 void VisualizationFrame::markRecentConfig( const std::string& path ) 00364 { 00365 D_string::iterator it = std::find( recent_configs_.begin(), recent_configs_.end(), path ); 00366 if( it != recent_configs_.end() ) 00367 { 00368 recent_configs_.erase( it ); 00369 } 00370 00371 recent_configs_.push_front( path ); 00372 00373 if( recent_configs_.size() > RECENT_CONFIG_COUNT ) 00374 { 00375 recent_configs_.pop_back(); 00376 } 00377 00378 updateRecentConfigMenu(); 00379 } 00380 00381 void VisualizationFrame::loadDisplayConfig( const std::string& path ) 00382 { 00383 if( !fs::exists( path )) 00384 { 00385 QString message = QString::fromStdString( path ) + " does not exist!"; 00386 QMessageBox::critical( this, "Config file does not exist", message ); 00387 return; 00388 } 00389 00390 manager_->removeAllDisplays(); 00391 00392 LoadingDialog dialog( this ); 00393 dialog.show(); 00394 00395 boost::shared_ptr<Config> config( new Config ); 00396 config->readFromFile( path ); 00397 manager_->loadDisplayConfig( config, boost::bind( &LoadingDialog::setState, &dialog, _1 )); 00398 00399 markRecentConfig(path); 00400 } 00401 00402 00403 void VisualizationFrame::moveEvent( QMoveEvent* event ) 00404 { 00405 // GdkRectangle rect; 00406 // GdkWindow* gdk_window = gdk_window_foreign_new( winId() ); 00407 // gdk_window_get_frame_extents( gdk_window, &rect ); 00408 // printf( "gdk x=%d, y=%d\n", rect.x, rect.y ); 00409 // the above works! should I just use gdk?? 00410 00411 // HACK to work around a bug in Qt-for-X11. The first time we get a 00412 // moveEvent, the position is that of the top-left corner of the 00413 // window frame. The second time we get one, the position is the 00414 // top-left corner *inside* the frame. There is no significant time 00415 // lag between the two calls, certainly no user events, so I just 00416 // remember the first position and diff it with the second position 00417 // and remember the diff as a corrective offset for future geometry 00418 // requests. 00419 // 00420 // This seems like it would be brittle to OS, code changes, etc, so 00421 // sometime I should get something better going here. Maybe call 00422 // out to gdk (as above), which seems to work right. 00423 switch( num_move_events_ ) 00424 { 00425 case 0: 00426 first_position_ = pos(); 00427 num_move_events_++; 00428 break; 00429 case 1: 00430 position_correction_ = first_position_ - pos(); 00431 num_move_events_++; 00432 break; 00433 } 00434 } 00435 00436 QRect VisualizationFrame::hackedFrameGeometry() 00437 { 00438 QRect geom = frameGeometry(); 00439 geom.moveTopLeft( pos() + position_correction_ ); 00440 return geom; 00441 } 00442 00443 void VisualizationFrame::saveConfigs() 00444 { 00445 ROS_INFO("Saving general config to [%s]", general_config_file_.c_str()); 00446 general_config_->clear(); 00447 QRect geom = hackedFrameGeometry(); 00448 general_config_->set( CONFIG_WINDOW_X, geom.x() ); 00449 general_config_->set( CONFIG_WINDOW_Y, geom.y() ); 00450 general_config_->set( CONFIG_WINDOW_WIDTH, geom.width() ); 00451 general_config_->set( CONFIG_WINDOW_HEIGHT, geom.height() ); 00452 00453 QByteArray window_state = saveState().toHex(); 00454 general_config_->set( CONFIG_QMAINWINDOW, std::string( window_state.constData() )); 00455 00456 { 00457 std::stringstream ss; 00458 D_string::iterator it = recent_configs_.begin(); 00459 D_string::iterator end = recent_configs_.end(); 00460 for (; it != end; ++it) 00461 { 00462 if (it != recent_configs_.begin()) 00463 { 00464 ss << ":"; 00465 } 00466 ss << *it; 00467 } 00468 00469 general_config_->set( CONFIG_RECENT_CONFIGS, ss.str() ); 00470 } 00471 00472 general_config_->set( CONFIG_LAST_DIR, last_config_dir_ ); 00473 00474 manager_->saveGeneralConfig( general_config_ ); 00475 general_config_->writeToFile( general_config_file_ ); 00476 00477 ROS_INFO( "Saving display config to [%s]", display_config_file_.c_str() ); 00478 display_config_->clear(); 00479 manager_->saveDisplayConfig( display_config_ ); 00480 display_config_->writeToFile( display_config_file_ ); 00481 } 00482 00483 void VisualizationFrame::onOpen() 00484 { 00485 QString filename = QFileDialog::getOpenFileName( this, "Choose a file to open", 00486 QString::fromStdString( last_config_dir_ ), 00487 "RViz config files (" CONFIG_EXTENSION_WILDCARD ")" ); 00488 00489 if( !filename.isEmpty() ) 00490 { 00491 std::string filename_string = filename.toStdString(); 00492 loadDisplayConfig( filename_string ); 00493 last_config_dir_ = fs::path( filename_string ).parent_path().string(); 00494 } 00495 } 00496 00497 void VisualizationFrame::onSave() 00498 { 00499 QString q_filename = QFileDialog::getSaveFileName( this, "Choose a file to save to", 00500 QString::fromStdString( last_config_dir_ ), 00501 "RViz config files (" CONFIG_EXTENSION_WILDCARD ")" ); 00502 00503 if( !q_filename.isEmpty() ) 00504 { 00505 std::string filename = q_filename.toStdString(); 00506 fs::path path( filename ); 00507 if( path.extension() != "."CONFIG_EXTENSION ) 00508 { 00509 filename += "."CONFIG_EXTENSION; 00510 } 00511 00512 boost::shared_ptr<Config> config( new Config() ); 00513 manager_->saveDisplayConfig( config ); 00514 config->writeToFile( filename ); 00515 00516 markRecentConfig( filename ); 00517 00518 last_config_dir_ = fs::path( filename ).parent_path().string(); 00519 } 00520 } 00521 00522 void VisualizationFrame::onRecentConfigSelected() 00523 { 00524 QAction* action = dynamic_cast<QAction*>( sender() ); 00525 if( action ) 00526 { 00527 std::string path = action->text().toStdString(); 00528 if( !path.empty() ) 00529 { 00530 loadDisplayConfig( path ); 00531 } 00532 } 00533 } 00534 00535 void VisualizationFrame::addTool( Tool* tool ) 00536 { 00537 QAction* action = new QAction( QString::fromStdString( tool->getName() ), toolbar_actions_ ); 00538 action->setCheckable( true ); 00539 action->setShortcut( QKeySequence( QString( tool->getShortcutKey() ))); 00540 toolbar_->addAction( action ); 00541 action_to_tool_map_[ action ] = tool; 00542 tool_to_action_map_[ tool ] = action; 00543 } 00544 00545 void VisualizationFrame::onToolbarActionTriggered( QAction* action ) 00546 { 00547 Tool* tool = action_to_tool_map_[ action ]; 00548 if( tool ) 00549 { 00550 manager_->setCurrentTool( tool ); 00551 } 00552 } 00553 00554 void VisualizationFrame::indicateToolIsCurrent( Tool* tool ) 00555 { 00556 QAction* action = tool_to_action_map_[ tool ]; 00557 if( action ) 00558 { 00559 action->setChecked( true ); 00560 } 00561 } 00562 00569 void VisualizationFrame::onHelpWiki() 00570 { 00571 QDesktopServices::openUrl( QUrl( "http://www.ros.org/wiki/rviz" )); 00572 } 00573 00574 QWidget* VisualizationFrame::getParentWindow() 00575 { 00576 return this; 00577 } 00578 00579 PanelDockWidget* VisualizationFrame::addPane( const std::string& name, QWidget* panel, Qt::DockWidgetArea area, bool floating ) 00580 { 00581 QString q_name = QString::fromStdString( name ); 00582 PanelDockWidget *dock; 00583 dock = new PanelDockWidget( q_name, this ); 00584 dock->setWidget( panel ); 00585 dock->setFloating( floating ); 00586 dock->setObjectName( q_name ); 00587 addDockWidget( area, dock ); 00588 view_menu_->addAction( dock->toggleViewAction() ); 00589 return dock; 00590 } 00591 00592 }