diff options
author | Miika Turkia <miika.turkia@gmail.com> | 2013-10-16 22:05:19 +0300 |
---|---|---|
committer | Dirk Hohndel <dirk@hohndel.org> | 2013-10-16 12:44:07 -0700 |
commit | 4c49670cdba403090067d8b92940b577d16ba550 (patch) | |
tree | c6d331e238d934e95e54a05e4967a7eec564784c | |
parent | 80bced4f560047874515536979e3cba1e519e147 (diff) | |
download | subsurface-4c49670cdba403090067d8b92940b577d16ba550.tar.gz |
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 <miika.turkia@gmail.com>
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
-rw-r--r-- | dive.h | 3 | ||||
-rw-r--r-- | file.c | 53 | ||||
-rw-r--r-- | parse-xml.c | 31 | ||||
-rw-r--r-- | qt-ui/csvimportdialog.cpp | 102 | ||||
-rw-r--r-- | qt-ui/csvimportdialog.h | 48 | ||||
-rw-r--r-- | qt-ui/csvimportdialog.ui | 253 | ||||
-rw-r--r-- | qt-ui/mainwindow.cpp | 15 | ||||
-rw-r--r-- | qt-ui/mainwindow.h | 2 | ||||
-rw-r--r-- | qt-ui/mainwindow.ui | 8 | ||||
-rw-r--r-- | qt-ui/subsurfacewebservices.cpp | 2 | ||||
-rw-r--r-- | subsurface.pro | 9 |
11 files changed, 494 insertions, 32 deletions
@@ -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); @@ -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 <QtDebug> +#include <QFileDialog> +#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 <QDialog> +#include <QModelIndex> +#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 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>CSVImportDialog</class> + <widget class="QDialog" name="CSVImportDialog"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>400</width> + <height>300</height> + </rect> + </property> + <property name="windowTitle"> + <string>Dialog</string> + </property> + <widget class="QDialogButtonBox" name="buttonBox"> + <property name="geometry"> + <rect> + <x>30</x> + <y>240</y> + <width>341</width> + <height>32</height> + </rect> + </property> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="standardButtons"> + <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> + </property> + </widget> + <widget class="QGroupBox" name="groupBox"> + <property name="geometry"> + <rect> + <x>40</x> + <y>10</y> + <width>331</width> + <height>71</height> + </rect> + </property> + <property name="title"> + <string>Import File (CSV)</string> + </property> + <widget class="QLineEdit" name="CSVFile"> + <property name="geometry"> + <rect> + <x>0</x> + <y>30</y> + <width>291</width> + <height>29</height> + </rect> + </property> + </widget> + <widget class="QToolButton" name="CSVFileSelector"> + <property name="geometry"> + <rect> + <x>300</x> + <y>30</y> + <width>25</width> + <height>27</height> + </rect> + </property> + <property name="text"> + <string>...</string> + </property> + </widget> + </widget> + <widget class="QGroupBox" name="groupBox_2"> + <property name="geometry"> + <rect> + <x>200</x> + <y>80</y> + <width>121</width> + <height>61</height> + </rect> + </property> + <property name="title"> + <string>Field Separator</string> + </property> + <widget class="QComboBox" name="CSVSeparator"> + <property name="geometry"> + <rect> + <x>0</x> + <y>30</y> + <width>111</width> + <height>29</height> + </rect> + </property> + </widget> + </widget> + <widget class="QGroupBox" name="groupBox_3"> + <property name="geometry"> + <rect> + <x>40</x> + <y>80</y> + <width>151</width> + <height>151</height> + </rect> + </property> + <property name="title"> + <string>Field Configuration</string> + </property> + <widget class="QSpinBox" name="CSVTime"> + <property name="geometry"> + <rect> + <x>60</x> + <y>30</y> + <width>56</width> + <height>29</height> + </rect> + </property> + <property name="minimum"> + <number>0</number> + </property> + <property name="value"> + <number>0</number> + </property> + </widget> + <widget class="QSpinBox" name="CSVDepth"> + <property name="geometry"> + <rect> + <x>60</x> + <y>70</y> + <width>56</width> + <height>29</height> + </rect> + </property> + <property name="minimum"> + <number>0</number> + </property> + <property name="value"> + <number>1</number> + </property> + </widget> + <widget class="QSpinBox" name="CSVTemperature"> + <property name="geometry"> + <rect> + <x>60</x> + <y>110</y> + <width>56</width> + <height>29</height> + </rect> + </property> + <property name="minimum"> + <number>0</number> + </property> + <property name="value"> + <number>15</number> + </property> + </widget> + <widget class="QLabel" name="label"> + <property name="geometry"> + <rect> + <x>0</x> + <y>30</y> + <width>41</width> + <height>19</height> + </rect> + </property> + <property name="text"> + <string>Time</string> + </property> + </widget> + <widget class="QLabel" name="label_2"> + <property name="geometry"> + <rect> + <x>0</x> + <y>70</y> + <width>51</width> + <height>19</height> + </rect> + </property> + <property name="text"> + <string>Depth</string> + </property> + </widget> + <widget class="QLabel" name="label_3"> + <property name="geometry"> + <rect> + <x>0</x> + <y>110</y> + <width>41</width> + <height>19</height> + </rect> + </property> + <property name="text"> + <string>Temp</string> + </property> + </widget> + </widget> + <widget class="QGroupBox" name="groupBox_4"> + <property name="geometry"> + <rect> + <x>200</x> + <y>159</y> + <width>181</width> + <height>61</height> + </rect> + </property> + <property name="title"> + <string>Pre-configured imports</string> + </property> + <widget class="QComboBox" name="knownImports"> + <property name="geometry"> + <rect> + <x>0</x> + <y>30</y> + <width>161</width> + <height>29</height> + </rect> + </property> + <property name="currentIndex"> + <number>-1</number> + </property> + </widget> + </widget> + </widget> + <resources/> + <connections> + <connection> + <sender>buttonBox</sender> + <signal>accepted()</signal> + <receiver>CSVImportDialog</receiver> + <slot>accept()</slot> + <hints> + <hint type="sourcelabel"> + <x>248</x> + <y>254</y> + </hint> + <hint type="destinationlabel"> + <x>157</x> + <y>274</y> + </hint> + </hints> + </connection> + <connection> + <sender>buttonBox</sender> + <signal>rejected()</signal> + <receiver>CSVImportDialog</receiver> + <slot>reject()</slot> + <hints> + <hint type="sourcelabel"> + <x>316</x> + <y>260</y> + </hint> + <hint type="destinationlabel"> + <x>286</x> + <y>274</y> + </hint> + </hints> + </connection> + </connections> +</ui> 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 @@ <addaction name="actionClose"/> <addaction name="separator"/> <addaction name="actionImport"/> + <addaction name="actionImportCSV"/> <addaction name="actionExportUDDF"/> <addaction name="separator"/> <addaction name="actionPrint"/> @@ -441,6 +442,13 @@ <string>Dive Planner</string> </property> </action> + <action name="actionImportCSV"> + <property name="text"> + <string>Import CSV</string> + </property> + <property name="toolTip"> + </property> + </action> </widget> <customwidgets> <customwidget> 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 |