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
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
00170 if (line.isEmpty())
00171 continue;
00172
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
00191 if (qApp->argc() < 2)
00192 return false;
00193
00194 QString gui_dir, elements_dir;
00195 QStringList args = qApp->arguments();
00196
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
00271
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
00355
00356
00357
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
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 }