editor.cpp
Go to the documentation of this file.
1 #include "editor.h"
2 #include "ui_editor.h"
3 
4 #include <QGraphicsScene>
5 #include <QGraphicsView>
6 #include <QGraphicsPixmapItem>
7 #include <QFile>
8 #include <QDebug>
9 #include <QDomDocument>
10 #include <QDomElement>
11 #include <QFileDialog>
12 #include <QMessageBox>
13 #include <QProcess>
14 #include <QDesktopServices>
15 #include <QUrl>
16 
17 #include "../model/arena.h"
18 #include "../model/arenaelement.h"
19 #include "../model/arenaelementtype.h"
20 #include "../model/arenaelementtyperegistry.h"
21 
22 #include "arenascene.h"
23 #include "arenaview.h"
24 #include "arenasceneelement.h"
25 #include "arenaelementtypescene.h"
26 
27 #include "../global.h"
28 
29 Editor::Editor(QWidget *parent)
30  : QMainWindow(parent)
31  , m_ui(new Ui::MainWindow)
32  , m_loadingSuccessful(false)
33 {
34  m_ui->setupUi(this);
35 
37  {
38  qDebug() << "[Rescue Arena Designer] Successfully parsed hector_arena_gui and hector_arena_elements package dirs from command line arguments.";
39  }
40  else
41  {
42  qDebug() << "[Rescue Arena Designer] Using rospack to find hector_arena_gui and hector_arena_elements packages.";
43  m_hector_arena_gui_package_dir = findRosPackage("hector_nist_arena_designer");
44  // findRosPackage() pops up an error message if necessary
45  if (m_hector_arena_gui_package_dir.isEmpty())
46  qApp->quit();
47 
48  m_hector_arena_elements_package_dir = findRosPackage("hector_nist_arena_elements");
50  qApp->quit();
51 
52  m_hector_arena_worlds_package_dir = findRosPackage("hector_nist_arena_worlds");
54  qApp->quit();
55  }
56 
57  QString arenaElementDir = m_hector_arena_elements_package_dir + "/elements";
58 
61 
62  m_typeRegistry = new ArenaElementTypeRegistry(arenaElementDir);
64 
68 
69  m_ui->elementView->setScene(m_typeScene);
70 
71  m_arenaView = new ArenaView(m_controller, this);
72  m_arenaView->setScene(m_arenaScene);
74  m_arenaView->setAcceptDrops(true);
75  m_arenaView->setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform);
76 
77  m_ui->arenaViewContainer->layout()->addWidget(m_arenaView);
78  m_ui->infoRot->setTextFormat(Qt::RichText);
79 
80  qDebug() << "[Rescue Arena Designer] hector_arena_gui = " << m_hector_arena_gui_package_dir;
81  qDebug() << "[Rescue Arena Designer] hector_arena_elements = " << m_hector_arena_elements_package_dir;
82  qDebug() << "[Rescue Arena Designer] hector_arena_worlds = " << m_hector_arena_worlds_package_dir;
83 
85 
86  connect(m_ui->actionRotateClockwise, SIGNAL(triggered()),
87  this, SLOT(slotRotateClockwise()));
88 
89  connect(m_ui->actionRotateCounterClockwise, SIGNAL(triggered()),
90  this, SLOT(slotRotateCounterClockwise()));
91 
92  connect(m_ui->actionRemove, SIGNAL(triggered()),
93  this, SLOT(slotRemove()));
94 
95  connect(m_ui->infoItemMountPoint, SIGNAL(currentIndexChanged(int)),
96  this, SLOT(slotItemMountPointChanged(int)));
97 
98 
99  connect(m_ui->actionNew, SIGNAL(triggered()),
100  this, SLOT(slotNew()));
101 
102  connect(m_ui->actionOpen, SIGNAL(triggered()),
103  this, SLOT(slotOpen()));
104 
105  connect(m_ui->actionSave, SIGNAL(triggered()),
106  this, SLOT(slotSave()));
107 
108  connect(m_ui->actionSaveAs, SIGNAL(triggered()),
109  this, SLOT(slotSaveAs()));
110 
111  connect(m_ui->actionExport, SIGNAL(triggered()),
112  this, SLOT(slotExport()));
113 
114  connect(m_ui->actionExportSdf, SIGNAL(triggered()),
115  this, SLOT(slotExportSdf()));
116 
117  connect(m_ui->actionShowDocumentation, SIGNAL(triggered()),
118  this, SLOT(slotShowDocumentation()));
119 
120  connect(m_arena, SIGNAL(modified()),
121  this, SLOT(slotSelectionChanged()));
122 
123  connect(m_arenaScene, SIGNAL(selectionChanged()),
124  this, SLOT(slotSelectionChanged()));
125 
126  connect(m_ui->actionZoomIn, SIGNAL(triggered()),
127  m_arenaView, SLOT(slotZoomIn()));
128 
129  connect(m_ui->actionZoomOut, SIGNAL(triggered()),
130  m_arenaView, SLOT(slotZoomOut()));
131 
132  connect(m_typeScene, SIGNAL(elementHovered(ArenaElement*)),
133  this, SLOT(slotElementTypeHovered(ArenaElement*)));
134 
135 
136  connect(m_arena, SIGNAL(modified()),
137  this, SLOT(slotModified()));
138 
139  setUnifiedTitleAndToolBarOnMac(true);
140 
141  m_windowTitlePrefix = windowTitle();
142  setWindowTitle(m_windowTitlePrefix + " - untitled.raf[*]");
143 
145  setWindowModified(false);
146 }
147 
148 QString Editor::findRosPackage(const QString& name)
149 {
150  QString result;
151 
152  QProcess rosFind;
153  rosFind.start("rospack", QStringList() << "find" << name);
154  if (!rosFind.waitForFinished())
155  {
156  if (rosFind.error() == QProcess::FailedToStart)
157  QMessageBox::critical(this, "Can't find rospack",
158  "The program 'rospack' is missing or not in your PATH. Check your ROS installation.");
159  else
160  QMessageBox::critical(this, "Can't find " + name + " ROS package",
161  "The program 'rospack' used to locate ROS packages didn't terminate correctly. Check your ROS installation.");
162  return "";
163  }
164 
165  QByteArray rosFindOutput = rosFind.readAll();
166  QList<QByteArray> rosFindOutputLines = rosFindOutput.split('\n');
167  foreach (QByteArray line, rosFindOutputLines)
168  {
169  // last line also ends with "\n", thus there is one extra empty line that we'll ignore
170  if (line.isEmpty())
171  continue;
172  // Debug output from rospack starts with [rospack]
173  if (line.startsWith("["))
174  continue;
175  result = line;
176  }
177 
178  if (result.isEmpty())
179  {
180  QMessageBox::critical(this, "Can't find " + name + " ROS package",
181  "The program 'rospack' could not locate the package " + name + ". Check your installation.");
182  return "";
183  }
184 
185  return result;
186 }
187 
189 {
190  // First check if any arguments were specified
191  if (qApp->arguments().size() < 2)
192  return false;
193 
194  QString gui_dir, elements_dir;
195  QStringList args = qApp->arguments();
196  // Ignore program name (first argument)
197  args.removeFirst();
198  foreach (QString arg, args)
199  {
200  QStringList splitted = arg.split('=');
201  if (splitted.size() != 2)
202  continue;
203  if (splitted.at(0) == "-hector_arena_gui_dir")
204  gui_dir = splitted.at(1);
205  else if (splitted.at(0) == "-hector_arena_elements_dir")
206  elements_dir = splitted.at(1);
207  }
208 
209  if (!gui_dir.isEmpty() && !elements_dir.isEmpty())
210  {
211  if (!QDir(gui_dir).exists())
212  {
213  QMessageBox::critical(this, "Invalid path name",
214  "The path " + gui_dir + " doesn't exist. Check your -hector_arena_gui command line argument. Falling back to 'rospack find'...");
215  return false;
216  }
217  if (!QDir(elements_dir).exists())
218  {
219  QMessageBox::critical(this, "Invalid path name",
220  "The path " + elements_dir + " doesn't exist. Check your -hector_arena_elements command line argument. Falling back to 'rospack find'...");
221  return false;
222  }
225  return true;
226  }
227  else
228  {
229  qDebug() << "[Rescue Arena Designer] Invalid command line arguments:";
230  qDebug() << "[Rescue Arena Designer]" << args;
231  }
232  return false;
233 }
234 
235 void Editor::loadConfig(const QString &configFile)
236 {
237  QFile in(configFile);
238  in.open(QFile::ReadOnly);
239  QDomDocument doc;
240  bool configValid = doc.setContent(&in);
241  in.close();
242 
243  if (configValid)
244  {
245  QDomElement rootElement = doc.firstChildElement();
246  Q_ASSERT(rootElement.tagName() == "config");
247 
248  QDomNodeList childNodes = rootElement.childNodes();
249  for (int i = 0; i < childNodes.count(); i++)
250  {
251  QDomElement childElement = childNodes.at(i).toElement();
252  if (childElement.tagName() == "arena-elements")
253  {
254  m_typeScene->loadConfig(childElement);
255  }
256  }
257  m_loadingSuccessful = true;
258  }
259  else
260  {
261  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.");
262  }
263 }
264 
266 {
267  delete m_typeScene;
268  delete m_arenaScene;
269  delete m_arena;
270  // This must be deleted last because it receives signals when e.g. an element
271  // is removed
272  delete m_ui;
273 }
274 
276 {
277  foreach (ArenaSceneElement *selection, m_arenaScene->selectedElements())
278  {
279  ArenaElement *element = selection->element();
280  element->setRotation(element->rotation() + 90);
281  }
282 }
283 
285 {
286  foreach (ArenaSceneElement *selection, m_arenaScene->selectedElements())
287  {
288  ArenaElement *element = selection->element();
289  element->setRotation(element->rotation() - 90);
290  }
291 }
292 
294 {
295  foreach (ArenaSceneElement *selection, m_arenaScene->selectedElements())
296  {
297  ArenaElement *element = selection->element();
298  m_arena->removeElement(element);
299  delete element;
300  }
301 }
302 
304 {
305  QList<ArenaSceneElement*> _selectedElements = m_arenaScene->selectedElements();
306  bool hasSingleSelection = _selectedElements.count() == 1;
307  m_ui->infoWidget->setVisible(hasSingleSelection);
308  m_ui->infoHintLabel->setVisible(!hasSingleSelection);
309  if (!hasSingleSelection)
310  return;
311 
312  ArenaSceneElement *selection = dynamic_cast<ArenaSceneElement*>(_selectedElements.first());
313 
314  m_ui->infoWidget->show();
315  m_ui->infoHintLabel->hide();
316 
317  ArenaElement *element = selection->element();
318  const ArenaElementType *type = element->type();
319 
320  QString name;
321  QTextStream(&name) << "(" << element->pos().x() << ", " << element->pos().y() << ")";
322  m_ui->infoPos->setText(name);
323 
324  QString rot;
325  QTextStream(&rot) << element->rotation() << "&deg;";
326  m_ui->infoRot->setText(rot);
327 
328  if (element->isItem())
329  {
330  m_ui->infoOffsetLabel->setVisible(true);
331  m_ui->infoOffset->setVisible(true);
332  QString offsetText;
333  QTextStream stream(&offsetText);
334  stream.setRealNumberNotation(QTextStream::FixedNotation);
335  stream.setRealNumberPrecision(3);
336  stream << "(" << element->itemOffset().x() << ", " << element->itemOffset().y() << ")";
337  m_ui->infoOffset->setText(offsetText);
338  }
339  else
340  {
341  m_ui->infoOffsetLabel->setVisible(false);
342  m_ui->infoOffset->setVisible(false);
343  }
344 
345  if (element->isMountableItem())
346  {
347  m_ui->infoItemMountPointLabel->setVisible(true);
348  m_ui->infoItemMountPoint->setVisible(true);
349  m_ui->infoItemMountPoint->clear();
350  ArenaElement *contextElement = m_arena->contextElement(element);
351  m_ui->infoItemMountPoint->setEnabled(contextElement);
352  if (contextElement)
353  {
354  // Block signals, otherwise modifying the item mount point list will
355  // cause an infinite loop (slotSelectionChanged() is called whenever
356  // something in the arena changed, such as the item mount point index of
357  // an element)
358  m_ui->infoItemMountPoint->blockSignals(true);
359  QList<ItemMountPoint> itemMountPoints = contextElement->type()->itemMountPoints();
360  foreach (ItemMountPoint hole, itemMountPoints)
361  m_ui->infoItemMountPoint->addItem(hole.first);
362  if (element->itemMountPoint() >= 0)
363  m_ui->infoItemMountPoint->setCurrentIndex(element->itemMountPoint());
364  m_ui->infoItemMountPoint->blockSignals(false);
365  }
366  }
367  else
368  {
369  m_ui->infoItemMountPointLabel->setVisible(false);
370  m_ui->infoItemMountPoint->setVisible(false);
371  }
372 
373  m_ui->metaInfoBox->setMetaInfos(type->metaInfos());
374 }
375 
377 {
378  if (index < 0)
379  return;
380  QList<ArenaSceneElement*> _selectedElements = m_arenaScene->selectedElements();
381  bool hasSingleSelection = _selectedElements.count() == 1;
382  if (!hasSingleSelection)
383  return;
384 
385  ArenaSceneElement *selection = dynamic_cast<ArenaSceneElement*>(_selectedElements.first());
386  ArenaElement *element = selection->element();
387 
388  element->setItemMountPoint(index);
389 }
390 
392 {
393  if (isWindowModified())
394  {
395  QString text = "Do you want to save changes to the current arena?";
396  if (QMessageBox::question(this, "Save Arena?", text, QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes) == QMessageBox::Yes)
397  slotSave();
398  }
399 }
400 
402 {
403  setWindowModified(true);
404 }
405 
407 {
409  m_arena->clear();
410  m_currentArenaFile.clear();
411 
412  setWindowModified(false);
413 }
414 
416 {
418  QString target = QFileDialog::getOpenFileName(this, "Select arena file to open", m_openSaveDir, ".raf File (*.raf)");
419  if (!target.isEmpty())
420  {
421  m_arena->clear();
422  setCurrentArenaFile(target);
424  setWindowModified(false);
425  }
426 }
427 
429 {
430  if (!m_currentArenaFile.isEmpty())
432  else
433  slotSaveAs();
434 
435  setWindowModified(false);
436 }
437 
439 {
440  QString target = QFileDialog::getSaveFileName(this, "Select file to save arena to", m_openSaveDir, ".raf File (*.raf)");
441  if (!target.isEmpty())
442  {
443  m_currentArenaFile = target;
445  setWindowModified(false);
446  }
447 }
448 
450 {
451  QString target = QFileDialog::getSaveFileName(this, "Select file to export arena to", m_exportDir, "Gazebo .world File (*.world)");
452  if (!target.isEmpty())
453  {
454  m_arena->saveWorld(target);
455  m_exportDir = QFileInfo(target).path();
456  }
457 }
458 
460 {
461  QString target = QFileDialog::getSaveFileName(this, "Select file to export arena to", m_exportDir, "Gazebo .world File (*.world)");
462  if (!target.isEmpty())
463  {
464  m_arena->saveWorldSdf(target);
465  m_exportDir = QFileInfo(target).path();
466  }
467 }
468 
470 {
471  QString indexHtml = QString("file://") + m_hector_arena_gui_package_dir + "/doc/index.html";
472  QDesktopServices::openUrl(indexHtml);
473 }
474 
476 {
477  // Show status bar info for 1s
478  statusBar()->showMessage(element->type()->humanReadableName(), 1000);
479 }
480 
481 void Editor::setCurrentArenaFile(QString fileName)
482 {
483  m_currentArenaFile = fileName;
484  setWindowTitle(m_windowTitlePrefix + " - " + QFileInfo(fileName).fileName() + "[*]");
485 
486  m_openSaveDir = QFileInfo(m_currentArenaFile).path();
487 }
488 
489 void Editor::closeEvent(QCloseEvent *event)
490 {
492 
493  QMainWindow::closeEvent(event);
494 }
QString findRosPackage(const QString &name)
Definition: editor.cpp:148
void slotOpen()
Definition: editor.cpp:415
ArenaElementType const * type() const
Definition: arenaelement.h:43
void loadConfig(const QDomElement &arenaElements)
void slotSave()
Definition: editor.cpp:428
void slotModified()
Definition: editor.cpp:401
ArenaView * m_arenaView
Definition: editor.h:81
void removeElement(ArenaElement *element)
Definition: arena.cpp:461
QList< ItemMountPoint > itemMountPoints() const
void slotElementTypeHovered(ArenaElement *element)
Definition: editor.cpp:475
void setCurrentArenaFile(QString fileName)
Definition: editor.cpp:481
Definition: editor.h:7
void setItemMountPoint(int mountPoint)
See itemMountPoint(). Use -1 to set index to "unspecified".
void clear()
Removes all elements from this arena.
Definition: arena.cpp:469
QList< ArenaSceneElement * > selectedElements()
Definition: arenascene.cpp:61
int itemMountPoint() const
Definition: arenaelement.h:62
QString m_hector_arena_gui_package_dir
Definition: editor.h:84
Ui::MainWindow * m_ui
Definition: editor.h:70
void saveWorld(const QString &filename)
Writes .world format-compliant XML.
Definition: arena.cpp:112
void load(const QString &filename)
Deserializes this arena from XML.
Definition: arena.cpp:24
ArenaScene * m_arenaScene
Definition: editor.h:80
void askToSaveChangesIfModified()
Definition: editor.cpp:391
bool isItem() const
Convenience method returning true iff. this is an item element (freely movable)
void slotRotateClockwise()
Definition: editor.cpp:275
void slotSelectionChanged()
Definition: editor.cpp:303
QVector< QStringPair > metaInfos() const
void slotRotateCounterClockwise()
Definition: editor.cpp:284
void slotRemove()
Definition: editor.cpp:293
ArenaElementTypeScene * m_typeScene
Definition: editor.h:79
QString m_exportDir
Definition: editor.h:74
Editor(QWidget *parent=0)
Definition: editor.cpp:29
ArenaElement * element() const
ArenaElement * contextElement(ArenaElement *element) const
Definition: arena.cpp:498
Arena * m_arena
Definition: editor.h:77
Definition: arena.h:10
QString m_currentArenaFile
Definition: editor.h:76
QPair< QString, QPointF > ItemMountPoint
QPointF itemOffset() const
Returns the item&#39;s offset from the grid point with 0 <= x,y <= 1 ( (0,0) is bottom left) ...
Definition: arenaelement.h:55
void loadConfig(const QString &configFile)
Definition: editor.cpp:235
void slotExport()
Definition: editor.cpp:449
int rotation() const
Returns the rotation in degrees.
Definition: arenaelement.h:26
bool parseRosPackageDirsFromCommandLineArguments()
Definition: editor.cpp:188
void slotExportSdf()
Definition: editor.cpp:459
QString m_openSaveDir
Definition: editor.h:75
~Editor()
Definition: editor.cpp:265
void setRotation(int rotation)
Sets the rotation in degrees.
QPoint pos() const
Returns the position in arena coordinates.
Definition: arenaelement.h:22
void slotItemMountPointChanged(int index)
Definition: editor.cpp:376
bool m_loadingSuccessful
Definition: editor.h:72
QString m_windowTitlePrefix
Definition: editor.h:89
QString m_hector_arena_worlds_package_dir
Definition: editor.h:86
void setArena(Arena *arena)
Definition: arenaview.h:29
void slotSaveAs()
Definition: editor.cpp:438
ArenaElementTypeRegistry * m_typeRegistry
Definition: editor.h:82
void closeEvent(QCloseEvent *event)
Definition: editor.cpp:489
void saveWorldSdf(const QString &filename)
Definition: arena.cpp:263
void slotShowDocumentation()
Definition: editor.cpp:469
ArenaController * m_controller
Definition: editor.h:78
void slotNew()
Definition: editor.cpp:406
bool isMountableItem() const
Convenience method returning true iff. this is a mountable-item element.
QString humanReadableName() const
Returns the "Name" meta info field if it exists, otherwise name()
QString m_hector_arena_elements_package_dir
Definition: editor.h:85
void save(const QString &filename)
Serializes this arena in XML.
Definition: arena.cpp:88


hector_nist_arena_designer
Author(s): Stefan Kohlbrecher , Johannes Simon
autogenerated on Fri Aug 21 2020 10:45:27