$search
00001 #include "editor.h" 00002 #include "ui_editor.h" 00003 00004 #include <QGraphicsScene> 00005 #include <QGraphicsView> 00006 #include <QGraphicsPixmapItem> 00007 #include <QFile> 00008 #include <QDebug> 00009 #include <QDomDocument> 00010 #include <QDomElement> 00011 #include <QFileDialog> 00012 #include <QMessageBox> 00013 #include <QProcess> 00014 #include <QDesktopServices> 00015 #include <QUrl> 00016 00017 #include "../model/arena.h" 00018 #include "../model/arenaelement.h" 00019 #include "../model/arenaelementtype.h" 00020 #include "../model/arenaelementtyperegistry.h" 00021 00022 #include "arenascene.h" 00023 #include "arenaview.h" 00024 #include "arenasceneelement.h" 00025 #include "arenaelementtypescene.h" 00026 00027 #include "../global.h" 00028 00029 Editor::Editor(QWidget *parent) 00030 : QMainWindow(parent) 00031 , m_ui(new Ui::MainWindow) 00032 , m_loadingSuccessful(false) 00033 { 00034 m_ui->setupUi(this); 00035 00036 if (parseRosPackageDirsFromCommandLineArguments()) 00037 { 00038 qDebug() << "[Rescue Arena Designer] Successfully parsed hector_arena_gui and hector_arena_elements package dirs from command line arguments."; 00039 } 00040 else 00041 { 00042 qDebug() << "[Rescue Arena Designer] Using rospack to find hector_arena_gui and hector_arena_elements packages."; 00043 m_hector_arena_gui_package_dir = findRosPackage("hector_nist_arena_designer"); 00044 // findRosPackage() pops up an error message if necessary 00045 if (m_hector_arena_gui_package_dir.isEmpty()) 00046 qApp->quit(); 00047 00048 m_hector_arena_elements_package_dir = findRosPackage("hector_nist_arena_elements"); 00049 if (m_hector_arena_elements_package_dir.isEmpty()) 00050 qApp->quit(); 00051 00052 m_hector_arena_worlds_package_dir = findRosPackage("hector_nist_arena_worlds"); 00053 if (m_hector_arena_worlds_package_dir.isEmpty()) 00054 qApp->quit(); 00055 } 00056 00057 QString arenaElementDir = m_hector_arena_elements_package_dir + "/elements"; 00058 00059 m_openSaveDir = m_hector_arena_worlds_package_dir + "/arenas"; 00060 m_exportDir = m_hector_arena_worlds_package_dir + "/worlds"; 00061 00062 m_typeRegistry = new ArenaElementTypeRegistry(arenaElementDir); 00063 m_arena = new Arena(m_typeRegistry); 00064 00065 m_typeScene = new ArenaElementTypeScene(m_typeRegistry); 00066 m_arenaScene = new ArenaScene(m_arena); 00067 m_controller = new ArenaController(m_arena, m_arenaScene); 00068 00069 m_ui->elementView->setScene(m_typeScene); 00070 00071 m_arenaView = new ArenaView(m_controller, this); 00072 m_arenaView->setScene(m_arenaScene); 00073 m_arenaView->setArena(m_arena); 00074 m_arenaView->setAcceptDrops(true); 00075 m_arenaView->setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform); 00076 00077 m_ui->arenaViewContainer->layout()->addWidget(m_arenaView); 00078 m_ui->infoRot->setTextFormat(Qt::RichText); 00079 00080 qDebug() << "[Rescue Arena Designer] hector_arena_gui = " << m_hector_arena_gui_package_dir; 00081 qDebug() << "[Rescue Arena Designer] hector_arena_elements = " << m_hector_arena_elements_package_dir; 00082 qDebug() << "[Rescue Arena Designer] hector_arena_worlds = " << m_hector_arena_worlds_package_dir; 00083 00084 loadConfig(m_hector_arena_gui_package_dir + "/config.xml"); 00085 00086 connect(m_ui->actionRotateClockwise, SIGNAL(triggered()), 00087 this, SLOT(slotRotateClockwise())); 00088 00089 connect(m_ui->actionRotateCounterClockwise, SIGNAL(triggered()), 00090 this, SLOT(slotRotateCounterClockwise())); 00091 00092 connect(m_ui->actionRemove, SIGNAL(triggered()), 00093 this, SLOT(slotRemove())); 00094 00095 connect(m_ui->infoItemMountPoint, SIGNAL(currentIndexChanged(int)), 00096 this, SLOT(slotItemMountPointChanged(int))); 00097 00098 00099 connect(m_ui->actionNew, SIGNAL(triggered()), 00100 this, SLOT(slotNew())); 00101 00102 connect(m_ui->actionOpen, SIGNAL(triggered()), 00103 this, SLOT(slotOpen())); 00104 00105 connect(m_ui->actionSave, SIGNAL(triggered()), 00106 this, SLOT(slotSave())); 00107 00108 connect(m_ui->actionSaveAs, SIGNAL(triggered()), 00109 this, SLOT(slotSaveAs())); 00110 00111 connect(m_ui->actionExport, SIGNAL(triggered()), 00112 this, SLOT(slotExport())); 00113 00114 connect(m_ui->actionExportSdf, SIGNAL(triggered()), 00115 this, SLOT(slotExportSdf())); 00116 00117 connect(m_ui->actionShowDocumentation, SIGNAL(triggered()), 00118 this, SLOT(slotShowDocumentation())); 00119 00120 connect(m_arena, SIGNAL(modified()), 00121 this, SLOT(slotSelectionChanged())); 00122 00123 connect(m_arenaScene, SIGNAL(selectionChanged()), 00124 this, SLOT(slotSelectionChanged())); 00125 00126 connect(m_ui->actionZoomIn, SIGNAL(triggered()), 00127 m_arenaView, SLOT(slotZoomIn())); 00128 00129 connect(m_ui->actionZoomOut, SIGNAL(triggered()), 00130 m_arenaView, SLOT(slotZoomOut())); 00131 00132 connect(m_typeScene, SIGNAL(elementHovered(ArenaElement*)), 00133 this, SLOT(slotElementTypeHovered(ArenaElement*))); 00134 00135 00136 connect(m_arena, SIGNAL(modified()), 00137 this, SLOT(slotModified())); 00138 00139 setUnifiedTitleAndToolBarOnMac(true); 00140 00141 m_windowTitlePrefix = windowTitle(); 00142 setWindowTitle(m_windowTitlePrefix + " - untitled.raf[*]"); 00143 00144 slotSelectionChanged(); 00145 setWindowModified(false); 00146 } 00147 00148 QString Editor::findRosPackage(const QString& name) 00149 { 00150 QString result; 00151 00152 QProcess rosFind; 00153 rosFind.start("rospack", QStringList() << "find" << name); 00154 if (!rosFind.waitForFinished()) 00155 { 00156 if (rosFind.error() == QProcess::FailedToStart) 00157 QMessageBox::critical(this, "Can't find rospack", 00158 "The program 'rospack' is missing or not in your PATH. Check your ROS installation."); 00159 else 00160 QMessageBox::critical(this, "Can't find " + name + " ROS package", 00161 "The program 'rospack' used to locate ROS packages didn't terminate correctly. Check your ROS installation."); 00162 return ""; 00163 } 00164 00165 QByteArray rosFindOutput = rosFind.readAll(); 00166 QList<QByteArray> rosFindOutputLines = rosFindOutput.split('\n'); 00167 foreach (QByteArray line, rosFindOutputLines) 00168 { 00169 // last line also ends with "\n", thus there is one extra empty line that we'll ignore 00170 if (line.isEmpty()) 00171 continue; 00172 // Debug output from rospack starts with [rospack] 00173 if (line.startsWith("[")) 00174 continue; 00175 result = line; 00176 } 00177 00178 if (result.isEmpty()) 00179 { 00180 QMessageBox::critical(this, "Can't find " + name + " ROS package", 00181 "The program 'rospack' could not locate the package " + name + ". Check your installation."); 00182 return ""; 00183 } 00184 00185 return result; 00186 } 00187 00188 bool Editor::parseRosPackageDirsFromCommandLineArguments() 00189 { 00190 // First check if any arguments were specified 00191 if (qApp->argc() < 2) 00192 return false; 00193 00194 QString gui_dir, elements_dir; 00195 QStringList args = qApp->arguments(); 00196 // Ignore program name (first argument) 00197 args.removeFirst(); 00198 foreach (QString arg, args) 00199 { 00200 QStringList splitted = arg.split('='); 00201 if (splitted.size() != 2) 00202 continue; 00203 if (splitted.at(0) == "-hector_arena_gui_dir") 00204 gui_dir = splitted.at(1); 00205 else if (splitted.at(0) == "-hector_arena_elements_dir") 00206 elements_dir = splitted.at(1); 00207 } 00208 00209 if (!gui_dir.isEmpty() && !elements_dir.isEmpty()) 00210 { 00211 if (!QDir(gui_dir).exists()) 00212 { 00213 QMessageBox::critical(this, "Invalid path name", 00214 "The path " + gui_dir + " doesn't exist. Check your -hector_arena_gui command line argument. Falling back to 'rospack find'..."); 00215 return false; 00216 } 00217 if (!QDir(elements_dir).exists()) 00218 { 00219 QMessageBox::critical(this, "Invalid path name", 00220 "The path " + elements_dir + " doesn't exist. Check your -hector_arena_elements command line argument. Falling back to 'rospack find'..."); 00221 return false; 00222 } 00223 m_hector_arena_gui_package_dir = gui_dir; 00224 m_hector_arena_elements_package_dir = elements_dir; 00225 return true; 00226 } 00227 else 00228 { 00229 qDebug() << "[Rescue Arena Designer] Invalid command line arguments:"; 00230 qDebug() << "[Rescue Arena Designer]" << args; 00231 } 00232 return false; 00233 } 00234 00235 void Editor::loadConfig(const QString &configFile) 00236 { 00237 QFile in(configFile); 00238 in.open(QFile::ReadOnly); 00239 QDomDocument doc; 00240 bool configValid = doc.setContent(&in); 00241 in.close(); 00242 00243 if (configValid) 00244 { 00245 QDomElement rootElement = doc.firstChildElement(); 00246 Q_ASSERT(rootElement.tagName() == "config"); 00247 00248 QDomNodeList childNodes = rootElement.childNodes(); 00249 for (int i = 0; i < childNodes.count(); i++) 00250 { 00251 QDomElement childElement = childNodes.at(i).toElement(); 00252 if (childElement.tagName() == "arena-elements") 00253 { 00254 m_typeScene->loadConfig(childElement); 00255 } 00256 } 00257 m_loadingSuccessful = true; 00258 } 00259 else 00260 { 00261 QMessageBox::warning(this, "Failed to load config.xml", "Your config.xml seems to be an invalid XML file. Make sure it is valid and try again."); 00262 } 00263 } 00264 00265 Editor::~Editor() 00266 { 00267 delete m_typeScene; 00268 delete m_arenaScene; 00269 delete m_arena; 00270 // This must be deleted last because it receives signals when e.g. an element 00271 // is removed 00272 delete m_ui; 00273 } 00274 00275 void Editor::slotRotateClockwise() 00276 { 00277 foreach (ArenaSceneElement *selection, m_arenaScene->selectedElements()) 00278 { 00279 ArenaElement *element = selection->element(); 00280 element->setRotation(element->rotation() + 90); 00281 } 00282 } 00283 00284 void Editor::slotRotateCounterClockwise() 00285 { 00286 foreach (ArenaSceneElement *selection, m_arenaScene->selectedElements()) 00287 { 00288 ArenaElement *element = selection->element(); 00289 element->setRotation(element->rotation() - 90); 00290 } 00291 } 00292 00293 void Editor::slotRemove() 00294 { 00295 foreach (ArenaSceneElement *selection, m_arenaScene->selectedElements()) 00296 { 00297 ArenaElement *element = selection->element(); 00298 m_arena->removeElement(element); 00299 delete element; 00300 } 00301 } 00302 00303 void Editor::slotSelectionChanged() 00304 { 00305 QList<ArenaSceneElement*> _selectedElements = m_arenaScene->selectedElements(); 00306 bool hasSingleSelection = _selectedElements.count() == 1; 00307 m_ui->infoWidget->setVisible(hasSingleSelection); 00308 m_ui->infoHintLabel->setVisible(!hasSingleSelection); 00309 if (!hasSingleSelection) 00310 return; 00311 00312 ArenaSceneElement *selection = dynamic_cast<ArenaSceneElement*>(_selectedElements.first()); 00313 00314 m_ui->infoWidget->show(); 00315 m_ui->infoHintLabel->hide(); 00316 00317 ArenaElement *element = selection->element(); 00318 const ArenaElementType *type = element->type(); 00319 00320 QString name; 00321 QTextStream(&name) << "(" << element->pos().x() << ", " << element->pos().y() << ")"; 00322 m_ui->infoPos->setText(name); 00323 00324 QString rot; 00325 QTextStream(&rot) << element->rotation() << "°"; 00326 m_ui->infoRot->setText(rot); 00327 00328 if (element->isItem()) 00329 { 00330 m_ui->infoOffsetLabel->setVisible(true); 00331 m_ui->infoOffset->setVisible(true); 00332 QString offsetText; 00333 QTextStream stream(&offsetText); 00334 stream.setRealNumberNotation(QTextStream::FixedNotation); 00335 stream.setRealNumberPrecision(3); 00336 stream << "(" << element->itemOffset().x() << ", " << element->itemOffset().y() << ")"; 00337 m_ui->infoOffset->setText(offsetText); 00338 } 00339 else 00340 { 00341 m_ui->infoOffsetLabel->setVisible(false); 00342 m_ui->infoOffset->setVisible(false); 00343 } 00344 00345 if (element->isMountableItem()) 00346 { 00347 m_ui->infoItemMountPointLabel->setVisible(true); 00348 m_ui->infoItemMountPoint->setVisible(true); 00349 m_ui->infoItemMountPoint->clear(); 00350 ArenaElement *contextElement = m_arena->contextElement(element); 00351 m_ui->infoItemMountPoint->setEnabled(contextElement); 00352 if (contextElement) 00353 { 00354 // Block signals, otherwise modifying the item mount point list will 00355 // cause an infinite loop (slotSelectionChanged() is called whenever 00356 // something in the arena changed, such as the item mount point index of 00357 // an element) 00358 m_ui->infoItemMountPoint->blockSignals(true); 00359 QList<ItemMountPoint> itemMountPoints = contextElement->type()->itemMountPoints(); 00360 foreach (ItemMountPoint hole, itemMountPoints) 00361 m_ui->infoItemMountPoint->addItem(hole.first); 00362 if (element->itemMountPoint() >= 0) 00363 m_ui->infoItemMountPoint->setCurrentIndex(element->itemMountPoint()); 00364 m_ui->infoItemMountPoint->blockSignals(false); 00365 } 00366 } 00367 else 00368 { 00369 m_ui->infoItemMountPointLabel->setVisible(false); 00370 m_ui->infoItemMountPoint->setVisible(false); 00371 } 00372 00373 m_ui->metaInfoBox->setMetaInfos(type->metaInfos()); 00374 } 00375 00376 void Editor::slotItemMountPointChanged(int index) 00377 { 00378 if (index < 0) 00379 return; 00380 QList<ArenaSceneElement*> _selectedElements = m_arenaScene->selectedElements(); 00381 bool hasSingleSelection = _selectedElements.count() == 1; 00382 if (!hasSingleSelection) 00383 return; 00384 00385 ArenaSceneElement *selection = dynamic_cast<ArenaSceneElement*>(_selectedElements.first()); 00386 ArenaElement *element = selection->element(); 00387 00388 element->setItemMountPoint(index); 00389 } 00390 00391 void Editor::askToSaveChangesIfModified() 00392 { 00393 if (isWindowModified()) 00394 { 00395 QString text = "Do you want to save changes to the current arena?"; 00396 if (QMessageBox::question(this, "Save Arena?", text, QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes) == QMessageBox::Yes) 00397 slotSave(); 00398 } 00399 } 00400 00401 void Editor::slotModified() 00402 { 00403 setWindowModified(true); 00404 } 00405 00406 void Editor::slotNew() 00407 { 00408 askToSaveChangesIfModified(); 00409 m_arena->clear(); 00410 m_currentArenaFile.clear(); 00411 00412 setWindowModified(false); 00413 } 00414 00415 void Editor::slotOpen() 00416 { 00417 askToSaveChangesIfModified(); 00418 QString target = QFileDialog::getOpenFileName(this, "Select arena file to open", m_openSaveDir, ".raf File (*.raf)"); 00419 if (!target.isEmpty()) 00420 { 00421 m_arena->clear(); 00422 setCurrentArenaFile(target); 00423 m_arena->load(m_currentArenaFile); 00424 setWindowModified(false); 00425 } 00426 } 00427 00428 void Editor::slotSave() 00429 { 00430 if (!m_currentArenaFile.isEmpty()) 00431 m_arena->save(m_currentArenaFile); 00432 else 00433 slotSaveAs(); 00434 00435 setWindowModified(false); 00436 } 00437 00438 void Editor::slotSaveAs() 00439 { 00440 QString target = QFileDialog::getSaveFileName(this, "Select file to save arena to", m_openSaveDir, ".raf File (*.raf)"); 00441 if (!target.isEmpty()) 00442 { 00443 m_currentArenaFile = target; 00444 m_arena->save(m_currentArenaFile); 00445 setWindowModified(false); 00446 } 00447 } 00448 00449 void Editor::slotExport() 00450 { 00451 QString target = QFileDialog::getSaveFileName(this, "Select file to export arena to", m_exportDir, "Gazebo .world File (*.world)"); 00452 if (!target.isEmpty()) 00453 { 00454 m_arena->saveWorld(target); 00455 m_exportDir = QFileInfo(target).path(); 00456 } 00457 } 00458 00459 void Editor::slotExportSdf() 00460 { 00461 QString target = QFileDialog::getSaveFileName(this, "Select file to export arena to", m_exportDir, "Gazebo .world File (*.world)"); 00462 if (!target.isEmpty()) 00463 { 00464 m_arena->saveWorldSdf(target); 00465 m_exportDir = QFileInfo(target).path(); 00466 } 00467 } 00468 00469 void Editor::slotShowDocumentation() 00470 { 00471 QString indexHtml = QString("file://") + m_hector_arena_gui_package_dir + "/doc/index.html"; 00472 QDesktopServices::openUrl(indexHtml); 00473 } 00474 00475 void Editor::slotElementTypeHovered(ArenaElement *element) 00476 { 00477 // Show status bar info for 1s 00478 statusBar()->showMessage(element->type()->humanReadableName(), 1000); 00479 } 00480 00481 void Editor::setCurrentArenaFile(QString fileName) 00482 { 00483 m_currentArenaFile = fileName; 00484 setWindowTitle(m_windowTitlePrefix + " - " + QFileInfo(fileName).fileName() + "[*]"); 00485 00486 m_openSaveDir = QFileInfo(m_currentArenaFile).path(); 00487 } 00488 00489 void Editor::closeEvent(QCloseEvent *event) 00490 { 00491 askToSaveChangesIfModified(); 00492 00493 QMainWindow::closeEvent(event); 00494 }