dRonin  adbada4
dRonin GCS
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Groups Pages
telemetryschedulergadgetwidget.cpp
Go to the documentation of this file.
1 
11 /*
12  * This program is free software; you can redistribute it and/or modify
13  * it under the terms of the GNU General Public License as published by
14  * the Free Software Foundation; either version 3 of the License, or
15  * (at your option) any later version.
16  *
17  * This program is distributed in the hope that it will be useful, but
18  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
19  * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
20  * for more details.
21  *
22  * You should have received a copy of the GNU General Public License along
23  * with this program; if not, see <http://www.gnu.org/licenses/>
24  */
26 #include "metadata_dialog.h"
27 #include "ui_telemetryscheduler.h"
28 #include "ui_metadata_dialog.h"
29 
30 #include <math.h>
31 #include <QtCore/qglobal.h>
32 #include <QDebug>
33 #include <QClipboard>
34 #include <QKeyEvent>
35 #include <QString>
36 #include <QStringList>
37 #include <QMessageBox>
38 #include <QFileInfo>
39 #include <QFileDialog>
40 #include <QScrollBar>
41 #include <QWidget>
42 #include <QTextEdit>
43 #include <QVBoxLayout>
44 #include <QPushButton>
45 #include <QFileInfo>
46 #include <QInputDialog>
47 
49 #include "utils/xmlconfig.h"
56 #include <QMenu>
57 
59  : QWidget(parent)
60 {
61  m_telemetryeditor = new Ui_TelemetryScheduler();
62  m_telemetryeditor->setupUi(this);
63 
64  // In case GCS is not in expert mode, hide the apply button
65  ExtensionSystem::PluginManager *pm = ExtensionSystem::PluginManager::instance();
67  if (!settings->useExpertMode())
68  m_telemetryeditor->bnApplySchedule->setVisible(false);
69 
70  schedulerModel = new SchedulerModel(0, 0, this); // 0 Rows and 0 Columns
71 
72  telemetryScheduleView = new QFrozenTableViewWithCopyPaste(schedulerModel);
73  telemetryScheduleView->setObjectName(QString::fromUtf8("telemetryScheduleView"));
74  telemetryScheduleView->setAlternatingRowColors(true);
75  telemetryScheduleView->horizontalHeader()->setCascadingSectionResizes(false);
76  telemetryScheduleView->horizontalHeader()->setSectionsMovable(true);
77 
78  // The dummy table exists only to force the other widgets into the correct place.
79  // It is removed and replaced tby the custom copy/paste-enabled table
80  int dummyIndex = m_telemetryeditor->gridLayout->indexOf(m_telemetryeditor->tableWidgetDummy);
81  int row, col, rowSpan, colSpan;
82  m_telemetryeditor->gridLayout->getItemPosition(dummyIndex, &row, &col, &rowSpan, &colSpan);
83  m_telemetryeditor->gridLayout->removeWidget(m_telemetryeditor->tableWidgetDummy);
84  m_telemetryeditor->tableWidgetDummy->setVisible(false);
85  m_telemetryeditor->gridLayout->addWidget(telemetryScheduleView, row, col, rowSpan, colSpan);
86  connect(m_telemetryeditor->hideNotPresent, &QAbstractButton::clicked, this,
87  &TelemetrySchedulerGadgetWidget::onHideNotPresent);
88  // Sets the fields in the table to spinboxes
89  SpinBoxDelegate *delegate = new SpinBoxDelegate();
90  telemetryScheduleView->setItemDelegate(delegate);
91 
92  // Connect the before setting any signals
93  connect(m_telemetryeditor->bnSaveTelemetryToFile, &QAbstractButton::clicked, this,
94  &TelemetrySchedulerGadgetWidget::saveTelemetryToFile);
95  connect(m_telemetryeditor->bnLoadTelemetryFromFile, &QAbstractButton::clicked, this,
96  &TelemetrySchedulerGadgetWidget::loadTelemetryFromFile);
97  connect(m_telemetryeditor->bnApplySchedule, &QAbstractButton::clicked, this,
98  &TelemetrySchedulerGadgetWidget::applySchedule);
99  connect(m_telemetryeditor->bnSaveSchedule, &QAbstractButton::clicked, this,
100  &TelemetrySchedulerGadgetWidget::saveSchedule);
101  connect(m_telemetryeditor->bnAddTelemetryColumn, &QAbstractButton::clicked, this,
102  &TelemetrySchedulerGadgetWidget::addTelemetryColumn);
103  connect(m_telemetryeditor->bnRemoveTelemetryColumn, &QAbstractButton::clicked, this,
104  &TelemetrySchedulerGadgetWidget::removeTelemetryColumn);
105  connect(schedulerModel, &SchedulerModel::itemChanged, this,
106  QOverload<QStandardItem *>::of(&TelemetrySchedulerGadgetWidget::dataModel_itemChanged));
107  connect(telemetryScheduleView->horizontalHeader(), &QHeaderView::sectionDoubleClicked, this,
108  &TelemetrySchedulerGadgetWidget::changeHorizontalHeader);
109  connect(telemetryScheduleView->verticalHeader(), &QHeaderView::sectionDoubleClicked, this,
110  &TelemetrySchedulerGadgetWidget::changeVerticalHeader);
111  connect(telemetryScheduleView, &QWidget::customContextMenuRequested, this,
112  &TelemetrySchedulerGadgetWidget::customMenuRequested);
113  telemetryScheduleView->setContextMenuPolicy(Qt::CustomContextMenu);
114 
115  // Generate the list of UAVOs on left side
116  objManager = pm->getObject<UAVObjectManager>();
117  Q_ASSERT(objManager != NULL);
118 
119  QVector<QVector<UAVDataObject *>> objList = objManager->getDataObjectsVector();
120  QStringList strList;
121  foreach (QVector<UAVDataObject *> list, objList) {
122  if (!list.first()->isSettings()) {
123  strList.append(list.first()->getName());
124  connect(qobject_cast<UAVDataObject *>(list.first()),
125  QOverload<UAVDataObject *>::of(&UAVDataObject::presentOnHardwareChanged), this,
126  &TelemetrySchedulerGadgetWidget::uavoPresentOnHardwareChanged,
127  Qt::UniqueConnection);
128  }
129  }
130  strList.sort(Qt::CaseInsensitive);
131  int rowIndex = 1;
132  foreach (QString str, strList) {
133  schedulerModel->setVerticalHeaderItem(rowIndex, new QStandardItem(str));
134  UAVObject *obj = objManager->getObject(str);
135  Q_ASSERT(obj);
136  UAVDataObject *dobj = dynamic_cast<UAVDataObject *>(obj);
137  Q_ASSERT(dobj);
138  uavoIndex.insert(dobj, rowIndex);
139  rowIndex++;
140  }
141 
142  // Set the bandwidth required header
143  telemetryScheduleView->getFrozenModel()->setVerticalHeaderItem(
144  0, new QStandardItem("Bandwidth required [bytes/s]"));
145 
146  // Set the header width to the table header width
147  // TODO: Do this as a reimplemented function in the tableview subclass
148  int width = telemetryScheduleView->verticalHeader()->sizeHint().width();
149  telemetryScheduleView->getFrozenTableView()->verticalHeader()->setFixedWidth(width);
150 
151  // Generate the list of column headers
152  columnHeaders << "Default"
153  << "Current"
154  << "Low speed"
155  << "Medium speed"
156  << "High speed";
157 
158  int columnIndex = 0;
159  foreach (QString header, columnHeaders) {
160  schedulerModel->setHorizontalHeaderItem(columnIndex, new QStandardItem(header));
161  telemetryScheduleView->setHorizontalHeaderItem(columnIndex, new QStandardItem(header));
162  telemetryScheduleView->setColumnWidth(columnIndex, 100); // 65 pixels is wide enough for the
163  // string "65535", but we set 100
164  // for the column headers
165  columnIndex++;
166  }
167 
168  // 1) Populate the "Current" column with live update rates. 2) Connect these values to
169  // the current metadata. 3) Populate the default column.
170  rowIndex = 1;
171  foreach (QString objStr, strList) {
172  // Add defaults
173  UAVObject *obj = objManager->getObject(objStr);
174  Q_ASSERT(obj);
175  UAVObject::Metadata mdataDefault = obj->getDefaultMetadata();
176  QModelIndex index = schedulerModel->index(rowIndex, 0, QModelIndex());
177  schedulerModel->setData(index,
178  QString("%1ms").arg(mdataDefault.flightTelemetryUpdatePeriod));
179 
180  // Save default metadata for later use
181  defaultMdata.insert(obj->getName().append("Meta"), mdataDefault);
182 
183  // Connect live values to the "Current" column
184  UAVDataObject *dobj = dynamic_cast<UAVDataObject *>(obj);
185  Q_ASSERT(dobj);
186  UAVMetaObject *mobj = dobj->getMetaObject();
187  connect(mobj, &UAVObject::objectUpdated, this,
188  &TelemetrySchedulerGadgetWidget::updateCurrentColumn);
189 
190  // Updates the "Current" column with the live value
191  updateCurrentColumn(mobj);
192  rowIndex++;
193  }
194 
195  // Populate combobox
196  m_telemetryeditor->cmbScheduleList->addItem("");
197  m_telemetryeditor->cmbScheduleList->addItems(columnHeaders);
198  onHideNotPresent(true);
199 }
200 
202 {
203  // Do nothing
204 }
205 
210 void TelemetrySchedulerGadgetWidget::updateCurrentColumn(UAVObject *obj)
211 {
212  int rowIndex = -1;
213 
214  UAVMetaObject *mobj = dynamic_cast<UAVMetaObject *>(obj);
215  if (mobj == NULL) {
216  Q_ASSERT(0);
217  return;
218  }
219 
220  // Iterate over all headers, looking for the UAVO
221  for (int i = 1; i < schedulerModel->rowCount(); i++) {
222  // Add "Meta" to the end of the header name, in order to match the UAVO
223  // metadata object name
224  if (schedulerModel->verticalHeaderItem(i)->text().append("Meta") == mobj->getName()) {
225  rowIndex = i;
226  break;
227  }
228  }
229 
230  // This can only happen if something has gone wrong in the object manager
231  if (rowIndex == -1) {
232  Q_ASSERT(0);
233  return;
234  }
235 
236  // Use row index value to populate new data. Only populate data if it is differnt
237  UAVObject::Metadata mdataDefault = defaultMdata.value(mobj->getName());
238  UAVObject::Metadata mdata = mobj->getData();
239 
240  if (mdata.flightTelemetryUpdatePeriod != mdataDefault.flightTelemetryUpdatePeriod) {
241  QModelIndex index = schedulerModel->index(rowIndex, 1, QModelIndex());
242  schedulerModel->setData(index, QString("%1ms").arg(mdata.flightTelemetryUpdatePeriod));
243  }
244 }
245 
250 void TelemetrySchedulerGadgetWidget::dataModel_itemChanged(QStandardItem *item)
251 {
252  m_telemetryeditor->bnSaveSchedule->setIcon(QIcon());
253  m_telemetryeditor->bnApplySchedule->setIcon(QIcon());
254  int col = item->column();
255  dataModel_itemChanged(col);
256 }
257 
262 void TelemetrySchedulerGadgetWidget::dataModel_itemChanged(int col)
263 {
264  // Update the speed estimate
265  double bandwidthRequired_bps = 0;
266  for (int i = 1; i < schedulerModel->rowCount(); i++) {
267  // Get UAVO size
268  QString uavObjectName = schedulerModel->verticalHeaderItem(i)->text();
269  UAVObject *obj = objManager->getObject(uavObjectName);
270  UAVDataObject *dobj = dynamic_cast<UAVDataObject *>(obj);
271  if (dobj && m_telemetryeditor->hideNotPresent->isChecked()
272  && (!dobj->getIsPresentOnHardware()))
273  continue;
274  Q_ASSERT(obj);
275  quint16 size = obj->getNumBytes();
276 
277  // Get UAVO speed
278  QModelIndex index = schedulerModel->index(i, col, QModelIndex());
279  double updatePeriod_s;
280  if (schedulerModel->data(index).isValid() && stripMs(schedulerModel->data(index)) >= 0)
281  updatePeriod_s = stripMs(schedulerModel->data(index)) / 1000.0;
282  else
283  updatePeriod_s =
284  defaultMdata.value(obj->getName().append("Meta")).flightTelemetryUpdatePeriod
285  / 1000.0;
286 
287  double updateFrequency_Hz = updatePeriod_s > 0 ? 1.0 / updatePeriod_s : 0;
288 
289  // Accumulate bandwidth
290  bandwidthRequired_bps += updateFrequency_Hz * size;
291  }
292 
293  QModelIndex index = telemetryScheduleView->getFrozenModel()->index(0, col, QModelIndex());
294  telemetryScheduleView->getFrozenModel()->setData(
295  index, QString("%1B/s").arg(lround(bandwidthRequired_bps)));
296 
297  // TODO: Set color as function of available speed
298 }
299 
300 void TelemetrySchedulerGadgetWidget::saveTelemetryToFile()
301 {
302  QString file = filename;
303  QString filter = tr("Telemetry Scheduler file (*.xml)");
304  file = QFileDialog::getSaveFileName(this,
305  tr("Save Telemetry Schedule to file .."),
306  QFileInfo(file).absoluteFilePath(), filter, &filter).trimmed();
307 
308  if (file.isEmpty()) {
309  return;
310  }
311  if (!file.endsWith(".xml", Qt::CaseInsensitive))
312  file.append(".xml");
313  filename = file;
314 
315  // Create an XML document from UAVObject database
316  {
317  // generate an XML first (used for all export formats as a formatted data source)
318  // create an XML root
319  QDomDocument doc("TelemetryScheduler");
320  QDomElement root = doc.createElement("telemetry_scheduler");
321  doc.appendChild(root);
322 
323  // add hardware, firmware and GCS version info
324  QDomElement versionInfo = doc.createElement("version");
325  root.appendChild(versionInfo);
326 
327  // create headings and settings elements
328  QDomElement settings = doc.createElement("settings");
329  QDomElement headings = doc.createElement("headings");
330 
331  // Remove the "Current" and "Default" headers from the list
332  QStringList tmpStringList = columnHeaders;
333  tmpStringList.pop_front();
334  tmpStringList.pop_front();
335 
336  // append to the headings element
337  QDomElement o = doc.createElement("headings");
338  o.setAttribute("values", tmpStringList.join(","));
339  root.appendChild(o);
340 
341  root.appendChild(settings);
342 
343  // iterate over UAVObjects
344  for (int row = 1; row < schedulerModel->rowCount(); row++) {
345  QString uavObjectName = schedulerModel->verticalHeaderItem(row)->text();
346  UAVObject *obj = objManager->getObject(uavObjectName);
347  Q_ASSERT(obj);
348 
349  // add UAVObject to the XML
350  QDomElement o = doc.createElement("uavobject");
351  o.setAttribute("name", obj->getName());
352  o.setAttribute("id", QString("0x") + QString().setNum(obj->getObjID(), 16).toUpper());
353 
354  QStringList vals;
355  for (int col = 0; col < columnHeaders.length(); col++) {
356  QModelIndex index = schedulerModel->index(row, col + 1, QModelIndex());
357  vals << schedulerModel->data(index).toString();
358  }
359 
360  QDomElement f = doc.createElement("field");
361  f.setAttribute("values", vals.join(","));
362  o.appendChild(f);
363 
364  // append to the settings or data element
365  settings.appendChild(o);
366  }
367 
368  QString xml = doc.toString(4);
369 
370  // save file
371  QFile file(filename);
372  if (file.open(QIODevice::WriteOnly) && (file.write(xml.toLatin1()) != -1)) {
373  file.close();
374  } else {
375  QMessageBox::critical(this, tr("UAV Data Export"), tr("Unable to save data: ") + filename,
376  QMessageBox::Ok);
377  return;
378  }
379 
380  QMessageBox msgBox;
381  msgBox.setText(tr("Data saved."));
382  msgBox.setStandardButtons(QMessageBox::Ok);
383  msgBox.exec();
384  }
385 }
386 
391 QList<UAVMetaObject *> TelemetrySchedulerGadgetWidget::applySchedule()
392 {
393  int col = -1;
394  QList<UAVMetaObject *> metaList;
395  if (m_telemetryeditor->cmbScheduleList->currentText().isEmpty()) {
396  QMessageBox::warning(this, tr("No Schedule selected"),
397  tr("Please select a schedule on the dropbox to the left and retry"),
398  QMessageBox::Ok);
399  return metaList;
400  }
401  // Iterate over the list of columns, looking for the selected schedule
402  for (int j = 0; j < schedulerModel->columnCount(); j++) {
403  if (schedulerModel->horizontalHeaderItem(j)->text()
404  == m_telemetryeditor->cmbScheduleList->currentText()) {
405  col = j;
406  break;
407  }
408  }
409 
410  // This shouldn't be possible. Leaving the check in just in case.
411  if (col == -1) {
412  Q_ASSERT(0);
413  return metaList;
414  }
415  QMap<QString, UAVObject::Metadata> metaDataList;
416  for (int i = 1; i < schedulerModel->rowCount(); i++) {
417  // Get UAVO name and metadata
418  QString uavObjectName = schedulerModel->verticalHeaderItem(i)->text();
419  UAVObject *obj = objManager->getObject(uavObjectName);
420  UAVDataObject *dobj = dynamic_cast<UAVDataObject *>(obj);
421  if (dobj && !(dobj->getIsPresentOnHardware()))
422  continue;
423  UAVObject::Metadata mdata = obj->getMetadata();
424 
425  // Get update period
426  double updatePeriod_ms;
427  QModelIndex index = schedulerModel->index(i, col, QModelIndex());
428  if (schedulerModel->data(index).isValid() && stripMs(schedulerModel->data(index)) >= 0) {
429  updatePeriod_ms = stripMs(schedulerModel->data(index));
430  mdata.flightTelemetryUpdatePeriod = updatePeriod_ms;
431  metaDataList.insert(uavObjectName, mdata);
432  metaList.append(dobj->getMetaObject());
433  }
434  }
435 
436  // Set new metadata
437  if (sender() == m_telemetryeditor->bnApplySchedule) {
438  connect(getObjectUtilManager(), &UAVObjectUtilManager::completedMetadataWrite, this,
439  &TelemetrySchedulerGadgetWidget::onCompletedMetadataWrite);
440  m_telemetryeditor->bnApplySchedule->setIcon(QIcon(":/uploader/images/system-run.svg"));
441  } else {
442  connect(getObjectUtilManager(), &UAVObjectUtilManager::completedMetadataWrite, this,
443  &TelemetrySchedulerGadgetWidget::onSavedSchedule);
444  m_telemetryeditor->bnSaveSchedule->setIcon(QIcon(":/uploader/images/system-run.svg"));
445  }
446  m_telemetryeditor->bnApplySchedule->setEnabled(false);
447  m_telemetryeditor->bnSaveSchedule->setEnabled(false);
448  getObjectUtilManager()->setAllNonSettingsMetadata(metaDataList);
449  return metaList;
450 }
451 
455 void TelemetrySchedulerGadgetWidget::saveSchedule()
456 {
457  // Make sure we are saving the selected schedule
458  metaObjectsToSave = applySchedule();
459 }
460 
461 void TelemetrySchedulerGadgetWidget::onSavedSchedule(bool value)
462 {
463  disconnect(getObjectUtilManager(), &UAVObjectUtilManager::completedMetadataWrite, this,
464  &TelemetrySchedulerGadgetWidget::onSavedSchedule);
465  if (metaObjectsToSave.isEmpty()) {
466  m_telemetryeditor->bnSaveSchedule->setIcon(QIcon(":/uploader/images/dialog-apply.svg"));
467  return;
468  }
469  if (!value) {
470  m_telemetryeditor->bnSaveSchedule->setIcon(QIcon(":/uploader/images/process-stop.svg"));
471  return;
472  }
473  connect(getObjectUtilManager(), &UAVObjectUtilManager::saveCompleted, this,
474  &TelemetrySchedulerGadgetWidget::onCompletedMetadataSave);
475  onCompletedMetadataSave(-1, true);
476  foreach (UAVMetaObject *meta, metaObjectsToSave) {
477  getObjectUtilManager()->saveObjectToFlash(meta);
478  }
479 }
480 
481 void TelemetrySchedulerGadgetWidget::onCompletedMetadataWrite(bool value)
482 {
483  if (value)
484  m_telemetryeditor->bnApplySchedule->setIcon(QIcon(":/uploader/images/dialog-apply.svg"));
485  else
486  m_telemetryeditor->bnApplySchedule->setIcon(QIcon(":/uploader/images/process-stop.svg"));
487  m_telemetryeditor->bnApplySchedule->setEnabled(true);
488  m_telemetryeditor->bnSaveSchedule->setEnabled(true);
489  disconnect(getObjectUtilManager(), &UAVObjectUtilManager::completedMetadataWrite, this,
490  &TelemetrySchedulerGadgetWidget::onCompletedMetadataWrite);
491 }
492 
493 void TelemetrySchedulerGadgetWidget::onCompletedMetadataSave(int id, bool result)
494 {
495  static bool success = true;
496  if (id == -1) {
497  success = true;
498  return;
499  }
500  UAVObject *obj = getObjectManager()->getObject(id);
501  Q_ASSERT(obj);
502  UAVMetaObject *meta = dynamic_cast<UAVMetaObject *>(obj);
503  if (meta) {
504  if (!result && metaObjectsToSave.contains(meta)) {
505  qDebug() << meta->getName() << "save failed";
506  success = false;
507  }
508  metaObjectsToSave.removeAll(meta);
509  }
510  if (metaObjectsToSave.isEmpty()) {
511  if (success)
512  m_telemetryeditor->bnSaveSchedule->setIcon(QIcon(":/uploader/images/dialog-apply.svg"));
513  else
514  m_telemetryeditor->bnSaveSchedule->setIcon(QIcon(":/uploader/images/process-stop.svg"));
515  m_telemetryeditor->bnApplySchedule->setEnabled(true);
516  m_telemetryeditor->bnSaveSchedule->setEnabled(true);
517  disconnect(getObjectUtilManager(), &UAVObjectUtilManager::saveCompleted, this,
518  &TelemetrySchedulerGadgetWidget::onCompletedMetadataSave);
519  }
520 }
521 
522 void TelemetrySchedulerGadgetWidget::loadTelemetryFromFile()
523 {
524  // ask for file name
525  QString file = filename;
526  QString filter = tr("Telemetry Scheduler file (*.xml)");
527  file = QFileDialog::getOpenFileName(this,
528  tr("Load Telemetry Schedule from file .."),
529  QFileInfo(file).absoluteFilePath(), filter).trimmed();
530  if (file.isEmpty()) {
531  return;
532  }
533 
534  filename = file;
535 
536  QMessageBox msgBox(this);
537  if (!QFileInfo(file).isReadable()) {
538  msgBox.setText(tr("Can't read file ") + QFileInfo(file).absoluteFilePath());
539  msgBox.exec();
540  return;
541  }
542  importTelemetryConfiguration(file);
543 }
544 
545 void TelemetrySchedulerGadgetWidget::importTelemetryConfiguration(const QString &fileName)
546 {
547  // Open the file
548  QFile file(fileName);
549  QDomDocument doc("TelemetryScheduler");
550  file.open(QFile::ReadOnly | QFile::Text);
551  if (!doc.setContent(file.readAll())) {
552  QMessageBox msgBox(this);
553  msgBox.setText(tr("File Parsing Failed."));
554  msgBox.setInformativeText(tr("This file is not a correct XML file"));
555  msgBox.setStandardButtons(QMessageBox::Ok);
556  msgBox.exec();
557  return;
558  }
559  file.close();
560 
561  // Read the headings
562  // find the root of headings subtree
563  QDomElement root = doc.documentElement();
564  if (root.tagName() == "telemetry_scheduler") {
565  root = root.firstChildElement("headings");
566  }
567 
568  // Check that this a good file
569  if (root.isNull() || (root.tagName() != "headings")) {
570  QMessageBox msgBox(this);
571  msgBox.setText(tr("Wrong file contents"));
572  msgBox.setInformativeText(tr("This file does not contain correct telemetry settings"));
573  msgBox.setStandardButtons(QMessageBox::Ok);
574  msgBox.exec();
575  return;
576  }
577 
578  QDomElement f = root.toElement();
579  QStringList new_columnHeaders = f.attribute("values").split(",");
580  new_columnHeaders.insert(0, "Default");
581  new_columnHeaders.insert(1, "Current");
582 
583  // Remove old columns
584  schedulerModel->removeColumns(2, columnHeaders.length() - 2, QModelIndex());
585  telemetryScheduleView->removeColumns(2, columnHeaders.length() - 2, QModelIndex());
586 
587  // Add new ones
588  schedulerModel->setHorizontalHeaderLabels(new_columnHeaders); //<-- TODO: Reimplement this
589  //function if possible, so that
590  //when a new column is added it
591  //automatically updates a list of
592  //columns
593  for (int columnIndex = 0; columnIndex < new_columnHeaders.length(); columnIndex++) {
594  telemetryScheduleView->setHorizontalHeaderItem(columnIndex, new QStandardItem(""));
595  telemetryScheduleView->setColumnWidth(columnIndex, 100); // 65 pixels is wide enough for the
596  // string "65535", but we set 100
597  // for the column headers
598  }
599 
600  // Update columnHeaders
601  columnHeaders = new_columnHeaders;
602 
603  // Populate combobox
604  m_telemetryeditor->cmbScheduleList->clear();
605  m_telemetryeditor->cmbScheduleList->addItem("");
606  m_telemetryeditor->cmbScheduleList->addItems(columnHeaders);
607 
608  // find the root of settings subtree
609  root = doc.documentElement();
610  if (root.tagName() == "telemetry_scheduler") {
611  root = root.firstChildElement("settings");
612  }
613  if (root.isNull() || (root.tagName() != "settings")) {
614  QMessageBox msgBox(this);
615  msgBox.setText(tr("Wrong file contents"));
616  msgBox.setInformativeText(tr("This file does not contain correct telemetry settings"));
617  msgBox.setStandardButtons(QMessageBox::Ok);
618  msgBox.exec();
619  return;
620  }
621 
622  QDomNode node = root.firstChild();
623  while (!node.isNull()) {
624  QDomElement e = node.toElement();
625  if (e.tagName() == "uavobject") {
626  // - Read each UAVObject
627  QString uavObjectName = e.attribute("name");
628  uint uavObjectID = e.attribute("id").toUInt(NULL, 16);
629 
630  // Sanity Check:
631  UAVObject *obj = objManager->getObject(uavObjectName);
632  if (obj == NULL) {
633  // This object is unknown!
634  qDebug() << "Object unknown:" << uavObjectName << uavObjectID;
635  } else {
636  // - Update each field
637  QDomNode field = node.firstChild();
638  QDomElement f = field.toElement();
639  if (f.tagName() == "field") {
640 
641  QStringList valuesList = f.attribute("values").split(",");
642 
643  // Iterate over all headers, looking for the UAVO
644  int row = 0;
645  for (int i = 1; i < schedulerModel->rowCount(); i++) {
646  if (schedulerModel->verticalHeaderItem(i)->text() == uavObjectName) {
647  row = i;
648  break;
649  }
650  }
651 
652  // Load the config file values into the table
653  for (int j = 0; j < valuesList.length(); j++) {
654  QModelIndex index = schedulerModel->index(row, j + 2, QModelIndex());
655  quint32 val = stripMs(valuesList[j]);
656  if (val == 0) {
657  // If it's 0, do nothing, since a blank cell indicates a default.
658  } else
659  schedulerModel->setData(index, QString("%1ms").arg(val));
660  }
661  }
662  field = field.nextSibling();
663  }
664  }
665  node = node.nextSibling();
666  }
667  qDebug() << "Import ended";
668 }
669 
673 void TelemetrySchedulerGadgetWidget::addTelemetryColumn()
674 {
675  int newColumnIndex = schedulerModel->columnCount();
676  QString newColumnString = "New Column";
677  schedulerModel->setHorizontalHeaderItem(newColumnIndex, new QStandardItem(newColumnString));
678  telemetryScheduleView->setHorizontalHeaderItem(newColumnIndex, new QStandardItem(""));
679  telemetryScheduleView->setColumnWidth(newColumnIndex,
680  65); // 65 pixels is wide enough for the string "65535"
681 
682  columnHeaders.append(newColumnString);
683  m_telemetryeditor->cmbScheduleList->clear();
684  m_telemetryeditor->cmbScheduleList->addItem("");
685  m_telemetryeditor->cmbScheduleList->addItems(columnHeaders);
686 }
687 
691 void TelemetrySchedulerGadgetWidget::removeTelemetryColumn()
692 {
693  int oldColumnIndex = schedulerModel->columnCount();
694  schedulerModel->removeColumns(oldColumnIndex - 1, 1);
695  telemetryScheduleView->removeColumns(oldColumnIndex - 1, 1);
696 
697  columnHeaders.pop_back();
698  m_telemetryeditor->cmbScheduleList->clear();
699  m_telemetryeditor->cmbScheduleList->addItem("");
700  m_telemetryeditor->cmbScheduleList->addItems(columnHeaders);
701 }
702 
706 void TelemetrySchedulerGadgetWidget::changeHorizontalHeader(int headerIndex)
707 {
708  bool ok;
709  QString headerName =
710  QInputDialog::getText(this, tr("Change header name"), tr("Input new column name:"),
711  QLineEdit::Normal, columnHeaders.at(headerIndex), &ok);
712  if (!ok)
713  return;
714 
715  schedulerModel->setHorizontalHeaderItem(headerIndex, new QStandardItem(headerName));
716 
717  columnHeaders.replace(headerIndex, headerName);
718  m_telemetryeditor->cmbScheduleList->clear();
719  m_telemetryeditor->cmbScheduleList->addItem("");
720  m_telemetryeditor->cmbScheduleList->addItems(columnHeaders);
721 }
722 
723 void TelemetrySchedulerGadgetWidget::customMenuRequested(QPoint pos)
724 {
725  Q_UNUSED(pos)
726  bool ok;
727  QString text = QInputDialog::getText(this, tr("Mass value filling"), tr("Choose value to use"),
728  QLineEdit::Normal, "", &ok);
729  if (!ok)
730  return;
731  text.toInt(&ok);
732  if (!ok) {
733  QMessageBox msgBox(this);
734  msgBox.setText("Value must be numeric");
735  msgBox.exec();
736  return;
737  }
738  if (!text.isEmpty()) {
739  foreach (QModelIndex index, telemetryScheduleView->selectionModel()->selectedIndexes()) {
740  telemetryScheduleView->model()->setData(index, text);
741  }
742  }
743 }
744 
745 void TelemetrySchedulerGadgetWidget::uavoPresentOnHardwareChanged(UAVDataObject *obj)
746 {
747  if (m_telemetryeditor->hideNotPresent->isChecked())
748  telemetryScheduleView->setRowHidden(uavoIndex.value(obj), !(obj->getIsPresentOnHardware()));
749  int width = telemetryScheduleView->verticalHeader()->sizeHint().width();
750  telemetryScheduleView->getFrozenTableView()->verticalHeader()->setFixedWidth(width);
751  telemetryScheduleView->fixGeometry(width);
752  for (int x = 0; x < schedulerModel->columnCount(); ++x) {
753  dataModel_itemChanged(x);
754  }
755 }
756 
757 void TelemetrySchedulerGadgetWidget::onHideNotPresent(bool hide)
758 {
759  if (hide) {
760  foreach (UAVDataObject *obj, uavoIndex.keys()) {
761  telemetryScheduleView->setRowHidden(uavoIndex.value(obj),
762  !(obj->getIsPresentOnHardware()));
763  }
764  int width = telemetryScheduleView->verticalHeader()->sizeHint().width();
765  telemetryScheduleView->getFrozenTableView()->verticalHeader()->setFixedWidth(width);
766  telemetryScheduleView->fixGeometry(width);
767  } else {
768  foreach (int index, uavoIndex.values()) {
769  telemetryScheduleView->setRowHidden(index, false);
770  }
771  int width = telemetryScheduleView->verticalHeader()->sizeHint().width();
772  telemetryScheduleView->getFrozenTableView()->verticalHeader()->setFixedWidth(width);
773  telemetryScheduleView->fixGeometry(width);
774  }
775  for (int x = 0; x < schedulerModel->columnCount(); ++x) {
776  dataModel_itemChanged(x);
777  }
778 }
779 
783 void TelemetrySchedulerGadgetWidget::changeVerticalHeader(int headerIndex)
784 {
785  // Get the UAVO name
786  QString uavObjectName = schedulerModel->verticalHeaderItem(headerIndex)->text();
787  UAVObject *uavObj = objManager->getObject(uavObjectName);
788 
789  // Get the metadata
790  UAVObject::Metadata mdata = uavObj->getMetadata();
791 
792  MetadataDialog metadataDialog(mdata);
793  metadataDialog.setWindowTitle(QString(uavObj->getName() + " settings"));
794 
795  if (metadataDialog.exec() != QDialog::Accepted)
796  return;
797 
798  UAVObject::Metadata newMetadata;
799  if (metadataDialog.getResetDefaults_flag() == false)
800  newMetadata = metadataDialog.getMetadata();
801  else {
802  newMetadata = uavObj->getDefaultMetadata();
803  newMetadata.flightTelemetryUpdatePeriod = mdata.flightTelemetryUpdatePeriod;
804  }
805 
806  // Update metadata, and save if necessary
807  uavObj->setMetadata(newMetadata);
808  if (metadataDialog.getSaveState_flag()) {
809  UAVDataObject *obj = dynamic_cast<UAVDataObject *>(objManager->getObject(uavObjectName));
810  if (obj) {
811  UAVMetaObject *meta = obj->getMetaObject();
812  getObjectUtilManager()->saveObjectToFlash(meta);
813  }
814  }
815 }
816 
822 int TelemetrySchedulerGadgetWidget::stripMs(QVariant rate_ms)
823 {
824  return rate_ms.toString().replace(QString("ms"), QString("")).toUInt();
825 }
826 
832 UAVObjectManager *TelemetrySchedulerGadgetWidget::getObjectManager()
833 {
834  ExtensionSystem::PluginManager *pm = ExtensionSystem::PluginManager::instance();
835  UAVObjectManager *objMngr = pm->getObject<UAVObjectManager>();
836  Q_ASSERT(objMngr);
837  return objMngr;
838 }
839 
845 UAVObjectUtilManager *TelemetrySchedulerGadgetWidget::getObjectUtilManager()
846 {
847  ExtensionSystem::PluginManager *pm = ExtensionSystem::PluginManager::instance();
848  UAVObjectUtilManager *utilMngr = pm->getObject<UAVObjectUtilManager>();
849  Q_ASSERT(utilMngr);
850  return utilMngr;
851 }
852 
853 //=======================================
854 
855 SpinBoxDelegate::SpinBoxDelegate(QObject *parent)
856  : QItemDelegate(parent)
857 {
858 }
859 
860 QWidget *SpinBoxDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem & /* option */,
861  const QModelIndex & /* index */) const
862 {
863  QSpinBox *editor = new QSpinBox(parent);
864  editor->setMinimum(0);
865  editor->setMaximum(65535); // Update period datatype is uint16
866  editor->setSuffix(QString("ms"));
867 
868  return editor;
869 }
870 
871 void SpinBoxDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const
872 {
873  int value = index.model()->data(index, Qt::EditRole).toInt();
874 
875  QSpinBox *spinBox = static_cast<QSpinBox *>(editor);
876  if (value >= 0)
877  spinBox->setValue(value);
878  else
879  spinBox->clear();
880 }
881 
882 void SpinBoxDelegate::setModelData(QWidget *editor, QAbstractItemModel *model,
883  const QModelIndex &index) const
884 {
885  QSpinBox *spinBox = static_cast<QSpinBox *>(editor);
886  spinBox->interpretText();
887  int value = spinBox->value();
888 
889  if (value >= 0)
890  model->setData(index, QString("%1ms").arg(value), Qt::EditRole);
891 }
892 
893 void SpinBoxDelegate::updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option,
894  const QModelIndex & /* index */) const
895 {
896  editor->setGeometry(option.rect);
897 }
898 
899 //==========================
900 
901 void QFrozenTableViewWithCopyPaste::copy()
902 {
903  QItemSelectionModel *selection = selectionModel();
904  QModelIndexList indexes = selection->selectedIndexes();
905 
906  if (indexes.size() < 1)
907  return;
908 
909  // QModelIndex::operator < sorts first by row, then by column.
910  // this is what we need
911  std::sort(indexes.begin(), indexes.end());
912 
913  // You need a pair of indexes to find the row changes
914  QModelIndex previous = indexes.first();
915  indexes.removeFirst();
916  QString selected_text;
917  QModelIndex current;
918  foreach (current, indexes) {
919  QVariant data = model()->data(previous);
920  QString text = data.toString();
921  // At this point `text` contains the text in one cell
922  selected_text.append(text);
923  // If you are at the start of the row the row number of the previous index
924  // isn't the same. Text is followed by a row separator, which is a newline.
925  if (current.row() != previous.row()) {
926  selected_text.append(QLatin1Char('\n'));
927  }
928  // Otherwise it's the same row, so append a column separator, which is a tab.
929  else {
930  selected_text.append(QLatin1Char('\t'));
931  }
932  previous = current;
933  }
934 
935  // add last element
936  selected_text.append(model()->data(current).toString());
937  selected_text.append(QLatin1Char('\n'));
938  qApp->clipboard()->setText(selected_text);
939 }
940 
941 void QFrozenTableViewWithCopyPaste::paste()
942 {
943  QItemSelectionModel *selection = selectionModel();
944  QModelIndexList indexes = selection->selectedIndexes();
945 
946  QString selected_text = qApp->clipboard()->text();
947  QStringList cells = selected_text.split(QRegExp(QLatin1String("\\n|\\t")));
948  while (!cells.empty() && cells.back().size() == 0) {
949  cells.pop_back(); // strip empty trailing tokens
950  }
951  int rows = selected_text.count(QLatin1Char('\n'));
952  int cols = cells.size() / rows;
953  if (cells.size() % rows != 0) {
954  // error, uneven number of columns, probably bad data
955  QMessageBox::critical(this, tr("Error"),
956  tr("Invalid clipboard data, unable to perform paste operation."));
957  return;
958  }
959 
960  // Give an error if there are too few rows. A better solution would be to expand the view size
961  if (indexes.front().row() + rows > model()->rowCount()) {
962  // error, clipboard does not match current number of rows
963  QMessageBox::critical(this, tr("Error"),
964  tr("Invalid operation, pasting would exceed the number of rows."));
965  return;
966  }
967 
968  // Give an error if there are too few columns. A better solution would be to expand the view
969  // size
970  if (indexes.front().column() + cols > model()->columnCount()) {
971  // error, clipboard does not match current number of columns
972  QMessageBox::critical(this, tr("Error"),
973  tr("Invalid operation, pasting would exceed the number of columns."));
974  return;
975  }
976 
977  // Paste the results into the appropriate cells
978  int cell = 0;
979  for (int row = 0; row < rows; ++row) {
980  for (int col = 0; col < cols; ++col, ++cell) {
981  QModelIndex index = model()->index(indexes.front().row() + row,
982  indexes.front().column() + col, QModelIndex());
983  model()->setData(index, cells[cell]);
984  }
985  }
986 }
987 
988 void QFrozenTableViewWithCopyPaste::deleteCells()
989 {
990  QItemSelectionModel *selection = selectionModel();
991  QModelIndexList indices = selection->selectedIndexes();
992 
993  if (indices.size() < 1)
994  return;
995 
996  foreach (QModelIndex index, indices) {
997  QStandardItemModel *stdModel = dynamic_cast<QStandardItemModel *>(model());
998  if (stdModel == NULL) {
999  Q_ASSERT(0);
1000  return;
1001  }
1002 
1003  stdModel->takeItem(index.row(), index.column());
1004  }
1005 
1006  // Clear the selection. If this is not done, then the cells stay selected.
1007  selection->clear();
1008 }
1009 
1011 {
1012  if (event->matches(QKeySequence::Copy)) {
1013  copy();
1014  } else if (event->matches(QKeySequence::Paste)) {
1015  paste();
1016  } else if (event->matches(QKeySequence::Delete) || event->key() == Qt::Key_Backspace) {
1017  deleteCells();
1018  } else {
1019  QTableView::keyPressEvent(event);
1020  }
1021 }
1022 
1024 {
1025  setModel(model);
1026  frozenTableView = new QTableView(this);
1027 
1028  init();
1029 
1030  // connect the headers and scrollbars of both tableviews together
1031  connect(horizontalHeader(), &QHeaderView::sectionResized, this,
1032  &QFrozenTableViewWithCopyPaste::updateSectionWidth);
1033  connect(verticalHeader(), &QHeaderView::sectionResized, this,
1034  &QFrozenTableViewWithCopyPaste::updateSectionHeight);
1035 
1036  connect(frozenTableView->horizontalScrollBar(), &QAbstractSlider::valueChanged,
1037  horizontalScrollBar(), &QAbstractSlider::setValue);
1038  connect(horizontalScrollBar(), &QAbstractSlider::valueChanged,
1039  frozenTableView->horizontalScrollBar(), &QAbstractSlider::setValue);
1040 }
1041 
1043 {
1044  delete frozenTableView;
1045 }
1046 
1051 void QFrozenTableViewWithCopyPaste::init()
1052 {
1053  frozenModel = new QStandardItemModel(0, 0, this); // 0 Rows and 0 Columns
1054 
1055  frozenTableView->setModel(frozenModel);
1056  frozenTableView->setFocusPolicy(Qt::NoFocus);
1057  frozenTableView->horizontalHeader()->hide();
1058  frozenTableView->verticalHeader()->setSectionResizeMode(QHeaderView::Fixed);
1059 
1060  viewport()->stackUnder(frozenTableView);
1061 
1062  // Set table color to green background and red text. This is ugly as sin, but functional for the
1063  // moment.
1064  frozenTableView->setStyleSheet("QTableView { border: none;"
1065  "color: #FF0000;"
1066  "background-color: #8EDE21;"
1067  "selection-background-color: #999}"); // for demo purposes
1068  for (int row = 1; row < model()->rowCount(); row++)
1069  frozenTableView->setRowHidden(row, true);
1070 
1071  frozenTableView->setRowHeight(rowHeight(0), 0);
1072 
1073  frozenTableView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
1074  this->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
1075  frozenTableView->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
1076  frozenTableView->show();
1077 
1078  updateFrozenTableGeometry(0);
1079 
1080  setHorizontalScrollMode(ScrollPerPixel);
1081  setVerticalScrollMode(ScrollPerPixel);
1082  frozenTableView->setHorizontalScrollMode(ScrollPerPixel);
1083 
1084  // Turn off any kind of selection or interaction with the frozen table
1085  frozenTableView->setEnabled(false);
1086 
1087  // TODO: Make it so that the wheel events in the frozen table are passed to the main table.
1088 }
1089 
1090 void QFrozenTableViewWithCopyPaste::updateSectionWidth(int logicalIndex, int, int newSize)
1091 {
1092  frozenTableView->setColumnWidth(logicalIndex, newSize);
1093  updateFrozenTableGeometry(0);
1094 }
1095 
1096 void QFrozenTableViewWithCopyPaste::updateSectionHeight(int logicalIndex, int, int newSize)
1097 {
1098  if (logicalIndex == 0) {
1099  frozenTableView->setRowHeight(0, newSize);
1100  updateFrozenTableGeometry(0);
1101  }
1102 }
1103 
1110 {
1111  for (int x = 0; x < frozenTableView->model()->columnCount(); ++x) {
1112  frozenTableView->setColumnWidth(x, horizontalHeader()->sectionSize(x));
1113  }
1114  updateFrozenTableGeometry(value);
1115 }
1116 
1118 {
1119  QTableView::resizeEvent(event);
1120 }
1121 
1122 void QFrozenTableViewWithCopyPaste::scrollTo(const QModelIndex &index, ScrollHint hint)
1123 {
1124  if (index.row() != 0) {
1125  QTableView::scrollTo(index, hint);
1126  }
1127 }
1128 
1129 void QFrozenTableViewWithCopyPaste::updateFrozenTableGeometry(int verticalHeaderWidth)
1130 {
1131  if (verticalHeaderWidth == 0)
1132  verticalHeaderWidth = verticalHeader()->width();
1133  int col_width = 0;
1134  for (int i = 0; i < this->model()->columnCount(); ++i) {
1135  col_width += columnWidth(i);
1136  }
1137  frozenTableView->setGeometry(frameWidth(), horizontalHeader()->height() + frameWidth(),
1138  verticalHeaderWidth + col_width, rowHeight(0));
1139 }
1140 
1146 void QFrozenTableViewWithCopyPaste::setHorizontalHeaderItem(int column, QStandardItem *item)
1147 {
1148  frozenModel->setHorizontalHeaderItem(column, item);
1149  updateFrozenTableGeometry(0);
1150 }
1151 
1156 bool QFrozenTableViewWithCopyPaste::removeColumns(int column, int count, const QModelIndex &parent)
1157 {
1158  bool ret = frozenModel->removeColumns(column, count, parent);
1159  updateFrozenTableGeometry(0);
1160 
1161  return ret;
1162 }
1163 
virtual Metadata getDefaultMetadata()=0
virtual void setMetadata(const Metadata &mdata)=0
void completedMetadataWrite(bool)
virtual void keyPressEvent(QKeyEvent *event)
TelemetrySchedulerGadgetWidget(QWidget *parent=nullptr)
bool removeColumns(int column, int count, const QModelIndex &parent=QModelIndex())
QFrozenTableViewWithCopyPaste::removeColumns Ensures that the frozen table geometry is updated when c...
Core plugin system that manages the plugins, their life cycle and their registered objects...
Definition: pluginmanager.h:53
virtual void resizeEvent(QResizeEvent *event)
for i
Definition: OPPlots.m:140
void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const
QFrozenTableViewWithCopyPaste(QAbstractItemModel *model)
void objectUpdated(UAVObject *obj)
Signal sent whenever any field of the object is updated.
void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const
DataFields data
virtual Metadata getMetadata()=0
UAVMetaObject * getMetaObject()
bool setAllNonSettingsMetadata(QMap< QString, UAVObject::Metadata >)
UAVObjectUtilManager::setAllNonSettingsMetadata Convenience function for calling setMetadata.
Parse log file
void saveObjectToFlash(UAVObject *obj)
UAVObjectUtilManager::saveObjectToSD Add a new object to save in the queue.
The QFrozenTableViewWithCopyPaste class QTableView with support for a frozen row as well as copy and ...
bool getIsPresentOnHardware() const
void fixGeometry(int value)
This class uses a tableview inside a table view to achieve the frozen row effect on the 1st row this ...
quint32 getObjID()
Definition: uavobject.cpp:107
void scrollTo(const QModelIndex &index, ScrollHint hint=EnsureVisible)
The SchedulerModel class Subclasses QStandardItemModel in order to reimplement the editable flags...
quint32 getNumBytes()
Definition: uavobject.cpp:155
SpinBoxDelegate(QObject *parent=nullptr)
void setHorizontalHeaderItem(int column, QStandardItem *item)
QFrozenTableViewWithCopyPaste::setHorizontalHeaderItem Ensures that the frozen table geometry is upda...
QString getName()
Definition: uavobject.cpp:131
void saveCompleted(int objectID, bool status)
x
Definition: OPPlots.m:100
UAVObject * getObject(const QString &name, quint32 instId=0)
QVector< QVector< UAVDataObject * > > getDataObjectsVector()
void setEditorData(QWidget *editor, const QModelIndex &index) const
Metadata getData()
e
Definition: OPPlots.m:99
QWidget * createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const
void presentOnHardwareChanged(UAVDataObject *)