dataload_csv.cpp
Go to the documentation of this file.
1 #include "datetimehelp.h"
2 #include "dataload_csv.h"
3 
4 #include <QTextStream>
5 #include <QFile>
6 #include <QMessageBox>
7 #include <QDebug>
8 #include <QSettings>
9 #include <QProgressDialog>
10 #include <QDateTime>
11 #include <QInputDialog>
12 #include <QPushButton>
13 #include <QSyntaxStyle>
14 #include <QRadioButton>
15 
16 #include <array>
17 #include <set>
18 
19 #include <QStandardItemModel>
20 
21 static constexpr int TIME_INDEX_NOT_DEFINED = -2;
22 static constexpr int TIME_INDEX_GENERATED = -1;
23 static constexpr const char* INDEX_AS_TIME = "__TIME_INDEX_GENERATED__";
24 
25 void SplitLine(const QString& line, QChar separator, QStringList& parts)
26 {
27  parts.clear();
28  bool inside_quotes = false;
29  bool quoted_word = false;
30  int start_pos = 0;
31 
32  int quote_start = 0;
33  int quote_end = 0;
34 
35  for (int pos = 0; pos < line.size(); pos++)
36  {
37  if (line[pos] == '"')
38  {
39  if (inside_quotes)
40  {
41  quoted_word = true;
42  quote_end = pos - 1;
43  }
44  else
45  {
46  quote_start = pos + 1;
47  }
48  inside_quotes = !inside_quotes;
49  }
50 
51  bool part_completed = false;
52  bool add_empty = false;
53  int end_pos = pos;
54 
55  if ((!inside_quotes && line[pos] == separator))
56  {
57  part_completed = true;
58  }
59  if (pos + 1 == line.size())
60  {
61  part_completed = true;
62  end_pos = pos + 1;
63  // special case
64  if (line[pos] == separator)
65  {
66  end_pos = pos;
67  add_empty = true;
68  }
69  }
70 
71  if (part_completed)
72  {
73  QString part;
74  if (quoted_word)
75  {
76  part = line.mid(quote_start, quote_end - quote_start + 1);
77  }
78  else
79  {
80  part = line.mid(start_pos, end_pos - start_pos);
81  }
82 
83  parts.push_back(part.trimmed());
84  start_pos = pos + 1;
85  quoted_word = false;
86  inside_quotes = false;
87  }
88  if (add_empty)
89  {
90  parts.push_back(QString());
91  }
92  }
93 }
94 
96 {
97  _extensions.push_back("csv");
98  _delimiter = ',';
100  // setup the dialog
101 
102  _dialog = new QDialog();
103  _ui = new Ui::DialogCSV();
104  _ui->setupUi(_dialog);
105 
107 
108  _ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
109 
110  connect(_ui->radioButtonSelect, &QRadioButton::toggled, this, [this](bool checked) {
111  _ui->listWidgetSeries->setEnabled(checked);
112  auto selected = _ui->listWidgetSeries->selectionModel()->selectedIndexes();
113  bool box_enabled = !checked || selected.size() == 1;
114  _ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(box_enabled);
115  });
116  connect(_ui->listWidgetSeries, &QListWidget::itemSelectionChanged, this, [this]() {
117  auto selected = _ui->listWidgetSeries->selectionModel()->selectedIndexes();
118  bool box_enabled = _ui->radioButtonIndex->isChecked() || selected.size() == 1;
119  _ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(box_enabled);
120  });
121 
122  connect(_ui->listWidgetSeries, &QListWidget::itemDoubleClicked, this,
123  [this]() { emit _ui->buttonBox->accepted(); });
124 
125  connect(_ui->radioCustomTime, &QRadioButton::toggled, this,
126  [this](bool checked) { _ui->lineEditDateFormat->setEnabled(checked); });
127 
128  connect(_ui->dateTimeHelpButton, &QPushButton::clicked, this,
129  [this]() { _dateTime_dialog->show(); });
130  _ui->rawText->setHighlighter(&_csvHighlighter);
131 
132  QSizePolicy sp_retain = _ui->tableView->sizePolicy();
133  sp_retain.setRetainSizeWhenHidden(true);
134  _ui->tableView->setSizePolicy(sp_retain);
135 
136  _ui->splitter->setStretchFactor(0, 1);
137  _ui->splitter->setStretchFactor(1, 2);
138 
139  _model = new QStandardItemModel;
140  _ui->tableView->setModel(_model);
141 }
142 
144 {
145  delete _ui;
146  delete _dateTime_dialog;
147  delete _dialog;
148 }
149 
150 const std::vector<const char*>& DataLoadCSV::compatibleFileExtensions() const
151 {
152  return _extensions;
153 }
154 
155 void DataLoadCSV::parseHeader(QFile& file, std::vector<std::string>& column_names)
156 {
157  file.open(QFile::ReadOnly);
158 
160 
161  column_names.clear();
162  _ui->listWidgetSeries->clear();
163 
164  QTextStream inA(&file);
165  // The first line should contain the header. If it contains a number, we will
166  // apply a name ourselves
167  QString first_line = inA.readLine();
168 
169  QString preview_lines = first_line + "\n";
170 
171  QStringList firstline_items;
172  SplitLine(first_line, _delimiter, firstline_items);
173 
174  int is_number_count = 0;
175 
176  std::set<std::string> different_columns;
177 
178  // check if all the elements in first row are numbers
179  for (int i = 0; i < firstline_items.size(); i++)
180  {
181  bool isNum;
182  firstline_items[i].trimmed().toDouble(&isNum);
183  if (isNum)
184  {
185  is_number_count++;
186  }
187  }
188 
189  if (is_number_count == firstline_items.size())
190  {
191  for (int i = 0; i < firstline_items.size(); i++)
192  {
193  auto field_name = QString("_Column_%1").arg(i);
194  auto column_name = field_name.toStdString();
195  column_names.push_back(column_name);
196  different_columns.insert(column_name);
197  }
198  }
199  else
200  {
201  for (int i = 0; i < firstline_items.size(); i++)
202  {
203  // remove annoying prefix
204  QString field_name(firstline_items[i].trimmed());
205 
206  if (field_name.isEmpty())
207  {
208  field_name = QString("_Column_%1").arg(i);
209  }
210  auto column_name = field_name.toStdString();
211  column_names.push_back(column_name);
212  different_columns.insert(column_name);
213  }
214  }
215 
216  if (different_columns.size() < column_names.size())
217  {
219  {
220  QMessageBox::warning(nullptr, "Duplicate Column Name",
221  "Multiple Columns have the same name.\n"
222  "The column number will be added (as suffix) to the name.");
224  }
225 
226  std::vector<size_t> repeated_columns;
227  for (size_t i = 0; i < column_names.size(); i++)
228  {
229  repeated_columns.clear();
230  repeated_columns.push_back(i);
231 
232  for (size_t j = i + 1; j < column_names.size(); j++)
233  {
234  if (column_names[i] == column_names[j])
235  {
236  repeated_columns.push_back(j);
237  }
238  }
239  if (repeated_columns.size() > 1)
240  {
241  for (size_t index : repeated_columns)
242  {
243  QString suffix = "_";
244  suffix += QString::number(index).rightJustified(2, '0');
245  column_names[index] += suffix.toStdString();
246  }
247  }
248  }
249  }
250 
251  QStringList column_labels;
252  for (const auto& name : column_names)
253  {
254  auto qname = QString::fromStdString(name);
255  _ui->listWidgetSeries->addItem(qname);
256  column_labels.push_back(qname);
257  }
258  _model->setColumnCount(column_labels.size());
259  _model->setHorizontalHeaderLabels(column_labels);
260 
261  QStringList lines;
262 
263  for (int row = 0; row <= 100 && !inA.atEnd(); row++)
264  {
265  auto line = inA.readLine();
266  preview_lines += line + "\n";
267  lines.push_back(line);
268  }
269 
270  _model->setRowCount(lines.count());
271  for (int row = 0; row < lines.count(); row++)
272  {
273  QVector<QStringRef> lineToken = lines[row].splitRef(_delimiter);
274  for (int j = 0; j < lineToken.size(); j++)
275  {
276  QString value = lineToken[j].toString();
277  if (auto item = _model->item(row, j))
278  {
279  item->setText(value);
280  }
281  else
282  {
283  _model->setItem(row, j, new QStandardItem(value));
284  }
285  }
286  }
287 
288  _ui->rawText->setPlainText(preview_lines);
289  _ui->tableView->resizeColumnsToContents();
290 
291  file.close();
292 }
293 
294 int DataLoadCSV::launchDialog(QFile& file, std::vector<std::string>* column_names)
295 {
296  column_names->clear();
297  _ui->tabWidget->setCurrentIndex(0);
298 
299  QSettings settings;
300  _dialog->restoreGeometry(settings.value("DataLoadCSV.geometry").toByteArray());
301 
302  _ui->radioButtonIndex->setChecked(
303  settings.value("DataLoadCSV.useIndex", false).toBool());
304  bool use_custom_time = settings.value("DataLoadCSV.useDateFormat", false).toBool();
305  if (use_custom_time)
306  {
307  _ui->radioCustomTime->setChecked(true);
308  }
309  else
310  {
311  _ui->radioAutoTime->setChecked(true);
312  }
313  _ui->lineEditDateFormat->setText(
314  settings.value("DataLoadCSV.dateFormat", "yyyy-MM-dd hh:mm:ss").toString());
315 
316  // suggest separator
317  {
318  file.open(QFile::ReadOnly);
319  QTextStream in(&file);
320 
321  QString first_line = in.readLine();
322  int comma_count = first_line.count(QLatin1Char(','));
323  int semicolon_count = first_line.count(QLatin1Char(';'));
324  int space_count = first_line.count(QLatin1Char(' '));
325  int tab_count = first_line.count(QLatin1Char('\t'));
326 
327  if (comma_count > 3 && comma_count > semicolon_count)
328  {
329  _ui->comboBox->setCurrentIndex(0);
330  _delimiter = ',';
331  }
332  if (semicolon_count > 3 && semicolon_count > comma_count)
333  {
334  _ui->comboBox->setCurrentIndex(1);
335  _delimiter = ';';
336  }
337  if (space_count > 3 && comma_count == 0 && semicolon_count == 0)
338  {
339  _ui->comboBox->setCurrentIndex(2);
340  _delimiter = ' ';
341  }
342  if (tab_count > 3 && comma_count == 0 && semicolon_count == 0)
343  {
344  _ui->comboBox->setCurrentIndex(3);
345  _delimiter = '\t';
346  }
347  file.close();
348  }
349 
350  QString theme = settings.value("StyleSheet::theme", "light").toString();
351  auto style_path = (theme == "light") ? ":/resources/lua_style_light.xml" :
352  ":/resources/lua_style_dark.xml";
353 
354  QFile fl(style_path);
355  if (fl.open(QIODevice::ReadOnly))
356  {
357  auto style = new QSyntaxStyle(this);
358  if (style->load(fl.readAll()))
359  {
360  _ui->rawText->setSyntaxStyle(style);
361  }
362  }
363 
364  // temporary connection
365  std::unique_ptr<QObject> pcontext(new QObject);
366  QObject* context = pcontext.get();
367  QObject::connect(_ui->comboBox, qOverload<int>(&QComboBox::currentIndexChanged),
368  context, [&](int index) {
369  const std::array<char, 4> delimiters = { ',', ';', ' ', '\t' };
370  _delimiter = delimiters[std::clamp(index, 0, 3)];
372  parseHeader(file, *column_names);
373  });
374 
375  // parse the header once and launch the dialog
376  parseHeader(file, *column_names);
377 
378  QString previous_index = settings.value("DataLoadCSV.timeIndex", "").toString();
379  if (previous_index.isEmpty() == false)
380  {
381  auto items = _ui->listWidgetSeries->findItems(previous_index, Qt::MatchExactly);
382  if (items.size() > 0)
383  {
384  _ui->listWidgetSeries->setCurrentItem(items.front());
385  }
386  }
387 
388  int res = _dialog->exec();
389 
390  settings.setValue("DataLoadCSV.geometry", _dialog->saveGeometry());
391  settings.setValue("DataLoadCSV.useIndex", _ui->radioButtonIndex->isChecked());
392  settings.setValue("DataLoadCSV.useDateFormat", _ui->radioCustomTime->isChecked());
393  settings.setValue("DataLoadCSV.dateFormat", _ui->lineEditDateFormat->text());
394 
395  if (res == QDialog::Rejected)
396  {
397  return TIME_INDEX_NOT_DEFINED;
398  }
399 
400  if (_ui->radioButtonIndex->isChecked())
401  {
402  return TIME_INDEX_GENERATED;
403  }
404 
405  QModelIndexList indexes = _ui->listWidgetSeries->selectionModel()->selectedRows();
406  if (indexes.size() == 1)
407  {
408  int row = indexes.front().row();
409  auto item = _ui->listWidgetSeries->item(row);
410  settings.setValue("DataLoadCSV.timeIndex", item->text());
411  return row;
412  }
413 
414  return TIME_INDEX_NOT_DEFINED;
415 }
416 
417 std::optional<double> AutoParseTimestamp(const QString& str)
418 {
419  bool is_number = false;
420  QString str_trimmed = str.trimmed();
421  double val = 0.0;
422 
423  // Support the case where the timestamp is in nanoseconds / microseconds
424  int64_t ts = str.toLong(&is_number);
425  const int64_t first_ts = 1400000000; // July 14, 2017
426  const int64_t last_ts = 2000000000; // May 18, 2033
427  if (is_number)
428  {
429  // check if it is an absolute time in nanoseconds.
430  // convert to seconds if it is
431  if (ts > first_ts * 1e9 && ts < last_ts * 1e9)
432  {
433  val = double(ts) * 1e-9;
434  }
435  else if (ts > first_ts * 1e6 && ts < last_ts * 1e6)
436  {
437  // check if it is an absolute time in microseconds.
438  // convert to seconds if it is
439  val = double(ts) * 1e-6;
440  }
441  else
442  {
443  val = double(ts);
444  }
445  }
446  else
447  {
448  // Try a double value (seconds)
449  val = str.toDouble(&is_number);
450  }
451 
452  // handle numbers with comma instead of point as decimal separator
453  if (!is_number)
454  {
455  static QLocale locale_with_comma(QLocale::German);
456  val = locale_with_comma.toDouble(str, &is_number);
457  }
458  if (!is_number)
459  {
460  QDateTime ts = QDateTime::fromString(str, Qt::ISODateWithMs);
461  if (ts.isValid())
462  {
463  return double(ts.toMSecsSinceEpoch()) / 1000.0;
464  }
465  else
466  {
467  return std::nullopt;
468  }
469  }
470  return is_number ? std::optional<double>(val) : std::nullopt;
471 };
472 
473 std::optional<double> FormatParseTimestamp(const QString& str, const QString& format)
474 {
475  QDateTime ts = QDateTime::fromString(str, format);
476  if (ts.isValid())
477  {
478  return double(ts.toMSecsSinceEpoch()) / 1000.0;
479  }
480  return std::nullopt;
481 }
482 
484 {
486 
487  _fileInfo = info;
488 
489  QFile file(info->filename);
490  std::vector<std::string> column_names;
491 
492  int time_index = TIME_INDEX_NOT_DEFINED;
493 
494  if (!info->plugin_config.hasChildNodes())
495  {
496  _default_time_axis.clear();
497  time_index = launchDialog(file, &column_names);
498  }
499  else
500  {
501  parseHeader(file, column_names);
503  {
504  time_index = TIME_INDEX_GENERATED;
505  }
506  else
507  {
508  for (size_t i = 0; i < column_names.size(); i++)
509  {
510  if (column_names[i] == _default_time_axis)
511  {
512  time_index = i;
513  break;
514  }
515  }
516  }
517  }
518 
519  if (time_index == TIME_INDEX_NOT_DEFINED)
520  {
521  return false;
522  }
523 
524  //-----------------------------------
525  bool interrupted = false;
526  int linecount = 0;
527 
528  // count the number of lines first
529  int tot_lines = 0;
530  {
531  file.open(QFile::ReadOnly);
532  QTextStream in(&file);
533  while (!in.atEnd())
534  {
535  in.readLine();
536  tot_lines++;
537  }
538  file.close();
539  }
540 
541  QProgressDialog progress_dialog;
542  progress_dialog.setWindowTitle("Loading the CSV file");
543  progress_dialog.setLabelText("Loading... please wait");
544  progress_dialog.setWindowModality(Qt::ApplicationModal);
545  progress_dialog.setRange(0, tot_lines - 1);
546  progress_dialog.setAutoClose(true);
547  progress_dialog.setAutoReset(true);
548  progress_dialog.show();
549 
550  //---- build plots_vector from header ------
551 
552  std::vector<PlotData*> plots_vector;
553  std::vector<StringSeries*> string_vector;
554  bool sortRequired = false;
555 
556  for (unsigned i = 0; i < column_names.size(); i++)
557  {
558  const std::string& field_name = (column_names[i]);
559  auto num_it = plot_data.addNumeric(field_name);
560  plots_vector.push_back(&(num_it->second));
561 
562  auto str_it = plot_data.addStringSeries(field_name);
563  string_vector.push_back(&(str_it->second));
564  }
565 
566  //-----------------
567  double prev_time = std::numeric_limits<double>::lowest();
568  const QString format_string = _ui->lineEditDateFormat->text();
569  const bool parse_date_format = _ui->radioCustomTime->isChecked();
570 
571  auto ParseNumber = [&](QString str, bool& is_number) {
572  QString str_trimmed = str.trimmed();
573  double val = val = str_trimmed.toDouble(&is_number);
574  // handle numbers with comma instead of point as decimal separator
575  if (!is_number)
576  {
577  static QLocale locale_with_comma(QLocale::German);
578  val = locale_with_comma.toDouble(str_trimmed, &is_number);
579  }
580  if (!is_number)
581  {
582  QDateTime ts;
583  if (parse_date_format)
584  {
585  ts = QDateTime::fromString(str_trimmed, format_string);
586  }
587  else
588  {
589  ts = QDateTime::fromString(str_trimmed, Qt::ISODateWithMs);
590  }
591  is_number = ts.isValid();
592  if (is_number)
593  {
594  val = ts.toMSecsSinceEpoch() / 1000.0;
595  }
596  }
597  return val;
598  };
599 
600  file.open(QFile::ReadOnly);
601  QTextStream in(&file);
602  // remove first line (header)
603  QString header_str = in.readLine();
604  QStringList string_items;
605  QStringList header_string_items;
606 
607  SplitLine(header_str, _delimiter, header_string_items);
608  QString time_header_str;
609  QString t_str;
610  QString prev_t_str;
611 
612  while (!in.atEnd())
613  {
614  QString line = in.readLine();
615  SplitLine(line, _delimiter, string_items);
616 
617  // empty line? just try skipping
618  if (string_items.size() == 0)
619  {
620  continue;
621  }
622 
623  if (string_items.size() != column_names.size())
624  {
625  QMessageBox msgBox;
626  msgBox.setWindowTitle(tr("Error reading file"));
627  msgBox.setText(tr("The number of values at line %1 is %2,\n"
628  "but the expected number of columns is %3.\n"
629  "Aborting...")
630  .arg(linecount + 2)
631  .arg(string_items.size())
632  .arg(column_names.size()));
633 
634  msgBox.setDetailedText(tr("File: \"%1\" \n\n"
635  "Error reading file | Mismatched field count\n"
636  "Delimiter: [%2]\n"
637  "Header fields: %6\n"
638  "Fields on line [%4]: %7\n\n"
639  "File Preview:\n"
640  "[1]%3\n"
641  "[...]\n"
642  "[%4]%5\n")
644  .arg(_delimiter)
645  .arg(header_str)
646  .arg(linecount + 2)
647  .arg(line)
648  .arg(column_names.size())
649  .arg(string_items.size()));
650 
651  QPushButton* abortButton = msgBox.addButton(QMessageBox::Ok);
652 
653  msgBox.setIcon(QMessageBox::Warning);
654 
655  msgBox.exec();
656 
657  return false;
658  }
659 
660  double timestamp = linecount;
661 
662  if (time_index >= 0)
663  {
664  t_str = string_items[time_index];
665  const auto time_trimm = t_str.trimmed();
666  bool is_number = false;
667  if (parse_date_format)
668  {
669  if (auto ts = FormatParseTimestamp(time_trimm, format_string))
670  {
671  is_number = true;
672  timestamp = *ts;
673  }
674  }
675  else
676  {
677  if (auto ts = AutoParseTimestamp(time_trimm))
678  {
679  is_number = true;
680  timestamp = *ts;
681  }
682  }
683 
684  time_header_str = header_string_items[time_index];
685 
686  if (!is_number)
687  {
688  QMessageBox msgBox;
689  msgBox.setWindowTitle(tr("Error reading file"));
690  msgBox.setText(tr("Couldn't parse timestamp on line %1 with string \"%2\" . "
691  "Aborting.\n")
692  .arg(linecount + 1)
693  .arg(t_str));
694 
695  msgBox.setDetailedText(tr("File: \"%1\" \n\n"
696  "Error reading file | Couldn't parse timestamp\n"
697  "Parsing format: [%4]\n"
698  "Time at line %2 : [%3]\n")
700  .arg(linecount + 1)
701  .arg(t_str)
702  .arg((parse_date_format && !format_string.isEmpty()) ?
703  format_string :
704  "None"));
705 
706  QPushButton* abortButton = msgBox.addButton(QMessageBox::Ok);
707 
708  msgBox.setIcon(QMessageBox::Warning);
709 
710  msgBox.exec();
711 
712  return false;
713  }
714 
715  if (prev_time > timestamp && !sortRequired)
716  {
717  QMessageBox msgBox;
718  QString timeName;
719  timeName = time_header_str;
720 
721  msgBox.setWindowTitle(tr("Selected time is not monotonic"));
722  msgBox.setText(tr("PlotJuggler detected that the time in this file is "
723  "non-monotonic. This may indicate an issue with the input "
724  "data. Continue? (Input file will not be modified but data "
725  "will be sorted by PlotJuggler)"));
726  msgBox.setDetailedText(tr("File: \"%1\" \n\n"
727  "Selected time is not monotonic\n"
728  "Time Index: %6 [%7]\n"
729  "Time at line %2 : %3\n"
730  "Time at line %4 : %5")
732  .arg(linecount + 1)
733  .arg(prev_t_str)
734  .arg(linecount + 2)
735  .arg(t_str)
736  .arg(time_index)
737  .arg(timeName));
738 
739  QPushButton* sortButton =
740  msgBox.addButton(tr("Continue"), QMessageBox::ActionRole);
741  QPushButton* abortButton = msgBox.addButton(QMessageBox::Abort);
742  msgBox.setIcon(QMessageBox::Warning);
743  msgBox.exec();
744 
745  if (msgBox.clickedButton() == abortButton)
746  {
747  return false;
748  }
749  else if (msgBox.clickedButton() == sortButton)
750  {
751  sortRequired = true;
752  }
753  else
754  {
755  return false;
756  }
757  }
758 
759  prev_time = timestamp;
760  prev_t_str = t_str;
761  }
762 
763  for (unsigned i = 0; i < string_items.size(); i++)
764  {
765  bool is_number = false;
766  const auto& str = string_items[i];
767  double y = ParseNumber(str, is_number);
768  if (is_number)
769  {
770  plots_vector[i]->pushBack({ timestamp, y });
771  }
772  else
773  {
774  string_vector[i]->pushBack({ timestamp, str.toStdString() });
775  }
776  }
777 
778  if (linecount++ % 100 == 0)
779  {
780  progress_dialog.setValue(linecount);
781  QApplication::processEvents();
782  if (progress_dialog.wasCanceled())
783  {
784  interrupted = true;
785  break;
786  }
787  }
788  }
789 
790  if (interrupted)
791  {
792  progress_dialog.cancel();
793  plot_data.clear();
794  return false;
795  }
796 
797  if (time_index >= 0)
798  {
799  _default_time_axis = column_names[time_index];
800  }
801  else if (time_index == TIME_INDEX_GENERATED)
802  {
804  }
805 
806  // cleanups
807  for (unsigned i = 0; i < column_names.size(); i++)
808  {
809  const auto& name = column_names[i];
810  bool is_numeric = true;
811  if (plots_vector[i]->size() == 0 && string_vector[i]->size() > 0)
812  {
813  is_numeric = false;
814  }
815  if (is_numeric)
816  {
817  plot_data.strings.erase(plot_data.strings.find(name));
818  }
819  else
820  {
821  plot_data.numeric.erase(plot_data.numeric.find(name));
822  }
823  }
824  return true;
825 }
826 
827 bool DataLoadCSV::xmlSaveState(QDomDocument& doc, QDomElement& parent_element) const
828 {
829  QDomElement elem = doc.createElement("parameters");
830  elem.setAttribute("time_axis", _default_time_axis.c_str());
831  elem.setAttribute("delimiter", _ui->comboBox->currentIndex());
832 
833  QString date_format;
834  if (_ui->radioCustomTime->isChecked())
835  {
836  elem.setAttribute("date_format", _ui->lineEditDateFormat->text());
837  }
838 
839  parent_element.appendChild(elem);
840  return true;
841 }
842 
843 bool DataLoadCSV::xmlLoadState(const QDomElement& parent_element)
844 {
845  QDomElement elem = parent_element.firstChildElement("parameters");
846  if (elem.isNull())
847  {
848  return false;
849  }
850  if (elem.hasAttribute("time_axis"))
851  {
852  _default_time_axis = elem.attribute("time_axis").toStdString();
853  }
854  if (elem.hasAttribute("delimiter"))
855  {
856  int separator_index = elem.attribute("delimiter").toInt();
857  _ui->comboBox->setCurrentIndex(separator_index);
858  switch (separator_index)
859  {
860  case 0:
861  _delimiter = ',';
862  break;
863  case 1:
864  _delimiter = ';';
865  break;
866  case 2:
867  _delimiter = ' ';
868  break;
869  }
870  }
871  if (elem.hasAttribute("date_format"))
872  {
873  _ui->radioCustomTime->setChecked(true);
874  _ui->lineEditDateFormat->setText(elem.attribute("date_format"));
875  }
876  else
877  {
878  _ui->radioAutoTime->setChecked(true);
879  }
880  return true;
881 }
DataLoadCSV::_ui
Ui::DialogCSV * _ui
Definition: dataload_csv.h:55
DataLoadCSV::_extensions
std::vector< const char * > _extensions
Definition: dataload_csv.h:44
DataLoadCSV::readDataFromFile
virtual bool readDataFromFile(PJ::FileLoadInfo *fileload_info, PlotDataMapRef &destination) override
Definition: dataload_csv.cpp:483
dataload_csv.h
DataLoadCSV::_fileInfo
FileLoadInfo * _fileInfo
Definition: dataload_csv.h:52
PJ::PlotDataMapRef::addStringSeries
StringSeriesMap::iterator addStringSeries(const std::string &name, PlotGroup::Ptr group={})
Definition: plotdata.cpp:63
PJ::FileLoadInfo
Definition: dataloader_base.h:18
DataLoadCSV::parseHeader
void parseHeader(QFile &file, std::vector< std::string > &ordered_names)
Definition: dataload_csv.cpp:155
SplitLine
void SplitLine(const QString &line, QChar separator, QStringList &parts)
Definition: dataload_csv.cpp:25
PJ::PlotDataMapRef::addNumeric
TimeseriesMap::iterator addNumeric(const std::string &name, PlotGroup::Ptr group={})
Definition: plotdata.cpp:51
TIME_INDEX_GENERATED
static constexpr int TIME_INDEX_GENERATED
Definition: dataload_csv.cpp:22
QVector
Definition: qwt_clipper.h:23
PJ::FileLoadInfo::plugin_config
QDomDocument plugin_config
Saved configuration from a previous run or a Layout file.
Definition: dataloader_base.h:25
DataLoadCSV::_model
QStandardItemModel * _model
Definition: dataload_csv.h:58
arg
auto arg(const Char *name, const T &arg) -> detail::named_arg< Char, T >
Definition: core.h:1875
DataLoadCSV::name
virtual const char * name() const override
Name of the plugin type, NOT the particular instance.
Definition: dataload_csv.h:29
detail::in
constexpr auto in(type t, int set) -> bool
Definition: core.h:628
DateTimeHelp
Definition: datetimehelp.h:11
datetimehelp.h
DataLoadCSV::~DataLoadCSV
virtual ~DataLoadCSV()
Definition: dataload_csv.cpp:143
mqtt_test_proto.y
y
Definition: mqtt_test_proto.py:35
PJ::PlotDataMapRef::numeric
TimeseriesMap numeric
Numerical timeseries.
Definition: plotdata.h:39
basic_format_string
Definition: core.h:2768
DataLoadCSV::launchDialog
int launchDialog(QFile &file, std::vector< std::string > *ordered_names)
Definition: dataload_csv.cpp:294
nonstd::span_lite::size
span_constexpr std::size_t size(span< T, Extent > const &spn)
Definition: span.hpp:1554
DataLoadCSV::multiple_columns_warning_
bool multiple_columns_warning_
Definition: dataload_csv.h:60
DataLoadCSV::_dateTime_dialog
DateTimeHelp * _dateTime_dialog
Definition: dataload_csv.h:56
DataLoadCSV::_delimiter
QChar _delimiter
Definition: dataload_csv.h:48
DataLoadCSV::DataLoadCSV
DataLoadCSV()
Definition: dataload_csv.cpp:95
DataLoadCSV::xmlLoadState
bool xmlLoadState(const QDomElement &parent_element) override
Override this method to load the status of the plugin from XML.
Definition: dataload_csv.cpp:843
PJ::PlotDataMapRef::clear
void clear()
Definition: plotdata.cpp:125
DataLoadCSV::_dialog
QDialog * _dialog
Definition: dataload_csv.h:54
AutoParseTimestamp
std::optional< double > AutoParseTimestamp(const QString &str)
Definition: dataload_csv.cpp:417
FormatParseTimestamp
std::optional< double > FormatParseTimestamp(const QString &str, const QString &format)
Definition: dataload_csv.cpp:473
format
auto format(const text_style &ts, const S &format_str, const Args &... args) -> std::basic_string< Char >
Definition: color.h:543
DataLoadCSV::_csvHighlighter
QCSVHighlighter _csvHighlighter
Definition: dataload_csv.h:50
QSyntaxStyle
Class, that describes Qt style parser for QCodeEditor.
Definition: QSyntaxStyle.hpp:13
TIME_INDEX_NOT_DEFINED
static constexpr int TIME_INDEX_NOT_DEFINED
Definition: dataload_csv.cpp:21
QCSVHighlighter::delimiter
QChar delimiter
Definition: QCSVHighlighter.hpp:31
INDEX_AS_TIME
static constexpr const char * INDEX_AS_TIME
Definition: dataload_csv.cpp:23
PJ::PlotDataMapRef
Definition: plotdata.h:34
eprosima::fastcdr::nullopt
static constexpr nullopt_t nullopt
nullopt is a constant of type nullopt_t that is used to indicate optional type with uninitialized sta...
Definition: optional.hpp:40
DataLoadCSV::compatibleFileExtensions
virtual const std::vector< const char * > & compatibleFileExtensions() const override
Provide a list of file extensions that this plugin can open.
Definition: dataload_csv.cpp:150
PJ::PlotDataMapRef::strings
StringSeriesMap strings
Series of strings.
Definition: plotdata.h:46
PJ::FileLoadInfo::filename
QString filename
name of the file to open
Definition: dataloader_base.h:21
DataLoadCSV::xmlSaveState
bool xmlSaveState(QDomDocument &doc, QDomElement &parent_element) const override
Override this method to save the status of the plugin to XML.
Definition: dataload_csv.cpp:827
DataLoadCSV::_default_time_axis
std::string _default_time_axis
Definition: dataload_csv.h:46


plotjuggler
Author(s): Davide Faconti
autogenerated on Mon Nov 11 2024 03:23:43