editor.cpp
Go to the documentation of this file.
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() << "&deg;";
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 }


hector_nist_arena_designer
Author(s): Johannes Simon, Stefan Kohlbrecher
autogenerated on Mon Oct 6 2014 00:26:31