diff options
-rw-r--r-- | dive.c | 1 | ||||
-rw-r--r-- | dive.h | 19 | ||||
-rw-r--r-- | divelist.c | 11 | ||||
-rw-r--r-- | parse-xml.c | 30 | ||||
-rw-r--r-- | profile.c | 93 | ||||
-rw-r--r-- | profile.h | 1 | ||||
-rw-r--r-- | qt-ui/csvimportdialog.h | 13 | ||||
-rw-r--r-- | qt-ui/csvimportdialog.ui | 372 | ||||
-rw-r--r-- | qt-ui/divelistview.cpp | 3 | ||||
-rw-r--r-- | qt-ui/divelogimportdialog.cpp (renamed from qt-ui/csvimportdialog.cpp) | 60 | ||||
-rw-r--r-- | qt-ui/divelogimportdialog.h | 49 | ||||
-rw-r--r-- | qt-ui/divelogimportdialog.ui | 392 | ||||
-rw-r--r-- | qt-ui/maintab.cpp | 15 | ||||
-rw-r--r-- | qt-ui/mainwindow.cpp | 53 | ||||
-rw-r--r-- | qt-ui/mainwindow.h | 7 | ||||
-rw-r--r-- | qt-ui/mainwindow.ui | 23 | ||||
-rw-r--r-- | qt-ui/models.cpp | 40 | ||||
-rw-r--r-- | qt-ui/models.h | 4 | ||||
-rw-r--r-- | qt-ui/printlayout.cpp | 2 | ||||
-rw-r--r-- | qt-ui/profilegraphics.cpp | 25 | ||||
-rw-r--r-- | qt-ui/profilegraphics.h | 2 | ||||
-rw-r--r-- | qt-ui/usermanual.cpp | 93 | ||||
-rw-r--r-- | qt-ui/usermanual.h | 32 | ||||
-rw-r--r-- | qt-ui/usermanual.ui | 146 | ||||
-rw-r--r-- | qthelper.cpp | 55 | ||||
-rw-r--r-- | subsurface.pro | 13 |
26 files changed, 1035 insertions, 519 deletions
@@ -892,6 +892,7 @@ struct dive *fixup_dive(struct dive *dive) weightsystem_t *ws = dive->weightsystem + i; add_weightsystem_description(ws); } + dive->id = getUniqID(dive); return dive; } @@ -412,9 +412,10 @@ struct dive { pressure_t surface_pressure; duration_t duration; int salinity; // kg per 10000 l - struct tag_entry *tag_list; + struct tag_entry *tag_list; struct divecomputer dc; + int id; // unique ID for this dive }; static inline int dive_has_gps_location(struct dive *dive) @@ -611,6 +612,21 @@ static inline struct dive *get_dive_by_diveid(uint32_t diveid, uint32_t deviceid } return NULL; } +// this is very different from get_dive_by_diveid() (which is only used +// by the UEMIS downloader) -- this uses the unique diveID to allow us +// to hold an identifier for a dive across operations that might change +// the dive_table +static inline struct dive *getDiveById(int id) +{ + int i; + struct dive *dive = NULL; + + for_each_dive(i, dive) { + if (dive->id == id) + break; + } + return dive; +} extern struct dive *find_dive_including(timestamp_t when); extern bool dive_within_time_range(struct dive *dive, timestamp_t when, timestamp_t offset); struct dive *find_dive_n_near(timestamp_t when, int n, timestamp_t offset); @@ -654,6 +670,7 @@ extern void finish_sample(struct divecomputer *dc); extern void sort_table(struct dive_table *table); extern struct dive *fixup_dive(struct dive *dive); +extern int getUniqID(struct dive *d); extern unsigned int dc_airtemp(struct divecomputer *dc); extern unsigned int dc_watertemp(struct divecomputer *dc); extern struct dive *merge_dives(struct dive *a, struct dive *b, int offset, bool prefer_downloaded); diff --git a/divelist.c b/divelist.c index 90c5112bc..dfb7b6577 100644 --- a/divelist.c +++ b/divelist.c @@ -792,6 +792,7 @@ struct dive *merge_two_dives(struct dive *a, struct dive *b) { struct dive *res; int i,j; + int id = a->id; if (!a || !b) return NULL; @@ -804,6 +805,10 @@ struct dive *merge_two_dives(struct dive *a, struct dive *b) add_single_dive(i, res); delete_single_dive(i+1); delete_single_dive(j); + // now make sure that we keep the id of the first dive. + // why? + // because this way one of the previously selected ids is still around + res->id = id; mark_divelist_changed(TRUE); return res; } @@ -956,6 +961,7 @@ void process_dives(bool is_imported, bool prefer_imported) struct dive *prev = pp[0]; struct dive *dive = pp[1]; struct dive *merged; + int id; /* only try to merge overlapping dives - or if one of the dives has * zero duration (that might be a gps marker from the webservice) */ @@ -967,6 +973,9 @@ void process_dives(bool is_imported, bool prefer_imported) if (!merged) continue; + // remember the earlier dive's id + id = prev->id; + /* careful - we might free the dive that last points to. Oops... */ if (last == prev || last == dive) last = merged; @@ -976,6 +985,8 @@ void process_dives(bool is_imported, bool prefer_imported) add_single_dive(i, merged); delete_single_dive(i+1); delete_single_dive(i+1); + // keep the id or the first dive for the merged dive + merged->id = id; } /* make sure no dives are still marked as downloaded */ for (i = 1; i < dive_table.nr; i++) diff --git a/parse-xml.c b/parse-xml.c index 8fc504a3f..04b6d4d7d 100644 --- a/parse-xml.c +++ b/parse-xml.c @@ -1681,6 +1681,10 @@ extern int dm4_events(void *handle, int columns, char **data, char **column) /* 13 Air time */ cur_event.name = strdup("airtime"); break; + case 17: + /* 17 Ascent warning */ + cur_event.name = strdup("ascent"); + break; case 18: /* 18 Ceiling error */ cur_event.name = strdup("ceiling"); @@ -1689,6 +1693,14 @@ extern int dm4_events(void *handle, int columns, char **data, char **column) /* 19 Surfaced */ cur_event.name = strdup("surface"); break; + case 20: + /* 20 Deco */ + cur_event.name = strdup("deco"); + break; + case 22: + /* 22 Mandatory safety stop violation */ + cur_event.name = strdup("violation"); + break; case 257: /* 257 Dive active */ /* This seems to be given after surface @@ -1714,6 +1726,14 @@ extern int dm4_events(void *handle, int columns, char **data, char **column) return 0; } +extern int dm4_tags(void *handle, int columns, char **data, char **column) +{ + if(data[0]) + taglist_add_tag(cur_dive->tag_list, data[0]); + + return 0; +} + extern int dm4_dive(void *param, int columns, char **data, char **column) { int i, interval, retval = 0; @@ -1723,6 +1743,7 @@ extern int dm4_dive(void *param, int columns, char **data, char **column) int *pressureBlob; char *err = NULL; char get_events_template[] = "select * from Mark where DiveId = %d"; + char get_tags_template[] = "select Text from DiveTag where DiveId = %d"; char get_events[64]; dive_start(); @@ -1802,7 +1823,7 @@ extern int dm4_dive(void *param, int columns, char **data, char **column) else cur_sample->depth.mm = cur_dive->dc.maxdepth.mm; - if (tempBlob) + if (data[18] && data[18][0]) cur_sample->temperature.mkelvin = C_to_mkelvin(tempBlob[i]); if (data[19] && data[19][0]) cur_sample->cylinderpressure.mbar = pressureBlob[i] ; @@ -1816,6 +1837,13 @@ extern int dm4_dive(void *param, int columns, char **data, char **column) return 1; } + snprintf(get_events, sizeof(get_events) - 1, get_tags_template, cur_dive->number); + retval = sqlite3_exec(handle, get_events, &dm4_tags, 0, &err); + if (retval != SQLITE_OK) { + fprintf(stderr, "%s", translate("gettextFromC","Database query get_tags failed.\n")); + return 1; + } + dive_end(); /* @@ -417,6 +417,22 @@ static void dump_pr_track(pr_track_t **track_pr) } #endif +typedef struct pr_interpolate_struct pr_interpolate_t; +struct pr_interpolate_struct { + int start; + int end; + int pressure_time; + int acc_pressure_time; +}; + +#ifdef DEBUG_PR_INTERPOLATE +static void dump_pr_interpolate(int i, pr_interpolate_t interpolate_pr) +{ + printf("Interpolate for entry %d: start %d - end %d - pt %d - acc_pt %d\n", i, + interpolate_pr.start, interpolate_pr.end, interpolate_pr.pressure_time, interpolate_pr.acc_pressure_time); +} +#endif + /* * This looks at the pressures for one cylinder, and * calculates any missing beginning/end pressures for @@ -506,6 +522,57 @@ static inline int pressure_time(struct dive *dive, struct divecomputer *dc, stru return depth_to_mbar(depth, dive) * time; } +static struct pr_interpolate_struct get_pr_interpolate_data(pr_track_t *segment, struct plot_info *pi, int cur) +{ + struct pr_interpolate_struct interpolate; + int i; + struct plot_data *entry; + + interpolate.start = segment->start; + interpolate.end = segment->end; + interpolate.acc_pressure_time = 0; + interpolate.pressure_time = 0; + + for (i = 0; i < pi->nr; i++) { + entry = pi->entry + i; + if (entry->sec < segment->t_start) + continue; + if (entry->sec >= segment->t_end) { + interpolate.pressure_time += entry->pressure_time; + break; + } + if (entry->sec == segment->t_start) { + interpolate.acc_pressure_time = 0; + interpolate.pressure_time = 0; + if (SENSOR_PRESSURE(entry)) + interpolate.start = SENSOR_PRESSURE(entry); + continue; + } + if (i < cur) { + if (SENSOR_PRESSURE(entry)) { + interpolate.start = SENSOR_PRESSURE(entry); + interpolate.acc_pressure_time = 0; + interpolate.pressure_time = 0; + } else { + interpolate.acc_pressure_time += entry->pressure_time; + interpolate.pressure_time += entry->pressure_time; + } + continue; + } + if (i == cur) { + interpolate.acc_pressure_time += entry->pressure_time; + interpolate.pressure_time += entry->pressure_time; + continue; + } + interpolate.pressure_time += entry->pressure_time; + if (SENSOR_PRESSURE(entry)) { + interpolate.end = SENSOR_PRESSURE(entry); + break; + } + } + return interpolate; +} + static void fill_missing_tank_pressures(struct dive *dive, struct plot_info *pi, pr_track_t **track_pr) { int cyl, i; @@ -526,9 +593,9 @@ static void fill_missing_tank_pressures(struct dive *dive, struct plot_info *pi, /* The first two are "fillers", but in case we don't have a sample * at time 0 we need to process the second of them here */ for (i = 1; i < pi->nr; i++) { - double magic, cur_pt; + double magic; pr_track_t *segment; - int pressure; + pr_interpolate_t interpolate; entry = pi->entry + i; cyl = entry->cylinderindex; @@ -549,14 +616,19 @@ static void fill_missing_tank_pressures(struct dive *dive, struct plot_info *pi, continue; } - /* Overall pressure change over total pressure-time for this segment*/ - magic = (segment->end - segment->start) / (double) segment->pressure_time; + interpolate = get_pr_interpolate_data(segment, pi, i); +#ifdef DEBUG_PR_INTERPOLATE + dump_pr_interpolate(i, interpolate); +#endif + /* if this segment has pressure time, calculate a new interpolated pressure */ + if (interpolate.pressure_time) { + /* Overall pressure change over total pressure-time for this segment*/ + magic = (interpolate.end - interpolate.start) / (double) interpolate.pressure_time; - /* Use that overall pressure change to update the current pressure */ - cur_pt = pressure_time(dive, &dive->dc, entry-1, entry); - pressure = cur_pr[cyl] + cur_pt * magic + 0.5; - INTERPOLATED_PRESSURE(entry) = pressure; - cur_pr[cyl] = pressure; + /* Use that overall pressure change to update the current pressure */ + cur_pr[cyl] = interpolate.start + magic * interpolate.acc_pressure_time + 0.5; + } + INTERPOLATED_PRESSURE(entry) = cur_pr[cyl]; } } @@ -906,7 +978,8 @@ static void populate_pressure_information(struct dive *dive, struct divecomputer /* discrete integration of pressure over time to get the SAC rate equivalent */ if (current) { - current->pressure_time += pressure_time(dive, dc, entry-1, entry); + entry->pressure_time = pressure_time(dive, dc, entry-1, entry); + current->pressure_time += entry->pressure_time; current->t_end = entry->sec; } @@ -41,6 +41,7 @@ struct plot_data { int tts_calc; int stoptime_calc; int stopdepth_calc; + int pressure_time; }; void calculate_max_limits(struct dive *dive, struct divecomputer *dc, struct graphics_context *gc); diff --git a/qt-ui/csvimportdialog.h b/qt-ui/csvimportdialog.h index fda36bcb7..f062d49fd 100644 --- a/qt-ui/csvimportdialog.h +++ b/qt-ui/csvimportdialog.h @@ -7,16 +7,16 @@ #include "../divelist.h" namespace Ui { -class CSVImportDialog; +class DiveLogImportDialog; } -class CSVImportDialog : public QDialog +class DiveLogImportDialog : public QDialog { Q_OBJECT public: - explicit CSVImportDialog(QWidget *parent = 0); - ~CSVImportDialog(); + explicit DiveLogImportDialog(QWidget *parent = 0); + ~DiveLogImportDialog(); private slots: void on_buttonBox_accepted(); @@ -26,11 +26,14 @@ private slots: void unknownImports(int); void unknownImports(bool); + void on_DiveLogFileSelector_clicked(); + void on_DiveLogFile_editingFinished(); + private: void unknownImports(); bool selector; - Ui::CSVImportDialog *ui; + Ui::DiveLogImportDialog *ui; struct CSVAppConfig { QString name; diff --git a/qt-ui/csvimportdialog.ui b/qt-ui/csvimportdialog.ui deleted file mode 100644 index 9c89704f3..000000000 --- a/qt-ui/csvimportdialog.ui +++ /dev/null @@ -1,372 +0,0 @@ -<?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>432</width> - <height>330</height> - </rect> - </property> - <property name="windowTitle"> - <string>Import CSV file</string> - </property> - <property name="windowIcon"> - <iconset> - <normalon>:/subsurface-icon</normalon> - </iconset> - </property> - <layout class="QVBoxLayout" name="verticalLayout"> - <item> - <widget class="QGroupBox" name="groupBox"> - <property name="sizePolicy"> - <sizepolicy hsizetype="Expanding" vsizetype="Preferred"> - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> - </property> - <property name="title"> - <string>Import File (CSV)</string> - </property> - <layout class="QHBoxLayout" name="horizontalLayout"> - <item> - <widget class="QLineEdit" name="CSVFile"/> - </item> - <item> - <widget class="QToolButton" name="CSVFileSelector"> - <property name="text"> - <string>...</string> - </property> - </widget> - </item> - </layout> - </widget> - </item> - <item> - <widget class="QWidget" name="horizontalWidget" native="true"> - <layout class="QHBoxLayout" name="horizontalLayout_3"> - <property name="spacing"> - <number>0</number> - </property> - <property name="margin"> - <number>0</number> - </property> - <item> - <widget class="QGroupBox" name="groupBox_3"> - <property name="sizePolicy"> - <sizepolicy hsizetype="Preferred" vsizetype="Expanding"> - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> - </property> - <property name="title"> - <string>Field Configuration</string> - </property> - <layout class="QGridLayout" name="gridLayout"> - <item row="0" column="1"> - <widget class="QSpinBox" name="CSVTime"> - <property name="minimum"> - <number>1</number> - </property> - <property name="value"> - <number>1</number> - </property> - </widget> - </item> - <item row="0" column="0"> - <widget class="QLabel" name="label"> - <property name="text"> - <string>Time</string> - </property> - </widget> - </item> - <item row="1" column="1"> - <widget class="QSpinBox" name="CSVDepth"> - <property name="minimum"> - <number>1</number> - </property> - <property name="value"> - <number>2</number> - </property> - </widget> - </item> - <item row="1" column="0"> - <widget class="QLabel" name="label_2"> - <property name="text"> - <string>Depth</string> - </property> - </widget> - </item> - <item row="2" column="1"> - <widget class="QSpinBox" name="CSVTemperature"> - <property name="minimum"> - <number>1</number> - </property> - <property name="enabled"> - <bool>false</bool> - </property> - </widget> - </item> - <item row="2" column="0"> - <widget class="QCheckBox" name="temperatureCheckBox"> - <property name="text"> - <string>Temp</string> - </property> - </widget> - </item> - <item row="3" column="1"> - <widget class="QSpinBox" name="CSVpo2"> - <property name="minimum"> - <number>1</number> - </property> - <property name="enabled"> - <bool>false</bool> - </property> - <property name="value"> - <number>0</number> - </property> - </widget> - </item> - <item row="3" column="0"> - <widget class="QCheckBox" name="po2CheckBox"> - <property name="text"> - <string>PO2</string> - </property> - </widget> - </item> - <item row="4" column="1"> - <widget class="QSpinBox" name="CSVcns"> - <property name="minimum"> - <number>1</number> - </property> - <property name="enabled"> - <bool>false</bool> - </property> - <property name="value"> - <number>0</number> - </property> - </widget> - </item> - <item row="4" column="0"> - <widget class="QCheckBox" name="cnsCheckBox"> - <property name="text"> - <string>Cns</string> - </property> - </widget> - </item> - <item row="5" column="1"> - <widget class="QSpinBox" name="CSVstopdepth"> - <property name="minimum"> - <number>1</number> - </property> - <property name="enabled"> - <bool>false</bool> - </property> - <property name="value"> - <number>0</number> - </property> - </widget> - </item> - <item row="5" column="0"> - <widget class="QCheckBox" name="stopdepthCheckBox"> - <property name="text"> - <string>Stopdepth</string> - </property> - </widget> - </item> - </layout> - <zorder>label</zorder> - <zorder>label_2</zorder> - <zorder>CSVTime</zorder> - <zorder>CSVDepth</zorder> - <zorder>temperatureCheckBox</zorder> - <zorder>CSVTemperature</zorder> - <zorder>po2CheckBox</zorder> - <zorder>CSVpo2</zorder> - <zorder>cnsCheckBox</zorder> - <zorder>CSVcns</zorder> - <zorder>stopdepthCheckBox</zorder> - <zorder>CSVstopdepth</zorder> - </widget> - </item> - <item> - <widget class="QWidget" name="verticalWidget" native="true"> - <layout class="QVBoxLayout" name="verticalLayout_2"> - <property name="topMargin"> - <number>0</number> - </property> - <property name="rightMargin"> - <number>0</number> - </property> - <property name="bottomMargin"> - <number>0</number> - </property> - <item> - <widget class="QGroupBox" name="groupBox_2"> - <property name="title"> - <string>Field Separator</string> - </property> - <layout class="QHBoxLayout" name="horizontalLayout_2"> - <item> - <widget class="QComboBox" name="CSVSeparator"/> - </item> - </layout> - </widget> - </item> - <item> - <widget class="QGroupBox" name="groupBox_4"> - <property name="sizePolicy"> - <sizepolicy hsizetype="Preferred" vsizetype="Minimum"> - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> - </property> - <property name="title"> - <string>Pre-configured imports</string> - </property> - <layout class="QHBoxLayout" name="horizontalLayout_4"> - <item> - <widget class="QComboBox" name="knownImports"> - <property name="currentIndex"> - <number>-1</number> - </property> - </widget> - </item> - </layout> - </widget> - </item> - <item> - <spacer name="verticalSpacer"> - <property name="orientation"> - <enum>Qt::Vertical</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>20</width> - <height>40</height> - </size> - </property> - </spacer> - </item> - <item> - <widget class="QDialogButtonBox" name="buttonBox"> - <property name="orientation"> - <enum>Qt::Horizontal</enum> - </property> - <property name="standardButtons"> - <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> - </property> - </widget> - </item> - </layout> - </widget> - </item> - </layout> - </widget> - </item> - </layout> - </widget> - <resources> - <include location="../subsurface.qrc"/> - </resources> - <connections> - <connection> - <sender>buttonBox</sender> - <signal>accepted()</signal> - <receiver>CSVImportDialog</receiver> - <slot>accept()</slot> - <hints> - <hint type="sourcelabel"> - <x>310</x> - <y>286</y> - </hint> - <hint type="destinationlabel"> - <x>215</x> - <y>164</y> - </hint> - </hints> - </connection> - <connection> - <sender>buttonBox</sender> - <signal>rejected()</signal> - <receiver>CSVImportDialog</receiver> - <slot>reject()</slot> - <hints> - <hint type="sourcelabel"> - <x>310</x> - <y>286</y> - </hint> - <hint type="destinationlabel"> - <x>215</x> - <y>164</y> - </hint> - </hints> - </connection> - <connection> - <sender>temperatureCheckBox</sender> - <signal>clicked(bool)</signal> - <receiver>CSVTemperature</receiver> - <slot>setEnabled(bool)</slot> - <hints> - <hint type="sourcelabel"> - <x>77</x> - <y>191</y> - </hint> - <hint type="destinationlabel"> - <x>161</x> - <y>191</y> - </hint> - </hints> - </connection> - <connection> - <sender>po2CheckBox</sender> - <signal>clicked(bool)</signal> - <receiver>CSVpo2</receiver> - <slot>setEnabled(bool)</slot> - <hints> - <hint type="sourcelabel"> - <x>77</x> - <y>223</y> - </hint> - <hint type="destinationlabel"> - <x>161</x> - <y>223</y> - </hint> - </hints> - </connection> - <connection> - <sender>cnsCheckBox</sender> - <signal>clicked(bool)</signal> - <receiver>CSVcns</receiver> - <slot>setEnabled(bool)</slot> - <hints> - <hint type="sourcelabel"> - <x>77</x> - <y>255</y> - </hint> - <hint type="destinationlabel"> - <x>161</x> - <y>255</y> - </hint> - </hints> - </connection> - <connection> - <sender>stopdepthCheckBox</sender> - <signal>clicked(bool)</signal> - <receiver>CSVstopdepth</receiver> - <slot>setEnabled(bool)</slot> - <hints> - <hint type="sourcelabel"> - <x>77</x> - <y>287</y> - </hint> - <hint type="destinationlabel"> - <x>161</x> - <y>287</y> - </hint> - </hints> - </connection> - </connections> -</ui> diff --git a/qt-ui/divelistview.cpp b/qt-ui/divelistview.cpp index 9f5b0c0a7..5a436f2a9 100644 --- a/qt-ui/divelistview.cpp +++ b/qt-ui/divelistview.cpp @@ -31,6 +31,7 @@ DiveListView::DiveListView(QWidget *parent) : QTreeView(parent), mouseClickSelec QSortFilterProxyModel *model = new QSortFilterProxyModel(this); model->setSortRole(DiveTripModel::SORT_ROLE); model->setFilterKeyColumn(-1); // filter all columns + model->setFilterCaseSensitivity(Qt::CaseInsensitive); setModel(model); connect(model, SIGNAL(layoutChanged()), this, SLOT(fixMessyQtModelBehaviour())); @@ -42,7 +43,7 @@ DiveListView::DiveListView(QWidget *parent) : QTreeView(parent), mouseClickSelec header()->setStretchLastSection(true); QAction *showSearchBox = new QAction(tr("Show Search Box"), this); showSearchBox->setShortcut( Qt::CTRL + Qt::Key_F); - showSearchBox->setShortcutContext(Qt::ApplicationShortcut); + showSearchBox->setShortcutContext(Qt::WindowShortcut); addAction(showSearchBox); searchBox->installEventFilter(this); diff --git a/qt-ui/csvimportdialog.cpp b/qt-ui/divelogimportdialog.cpp index b88d9ceca..e5814795d 100644 --- a/qt-ui/csvimportdialog.cpp +++ b/qt-ui/divelogimportdialog.cpp @@ -1,22 +1,23 @@ #include <QtDebug> #include <QFileDialog> -#include "csvimportdialog.h" +#include "divelogimportdialog.h" #include "mainwindow.h" -#include "ui_csvimportdialog.h" +#include "ui_divelogimportdialog.h" -const CSVImportDialog::CSVAppConfig CSVImportDialog::CSVApps[CSVAPPS] = { +const DiveLogImportDialog::CSVAppConfig DiveLogImportDialog::CSVApps[CSVAPPS] = { {"", }, {"APD Log Viewer", 1, 2, 16, 7, 18, 19, "Tab"}, {"XP5", 1, 2, 10, -1, -1, -1, "Tab"}, {NULL,} }; -CSVImportDialog::CSVImportDialog(QWidget *parent) : +DiveLogImportDialog::DiveLogImportDialog(QStringList *fn, QWidget *parent) : QDialog(parent), selector(true), - ui(new Ui::CSVImportDialog) + ui(new Ui::DiveLogImportDialog) { ui->setupUi(this); + fileNames = *fn; for (int i = 0; !CSVApps[i].name.isNull(); ++i) ui->knownImports->addItem(CSVApps[i].name); @@ -24,7 +25,6 @@ CSVImportDialog::CSVImportDialog(QWidget *parent) : ui->CSVSeparator->addItem("Tab"); ui->CSVSeparator->addItem(","); ui->knownImports->setCurrentIndex(1); - ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false); connect(ui->CSVDepth, SIGNAL(valueChanged(int)), this, SLOT(unknownImports(int))); connect(ui->CSVTime, SIGNAL(valueChanged(int)), this, SLOT(unknownImports(int))); @@ -38,40 +38,35 @@ CSVImportDialog::CSVImportDialog(QWidget *parent) : connect(ui->stopdepthCheckBox, SIGNAL(clicked(bool)), this, SLOT(unknownImports(bool))); } -CSVImportDialog::~CSVImportDialog() +DiveLogImportDialog::~DiveLogImportDialog() { delete ui; } #define VALUE_IF_CHECKED(x) (ui->x->isEnabled() ? ui->x->value() - 1: -1) -void CSVImportDialog::on_buttonBox_accepted() +void DiveLogImportDialog::on_buttonBox_accepted() { char *error = NULL; - parse_csv_file(ui->CSVFile->text().toUtf8().data(), ui->CSVTime->value() - 1, - ui->CSVDepth->value() - 1, VALUE_IF_CHECKED(CSVTemperature), - VALUE_IF_CHECKED(CSVpo2), - VALUE_IF_CHECKED(CSVcns), - VALUE_IF_CHECKED(CSVstopdepth), - ui->CSVSeparator->currentIndex(), - &error); - if (error != NULL) { - mainWindow()->showError(error); - free(error); - error = NULL; + for (int i = 0; i < fileNames.size(); ++i) { + parse_csv_file(fileNames[i].toUtf8().data(), ui->CSVTime->value() - 1, + ui->CSVDepth->value() - 1, VALUE_IF_CHECKED(CSVTemperature), + VALUE_IF_CHECKED(CSVpo2), + VALUE_IF_CHECKED(CSVcns), + VALUE_IF_CHECKED(CSVstopdepth), + ui->CSVSeparator->currentIndex(), + &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);;All Files(*)")); - ui->CSVFile->setText(filename); - ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(!filename.isEmpty()); -} - #define SET_VALUE_AND_CHECKBOX(CSV, BOX, VAL) ({\ ui->CSV->blockSignals(true);\ ui->CSV->setValue(VAL);\ @@ -79,7 +74,7 @@ void CSVImportDialog::on_CSVFileSelector_clicked() ui->BOX->setChecked(VAL >= 0);\ ui->CSV->blockSignals(false);\ }) -void CSVImportDialog::on_knownImports_currentIndexChanged(int index) +void DiveLogImportDialog::on_knownImports_currentIndexChanged(int index) { if (index == 0) return; @@ -96,22 +91,17 @@ void CSVImportDialog::on_knownImports_currentIndexChanged(int index) SET_VALUE_AND_CHECKBOX(CSVstopdepth, stopdepthCheckBox, CSVApps[index].stopdepth); } -void CSVImportDialog::unknownImports(bool arg1) +void DiveLogImportDialog::unknownImports(bool arg1) { unknownImports(); } -void CSVImportDialog::unknownImports(int arg1) +void DiveLogImportDialog::unknownImports(int arg1) { unknownImports(); } -void CSVImportDialog::unknownImports() +void DiveLogImportDialog::unknownImports() { ui->knownImports->setCurrentIndex(0); } - -void CSVImportDialog::on_CSVFile_textEdited() -{ - ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(!ui->CSVFile->text().isEmpty()); -} diff --git a/qt-ui/divelogimportdialog.h b/qt-ui/divelogimportdialog.h new file mode 100644 index 000000000..d8cedab6a --- /dev/null +++ b/qt-ui/divelogimportdialog.h @@ -0,0 +1,49 @@ +#ifndef DIVELOGIMPORTDIALOG_H +#define DIVELOGIMPORTDIALOG_H + +#include <QDialog> +#include <QModelIndex> +#include "../dive.h" +#include "../divelist.h" + +namespace Ui { +class DiveLogImportDialog; +} + +class DiveLogImportDialog : public QDialog +{ + Q_OBJECT + +public: + explicit DiveLogImportDialog(QStringList *fn, QWidget *parent = 0); + ~DiveLogImportDialog(); + +private slots: + void on_buttonBox_accepted(); + void on_knownImports_currentIndexChanged(int index); + void unknownImports(int); + void unknownImports(bool); + +private: + void unknownImports(); + + bool selector; + QStringList fileNames; + Ui::DiveLogImportDialog *ui; + + struct CSVAppConfig { + QString name; + int time; + int depth; + int temperature; + int po2; + int cns; + int stopdepth; + QString separator; + }; + +#define CSVAPPS 4 + static const CSVAppConfig CSVApps[CSVAPPS]; +}; + +#endif // DIVELOGIMPORTDIALOG_H diff --git a/qt-ui/divelogimportdialog.ui b/qt-ui/divelogimportdialog.ui new file mode 100644 index 000000000..36f62de54 --- /dev/null +++ b/qt-ui/divelogimportdialog.ui @@ -0,0 +1,392 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>DiveLogImportDialog</class> + <widget class="QDialog" name="DiveLogImportDialog"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>515</width> + <height>370</height> + </rect> + </property> + <property name="windowTitle"> + <string>Import dive log file</string> + </property> + <property name="windowIcon"> + <iconset> + <normalon>:/subsurface-icon</normalon> + </iconset> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="QLabel" name="label_3"> + <property name="font"> + <font> + <family>Droid Sans [unknown]</family> + <pointsize>14</pointsize> + <weight>75</weight> + <bold>true</bold> + </font> + </property> + <property name="frameShape"> + <enum>QFrame::NoFrame</enum> + </property> + <property name="text"> + <string>Import CSV Dive Log Files</string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + </widget> + </item> + <item> + <widget class="QTabWidget" name="tabWidget"> + <property name="currentIndex"> + <number>0</number> + </property> + <widget class="QWidget" name="tab"> + <attribute name="title"> + <string>CSV options</string> + </attribute> + <widget class="QGroupBox" name="groupBox_2"> + <property name="geometry"> + <rect> + <x>210</x> + <y>10</y> + <width>281</width> + <height>65</height> + </rect> + </property> + <property name="title"> + <string>Field Separator</string> + </property> + <layout class="QHBoxLayout" name="horizontalLayout_2"> + <item> + <widget class="QComboBox" name="CSVSeparator"/> + </item> + </layout> + </widget> + <widget class="QGroupBox" name="groupBox_4"> + <property name="geometry"> + <rect> + <x>210</x> + <y>80</y> + <width>281</width> + <height>65</height> + </rect> + </property> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Minimum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="title"> + <string>Pre-configured imports</string> + </property> + <layout class="QHBoxLayout" name="horizontalLayout_4"> + <item> + <widget class="QComboBox" name="knownImports"> + <property name="currentIndex"> + <number>-1</number> + </property> + </widget> + </item> + </layout> + </widget> + <widget class="QGroupBox" name="groupBox_3"> + <property name="geometry"> + <rect> + <x>10</x> + <y>10</y> + <width>185</width> + <height>246</height> + </rect> + </property> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Expanding"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="title"> + <string>Field Configuration</string> + </property> + <layout class="QGridLayout" name="gridLayout"> + <item row="3" column="1"> + <widget class="QSpinBox" name="CSVpo2"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="minimum"> + <number>1</number> + </property> + <property name="value"> + <number>1</number> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QSpinBox" name="CSVDepth"> + <property name="minimum"> + <number>1</number> + </property> + <property name="value"> + <number>2</number> + </property> + </widget> + </item> + <item row="2" column="1"> + <widget class="QSpinBox" name="CSVTemperature"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="minimum"> + <number>1</number> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="label_2"> + <property name="text"> + <string>Depth</string> + </property> + </widget> + </item> + <item row="5" column="1"> + <widget class="QSpinBox" name="CSVstopdepth"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="minimum"> + <number>1</number> + </property> + <property name="value"> + <number>1</number> + </property> + </widget> + </item> + <item row="5" column="0"> + <widget class="QCheckBox" name="stopdepthCheckBox"> + <property name="text"> + <string>Stopdepth</string> + </property> + </widget> + </item> + <item row="3" column="0"> + <widget class="QCheckBox" name="po2CheckBox"> + <property name="text"> + <string>PO2</string> + </property> + </widget> + </item> + <item row="0" column="0"> + <widget class="QLabel" name="label"> + <property name="text"> + <string>Time</string> + </property> + </widget> + </item> + <item row="2" column="0"> + <widget class="QCheckBox" name="temperatureCheckBox"> + <property name="text"> + <string>Temp</string> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QSpinBox" name="CSVTime"> + <property name="minimum"> + <number>1</number> + </property> + <property name="value"> + <number>1</number> + </property> + </widget> + </item> + <item row="4" column="1"> + <widget class="QSpinBox" name="CSVcns"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="minimum"> + <number>1</number> + </property> + <property name="value"> + <number>1</number> + </property> + </widget> + </item> + <item row="4" column="0"> + <widget class="QCheckBox" name="cnsCheckBox"> + <property name="text"> + <string>Cns</string> + </property> + </widget> + </item> + </layout> + <zorder>label</zorder> + <zorder>label_2</zorder> + <zorder>CSVTime</zorder> + <zorder>CSVDepth</zorder> + <zorder>temperatureCheckBox</zorder> + <zorder>CSVTemperature</zorder> + <zorder>po2CheckBox</zorder> + <zorder>CSVpo2</zorder> + <zorder>cnsCheckBox</zorder> + <zorder>CSVcns</zorder> + <zorder>stopdepthCheckBox</zorder> + <zorder>CSVstopdepth</zorder> + </widget> + </widget> + </widget> + </item> + <item> + <widget class="QWidget" name="horizontalWidget" native="true"> + <layout class="QHBoxLayout" name="horizontalLayout_3"> + <property name="spacing"> + <number>0</number> + </property> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <widget class="QWidget" name="verticalWidget" native="true"> + <layout class="QVBoxLayout" name="verticalLayout_2"> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <widget class="QDialogButtonBox" name="buttonBox"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="standardButtons"> + <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> + </property> + </widget> + </item> + </layout> + </widget> + </item> + </layout> + </widget> + </item> + </layout> + </widget> + <resources/> + <connections> + <connection> + <sender>buttonBox</sender> + <signal>accepted()</signal> + <receiver>DiveLogImportDialog</receiver> + <slot>accept()</slot> + <hints> + <hint type="sourcelabel"> + <x>310</x> + <y>286</y> + </hint> + <hint type="destinationlabel"> + <x>215</x> + <y>164</y> + </hint> + </hints> + </connection> + <connection> + <sender>buttonBox</sender> + <signal>rejected()</signal> + <receiver>DiveLogImportDialog</receiver> + <slot>reject()</slot> + <hints> + <hint type="sourcelabel"> + <x>310</x> + <y>286</y> + </hint> + <hint type="destinationlabel"> + <x>215</x> + <y>164</y> + </hint> + </hints> + </connection> + <connection> + <sender>temperatureCheckBox</sender> + <signal>clicked(bool)</signal> + <receiver>CSVTemperature</receiver> + <slot>setEnabled(bool)</slot> + <hints> + <hint type="sourcelabel"> + <x>77</x> + <y>191</y> + </hint> + <hint type="destinationlabel"> + <x>161</x> + <y>191</y> + </hint> + </hints> + </connection> + <connection> + <sender>po2CheckBox</sender> + <signal>clicked(bool)</signal> + <receiver>CSVpo2</receiver> + <slot>setEnabled(bool)</slot> + <hints> + <hint type="sourcelabel"> + <x>77</x> + <y>223</y> + </hint> + <hint type="destinationlabel"> + <x>161</x> + <y>223</y> + </hint> + </hints> + </connection> + <connection> + <sender>cnsCheckBox</sender> + <signal>clicked(bool)</signal> + <receiver>CSVcns</receiver> + <slot>setEnabled(bool)</slot> + <hints> + <hint type="sourcelabel"> + <x>77</x> + <y>255</y> + </hint> + <hint type="destinationlabel"> + <x>161</x> + <y>255</y> + </hint> + </hints> + </connection> + <connection> + <sender>stopdepthCheckBox</sender> + <signal>clicked(bool)</signal> + <receiver>CSVstopdepth</receiver> + <slot>setEnabled(bool)</slot> + <hints> + <hint type="sourcelabel"> + <x>77</x> + <y>287</y> + </hint> + <hint type="destinationlabel"> + <x>161</x> + <y>287</y> + </hint> + </hints> + </connection> + </connections> +</ui> diff --git a/qt-ui/maintab.cpp b/qt-ui/maintab.cpp index d149815f6..1de4b6df7 100644 --- a/qt-ui/maintab.cpp +++ b/qt-ui/maintab.cpp @@ -980,7 +980,8 @@ void MainTab::editWeightWidget(const QModelIndex& index) QString MainTab::printGPSCoords(int lat, int lon) { unsigned int latdeg, londeg; - unsigned int ilatmin, ilonmin; + unsigned int latmin, lonmin; + double latsec, lonsec; QString lath, lonh, result; if (!lat && !lon) @@ -992,11 +993,13 @@ QString MainTab::printGPSCoords(int lat, int lon) lon = abs(lon); latdeg = lat / 1000000; londeg = lon / 1000000; - ilatmin = (lat % 1000000) * 60; - ilonmin = (lon % 1000000) * 60; - result.sprintf("%s%u%s %2d.%05d\' , %s%u%s %2d.%05d\'", - lath.toUtf8().data(), latdeg, UTF8_DEGREE, ilatmin / 1000000, (ilatmin % 1000000) / 10, - lonh.toUtf8().data(), londeg, UTF8_DEGREE, ilonmin / 1000000, (ilonmin % 1000000) / 10); + latmin = (lat % 1000000) * 60; + lonmin = (lon % 1000000) * 60; + latsec = (latmin % 1000000) * 60; + lonsec = (lonmin % 1000000) * 60; + result.sprintf("%u%s%02d\'%06.3f\"%s %u%s%02d\'%06.3f\"%s", + latdeg, UTF8_DEGREE, latmin / 1000000, latsec / 1000000, lath.toUtf8().data(), + londeg, UTF8_DEGREE, lonmin / 1000000, lonsec / 1000000, lonh.toUtf8().data()); return result; } diff --git a/qt-ui/mainwindow.cpp b/qt-ui/mainwindow.cpp index ad52c7d7f..80da754a2 100644 --- a/qt-ui/mainwindow.cpp +++ b/qt-ui/mainwindow.cpp @@ -35,7 +35,7 @@ #include "diveplanner.h" #include "about.h" #include "printdialog.h" -#include "csvimportdialog.h" +#include "divelogimportdialog.h" static MainWindow* instance = 0; @@ -167,15 +167,6 @@ void MainWindow::on_actionClose_triggered() clear_events(); } -void MainWindow::on_actionImport_triggered() -{ - QStringList fileNames = QFileDialog::getOpenFileNames(this, tr("Import Files"), lastUsedDir(), filter()); - if (!fileNames.size()) - return; // no selection - updateLastUsedDir(QFileInfo(fileNames.at(0)).dir().path()); - importFiles(fileNames); -} - QString MainWindow::lastUsedDir() { QSettings settings; @@ -493,27 +484,11 @@ void MainWindow::on_actionAboutSubsurface_triggered() void MainWindow::on_actionUserManual_triggered() { if(!helpView){ - helpView = new QWebView(); - helpView->page()->setLinkDelegationPolicy(QWebPage::DelegateExternalLinks); - connect(helpView, SIGNAL(linkClicked(QUrl)), this, SLOT(linkClickedSlot(QUrl))); - } - QString searchPath = getSubsurfaceDataPath("Documentation"); - if (searchPath != "") { - QUrl url(searchPath.append("/user-manual.html")); - helpView->setWindowTitle(tr("User Manual")); - helpView->setWindowIcon(QIcon(":/subsurface-icon")); - helpView->setUrl(url); - } else { - helpView->setHtml(tr("Cannot find the Subsurface manual")); + helpView = new UserManual(); } helpView->show(); } -void MainWindow::linkClickedSlot(QUrl url) -{ - QDesktopServices::openUrl(url); -} - QString MainWindow::filter() { QString f; @@ -864,12 +839,26 @@ void MainWindow::loadFiles(const QStringList fileNames) ui.actionAutoGroup->setChecked(autogroup); } -void MainWindow::on_actionImportCSV_triggered() +void MainWindow::on_actionImportDiveLog_triggered() { - CSVImportDialog *csvImport = new CSVImportDialog(); - csvImport->show(); - process_dives(TRUE, FALSE); - refreshDisplay(); + QStringList fileNames = QFileDialog::getOpenFileNames(this, tr("Open Dive Log File"), lastUsedDir(), tr("Dive Log Files (*.xml *.uddf *.udcf *.csv *.jlb *.dld *.sde *.db);;XML Files (*.xml);;UDDF/UDCF Files(*.uddf *.udcf);;JDiveLog Files(*.jlb);;Suunto Files(*.sde *.db);;CSV Files(*.csv);;All Files(*)")); + + if (fileNames.isEmpty()) + return; + updateLastUsedDir(QFileInfo(fileNames[0]).dir().path()); + + QStringList logFiles = fileNames.filter( QRegExp("^.*\\.(?!csv)", Qt::CaseInsensitive) ) ; + QStringList csvFiles = fileNames.filter(".csv", Qt::CaseInsensitive); + if (logFiles.size()) { + importFiles(logFiles); + } + + if (csvFiles.size()) { + DiveLogImportDialog *diveLogImport = new DiveLogImportDialog(&csvFiles); + diveLogImport->show(); + process_dives(TRUE, FALSE); + refreshDisplay(); + } } void MainWindow::editCurrentDive() diff --git a/qt-ui/mainwindow.h b/qt-ui/mainwindow.h index d4e10e822..7d14fc7fe 100644 --- a/qt-ui/mainwindow.h +++ b/qt-ui/mainwindow.h @@ -12,6 +12,7 @@ #include <QUrl> #include "ui_mainwindow.h" +#include "usermanual.h" struct DiveList; class QSortFilterProxyModel; @@ -62,7 +63,6 @@ private slots: void on_actionSave_triggered(); void on_actionSaveAs_triggered(); void on_actionClose_triggered(); - void on_actionImport_triggered(); void on_actionExportUDDF_triggered(); void on_actionPrint_triggered(); void on_actionPreferences_triggered(); @@ -102,8 +102,7 @@ private slots: void current_dive_changed(int divenr); void initialUiSetup(); - void on_actionImportCSV_triggered(); - void linkClickedSlot(QUrl url); + void on_actionImportDiveLog_triggered(); protected: void closeEvent(QCloseEvent *); @@ -118,7 +117,7 @@ private: Ui::MainWindow ui; QAction *actionNextDive; QAction *actionPreviousDive; - QWebView *helpView; + UserManual *helpView; CurrentState state; QString filter(); bool askSaveChanges(); diff --git a/qt-ui/mainwindow.ui b/qt-ui/mainwindow.ui index 60aa787e4..3748f5950 100644 --- a/qt-ui/mainwindow.ui +++ b/qt-ui/mainwindow.ui @@ -197,8 +197,7 @@ <string>&Import</string> </property> <addaction name="actionDownloadDC"/> - <addaction name="actionImport"/> - <addaction name="actionImportCSV"/> + <addaction name="actionImportDiveLog"/> <addaction name="actionDownloadWeb"/> <addaction name="actionDivelogs_de"/> </widget> @@ -264,17 +263,6 @@ <string>Ctrl+W</string> </property> </action> - <action name="actionImport"> - <property name="text"> - <string>Import Files</string> - </property> - <property name="toolTip"> - <string>Import Files</string> - </property> - <property name="shortcut"> - <string>Ctrl+I</string> - </property> - </action> <action name="actionExportUDDF"> <property name="text"> <string>Export &UDDF</string> @@ -461,12 +449,15 @@ <string>Ctrl+L</string> </property> </action> - <action name="actionImportCSV"> + <action name="actionImportDiveLog"> <property name="text"> - <string>Import CSV</string> + <string>Import Log Files</string> </property> <property name="toolTip"> - <string>Import CS&V</string> + <string>Import divelog files from other applications</string> + </property> + <property name="shortcut"> + <string>Ctrl+I</string> </property> </action> <action name="actionDivelogs_de"> diff --git a/qt-ui/models.cpp b/qt-ui/models.cpp index b981793fb..4debafd9a 100644 --- a/qt-ui/models.cpp +++ b/qt-ui/models.cpp @@ -1010,6 +1010,7 @@ static int nitrox_sort_value(struct dive *dive) QVariant DiveItem::data(int column, int role) const { QVariant retVal; + struct dive *dive = getDiveById(diveId); switch (role) { case Qt::TextAlignmentRole: @@ -1025,6 +1026,7 @@ QVariant DiveItem::data(int column, int role) const } break; case DiveTripModel::SORT_ROLE: + Q_ASSERT(dive != NULL); switch (column) { case NR: retVal = (qulonglong) dive->when; break; case DATE: retVal = (qulonglong) dive->when; break; @@ -1043,6 +1045,7 @@ QVariant DiveItem::data(int column, int role) const } break; case Qt::DisplayRole: + Q_ASSERT(dive != NULL); switch (column) { case NR: retVal = dive->number; break; case DATE: retVal = displayDate(); break; @@ -1061,13 +1064,15 @@ QVariant DiveItem::data(int column, int role) const break; } - if (role == DiveTripModel::STAR_ROLE) + if (role == DiveTripModel::STAR_ROLE) { + Q_ASSERT(dive != NULL); retVal = dive->rating; - - if (role == DiveTripModel::DIVE_ROLE) + } + if (role == DiveTripModel::DIVE_ROLE) { retVal = QVariant::fromValue<void*>(dive); - + } if(role == DiveTripModel::DIVE_IDX){ + Q_ASSERT(dive != NULL); retVal = get_divenr(dive); } return retVal; @@ -1098,21 +1103,26 @@ bool DiveItem::setData(const QModelIndex& index, const QVariant& value, int role if (d->number == v) return false; } - - dive->number = value.toInt(); + d = getDiveById(diveId); + Q_ASSERT(d != NULL); + d->number = value.toInt(); mark_divelist_changed(TRUE); return true; } QString DiveItem::displayDate() const { + struct dive *dive = getDiveById(diveId); + Q_ASSERT(dive != NULL); return get_dive_date_string(dive->when); } QString DiveItem::displayDepth() const { - const int scale = 1000; QString fract, str; + const int scale = 1000; + struct dive *dive = getDiveById(diveId); + Q_ASSERT(dive != NULL); if (get_units()->length == units::METERS) { fract = QString::number((unsigned)(dive->maxdepth.mm % scale) / 100); str = QString("%1.%2").arg((unsigned)(dive->maxdepth.mm / scale)).arg(fract, 1, QChar('0')); @@ -1126,6 +1136,8 @@ QString DiveItem::displayDepth() const QString DiveItem::displayDuration() const { int hrs, mins, secs; + struct dive *dive = getDiveById(diveId); + Q_ASSERT(dive != NULL); secs = dive->duration.seconds % 60; mins = dive->duration.seconds / 60; hrs = mins / 60; @@ -1143,6 +1155,8 @@ QString DiveItem::displayDuration() const QString DiveItem::displayTemperature() const { QString str; + struct dive *dive = getDiveById(diveId); + Q_ASSERT(dive != NULL); if (!dive->watertemp.mkelvin) return str; if (get_units()->temperature == units::CELSIUS) @@ -1155,6 +1169,8 @@ QString DiveItem::displayTemperature() const QString DiveItem::displaySac() const { QString str; + struct dive *dive = getDiveById(diveId); + Q_ASSERT(dive != NULL); if (get_units()->volume == units::LITER) str = QString::number(dive->sac / 1000.0, 'f', 1).append(tr(" l/min")); else @@ -1170,6 +1186,8 @@ QString DiveItem::displayWeight() const int DiveItem::weight() const { + struct dive *dive = getDiveById(diveId); + Q_ASSERT(dive != 0); weight_t tw = { total_weight(dive) }; return tw.grams; } @@ -1237,7 +1255,7 @@ void DiveTripModel::setupModelData() dive_trip_t* trip = dive->divetrip; DiveItem* diveItem = new DiveItem(); - diveItem->dive = dive; + diveItem->diveId = dive->id; if (!trip || currentLayout == LIST) { diveItem->parent = rootItem; @@ -1644,7 +1662,7 @@ ProfilePrintModel::ProfilePrintModel(QObject *parent) void ProfilePrintModel::setDive(struct dive *divePtr) { - dive = divePtr; + diveId = divePtr->id; // reset(); } @@ -1665,8 +1683,10 @@ QVariant ProfilePrintModel::data(const QModelIndex &index, int role) const switch (role) { case Qt::DisplayRole: { + struct dive *dive = getDiveById(diveId); + Q_ASSERT(dive != NULL); struct DiveItem di; - di.dive = dive; + di.diveId = diveId; const QString unknown = tr("unknown"); diff --git a/qt-ui/models.h b/qt-ui/models.h index baa7b32c4..06dc66aa1 100644 --- a/qt-ui/models.h +++ b/qt-ui/models.h @@ -157,7 +157,7 @@ struct DiveItem : public TreeItem { SUIT, CYLINDER, NITROX, SAC, OTU, MAXCNS, LOCATION, COLUMNS }; virtual QVariant data(int column, int role) const; - struct dive* dive; + int diveId; virtual bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::EditRole); virtual Qt::ItemFlags flags(const QModelIndex& index) const; QString displayDate() const; @@ -291,7 +291,7 @@ class ProfilePrintModel : public QAbstractTableModel Q_OBJECT private: - struct dive *dive; + int diveId; QString truncateString(char *str, const int maxlen) const; public: diff --git a/qt-ui/printlayout.cpp b/qt-ui/printlayout.cpp index dad940fbb..e60a3189f 100644 --- a/qt-ui/printlayout.cpp +++ b/qt-ui/printlayout.cpp @@ -391,7 +391,7 @@ void PrintLayout::printTable() void PrintLayout::addTablePrintDataRow(TablePrintModel *model, int row, struct dive *dive) const { struct DiveItem di; - di.dive = dive; + di.diveId = dive->id; model->insertRow(); model->setData(model->index(row, 0), QString::number(dive->number), Qt::DisplayRole); model->setData(model->index(row, 1), di.displayDate(), Qt::DisplayRole); diff --git a/qt-ui/profilegraphics.cpp b/qt-ui/profilegraphics.cpp index 76eb64b84..5bb55d11b 100644 --- a/qt-ui/profilegraphics.cpp +++ b/qt-ui/profilegraphics.cpp @@ -52,7 +52,7 @@ extern int evn_used; QPoint(viewport()->geometry().width() - toolBarProxy->boundingRect().width(), \ viewport()->geometry().height() - toolBarProxy->boundingRect().height() ) -ProfileGraphicsView::ProfileGraphicsView(QWidget* parent) : QGraphicsView(parent), toolTip(0) , dive(0), diveDC(0), rulerItem(0), toolBarProxy(0) +ProfileGraphicsView::ProfileGraphicsView(QWidget* parent) : QGraphicsView(parent), toolTip(0) , diveId(0), diveDC(0), rulerItem(0), toolBarProxy(0) { printMode = false; isGrayscale = false; @@ -313,8 +313,8 @@ void ProfileGraphicsView::showEvent(QShowEvent* event) // but the dive was not ploted. // force a replot by modifying the dive // hold by the view, and issuing a plot. - if (dive && !scene()->items().count()) { - dive = 0; + if (diveId && !scene()->items().count()) { + diveId = 0; plot(get_dive(selected_dive)); } if (toolBarProxy) @@ -369,14 +369,14 @@ void ProfileGraphicsView::plot(struct dive *d, bool forceRedraw) if (d) dc = select_dc(&d->dc); - if (!forceRedraw && dive == d && (d && dc == diveDC)) + if (!forceRedraw && getDiveById(diveId) == d && (d && dc == diveDC)) return; clear(); - dive = d; + diveId = d ? d->id : 0; diveDC = d ? dc : NULL; - if (!isVisible() || !dive || !mainWindow()) { + if (!isVisible() || !d || !mainWindow()) { return; } setBackgroundBrush(getColor(BACKGROUND)); @@ -415,14 +415,14 @@ void ProfileGraphicsView::plot(struct dive *d, bool forceRedraw) * Set up limits that are independent of * the dive computer */ - calculate_max_limits(dive, dc, &gc); + calculate_max_limits(d, dc, &gc); QRectF profile_grid_area = scene()->sceneRect(); gc.maxx = (profile_grid_area.width() - 2 * profile_grid_area.x()); gc.maxy = (profile_grid_area.height() - 2 * profile_grid_area.y()); /* This is per-dive-computer */ - gc.pi = *create_plot_info(dive, dc, &gc, printMode); + gc.pi = *create_plot_info(d, dc, &gc, printMode); /* Bounding box */ QPen pen = defaultPen; @@ -483,7 +483,7 @@ void ProfileGraphicsView::plot(struct dive *d, bool forceRedraw) if(mode == PLAN){ timeEditor = new GraphicsTextEditor(); - timeEditor->setPlainText( dive->duration.seconds ? QString::number(dive->duration.seconds/60) : tr("Set Duration: 10 minutes")); + timeEditor->setPlainText(d->duration.seconds ? QString::number(d->duration.seconds/60) : tr("Set Duration: 10 minutes")); timeEditor->setPos(profile_grid_area.width() - timeEditor->boundingRect().width(), timeMarkers->y()); timeEditor->document(); connect(timeEditor, SIGNAL(editingFinished(QString)), this, SLOT(edit_dive_time(QString))); @@ -720,6 +720,8 @@ void ProfileGraphicsView::plot_cylinder_pressure_text() int last_time[MAX_CYLINDERS] = { 0, }; struct plot_data *entry; struct plot_info *pi = &gc.pi; + struct dive *dive = getDiveById(diveId); + Q_ASSERT(dive != NULL); if (!get_cylinder_pressure_range(&gc)) return; @@ -888,6 +890,8 @@ void ProfileGraphicsView::plot_cylinder_pressure() if (!get_cylinder_pressure_range(&gc)) return; + struct dive *dive = getDiveById(diveId); + Q_ASSERT(dive != NULL); QPointF from, to; for (i = 0; i < gc.pi.nr; i++) { int mbar; @@ -1006,7 +1010,8 @@ void ProfileGraphicsView::plot_one_event(struct event *ev) int x = SCALEXGC(ev->time.seconds); int y = SCALEYGC(entry->depth); - + struct dive *dive = getDiveById(diveId); + Q_ASSERT(dive != NULL); EventItem *item = new EventItem(ev, 0, isGrayscale); item->setPos(x, y); scene()->addItem(item); diff --git a/qt-ui/profilegraphics.h b/qt-ui/profilegraphics.h index 064b4478d..b8e939ff8 100644 --- a/qt-ui/profilegraphics.h +++ b/qt-ui/profilegraphics.h @@ -190,7 +190,7 @@ private: QBrush defaultBrush; ToolTipItem *toolTip; graphics_context gc; - struct dive *dive; + int diveId; struct divecomputer *diveDC; int zoomLevel; diff --git a/qt-ui/usermanual.cpp b/qt-ui/usermanual.cpp new file mode 100644 index 000000000..cb9b4da50 --- /dev/null +++ b/qt-ui/usermanual.cpp @@ -0,0 +1,93 @@ +#include <QDesktopServices> + +#include "usermanual.h" +#include "ui_usermanual.h" + +#include "../helpers.h" + +UserManual::UserManual(QWidget *parent) : + QMainWindow(parent), + ui(new Ui::UserManual) +{ + ui->setupUi(this); + + QAction *actionShowSearch = new QAction(this); + actionShowSearch->setShortcut(Qt::CTRL + Qt::Key_F); + actionShowSearch->setShortcutContext(Qt::WindowShortcut); + addAction(actionShowSearch); + + QAction *actionHideSearch = new QAction(this); + actionHideSearch->setShortcut(Qt::Key_Escape); + actionHideSearch->setShortcutContext(Qt::WindowShortcut); + addAction(actionHideSearch); + + setWindowTitle(tr("User Manual")); + + ui->webView->page()->setLinkDelegationPolicy(QWebPage::DelegateExternalLinks); + QString searchPath = getSubsurfaceDataPath("Documentation"); + if (searchPath != "") { + QUrl url(searchPath.append("/user-manual.html")); + ui->webView->setUrl(url); + } else { + ui->webView->setHtml(tr("Cannot find the Subsurface manual")); + } + ui->searchPanel->setParent(this); + ui->searchPanel->hide(); + + connect(actionShowSearch, SIGNAL(triggered(bool)), this, SLOT(showSearchPanel())); + connect(actionHideSearch, SIGNAL(triggered(bool)), this, SLOT(hideSearchPanel())); + connect(ui->webView, SIGNAL(linkClicked(QUrl)), this, SLOT(linkClickedSlot(QUrl))); + connect(ui->searchEdit, SIGNAL(textChanged(QString)), this, SLOT(searchTextChanged(QString))); + connect(ui->findNext, SIGNAL(clicked()), this, SLOT(searchNext())); + connect(ui->findPrev, SIGNAL(clicked()), this, SLOT(searchPrev())); +} + +void UserManual::showSearchPanel() +{ + ui->searchPanel->show(); + ui->searchEdit->setFocus(); + ui->searchEdit->selectAll(); +} + +void UserManual::hideSearchPanel() +{ + ui->searchPanel->hide(); +} + +void UserManual::search(QString text, QWebPage::FindFlags flags = 0) +{ + if (ui->webView->findText(text, QWebPage::FindWrapsAroundDocument|flags) || text.length() == 0) { + ui->searchEdit->setStyleSheet(""); + } else { + ui->searchEdit->setStyleSheet("QLineEdit{background: red;}"); + } +} + +void UserManual::searchTextChanged(QString text) { + bool hasText = text.length() > 0; + + ui->findPrev->setEnabled(hasText); + ui->findNext->setEnabled(hasText); + + search(text); +} + +void UserManual::searchNext() +{ + search(ui->searchEdit->text()); +} + +void UserManual::searchPrev() +{ + search(ui->searchEdit->text(), QWebPage::FindBackward); +} + +void UserManual::linkClickedSlot(QUrl url) +{ + QDesktopServices::openUrl(url); +} + +UserManual::~UserManual() +{ + delete ui; +} diff --git a/qt-ui/usermanual.h b/qt-ui/usermanual.h new file mode 100644 index 000000000..6c5860206 --- /dev/null +++ b/qt-ui/usermanual.h @@ -0,0 +1,32 @@ +#ifndef USERMANUAL_H +#define USERMANUAL_H + +#include <QMainWindow> +#include <QWebPage> + +namespace Ui { +class UserManual; +} + +class UserManual : public QMainWindow +{ + Q_OBJECT + +public: + explicit UserManual(QWidget *parent = 0); + ~UserManual(); + +private slots: + void showSearchPanel(); + void hideSearchPanel(); + void searchTextChanged(QString); + void searchNext(); + void searchPrev(); + void linkClickedSlot(QUrl url); + +private: + Ui::UserManual *ui; + void search(QString, QWebPage::FindFlags); +}; + +#endif // USERMANUAL_H diff --git a/qt-ui/usermanual.ui b/qt-ui/usermanual.ui new file mode 100644 index 000000000..766a0a832 --- /dev/null +++ b/qt-ui/usermanual.ui @@ -0,0 +1,146 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>UserManual</class> + <widget class="QMainWindow" name="UserManual"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>834</width> + <height>599</height> + </rect> + </property> + <property name="windowIcon"> + <iconset resource="../subsurface.qrc"> + <normaloff>:/subsurface-icon</normaloff>:/subsurface-icon</iconset> + </property> + <widget class="QWidget" name="centralWidget"> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="QWidget" name="searchPanel" native="true"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Maximum" vsizetype="Maximum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>230</width> + <height>40</height> + </size> + </property> + <property name="autoFillBackground"> + <bool>true</bool> + </property> + <layout class="QHBoxLayout" name="horizontalLayout"> + <property name="spacing"> + <number>0</number> + </property> + <property name="leftMargin"> + <number>9</number> + </property> + <property name="topMargin"> + <number>9</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <widget class="QLineEdit" name="searchEdit"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Minimum" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>100</width> + <height>0</height> + </size> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="findPrev"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="text"> + <string/> + </property> + <property name="icon"> + <iconset theme="go-previous"> + <normaloff/> + </iconset> + </property> + <property name="flat"> + <bool>true</bool> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="findNext"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="sizePolicy"> + <sizepolicy hsizetype="Minimum" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string/> + </property> + <property name="icon"> + <iconset theme="go-next"> + <normaloff/> + </iconset> + </property> + <property name="flat"> + <bool>true</bool> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="findClose"> + <property name="text"> + <string/> + </property> + <property name="icon"> + <iconset theme="window-close"> + <normaloff/> + </iconset> + </property> + <property name="flat"> + <bool>true</bool> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QWebView" name="webView" native="true"/> + </item> + </layout> + </widget> + </widget> + <layoutdefault spacing="6" margin="11"/> + <customwidgets> + <customwidget> + <class>QWebView</class> + <extends>QWidget</extends> + <header>qwebview.h</header> + </customwidget> + </customwidgets> + <resources> + <include location="../subsurface.qrc"/> + </resources> + <connections/> +</ui> diff --git a/qthelper.cpp b/qthelper.cpp index 9587fcf3f..9d503cb6a 100644 --- a/qthelper.cpp +++ b/qthelper.cpp @@ -104,8 +104,9 @@ QString weight_string(int weight_in_grams) bool parseGpsText(const QString& gps_text, double *latitude, double *longitude) { - enum { SECONDS, MINUTES, DECIMAL } gpsStyle = DECIMAL; + enum { ISO6709D, SECONDS, MINUTES, DECIMAL } gpsStyle = ISO6709D; int eastWest = 4; + int northSouth = 1; QString regExp; /* an empty string is interpreted as 0.0,0.0 and therefore "no gps location" */ if (gps_text.trimmed() == "") { @@ -114,8 +115,16 @@ bool parseGpsText(const QString& gps_text, double *latitude, double *longitude) return true; } // trying to parse all formats in one regexp might be possible, but it seems insane - // so handle the three formats we understand separately - if (gps_text.count(QChar('"')) == 2) { + // so handle the four formats we understand separately + + // ISO 6709 Annex D representation + // http://en.wikipedia.org/wiki/ISO_6709#Representation_at_the_human_interface_.28Annex_D.29 + if (gps_text.at(0).isDigit()) { + gpsStyle = ISO6709D; + regExp = QString("(\\d+)[" UTF8_DEGREE "\\s](\\d+)[\'\\s](\\d+)([,\\.](\\d+))?[\"\\s]([NS%1%2])" + "\\s*(\\d+)[" UTF8_DEGREE "\\s](\\d+)[\'\\s](\\d+)([,\\.](\\d+))?[\"\\s]([EW%3%4])") + .arg(tr("N")).arg(tr("S")).arg(tr("E")).arg(tr("W")); + } else if (gps_text.count(QChar('"')) == 2) { gpsStyle = SECONDS; regExp = QString("\\s*([NS%1%2])\\s*(\\d+)[" UTF8_DEGREE "\\s]+(\\d+)[\'\\s]+(\\d+)([,\\.](\\d+))?[^EW%3%4]*" "([EW%6%7])\\s*(\\d+)[" UTF8_DEGREE "\\s]+(\\d+)[\'\\s]+(\\d+)([,\\.](\\d+))?") @@ -126,6 +135,7 @@ bool parseGpsText(const QString& gps_text, double *latitude, double *longitude) "([EW%6%7])\\s*(\\d+)[" UTF8_DEGREE "\\s]+(\\d+)([,\\.](\\d+))?") .arg(tr("N")).arg(tr("S")).arg(tr("E")).arg(tr("W")).arg(tr("E")).arg(tr("W")); } else { + gpsStyle = DECIMAL; regExp = QString("\\s*([-NS%1%2]?)\\s*(\\d+)[,\\.](\\d+)[^-EW%3%4\\d]*([-EW%5%6]?)\\s*(\\d+)[,\\.](\\d+)") .arg(tr("N")).arg(tr("S")).arg(tr("E")).arg(tr("W")).arg(tr("E")).arg(tr("W")); } @@ -134,6 +144,14 @@ bool parseGpsText(const QString& gps_text, double *latitude, double *longitude) // qDebug() << "Hemisphere" << r.cap(1) << "deg" << r.cap(2) << "min" << r.cap(3) << "decimal" << r.cap(4); // qDebug() << "Hemisphere" << r.cap(5) << "deg" << r.cap(6) << "min" << r.cap(7) << "decimal" << r.cap(8); switch(gpsStyle) { + case ISO6709D: + *latitude = r.cap(1).toInt() + r.cap(2).toInt() / 60.0 + + (r.cap(3) + QString(".") + r.cap(5)).toDouble() / 3600.0; + *longitude = r.cap(7).toInt() + r.cap(8).toInt() / 60.0 + + (r.cap(9) + QString(".") + r.cap(11)).toDouble() / 3600.0; + northSouth = 6; + eastWest = 12; + break; case SECONDS: *latitude = r.cap(2).toInt() + r.cap(3).toInt() / 60.0 + (r.cap(4) + QString(".") + r.cap(6)).toDouble() / 3600.0; @@ -147,15 +165,14 @@ bool parseGpsText(const QString& gps_text, double *latitude, double *longitude) eastWest = 6; break; case DECIMAL: - default: + default: *latitude = (r.cap(2) + QString(".") + r.cap(3)).toDouble(); *longitude = (r.cap(5) + QString(".") + r.cap(6)).toDouble(); - eastWest = 4; break; } - if (r.cap(1) == "S" || r.cap(1) == tr("S") || r.cap(1) == "-") + if (r.cap(northSouth) == "S" || r.cap(northSouth) == tr("S") || r.cap(northSouth) == "-") *latitude *= -1.0; - if (r.cap(eastWest) == "W" || r.cap(5) == tr("W") || r.cap(5) == "-") + if (r.cap(eastWest) == "W" || r.cap(eastWest) == tr("W") || r.cap(eastWest) == "-") *longitude *= -1.0; // qDebug("%s -> %8.5f / %8.5f", gps_text.toLocal8Bit().data(), *latitude, *longitude); return true; @@ -200,3 +217,27 @@ QList< int > getDivesInTrip ( dive_trip_t* trip ) } return ret; } + +// we need this to be uniq, but also make sure +// it doesn't change during the life time of a Subsurface session +// oh, and it has no meaning whatsoever - that's why we have the +// silly initial number and increment by 3 :-) +int getUniqID(struct dive *d) +{ + static QSet<int> ids; + static int maxId = 83529; + + int id = d->id; + if (id) { + if (!ids.contains(id)) { + qDebug() << "WTF - only I am allowed to create IDs"; + ids.insert(id); + } + return id; + } + maxId += 3; + id = maxId; + Q_ASSERT(!ids.contains(id)); + ids.insert(id); + return id; +} diff --git a/subsurface.pro b/subsurface.pro index 5d51d242a..d230b3885 100644 --- a/subsurface.pro +++ b/subsurface.pro @@ -54,9 +54,10 @@ HEADERS = \ subsurfacestartup.h \ uemis.h \ webservice.h \ - qt-ui/csvimportdialog.h \ + qt-ui/divelogimportdialog.h \ qt-ui/tagwidget.h \ - qt-ui/groupedlineedit.h + qt-ui/groupedlineedit.h \ + qt-ui/usermanual.h SOURCES = \ deco.c \ @@ -103,9 +104,10 @@ SOURCES = \ time.c \ uemis.c \ uemis-downloader.c \ - qt-ui/csvimportdialog.cpp \ + qt-ui/divelogimportdialog.cpp \ qt-ui/tagwidget.cpp \ - qt-ui/groupedlineedit.cpp + qt-ui/groupedlineedit.cpp \ + qt-ui/usermanual.cpp linux*: SOURCES += linux.c mac: SOURCES += macos.c @@ -124,7 +126,8 @@ FORMS = \ qt-ui/shifttimes.ui \ qt-ui/webservices.ui \ qt-ui/tableview.ui \ - qt-ui/csvimportdialog.ui + qt-ui/divelogimportdialog.ui \ + qt-ui/usermanual.ui RESOURCES = subsurface.qrc |