7 #include <QProgressDialog> 9 #include <QInputDialog> 10 #include <QPushButton> 11 #include "QSyntaxStyle" 15 #include <QStandardItemModel> 20 void SplitLine(
const QString& line, QChar separator, QStringList& parts)
23 bool inside_quotes =
false;
24 bool quoted_word =
false;
30 for (
int pos = 0; pos < line.size(); pos++)
41 quote_start = pos + 1;
43 inside_quotes = !inside_quotes;
46 bool part_completed =
false;
47 bool add_empty =
false;
50 if ((!inside_quotes && line[pos] == separator))
52 part_completed =
true;
54 if (pos + 1 == line.size())
56 part_completed =
true;
59 if (line[pos] == separator)
71 part = line.mid(quote_start, quote_end - quote_start + 1);
75 part = line.mid(start_pos, end_pos - start_pos);
78 parts.push_back(part.trimmed());
81 inside_quotes =
false;
85 parts.push_back(QString());
98 _ui =
new Ui::DialogCSV();
104 _ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(
false);
106 connect(
_ui->radioButtonSelect, &QRadioButton::toggled,
this, [
this](
bool checked) {
107 _ui->listWidgetSeries->setEnabled(checked);
108 auto selected = _ui->listWidgetSeries->selectionModel()->selectedIndexes();
109 bool box_enabled = !checked || selected.size() == 1;
110 _ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(box_enabled);
112 connect(
_ui->listWidgetSeries, &QListWidget::itemSelectionChanged,
this, [
this]() {
113 auto selected = _ui->listWidgetSeries->selectionModel()->selectedIndexes();
114 bool box_enabled = _ui->radioButtonIndex->isChecked() || selected.size() == 1;
115 _ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(box_enabled);
118 connect(
_ui->listWidgetSeries, &QListWidget::itemDoubleClicked,
this,
119 [
this]() { emit _ui->buttonBox->accepted(); });
121 connect(
_ui->checkBoxDateFormat, &QCheckBox::toggled,
this,
122 [
this](
bool checked) { _ui->lineEditDateFormat->setEnabled(checked); });
124 connect(
_ui->dateTimeHelpButton,&QPushButton::clicked,
this, [
this](){
125 _dateTime_dialog->show();
129 QSizePolicy sp_retain =
_ui->tableView->sizePolicy();
130 sp_retain.setRetainSizeWhenHidden(
true);
131 _ui->tableView->setSizePolicy(sp_retain);
133 _ui->splitter->setStretchFactor(0, 1);
134 _ui->splitter->setStretchFactor(1, 2);
136 _model =
new QStandardItemModel;
154 file.open(QFile::ReadOnly);
158 column_names.clear();
159 _ui->listWidgetSeries->clear();
161 QTextStream inA(&file);
164 QString first_line = inA.readLine();
166 QString preview_lines = first_line +
"\n";
168 QStringList firstline_items;
171 int is_number_count = 0;
173 std::set<std::string> different_columns;
176 for (
int i = 0; i < firstline_items.size(); i++)
179 firstline_items[i].trimmed().toDouble(&isNum);
186 if (is_number_count == firstline_items.size())
188 for (
int i = 0; i < firstline_items.size(); i++)
190 auto field_name = QString(
"_Column_%1").arg(i);
191 auto column_name = field_name.toStdString();
192 column_names.push_back(column_name);
193 different_columns.insert(column_name);
198 for (
int i = 0; i < firstline_items.size(); i++)
201 QString field_name(firstline_items[i].trimmed());
203 if (field_name.isEmpty())
205 field_name = QString(
"_Column_%1").arg(i);
207 auto column_name = field_name.toStdString();
208 column_names.push_back(column_name);
209 different_columns.insert(column_name);
213 if (different_columns.size() < column_names.size())
217 QMessageBox::warning(
nullptr,
"Duplicate Column Name",
218 "Multiple Columns have the same name.\n" 219 "The column number will be added (as suffix) to the name.");
223 std::vector<size_t> repeated_columns;
224 for (
size_t i = 0; i < column_names.size(); i++)
226 repeated_columns.clear();
227 repeated_columns.push_back(i);
229 for (
size_t j = i + 1; j < column_names.size(); j++)
231 if (column_names[i] == column_names[j])
233 repeated_columns.push_back(j);
236 if (repeated_columns.size() > 1)
238 for (
size_t index : repeated_columns)
240 QString suffix =
"_";
241 suffix += QString::number(
index).rightJustified(2,
'0');
242 column_names[
index] += suffix.toStdString();
248 QStringList column_labels;
249 for (
const auto&
name : column_names)
251 auto qname = QString::fromStdString(
name );
252 _ui->listWidgetSeries->addItem( qname );
253 column_labels.push_back( qname );
255 _model->setColumnCount(column_labels.size());
256 _model->setHorizontalHeaderLabels(column_labels);
260 for(
int row = 0; row <= 100 && !inA.atEnd(); row ++)
262 auto line = inA.readLine();
263 preview_lines += line +
"\n";
264 lines.push_back( line );
267 _model->setRowCount( lines.count() );
268 for(
int row = 0; row < lines.count(); row ++)
271 for (
int j = 0; j < lineToken.size(); j++)
273 QString
value = lineToken[j].toString();
274 if(
auto item =
_model->item(row, j) )
276 item->setText(value);
279 _model->setItem(row, j,
new QStandardItem(value));
284 _ui->rawText->setPlainText(preview_lines);
285 _ui->tableView->resizeColumnsToContents();
292 column_names->clear();
293 _ui->tabWidget->setCurrentIndex(0);
296 _dialog->restoreGeometry(settings.value(
"DataLoadCSV.geometry").toByteArray());
298 _ui->radioButtonIndex->setChecked(
299 settings.value(
"DataLoadCSV.useIndex",
false).toBool());
300 _ui->checkBoxDateFormat->setChecked(
301 settings.value(
"DataLoadCSV.useDateFormat",
false).toBool());
302 _ui->lineEditDateFormat->setText(
303 settings.value(
"DataLoadCSV.dateFormat",
"yyyy-MM-dd hh:mm:ss").toString());
307 file.open(QFile::ReadOnly);
308 QTextStream in(&file);
310 QString first_line = in.readLine();
311 int comma_count = first_line.count(QLatin1Char(
','));
312 int semicolon_count = first_line.count(QLatin1Char(
';'));
313 int space_count = first_line.count(QLatin1Char(
' '));
314 int tab_count = first_line.count(QLatin1Char(
'\t'));
316 if (comma_count > 3 && comma_count > semicolon_count)
318 _ui->comboBox->setCurrentIndex(0);
321 if (semicolon_count > 3 && semicolon_count > comma_count)
323 _ui->comboBox->setCurrentIndex(1);
326 if (space_count > 3 && comma_count == 0 && semicolon_count == 0)
328 _ui->comboBox->setCurrentIndex(2);
331 if (tab_count > 3 && comma_count == 0 && semicolon_count == 0)
333 _ui->comboBox->setCurrentIndex(3);
339 QString theme = settings.value(
"StyleSheet::theme",
"light").toString();
340 auto style_path = (theme ==
"light" ) ?
":/resources/lua_style_light.xml" :
341 ":/resources/lua_style_dark.xml";
343 QFile fl(style_path);
344 if (fl.open(QIODevice::ReadOnly))
347 if (style->load(fl.readAll()))
349 _ui->rawText->setSyntaxStyle( style );
354 std::unique_ptr<QObject> pcontext(
new QObject);
355 QObject* context = pcontext.get();
356 QObject::connect(
_ui->comboBox, qOverload<int>(&QComboBox::currentIndexChanged),
357 context, [&](
int index) {
380 QString previous_index = settings.value(
"DataLoadCSV.timeIndex",
"").toString();
381 if (previous_index.isEmpty() ==
false)
383 auto items =
_ui->listWidgetSeries->findItems(previous_index, Qt::MatchExactly);
384 if( items.size() > 0 )
386 _ui->listWidgetSeries->setCurrentItem(items.front());
392 settings.setValue(
"DataLoadCSV.geometry",
_dialog->saveGeometry());
393 settings.setValue(
"DataLoadCSV.useIndex",
_ui->radioButtonIndex->isChecked());
394 settings.setValue(
"DataLoadCSV.useDateFormat",
_ui->checkBoxDateFormat->isChecked());
395 settings.setValue(
"DataLoadCSV.dateFormat",
_ui->lineEditDateFormat->text());
397 if (res == QDialog::Rejected)
402 if (
_ui->radioButtonIndex->isChecked())
407 QModelIndexList indexes =
_ui->listWidgetSeries->selectionModel()->selectedRows();
408 if (indexes.size() == 1)
410 int row = indexes.front().row();
411 auto item =
_ui->listWidgetSeries->item(row);
412 settings.setValue(
"DataLoadCSV.timeIndex", item->text());
421 bool use_provided_configuration =
false;
429 use_provided_configuration =
true;
434 std::vector<std::string> column_names;
438 if (!use_provided_configuration)
451 for (
size_t i = 0; i < column_names.size(); i++)
468 bool interrupted =
false;
474 file.open(QFile::ReadOnly);
475 QTextStream in(&file);
484 QProgressDialog progress_dialog;
485 progress_dialog.setLabelText(
"Loading... please wait");
486 progress_dialog.setWindowModality(Qt::ApplicationModal);
487 progress_dialog.setRange(0, tot_lines - 1);
488 progress_dialog.setAutoClose(
true);
489 progress_dialog.setAutoReset(
true);
490 progress_dialog.show();
494 std::vector<PlotData*> plots_vector;
495 std::vector<StringSeries*> string_vector;
496 bool sortRequired =
false;
498 for (
unsigned i = 0; i < column_names.size(); i++)
500 const std::string& field_name = (column_names[i]);
501 auto num_it = plot_data.
addNumeric(field_name);
502 plots_vector.push_back(&(num_it->second));
505 string_vector.push_back(&(str_it->second));
509 double prev_time = std::numeric_limits<double>::lowest();
510 bool parse_date_format =
_ui->checkBoxDateFormat->isChecked();
513 auto ParseNumber = [&](QString str,
bool& is_number) {
514 QString str_trimmed = str.trimmed();
515 double val = str_trimmed.toDouble(&is_number);
519 static QLocale locale_with_comma(QLocale::German);
520 val = locale_with_comma.toDouble(str_trimmed, &is_number);
522 if (!is_number && parse_date_format && !format_string.isEmpty())
524 QDateTime ts = QDateTime::fromString(str_trimmed, format_string);
525 is_number = ts.isValid();
528 val = ts.toMSecsSinceEpoch() / 1000.0;
534 file.open(QFile::ReadOnly);
535 QTextStream in(&file);
537 QString header_str = in.readLine();
538 QStringList string_items;
539 QStringList header_string_items;
542 QString time_header_str;
548 QString line = in.readLine();
552 if(string_items.size() == 0)
557 if (string_items.size() != column_names.size())
560 msgBox.setWindowTitle(tr(
"Error reading file"));
561 msgBox.setText(tr(
"The number of values at line %1 is %2,\n" 562 "but the expected number of columns is %3.\n" 565 .
arg(string_items.size())
566 .
arg(column_names.size()));
568 msgBox.setDetailedText(tr(
"File: \"%1\" \n\n" 569 "Error reading file | Mismatched field count\n" 571 "Header fields: %6\n" 572 "Fields on line [%4]: %7\n\n" 578 QPushButton *abortButton = msgBox.addButton(QMessageBox::Ok);
580 msgBox.setIcon(QMessageBox::Warning);
587 double t = linecount;
591 bool is_number =
false;
592 t_str = string_items[time_index];
593 t = ParseNumber(t_str, is_number);
594 time_header_str = header_string_items[time_index];
599 msgBox.setWindowTitle(tr(
"Error reading file"));
600 msgBox.setText(tr(
"Couldn't parse timestamp on line %1 with string \"%2\" . Aborting.\n").
arg(linecount+1).
arg(t_str));
602 msgBox.setDetailedText(tr(
"File: \"%1\" \n\n" 603 "Error reading file | Couldn't parse timestamp\n" 604 "Parsing format: [%4]\n" 605 "Time at line %2 : [%3]\n").
arg(
_fileInfo->
filename).arg(linecount + 1).arg(t_str).arg((parse_date_format && !format_string.isEmpty())? format_string:
"None"));
607 QPushButton *abortButton = msgBox.addButton(QMessageBox::Ok);
609 msgBox.setIcon(QMessageBox::Warning);
616 if (prev_time > t && !sortRequired)
620 timeName = time_header_str;
622 msgBox.setWindowTitle(tr(
"Selected time is not monotonic"));
623 msgBox.setText(tr(
"PlotJuggler detected that the time in this file is non-monotonic. This may indicate an issue with the input data. Continue? (Input file will not be modified but data will be sorted by PlotJuggler)"));
624 msgBox.setDetailedText(tr(
"File: \"%1\" \n\n" 625 "Selected time is not monotonic\n" 626 "Time Index: %6 [%7]\n" 627 "Time at line %2 : %3\n" 628 "Time at line %4 : %5").
arg(
_fileInfo->
filename).arg(linecount + 1).arg(prev_t_str).arg(linecount + 2).arg(t_str).arg(time_index).arg(timeName));
630 QPushButton *sortButton = msgBox.addButton(tr(
"Continue"), QMessageBox::ActionRole);
631 QPushButton *abortButton = msgBox.addButton(QMessageBox::Abort);
632 msgBox.setIcon(QMessageBox::Warning);
635 if(msgBox.clickedButton() == abortButton)
639 else if (msgBox.clickedButton() == sortButton)
655 for (
unsigned i = 0; i < string_items.size(); i++)
657 bool is_number =
false;
658 const auto& str = string_items[i];
659 double y = ParseNumber(str, is_number);
662 plots_vector[i]->pushBack({ t, y });
666 string_vector[i]->pushBack({ t, str.toStdString() });
670 if (linecount++ % 100 == 0)
672 progress_dialog.setValue(linecount);
673 QApplication::processEvents();
674 if (progress_dialog.wasCanceled())
684 progress_dialog.cancel();
698 for (
unsigned i = 0; i < column_names.size(); i++)
700 const auto&
name = column_names[i];
701 bool is_numeric =
true;
702 if (plots_vector[i]->
size() == 0 && string_vector[i]->size() > 0)
720 QDomElement elem = doc.createElement(
"default");
722 elem.setAttribute(
"delimiter",
_ui->comboBox->currentIndex());
725 if (
_ui->checkBoxDateFormat->isChecked())
727 elem.setAttribute(
"date_format",
_ui->lineEditDateFormat->text());
730 parent_element.appendChild(elem);
736 QDomElement elem = parent_element.firstChildElement(
"default");
739 if (elem.hasAttribute(
"time_axis"))
743 if (elem.hasAttribute(
"delimiter"))
745 int separator_index = elem.attribute(
"delimiter").toInt();
746 _ui->comboBox->setCurrentIndex(separator_index);
747 switch (separator_index)
760 if (elem.hasAttribute(
"date_format"))
762 _ui->checkBoxDateFormat->setChecked(
true);
763 _ui->lineEditDateFormat->setText(elem.attribute(
"date_format"));
void SplitLine(const QString &line, QChar separator, QStringList &parts)
virtual bool xmlSaveState(QDomDocument &doc, QDomElement &parent_element) const override
Override this method to save the status of the plugin to XML.
void parseHeader(QFile &file, std::vector< std::string > &ordered_names)
QStandardItemModel * _model
virtual bool readDataFromFile(PJ::FileLoadInfo *fileload_info, PlotDataMapRef &destination) override
std::vector< const char * > _extensions
auto arg(const Char *name, const T &arg) -> detail::named_arg< Char, T >
QString filename
name of the file to open
DateTimeHelp * _dateTime_dialog
virtual const char * name() const override
Name of the plugin type, NOT the particular instance.
TimeseriesMap numeric
Numerical timeseries.
int launchDialog(QFile &file, std::vector< std::string > *ordered_names)
StringSeriesMap strings
Series of strings.
bool multiple_columns_warning_
TimeseriesMap::iterator addNumeric(const std::string &name, PlotGroup::Ptr group={})
Class, that describes Qt style parser for QCodeEditor.
const int TIME_INDEX_GENERATED
QCSVHighlighter _csvHighlighter
StringSeriesMap::iterator addStringSeries(const std::string &name, PlotGroup::Ptr group={})
virtual bool xmlLoadState(const QDomElement &parent_element) override
Override this method to load the status of the plugin from XML.
std::string _default_time_axis
span_constexpr std::size_t size(span< T, Extent > const &spn)
QDomDocument plugin_config
Saved configuration from a previous run or a Layout file.
virtual const std::vector< const char * > & compatibleFileExtensions() const override
Provide a list of file extensions that this plugin can open.
const int TIME_INDEX_NOT_DEFINED