dRonin  adbada4
dRonin GCS
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Groups Pages
uavsettingsimportexportmanager.cpp
Go to the documentation of this file.
1 
14 /*
15  * This program is free software; you can redistribute it and/or modify
16  * it under the terms of the GNU General Public License as published by
17  * the Free Software Foundation; either version 3 of the License, or
18  * (at your option) any later version.
19  *
20  * This program is distributed in the hope that it will be useful, but
21  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
22  * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
23  * for more details.
24  *
25  * You should have received a copy of the GNU General Public License along
26  * with this program; if not, see <http://www.gnu.org/licenses/>
27  *
28  * Additional note on redistribution: The copyright and license notices above
29  * must be maintained in each individual source file that is a derivative work
30  * of this source file; otherwise redistribution is prohibited.
31  */
32 
34 #include <QtPlugin>
35 #include <QStringList>
36 #include <QDebug>
37 #include <QCheckBox>
38 #include <QMainWindow>
39 #include "importsummary.h"
40 
41 // for menu item
44 #include <coreplugin/icore.h>
46 #include <QKeySequence>
47 
48 // for UAVObjects
53 
54 // for XML object
55 #include <QDomDocument>
56 #include <QXmlQuery>
57 
58 // for file dialog and error messages
59 #include <QFileDialog>
60 #include <QMessageBox>
61 
63 {
64  // Do nothing
65 }
66 
68  : QObject(parent)
69 {
70  auto core = Core::ICore::instance();
71 
72  // Add Menu entry
73  Core::ActionManager *am = core->actionManager();
75  Core::Command *cmd =
76  am->registerAction(new QAction(this), "UAVSettingsImportExportPlugin.UAVSettingsExport",
78  cmd->setDefaultKeySequence(QKeySequence("Ctrl+E"));
79  cmd->action()->setText(tr("Export UAV Settings..."));
81  connect(cmd->action(), SIGNAL(triggered(bool)), this, SLOT(exportUAVSettings()));
82 
83  cmd = am->registerAction(new QAction(this), "UAVSettingsImportExportPlugin.UAVSettingsImport",
85  cmd->setDefaultKeySequence(QKeySequence("Ctrl+I"));
86  cmd->action()->setText(tr("Import UAV Settings..."));
88  connect(cmd->action(), SIGNAL(triggered(bool)), this, SLOT(importUAVSettings()));
89 
91  cmd = am->registerAction(new QAction(this), "UAVSettingsImportExportPlugin.UAVDataExport",
93  cmd->action()->setText(tr("Export UAV Data..."));
94  ac->addAction(cmd, Core::Constants::G_HELP_HELP);
95  connect(cmd->action(), SIGNAL(triggered(bool)), this, SLOT(exportUAVData()));
96 }
97 
99 {
100  // problems with circular lib dep on windows, probably related to uploader
101 #ifndef Q_OS_WIN
102  auto pm = ExtensionSystem::PluginManager::instance();
103  auto telemetry = pm->getObject<TelemetryManager>();
104  // enabled/disabled commands when board is connected/disconnected
105  connect(telemetry, &TelemetryManager::connectedChanged, this,
106  &UAVSettingsImportExportManager::setCommandsEnabled);
107  setCommandsEnabled(telemetry->isConnected());
108 #endif
109 }
110 
111 bool UAVSettingsImportExportManager::importUAVSettings(const QByteArray &settings, bool quiet)
112 {
113  QDomDocument doc("UAVObjects");
114 
115  if (!doc.setContent(settings)) {
116  QMessageBox msgBox(dynamic_cast<QWidget *>(Core::ICore::instance()->mainWindow()));
117  msgBox.setText(tr("File Parsing Failed."));
118  msgBox.setInformativeText(tr("This file is not a correct XML file"));
119  msgBox.setStandardButtons(QMessageBox::Ok);
120  msgBox.exec();
121  return false;
122  }
123  emit importAboutToBegin();
124  qDebug() << "Import about to begin";
125 
126  // find the root of settings subtree
127  QDomElement root = doc.documentElement();
128  if (root.tagName() == "uavobjects") {
129  root = root.firstChildElement("settings");
130  }
131  if (root.isNull() || (root.tagName() != "settings")) {
132  QMessageBox msgBox(dynamic_cast<QWidget *>(Core::ICore::instance()->mainWindow()));
133  msgBox.setText(tr("Wrong file contents"));
134  msgBox.setInformativeText(tr("This file does not contain correct UAVSettings"));
135  msgBox.setStandardButtons(QMessageBox::Ok);
136  msgBox.exec();
137  return false;
138  }
139 
140  // We are now ok: setup the import summary dialog & update it as we
141  // go along.
142  ImportSummaryDialog swui(dynamic_cast<QWidget *>(Core::ICore::instance()->mainWindow()), quiet);
143 
144  ExtensionSystem::PluginManager *pm = ExtensionSystem::PluginManager::instance();
145  UAVObjectManager *boardObjManager = pm->getObject<UAVObjectManager>();
146  UAVObjectManager *importedObjectManager = new UAVObjectManager();
147 
148  swui.show();
149 
150  QDomNode node = root.firstChild();
151  while (!node.isNull()) {
152  QDomElement e = node.toElement();
153  if (e.tagName() == "object") {
154 
155  // - Read each object
156  QString uavObjectName = e.attribute("name");
157  uint uavObjectID = e.attribute("id").toUInt(NULL, 16);
158 
159  // Sanity Check:
160  UAVObject *obj = boardObjManager->getObject(uavObjectName);
161  UAVDataObject *dobj = dynamic_cast<UAVDataObject *>(obj);
162  if (obj == NULL) {
163  // This object is unknown!
164  qDebug() << "Object unknown:" << uavObjectName << uavObjectID;
165  swui.addLine(uavObjectName, "Error (Object unknown)", false);
166  } else if (dobj && !dobj->getIsPresentOnHardware()) {
167  swui.addLine(uavObjectName, "Error (Object not present on hw)", false);
168  } else {
169  // - Update each field
170  // - Issue and "updated" command
171  UAVDataObject *newObj = dobj->clone();
172 
173  bool error = false;
174  bool setError = false;
175  QDomNode field = node.firstChild();
176  while (!field.isNull()) {
177  QDomElement f = field.toElement();
178  if (f.tagName() == "field") {
179  UAVObjectField *uavfield = newObj->getField(f.attribute("name"));
180  if (uavfield) {
181  QStringList list = f.attribute("values").split(",");
182  if (list.length() == 1) {
183  if (false == uavfield->checkValue(f.attribute("values"))) {
184  qDebug() << "checkValue returned false on: " << uavObjectName
185  << f.attribute("values");
186  setError = true;
187  } else {
188  uavfield->setValue(f.attribute("values"));
189  }
190  } else {
191  // This is an enum:
192  int i = 0;
193  QStringList list = f.attribute("values").split(",");
194  foreach (QString element, list) {
195  if (false == uavfield->checkValue(element, i)) {
196  qDebug() << "checkValue(list) returned false on: "
197  << uavObjectName << list;
198  setError = true;
199  } else {
200  uavfield->setValue(element, i);
201  }
202  i++;
203  }
204  }
205  } else {
206  error = true;
207  }
208  }
209  field = field.nextSibling();
210  }
211  newObj->updated();
212 
213  if (error) {
214  swui.addLine(uavObjectName, "Warning (Object field unknown)", true);
215  } else if (uavObjectID != newObj->getObjID()) {
216  qDebug() << "Mismatch for Object " << uavObjectName << uavObjectID << " - "
217  << newObj->getObjID();
218  swui.addLine(uavObjectName, "Warning (ObjectID mismatch)", true);
219  } else if (setError) {
220  swui.addLine(uavObjectName, "Warning (Objects field value(s) invalid)", false);
221  } else {
222  swui.addLine(uavObjectName, "OK", true);
223  }
224  importedObjectManager->registerObject(newObj);
225  }
226  }
227  node = node.nextSibling();
228  }
229  qDebug() << "End import";
230 
231  if (swui.numLines() < 1) {
232  QMessageBox::critical(dynamic_cast<QWidget *>(Core::ICore::instance()->mainWindow()),
233  tr("Unable to import settings"),
234  tr("No settings found in XML dump; the flight controller settings "
235  "may have been blank."),
236  QMessageBox::Ok);
237  /* This is a termination condition-- no settings found. */
238  return true;
239  }
240 
241  swui.setUAVOSettings(importedObjectManager);
242  swui.exec();
243 
244  return swui.result() == QDialog::Accepted;
245 }
246 
247 // Slot called by the menu manager on user action
249 {
250  // ask for file name
251  QString fileName;
252  QString filters = tr("UAVObjects XML files (*.uav);; XML files (*.xml)");
253  fileName =
254  QFileDialog::getOpenFileName(dynamic_cast<QWidget *>(Core::ICore::instance()->mainWindow()),
255  tr("Import UAV Settings"), "", filters);
256  if (fileName.isEmpty()) {
257  return;
258  }
259 
260  // Now open the file
261  QFile file(fileName);
262  QDomDocument doc("UAVObjects");
263  file.open(QFile::ReadOnly | QFile::Text);
264 
265  importUAVSettings(file.readAll());
266 
267  file.close();
268 }
269 
270 // Create an XML document from UAVObject database
271 QString UAVSettingsImportExportManager::createXMLDocument(const enum storedData what,
272  const bool fullExport)
273 {
274  // generate an XML first (used for all export formats as a formatted data source)
275  ExtensionSystem::PluginManager *pm = ExtensionSystem::PluginManager::instance();
276  UAVObjectManager *objManager = pm->getObject<UAVObjectManager>();
277 
278  // create an XML root
279  QDomDocument doc("UAVObjects");
280  QDomElement root = doc.createElement("uavobjects");
281  doc.appendChild(root);
282 
283  // add hardware, firmware and GCS version info
284  QDomElement versionInfo = doc.createElement("version");
285  root.appendChild(versionInfo);
286 
287  UAVObjectUtilManager *utilMngr = pm->getObject<UAVObjectUtilManager>();
289  utilMngr->getBoardDescriptionStruct(board);
290 
291  QDomElement hw = doc.createElement("hardware");
292  hw.setAttribute("type", QString().setNum(board.boardType, 16));
293  hw.setAttribute("revision", QString().setNum(board.boardRevision, 16));
294  hw.setAttribute("serial", QString(utilMngr->getBoardCPUSerial().toHex()));
295  versionInfo.appendChild(hw);
296 
297  QDomElement fw = doc.createElement("firmware");
298  fw.setAttribute("date", board.gitDate);
299  fw.setAttribute("hash", board.gitHash);
300  fw.setAttribute("tag", board.gitTag);
301  versionInfo.appendChild(fw);
302 
303  QString gcsRevision = QString::fromLatin1(Core::Constants::GCS_REVISION_STR);
304  QString gcsGitDate = gcsRevision.mid(gcsRevision.indexOf(" ") + 1, 14);
305  QString gcsGitHash = gcsRevision.mid(gcsRevision.indexOf(":") + 1, 8);
306  QString gcsGitTag = gcsRevision.left(gcsRevision.indexOf(":"));
307 
308  QDomElement gcs = doc.createElement("gcs");
309  gcs.setAttribute("date", gcsGitDate);
310  gcs.setAttribute("hash", gcsGitHash);
311  gcs.setAttribute("tag", gcsGitTag);
312  versionInfo.appendChild(gcs);
313 
314  // create settings and/or data elements
315  QDomElement settings = doc.createElement("settings");
316  QDomElement data = doc.createElement("data");
317 
318  switch (what) {
319  case Settings:
320  root.appendChild(settings);
321  break;
322  case Data:
323  root.appendChild(data);
324  break;
325  case Both:
326  root.appendChild(data);
327  root.appendChild(settings);
328  break;
329  }
330 
331  // iterate over settings objects
332  QVector<QVector<UAVDataObject *>> objList = objManager->getDataObjectsVector();
333  foreach (QVector<UAVDataObject *> list, objList) {
334  foreach (UAVDataObject *obj, list) {
335  if (!obj->getIsPresentOnHardware())
336  continue;
337  if (((what == Settings) && obj->isSettings()) || ((what == Data) && !obj->isSettings())
338  || (what == Both)) {
339 
340  // add each object to the XML
341  QDomElement o = doc.createElement("object");
342  o.setAttribute("name", obj->getName());
343  o.setAttribute("id",
344  QString("0x") + QString().setNum(obj->getObjID(), 16).toUpper());
345  if (fullExport) {
346  QDomElement d = doc.createElement("description");
347  QDomText t = doc.createTextNode(
348  obj->getDescription().remove("@Ref ", Qt::CaseInsensitive));
349  d.appendChild(t);
350  o.appendChild(d);
351  }
352 
353  // iterate over fields
354  QList<UAVObjectField *> fieldList = obj->getFields();
355 
356  foreach (UAVObjectField *field, fieldList) {
357  QDomElement f = doc.createElement("field");
358 
359  // iterate over values
360  QString vals;
361  quint32 nelem = field->getNumElements();
362  for (unsigned int n = 0; n < nelem; ++n) {
363  vals.append(QString("%1,").arg(field->getValue(n).toString()));
364  }
365  vals.chop(1);
366 
367  f.setAttribute("name", field->getName());
368  f.setAttribute("values", vals);
369  if (fullExport) {
370  f.setAttribute("type", field->getTypeAsString());
371  f.setAttribute("units", field->getUnits());
372  f.setAttribute("elements", nelem);
373  if (field->getType() == UAVObjectField::ENUM) {
374  f.setAttribute("options", field->getOptions().join(","));
375  }
376  }
377  o.appendChild(f);
378  }
379 
380  // append to the settings or data element
381  if (obj->isSettings())
382  settings.appendChild(o);
383  else
384  data.appendChild(o);
385  }
386  }
387  }
388 
389  // This sorts the XML <object> children's <name=".."> attribute by alphabetical order,
390  // as well as sorting all attributes by alphabetical order (see note below for
391  // special circumstances around sorting attributes. This is particularly helpful when
392  // comparing *.uav files to each other.
393  QString preliminaryXMLDoc = doc.toString(4);
394  QString alphabetizedAttributesXMLDoc;
395  QString alphabetizedXMLDoc;
396 
397  // (New C++11 "raw string literal" string format.)
398  // This sorts all attributes by alphabetical order. This is *not* guaranteed to
399  // work. In fact, it is guaranteed *not* to work in all conditions. See
400  // http://stackoverflow.com/questions/1429991/using-xsl-to-sort-attributes.
401  // However, it seems to work so far and there's no expectation it will change
402  // in Qt's near future.
403  QString xmlAttributeSorter = R"(
404  <xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
405  <xsl:output method="xml" encoding="UTF-8" indent="yes"/>
406  <xsl:template match="*">
407  <xsl:copy>
408  <xsl:apply-templates select="@*">
409  <xsl:sort select="name() "/>
410  </xsl:apply-templates>
411  <xsl:apply-templates/>
412  </xsl:copy>
413  </xsl:template>
414  <xsl:template match="@*|comment()|processing-instruction() ">
415  <xsl:copy />
416  </xsl:template>
417  </xsl:stylesheet>
418  )";
419 
420  // (New C++11 "raw string literal" string format.)
421  // This XSLT template sorts all children by alphabetical order. This is guaranteed
422  // to work (unlike the attribute sorting).
423  QString xmlAlpheticalSorter = R"(
424  <xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
425  <xsl:output method="xml" indent="yes" omit-xml-declaration="no"/>
426  <xsl:strip-space elements="*"/>
427 
428  <xsl:template match="@* | node() ">
429  <xsl:copy>
430  <xsl:apply-templates select="@* | node() "/>
431  </xsl:copy>
432  </xsl:template>
433 
434  <xsl:template match="settings">
435  <xsl:copy>
436  <xsl:apply-templates select="@*" />
437  <xsl:apply-templates select="object">
438  <xsl:sort select="@name" data-type="text"/>
439 
440  </xsl:apply-templates>
441  </xsl:copy>
442  </xsl:template>
443 
444  </xsl:stylesheet>
445  )";
446 
447  // Generate query and set to XSLT v2.0
448  QXmlQuery query(QXmlQuery::XSLT20);
449 
450  // First sort attributes...
451  query.setFocus(preliminaryXMLDoc);
452  query.setQuery(xmlAttributeSorter);
453  query.evaluateTo(&alphabetizedAttributesXMLDoc);
454 
455  // Then sort children.
456  query.setFocus(alphabetizedAttributesXMLDoc);
457  query.setQuery(xmlAlpheticalSorter);
458  query.evaluateTo(&alphabetizedXMLDoc);
459 
460  return alphabetizedXMLDoc;
461 }
462 
463 // Slot called by the menu manager on user action
465 {
466  // ask for file name
467  QString fileName;
468  QString filters = tr("UAVObjects XML files (*.uav)");
469 
470  fileName =
471  QFileDialog::getSaveFileName(dynamic_cast<QWidget *>(Core::ICore::instance()->mainWindow()),
472  tr("Save UAVSettings File As"), "", filters);
473 
474  if (fileName.isEmpty()) {
475  return;
476  }
477 
478  // If the filename ends with .xml, we will do a full export, otherwise, a simple export
479  bool fullExport = false;
480  if (fileName.endsWith(".xml")) {
481  fullExport = true;
482  } else if (!fileName.endsWith(".uav")) {
483  fileName.append(".uav");
484  }
485 
486  // generate an XML first (used for all export formats as a formatted data source)
487  QString xml = createXMLDocument(Settings, fullExport);
488 
489  // save file
490  QFile file(fileName);
491  if (file.open(QIODevice::WriteOnly) && (file.write(xml.toLatin1()) != -1)) {
492  file.close();
493  } else {
494  QMessageBox::critical(dynamic_cast<QWidget *>(Core::ICore::instance()->mainWindow()),
495  tr("UAV Settings Export"), tr("Unable to save settings: ") + fileName,
496  QMessageBox::Ok);
497  return;
498  }
499 
500  QMessageBox msgBox(dynamic_cast<QWidget *>(Core::ICore::instance()->mainWindow()));
501  msgBox.setText(tr("Settings saved."));
502  msgBox.setStandardButtons(QMessageBox::Ok);
503  msgBox.exec();
504 }
505 
506 // Slot called by the menu manager on user action
508 {
509  // ask for file name
510  QString fileName;
511  QString filters = tr("UAVObjects XML files (*.uav)");
512 
513  fileName =
514  QFileDialog::getSaveFileName(dynamic_cast<QWidget *>(Core::ICore::instance()->mainWindow()),
515  tr("Save UAVData File As"), "", filters);
516  if (fileName.isEmpty()) {
517  return;
518  }
519 
520  // If the filename ends with .xml, we will do a full export, otherwise, a simple export
521  bool fullExport = false;
522  if (fileName.endsWith(".xml")) {
523  fullExport = true;
524  } else if (!fileName.endsWith(".uav")) {
525  fileName.append(".uav");
526  }
527 
528  // generate an XML first (used for all export formats as a formatted data source)
529  QString xml = createXMLDocument(Both, fullExport);
530 
531  // save file
532  QFile file(fileName);
533  if (file.open(QIODevice::WriteOnly) && (file.write(xml.toLatin1()) != -1)) {
534  file.close();
535  } else {
536  QMessageBox::critical(dynamic_cast<QWidget *>(Core::ICore::instance()->mainWindow()),
537  tr("UAV Data Export"), tr("Unable to save data: ") + fileName,
538  QMessageBox::Ok);
539  return;
540  }
541 
542  QMessageBox msgBox(dynamic_cast<QWidget *>(Core::ICore::instance()->mainWindow()));
543  msgBox.setText(tr("Data saved."));
544  msgBox.setStandardButtons(QMessageBox::Ok);
545  msgBox.exec();
546 }
547 
548 void UAVSettingsImportExportManager::setCommandsEnabled(bool enabled)
549 {
550  static const std::vector<QString> commands{
551  QStringLiteral("UAVSettingsImportExportPlugin.UAVSettingsExport"),
552  QStringLiteral("UAVSettingsImportExportPlugin.UAVSettingsImport"),
553  QStringLiteral("UAVSettingsImportExportPlugin.UAVDataExport")
554  };
555 
557  if (!am)
558  return;
559 
560  for (const auto &command : commands)
561  if (Core::Command *cmd = am->command(command))
562  cmd->action()->setEnabled(enabled);
563 }
bool checkValue(const QVariant &data, int index=0) const
const char *const M_HELP
Definition: coreconstants.h:86
QString getTypeAsString() const
void connectedChanged(bool)
virtual QAction * action() const =0
const char t[]
Definition: coreconstants.h:40
virtual Command * registerAction(QAction *action, const QString &id, const QList< int > &context)=0
Makes an action known to the system under the specified string id.
virtual ActionContainer * actionContainer(const QString &id) const =0
Returns the IActionContainter object that is know to the system under the given string id...
Core plugin system that manages the plugins, their life cycle and their registered objects...
Definition: pluginmanager.h:53
QVariant getValue(int index=0) const
const char *const G_HELP_HELP
int getNumElements() const
UAVSettingsImportExportManager(QObject *parent=nullptr)
virtual ActionManager * actionManager() const =0
Returns the application's action manager.
for i
Definition: OPPlots.m:140
void setUAVOSettings(UAVObjectManager *obj)
const char *const G_FILE_SAVE
DataFields data
const int C_GLOBAL_ID
Definition: coreconstants.h:90
virtual Command * command(const QString &id) const =0
Returns the Command object that is known to the system under the given string id. ...
QString getDescription()
Definition: uavobject.cpp:139
QStringList getOptions() const
T * query(Aggregate *obj)
Definition: aggregate.h:85
Parse log file
const char *const M_FILE
Definition: coreconstants.h:77
void setValue(const QVariant &data, int index=0)
static ICore * instance()
Definition: coreimpl.cpp:46
void updated()
Definition: uavobject.cpp:179
UAVObjectField * getField(const QString &name)
Definition: uavobject.cpp:236
Eccentricity n
Definition: OPPlots.m:137
bool getIsPresentOnHardware() const
QString getName() const
quint32 getObjID()
Definition: uavobject.cpp:107
QList< UAVObjectField * > getFields()
Definition: uavobject.cpp:196
virtual void addAction(Core::Command *action, const QString &group=QString())=0
const char *const GCS_REVISION_STR
Definition: coreconstants.h:45
virtual void setDefaultKeySequence(const QKeySequence &key)=0
void addLine(QString objectName, QString text, bool status)
The action manager is responsible for registration of menus and menu items and keyboard shortcuts...
Definition: actionmanager.h:47
QString getUnits() const
QString getName()
Definition: uavobject.cpp:131
virtual UAVDataObject * clone(quint32 instID=0)=0
else error('Your technical computing program does not support file choosers.Please input the file name in the argument. ') end elseif nargin >0 logfile
bool getBoardDescriptionStruct(deviceDescriptorStruct &device)
UAVObject * getObject(const QString &name, quint32 instId=0)
QVector< QVector< UAVDataObject * > > getDataObjectsVector()
The class Command represents an action like a menu item, tool button, or shortcut. You don't create Command objects directly, instead use {ActionManager::registerAction()} to register an action and retrieve a Command. The Command object represents the user visible action and its properties. If multiple actions are registered with the same ID (but different contexts) the returned Command is the shared one between these actions.
Definition: command.h:43
e
Definition: OPPlots.m:99
FieldType getType() const