From 4c49670cdba403090067d8b92940b577d16ba550 Mon Sep 17 00:00:00 2001 From: Miika Turkia Date: Wed, 16 Oct 2013 22:05:19 +0300 Subject: GUI for CSV import This patch implements GUI for importing CSV log files. One is able to configure what columns contain time, depth and temperature fields. Pre-configured log applications currently included are ADP log viewer and XP5. (Both of these use actually tab as separator, so the field separator currently hard-coded.) [Dirk Hohndel: minor fixes] Signed-off-by: Miika Turkia Signed-off-by: Dirk Hohndel --- dive.h | 3 +- file.c | 53 ++++++++- parse-xml.c | 31 +---- qt-ui/csvimportdialog.cpp | 102 ++++++++++++++++ qt-ui/csvimportdialog.h | 48 ++++++++ qt-ui/csvimportdialog.ui | 253 ++++++++++++++++++++++++++++++++++++++++ qt-ui/mainwindow.cpp | 15 +++ qt-ui/mainwindow.h | 2 + qt-ui/mainwindow.ui | 8 ++ qt-ui/subsurfacewebservices.cpp | 2 +- subsurface.pro | 9 +- 11 files changed, 494 insertions(+), 32 deletions(-) create mode 100644 qt-ui/csvimportdialog.cpp create mode 100644 qt-ui/csvimportdialog.h create mode 100644 qt-ui/csvimportdialog.ui diff --git a/dive.h b/dive.h index fd9431a30..b1a3614ef 100644 --- a/dive.h +++ b/dive.h @@ -609,13 +609,14 @@ extern int match_one_dc(struct divecomputer *a, struct divecomputer *b); extern double ascii_strtod(char *, char **); extern void parse_xml_init(void); -extern void parse_xml_buffer(const char *url, const char *buf, int size, struct dive_table *table, char **error); +extern void parse_xml_buffer(const char *url, const char *buf, int size, struct dive_table *table, const char **params, char **error); extern void parse_xml_exit(void); extern void set_filename(const char *filename, bool force); extern int parse_dm4_buffer(const char *url, const char *buf, int size, struct dive_table *table, char **error); extern void parse_file(const char *filename, char **error); +extern void parse_csv_file(const char *filename, int time, int depth, int temp, char **error); extern void save_dives(const char *filename); extern void save_dives_logic(const char *filename, bool select_only); diff --git a/file.c b/file.c index a4b90129a..acabd1b6b 100644 --- a/file.c +++ b/file.c @@ -72,7 +72,7 @@ static void zip_read(struct zip_file *file, char **error, const char *filename) mem = realloc(mem, size); } mem[read] = 0; - parse_xml_buffer(filename, mem, read, &dive_table, error); + parse_xml_buffer(filename, mem, read, &dive_table, NULL, error); free(mem); } @@ -286,7 +286,7 @@ static void parse_file_buffer(const char *filename, struct memblock *mem, char * if (fmt && open_by_filename(filename, fmt+1, mem, error)) return; - parse_xml_buffer(filename, mem->buffer, mem->size, &dive_table, error); + parse_xml_buffer(filename, mem->buffer, mem->size, &dive_table, NULL, error); } void parse_file(const char *filename, char **error) @@ -319,3 +319,52 @@ void parse_file(const char *filename, char **error) parse_file_buffer(filename, &mem, error); free(mem.buffer); } + +#define MAXCOLDIGITS 3 +#define MAXCOLS 100 +void parse_csv_file(const char *filename, int time, int depth, int temp, char **error) +{ + struct memblock mem; + char *params[7]; + char timebuf[MAXCOLDIGITS]; + char depthbuf[MAXCOLDIGITS]; + char tempbuf[MAXCOLDIGITS]; + + if (time >= MAXCOLS || depth >= MAXCOLS || temp >= MAXCOLS) { + int len = strlen(translate("gettextFromC", "Maximum number of supported columns on CSV import is %d")) + MAXCOLDIGITS; + *error = malloc(len); + snprintf(*error, len, translate("gettextFromC", "Maximum number of supported columns on CSV import is %d"), MAXCOLS); + + return; + } + snprintf(timebuf, MAXCOLDIGITS, "%d", time); + snprintf(depthbuf, MAXCOLDIGITS, "%d", depth); + snprintf(tempbuf, MAXCOLDIGITS, "%d", temp); + + params[0] = "timeField"; + params[1] = timebuf; + params[2] = "depthField"; + params[3] = depthbuf; + params[4] = "tempField"; + params[5] = tempbuf; + params[6] = NULL; + + if (filename == NULL) + return; + + if (readfile(filename, &mem) < 0) { + if (error) { + int len = strlen(translate("gettextFromC","Failed to read '%s'")) + strlen(filename); + *error = malloc(len); + snprintf(*error, len, translate("gettextFromC","Failed to read '%s'"), filename); + } + + return; + } + + if (try_to_xslt_open_csv(filename, &mem, error)) + return; + + parse_xml_buffer(filename, mem.buffer, mem.size, &dive_table, (const char **)params, error); + free(mem.buffer); +} diff --git a/parse-xml.c b/parse-xml.c index fbe805a42..129e2dc4b 100644 --- a/parse-xml.c +++ b/parse-xml.c @@ -21,7 +21,7 @@ int verbose; -static xmlDoc *test_xslt_transforms(xmlDoc *doc, char **error); +static xmlDoc *test_xslt_transforms(xmlDoc *doc, const char **params, char **error); char *xslt_path; /* the dive table holds the overall dive list; target table points at @@ -1681,7 +1681,7 @@ const char *preprocess_divelog_de(const char *buffer) } void parse_xml_buffer(const char *url, const char *buffer, int size, - struct dive_table *table, char **error) + struct dive_table *table, const char **params, char **error) { xmlDoc *doc; const char *res = preprocess_divelog_de(buffer); @@ -1698,7 +1698,7 @@ void parse_xml_buffer(const char *url, const char *buffer, int size, } reset_all(); dive_start(); - doc = test_xslt_transforms(doc, error); + doc = test_xslt_transforms(doc, params, error); traverse(xmlDocGetRootElement(doc)); dive_end(); xmlFreeDoc(doc); @@ -2019,14 +2019,13 @@ static struct xslt_files { { NULL, } }; -static xmlDoc *test_xslt_transforms(xmlDoc *doc, char **error) +static xmlDoc *test_xslt_transforms(xmlDoc *doc, const char **params, char **error) { struct xslt_files *info = xslt_files; xmlDoc *transformed; xsltStylesheetPtr xslt = NULL; xmlNode *root_element = xmlDocGetRootElement(doc); char *attribute; - char *params[3]; while ((info->root) && (strcasecmp(root_element->name, info->root) != 0)) { info++; @@ -2048,28 +2047,10 @@ static xmlDoc *test_xslt_transforms(xmlDoc *doc, char **error) parser_error(error, translate("gettextFromC","Can't open stylesheet (%s)/%s"), xslt_path, info->file); return doc; } - - /* - * params is only used for CSV import, but it does not - * hurt if we supply unused parameters for other - * transforms as well. - * - * We should have a GUI set the parameters but currently - * we just have PoC how parameters would be handled. - * - * (Field 9 is temperature for XP5 import, field 15 - * is temperature for AP Logviewer. - */ - - params[0] = strdup("tempField"); - params[1] = strdup("15"); - params[2] = NULL; - - transformed = xsltApplyStylesheet(xslt, doc, (const char **)params); + transformed = xsltApplyStylesheet(xslt, doc, params); xmlFreeDoc(doc); xsltFreeStylesheet(xslt); - free(params[0]); - free(params[1]); + return transformed; } return doc; diff --git a/qt-ui/csvimportdialog.cpp b/qt-ui/csvimportdialog.cpp new file mode 100644 index 000000000..5ae4036f5 --- /dev/null +++ b/qt-ui/csvimportdialog.cpp @@ -0,0 +1,102 @@ +#include +#include +#include "csvimportdialog.h" +#include "mainwindow.h" +#include "ui_csvimportdialog.h" + +const CSVImportDialog::CSVAppConfig CSVImportDialog::CSVApps[CSVAPPS] = { + {"", }, + {"APD Log Viewer", 0, 1, 15, "Tab"}, + {"XP5", 0, 1, 9, "Tab"}, + {NULL,} +}; + +CSVImportDialog::CSVImportDialog(QWidget *parent) : + QDialog(parent), + selector(true), + ui(new Ui::CSVImportDialog) +{ + ui->setupUi(this); + + for (int i = 0; !CSVApps[i].name.isNull(); ++i) + ui->knownImports->addItem(CSVApps[i].name); + + ui->CSVSeparator->addItem("Tab"); + ui->knownImports->setCurrentIndex(1); + ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false); +} + +CSVImportDialog::~CSVImportDialog() +{ + delete ui; +} + +void CSVImportDialog::on_buttonBox_accepted() +{ + char *error = NULL; + + parse_csv_file(ui->CSVFile->text().toUtf8().data(), ui->CSVTime->value(), ui->CSVDepth->value(), ui->CSVTemperature->value(), &error); + if (error != NULL) { + + mainWindow()->showError(error); + free(error); + error = NULL; + } + process_dives(TRUE, FALSE); + + mainWindow()->refreshDisplay(); +} + +void CSVImportDialog::on_CSVFileSelector_clicked() +{ + QString filename = QFileDialog::getOpenFileName(this, tr("Open CSV Log File"), ".", tr("CSV Files (*.csv)")); + ui->CSVFile->setText(filename); + if (filename.isEmpty()) + ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false); + else + ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(true); +} + +void CSVImportDialog::on_knownImports_currentIndexChanged(int index) +{ + if (index == 0) + return; + + ui->CSVTime->blockSignals(true); + ui->CSVDepth->blockSignals(true); + ui->CSVTemperature->blockSignals(true); + ui->CSVTime->setValue(CSVApps[index].time); + ui->CSVDepth->setValue(CSVApps[index].depth); + ui->CSVTemperature->setValue(CSVApps[index].temperature); + ui->CSVTime->blockSignals(false); + ui->CSVDepth->blockSignals(false); + ui->CSVTemperature->blockSignals(false); +} + +void CSVImportDialog::on_CSVTime_valueChanged(int arg1) +{ + unknownImports(); +} + +void CSVImportDialog::on_CSVDepth_valueChanged(int arg1) +{ + unknownImports(); +} + +void CSVImportDialog::on_CSVTemperature_valueChanged(int arg1) +{ + unknownImports(); +} + +void CSVImportDialog::unknownImports() +{ + ui->knownImports->setCurrentIndex(0); +} + +void CSVImportDialog::on_CSVFile_textEdited() +{ + if (ui->CSVFile->text().isEmpty()) + ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false); + else + ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(true); +} diff --git a/qt-ui/csvimportdialog.h b/qt-ui/csvimportdialog.h new file mode 100644 index 000000000..057533018 --- /dev/null +++ b/qt-ui/csvimportdialog.h @@ -0,0 +1,48 @@ +#ifndef CSVIMPORTDIALOG_H +#define CSVIMPORTDIALOG_H + +#include +#include +#include "../dive.h" +#include "../divelist.h" + +namespace Ui { +class CSVImportDialog; +} + +class CSVImportDialog : public QDialog +{ + Q_OBJECT + +public: + explicit CSVImportDialog(QWidget *parent = 0); + ~CSVImportDialog(); + +private slots: + void on_buttonBox_accepted(); + void on_CSVFileSelector_clicked(); + void on_knownImports_currentIndexChanged(int index); + void on_CSVTime_valueChanged(int arg1); + void on_CSVDepth_valueChanged(int arg1); + void on_CSVTemperature_valueChanged(int arg1); + void on_CSVFile_textEdited(); + +private: + void unknownImports(); + + bool selector; + Ui::CSVImportDialog *ui; + + struct CSVAppConfig { + QString name; + int time; + int depth; + int temperature; + QString separator; + }; + +#define CSVAPPS 4 + static const CSVAppConfig CSVApps[CSVAPPS]; +}; + +#endif // CSVIMPORTDIALOG_H diff --git a/qt-ui/csvimportdialog.ui b/qt-ui/csvimportdialog.ui new file mode 100644 index 000000000..ec5002baf --- /dev/null +++ b/qt-ui/csvimportdialog.ui @@ -0,0 +1,253 @@ + + + CSVImportDialog + + + + 0 + 0 + 400 + 300 + + + + Dialog + + + + + 30 + 240 + 341 + 32 + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + 40 + 10 + 331 + 71 + + + + Import File (CSV) + + + + + 0 + 30 + 291 + 29 + + + + + + + 300 + 30 + 25 + 27 + + + + ... + + + + + + + 200 + 80 + 121 + 61 + + + + Field Separator + + + + + 0 + 30 + 111 + 29 + + + + + + + + 40 + 80 + 151 + 151 + + + + Field Configuration + + + + + 60 + 30 + 56 + 29 + + + + 0 + + + 0 + + + + + + 60 + 70 + 56 + 29 + + + + 0 + + + 1 + + + + + + 60 + 110 + 56 + 29 + + + + 0 + + + 15 + + + + + + 0 + 30 + 41 + 19 + + + + Time + + + + + + 0 + 70 + 51 + 19 + + + + Depth + + + + + + 0 + 110 + 41 + 19 + + + + Temp + + + + + + + 200 + 159 + 181 + 61 + + + + Pre-configured imports + + + + + 0 + 30 + 161 + 29 + + + + -1 + + + + + + + + buttonBox + accepted() + CSVImportDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + CSVImportDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/qt-ui/mainwindow.cpp b/qt-ui/mainwindow.cpp index 247cfdf2f..3b56134c1 100644 --- a/qt-ui/mainwindow.cpp +++ b/qt-ui/mainwindow.cpp @@ -34,6 +34,7 @@ #include "diveplanner.h" #include "about.h" #include "printdialog.h" +#include "csvimportdialog.h" static MainWindow* instance = 0; @@ -801,3 +802,17 @@ void MainWindow::loadFiles(const QStringList fileNames) WSInfoModel *wsim = WSInfoModel::instance(); wsim->updateInfo(); } + +void MainWindow::on_actionImportCSV_triggered() +{ + CSVImportDialog *csvImport = new(CSVImportDialog); + csvImport->show(); + process_dives(TRUE, FALSE); + + ui.InfoWidget->reload(); + ui.globe->reload(); + ui.ListWidget->reload(DiveTripModel::TREE); + ui.ListWidget->setFocus(); + WSInfoModel *wsim = WSInfoModel::instance(); + wsim->updateInfo(); +} diff --git a/qt-ui/mainwindow.h b/qt-ui/mainwindow.h index 0319c7b28..5dfae3e98 100644 --- a/qt-ui/mainwindow.h +++ b/qt-ui/mainwindow.h @@ -99,6 +99,8 @@ private slots: void current_dive_changed(int divenr); void initialUiSetup(); + void on_actionImportCSV_triggered(); + protected: void closeEvent(QCloseEvent *); diff --git a/qt-ui/mainwindow.ui b/qt-ui/mainwindow.ui index 63dded7f8..8a108118e 100644 --- a/qt-ui/mainwindow.ui +++ b/qt-ui/mainwindow.ui @@ -183,6 +183,7 @@ + @@ -441,6 +442,13 @@ Dive Planner + + + Import CSV + + + + diff --git a/qt-ui/subsurfacewebservices.cpp b/qt-ui/subsurfacewebservices.cpp index 2a560efdd..f2b1b88cc 100644 --- a/qt-ui/subsurfacewebservices.cpp +++ b/qt-ui/subsurfacewebservices.cpp @@ -47,7 +47,7 @@ void SubsurfaceWebServices::buttonClicked(QAbstractButton* button) case QDialogButtonBox::ApplyRole:{ clear_table(&gps_location_table); QByteArray url = tr("Webservice").toLocal8Bit(); - parse_xml_buffer(url.data(), downloadedData.data(), downloadedData.length(), &gps_location_table, NULL); + parse_xml_buffer(url.data(), downloadedData.data(), downloadedData.length(), &gps_location_table, NULL, NULL); /* now merge the data in the gps_location table into the dive_table */ if (merge_locations_into_dives()) { diff --git a/subsurface.pro b/subsurface.pro index a2fd97320..e9927686f 100644 --- a/subsurface.pro +++ b/subsurface.pro @@ -55,7 +55,8 @@ HEADERS = \ subsurface-icon.h \ subsurfacestartup.h \ uemis.h \ - webservice.h + webservice.h \ + qt-ui/csvimportdialog.h SOURCES = \ deco.c \ @@ -101,7 +102,8 @@ SOURCES = \ subsurfacestartup.c \ time.c \ uemis.c \ - uemis-downloader.c + uemis-downloader.c \ + qt-ui/csvimportdialog.cpp linux*: SOURCES += linux.c mac: SOURCES += macos.c @@ -118,7 +120,8 @@ FORMS = \ qt-ui/printoptions.ui \ qt-ui/renumber.ui \ qt-ui/subsurfacewebservices.ui \ - qt-ui/tableview.ui + qt-ui/tableview.ui \ + qt-ui/csvimportdialog.ui RESOURCES = subsurface.qrc -- cgit v1.2.3-70-g09d2