diff options
Diffstat (limited to 'qt-ui')
61 files changed, 3244 insertions, 1098 deletions
diff --git a/qt-ui/about.cpp b/qt-ui/about.cpp index 361031599..203357010 100644 --- a/qt-ui/about.cpp +++ b/qt-ui/about.cpp @@ -1,5 +1,5 @@ #include "about.h" -#include "ssrf-version.h" +#include "version.h" #include <QDesktopServices> #include <QUrl> #include <QShortcut> @@ -9,7 +9,7 @@ SubsurfaceAbout::SubsurfaceAbout(QWidget *parent, Qt::WindowFlags f) : QDialog(p ui.setupUi(this); setWindowModality(Qt::ApplicationModal); - QString versionString(GIT_VERSION_STRING); + QString versionString(subsurface_git_version()); QStringList readableVersions = QStringList() << "4.3.950" << "4.4 Beta 1" << "4.3.960" << "4.4 Beta 2" << "4.3.970" << "4.4 Beta 3"; diff --git a/qt-ui/completionmodels.cpp b/qt-ui/completionmodels.cpp index fd3cc7504..f2e70afd1 100644 --- a/qt-ui/completionmodels.cpp +++ b/qt-ui/completionmodels.cpp @@ -40,9 +40,21 @@ CREATE_CSV_UPDATE_METHOD(BuddyCompletionModel, buddy); CREATE_CSV_UPDATE_METHOD(DiveMasterCompletionModel, divemaster); -CREATE_UPDATE_METHOD(LocationCompletionModel, location); CREATE_UPDATE_METHOD(SuitCompletionModel, suit); +void LocationCompletionModel::updateModel() +{ + QStringList list; + struct dive_site *ds; + int i = 0; + for_each_dive_site(i, ds) { + if (!list.contains(ds->name)) + list.append(ds->name); + } + std::sort(list.begin(), list.end()); + setStringList(list); +} + void TagCompletionModel::updateModel() { if (g_tag_list == NULL) diff --git a/qt-ui/configuredivecomputerdialog.cpp b/qt-ui/configuredivecomputerdialog.cpp index 95bc0f882..172a1a480 100644 --- a/qt-ui/configuredivecomputerdialog.cpp +++ b/qt-ui/configuredivecomputerdialog.cpp @@ -2,6 +2,8 @@ #include "helpers.h" #include "mainwindow.h" +#include "display.h" + #include <QFileDialog> #include <QMessageBox> #include <QSettings> diff --git a/qt-ui/divecomponentselection.ui b/qt-ui/divecomponentselection.ui index dbd0839ba..7eade039b 100644 --- a/qt-ui/divecomponentselection.ui +++ b/qt-ui/divecomponentselection.ui @@ -9,8 +9,8 @@ <rect> <x>0</x> <y>0</y> - <width>308</width> - <height>263</height> + <width>401</width> + <height>317</height> </rect> </property> <property name="sizePolicy"> @@ -41,9 +41,9 @@ </property> <layout class="QGridLayout" name="gridLayout"> <item row="0" column="0"> - <widget class="QCheckBox" name="location"> + <widget class="QCheckBox" name="divesite"> <property name="text"> - <string>Location</string> + <string>Dive site</string> </property> </widget> </item> @@ -54,34 +54,6 @@ </property> </widget> </item> - <item row="1" column="0"> - <widget class="QCheckBox" name="gps"> - <property name="text"> - <string>GPS coordinates</string> - </property> - </widget> - </item> - <item row="2" column="0"> - <widget class="QCheckBox" name="divemaster"> - <property name="text"> - <string>Divemaster</string> - </property> - </widget> - </item> - <item row="3" column="0"> - <widget class="QCheckBox" name="buddy"> - <property name="text"> - <string>Buddy</string> - </property> - </widget> - </item> - <item row="4" column="0"> - <widget class="QCheckBox" name="rating"> - <property name="text"> - <string>Rating</string> - </property> - </widget> - </item> <item row="5" column="0"> <widget class="QCheckBox" name="visibility"> <property name="text"> @@ -117,6 +89,27 @@ </property> </widget> </item> + <item row="1" column="0"> + <widget class="QCheckBox" name="divemaster"> + <property name="text"> + <string>Divemaster</string> + </property> + </widget> + </item> + <item row="2" column="0"> + <widget class="QCheckBox" name="buddy"> + <property name="text"> + <string>Buddy</string> + </property> + </widget> + </item> + <item row="3" column="0"> + <widget class="QCheckBox" name="rating"> + <property name="text"> + <string>Rating</string> + </property> + </widget> + </item> </layout> </widget> </item> diff --git a/qt-ui/divecomputermanagementdialog.cpp b/qt-ui/divecomputermanagementdialog.cpp index 742facdcb..552f6058f 100644 --- a/qt-ui/divecomputermanagementdialog.cpp +++ b/qt-ui/divecomputermanagementdialog.cpp @@ -1,6 +1,7 @@ #include "divecomputermanagementdialog.h" #include "mainwindow.h" #include "helpers.h" +#include "models.h" #include <QMessageBox> #include <QShortcut> diff --git a/qt-ui/divelistview.cpp b/qt-ui/divelistview.cpp index d4e744237..16033dca9 100644 --- a/qt-ui/divelistview.cpp +++ b/qt-ui/divelistview.cpp @@ -4,13 +4,22 @@ * classes for the divelist of Subsurface * */ -#include "divelistview.h" #include "filtermodels.h" #include "modeldelegates.h" #include "mainwindow.h" +#include "divepicturewidget.h" +#include "display.h" +#include <unistd.h> #include <QSettings> +#include <QKeyEvent> #include <QFileDialog> +#include <QNetworkAccessManager> +#include <QNetworkReply> +#include <QStandardPaths> +#include <QMessageBox> #include "qthelper.h" +#include "undocommands.h" +#include "divelistview.h" // # Date Rtg Dpth Dur Tmp Wght Suit Cyl Gas SAC OTU CNS Loc static int defaultWidth[] = { 70, 140, 90, 50, 50, 50, 50, 70, 50, 50, 70, 50, 50, 500}; @@ -30,6 +39,7 @@ DiveListView::DiveListView(QWidget *parent) : QTreeView(parent), mouseClickSelec setSortingEnabled(false); setContextMenuPolicy(Qt::DefaultContextMenu); + setSelectionMode(ExtendedSelection); header()->setContextMenuPolicy(Qt::ActionsContextMenu); const QFontMetrics metrics(defaultModelFont()); @@ -39,7 +49,7 @@ DiveListView::DiveListView(QWidget *parent) : QTreeView(parent), mouseClickSelec // Fixes for the layout needed for mac #ifdef Q_OS_MAC int ht = metrics.height(); - header()->setMinimumHeight(ht + 10); + header()->setMinimumHeight(ht + 4); #endif // TODO FIXME we need this to get the header names @@ -84,6 +94,8 @@ DiveListView::DiveListView(QWidget *parent) : QTreeView(parent), mouseClickSelec header()->setStretchLastSection(true); + + installEventFilter(this); } DiveListView::~DiveListView() @@ -175,8 +187,13 @@ void DiveListView::rememberSelection() if (index.column() != 0) // We only care about the dives, so, let's stick to rows and discard columns. continue; struct dive *d = (struct dive *)index.data(DiveTripModel::DIVE_ROLE).value<void *>(); - if (d) + if (d) { selectedDives.insert(d->divetrip, get_divenr(d)); + } else { + struct dive_trip *t = (struct dive_trip *)index.data(DiveTripModel::TRIP_ROLE).value<void *>(); + if (t) + selectedDives.insert(t, -1); + } } selectionSaved = true; } @@ -195,8 +212,10 @@ void DiveListView::restoreSelection() QList<int> selectedDivesOnTrip = selectedDives.values(trip); // Only select trip if all of its dives were selected - if (trip != NULL && divesOnTrip.count() == selectedDivesOnTrip.count()) + if(selectedDivesOnTrip.contains(-1)) { selectTrip(trip); + selectedDivesOnTrip.removeAll(-1); + } selectDives(selectedDivesOnTrip); } } @@ -343,6 +362,10 @@ bool DiveListView::eventFilter(QObject *, QEvent *event) if (event->type() != QEvent::KeyPress) return false; QKeyEvent *keyEv = static_cast<QKeyEvent *>(event); + if (keyEv->key() == Qt::Key_Delete) { + contextMenuIndex = currentIndex(); + deleteDive(); + } if (keyEv->key() != Qt::Key_Escape) return false; return true; @@ -492,6 +515,8 @@ void DiveListView::toggleColumnVisibilityByIndex() void DiveListView::currentChanged(const QModelIndex ¤t, const QModelIndex &previous) { + if (!isVisible()) + return; if (!current.isValid()) return; scrollTo(current); @@ -542,15 +567,32 @@ void DiveListView::selectionChanged(const QItemSelection &selected, const QItemS Q_EMIT currentDiveChanged(selected_dive); } -static bool can_merge(const struct dive *a, const struct dive *b) +enum asked_user {NOTYET, MERGE, DONTMERGE}; + +static bool can_merge(const struct dive *a, const struct dive *b, enum asked_user *have_asked) { if (!a || !b) return false; if (a->when > b->when) return false; /* Don't merge dives if there's more than half an hour between them */ - if (a->when + a->duration.seconds + 30 * 60 < b->when) - return false; + if (a->when + a->duration.seconds + 30 * 60 < b->when) { + if (*have_asked == NOTYET) { + if (QMessageBox::warning(MainWindow::instance(), + MainWindow::instance()->tr("Warning"), + MainWindow::instance()->tr("Trying to merge dives with %1min interval in between").arg( + (b->when - a->when - a->duration.seconds) / 60), + QMessageBox::Ok | QMessageBox::Cancel) == QMessageBox::Cancel) { + *have_asked = DONTMERGE; + return false; + } else { + *have_asked = MERGE; + return true; + } + } else { + return *have_asked == MERGE ? true : false; + } + } return true; } @@ -558,10 +600,11 @@ void DiveListView::mergeDives() { int i; struct dive *dive, *maindive = NULL; + enum asked_user have_asked = NOTYET; for_each_dive (i, dive) { if (dive->selected) { - if (!can_merge(maindive, dive)) { + if (!can_merge(maindive, dive, &have_asked)) { maindive = dive; } else { maindive = merge_two_dives(maindive, dive); @@ -611,10 +654,14 @@ void DiveListView::removeFromTrip() //TODO: move this to C-code. int i; struct dive *d; + QMap<struct dive*, dive_trip*> divesToRemove; for_each_dive (i, d) { if (d->selected) - remove_dive_from_trip(d, false); + divesToRemove.insert(d, d->divetrip); } + UndoRemoveDivesFromTrip *undoCommand = new UndoRemoveDivesFromTrip(divesToRemove); + MainWindow::instance()->undoStack->push(undoCommand); + rememberSelection(); reload(currentLayout, false); fixMessyQtModelBehaviour(); @@ -721,13 +768,19 @@ void DiveListView::deleteDive() // so instead of using the for_each_dive macro I'm using an explicit for loop // to make this easier to understand int lastDiveNr = -1; + QList<struct dive*> deletedDives; //a list of all deleted dives to be stored in the undo command for_each_dive (i, d) { if (!d->selected) continue; + struct dive* undo_entry = alloc_dive(); + copy_dive(get_dive(i), undo_entry); + deletedDives.append(undo_entry); delete_single_dive(i); i--; // so the next dive isn't skipped... it's now #i lastDiveNr = i; } + UndoDeleteDive *undoEntry = new UndoDeleteDive(deletedDives); + MainWindow::instance()->undoStack->push(undoEntry); if (amount_selected == 0) { MainWindow::instance()->cleanUpEmpty(); } @@ -765,9 +818,32 @@ void DiveListView::contextMenuEvent(QContextMenuEvent *event) dive_trip_t *trip = (dive_trip_t *)contextMenuIndex.data(DiveTripModel::TRIP_ROLE).value<void *>(); QMenu popup(this); if (currentLayout == DiveTripModel::TREE) { - popup.addAction(tr("Expand all"), this, SLOT(expandAll())); - popup.addAction(tr("Collapse all"), this, SLOT(collapseAll())); - collapseAction = popup.addAction(tr("Collapse others"), this, SLOT(collapseAll())); + // verify if there is a node that`s not expanded. + bool needs_expand = false; + bool needs_collapse = false; + uint expanded_nodes = 0; + for(int i = 0, end = model()->rowCount(); i < end; i++) { + QModelIndex idx = model()->index(i, 0); + if (idx.data(DiveTripModel::DIVE_ROLE).value<void *>()) + continue; + + if (!isExpanded(idx)) { + needs_expand = true; + } else { + needs_collapse = true; + expanded_nodes ++; + } + } + if (needs_expand) + popup.addAction(tr("Expand all"), this, SLOT(expandAll())); + if (needs_collapse) + popup.addAction(tr("Collapse all"), this, SLOT(collapseAll())); + + // verify if there`s a need for collapse others + if (expanded_nodes > 1) + collapseAction = popup.addAction(tr("Collapse others"), this, SLOT(collapseAll())); + + if (d) { popup.addAction(tr("Remove dive(s) from trip"), this, SLOT(removeFromTrip())); popup.addAction(tr("Create new trip above"), this, SLOT(newTripAbove())); @@ -804,8 +880,9 @@ void DiveListView::contextMenuEvent(QContextMenuEvent *event) popup.addAction(tr("Merge selected dives"), this, SLOT(mergeDives())); if (amount_selected >= 1) { popup.addAction(tr("Renumber dive(s)"), this, SLOT(renumberDives())); - popup.addAction(tr("Shift times"), this, SLOT(shiftTimes())); - popup.addAction(tr("Load images"), this, SLOT(loadImages())); + popup.addAction(tr("Shift dive times"), this, SLOT(shiftTimes())); + popup.addAction(tr("Load image(s) from file(s)"), this, SLOT(loadImages())); + popup.addAction(tr("Load image(s) from web"), this, SLOT(loadWebImages())); } // "collapse all" really closes all trips, @@ -831,11 +908,16 @@ void DiveListView::loadImages() QStringList fileNames = QFileDialog::getOpenFileNames(this, tr("Open image files"), lastUsedImageDir(), tr("Image files (*.jpg *.jpeg *.pnm *.tif *.tiff)")); if (fileNames.isEmpty()) return; - updateLastUsedImageDir(QFileInfo(fileNames[0]).dir().path()); - ShiftImageTimesDialog shiftDialog(this); + matchImagesToDives(fileNames); +} + +void DiveListView::matchImagesToDives(QStringList fileNames) +{ + ShiftImageTimesDialog shiftDialog(this, fileNames); shiftDialog.setOffset(lastImageTimeOffset()); - shiftDialog.exec(); + if (!shiftDialog.exec()) + return; updateLastImageTimeOffset(shiftDialog.amount()); Q_FOREACH (const QString &fileName, fileNames) { @@ -844,7 +926,7 @@ void DiveListView::loadImages() for_each_dive (j, dive) { if (!dive->selected) continue; - dive_create_picture(dive, qstrdup(fileName.toUtf8().data()), shiftDialog.amount()); + dive_create_picture(dive, copy_string(fileName.toUtf8().data()), shiftDialog.amount()); } } @@ -853,6 +935,60 @@ void DiveListView::loadImages() DivePictureModel::instance()->updateDivePictures(); } +void DiveListView::loadWebImages() +{ + URLDialog urlDialog(this); + if (!urlDialog.exec()) + return; + loadImageFromURL(QUrl::fromUserInput(urlDialog.url())); + +} + +void DiveListView::loadImageFromURL(QUrl url) +{ + if (url.isValid()) { + QEventLoop loop; + QNetworkRequest request(url); + QNetworkReply *reply = manager.get(request); + while (reply->isRunning()) { + loop.processEvents(); + sleep(1); + } + QByteArray imageData = reply->readAll(); + + QImage image = QImage(); + image.loadFromData(imageData); + if (image.isNull()) + // If this is not an image, maybe it's an html file and Miika can provide some xslr magic to extract images. + // In this case we would call the function recursively on the list of image source urls; + return; + + // Since we already downloaded the image we can cache it as well. + QCryptographicHash hash(QCryptographicHash::Sha1); + hash.addData(imageData); + QString path = QStandardPaths::standardLocations(QStandardPaths::CacheLocation).first(); + QDir dir(path); + if (!dir.exists()) + dir.mkpath(path); + QFile imageFile(path.append("/").append(hash.result().toHex())); + if (imageFile.open(QIODevice::WriteOnly)) { + QDataStream stream(&imageFile); + stream.writeRawData(imageData.data(), imageData.length()); + imageFile.waitForBytesWritten(-1); + imageFile.close(); + add_hash(imageFile.fileName(), hash.result()); + struct picture picture; + picture.hash = NULL; + picture.filename = strdup(url.toString().toUtf8().data()); + learnHash(&picture, hash.result()); + matchImagesToDives(QStringList(url.toString())); + } + } + + +} + + QString DiveListView::lastUsedImageDir() { QSettings settings; diff --git a/qt-ui/divelistview.h b/qt-ui/divelistview.h index a6522fa9a..6e9a18215 100644 --- a/qt-ui/divelistview.h +++ b/qt-ui/divelistview.h @@ -13,6 +13,7 @@ #include <QTreeView> #include <QLineEdit> +#include <QNetworkAccessManager> #include "models.h" class DiveListView : public QTreeView { @@ -51,6 +52,7 @@ slots: void renumberDives(); void shiftTimes(); void loadImages(); + void loadWebImages(); static QString lastUsedImageDir(); signals: @@ -78,6 +80,9 @@ private: void updateLastImageTimeOffset(int offset); int lastImageTimeOffset(); void addToTrip(int delta); + void matchImagesToDives(QStringList fileNames); + void loadImageFromURL(QUrl url); + QNetworkAccessManager manager; }; #endif // DIVELISTVIEW_H diff --git a/qt-ui/divelogexportdialog.cpp b/qt-ui/divelogexportdialog.cpp index 43c41550f..12a8c320f 100644 --- a/qt-ui/divelogexportdialog.cpp +++ b/qt-ui/divelogexportdialog.cpp @@ -9,6 +9,7 @@ #include "subsurfacewebservices.h" #include "worldmap-save.h" #include "save-html.h" +#include "mainwindow.h" #define GET_UNIT(name, field, f, t) \ v = settings.value(QString(name)); \ @@ -55,6 +56,9 @@ DiveLogExportDialog::DiveLogExportDialog(QWidget *parent) : QDialog(parent), if (settings.contains("listOnly")) { ui->exportListOnly->setChecked(settings.value("listOnly").toBool()); } + if (settings.contains("exportPhotos")) { + ui->exportPhotos->setChecked(settings.value("exportPhotos").toBool()); + } settings.endGroup(); } @@ -104,8 +108,12 @@ void DiveLogExportDialog::exportHtmlInit(const QString &filename) QString json_settings = exportFiles + QDir::separator() + "settings.js"; QString translation = exportFiles + QDir::separator() + "translation.js"; QString stat_file = exportFiles + QDir::separator() + "stat.js"; - QString photos_directory = exportFiles + QDir::separator() + "photos" + QDir::separator(); - mainDir.mkdir(photos_directory); + + QString photos_directory; + if (ui->exportPhotos->isChecked()) { + photos_directory = exportFiles + QDir::separator() + "photos" + QDir::separator(); + mainDir.mkdir(photos_directory); + } exportFiles += "/"; exportHTMLsettings(json_settings); @@ -142,6 +150,7 @@ void DiveLogExportDialog::exportHTMLsettings(const QString &filename) settings.setValue("subsurfaceNumbers", ui->exportSubsurfaceNumber->isChecked()); settings.setValue("yearlyStatistics", ui->exportStatistics->isChecked()); settings.setValue("listOnly", ui->exportListOnly->isChecked()); + settings.setValue("exportPhotos", ui->exportPhotos->isChecked()); settings.endGroup(); QString fontSize = ui->fontSizeSelection->currentText(); @@ -312,8 +321,11 @@ void DiveLogExportDialog::on_buttonBox_accepted() settings.setValue("LastDir", fileInfo.dir().path()); settings.endGroup(); // the non XSLT exports are called directly above, the XSLT based ons are called here - if (!stylesheet.isEmpty()) + if (!stylesheet.isEmpty()) { future = QtConcurrent::run(export_dives_xslt, filename.toUtf8(), ui->exportSelected->isChecked(), ui->CSVUnits_2->currentIndex(), stylesheet.toUtf8()); + MainWindow::instance()->getNotificationWidget()->showNotification(tr("Please Wait, Exporting..."), KMessageWidget::Information); + MainWindow::instance()->getNotificationWidget()->setFuture(future); + } } } diff --git a/qt-ui/divelogexportdialog.ui b/qt-ui/divelogexportdialog.ui index 7514a6551..1f3675ef4 100644 --- a/qt-ui/divelogexportdialog.ui +++ b/qt-ui/divelogexportdialog.ui @@ -7,7 +7,7 @@ <x>0</x> <y>0</y> <width>507</width> - <height>423</height> + <height>468</height> </rect> </property> <property name="windowTitle"> @@ -233,9 +233,9 @@ </layout> </widget> <widget class="QGroupBox" name="groupBox"> - <property name="enabled"> - <bool>false</bool> - </property> + <property name="enabled"> + <bool>false</bool> + </property> <property name="geometry"> <rect> <x>0</x> @@ -338,13 +338,23 @@ </attribute> </widget> </item> - <item row="2" column="0"> + <item row="3" column="0"> <widget class="QCheckBox" name="exportListOnly"> <property name="text"> <string>Export list only</string> </property> </widget> </item> + <item row="2" column="0"> + <widget class="QCheckBox" name="exportPhotos"> + <property name="text"> + <string>Export photos</string> + </property> + <property name="checked"> + <bool>true</bool> + </property> + </widget> + </item> </layout> </widget> </item> @@ -363,6 +373,9 @@ <bool>false</bool> </property> <layout class="QFormLayout" name="formLayout"> + <property name="fieldGrowthPolicy"> + <enum>QFormLayout::AllNonFixedFieldsGrow</enum> + </property> <item row="0" column="0"> <widget class="QLabel" name="fontLabel"> <property name="text"> @@ -513,20 +526,40 @@ </hints> </connection> <connection> - <sender>exportCSV</sender> - <signal>toggled(bool)</signal> - <receiver>groupBox</receiver> - <slot>setEnabled(bool)</slot> + <sender>exportCSV</sender> + <signal>toggled(bool)</signal> + <receiver>groupBox</receiver> + <slot>setEnabled(bool)</slot> + <hints> + <hint type="sourcelabel"> + <x>20</x> + <y>20</y> + </hint> + <hint type="destinationlabel"> + <x>20</x> + <y>20</y> + </hint> + </hints> </connection> <connection> - <sender>exportCSVDetails</sender> - <signal>toggled(bool)</signal> - <receiver>groupBox</receiver> - <slot>setEnabled(bool)</slot> + <sender>exportCSVDetails</sender> + <signal>toggled(bool)</signal> + <receiver>groupBox</receiver> + <slot>setEnabled(bool)</slot> + <hints> + <hint type="sourcelabel"> + <x>20</x> + <y>20</y> + </hint> + <hint type="destinationlabel"> + <x>20</x> + <y>20</y> + </hint> + </hints> </connection> </connections> <buttongroups> - <buttongroup name="exportGroup"/> <buttongroup name="buttonGroup"/> + <buttongroup name="exportGroup"/> </buttongroups> </ui> diff --git a/qt-ui/divelogimportdialog.cpp b/qt-ui/divelogimportdialog.cpp index bb4701ce3..409064833 100644 --- a/qt-ui/divelogimportdialog.cpp +++ b/qt-ui/divelogimportdialog.cpp @@ -1,5 +1,6 @@ #include "divelogimportdialog.h" #include "mainwindow.h" +#include "color.h" #include "ui_divelogimportdialog.h" #include <QShortcut> #include <QDrag> @@ -23,7 +24,7 @@ const DiveLogImportDialog::CSVAppConfig DiveLogImportDialog::CSVApps[CSVAPPS] = ColumnNameProvider::ColumnNameProvider(QObject *parent) : QAbstractListModel(parent) { columnNames << tr("Dive #") << tr("Date") << tr("Time") << tr("Duration") << tr("Location") << tr("GPS") << tr("Weight") << tr("Cyl. size") << tr("Start pressure") << - tr("End pressure") << tr("Max. depth") << tr("Avg. depth") << tr("Divemaster") << tr("Buddy") << tr("Notes") << tr("Tags") << tr("Air temp.") << tr("Water temp.") << + tr("End pressure") << tr("Max. depth") << tr("Avg. depth") << tr("Divemaster") << tr("Buddy") << tr("Suit") << tr("Notes") << tr("Tags") << tr("Air temp.") << tr("Water temp.") << tr("Oâ‚‚") << tr("He") << tr("Sample time") << tr("Sample depth") << tr("Sample temperature") << tr("Sample pOâ‚‚") << tr("Sample CNS") << tr("Sample NDL") << tr("Sample TTS") << tr("Sample stopdepth") << tr("Sample pressure"); } @@ -315,6 +316,7 @@ DiveLogImportDialog::DiveLogImportDialog(QStringList fn, QWidget *parent) : QDia ui->setupUi(this); fileNames = fn; column = 0; + delta = "0"; /* Add indexes of XSLTs requiring special handling to the list */ specialCSV << 3; @@ -374,7 +376,53 @@ void DiveLogImportDialog::loadFileContents(int value, whatChanged triggeredBy) QString firstLine = f.readLine(); if (firstLine.contains("SEABEAR")) { seabear = true; - firstLine = "Sample time;Sample depth;Sample NDL;Sample TTS;Sample stopdepth;Sample temperature;Sample pressure"; + + /* + * Parse header - currently only interested in sample + * interval, or if we have old format (if interval value + * is missing from the header). + */ + + while ((firstLine = f.readLine()).length() > 3 && !f.atEnd()) { + if (firstLine.contains("//Log interval: ")) + delta = firstLine.remove(QString::fromLatin1("//Log interval: ")).trimmed(); + } + + /* + * Parse CSV fields + * The pO2 values from CCR diving are ignored later on. + */ + + firstLine = f.readLine().trimmed(); + + currColumns = firstLine.split(';'); + Q_FOREACH (QString columnText, currColumns) { + if (columnText == "Time") { + headers.append("Sample time"); + } else if (columnText == "Depth") { + headers.append("Sample depth"); + } else if (columnText == "Temperature") { + headers.append("Sample temperature"); + } else if (columnText == "NDT") { + headers.append("Sample NDL"); + } else if (columnText == "TTS") { + headers.append("Sample TTS"); + } else if (columnText == "pO2_1") { + headers.append("Sample pO2_1"); + } else if (columnText == "pO2_2") { + headers.append("Sample pO2_2"); + } else if (columnText == "pO2_3") { + headers.append("Sample pO2_3"); + } else if (columnText == "Ceiling") { + headers.append("Sample ceiling"); + } else { + // We do not know about this value + qDebug() << "Seabear import found an un-handled field: " << columnText; + headers.append(""); + } + } + + firstLine = headers.join(";"); blockSignals(true); ui->knownImports->setCurrentText("Seabear CSV"); blockSignals(false); @@ -513,7 +561,7 @@ void DiveLogImportDialog::loadFileContents(int value, whatChanged triggeredBy) } while (rows < 10 && !f.atEnd()) { - QString currLine = f.readLine(); + QString currLine = f.readLine().trimmed(); currColumns = currLine.split(separator); fileColumns.append(currColumns); rows += 1; @@ -526,6 +574,7 @@ void DiveLogImportDialog::loadFileContents(int value, whatChanged triggeredBy) void DiveLogImportDialog::on_buttonBox_accepted() { + imported_via_xslt = true; QStringList r = resultModel->result(); if (ui->knownImports->currentText() != "Manual import") { for (int i = 0; i < fileNames.size(); ++i) { @@ -542,10 +591,12 @@ void DiveLogImportDialog::on_buttonBox_accepted() r.indexOf(tr("Sample pressure")), ui->CSVSeparator->currentIndex(), specialCSV.contains(ui->knownImports->currentIndex()) ? CSVApps[ui->knownImports->currentIndex()].name.toUtf8().data() : "csv", - ui->CSVUnits->currentIndex() - ) < 0) + ui->CSVUnits->currentIndex(), + delta.toUtf8().data() + ) < 0) { + imported_via_xslt = false; return; - + } // Seabear CSV stores NDL and TTS in Minutes, not seconds struct dive *dive = dive_table.dives[dive_table.nr - 1]; for(int s_nr = 0 ; s_nr <= dive->dc.samples ; s_nr++) { @@ -585,9 +636,10 @@ void DiveLogImportDialog::on_buttonBox_accepted() r.indexOf(tr("Location")), r.indexOf(tr("GPS")), r.indexOf(tr("Max. depth")), - r.indexOf(tr("Mean depth")), + r.indexOf(tr("Avg. depth")), r.indexOf(tr("Divemaster")), r.indexOf(tr("Buddy")), + r.indexOf(tr("Suit")), r.indexOf(tr("Notes")), r.indexOf(tr("Weight")), r.indexOf(tr("Tags")), @@ -618,6 +670,7 @@ void DiveLogImportDialog::on_buttonBox_accepted() } process_dives(true, false); MainWindow::instance()->refreshDisplay(); + imported_via_xslt = false; } TagDragDelegate::TagDragDelegate(QObject *parent) : QStyledItemDelegate(parent) diff --git a/qt-ui/divelogimportdialog.h b/qt-ui/divelogimportdialog.h index 9281b2b10..e7d068a81 100644 --- a/qt-ui/divelogimportdialog.h +++ b/qt-ui/divelogimportdialog.h @@ -95,6 +95,7 @@ private: QList<int> specialCSV; int column; ColumnNameResult *resultModel; + QString delta; struct CSVAppConfig { QString name; diff --git a/qt-ui/divepicturewidget.cpp b/qt-ui/divepicturewidget.cpp index 92695b6a6..8c1cb3571 100644 --- a/qt-ui/divepicturewidget.cpp +++ b/qt-ui/divepicturewidget.cpp @@ -2,8 +2,90 @@ #include "metrics.h" #include "dive.h" #include "divelist.h" +#include <unistd.h> #include <QtConcurrentMap> +#include <QtConcurrentRun> +#include <QFuture> #include <QDir> +#include <QCryptographicHash> +#include <QNetworkAccessManager> +#include <QNetworkReply> +#include <mainwindow.h> +#include <qthelper.h> +#include <QStandardPaths> + +void loadPicuture(struct picture *picture) +{ + ImageDownloader download(picture); + download.load(); +} + +SHashedImage::SHashedImage(struct picture *picture) : QImage() +{ + QUrl url = QUrl::fromUserInput(QString(picture->filename)); + if(url.isLocalFile()) + load(url.toLocalFile()); + if (isNull()) { + // Hash lookup. + load(fileFromHash(picture->hash)); + if (!isNull()) { + QtConcurrent::run(updateHash, picture); + } else { + QtConcurrent::run(loadPicuture, picture); + } + } else { + QByteArray hash = hashFile(url.toLocalFile()); + free(picture->hash); + picture->hash = strdup(hash.toHex().data()); + } +} + +ImageDownloader::ImageDownloader(struct picture *pic) +{ + picture = pic; +} + +void ImageDownloader::load(){ + QUrl url = QUrl::fromUserInput(QString(picture->filename)); + if (url.isValid()) { + QEventLoop loop; + QNetworkRequest request(url); + connect(&manager, SIGNAL(finished(QNetworkReply *)), this, SLOT(saveImage(QNetworkReply *))); + QNetworkReply *reply = manager.get(request); + while (reply->isRunning()) { + loop.processEvents(); + sleep(1); + } + } + +} + +void ImageDownloader::saveImage(QNetworkReply *reply) +{ + QByteArray imageData = reply->readAll(); + QImage image = QImage(); + image.loadFromData(imageData); + if (image.isNull()) + return; + QCryptographicHash hash(QCryptographicHash::Sha1); + hash.addData(imageData); + QString path = QStandardPaths::standardLocations(QStandardPaths::CacheLocation).first(); + QDir dir(path); + if (!dir.exists()) + dir.mkpath(path); + QFile imageFile(path.append("/").append(hash.result().toHex())); + if (imageFile.open(QIODevice::WriteOnly)) { + QDataStream stream(&imageFile); + stream.writeRawData(imageData.data(), imageData.length()); + imageFile.waitForBytesWritten(-1); + imageFile.close(); + add_hash(imageFile.fileName(), hash.result()); + learnHash(picture, hash.result()); + DivePictureModel::instance()->updateDivePictures(); + } + reply->manager()->deleteLater(); + reply->deleteLater(); +} DivePictureModel *DivePictureModel::instance() { @@ -15,25 +97,36 @@ DivePictureModel::DivePictureModel() : numberOfPictures(0) { } -typedef QPair<QString, QImage> SPixmap; -typedef QList<SPixmap> SPixmapList; +typedef struct picture *picturepointer; +typedef QPair<picturepointer, QImage> SPixmap; +typedef QList<struct picture *> SPictureList; -SPixmap scaleImages(const QString &s) +SPixmap scaleImages(picturepointer picture) { static QHash <QString, QImage > cache; SPixmap ret; - ret.first = s; - if (cache.contains(s)) { - ret.second = cache.value(s); + ret.first = picture; + if (cache.contains(picture->filename) && !cache.value(picture->filename).isNull()) { + ret.second = cache.value(picture->filename); } else { int dim = defaultIconMetrics().sz_pic; - QImage p = QImage(s).scaled(dim, dim, Qt::KeepAspectRatio); - cache.insert(s, p); + QImage p = SHashedImage(picture); + if(!p.isNull()) + p = p.scaled(dim, dim, Qt::KeepAspectRatio); + cache.insert(picture->filename, p); ret.second = p; } return ret; } +void DivePictureModel::updateDivePicturesWhenDone(QList<QFuture<void> > futures) +{ + Q_FOREACH (QFuture<void> f, futures) { + f.waitForFinished(); + } + updateDivePictures(); +} + void DivePictureModel::updateDivePictures() { if (numberOfPictures != 0) { @@ -49,14 +142,15 @@ void DivePictureModel::updateDivePictures() } stringPixmapCache.clear(); - QStringList pictures; + SPictureList pictures; FOR_EACH_PICTURE_NON_PTR(displayed_dive) { stringPixmapCache[QString(picture->filename)].offsetSeconds = picture->offset.seconds; - pictures.push_back(QString(picture->filename)); + pictures.push_back(picture); } - Q_FOREACH (const SPixmap &pixmap, QtConcurrent::blockingMapped<SPixmapList>(pictures, scaleImages)) - stringPixmapCache[pixmap.first].image = pixmap.second; + QList<SPixmap> list = QtConcurrent::blockingMapped(pictures, scaleImages); + Q_FOREACH (const SPixmap &pixmap, list) + stringPixmapCache[pixmap.first->filename].image = pixmap.second; beginInsertRows(QModelIndex(), 0, numberOfPictures - 1); endInsertRows(); @@ -121,5 +215,5 @@ DivePictureWidget::DivePictureWidget(QWidget *parent) : QListView(parent) void DivePictureWidget::doubleClicked(const QModelIndex &index) { QString filePath = model()->data(index, Qt::DisplayPropertyRole).toString(); - emit photoDoubleClicked(filePath); + emit photoDoubleClicked(localFilePath(filePath)); } diff --git a/qt-ui/divepicturewidget.h b/qt-ui/divepicturewidget.h index aa524e1a6..2ce228daf 100644 --- a/qt-ui/divepicturewidget.h +++ b/qt-ui/divepicturewidget.h @@ -4,12 +4,33 @@ #include <QAbstractTableModel> #include <QListView> #include <QThread> +#include <QFuture> +#include <QNetworkReply> + +typedef QPair<QString, QByteArray> SHashedFilename; struct PhotoHelper { QImage image; int offsetSeconds; }; +class SHashedImage : public QImage { +public: + SHashedImage(struct picture *picture); +}; + +class ImageDownloader : public QObject { + Q_OBJECT; +public: + ImageDownloader(struct picture *picture); + void load(); +private: + struct picture *picture; + QNetworkAccessManager manager; +private slots: + void saveImage(QNetworkReply *reply); +}; + class DivePictureModel : public QAbstractTableModel { Q_OBJECT public: @@ -18,6 +39,7 @@ public: virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; virtual int rowCount(const QModelIndex &parent = QModelIndex()) const; void updateDivePictures(); + void updateDivePicturesWhenDone(QList<QFuture<void> >); void removePicture(const QString& fileUrl); private: diff --git a/qt-ui/diveplanner.cpp b/qt-ui/diveplanner.cpp index 7831cc6d9..c82bc0463 100644 --- a/qt-ui/diveplanner.cpp +++ b/qt-ui/diveplanner.cpp @@ -3,6 +3,8 @@ #include "mainwindow.h" #include "planner.h" #include "helpers.h" +#include "models.h" +#include "profile/profilewidget2.h" #include <QGraphicsSceneMouseEvent> #include <QMessageBox> @@ -42,15 +44,6 @@ void DivePlannerPointsModel::removeSelectedPoints(const QVector<int> &rows) QVector<int> v2 = rows; std::sort(v2.begin(), v2.end(), intLessThan); - /* - * If we end up having divepoints that are not within the dive - * profile, we need to just skip the removal to prevent - * crashing due to index out of range. - */ - - if (rowCount() >= divepoints.count()) - return; - beginRemoveRows(QModelIndex(), firstRow, rowCount() - 1); for (int i = v2.count() - 1; i >= 0; i--) { divepoints.remove(v2[i]); @@ -74,7 +67,7 @@ void DivePlannerPointsModel::createSimpleDive() if (!prefs.drop_stone_mode) plannerModel->addStop(M_OR_FT(15, 45), 1 * 60, &gas, 0, true); - plannerModel->addStop(M_OR_FT(15, 45), 40 * 60, &gas, 0, true); + plannerModel->addStop(M_OR_FT(15, 45), 20 * 60, &gas, 0, true); if (!isPlanner()) { plannerModel->addStop(M_OR_FT(5, 15), 42 * 60, &gas, 0, true); plannerModel->addStop(M_OR_FT(5, 15), 45 * 60, &gas, 0, true); @@ -99,10 +92,13 @@ void DivePlannerPointsModel::setupStartTime() void DivePlannerPointsModel::loadFromDive(dive *d) { + int depthsum = 0; + int samplecount = 0; bool oldRec = recalc; recalc = false; CylindersModel::instance()->updateDive(); duration_t lasttime = {}; + duration_t newtime = {}; struct gasmix gas; free_dps(&diveplan); diveplan.when = d->when; @@ -111,13 +107,27 @@ void DivePlannerPointsModel::loadFromDive(dive *d) // if it is we only add the manually entered samples as waypoints to the diveplan // otherwise we have to add all of them bool hasMarkedSamples = d->dc.sample[0].manually_entered; - for (int i = 0; i < d->dc.samples - 1; i++) { - const sample &s = d->dc.sample[i]; - if (s.time.seconds == 0 || (hasMarkedSamples && !s.manually_entered)) - continue; - get_gas_at_time(d, &d->dc, lasttime, &gas); - plannerModel->addStop(s.depth.mm, s.time.seconds, &gas, 0, true); - lasttime = s.time; + // if this dive has more than 100 samples (so it is probably a logged dive), + // average samples so we end up with a total of 100 samples. + int plansamples = d->dc.samples <= 100 ? d->dc.samples : 100; + int j = 0; + for (int i = 0; i < plansamples - 1; i++) { + while (j * plansamples <= i * d->dc.samples) { + const sample &s = d->dc.sample[j]; + if (s.time.seconds != 0 && (!hasMarkedSamples || s.manually_entered)) { + depthsum += s.depth.mm; + ++samplecount; + newtime = s.time; + } + j++; + } + if (samplecount) { + get_gas_at_time(d, &d->dc, lasttime, &gas); + plannerModel->addStop(depthsum / samplecount, newtime.seconds, &gas, 0, true); + lasttime = newtime; + depthsum = 0; + samplecount = 0; + } } recalc = oldRec; emitDataChanged(); @@ -231,7 +241,7 @@ void DiveHandler::changeGas() { QAction *action = qobject_cast<QAction *>(sender()); QModelIndex index = plannerModel->index(parentIndex(), DivePlannerPointsModel::GAS); - plannerModel->setData(index, action->text()); + plannerModel->gaschange(index.sibling(index.row() + 1, index.column()), action->text()); } void DiveHandler::mouseMoveEvent(QGraphicsSceneMouseEvent *event) @@ -383,6 +393,15 @@ void PlannerSettingsWidget::decoSacChanged(const double decosac) plannerModel->setDecoSac(decosac); } +void PlannerSettingsWidget::disableDecoElements(bool value) +{ + ui.lastStop->setDisabled(value); + ui.backgasBreaks->setDisabled(value); + ui.bottompo2->setDisabled(value); + ui.decopo2->setDisabled(value); + ui.reserve_gas->setDisabled(!value); +} + void DivePlannerWidget::printDecoPlan() { MainWindow::instance()->printPlan(); @@ -395,6 +414,14 @@ PlannerSettingsWidget::PlannerSettingsWidget(QWidget *parent, Qt::WindowFlags f) QSettings s; QStringList rebreater_modes; s.beginGroup("Planner"); + prefs.last_stop = s.value("last_stop", prefs.last_stop).toBool(); + prefs.verbatim_plan = s.value("verbatim_plan", prefs.verbatim_plan).toBool(); + prefs.display_duration = s.value("display_duration", prefs.display_duration).toBool(); + prefs.display_runtime = s.value("display_runtime", prefs.display_runtime).toBool(); + prefs.display_transitions = s.value("display_transitions", prefs.display_transitions).toBool(); + prefs.recreational_mode = s.value("recreational_mode", prefs.recreational_mode).toBool(); + prefs.safetystop = s.value("safetystop", prefs.safetystop).toBool(); + prefs.reserve_gas = s.value("reserve_gas", prefs.reserve_gas).toInt(); prefs.ascrate75 = s.value("ascrate75", prefs.ascrate75).toInt(); prefs.ascrate50 = s.value("ascrate50", prefs.ascrate50).toInt(); prefs.ascratestops = s.value("ascratestops", prefs.ascratestops).toInt(); @@ -411,6 +438,14 @@ PlannerSettingsWidget::PlannerSettingsWidget(QWidget *parent, Qt::WindowFlags f) s.endGroup(); updateUnitsUI(); + ui.lastStop->setChecked(prefs.last_stop); + ui.verbatim_plan->setChecked(prefs.verbatim_plan); + ui.display_duration->setChecked(prefs.display_duration); + ui.display_runtime->setChecked(prefs.display_runtime); + ui.display_transitions->setChecked(prefs.display_transitions); + ui.recreational_mode->setChecked(prefs.recreational_mode); + ui.safetystop->setChecked(prefs.safetystop); + ui.reserve_gas->setValue(prefs.reserve_gas / 1000); ui.bottompo2->setValue(prefs.bottompo2 / 1000.0); ui.decopo2->setValue(prefs.decopo2 / 1000.0); ui.backgasBreaks->setChecked(prefs.doo2breaks); @@ -424,6 +459,9 @@ PlannerSettingsWidget::PlannerSettingsWidget(QWidget *parent, Qt::WindowFlags f) connect(ui.display_duration, SIGNAL(toggled(bool)), plannerModel, SLOT(setDisplayDuration(bool))); connect(ui.display_runtime, SIGNAL(toggled(bool)), plannerModel, SLOT(setDisplayRuntime(bool))); connect(ui.display_transitions, SIGNAL(toggled(bool)), plannerModel, SLOT(setDisplayTransitions(bool))); + connect(ui.safetystop, SIGNAL(toggled(bool)), plannerModel, SLOT(setSafetyStop(bool))); + connect(ui.recreational_mode, SIGNAL(toggled(bool)), plannerModel, SLOT(setRecreationalMode(bool))); + connect(ui.reserve_gas, SIGNAL(valueChanged(int)), plannerModel, SLOT(setReserveGas(int))); connect(ui.ascRate75, SIGNAL(valueChanged(int)), this, SLOT(setAscRate75(int))); connect(ui.ascRate75, SIGNAL(valueChanged(int)), plannerModel, SLOT(emitDataChanged())); connect(ui.ascRate50, SIGNAL(valueChanged(int)), this, SLOT(setAscRate50(int))); @@ -445,6 +483,8 @@ PlannerSettingsWidget::PlannerSettingsWidget(QWidget *parent, Qt::WindowFlags f) connect(ui.gflow, SIGNAL(editingFinished()), plannerModel, SLOT(triggerGFLow())); connect(ui.backgasBreaks, SIGNAL(toggled(bool)), this, SLOT(setBackgasBreaks(bool))); connect(ui.rebreathermode, SIGNAL(currentIndexChanged(int)), plannerModel, SLOT(setRebreatherMode(int))); + connect(DivePlannerPointsModel::instance(), SIGNAL(recreationChanged(bool)), this, SLOT(disableDecoElements(bool))); + settingsChanged(); ui.gflow->setValue(prefs.gflow); ui.gfhigh->setValue(prefs.gfhigh); @@ -466,6 +506,14 @@ PlannerSettingsWidget::~PlannerSettingsWidget() { QSettings s; s.beginGroup("Planner"); + s.setValue("last_stop", prefs.last_stop); + s.setValue("verbatim_plan", prefs.verbatim_plan); + s.setValue("display_duration", prefs.display_duration); + s.setValue("display_runtime", prefs.display_runtime); + s.setValue("display_transitions", prefs.display_transitions); + s.setValue("recreational_mode", prefs.recreational_mode); + s.setValue("safetystop", prefs.safetystop); + s.setValue("reserve_gas", prefs.reserve_gas); s.setValue("ascrate75", prefs.ascrate75); s.setValue("ascrate50", prefs.ascrate50); s.setValue("ascratestops", prefs.ascratestops); @@ -693,6 +741,18 @@ bool DivePlannerPointsModel::setData(const QModelIndex &index, const QVariant &v return QAbstractItemModel::setData(index, value, role); } +void DivePlannerPointsModel::gaschange(const QModelIndex &index, QString newgas) +{ + int i = index.row(); + gasmix oldgas = divepoints[i].gasmix; + gasmix gas = { 0 }; + if (!validate_gas(newgas.toUtf8().data(), &gas)) + return; + while (i < plannerModel->rowCount() && gasmix_distance(&oldgas, &divepoints[i].gasmix) == 0) + divepoints[i++].gasmix = gas; + emit dataChanged(createIndex(0, 0), createIndex(rowCount() - 1, COLUMNS - 1)); +} + QVariant DivePlannerPointsModel::headerData(int section, Qt::Orientation orientation, int role) const { if (role == Qt::DisplayRole && orientation == Qt::Horizontal) { @@ -729,6 +789,7 @@ int DivePlannerPointsModel::rowCount(const QModelIndex &parent) const DivePlannerPointsModel::DivePlannerPointsModel(QObject *parent) : QAbstractTableModel(parent), mode(NOTHING), + recalc(false), tempGFHigh(100), tempGFLow(100) { @@ -820,30 +881,54 @@ int DivePlannerPointsModel::getSurfacePressure() void DivePlannerPointsModel::setLastStop6m(bool value) { set_last_stop(value); + prefs.last_stop = value; emit dataChanged(createIndex(0, 0), createIndex(rowCount() - 1, COLUMNS - 1)); } void DivePlannerPointsModel::setVerbatim(bool value) { set_verbatim(value); + prefs.verbatim_plan = value; emit dataChanged(createIndex(0, 0), createIndex(rowCount() - 1, COLUMNS - 1)); } void DivePlannerPointsModel::setDisplayRuntime(bool value) { set_display_runtime(value); + prefs.display_runtime = value; emit dataChanged(createIndex(0, 0), createIndex(rowCount() - 1, COLUMNS - 1)); } void DivePlannerPointsModel::setDisplayDuration(bool value) { set_display_duration(value); + prefs.display_duration = value; emit dataChanged(createIndex(0, 0), createIndex(rowCount() - 1, COLUMNS - 1)); } void DivePlannerPointsModel::setDisplayTransitions(bool value) { set_display_transitions(value); + prefs.display_transitions = value; + emit dataChanged(createIndex(0, 0), createIndex(rowCount() - 1, COLUMNS - 1)); +} + +void DivePlannerPointsModel::setRecreationalMode(bool value) +{ + prefs.recreational_mode = value; + emit recreationChanged(value); + emit dataChanged(createIndex(0, 0), createIndex(rowCount() - 1, COLUMNS -1)); +} + +void DivePlannerPointsModel::setSafetyStop(bool value) +{ + prefs.safetystop = value; + emit dataChanged(createIndex(0, 0), createIndex(rowCount() - 1, COLUMNS -1)); +} + +void DivePlannerPointsModel::setReserveGas(int reserve) +{ + prefs.reserve_gas = reserve * 1000; emit dataChanged(createIndex(0, 0), createIndex(rowCount() - 1, COLUMNS - 1)); } @@ -1110,7 +1195,7 @@ bool DivePlannerPointsModel::tankInUse(struct gasmix gasmix) continue; if (!p.entered) // removing deco gases is ok continue; - if (gasmix_distance(&p.gasmix, &gasmix) < 200) + if (gasmix_distance(&p.gasmix, &gasmix) < 100) return true; } return false; @@ -1137,7 +1222,7 @@ void DivePlannerPointsModel::tanksUpdated() struct gasmix gas; gas.o2.permille = oldGases.at(i).first; gas.he.permille = oldGases.at(i).second; - if (gasmix_distance(&gas, &p.gasmix) < 200) { + if (gasmix_distance(&gas, &p.gasmix) < 100) { p.gasmix.o2.permille = gases.at(i).first; p.gasmix.he.permille = gases.at(i).second; } @@ -1237,7 +1322,7 @@ void DivePlannerPointsModel::createPlan(bool replanCopy) plannerModel->setRecalc(oldRecalc); //TODO: C-based function here? - plan(&diveplan, &cache, isPlanner(), true); + bool did_deco = plan(&diveplan, &cache, isPlanner(), true); if (!current_dive || displayed_dive.id != current_dive->id) { // we were planning a new dive, not re-planning an existing on record_dive(clone_dive(&displayed_dive)); @@ -1255,6 +1340,12 @@ void DivePlannerPointsModel::createPlan(bool replanCopy) if (current_dive->divetrip) add_dive_to_trip(copy, current_dive->divetrip); record_dive(copy); + QString oldnotes(current_dive->notes); + if (oldnotes.indexOf(QString(disclaimer)) >= 0) + oldnotes.truncate(oldnotes.indexOf(QString(disclaimer))); + if (did_deco) + oldnotes.append(displayed_dive.notes); + displayed_dive.notes = strdup(oldnotes.toUtf8().data()); } copy_dive(&displayed_dive, current_dive); } @@ -1266,3 +1357,8 @@ void DivePlannerPointsModel::createPlan(bool replanCopy) setPlanMode(NOTHING); planCreated(); } + +PlannerDetails::PlannerDetails(QWidget *parent) : QWidget(parent) +{ + ui.setupUi(this); +} diff --git a/qt-ui/diveplanner.h b/qt-ui/diveplanner.h index 4093bacd1..42e0dc44a 100644 --- a/qt-ui/diveplanner.h +++ b/qt-ui/diveplanner.h @@ -35,6 +35,7 @@ public: virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; virtual bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole); virtual Qt::ItemFlags flags(const QModelIndex &index) const; + void gaschange(const QModelIndex &index, QString newgas); void removeSelectedPoints(const QVector<int> &rows); void setPlanMode(Mode mode); bool isPlanner(); @@ -82,6 +83,8 @@ slots: void setDisplayRuntime(bool value); void setDisplayDuration(bool value); void setDisplayTransitions(bool value); + void setRecreationalMode(bool value); + void setSafetyStop(bool value); void savePlan(); void saveDuplicatePlan(); void remove(const QModelIndex &index); @@ -91,12 +94,14 @@ slots: void loadFromDive(dive *d); void emitDataChanged(); void setRebreatherMode(int mode); + void setReserveGas(int reserve); signals: void planCreated(); void planCanceled(); void cylinderModelEdited(); void startTimeChanged(QDateTime); + void recreationChanged(bool); private: explicit DivePlannerPointsModel(QObject *parent = 0); @@ -180,12 +185,27 @@ slots: void setBottomPo2(double po2); void setDecoPo2(double po2); void setBackgasBreaks(bool dobreaks); + void disableDecoElements(bool value); private: Ui::plannerSettingsWidget ui; void updateUnitsUI(); }; +#include "ui_plannerDetails.h" + +class PlannerDetails : public QWidget { + Q_OBJECT +public: + explicit PlannerDetails(QWidget *parent = 0); + QPushButton *printPlan() const { return ui.printPlan; } + QTextEdit *divePlanOutput() const { return ui.divePlanOutput; } + +private: + Ui::plannerDetails ui; +}; + + QString dpGasToStr(const divedatapoint &p); #endif // DIVEPLANNER_H diff --git a/qt-ui/diveshareexportdialog.cpp b/qt-ui/diveshareexportdialog.cpp index 9e8e69ad6..40670d7fc 100644 --- a/qt-ui/diveshareexportdialog.cpp +++ b/qt-ui/diveshareexportdialog.cpp @@ -4,6 +4,7 @@ #include "save-html.h" #include "usersurvey.h" #include "subsurfacewebservices.h" +#include "helpers.h" #include <QDesktopServices> #include <QSettings> @@ -130,7 +131,7 @@ void DiveShareExportDialog::doUpload() else request.setUrl(QUrl(DIVESHARE_BASE_URI "/upload")); - request.setRawHeader("User-Agent", UserSurvey::getUserAgent().toUtf8()); + request.setRawHeader("User-Agent", getUserAgent().toUtf8()); if (ui->txtUID->text().length() != 0) request.setRawHeader("X-UID", ui->txtUID->text().toUtf8()); diff --git a/qt-ui/downloadfromdivecomputer.cpp b/qt-ui/downloadfromdivecomputer.cpp index c59fb0d7b..e6b51a513 100644 --- a/qt-ui/downloadfromdivecomputer.cpp +++ b/qt-ui/downloadfromdivecomputer.cpp @@ -557,8 +557,8 @@ void DownloadThread::run() } DiveImportedModel::DiveImportedModel(QObject *o) : QAbstractTableModel(o), - lastIndex(-1), firstIndex(0), + lastIndex(-1), checkStates(0) { } diff --git a/qt-ui/filtermodels.cpp b/qt-ui/filtermodels.cpp index e2597a634..be26a253b 100644 --- a/qt-ui/filtermodels.cpp +++ b/qt-ui/filtermodels.cpp @@ -1,5 +1,8 @@ #include "filtermodels.h" #include "mainwindow.h" +#include "models.h" +#include "divelistview.h" +#include "display.h" #define CREATE_INSTANCE_METHOD( CLASS ) \ CLASS *CLASS::instance() \ @@ -61,12 +64,12 @@ CREATE_MODEL_SET_DATA_METHOD( CLASS ); \ CREATE_INSTANCE_METHOD( CLASS ); \ CREATE_DATA_METHOD( CLASS, COUNTER_FUNCTION ) -CREATE_COMMON_METHODS_FOR_FILTER(TagFilterModel, count_dives_with_tag); -CREATE_COMMON_METHODS_FOR_FILTER(BuddyFilterModel, count_dives_with_person); -CREATE_COMMON_METHODS_FOR_FILTER(LocationFilterModel, count_dives_with_location); -CREATE_COMMON_METHODS_FOR_FILTER(SuitsFilterModel, count_dives_with_suit); +CREATE_COMMON_METHODS_FOR_FILTER(TagFilterModel, count_dives_with_tag) +CREATE_COMMON_METHODS_FOR_FILTER(BuddyFilterModel, count_dives_with_person) +CREATE_COMMON_METHODS_FOR_FILTER(LocationFilterModel, count_dives_with_location) +CREATE_COMMON_METHODS_FOR_FILTER(SuitsFilterModel, count_dives_with_suit) -CREATE_INSTANCE_METHOD(MultiFilterSortModel); +CREATE_INSTANCE_METHOD(MultiFilterSortModel) SuitsFilterModel::SuitsFilterModel(QObject *parent) : QStringListModel(parent) { @@ -246,7 +249,7 @@ bool LocationFilterModel::doFilter(struct dive *d, QModelIndex &index0, QAbstrac return true; } // Checked means 'Show', Unchecked means 'Hide'. - QString location(d->location); + QString location(get_dive_location(d)); // only show empty location dives if the user checked that. if (location.isEmpty()) { if (rowCount() > 0) @@ -274,7 +277,7 @@ void LocationFilterModel::repopulate() struct dive *dive; int i = 0; for_each_dive (i, dive) { - QString location(dive->location); + QString location(get_dive_location(dive)); if (!location.isEmpty() && !list.contains(location)) { list.append(location); } diff --git a/qt-ui/filtermodels.h b/qt-ui/filtermodels.h index 1406b8272..9d8724173 100644 --- a/qt-ui/filtermodels.h +++ b/qt-ui/filtermodels.h @@ -6,7 +6,7 @@ class MultiFilterInterface { public: - MultiFilterInterface() : checkState(NULL){}; + MultiFilterInterface() : checkState(NULL), anyChecked(false) {} virtual bool doFilter(struct dive *d, QModelIndex &index0, QAbstractItemModel *sourceModel) const = 0; virtual void clearFilter() = 0; bool *checkState; diff --git a/qt-ui/globe.cpp b/qt-ui/globe.cpp index 1e4639d5c..ea0c0f231 100644 --- a/qt-ui/globe.cpp +++ b/qt-ui/globe.cpp @@ -2,7 +2,13 @@ #ifndef NO_MARBLE #include "mainwindow.h" #include "helpers.h" +#include "divelistview.h" +#include "maintab.h" +#include "display.h" + #include <QTimer> +#include <QContextMenuEvent> +#include <QMouseEvent> #include <marble/AbstractFloatItem.h> #include <marble/GeoDataPlacemark.h> @@ -158,10 +164,11 @@ void GlobeGPS::mouseClicked(qreal lon, qreal lat, GeoDataCoordinates::Unit unit) QList<int> selectedDiveIds; for_each_dive (idx, dive) { long lat_diff, lon_diff; - if (!dive_has_gps_location(dive)) + struct dive_site *ds = get_dive_site_for_dive(dive); + if (!dive_site_has_gps_location(ds)) continue; - lat_diff = labs(dive->latitude.udeg - lat_udeg); - lon_diff = labs(dive->longitude.udeg - lon_udeg); + lat_diff = labs(ds->latitude.udeg - lat_udeg); + lon_diff = labs(ds->longitude.udeg - lon_udeg); if (lat_diff > 180000000) lat_diff = 360000000 - lat_diff; if (lon_diff > 180000000) @@ -180,6 +187,7 @@ void GlobeGPS::mouseClicked(qreal lon, qreal lat, GeoDataCoordinates::Unit unit) void GlobeGPS::repopulateLabels() { + struct dive_site *ds; if (loadedDives) { model()->treeModel()->removeDocument(loadedDives); delete loadedDives; @@ -198,12 +206,16 @@ void GlobeGPS::repopulateLabels() // don't show that flag, it's either already shown as displayed_dive // or it's the one that we are moving right now... continue; - if (dive_has_gps_location(dive)) { - GeoDataPlacemark *place = new GeoDataPlacemark(dive->location); - place->setCoordinate(dive->longitude.udeg / 1000000.0, dive->latitude.udeg / 1000000.0, 0, GeoDataCoordinates::Degree); + if (idx == -1) + ds = &displayed_dive_site; + else + ds = get_dive_site_for_dive(dive); + if (dive_site_has_gps_location(ds)) { + GeoDataPlacemark *place = new GeoDataPlacemark(ds->name); + place->setCoordinate(ds->longitude.udeg / 1000000.0, ds->latitude.udeg / 1000000.0, 0, GeoDataCoordinates::Degree); // don't add dive locations twice, unless they are at least 50m apart - if (locationMap[QString(dive->location)]) { - GeoDataCoordinates existingLocation = locationMap[QString(dive->location)]->coordinate(); + if (locationMap[QString(ds->name)]) { + GeoDataCoordinates existingLocation = locationMap[QString(ds->name)]->coordinate(); GeoDataLineString segment = GeoDataLineString(); segment.append(existingLocation); GeoDataCoordinates newLocation = place->coordinate(); @@ -214,7 +226,7 @@ void GlobeGPS::repopulateLabels() if (dist < 0.05) continue; } - locationMap[QString(dive->location)] = place; + locationMap[QString(ds->name)] = place; loadedDives->append(place); } } @@ -230,23 +242,23 @@ void GlobeGPS::reload() void GlobeGPS::centerOnCurrentDive() { - struct dive *dive = current_dive; + struct dive_site *ds = get_dive_site_for_dive(current_dive); // dive has changed, if we had the 'editingDive', hide it. - if (messageWidget->isVisible() && (!dive || dive_has_gps_location(dive) || amount_selected != 1)) + if (messageWidget->isVisible() && (!ds || dive_site_has_gps_location(ds) || amount_selected != 1)) messageWidget->hide(); editingDiveLocation = false; - if (!dive) + if (!ds) return; - qreal longitude = dive->longitude.udeg / 1000000.0; - qreal latitude = dive->latitude.udeg / 1000000.0; + qreal longitude = ds->longitude.udeg / 1000000.0; + qreal latitude = ds->latitude.udeg / 1000000.0; - if ((!dive_has_gps_location(dive) || MainWindow::instance()->information()->isEditing()) && amount_selected == 1) { + if ((!dive_site_has_gps_location(ds) || MainWindow::instance()->information()->isEditing()) && amount_selected == 1) { prepareForGetDiveCoordinates(); return; } - if (!dive_has_gps_location(dive)) { + if (!dive_site_has_gps_location(ds)) { zoomOutForNoGPS(); return; } @@ -282,7 +294,7 @@ void GlobeGPS::zoomOutForNoGPS() // we show a dive with GPS location we need to zoom in again if (fixZoomTimer->isActive()) fixZoomTimer->stop(); - setZoom(1200, Marble::Automatic); + setZoom(0, Marble::Automatic); if (!needResetZoom) { needResetZoom = true; currentZoomLevel = zoom(); @@ -303,8 +315,10 @@ void GlobeGPS::prepareForGetDiveCoordinates() } } +// This needs to update the dive site, not just this dive void GlobeGPS::changeDiveGeoPosition(qreal lon, qreal lat, GeoDataCoordinates::Unit unit) { + struct dive_site *ds; messageWidget->hide(); if (MainWindow::instance()->dive_list()->selectionModel()->selection().isEmpty()) @@ -318,8 +332,8 @@ void GlobeGPS::changeDiveGeoPosition(qreal lon, qreal lat, GeoDataCoordinates::U centerOn(lon, lat, true); // change the location of the displayed_dive and put the UI in edit mode - displayed_dive.latitude.udeg = lrint(lat * 1000000.0); - displayed_dive.longitude.udeg = lrint(lon * 1000000.0); + displayed_dive_site.latitude.udeg = lrint(lat * 1000000.0); + displayed_dive_site.longitude.udeg = lrint(lon * 1000000.0); emit(coordinatesChanged()); repopulateLabels(); editingDiveLocation = false; @@ -335,7 +349,12 @@ void GlobeGPS::mousePressEvent(QMouseEvent *event) // there could be two scenarios that got us here; let's check if we are editing a dive if (MainWindow::instance()->information()->isEditing() && clickOnGlobe) { - MainWindow::instance()->information()->updateCoordinatesText(lat, lon); + // + // FIXME + // TODO + // + // this needs to do this on the dive site screen + // MainWindow::instance()->information()->updateCoordinatesText(lat, lon); repopulateLabels(); } else if (clickOnGlobe) { changeDiveGeoPosition(lon, lat, GeoDataCoordinates::Degree); diff --git a/qt-ui/globe.h b/qt-ui/globe.h index b6a33bbbe..4f9d7c611 100644 --- a/qt-ui/globe.h +++ b/qt-ui/globe.h @@ -19,7 +19,7 @@ class GlobeGPS : public MarbleWidget { Q_OBJECT public: using MarbleWidget::centerOn; - GlobeGPS(QWidget *parent); + GlobeGPS(QWidget *parent = 0); void reload(); void repopulateLabels(); void centerOnCurrentDive(); diff --git a/qt-ui/locationInformation.ui b/qt-ui/locationInformation.ui new file mode 100644 index 000000000..38eb36145 --- /dev/null +++ b/qt-ui/locationInformation.ui @@ -0,0 +1,149 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>LocationInformation</class> + <widget class="QGroupBox" name="LocationInformation"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>556</width> + <height>584</height> + </rect> + </property> + <property name="windowTitle"> + <string>GroupBox</string> + </property> + <property name="title"> + <string>Dive Site</string> + </property> + <layout class="QGridLayout" name="gridLayout"> + <property name="bottomMargin"> + <number>12</number> + </property> + <property name="horizontalSpacing"> + <number>2</number> + </property> + <property name="verticalSpacing"> + <number>-1</number> + </property> + <item row="1" column="0"> + <widget class="QLabel" name="label_5"> + <property name="text"> + <string>Current Location</string> + </property> + </widget> + </item> + <item row="1" column="1"> + <layout class="QHBoxLayout" name="horizontalLayout"> + <property name="spacing"> + <number>1</number> + </property> + <item> + <widget class="QComboBox" name="currentLocation"/> + </item> + <item> + <widget class="QToolButton" name="addLocation"> + <property name="text"> + <string>...</string> + </property> + <property name="icon"> + <iconset resource="../subsurface.qrc"> + <normaloff>:/plus</normaloff>:/plus</iconset> + </property> + </widget> + </item> + <item> + <widget class="QToolButton" name="editLocation"> + <property name="text"> + <string>...</string> + </property> + <property name="icon"> + <iconset resource="../subsurface.qrc"> + <normaloff>:/edit</normaloff>:/edit</iconset> + </property> + </widget> + </item> + <item> + <widget class="QToolButton" name="removeLocation"> + <property name="text"> + <string>...</string> + </property> + <property name="icon"> + <iconset resource="../subsurface.qrc"> + <normaloff>:/trash</normaloff>:/trash</iconset> + </property> + </widget> + </item> + </layout> + </item> + <item row="2" column="0"> + <widget class="QLabel" name="label"> + <property name="text"> + <string>Name</string> + </property> + </widget> + </item> + <item row="2" column="1"> + <widget class="QLineEdit" name="diveSiteName"/> + </item> + <item row="3" column="0"> + <widget class="QLabel" name="label_2"> + <property name="text"> + <string>Coordinates</string> + </property> + </widget> + </item> + <item row="3" column="1"> + <widget class="QLineEdit" name="diveSiteCoordinates"/> + </item> + <item row="4" column="0"> + <widget class="QLabel" name="label_3"> + <property name="text"> + <string>Description</string> + </property> + </widget> + </item> + <item row="4" column="1"> + <widget class="QLineEdit" name="diveSiteDescription"/> + </item> + <item row="5" column="0"> + <widget class="QLabel" name="label_4"> + <property name="text"> + <string>Notes</string> + </property> + </widget> + </item> + <item row="5" column="1" rowspan="2" colspan="2"> + <widget class="QPlainTextEdit" name="diveSiteNotes"/> + </item> + <item row="6" column="0"> + <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 row="0" column="0" colspan="3"> + <widget class="KMessageWidget" name="diveSiteMessage" native="true"/> + </item> + </layout> + </widget> + <customwidgets> + <customwidget> + <class>KMessageWidget</class> + <extends>QWidget</extends> + <header>kmessagewidget.h</header> + <container>1</container> + </customwidget> + </customwidgets> + <resources> + <include location="../subsurface.qrc"/> + </resources> + <connections/> +</ui> diff --git a/qt-ui/locationinformation.cpp b/qt-ui/locationinformation.cpp new file mode 100644 index 000000000..18f780983 --- /dev/null +++ b/qt-ui/locationinformation.cpp @@ -0,0 +1,241 @@ +#include "locationinformation.h" +#include "dive.h" +#include "mainwindow.h" +#include "divelistview.h" +#include "qthelper.h" + +#include <QDebug> +#include <QShowEvent> + +LocationInformationModel::LocationInformationModel(QObject *obj) : QAbstractListModel(obj), internalRowCount(0) +{ +} + +int LocationInformationModel::rowCount(const QModelIndex &parent) const +{ + Q_UNUSED(parent); + return internalRowCount; +} + +QVariant LocationInformationModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid()) + return QVariant(); + struct dive_site *ds = get_dive_site(index.row()); + + switch(role) { + case Qt::DisplayRole : return qPrintable(ds->name); + } + + return QVariant(); +} + +void LocationInformationModel::update() +{ + int i; + struct dive_site *ds; + for_each_dive_site (i, ds); + + if (rowCount()) { + beginRemoveRows(QModelIndex(), 0, rowCount()-1); + endRemoveRows(); + } + if (i) { + beginInsertRows(QModelIndex(), 0, i); + internalRowCount = i; + endInsertRows(); + } +} + +LocationInformationWidget::LocationInformationWidget(QWidget *parent) : QGroupBox(parent), modified(false) +{ + ui.setupUi(this); + ui.diveSiteMessage->setCloseButtonVisible(false); + ui.diveSiteMessage->show(); + + // create the three buttons and only show the close button for now + closeAction = new QAction(tr("Close"), this); + connect(closeAction, SIGNAL(triggered(bool)), this, SLOT(rejectChanges())); + + acceptAction = new QAction(tr("Apply changes"), this); + connect(acceptAction, SIGNAL(triggered(bool)), this, SLOT(acceptChanges())); + + rejectAction = new QAction(tr("Discard changes"), this); + connect(rejectAction, SIGNAL(triggered(bool)), this, SLOT(rejectChanges())); + + ui.diveSiteMessage->setText(tr("Dive site management")); + ui.diveSiteMessage->addAction(closeAction); + + ui.currentLocation->setModel(new LocationInformationModel()); + connect(ui.currentLocation, SIGNAL(currentIndexChanged(int)), this, SLOT(setCurrentDiveSite(int))); +} + +void LocationInformationWidget::setCurrentDiveSite(int dive_nr) +{ + currentDs = get_dive_site(dive_nr); + if (currentDs) + setLocationId(currentDs->uuid); + else + setLocationId(displayed_dive.dive_site_uuid); +} + +void LocationInformationWidget::setLocationId(uint32_t uuid) +{ + currentDs = get_dive_site_by_uuid(uuid); + + if (!currentDs) { + currentDs = get_dive_site_by_uuid(create_dive_site("")); + displayed_dive.dive_site_uuid = currentDs->uuid; + ui.diveSiteName->clear(); + ui.diveSiteDescription->clear(); + ui.diveSiteNotes->clear(); + ui.diveSiteCoordinates->clear(); + } + displayed_dive_site = *currentDs; + if (displayed_dive_site.name) + ui.diveSiteName->setText(displayed_dive_site.name); + else + ui.diveSiteName->clear(); + if (displayed_dive_site.description) + ui.diveSiteDescription->setText(displayed_dive_site.description); + else + ui.diveSiteDescription->clear(); + if (displayed_dive_site.notes) + ui.diveSiteNotes->setPlainText(displayed_dive_site.notes); + else + ui.diveSiteNotes->clear(); + if (displayed_dive_site.latitude.udeg || displayed_dive_site.longitude.udeg) + ui.diveSiteCoordinates->setText(printGPSCoords(displayed_dive_site.latitude.udeg, displayed_dive_site.longitude.udeg)); + else + ui.diveSiteCoordinates->clear(); +} + +void LocationInformationWidget::updateGpsCoordinates() +{ + ui.diveSiteCoordinates->setText(printGPSCoords(displayed_dive_site.latitude.udeg, displayed_dive_site.longitude.udeg)); + MainWindow::instance()->setApplicationState("EditDiveSite"); +} + +void LocationInformationWidget::acceptChanges() +{ + char *uiString; + currentDs->latitude = displayed_dive_site.latitude; + currentDs->longitude = displayed_dive_site.longitude; + uiString = ui.diveSiteName->text().toUtf8().data(); + if (!same_string(uiString, currentDs->name)) { + free(currentDs->name); + currentDs->name = copy_string(uiString); + } + uiString = ui.diveSiteDescription->text().toUtf8().data(); + if (!same_string(uiString, currentDs->description)) { + free(currentDs->description); + currentDs->description = copy_string(uiString); + } + uiString = ui.diveSiteNotes->document()->toPlainText().toUtf8().data(); + if (!same_string(uiString, currentDs->notes)) { + free(currentDs->notes); + currentDs->notes = copy_string(uiString); + } + if (dive_site_is_empty(currentDs)) { + delete_dive_site(currentDs->uuid); + displayed_dive.dive_site_uuid = 0; + setLocationId(0); + } else { + setLocationId(currentDs->uuid); + } + mark_divelist_changed(true); + resetState(); + emit informationManagementEnded(); +} + +void LocationInformationWidget::rejectChanges() +{ + Q_ASSERT(currentDs != NULL); + if (dive_site_is_empty(currentDs)) { + delete_dive_site(currentDs->uuid); + displayed_dive.dive_site_uuid = 0; + setLocationId(0); + } else { + setLocationId(currentDs->uuid); + } + resetState(); + emit informationManagementEnded(); +} + +void LocationInformationWidget::showEvent(QShowEvent *ev) +{ + LocationInformationModel *m = (LocationInformationModel*) ui.currentLocation->model(); + ui.diveSiteMessage->setCloseButtonVisible(false); + m->update(); + QGroupBox::showEvent(ev); + +} + +void LocationInformationWidget::markChangedWidget(QWidget *w) +{ + QPalette p; + qreal h, s, l, a; + if (!modified) + enableEdition(); + qApp->palette().color(QPalette::Text).getHslF(&h, &s, &l, &a); + p.setBrush(QPalette::Base, (l <= 0.3) ? QColor(Qt::yellow).lighter() : (l <= 0.6) ? QColor(Qt::yellow).light() : /* else */ QColor(Qt::yellow).darker(300)); + w->setPalette(p); + modified = true; +} + +void LocationInformationWidget::resetState() +{ + modified = false; + resetPallete(); + MainWindow::instance()->dive_list()->setEnabled(true); + MainWindow::instance()->setEnabledToolbar(true); + ui.diveSiteMessage->setText(tr("Dive site management")); + ui.diveSiteMessage->addAction(closeAction); + ui.diveSiteMessage->removeAction(acceptAction); + ui.diveSiteMessage->removeAction(rejectAction); + ui.diveSiteMessage->setCloseButtonVisible(false); +} + +void LocationInformationWidget::enableEdition() +{ + MainWindow::instance()->dive_list()->setEnabled(false); + MainWindow::instance()->setEnabledToolbar(false); + ui.diveSiteMessage->setText(tr("You are editing a dive site")); + ui.diveSiteMessage->removeAction(closeAction); + ui.diveSiteMessage->addAction(acceptAction); + ui.diveSiteMessage->addAction(rejectAction); + ui.diveSiteMessage->setCloseButtonVisible(false); +} + +void LocationInformationWidget::on_diveSiteCoordinates_textChanged(const QString& text) +{ + if (!same_string(qPrintable(text), printGPSCoords(currentDs->latitude.udeg, currentDs->longitude.udeg))) + markChangedWidget(ui.diveSiteCoordinates); +} + +void LocationInformationWidget::on_diveSiteDescription_textChanged(const QString& text) +{ + if (!same_string(qPrintable(text), currentDs->description)) + markChangedWidget(ui.diveSiteDescription); +} + +void LocationInformationWidget::on_diveSiteName_textChanged(const QString& text) +{ + if (!same_string(qPrintable(text), currentDs->name)) + markChangedWidget(ui.diveSiteName); +} + +void LocationInformationWidget::on_diveSiteNotes_textChanged() +{ + if (!same_string(qPrintable(ui.diveSiteNotes->toPlainText()), currentDs->notes)) + markChangedWidget(ui.diveSiteNotes); +} + +void LocationInformationWidget::resetPallete() +{ + QPalette p; + ui.diveSiteCoordinates->setPalette(p); + ui.diveSiteDescription->setPalette(p); + ui.diveSiteName->setPalette(p); + ui.diveSiteNotes->setPalette(p); +} diff --git a/qt-ui/locationinformation.h b/qt-ui/locationinformation.h new file mode 100644 index 000000000..82105c333 --- /dev/null +++ b/qt-ui/locationinformation.h @@ -0,0 +1,50 @@ +#ifndef LOCATIONINFORMATION_H +#define LOCATIONINFORMATION_H + +#include "ui_locationInformation.h" +#include <stdint.h> +#include <QAbstractListModel> + +class LocationInformationModel : public QAbstractListModel { +Q_OBJECT +public: + LocationInformationModel(QObject *obj = 0); + int rowCount(const QModelIndex &parent = QModelIndex()) const; + QVariant data(const QModelIndex &index = QModelIndex(), int role = Qt::DisplayRole) const; + void update(); +private: + int internalRowCount; +}; + +class LocationInformationWidget : public QGroupBox { +Q_OBJECT +public: + LocationInformationWidget(QWidget *parent = 0); +protected: + void showEvent(QShowEvent *); +\ +public slots: + void acceptChanges(); + void rejectChanges(); + void setLocationId(uint32_t uuid); + void updateGpsCoordinates(void); + void markChangedWidget(QWidget *w); + void enableEdition(); + void resetState(); + void resetPallete(); + void setCurrentDiveSite(int dive_nr); + void on_diveSiteCoordinates_textChanged(const QString& text); + void on_diveSiteDescription_textChanged(const QString& text); + void on_diveSiteName_textChanged(const QString& text); + void on_diveSiteNotes_textChanged(); +signals: + void informationManagementEnded(); + +private: + struct dive_site *currentDs; + Ui::LocationInformation ui; + bool modified; + QAction *closeAction, *acceptAction, *rejectAction; +}; + +#endif diff --git a/qt-ui/maintab.cpp b/qt-ui/maintab.cpp index 2fafb65ff..fd760445b 100644 --- a/qt-ui/maintab.cpp +++ b/qt-ui/maintab.cpp @@ -6,9 +6,16 @@ */ #include "maintab.h" #include "mainwindow.h" +#include "globe.h" #include "helpers.h" #include "statistics.h" #include "modeldelegates.h" +#include "models.h" +#include "divelistview.h" +#include "display.h" +#include "profile/profilewidget2.h" +#include "diveplanner.h" +#include "divesitehelpers.h" #if defined(FBSUPPORT) #include "socialnetworks.h" @@ -20,6 +27,7 @@ #include <QShortcut> #include <QMessageBox> #include <QDesktopServices> +#include <QStringList> MainTab::MainTab(QWidget *parent) : QTabWidget(parent), weightModel(new WeightModel(this)), @@ -43,18 +51,19 @@ MainTab::MainTab(QWidget *parent) : QTabWidget(parent), ui.extraData->setModel(extraDataModel); closeMessage(); + connect(ui.manageDiveSite, SIGNAL(clicked()), this, SLOT(prepareDiveSiteEdit())); + QAction *action = new QAction(tr("Apply changes"), this); connect(action, SIGNAL(triggered(bool)), this, SLOT(acceptChanges())); addMessageAction(action); action = new QAction(tr("Discard changes"), this); connect(action, SIGNAL(triggered(bool)), this, SLOT(rejectChanges())); + addMessageAction(action); QShortcut *closeKey = new QShortcut(QKeySequence(Qt::Key_Escape), this); connect(closeKey, SIGNAL(activated()), this, SLOT(escDetected())); - addMessageAction(action); - if (qApp->style()->objectName() == "oxygen") setDocumentMode(true); else @@ -64,21 +73,6 @@ MainTab::MainTab(QWidget *parent) : QTabWidget(parent), // filled from a dive, they are made writeable setEnabled(false); - ui.location->installEventFilter(this); - ui.coordinates->installEventFilter(this); - ui.divemaster->installEventFilter(this); - ui.buddy->installEventFilter(this); - ui.suit->installEventFilter(this); - ui.notes->viewport()->installEventFilter(this); - ui.rating->installEventFilter(this); - ui.visibility->installEventFilter(this); - ui.airtemp->installEventFilter(this); - ui.watertemp->installEventFilter(this); - ui.dateEdit->installEventFilter(this); - ui.timeEdit->installEventFilter(this); - ui.tagWidget->installEventFilter(this); - ui.DiveType->installEventFilter(this); - Q_FOREACH (QObject *obj, ui.statisticsTab->children()) { QLabel *label = qobject_cast<QLabel *>(obj); if (label) @@ -192,6 +186,15 @@ MainTab::MainTab(QWidget *parent) : QTabWidget(parent), ui.socialNetworks->setVisible(false); #endif + ui.waitingSpinner->setRoundness(70.0); + ui.waitingSpinner->setMinimumTrailOpacity(15.0); + ui.waitingSpinner->setTrailFadePercentage(70.0); + ui.waitingSpinner->setNumberOfLines(8); + ui.waitingSpinner->setLineLength(5); + ui.waitingSpinner->setLineWidth(3); + ui.waitingSpinner->setInnerRadius(5); + ui.waitingSpinner->setRevolutionsPerSecond(1); + acceptingEdit = false; } @@ -206,6 +209,22 @@ MainTab::~MainTab() } } +void MainTab::enableGeoLoockupEdition() +{ + ui.waitingSpinner->stop(); + ui.manageDiveSite->show(); +} + +void MainTab::disableGeoLoockupEdition() +{ + ui.waitingSpinner->start(); + ui.manageDiveSite->hide(); +} + +void MainTab::prepareDiveSiteEdit() { + emit requestDiveSiteEdit(displayed_dive.dive_site_uuid); +} + void MainTab::toggleTriggeredColumn() { QAction *action = qobject_cast<QAction *>(sender()); @@ -284,11 +303,15 @@ void MainTab::updateTextLabels(bool showUnits) void MainTab::enableEdition(EditMode newEditMode) { + const bool isTripEdit = MainWindow::instance() && + MainWindow::instance()->dive_list()->selectedTrips().count() == 1; + if (((newEditMode == DIVE || newEditMode == NONE) && current_dive == NULL) || editMode != NONE) return; modified = false; copyPaste = false; if ((newEditMode == DIVE || newEditMode == NONE) && + !isTripEdit && current_dive->dc.model && strcmp(current_dive->dc.model, "manually added dive") == 0) { // editCurrentDive will call enableEdition with newEditMode == MANUALLY_ADDED_DIVE @@ -310,7 +333,7 @@ void MainTab::enableEdition(EditMode newEditMode) if (amount_selected == 1 && newEditMode != ADD) MainWindow::instance()->globe()->prepareForGetDiveCoordinates(); - if (MainWindow::instance() && MainWindow::instance()->dive_list()->selectedTrips().count() == 1) { + if (isTripEdit) { // we are editing trip location and notes displayMessage(tr("This trip is being edited.")); memset(&displayedTrip, 0, sizeof(displayedTrip)); @@ -385,12 +408,17 @@ bool MainTab::isEditing() return editMode != NONE; } +void MainTab::showLocation() +{ + ui.location->setText(get_dive_location(&displayed_dive)); +} + void MainTab::updateDiveInfo(bool clear) { // don't execute this while adding / planning a dive if (editMode == ADD || editMode == MANUALLY_ADDED_DIVE || MainWindow::instance()->graphics()->isPlanner()) return; - if (!isEnabled() && !clear) + if (!isEnabled() && !clear ) setEnabled(true); if (isEnabled() && clear) setEnabled(false); @@ -418,9 +446,7 @@ void MainTab::updateDiveInfo(bool clear) else ui.notes->setPlainText(tmp); } - UPDATE_TEXT(displayed_dive, notes); - UPDATE_TEXT(displayed_dive, location); UPDATE_TEXT(displayed_dive, suit); UPDATE_TEXT(displayed_dive, divemaster); UPDATE_TEXT(displayed_dive, buddy); @@ -429,7 +455,11 @@ void MainTab::updateDiveInfo(bool clear) ui.DiveType->setCurrentIndex(displayed_dive.dc.divemode); if (!clear) { - updateGpsCoordinates(); + struct dive_site *ds = get_dive_site_by_uuid(displayed_dive.dive_site_uuid); + if (ds) + ui.location->setText(ds->name); + else + ui.location->clear(); // Subsurface always uses "local time" as in "whatever was the local time at the location" // so all time stamps have no time zone information and are in UTC QDateTime localTime = QDateTime::fromTime_t(displayed_dive.when - gettimezoneoffset(displayed_dive.when)); @@ -440,8 +470,6 @@ void MainTab::updateDiveInfo(bool clear) setTabText(0, tr("Trip notes")); currentTrip = *MainWindow::instance()->dive_list()->selectedTrips().begin(); // only use trip relevant fields - ui.coordinates->setVisible(false); - ui.CoordinatedLabel->setVisible(false); ui.divemaster->setVisible(false); ui.DivemasterLabel->setVisible(false); ui.buddy->setVisible(false); @@ -468,11 +496,9 @@ void MainTab::updateDiveInfo(bool clear) clearEquipment(); ui.equipmentTab->setEnabled(false); } else { - setTabText(0, tr("Dive notes")); + setTabText(0, tr("Notes")); currentTrip = NULL; // make all the fields visible writeable - ui.coordinates->setVisible(true); - ui.CoordinatedLabel->setVisible(true); ui.divemaster->setVisible(true); ui.buddy->setVisible(true); ui.suit->setVisible(true); @@ -641,8 +667,8 @@ void MainTab::updateDiveInfo(bool clear) clearStats(); clearEquipment(); ui.rating->setCurrentStars(0); - ui.coordinates->clear(); ui.visibility->setCurrentStars(0); + ui.location->clear(); } editMode = NONE; ui.cylinders->view()->hideColumn(CylindersModel::DEPTH); @@ -750,19 +776,13 @@ void MainTab::acceptChanges() copy_samples(&displayed_dive.dc, ¤t_dive->dc); } struct dive *cd = current_dive; - //Reset coordinates field, in case it contains garbage. - updateGpsCoordinates(); // now check if something has changed and if yes, edit the selected dives that // were identical with the master dive shown (and mark the divelist as changed) - if (!same_string(displayed_dive.buddy, cd->buddy)) - MODIFY_SELECTED_DIVES(EDIT_TEXT(buddy)); if (!same_string(displayed_dive.suit, cd->suit)) MODIFY_SELECTED_DIVES(EDIT_TEXT(suit)); if (!same_string(displayed_dive.notes, cd->notes)) MODIFY_SELECTED_DIVES(EDIT_TEXT(notes)); if (!same_string(displayed_dive.divemaster, cd->divemaster)) - MODIFY_SELECTED_DIVES(EDIT_TEXT(divemaster)); - if (displayed_dive.rating != cd->rating) MODIFY_SELECTED_DIVES(EDIT_VALUE(rating)); if (displayed_dive.visibility != cd->visibility) MODIFY_SELECTED_DIVES(EDIT_VALUE(visibility)); @@ -779,18 +799,12 @@ void MainTab::acceptChanges() time_t offset = cd->when - displayed_dive.when; MODIFY_SELECTED_DIVES(mydive->when -= offset;); } - if (displayed_dive.latitude.udeg != cd->latitude.udeg || - displayed_dive.longitude.udeg != cd->longitude.udeg) - MODIFY_SELECTED_DIVES( - if (copyPaste || - (same_string(mydive->location, cd->location) && - mydive->latitude.udeg == cd->latitude.udeg && - mydive->longitude.udeg == cd->longitude.udeg)) - gpsHasChanged(mydive, cd, ui.coordinates->text(), 0); - ); - if (!same_string(displayed_dive.location, cd->location)) - MODIFY_SELECTED_DIVES(EDIT_TEXT(location)); + if (displayed_dive.dive_site_uuid != cd->dive_site_uuid) + MODIFY_SELECTED_DIVES(EDIT_VALUE(dive_site_uuid)); + // three text fields are somewhat special and are represented as tags + // in the UI - they need somewhat smarter handling + saveTaggedStrings(); saveTags(); if (editMode != ADD && cylindersModel->changed) { @@ -821,6 +835,16 @@ void MainTab::acceptChanges() cd->cylinder[i] = displayed_dive.cylinder[i]; cd->cylinder[i].type.description = copy_string(displayed_dive.cylinder[i].type.description); } + /* if cylinders changed we may have changed gas change events + * - so far this is ONLY supported for a single selected dive */ + struct divecomputer *tdc = ¤t_dive->dc; + struct divecomputer *sdc = &displayed_dive.dc; + while(tdc && sdc) { + free_events(tdc->events); + copy_events(sdc, tdc); + tdc = tdc->next; + sdc = sdc->next; + } do_replot = true; } @@ -895,7 +919,6 @@ void MainTab::resetPallete() ui.buddy->setPalette(p); ui.notes->setPalette(p); ui.location->setPalette(p); - ui.coordinates->setPalette(p); ui.divemaster->setPalette(p); ui.suit->setPalette(p); ui.airtemp->setPalette(p); @@ -970,10 +993,7 @@ void MainTab::markChangedWidget(QWidget *w) qApp->palette().color(QPalette::Text).getHslF(&h, &s, &l, &a); p.setBrush(QPalette::Base, (l <= 0.3) ? QColor(Qt::yellow).lighter() : (l <= 0.6) ? QColor(Qt::yellow).light() : /* else */ QColor(Qt::yellow).darker(300)); w->setPalette(p); - if (!modified) { - modified = true; - enableEdition(); - } + modified = true; } void MainTab::on_buddy_textChanged() @@ -1137,6 +1157,73 @@ void MainTab::saveTags() ); } +// buddy and divemaster are represented in the UI just like the tags, but the internal +// representation is just a string (with commas as delimiters). So we need to do the same +// thing we did for tags, just differently +void MainTab::saveTaggedStrings() +{ + QStringList addedList, removedList; + struct dive *cd = current_dive; + + diffTaggedStrings(cd->buddy, displayed_dive.buddy, addedList, removedList); + MODIFY_SELECTED_DIVES( + QStringList oldList = QString(mydive->buddy).split(QRegExp("\\s*,\\s*"), QString::SkipEmptyParts); + QString newString; + QString comma; + Q_FOREACH (const QString tag, oldList) { + if (!removedList.contains(tag, Qt::CaseInsensitive)) { + newString += comma + tag; + comma = ", "; + } + } + Q_FOREACH (const QString tag, addedList) { + if (!oldList.contains(tag, Qt::CaseInsensitive)) { + newString += comma + tag; + comma = ", "; + } + } + free(mydive->buddy); + mydive->buddy = copy_string(qPrintable(newString)); + ); + addedList.clear(); + removedList.clear(); + diffTaggedStrings(cd->divemaster, displayed_dive.divemaster, addedList, removedList); + MODIFY_SELECTED_DIVES( + QStringList oldList = QString(mydive->divemaster).split(QRegExp("\\s*,\\s*"), QString::SkipEmptyParts); + QString newString; + QString comma; + Q_FOREACH (const QString tag, oldList) { + if (!removedList.contains(tag, Qt::CaseInsensitive)) { + newString += comma + tag; + comma = ", "; + } + } + Q_FOREACH (const QString tag, addedList) { + if (!oldList.contains(tag, Qt::CaseInsensitive)) { + newString += comma + tag; + comma = ", "; + } + } + free(mydive->divemaster); + mydive->divemaster = copy_string(qPrintable(newString)); + ); +} + +void MainTab::diffTaggedStrings(QString currentString, QString displayedString, QStringList &addedList, QStringList &removedList) +{ + QStringList displayedList, currentList; + currentList = currentString.split(',', QString::SkipEmptyParts); + displayedList = displayedString.split(',', QString::SkipEmptyParts); + Q_FOREACH ( const QString tag, currentList) { + if (!displayedList.contains(tag, Qt::CaseInsensitive)) + removedList << tag.trimmed(); + } + Q_FOREACH (const QString tag, displayedList) { + if (!currentList.contains(tag, Qt::CaseInsensitive)) + addedList << tag.trimmed(); + } +} + void MainTab::on_tagWidget_textChanged() { char buf[1024]; @@ -1158,9 +1245,6 @@ void MainTab::on_location_textChanged(const QString &text) if (currentTrip) { free(displayedTrip.location); displayedTrip.location = strdup(ui.location->text().toUtf8().data()); - } else { - free(displayed_dive.location); - displayed_dive.location = strdup(ui.location->text().toUtf8().data()); } markChangedWidget(ui.location); } @@ -1168,25 +1252,13 @@ void MainTab::on_location_textChanged(const QString &text) // If we have GPS data for the location entered, add it. void MainTab::on_location_editingFinished() { - // if we have a location and no GPS data, look up the GPS data; - // but if the GPS data was intentionally cleared then don't - if (!currentTrip && - !same_string(displayed_dive.location, "") && - ui.coordinates->text().trimmed().isEmpty() && - !(editMode == DIVE && dive_has_gps_location(current_dive))) { - struct dive *dive; - int i = 0; - for_each_dive (i, dive) { - if (same_string(displayed_dive.location, dive->location) && - (dive->latitude.udeg || dive->longitude.udeg)) { - displayed_dive.latitude = dive->latitude; - displayed_dive.longitude = dive->longitude; - MainWindow::instance()->globe()->reload(); - updateGpsCoordinates(); - break; - } - } - } + // find the dive site or create it + const char *name = copy_string(qPrintable(ui.location->text())); + uint32_t uuid = get_dive_site_uuid_by_name(name, NULL); + if (!uuid) + uuid = create_dive_site(name); + displayed_dive.dive_site_uuid = uuid; + free((void*)name); } void MainTab::on_suit_textChanged(const QString &text) @@ -1219,6 +1291,7 @@ void MainTab::on_notes_textChanged() markChangedWidget(ui.notes); } +#if 0 // we'll need something like this for the dive site management void MainTab::on_coordinates_textChanged(const QString &text) { if (editMode == IGNORE || acceptingEdit == true) @@ -1235,6 +1308,7 @@ void MainTab::on_coordinates_textChanged(const QString &text) ui.coordinates->setPalette(p); // marks things red } } +#endif void MainTab::on_rating_valueChanged(int value) { @@ -1286,6 +1360,7 @@ void MainTab::editWeightWidget(const QModelIndex &index) ui.weights->edit(index); } +#if 0 // we'll need this for dive sites void MainTab::updateCoordinatesText(qreal lat, qreal lon) { int ulat = rint(lat * 1000000); @@ -1298,9 +1373,16 @@ void MainTab::updateGpsCoordinates() if (editMode == NONE) enableEdition(); - ui.coordinates->setText(printGPSCoords(displayed_dive.latitude.udeg, displayed_dive.longitude.udeg)); - ui.coordinates->setModified(displayed_dive.latitude.udeg || displayed_dive.longitude.udeg); + struct dive_site *ds = get_dive_site_by_uuid(displayed_dive.dive_site_uuid); + if (ds && dive_site_has_gps_location(ds)) { + ui.coordinates->setText(printGPSCoords(ds->latitude.udeg, ds->longitude.udeg)); + ui.coordinates->setModified(true); + } else if (!ui.coordinates->text().isEmpty()) { + ui.coordinates->setModified(true); + ui.coordinates->clear(); + } } +#endif void MainTab::escDetected() { @@ -1332,7 +1414,6 @@ void MainTab::showAndTriggerEditSelective(struct dive_components what) // take the data in our copyPasteDive and apply it to selected dives enableEdition(); copyPaste = true; - SHOW_SELECTIVE(location); SHOW_SELECTIVE(buddy); SHOW_SELECTIVE(divemaster); SHOW_SELECTIVE(suit); @@ -1347,8 +1428,8 @@ void MainTab::showAndTriggerEditSelective(struct dive_components what) ui.rating->setCurrentStars(displayed_dive.rating); if (what.visibility) ui.visibility->setCurrentStars(displayed_dive.visibility); - if (what.gps) - updateGpsCoordinates(); + if (what.divesite) + ui.location->setText(get_dive_location(&displayed_dive)); if (what.tags) { char buf[1024]; taglist_get_tagstring(displayed_dive.tag_list, buf, 1024); diff --git a/qt-ui/maintab.h b/qt-ui/maintab.h index 71703e3f1..4a44c03b3 100644 --- a/qt-ui/maintab.h +++ b/qt-ui/maintab.h @@ -10,6 +10,7 @@ #include <QTabWidget> #include <QDialog> #include <QMap> +#include <QUuid> #include "ui_maintab.h" #include "completionmodels.h" @@ -40,7 +41,7 @@ public: IGNORE }; - MainTab(QWidget *parent); + MainTab(QWidget *parent = 0); ~MainTab(); void clearStats(); void clearInfo(); @@ -55,7 +56,7 @@ public: signals: void addDiveFinished(); void dateTimeChanged(); - + void requestDiveSiteEdit(uint32_t uuid); public slots: void addCylinder_clicked(); @@ -65,7 +66,6 @@ slots: void rejectChanges(); void on_location_textChanged(const QString &text); void on_location_editingFinished(); - void on_coordinates_textChanged(const QString &text); void on_divemaster_textChanged(); void on_buddy_textChanged(); void on_suit_textChanged(const QString &text); @@ -92,7 +92,10 @@ slots: void escDetected(void); void photoDoubleClicked(const QString filePath); void removeSelectedPhotos(); - void updateGpsCoordinates(); + void prepareDiveSiteEdit(); + void showLocation(); + void enableGeoLoockupEdition(); + void disableGeoLoockupEdition(); private: Ui::MainTab ui; @@ -111,6 +114,8 @@ private: bool copyPaste; void resetPallete(); void saveTags(); + void saveTaggedStrings(); + void diffTaggedStrings(QString currentString, QString displayedString, QStringList &addedList, QStringList &removedList); void markChangedWidget(QWidget *w); dive_trip_t *currentTrip; dive_trip_t displayedTrip; diff --git a/qt-ui/maintab.ui b/qt-ui/maintab.ui index bffbac97a..da8cabecb 100644 --- a/qt-ui/maintab.ui +++ b/qt-ui/maintab.ui @@ -6,7 +6,7 @@ <rect> <x>0</x> <y>0</y> - <width>443</width> + <width>463</width> <height>815</height> </rect> </property> @@ -15,7 +15,7 @@ </property> <widget class="QWidget" name="notesTab"> <attribute name="title"> - <string>Dive notes</string> + <string>Notes</string> </attribute> <layout class="QGridLayout" name="diveNotesLayout"> <property name="spacing"> @@ -40,113 +40,137 @@ <rect> <x>0</x> <y>0</y> - <width>397</width> + <width>417</width> <height>744</height> </rect> </property> - <layout class="QGridLayout" name="diveNotesScrollAreaLayout"> - <property name="spacing"> - <number>0</number> - </property> - <item row="4" column="0" colspan="3"> - <layout class="QHBoxLayout" name="horizontalLayout"> - <property name="sizeConstraint"> - <enum>QLayout::SetNoConstraint</enum> - </property> - <item> - <widget class="QLabel" name="CoordinatedLabel"> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <layout class="QGridLayout" name="gridLayout"> + <item row="0" column="0"> + <widget class="QLabel" name="label_2"> + <property name="text"> + <string>Date</string> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QLabel" name="label"> + <property name="text"> + <string>Time</string> + </property> + </widget> + </item> + <item row="0" column="2"> + <widget class="QLabel" name="airTempLabel"> + <property name="text"> + <string>Air temp.</string> + </property> + </widget> + </item> + <item row="0" column="3"> + <widget class="QLabel" name="waterTempLabel"> + <property name="text"> + <string>Water temp.</string> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QDateEdit" name="dateEdit"> + <property name="calendarPopup"> + <bool>true</bool> + </property> + <property name="timeSpec"> + <enum>Qt::UTC</enum> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QTimeEdit" name="timeEdit"> <property name="sizePolicy"> - <sizepolicy hsizetype="MinimumExpanding" vsizetype="Preferred"> + <sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed"> <horstretch>0</horstretch> <verstretch>0</verstretch> </sizepolicy> </property> - <property name="text"> - <string>Coordinates</string> + <property name="timeSpec"> + <enum>Qt::UTC</enum> </property> </widget> </item> + <item row="1" column="2"> + <widget class="QLineEdit" name="airtemp"> + <property name="readOnly"> + <bool>false</bool> + </property> + </widget> + </item> + <item row="1" column="3"> + <widget class="QLineEdit" name="watertemp"> + <property name="readOnly"> + <bool>false</bool> + </property> + </widget> + </item> + </layout> + </item> + <item> + <layout class="QHBoxLayout" name="LocationLabelLayout"> <item> - <widget class="QLabel" name="TypeLabel"> + <widget class="QLabel" name="LocationLabel"> <property name="text"> - <string>Dive mode</string> + <string>Location</string> </property> </widget> </item> </layout> </item> - <item row="0" column="0"> - <widget class="QLabel" name="label_2"> - <property name="text"> - <string>Date</string> - </property> - </widget> - </item> - <item row="0" column="1"> - <widget class="QLabel" name="label"> - <property name="text"> - <string>Time</string> - </property> - </widget> - </item> - <item row="0" column="2"> - <layout class="QHBoxLayout" name="temperatureLabels"> - <property name="spacing"> - <number>0</number> - </property> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_2"> <item> - <widget class="QLabel" name="airTempLabel"> - <property name="text"> - <string>Air temp.</string> + <widget class="QLineEdit" name="location"> + <property name="readOnly"> + <bool>false</bool> </property> </widget> </item> <item> - <widget class="QLabel" name="waterTempLabel"> + <widget class="QPushButton" name="manageDiveSite"> <property name="text"> - <string>Water temp.</string> + <string>manage</string> </property> </widget> </item> + <item> + <widget class="QtWaitingSpinner" name="waitingSpinner" native="true"/> + </item> </layout> </item> - <item row="1" column="1"> - <widget class="QTimeEdit" name="timeEdit"> - <property name="sizePolicy"> - <sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed"> - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> - </property> - <property name="timeSpec"> - <enum>Qt::UTC</enum> - </property> - </widget> - </item> - <item row="1" column="0"> - <widget class="QDateEdit" name="dateEdit"> - <property name="calendarPopup"> - <bool>true</bool> - </property> - <property name="timeSpec"> - <enum>Qt::UTC</enum> - </property> - </widget> - </item> - <item row="1" column="2"> - <layout class="QHBoxLayout" name="airWaterTempLayout"> - <property name="spacing"> - <number>0</number> - </property> - <item> - <widget class="QLineEdit" name="airtemp"> + <item> + <layout class="QGridLayout" name="gridLayout_4"> + <item row="0" column="0"> + <widget class="QLabel" name="DivemasterLabel"> + <property name="text"> + <string>Divemaster</string> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QLabel" name="BuddyLabel"> + <property name="text"> + <string>Buddy</string> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="TagWidget" name="divemaster"> <property name="readOnly"> <bool>false</bool> </property> </widget> </item> - <item> - <widget class="QLineEdit" name="watertemp"> + <item row="1" column="1"> + <widget class="TagWidget" name="buddy"> <property name="readOnly"> <bool>false</bool> </property> @@ -154,47 +178,9 @@ </item> </layout> </item> - <item row="2" column="0"> - <widget class="QLabel" name="LocationLabel"> - <property name="text"> - <string>Location</string> - </property> - </widget> - </item> - <item row="10" column="0"> - <widget class="QLabel" name="DivemasterLabel"> - <property name="text"> - <string>Divemaster</string> - </property> - </widget> - </item> - <item row="10" column="2"> - <widget class="QLabel" name="BuddyLabel"> - <property name="text"> - <string>Buddy</string> - </property> - </widget> - </item> - <item row="11" column="0" colspan="2"> - <widget class="TagWidget" name="divemaster"> - <property name="readOnly"> - <bool>false</bool> - </property> - </widget> - </item> - <item row="11" column="2"> - <widget class="TagWidget" name="buddy"> - <property name="readOnly"> - <bool>false</bool> - </property> - </widget> - </item> - <item row="12" column="0" colspan="2"> - <layout class="QHBoxLayout" name="ratingVisibilityLabels"> - <property name="spacing"> - <number>0</number> - </property> - <item> + <item> + <layout class="QGridLayout" name="gridLayout_3"> + <item row="0" column="0"> <widget class="QLabel" name="RatingLabel"> <property name="sizePolicy"> <sizepolicy hsizetype="MinimumExpanding" vsizetype="Preferred"> @@ -207,7 +193,7 @@ </property> </widget> </item> - <item> + <item row="0" column="1"> <widget class="QLabel" name="visibilityLabel"> <property name="sizePolicy"> <sizepolicy hsizetype="MinimumExpanding" vsizetype="Preferred"> @@ -220,28 +206,14 @@ </property> </widget> </item> - </layout> - </item> - <item row="12" column="2"> - <widget class="QLabel" name="SuitLabel"> - <property name="text"> - <string>Suit</string> - </property> - </widget> - </item> - <item row="13" column="2"> - <widget class="QLineEdit" name="suit"> - <property name="readOnly"> - <bool>false</bool> - </property> - </widget> - </item> - <item row="13" column="0" colspan="2"> - <layout class="QHBoxLayout" name="ratingVisibilityWidgets"> - <property name="spacing"> - <number>0</number> - </property> - <item> + <item row="0" column="2"> + <widget class="QLabel" name="SuitLabel"> + <property name="text"> + <string>Suit</string> + </property> + </widget> + </item> + <item row="1" column="0"> <widget class="StarWidget" name="rating" native="true"> <property name="sizePolicy"> <sizepolicy hsizetype="MinimumExpanding" vsizetype="Preferred"> @@ -254,7 +226,7 @@ </property> </widget> </item> - <item> + <item row="1" column="1"> <widget class="StarWidget" name="visibility" native="true"> <property name="sizePolicy"> <sizepolicy hsizetype="MinimumExpanding" vsizetype="Preferred"> @@ -267,55 +239,69 @@ </property> </widget> </item> + <item row="1" column="2"> + <widget class="QLineEdit" name="suit"> + <property name="readOnly"> + <bool>false</bool> + </property> + </widget> + </item> </layout> </item> - <item row="14" column="0"> - <widget class="QLabel" name="TagLabel"> - <property name="text"> - <string>Tags</string> - </property> - </widget> + <item> + <layout class="QGridLayout" name="gridLayout_2"> + <item row="1" column="1"> + <widget class="QComboBox" name="DiveType"/> + </item> + <item row="0" column="0"> + <widget class="QLabel" name="TagLabel"> + <property name="text"> + <string>Tags</string> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QLabel" name="TypeLabel"> + <property name="text"> + <string>Dive mode</string> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="TagWidget" name="tagWidget"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>0</width> + <height>0</height> + </size> + </property> + <property name="verticalScrollBarPolicy"> + <enum>Qt::ScrollBarAlwaysOff</enum> + </property> + <property name="horizontalScrollBarPolicy"> + <enum>Qt::ScrollBarAlwaysOff</enum> + </property> + <property name="lineWrapMode"> + <enum>QPlainTextEdit::NoWrap</enum> + </property> + </widget> + </item> + </layout> </item> - <item row="16" column="0"> + <item> <widget class="QLabel" name="NotesLabel"> <property name="text"> <string>Notes</string> </property> </widget> </item> - <item row="15" column="0" colspan="3"> - <widget class="TagWidget" name="tagWidget"> - <property name="sizePolicy"> - <sizepolicy hsizetype="Expanding" vsizetype="Fixed"> - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> - </property> - <property name="minimumSize"> - <size> - <width>0</width> - <height>0</height> - </size> - </property> - <property name="verticalScrollBarPolicy"> - <enum>Qt::ScrollBarAlwaysOff</enum> - </property> - <property name="horizontalScrollBarPolicy"> - <enum>Qt::ScrollBarAlwaysOff</enum> - </property> - <property name="lineWrapMode"> - <enum>QPlainTextEdit::NoWrap</enum> - </property> - </widget> - </item> - <item row="3" column="0" colspan="3"> - <widget class="QLineEdit" name="location"> - <property name="readOnly"> - <bool>false</bool> - </property> - </widget> - </item> - <item row="17" column="0" colspan="3"> + <item> <layout class="QHBoxLayout" name="notesAndSocialNetworksLayout"> <property name="spacing"> <number>0</number> @@ -371,20 +357,6 @@ </item> </layout> </item> - <item row="7" column="0" colspan="3"> - <layout class="QHBoxLayout" name="coordinatesDiveTypeLayout"> - <item> - <widget class="QLineEdit" name="coordinates"> - <property name="readOnly"> - <bool>false</bool> - </property> - </widget> - </item> - <item> - <widget class="QComboBox" name="DiveType"/> - </item> - </layout> - </item> </layout> </widget> </widget> @@ -415,8 +387,8 @@ <rect> <x>0</x> <y>0</y> - <width>397</width> - <height>734</height> + <width>98</width> + <height>55</height> </rect> </property> <layout class="QGridLayout" name="equipmentTabScrollAreaLayout"> @@ -449,7 +421,7 @@ </widget> <widget class="QWidget" name="infoTab"> <attribute name="title"> - <string>Dive info</string> + <string>Info</string> </attribute> <layout class="QGridLayout" name="diveInfoLayout"> <item row="0" column="0"> @@ -471,8 +443,8 @@ <rect> <x>0</x> <y>0</y> - <width>397</width> - <height>734</height> + <width>330</width> + <height>334</height> </rect> </property> <layout class="QGridLayout" name="diveInfoScrollAreaLayout"> @@ -788,8 +760,8 @@ <rect> <x>0</x> <y>0</y> - <width>397</width> - <height>734</height> + <width>328</width> + <height>208</height> </rect> </property> <layout class="QGridLayout" name="statsScrollAreaLayout"> @@ -1039,6 +1011,12 @@ <extends>QListView</extends> <header>divepicturewidget.h</header> </customwidget> + <customwidget> + <class>QtWaitingSpinner</class> + <extends>QWidget</extends> + <header>qtwaitingspinner.h</header> + <container>1</container> + </customwidget> </customwidgets> <tabstops> <tabstop>dateEdit</tabstop> @@ -1051,7 +1029,6 @@ <tabstop>rating</tabstop> <tabstop>visibility</tabstop> <tabstop>suit</tabstop> - <tabstop>tagWidget</tabstop> <tabstop>notes</tabstop> </tabstops> <resources> diff --git a/qt-ui/mainwindow.cpp b/qt-ui/mainwindow.cpp index 82eaaac57..a06a2c227 100644 --- a/qt-ui/mainwindow.cpp +++ b/qt-ui/mainwindow.cpp @@ -11,7 +11,8 @@ #include <QSettings> #include <QShortcut> #include <QToolBar> -#include "ssrf-version.h" +#include "version.h" +#include "divelistview.h" #include "downloadfromdivecomputer.h" #include "preferences.h" #include "subsurfacewebservices.h" @@ -20,6 +21,10 @@ #include "updatemanager.h" #include "planner.h" #include "filtermodels.h" +#include "profile/profilewidget2.h" +#include "globe.h" +#include "maintab.h" +#include "diveplanner.h" #ifndef NO_PRINTING #include <QPrintDialog> #include "printdialog.h" @@ -27,10 +32,15 @@ #include "divelogimportdialog.h" #include "divelogexportdialog.h" #include "usersurvey.h" +#include "divesitehelpers.h" +#include "locationinformation.h" #ifndef NO_USERMANUAL #include "usermanual.h" #endif #include <QNetworkProxy> +#include <QUndoStack> +#include <qthelper.h> +#include <QtConcurrentRun> MainWindow *MainWindow::m_Instance = NULL; @@ -44,7 +54,26 @@ MainWindow::MainWindow() : QMainWindow(), Q_ASSERT_X(m_Instance == NULL, "MainWindow", "MainWindow recreated!"); m_Instance = this; ui.setupUi(this); - ui.multiFilter->hide(); + read_hashes(); + // Define the States of the Application Here, Currently the states are situations where the different + // widgets will change on the mainwindow. + + // for the "default" mode + MainTab *mainTab = new MainTab(); + DiveListView *diveListView = new DiveListView(); + ProfileWidget2 *profileWidget = new ProfileWidget2(); + +#ifndef NO_MARBLE + GlobeGPS *globeGps = new GlobeGPS(); +#else + QWidget *globeGps = NULL; +#endif + + PlannerSettingsWidget *plannerSettings = new PlannerSettingsWidget(); + DivePlannerWidget *plannerWidget = new DivePlannerWidget(); + PlannerDetails *plannerDetails = new PlannerDetails(); + LocationInformationWidget *locationInformation = new LocationInformationWidget(); + // what is a sane order for those icons? we should have the ones the user is // most likely to want towards the top so they are always visible // and the ones that someone likely sets and then never touches again towards the bottom @@ -57,69 +86,82 @@ MainWindow::MainWindow() : QMainWindow(), ui.profEad << ui.profSAC << ui.profHR << // very few dive computers support this ui.profTissues; // maybe less frequently used + + QToolBar *toolBar = new QToolBar(); + Q_FOREACH (QAction *a, profileToolbarActions) + toolBar->addAction(a); + toolBar->setOrientation(Qt::Vertical); + toolBar->setIconSize(QSize(24,24)); + + QWidget *profileContainer = new QWidget(); + QHBoxLayout *profLayout = new QHBoxLayout(); + profLayout->addWidget(toolBar); + profLayout->addWidget(profileWidget); + profileContainer->setLayout(profLayout); + + registerApplicationState("Default", mainTab, profileContainer, diveListView, globeGps ); + registerApplicationState("AddDive", mainTab, profileContainer, diveListView, globeGps ); + registerApplicationState("EditDive", mainTab, profileContainer, diveListView, globeGps ); + registerApplicationState("PlanDive", plannerWidget, profileContainer, plannerSettings, plannerDetails ); + registerApplicationState("EditPlannedDive", plannerWidget, profileContainer, diveListView, globeGps ); + registerApplicationState("EditDiveSite",locationInformation, profileContainer, diveListView, globeGps ); + + setApplicationState("Default"); + + ui.multiFilter->hide(); + setWindowIcon(QIcon(":subsurface-icon")); if (!QIcon::hasThemeIcon("window-close")) { QIcon::setThemeName("subsurface"); } - connect(ui.ListWidget, SIGNAL(currentDiveChanged(int)), this, SLOT(current_dive_changed(int))); + connect(dive_list(), SIGNAL(currentDiveChanged(int)), this, SLOT(current_dive_changed(int))); connect(PreferencesDialog::instance(), SIGNAL(settingsChanged()), this, SLOT(readSettings())); - connect(PreferencesDialog::instance(), SIGNAL(settingsChanged()), ui.ListWidget, SLOT(update())); - connect(PreferencesDialog::instance(), SIGNAL(settingsChanged()), ui.ListWidget, SLOT(reloadHeaderActions())); - connect(PreferencesDialog::instance(), SIGNAL(settingsChanged()), ui.InfoWidget, SLOT(updateDiveInfo())); - connect(PreferencesDialog::instance(), SIGNAL(settingsChanged()), ui.divePlannerWidget, SLOT(settingsChanged())); - connect(PreferencesDialog::instance(), SIGNAL(settingsChanged()), ui.plannerSettingsWidget, SLOT(settingsChanged())); + connect(PreferencesDialog::instance(), SIGNAL(settingsChanged()), diveListView, SLOT(update())); + connect(PreferencesDialog::instance(), SIGNAL(settingsChanged()), diveListView, SLOT(reloadHeaderActions())); + connect(PreferencesDialog::instance(), SIGNAL(settingsChanged()), information(), SLOT(updateDiveInfo())); + connect(PreferencesDialog::instance(), SIGNAL(settingsChanged()), divePlannerWidget(), SLOT(settingsChanged())); + connect(PreferencesDialog::instance(), SIGNAL(settingsChanged()), divePlannerSettingsWidget(), SLOT(settingsChanged())); connect(PreferencesDialog::instance(), SIGNAL(settingsChanged()), TankInfoModel::instance(), SLOT(update())); connect(ui.actionRecent1, SIGNAL(triggered(bool)), this, SLOT(recentFileTriggered(bool))); connect(ui.actionRecent2, SIGNAL(triggered(bool)), this, SLOT(recentFileTriggered(bool))); connect(ui.actionRecent3, SIGNAL(triggered(bool)), this, SLOT(recentFileTriggered(bool))); connect(ui.actionRecent4, SIGNAL(triggered(bool)), this, SLOT(recentFileTriggered(bool))); - connect(information(), SIGNAL(addDiveFinished()), ui.newProfile, SLOT(setProfileState())); + connect(information(), SIGNAL(addDiveFinished()), graphics(), SLOT(setProfileState())); connect(DivePlannerPointsModel::instance(), SIGNAL(planCreated()), this, SLOT(planCreated())); connect(DivePlannerPointsModel::instance(), SIGNAL(planCanceled()), this, SLOT(planCanceled())); - connect(ui.printPlan, SIGNAL(pressed()), ui.divePlannerWidget, SLOT(printDecoPlan())); + connect(plannerDetails->printPlan(), SIGNAL(pressed()), divePlannerWidget(), SLOT(printDecoPlan())); + connect(mainTab, SIGNAL(requestDiveSiteEdit(uint32_t)), this, SLOT(enableDiveSiteEdit(uint32_t))); + connect(locationInformation, SIGNAL(informationManagementEnded()), this, SLOT(setDefaultState())); + connect(locationInformation, SIGNAL(informationManagementEnded()), information(), SLOT(showLocation())); + #ifdef NO_PRINTING - ui.printPlan->hide(); + plannerDetails->printPlan()->hide(); + ui.menuFile->removeAction(ui.actionPrint); #endif ui.mainErrorMessage->hide(); - ui.newProfile->setEmptyState(); + graphics()->setEmptyState(); initialUiSetup(); readSettings(); - ui.ListWidget->reload(DiveTripModel::TREE); - ui.ListWidget->reloadHeaderActions(); - ui.ListWidget->setFocus(); - ui.globe->reload(); - ui.ListWidget->expand(ui.ListWidget->model()->index(0, 0)); - ui.ListWidget->scrollTo(ui.ListWidget->model()->index(0, 0), QAbstractItemView::PositionAtCenter); - ui.divePlannerWidget->settingsChanged(); - ui.plannerSettingsWidget->settingsChanged(); + diveListView->reload(DiveTripModel::TREE); + diveListView->reloadHeaderActions(); + diveListView->setFocus(); + globe()->reload(); + diveListView->expand(dive_list()->model()->index(0, 0)); + diveListView->scrollTo(dive_list()->model()->index(0, 0), QAbstractItemView::PositionAtCenter); + divePlannerWidget()->settingsChanged(); + divePlannerSettingsWidget()->settingsChanged(); #ifdef NO_MARBLE - ui.globePane->hide(); ui.menuView->removeAction(ui.actionViewGlobe); #else - connect(ui.globe, SIGNAL(coordinatesChanged()), ui.InfoWidget, SLOT(updateGpsCoordinates())); + connect(globe(), SIGNAL(coordinatesChanged()), locationInformation, SLOT(updateGpsCoordinates())); #endif #ifdef NO_USERMANUAL ui.menuHelp->removeAction(ui.actionUserManual); #endif -#ifdef NO_PRINTING - ui.menuFile->removeAction(ui.actionPrint); -#endif memset(©PasteDive, 0, sizeof(copyPasteDive)); memset(&what, 0, sizeof(what)); - QToolBar *toolBar = new QToolBar(); - Q_FOREACH (QAction *a, profileToolbarActions) - toolBar->addAction(a); - toolBar->setOrientation(Qt::Vertical); - toolBar->setIconSize(QSize(24,24)); - // since I'm adding the toolBar by hand, because designer - // has no concept of "toolbar" for a non-mainwindow widget (...) - // I need to take the current item that's in the toolbar Position - // and reposition it alongside the grid layout. - QLayoutItem *p = ui.profileInnerLayout->takeAt(0); - ui.profileInnerLayout->addWidget(toolBar, 0, 0); - ui.profileInnerLayout->addItem(p, 0, 1); // and now for some layout hackery // this gets us consistent margins everywhere and a much more balanced look @@ -146,19 +188,52 @@ MainWindow::MainWindow() : QMainWindow(), else layout->setContentsMargins(margins); } - margins = QMargins(0, 5, 5, 5); - ui.profileInnerLayout->setContentsMargins(margins); - ui.profileInnerLayout->setSpacing(0); toolBar->setContentsMargins(zeroMargins); updateManager = new UpdateManager(this); + + undoStack = new QUndoStack(this); + QAction *undoAction = undoStack->createUndoAction(this, tr("&Undo")); + QAction *redoAction = undoStack->createRedoAction(this, tr("&Redo")); + undoAction->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_Z)); + redoAction->setShortcut(QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_Z)); + QList<QAction*>undoRedoActions; + undoRedoActions.append(undoAction); + undoRedoActions.append(redoAction); + ui.menu_Edit->addActions(undoRedoActions); + + ReverseGeoLoockupThread *geoLoockup = ReverseGeoLoockupThread::instance(); + connect(geoLoockup, SIGNAL(started()),information(), SLOT(disableGeoLoockupEdition())); + connect(geoLoockup, SIGNAL(finished()), information(), SLOT(enableGeoLoockupEdition())); } MainWindow::~MainWindow() { + write_hashes(); m_Instance = NULL; } +PlannerDetails *MainWindow::plannerDetails() const { + return qobject_cast<PlannerDetails*>(applicationState["PlanDive"].bottomRight); +} + +PlannerSettingsWidget *MainWindow::divePlannerSettingsWidget() { + return qobject_cast<PlannerSettingsWidget*>(applicationState["PlanDive"].bottomLeft); +} + +LocationInformationWidget *MainWindow::locationInformationWidget() { + return qobject_cast<LocationInformationWidget*>(applicationState["EditDiveSite"].topLeft); +} + +void MainWindow::enableDiveSiteEdit(uint32_t id) { + locationInformationWidget()->setLocationId(displayed_dive.dive_site_uuid); + setApplicationState("EditDiveSite"); +} + +void MainWindow::setDefaultState() { + setApplicationState("Default"); +} + void MainWindow::setLoadedWithFiles(bool f) { filesAsArguments = f; @@ -177,19 +252,16 @@ MainWindow *MainWindow::instance() // this gets called after we download dives from a divecomputer void MainWindow::refreshDisplay(bool doRecreateDiveList) { - showError(get_error_string()); - ui.InfoWidget->reload(); + getNotificationWidget()->showNotification(get_error_string(), KMessageWidget::Error); + information()->reload(); TankInfoModel::instance()->update(); - ui.globe->reload(); + globe()->reload(); if (doRecreateDiveList) recreateDiveList(); - ui.diveListPane->setCurrentIndex(0); // switch to the dive list -#ifdef NO_MARBLE - ui.globePane->hide(); -#endif - ui.globePane->setCurrentIndex(0); - ui.ListWidget->setEnabled(true); - ui.ListWidget->setFocus(); + + setApplicationState("Default"); + dive_list()->setEnabled(true); + dive_list()->setFocus(); WSInfoModel::instance()->updateInfo(); if (amount_selected == 0) cleanUpEmpty(); @@ -197,7 +269,7 @@ void MainWindow::refreshDisplay(bool doRecreateDiveList) void MainWindow::recreateDiveList() { - ui.ListWidget->reload(DiveTripModel::CURRENT); + dive_list()->reload(DiveTripModel::CURRENT); TagFilterModel::instance()->repopulate(); BuddyFilterModel::instance()->repopulate(); LocationFilterModel::instance()->repopulate(); @@ -208,10 +280,11 @@ void MainWindow::current_dive_changed(int divenr) { if (divenr >= 0) { select_dive(divenr); - ui.globe->centerOnCurrentDive(); + globe()->centerOnCurrentDive(); } - ui.newProfile->plotDive(); - ui.InfoWidget->updateDiveInfo(); + graphics()->plotDive(); + information()->updateDiveInfo(); + locationInformationWidget()->setLocationId(displayed_dive.dive_site_uuid); } void MainWindow::on_actionNew_triggered() @@ -252,20 +325,49 @@ void MainWindow::on_actionSaveAs_triggered() file_save_as(); } +void learnImageDirs(QStringList dirnames) +{ + QList<QFuture<void> > futures; + foreach (QString dir, dirnames) { + futures << QtConcurrent::run(learnImages, QDir(dir), 10, false); + } + DivePictureModel::instance()->updateDivePicturesWhenDone(futures); +} + +void MainWindow::on_actionHash_images_triggered() +{ + QFuture<void> future; + QFileDialog dialog(this, tr("Traverse image directories"), lastUsedDir(), filter()); + dialog.setFileMode(QFileDialog::Directory); + dialog.setViewMode(QFileDialog::Detail); + dialog.setLabelText(QFileDialog::Accept, tr("Scan")); + dialog.setLabelText(QFileDialog::Reject, tr("Cancel")); + QStringList dirnames; + if (dialog.exec()) + dirnames = dialog.selectedFiles(); + if (dirnames.isEmpty()) + return; + future = QtConcurrent::run(learnImageDirs,dirnames); + MainWindow::instance()->getNotificationWidget()->showNotification(tr("Scanning images...(this can take a while)"), KMessageWidget::Information); + MainWindow::instance()->getNotificationWidget()->setFuture(future); + +} + ProfileWidget2 *MainWindow::graphics() const { - return ui.newProfile; + return qobject_cast<ProfileWidget2*>(applicationState["Default"].topRight->layout()->itemAt(1)->widget()); } void MainWindow::cleanUpEmpty() { - ui.InfoWidget->clearStats(); - ui.InfoWidget->clearInfo(); - ui.InfoWidget->clearEquipment(); - ui.InfoWidget->updateDiveInfo(true); - ui.newProfile->setEmptyState(); - ui.ListWidget->reload(DiveTripModel::TREE); - ui.globe->reload(); + information()->clearStats(); + information()->clearInfo(); + information()->clearEquipment(); + information()->updateDiveInfo(true); + graphics()->setEmptyState(); + dive_list()->reload(DiveTripModel::TREE); + locationInformationWidget()->setLocationId(0); + globe()->reload(); if (!existing_filename) setTitle(MWTF_DEFAULT); disableShortcuts(); @@ -274,7 +376,8 @@ void MainWindow::cleanUpEmpty() bool MainWindow::okToClose(QString message) { if (DivePlannerPointsModel::instance()->currentMode() != DivePlannerPointsModel::NOTHING || - ui.InfoWidget->isEditing()) { + information()->isEditing() || + currentApplicationState == "EditDiveSite") { QMessageBox::warning(this, tr("Warning"), message); return false; } @@ -286,11 +389,13 @@ bool MainWindow::okToClose(QString message) void MainWindow::closeCurrentFile() { - ui.newProfile->setEmptyState(); + graphics()->setEmptyState(); /* free the dives and trips */ clear_git_id(); while (dive_table.nr) delete_single_dive(0); + while (dive_site_table.nr) + delete_dive_site(get_dive_site(0)->uuid); free((void *)existing_filename); existing_filename = NULL; @@ -362,8 +467,8 @@ void MainWindow::enableShortcuts() void MainWindow::showProfile() { enableShortcuts(); - ui.newProfile->setProfileState(); - ui.infoPane->setCurrentIndex(MAINTAB); + graphics()->setProfileState(); + setApplicationState("Default"); } void MainWindow::on_actionPreferences_triggered() @@ -373,9 +478,9 @@ void MainWindow::on_actionPreferences_triggered() void MainWindow::on_actionQuit_triggered() { - if (ui.InfoWidget->isEditing()) { - ui.InfoWidget->rejectChanges(); - if (ui.InfoWidget->isEditing()) + if (information()->isEditing()) { + information()->rejectChanges(); + if (information()->isEditing()) // didn't discard the edits return; } @@ -421,7 +526,7 @@ void MainWindow::on_actionEditDeviceNames_triggered() bool MainWindow::plannerStateClean() { if (DivePlannerPointsModel::instance()->currentMode() != DivePlannerPointsModel::NOTHING || - ui.InfoWidget->isEditing()) { + information()->isEditing()) { QMessageBox::warning(this, tr("Warning"), tr("Please save or cancel the current dive edit before trying to add a dive.")); return false; } @@ -433,16 +538,16 @@ void MainWindow::planCanceled() // while planning we might have modified the displayed_dive // let's refresh what's shown on the profile showProfile(); - ui.newProfile->replot(); + graphics()->replot(); refreshDisplay(false); - ui.newProfile->plotDive(get_dive(selected_dive)); + graphics()->plotDive(get_dive(selected_dive)); DivePictureModel::instance()->updateDivePictures(); } void MainWindow::planCreated() { // get the new dive selected and assign a number if reasonable - ui.newProfile->setProfileState(); + graphics()->setProfileState(); if (displayed_dive.id == 0) { // we might have added a new dive (so displayed_dive was cleared out by clone_dive() dive_list()->unselectDives(); @@ -451,20 +556,20 @@ void MainWindow::planCreated() set_dive_nr_for_current_dive(); } // make sure our UI is in a consistent state - ui.InfoWidget->updateDiveInfo(); + information()->updateDiveInfo(); showProfile(); refreshDisplay(); } void MainWindow::setPlanNotes(const char *notes) { - ui.divePlanOutput->setHtml(notes); + plannerDetails()->divePlanOutput()->setHtml(notes); } void MainWindow::printPlan() { #ifndef NO_PRINTING - QString diveplan = ui.divePlanOutput->toHtml(); + QString diveplan = plannerDetails()->divePlanOutput()->toHtml(); QString withDisclaimer = QString("<img height=50 src=\":subsurface-icon\"> ") + diveplan + QString(disclaimer); QPrinter printer; @@ -473,9 +578,9 @@ void MainWindow::printPlan() if (dialog->exec() != QDialog::Accepted) return; - ui.divePlanOutput->setHtml(withDisclaimer); - ui.divePlanOutput->print(&printer); - ui.divePlanOutput->setHtml(diveplan); + plannerDetails()->divePlanOutput()->setHtml(withDisclaimer); + plannerDetails()->divePlanOutput()->print(&printer); + plannerDetails()->divePlanOutput()->setHtml(diveplan); #endif } @@ -489,31 +594,28 @@ void MainWindow::setupForAddAndPlan(const char *model) // setup the dive cylinders DivePlannerPointsModel::instance()->clear(); DivePlannerPointsModel::instance()->setupCylinders(); + locationInformationWidget()->setLocationId(0); } void MainWindow::on_actionReplanDive_triggered() { - if (!plannerStateClean()) - return; - if (!current_dive || !current_dive->dc.model || strcmp(current_dive->dc.model, "planned dive")) { - qDebug() << "trying to replan a dive that's not a planned dive:" << current_dive->dc.model; + if (!plannerStateClean() || !current_dive || !current_dive->dc.model) return; + else if (strcmp(current_dive->dc.model, "planned dive")) { + if (QMessageBox::warning(this, tr("Warning"), tr("trying to replan a dive that's not a planned dive."), + QMessageBox::Ok | QMessageBox::Cancel) == QMessageBox::Cancel) + return; } // put us in PLAN mode DivePlannerPointsModel::instance()->clear(); DivePlannerPointsModel::instance()->setPlanMode(DivePlannerPointsModel::PLAN); - ui.newProfile->setPlanState(); - ui.newProfile->clearHandlers(); - ui.infoPane->setCurrentIndex(PLANNERWIDGET); - ui.divePlannerWidget->setReplanButton(true); + graphics()->setPlanState(); + graphics()->clearHandlers(); + setApplicationState("PlanDive"); + divePlannerWidget()->setReplanButton(true); DivePlannerPointsModel::instance()->loadFromDive(current_dive); reset_cylinders(&displayed_dive, true); - ui.diveListPane->setCurrentIndex(1); // switch to the plan output - ui.globePane->setCurrentIndex(1); -#ifdef NO_MARBLE - ui.globePane->show(); -#endif } void MainWindow::on_actionDivePlanner_triggered() @@ -523,22 +625,20 @@ void MainWindow::on_actionDivePlanner_triggered() // put us in PLAN mode DivePlannerPointsModel::instance()->setPlanMode(DivePlannerPointsModel::PLAN); + setApplicationState("PlanDive"); - ui.newProfile->setPlanState(); - ui.infoPane->setCurrentIndex(PLANNERWIDGET); + graphics()->setPlanState(); - // create a simple starting dive, using the first gas from the just copied cylidners + // create a simple starting dive, using the first gas from the just copied cylinders setupForAddAndPlan("planned dive"); // don't translate, stored in XML file DivePlannerPointsModel::instance()->setupStartTime(); DivePlannerPointsModel::instance()->createSimpleDive(); DivePictureModel::instance()->updateDivePictures(); - ui.divePlannerWidget->setReplanButton(false); + divePlannerWidget()->setReplanButton(false); +} - ui.diveListPane->setCurrentIndex(1); // switch to the plan output - ui.globePane->setCurrentIndex(1); -#ifdef NO_MARBLE - ui.globePane->show(); -#endif +DivePlannerWidget* MainWindow::divePlannerWidget() { + return qobject_cast<DivePlannerWidget*>(applicationState["PlanDive"].topLeft); } void MainWindow::on_actionAddDive_triggered() @@ -551,23 +651,45 @@ void MainWindow::on_actionAddDive_triggered() dive_list()->clearSelection(); } + setApplicationState("AddDive"); DivePlannerPointsModel::instance()->setPlanMode(DivePlannerPointsModel::ADD); // setup things so we can later create our starting dive setupForAddAndPlan("manually added dive"); // don't translate, stored in the XML file // now show the mostly empty main tab - ui.InfoWidget->updateDiveInfo(); + information()->updateDiveInfo(); // show main tab - ui.InfoWidget->setCurrentIndex(0); + information()->setCurrentIndex(0); - ui.InfoWidget->addDiveStarted(); - ui.infoPane->setCurrentIndex(MAINTAB); + information()->addDiveStarted(); - ui.newProfile->setAddState(); + graphics()->setAddState(); DivePlannerPointsModel::instance()->createSimpleDive(); - ui.newProfile->plotDive(); + graphics()->plotDive(); +} + +void MainWindow::on_actionEditDive_triggered() +{ + if (information()->isEditing() || DivePlannerPointsModel::instance()->currentMode() != DivePlannerPointsModel::NOTHING) { + QMessageBox::warning(this, tr("Warning"), tr("Please, first finish the current edition before trying to do another.")); + return; + } + + const bool isTripEdit = dive_list()->selectedTrips().count() >= 1; + if (!current_dive || isTripEdit || strcmp(current_dive->dc.model, "manually added dive")) { + QMessageBox::warning(this, tr("Warning"), tr("Trying to edit a dive that's not a manually added dive.")); + return; + } + + DivePlannerPointsModel::instance()->clear(); + disableShortcuts(); + DivePlannerPointsModel::instance()->setPlanMode(DivePlannerPointsModel::ADD); + graphics()->setAddState(); + setApplicationState("EditDive"); + DivePlannerPointsModel::instance()->loadFromDive(current_dive); + information()->enableEdition(MainTab::MANUALLY_ADDED_DIVE); } void MainWindow::on_actionRenumber_triggered() @@ -612,16 +734,16 @@ void MainWindow::on_actionYearlyStatistics_triggered() #define TOGGLE_COLLAPSABLE( X ) \ ui.mainSplitter->setCollapsible(0, X); \ ui.mainSplitter->setCollapsible(1, X); \ - ui.infoProfileSplitter->setCollapsible(0, X); \ - ui.infoProfileSplitter->setCollapsible(1, X); \ - ui.listGlobeSplitter->setCollapsible(0, X); \ - ui.listGlobeSplitter->setCollapsible(1, X); + ui.topSplitter->setCollapsible(0, X); \ + ui.topSplitter->setCollapsible(1, X); \ + ui.bottomSplitter->setCollapsible(0, X); \ + ui.bottomSplitter->setCollapsible(1, X); void MainWindow::on_actionViewList_triggered() { TOGGLE_COLLAPSABLE( true ); beginChangeState(LIST_MAXIMIZED); - ui.listGlobeSplitter->setSizes(BEHAVIOR << EXPANDED << COLLAPSED); + ui.topSplitter->setSizes(BEHAVIOR << EXPANDED << COLLAPSED); ui.mainSplitter->setSizes(BEHAVIOR << COLLAPSED << EXPANDED); } @@ -629,7 +751,7 @@ void MainWindow::on_actionViewProfile_triggered() { TOGGLE_COLLAPSABLE( true ); beginChangeState(PROFILE_MAXIMIZED); - ui.infoProfileSplitter->setSizes(BEHAVIOR << COLLAPSED << EXPANDED); + ui.topSplitter->setSizes(BEHAVIOR << COLLAPSED << EXPANDED); ui.mainSplitter->setSizes(BEHAVIOR << EXPANDED << COLLAPSED); } @@ -637,7 +759,7 @@ void MainWindow::on_actionViewInfo_triggered() { TOGGLE_COLLAPSABLE( true ); beginChangeState(INFO_MAXIMIZED); - ui.infoProfileSplitter->setSizes(BEHAVIOR << EXPANDED << COLLAPSED); + ui.topSplitter->setSizes(BEHAVIOR << EXPANDED << COLLAPSED); ui.mainSplitter->setSizes(BEHAVIOR << EXPANDED << COLLAPSED); } @@ -646,7 +768,7 @@ void MainWindow::on_actionViewGlobe_triggered() TOGGLE_COLLAPSABLE( true ); beginChangeState(GLOBE_MAXIMIZED); ui.mainSplitter->setSizes(BEHAVIOR << COLLAPSED << EXPANDED); - ui.listGlobeSplitter->setSizes(BEHAVIOR << COLLAPSED << EXPANDED); + ui.bottomSplitter->setSizes(BEHAVIOR << COLLAPSED << EXPANDED); } #undef BEHAVIOR @@ -677,26 +799,26 @@ void MainWindow::on_actionViewAll_triggered() settings.beginGroup("MainWindow"); if (settings.value("mainSplitter").isValid()) { ui.mainSplitter->restoreState(settings.value("mainSplitter").toByteArray()); - ui.infoProfileSplitter->restoreState(settings.value("infoProfileSplitter").toByteArray()); - ui.listGlobeSplitter->restoreState(settings.value("listGlobeSplitter").toByteArray()); + ui.topSplitter->restoreState(settings.value("topSplitter").toByteArray()); + ui.bottomSplitter->restoreState(settings.value("bottomSplitter").toByteArray()); if (ui.mainSplitter->sizes().first() == 0 || ui.mainSplitter->sizes().last() == 0) ui.mainSplitter->setSizes(mainSizes); - if (ui.infoProfileSplitter->sizes().first() == 0 || ui.infoProfileSplitter->sizes().last() == 0) - ui.infoProfileSplitter->setSizes(infoProfileSizes); - if (ui.listGlobeSplitter->sizes().first() == 0 || ui.listGlobeSplitter->sizes().last() == 0) - ui.listGlobeSplitter->setSizes(listGlobeSizes); + if (ui.topSplitter->sizes().first() == 0 || ui.topSplitter->sizes().last() == 0) + ui.topSplitter->setSizes(infoProfileSizes); + if (ui.bottomSplitter->sizes().first() == 0 || ui.bottomSplitter->sizes().last() == 0) + ui.bottomSplitter->setSizes(listGlobeSizes); } else { ui.mainSplitter->setSizes(mainSizes); - ui.infoProfileSplitter->setSizes(infoProfileSizes); - ui.listGlobeSplitter->setSizes(listGlobeSizes); + ui.topSplitter->setSizes(infoProfileSizes); + ui.bottomSplitter->setSizes(listGlobeSizes); } ui.mainSplitter->setCollapsible(0, false); ui.mainSplitter->setCollapsible(1, false); - ui.infoProfileSplitter->setCollapsible(0, false); - ui.infoProfileSplitter->setCollapsible(1, false); - ui.listGlobeSplitter->setCollapsible(0,false); - ui.listGlobeSplitter->setCollapsible(1,false); + ui.topSplitter->setCollapsible(0, false); + ui.topSplitter->setCollapsible(1, false); + ui.bottomSplitter->setCollapsible(0,false); + ui.bottomSplitter->setCollapsible(1,false); } #undef TOGGLE_COLLAPSABLE @@ -714,24 +836,24 @@ void MainWindow::saveSplitterSizes() QSettings settings; settings.beginGroup("MainWindow"); settings.setValue("mainSplitter", ui.mainSplitter->saveState()); - settings.setValue("infoProfileSplitter", ui.infoProfileSplitter->saveState()); - settings.setValue("listGlobeSplitter", ui.listGlobeSplitter->saveState()); + settings.setValue("topSplitter", ui.topSplitter->saveState()); + settings.setValue("bottomSplitter", ui.bottomSplitter->saveState()); } void MainWindow::on_actionPreviousDC_triggered() { unsigned nrdc = number_of_computers(current_dive); dc_number = (dc_number + nrdc - 1) % nrdc; - ui.newProfile->plotDive(); - ui.InfoWidget->updateDiveInfo(); + graphics()->plotDive(); + information()->updateDiveInfo(); } void MainWindow::on_actionNextDC_triggered() { unsigned nrdc = number_of_computers(current_dive); dc_number = (dc_number + 1) % nrdc; - ui.newProfile->plotDive(); - ui.InfoWidget->updateDiveInfo(); + graphics()->plotDive(); + information()->updateDiveInfo(); } void MainWindow::on_actionFullScreen_triggered(bool checked) @@ -802,6 +924,7 @@ QString MainWindow::filter() f += "UDDF (*.uddf *.UDDF);;"; f += "XML (*.xml *.XML)"; f += "Divesoft (*.dlf *.DLF)"; + f += "Datatrak/WLog Files (*.log *.LOG)"; return f; } @@ -937,7 +1060,7 @@ void MainWindow::checkSurvey(QSettings *s) s->setValue("FirstUse42", value); } // wait a week for production versions, but not at all for non-tagged builds - QString ver(VERSION_STRING); + QString ver(subsurface_version()); int waitTime = 7; QDate firstUse42 = s->value("FirstUse42").toDate(); if (run_survey || (firstUse42.daysTo(QDate().currentDate()) > waitTime && !s->contains("SurveyDone"))) { @@ -965,7 +1088,7 @@ void MainWindow::writeSettings() void MainWindow::closeEvent(QCloseEvent *event) { if (DivePlannerPointsModel::instance()->currentMode() != DivePlannerPointsModel::NOTHING || - ui.InfoWidget->isEditing()) { + information()->isEditing()) { on_actionQuit_triggered(); event->ignore(); return; @@ -994,17 +1117,17 @@ void MainWindow::closeEvent(QCloseEvent *event) DiveListView *MainWindow::dive_list() { - return ui.ListWidget; + return qobject_cast<DiveListView*>(applicationState["Default"].bottomLeft); } GlobeGPS *MainWindow::globe() { - return ui.globe; + return qobject_cast<GlobeGPS*>(applicationState["Default"].bottomRight); } MainTab *MainWindow::information() { - return ui.InfoWidget; + return qobject_cast<MainTab*>(applicationState["Default"].topLeft); } void MainWindow::loadRecentFiles(QSettings *s) @@ -1174,20 +1297,31 @@ int MainWindow::file_save_as(void) { QString filename; const char *default_filename = existing_filename; - filename = QFileDialog::getSaveFileName(this, tr("Save file as"), default_filename, - tr("Subsurface XML files (*.ssrf *.xml *.XML)")); + + // create a file dialog that allows us to save to a new file + QFileDialog selection_dialog(this, tr("Save file as"), default_filename, + tr("Subsurface XML files (*.ssrf *.xml *.XML)")); + selection_dialog.setAcceptMode(QFileDialog::AcceptSave); + selection_dialog.setFileMode(QFileDialog::AnyFile); + + /* if the exit/cancel button is pressed return */ + if (!selection_dialog.exec()) + return 0; + + /* get the first selected file */ + filename = selection_dialog.selectedFiles().at(0); if (filename.isNull() || filename.isEmpty()) return report_error("No filename to save into"); - if (ui.InfoWidget->isEditing()) - ui.InfoWidget->acceptChanges(); + if (information()->isEditing()) + information()->acceptChanges(); if (save_dives(filename.toUtf8().data())) { - showError(get_error_string()); + getNotificationWidget()->showNotification(get_error_string(), KMessageWidget::Error); return -1; } - showError(get_error_string()); + getNotificationWidget()->showNotification(get_error_string(), KMessageWidget::Error); set_filename(filename.toUtf8().data(), true); setTitle(MWTF_FILENAME); mark_divelist_changed(false); @@ -1202,8 +1336,8 @@ int MainWindow::file_save(void) if (!existing_filename) return file_save_as(); - if (ui.InfoWidget->isEditing()) - ui.InfoWidget->acceptChanges(); + if (information()->isEditing()) + information()->acceptChanges(); current_default = prefs.default_filename; if (strcmp(existing_filename, current_default) == 0) { @@ -1214,23 +1348,18 @@ int MainWindow::file_save(void) current_def_dir.mkpath(current_def_dir.absolutePath()); } if (save_dives(existing_filename)) { - showError(get_error_string()); + getNotificationWidget()->showNotification(get_error_string(), KMessageWidget::Error); return -1; } - showError(get_error_string()); + getNotificationWidget()->showNotification(get_error_string(), KMessageWidget::Error); mark_divelist_changed(false); addRecentFile(QStringList() << QString(existing_filename)); return 0; } -void MainWindow::showError(QString message) +NotificationWidget *MainWindow::getNotificationWidget() { - if (message.isEmpty()) - return; - ui.mainErrorMessage->setText(message); - ui.mainErrorMessage->setCloseButtonVisible(true); - ui.mainErrorMessage->setMessageType(KMessageWidget::Error); - ui.mainErrorMessage->animatedShow(); + return ui.mainErrorMessage; } void MainWindow::setTitle(enum MainWindowTitleFormat format) @@ -1284,6 +1413,18 @@ void MainWindow::importTxtFiles(const QStringList fileNames) refreshDisplay(); } +void MainWindow::showV2Dialog() +{ + // here we need to ask the user if / how they want to do the reverse geo coding + // for now this is just a warning that things could take a long time + QMessageBox d(QMessageBox::Information, + tr("Welcom to Subsurface %1").arg(subsurface_version()), + tr("Importing data files from earlier versions of Subsurface can take a significant amount of time"), + QMessageBox::Ok, + this); + d.exec(); +} + void MainWindow::loadFiles(const QStringList fileNames) { if (fileNames.isEmpty()) @@ -1301,14 +1442,26 @@ void MainWindow::loadFiles(const QStringList fileNames) set_filename(fileNamePtr.data(), true); setTitle(MWTF_FILENAME); } else { + if (!v2_question_shown && abort_read_of_old_file) { + v2_question_shown = true; + abort_read_of_old_file = false; + showV2Dialog(); + getNotificationWidget()->showNotification(tr("Please Wait, Importing your files..."), KMessageWidget::Information); + i--; // so we re-try this file + continue; + } failedParses.append(fileNames.at(i)); } } - + getNotificationWidget()->hideNotification(); process_dives(false, false); addRecentFile(fileNames); removeRecentFile(failedParses); + // searches for geo lookup information in a thread so it doesn`t + // freezes the ui. + ReverseGeoLoockupThread::instance()->start(); + refreshDisplay(); ui.actionAutoGroup->setChecked(autogroup); } @@ -1316,18 +1469,21 @@ void MainWindow::loadFiles(const QStringList fileNames) void MainWindow::on_actionImportDiveLog_triggered() { QStringList fileNames = QFileDialog::getOpenFileNames(this, tr("Open dive log file"), lastUsedDir(), - tr("Dive log files (*.ssrf *.can *.csv *.db *.dld *.jlb *.lvd *.sde *.udcf *.uddf *.xml *.txt *.dlf *.apd);;" - "Cochran files (*.can);;" - "CSV files (*.csv);;" - "DiveLog.de files (*.dld);;" - "JDiveLog files (*.jlb);;" - "Liquivision files (*.lvd);;" - "MkVI files (*.txt);;" - "Suunto files (*.sde *.db);;" - "Divesoft files (*.dlf);;" - "UDDF/UDCF files (*.uddf *.udcf);;" - "XML files (*.xml);;" - "APD log viewer (*.apd);;" + tr("Dive log files (*.ssrf *.can *.csv *.db *.dld *.jlb *.lvd *.sde *.udcf *.uddf *.xml *.txt *.dlf *.apd" + "*.SSRF *.CAN *.CSV *.DB *.DLD *.JLB *.LVD *.SDE *.UDCF *.UDDF *.xml *.TXT *.DLF *.APD);;" + "Cochran files (*.can *.CAN);;" + "CSV files (*.csv *.CSV);;" + "DiveLog.de files (*.dld *.DLD);;" + "JDiveLog files (*.jlb *.JLB);;" + "Liquivision files (*.lvd *.LVD);;" + "MkVI files (*.txt *.TXT);;" + "Suunto files (*.sde *.db *.SDE *.DB);;" + "Divesoft files (*.dlf *.DLF);;" + "UDDF/UDCF files (*.uddf *.udcf *.UDDF *.UDCF);;" + "XML files (*.xml *.XML);;" + "APD log viewer (*.apd *.APD);;" + "Datatrak/WLog Files (*.log *.LOG);;" + "OSTCtools Files (*.dive *.DIVE);;" "All files (*)")); if (fileNames.isEmpty()) @@ -1368,20 +1524,16 @@ void MainWindow::editCurrentDive() if (defaultDC == "manually added dive") { disableShortcuts(); DivePlannerPointsModel::instance()->setPlanMode(DivePlannerPointsModel::ADD); - ui.newProfile->setAddState(); - ui.infoPane->setCurrentIndex(MAINTAB); + graphics()->setAddState(); + setApplicationState("EditDive"); DivePlannerPointsModel::instance()->loadFromDive(d); - ui.InfoWidget->enableEdition(MainTab::MANUALLY_ADDED_DIVE); + information()->enableEdition(MainTab::MANUALLY_ADDED_DIVE); } else if (defaultDC == "planned dive") { disableShortcuts(); DivePlannerPointsModel::instance()->setPlanMode(DivePlannerPointsModel::PLAN); - //TODO: I BROKE THIS BY COMMENTING THE LINE BELOW - // and I'm sleepy now, so I think I should not try to fix right away. - // we don't setCurrentIndex anymore, we ->setPlanState() or ->setAddState() on the ProfileView. - //ui.stackedWidget->setCurrentIndex(PLANNERPROFILE); // Planner. - ui.infoPane->setCurrentIndex(PLANNERWIDGET); + setApplicationState("EditPlannedDive"); DivePlannerPointsModel::instance()->loadFromDive(d); - ui.InfoWidget->enableEdition(MainTab::MANUALLY_ADDED_DIVE); + information()->enableEdition(MainTab::MANUALLY_ADDED_DIVE); } } @@ -1456,7 +1608,7 @@ void MainWindow::on_paste_triggered() { // take the data in our copyPasteDive and apply it to selected dives selective_copy_dive(©PasteDive, &displayed_dive, what, false); - ui.InfoWidget->showAndTriggerEditSelective(what); + information()->showAndTriggerEditSelective(what); } void MainWindow::on_actionFilterTags_triggered() @@ -1466,3 +1618,43 @@ void MainWindow::on_actionFilterTags_triggered() else ui.multiFilter->setVisible(true); } + +void MainWindow::registerApplicationState(const QByteArray& state, QWidget *topLeft, QWidget *topRight, QWidget *bottomLeft, QWidget *bottomRight) +{ + applicationState[state] = WidgetForQuadrant(topLeft, topRight, bottomLeft, bottomRight); + if (ui.topLeft->indexOf(topLeft) == -1 && topLeft) { + ui.topLeft->addWidget(topLeft); + } + if (ui.topRight->indexOf(topRight) == -1 && topRight) { + ui.topRight->addWidget(topRight); + } + if (ui.bottomLeft->indexOf(bottomLeft) == -1 && bottomLeft) { + ui.bottomLeft->addWidget(bottomLeft); + } + if(ui.bottomRight->indexOf(bottomRight) == -1 && bottomRight) { + ui.bottomRight->addWidget(bottomRight); + } +} + +void MainWindow::setApplicationState(const QByteArray& state) { + if (!applicationState.keys().contains(state)) + return; + + if (currentApplicationState == state) + return; + + currentApplicationState = state; +#define SET_CURRENT_INDEX( X ) \ + if (applicationState[state].X) { \ + ui.X->setCurrentWidget( applicationState[state].X); \ + ui.X->show(); \ + } else { \ + ui.X->hide(); \ + } + + SET_CURRENT_INDEX( topLeft ) + SET_CURRENT_INDEX( topRight ) + SET_CURRENT_INDEX( bottomLeft ) + SET_CURRENT_INDEX( bottomRight ) +#undef SET_CURRENT_INDEX +} diff --git a/qt-ui/mainwindow.h b/qt-ui/mainwindow.h index 2364caadc..f963330cc 100644 --- a/qt-ui/mainwindow.h +++ b/qt-ui/mainwindow.h @@ -10,8 +10,10 @@ #include <QMainWindow> #include <QAction> #include <QUrl> +#include <QUuid> #include "ui_mainwindow.h" +#include "notificationwidget.h" struct DiveList; class QSortFilterProxyModel; @@ -30,6 +32,12 @@ class QWebView; class QSettings; class UpdateManager; class UserManual; +class DivePlannerWidget; +class ProfileWidget2; +class PlannerDetails; +class PlannerSettingsWidget; +class QUndoStack; +class LocationInformationWidget; enum MainWindowTitleFormat { MWTF_DEFAULT, @@ -43,10 +51,7 @@ public: COLLAPSED, EXPANDED }; - enum InfoWidgetIndexes { - MAINTAB, - PLANNERWIDGET - }; + enum CurrentState { VIEWALL, GLOBE_MAXIMIZED, @@ -64,7 +69,9 @@ public: void removeRecentFile(QStringList failedFiles); DiveListView *dive_list(); GlobeGPS *globe(); - void showError(QString message); + DivePlannerWidget *divePlannerWidget(); + PlannerSettingsWidget *divePlannerSettingsWidget(); + LocationInformationWidget *locationInformationWidget(); void setTitle(enum MainWindowTitleFormat format); // Some shortcuts like "change DC" or "copy/paste dive components" @@ -77,11 +84,16 @@ public: void cleanUpEmpty(); void setToolButtonsEnabled(bool enabled); ProfileWidget2 *graphics() const; + PlannerDetails *plannerDetails() const; void setLoadedWithFiles(bool filesFromCommandLine); bool filesFromCommandLine() const; void setPlanNotes(const char *notes); void printPlan(); void checkSurvey(QSettings *s); + void setApplicationState(const QByteArray& state); + void showV2Dialog(); + QUndoStack *undoStack; + NotificationWidget *getNotificationWidget(); private slots: /* file menu action */ @@ -94,6 +106,7 @@ slots: void on_actionPrint_triggered(); void on_actionPreferences_triggered(); void on_actionQuit_triggered(); + void on_actionHash_images_triggered(); /* log menu actions */ void on_actionDownloadDC_triggered(); @@ -101,6 +114,7 @@ slots: void on_actionDivelogs_de_triggered(); void on_actionEditDeviceNames_triggered(); void on_actionAddDive_triggered(); + void on_actionEditDive_triggered(); void on_actionRenumber_triggered(); void on_actionAutoGroup_triggered(); void on_actionYearlyStatistics_triggered(); @@ -151,6 +165,8 @@ slots: void on_paste_triggered(); void on_actionFilterTags_triggered(); void on_actionConfigure_Dive_Computer_triggered(); + void enableDiveSiteEdit(uint32_t id); + void setDefaultState(); protected: void closeEvent(QCloseEvent *); @@ -185,6 +201,7 @@ private: void saveSplitterSizes(); QString lastUsedDir(); void updateLastUsedDir(const QString &s); + void registerApplicationState(const QByteArray& state, QWidget *topLeft, QWidget *topRight, QWidget *bottomLeft, QWidget *bottomRight); bool filesAsArguments; UpdateManager *updateManager; @@ -194,6 +211,17 @@ private: struct dive copyPasteDive; struct dive_components what; QList<QAction *> profileToolbarActions; + + struct WidgetForQuadrant { + WidgetForQuadrant(QWidget *tl = 0, QWidget *tr = 0, QWidget *bl = 0, QWidget *br = 0) : + topLeft(tl), topRight(tr), bottomLeft(bl), bottomRight(br) {} + QWidget *topLeft; + QWidget *topRight; + QWidget *bottomLeft; + QWidget *bottomRight; + }; + QHash<QByteArray, WidgetForQuadrant> applicationState; + QByteArray currentApplicationState; }; #endif // MAINWINDOW_H diff --git a/qt-ui/mainwindow.ui b/qt-ui/mainwindow.ui index 8ffb8bbd8..8e98bed6c 100644 --- a/qt-ui/mainwindow.ui +++ b/qt-ui/mainwindow.ui @@ -6,8 +6,8 @@ <rect> <x>0</x> <y>0</y> - <width>1682</width> - <height>1151</height> + <width>861</width> + <height>800</height> </rect> </property> <widget class="QWidget" name="centralwidget"> @@ -23,169 +23,24 @@ <property name="orientation"> <enum>Qt::Vertical</enum> </property> - <widget class="QSplitter" name="infoProfileSplitter"> + <widget class="QSplitter" name="topSplitter"> <property name="orientation"> <enum>Qt::Horizontal</enum> </property> - <widget class="QStackedWidget" name="infoPane"> - <property name="currentIndex"> - <number>0</number> - </property> - <widget class="QWidget" name="page"> - <layout class="QHBoxLayout" name="mainTabOuterLayout"> - <property name="spacing"> - <number>0</number> - </property> - <item> - <widget class="MainTab" name="InfoWidget" native="true"/> - </item> - </layout> - </widget> - <widget class="QWidget" name="page_2"> - <property name="enabled"> - <bool>true</bool> - </property> - <layout class="QHBoxLayout" name="divePlannerLayout"> - <item> - <widget class="DivePlannerWidget" name="divePlannerWidget" native="true"> - <property name="enabled"> - <bool>true</bool> - </property> - </widget> - </item> - </layout> - </widget> - </widget> - <widget class="QWidget" name="ProfileWidget"> - <layout class="QGridLayout" name="profileInnerLayout"> - <item row="0" column="0" rowspan="3"> - <widget class="ProfileWidget2" name="newProfile"/> - </item> - </layout> - </widget> + <widget class="QStackedWidget" name="topLeft"/> + <widget class="QStackedWidget" name="topRight"/> </widget> - <widget class="QSplitter" name="listGlobeSplitter"> + <widget class="QSplitter" name="bottomSplitter"> <property name="orientation"> <enum>Qt::Horizontal</enum> </property> - <widget class="QStackedWidget" name="diveListPane"> - <property name="currentIndex"> - <number>0</number> - </property> - <widget class="QWidget" name="page_3"> - <layout class="QVBoxLayout" name="diveListLayout"> - <item> - <widget class="DiveListView" name="ListWidget"> - <property name="selectionMode"> - <enum>QAbstractItemView::ExtendedSelection</enum> - </property> - </widget> - </item> - </layout> - </widget> - <widget class="PlannerSettingsWidget" name="plannerSettingsWidget"/> - </widget> - <widget class="QStackedWidget" name="globePane"> - <property name="currentIndex"> - <number>0</number> - </property> - <widget class="QWidget" name="stackedWidgetPage1"> - <property name="sizePolicy"> - <sizepolicy hsizetype="Expanding" vsizetype="Expanding"> - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> - </property> - <layout class="QVBoxLayout" name="globeLayout"> - <item> - <widget class="GlobeGPS" name="globe" native="true"/> - </item> - </layout> - </widget> - <widget class="QWidget" name="page_5"> - <property name="sizePolicy"> - <sizepolicy hsizetype="Expanding" vsizetype="Expanding"> - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> - </property> - <layout class="QVBoxLayout" name="verticalLayout_3"> - <item> - <layout class="QHBoxLayout" name="divePlanLayout"> - <item> - <widget class="QLabel" name="divePlanOutputLabel"> - <property name="maximumSize"> - <size> - <width>16777215</width> - <height>20</height> - </size> - </property> - <property name="text"> - <string><html><head/><body><p><span style=" font-weight:600;">Dive plan details</span></p></body></html></string> - </property> - <property name="textFormat"> - <enum>Qt::RichText</enum> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="printPlan"> - <property name="sizePolicy"> - <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> - </property> - <property name="text"> - <string>Print</string> - </property> - <property name="autoDefault"> - <bool>false</bool> - </property> - <property name="default"> - <bool>false</bool> - </property> - <property name="flat"> - <bool>false</bool> - </property> - </widget> - </item> - </layout> - </item> - <item> - <widget class="QTextEdit" name="divePlanOutput"> - <property name="enabled"> - <bool>true</bool> - </property> - <property name="sizePolicy"> - <sizepolicy hsizetype="Preferred" vsizetype="Preferred"> - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> - </property> - <property name="styleSheet"> - <string notr="true">font: 13pt "Courier";</string> - </property> - <property name="readOnly"> - <bool>true</bool> - </property> - <property name="html"> - <string><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> -p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'Courier'; font-size:13pt; font-weight:400; font-style:normal;"> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'.Curier New';"><br /></p></body></html></string> - </property> - </widget> - </item> - </layout> - </widget> - </widget> + <widget class="QStackedWidget" name="bottomLeft"/> + <widget class="QStackedWidget" name="bottomRight"/> </widget> </widget> </item> <item> - <widget class="KMessageWidget" name="mainErrorMessage" native="true"/> + <widget class="NotificationWidget" name="mainErrorMessage" native="true"/> </item> </layout> </widget> @@ -194,8 +49,8 @@ p, li { white-space: pre-wrap; } <rect> <x>0</x> <y>0</y> - <width>1682</width> - <height>27</height> + <width>861</width> + <height>22</height> </rect> </property> <widget class="QMenu" name="menuFile"> @@ -212,6 +67,7 @@ p, li { white-space: pre-wrap; } <addaction name="actionPrint"/> <addaction name="actionPreferences"/> <addaction name="separator"/> + <addaction name="actionHash_images"/> <addaction name="actionConfigure_Dive_Computer"/> <addaction name="separator"/> <addaction name="actionRecent1"/> @@ -226,6 +82,7 @@ p, li { white-space: pre-wrap; } <string>&Log</string> </property> <addaction name="actionAddDive"/> + <addaction name="actionEditDive"/> <addaction name="actionDivePlanner"/> <addaction name="actionReplanDive"/> <addaction name="copy"/> @@ -271,7 +128,13 @@ p, li { white-space: pre-wrap; } <addaction name="actionDownloadWeb"/> <addaction name="actionDivelogs_de"/> </widget> + <widget class="QMenu" name="menu_Edit"> + <property name="title"> + <string>&Edit</string> + </property> + </widget> <addaction name="menuFile"/> + <addaction name="menu_Edit"/> <addaction name="menuImport"/> <addaction name="menuLog"/> <addaction name="menuView"/> @@ -391,6 +254,11 @@ p, li { white-space: pre-wrap; } <string>Ctrl++</string> </property> </action> + <action name="actionEditDive"> + <property name="text"> + <string>&Edit dive</string> + </property> + </action> <action name="copy"> <property name="text"> <string>&Copy dive components</string> @@ -590,7 +458,7 @@ p, li { white-space: pre-wrap; } </action> <action name="actionReplanDive"> <property name="text"> - <string>Re-plan &dive</string> + <string>Edit &dive in planner</string> </property> </action> <action name="profPO2"> @@ -813,46 +681,33 @@ p, li { white-space: pre-wrap; } <string>User &survey</string> </property> </action> + <action name="action_Undo"> + <property name="text"> + <string>&Undo</string> + </property> + <property name="shortcut"> + <string>Ctrl+Z</string> + </property> + </action> + <action name="action_Redo"> + <property name="text"> + <string>&Redo</string> + </property> + <property name="shortcut"> + <string>Ctrl+Shift+Z</string> + </property> + </action> + <action name="actionHash_images"> + <property name="text"> + <string>Find moved images</string> + </property> + </action> </widget> <customwidgets> <customwidget> - <class>KMessageWidget</class> - <extends>QWidget</extends> - <header>kmessagewidget.h</header> - <container>1</container> - </customwidget> - <customwidget> - <class>MainTab</class> - <extends>QWidget</extends> - <header>qt-ui/maintab.h</header> - <container>1</container> - </customwidget> - <customwidget> - <class>DiveListView</class> - <extends>QTreeView</extends> - <header>divelistview.h</header> - </customwidget> - <customwidget> - <class>GlobeGPS</class> - <extends>QWidget</extends> - <header>globe.h</header> - <container>1</container> - </customwidget> - <customwidget> - <class>DivePlannerWidget</class> - <extends>QWidget</extends> - <header>diveplanner.h</header> - <container>1</container> - </customwidget> - <customwidget> - <class>ProfileWidget2</class> - <extends>QGraphicsView</extends> - <header>qt-ui/profile/profilewidget2.h</header> - </customwidget> - <customwidget> - <class>PlannerSettingsWidget</class> + <class>NotificationWidget</class> <extends>QWidget</extends> - <header>diveplanner.h</header> + <header>notificationwidget.h</header> <container>1</container> </customwidget> <customwidget> diff --git a/qt-ui/modeldelegates.cpp b/qt-ui/modeldelegates.cpp index ee7dc6cf5..66533b652 100644 --- a/qt-ui/modeldelegates.cpp +++ b/qt-ui/modeldelegates.cpp @@ -2,8 +2,13 @@ #include "dive.h" #include "gettextfromc.h" #include "mainwindow.h" +#include "models.h" +#include "starwidget.h" +#include "profile/profilewidget2.h" #include <QCompleter> +#include <QKeyEvent> +#include <QTextDocument> QSize DiveListDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const { @@ -247,7 +252,7 @@ TankInfoDelegate::TankInfoDelegate(QObject *parent) : ComboBoxDelegate(TankInfoM void TankInfoDelegate::reenableReplot(QWidget *widget, QAbstractItemDelegate::EndEditHint hint) { MainWindow::instance()->graphics()->setReplot(true); - // FIXME: We need to replot after a cylidner is selected but the replot below overwrites + // FIXME: We need to replot after a cylinder is selected but the replot below overwrites // the newly selected cylinder. // MainWindow::instance()->graphics()->replot(); } diff --git a/qt-ui/models.cpp b/qt-ui/models.cpp index fec7d02ed..69a276bfb 100644 --- a/qt-ui/models.cpp +++ b/qt-ui/models.cpp @@ -14,6 +14,7 @@ #include "qthelper.h" #include "gettextfromc.h" #include "display.h" +#include "color.h" #include <QCoreApplication> #include <QDebug> @@ -387,6 +388,7 @@ Qt::ItemFlags CylindersModel::flags(const QModelIndex &index) const void CylindersModel::remove(const QModelIndex &index) { + int mapping[MAX_CYLINDERS]; if (index.column() != REMOVE) { return; } @@ -394,6 +396,7 @@ void CylindersModel::remove(const QModelIndex &index) cylinder_t *cyl = &displayed_dive.cylinder[index.row()]; struct gasmix *mygas = &cyl->gasmix; for (int i = 0; i < MAX_CYLINDERS; i++) { + mapping[i] = i; if (i == index.row() || cylinder_none(&displayed_dive.cylinder[i])) continue; struct gasmix *gas2 = &displayed_dive.cylinder[i].gasmix; @@ -404,7 +407,7 @@ void CylindersModel::remove(const QModelIndex &index) ((DivePlannerPointsModel::instance()->currentMode() != DivePlannerPointsModel::NOTHING && DivePlannerPointsModel::instance()->tankInUse(cyl->gasmix)) || (DivePlannerPointsModel::instance()->currentMode() == DivePlannerPointsModel::NOTHING && - (cyl->manually_added || is_cylinder_used(&displayed_dive, index.row()))))) { + is_cylinder_used(&displayed_dive, index.row())))) { QMessageBox::warning(MainWindow::instance(), TITLE_OR_TEXT( tr("Cylinder cannot be removed"), tr("This gas is in use. Only cylinders that are not used in the dive can be removed.")), @@ -418,11 +421,24 @@ void CylindersModel::remove(const QModelIndex &index) // as first gas memmove(cyl, &displayed_dive.cylinder[same_gas], sizeof(*cyl)); remove_cylinder(&displayed_dive, same_gas); + mapping[same_gas] = 0; + for (int i = same_gas + 1; i < MAX_CYLINDERS; i++) + mapping[i] = i - 1; } else { remove_cylinder(&displayed_dive, index.row()); + if (same_gas > index.row()) + same_gas--; + mapping[index.row()] = same_gas; + for (int i = index.row() + 1; i < MAX_CYLINDERS; i++) + mapping[i] = i - 1; } changed = true; endRemoveRows(); + struct divecomputer *dc = &displayed_dive.dc; + while (dc) { + dc_cylinder_renumber(&displayed_dive, dc, mapping); + dc = dc->next; + } } WeightModel::WeightModel(QObject *parent) : CleanerTableModel(parent), @@ -1191,7 +1207,7 @@ QVariant DiveItem::data(int column, int role) const retVal = dive->maxcns; break; case LOCATION: - retVal = QString(dive->location); + retVal = QString(get_dive_location(dive)); break; } break; @@ -1232,7 +1248,7 @@ QVariant DiveItem::data(int column, int role) const retVal = dive->maxcns; break; case LOCATION: - retVal = QString(dive->location); + retVal = QString(get_dive_location(dive)); break; case GAS: const char *gas_string = get_dive_gas_string(dive); @@ -1356,15 +1372,19 @@ QString DiveItem::displayDepthWithUnit() const QString DiveItem::displayDuration() const { - int hrs, mins; + int hrs, mins, fullmins, secs; struct dive *dive = get_dive_by_uniq_id(diveId); mins = (dive->duration.seconds + 59) / 60; + fullmins = dive->duration.seconds / 60; + secs = dive->duration.seconds - 60 * fullmins; hrs = mins / 60; mins -= hrs * 60; QString displayTime; if (hrs) displayTime = QString("%1:%2").arg(hrs).arg(mins, 2, 10, QChar('0')); + else if (mins < 15 || dive->dc.divemode == FREEDIVE) + displayTime = QString("%1m%2s").arg(fullmins).arg(secs, 2, 10, QChar('0')); else displayTime = QString("%1").arg(mins); return displayTime; @@ -2110,7 +2130,7 @@ QVariant ProfilePrintModel::data(const QModelIndex &index, int role) const } if (row == 1) { if (col == 0) - return QString(dive->location); + return QString(get_dive_location(dive)); if (col == 3) return QString(tr("Duration: %1 min")).arg(di.displayDuration()); } diff --git a/qt-ui/notificationwidget.cpp b/qt-ui/notificationwidget.cpp new file mode 100644 index 000000000..44e2eed1b --- /dev/null +++ b/qt-ui/notificationwidget.cpp @@ -0,0 +1,37 @@ +#include "notificationwidget.h" + +NotificationWidget::NotificationWidget(QWidget *parent) : KMessageWidget(parent) +{ + future_watcher = new QFutureWatcher<void>(); + connect(future_watcher, SIGNAL(finished()), this, SLOT(finish())); +} + +void NotificationWidget::showNotification(QString message, KMessageWidget::MessageType type) +{ + if (message.isEmpty()) + return; + setText(message); + setCloseButtonVisible(true); + setMessageType(type); + animatedShow(); +} + +void NotificationWidget::hideNotification() +{ + animatedHide(); +} + +void NotificationWidget::setFuture(const QFuture<void> &future) +{ + future_watcher->setFuture(future); +} + +void NotificationWidget::finish() +{ + hideNotification(); +} + +NotificationWidget::~NotificationWidget() +{ + delete future_watcher; +} diff --git a/qt-ui/notificationwidget.h b/qt-ui/notificationwidget.h new file mode 100644 index 000000000..7a3f93b4d --- /dev/null +++ b/qt-ui/notificationwidget.h @@ -0,0 +1,31 @@ +#ifndef NOTIFICATIONWIDGET_H +#define NOTIFICATIONWIDGET_H + +#include <QWidget> +#include <QFutureWatcher> + +#include <kmessagewidget.h> + +namespace Ui { + class NotificationWidget; +} + +class NotificationWidget : public KMessageWidget { + Q_OBJECT + +public: + explicit NotificationWidget(QWidget *parent = 0); + void setFuture(const QFuture<void> &future); + void showNotification(QString message, KMessageWidget::MessageType type); + void hideNotification(); + ~NotificationWidget(); + +private: + QFutureWatcher<void> *future_watcher; + +private +slots: + void finish(); +}; + +#endif // NOTIFICATIONWIDGET_H diff --git a/qt-ui/plannerDetails.ui b/qt-ui/plannerDetails.ui new file mode 100644 index 000000000..349ec536a --- /dev/null +++ b/qt-ui/plannerDetails.ui @@ -0,0 +1,89 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>plannerDetails</class> + <widget class="QWidget" name="plannerDetails"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>400</width> + <height>300</height> + </rect> + </property> + <property name="windowTitle"> + <string>Form</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <widget class="QLabel" name="divePlanOutputLabel"> + <property name="maximumSize"> + <size> + <width>16777215</width> + <height>20</height> + </size> + </property> + <property name="text"> + <string><html><head/><body><p><span style=" font-weight:600;">Dive plan details</span></p></body></html></string> + </property> + <property name="textFormat"> + <enum>Qt::RichText</enum> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="printPlan"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Print</string> + </property> + <property name="autoDefault"> + <bool>false</bool> + </property> + <property name="default"> + <bool>false</bool> + </property> + <property name="flat"> + <bool>false</bool> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QTextEdit" name="divePlanOutput"> + <property name="enabled"> + <bool>true</bool> + </property> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="MinimumExpanding"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="styleSheet"> + <string notr="true">font: 13pt "Courier";</string> + </property> + <property name="readOnly"> + <bool>true</bool> + </property> + <property name="html"> + <string><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'Courier'; font-size:13pt; font-weight:400; font-style:normal;"> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'.Curier New';"><br /></p></body></html></string> + </property> + </widget> + </item> + </layout> + </widget> + <resources/> + <connections/> +</ui> diff --git a/qt-ui/plannerSettings.ui b/qt-ui/plannerSettings.ui index af03fd1fb..02d829c42 100644 --- a/qt-ui/plannerSettings.ui +++ b/qt-ui/plannerSettings.ui @@ -262,8 +262,22 @@ <property name="spacing"> <number>2</number> </property> - <item row="0" column="2"> - <widget class="QSpinBox" name="gflow"> + <item row="6" column="1"> + <widget class="QLabel" name="label_16"> + <property name="text"> + <string>GF high</string> + </property> + </widget> + </item> + <item row="9" column="1" colspan="2"> + <widget class="QCheckBox" name="backgasBreaks"> + <property name="text"> + <string>Plan backgas breaks</string> + </property> + </widget> + </item> + <item row="6" column="2"> + <widget class="QSpinBox" name="gfhigh"> <property name="suffix"> <string>%</string> </property> @@ -275,28 +289,44 @@ </property> </widget> </item> - <item row="0" column="1"> - <widget class="QLabel" name="label_15"> + <item row="8" column="1" colspan="2"> + <widget class="QCheckBox" name="lastStop"> <property name="text"> - <string>GF low</string> + <string>Last stop at 6m</string> </property> </widget> </item> - <item row="1" column="1"> - <widget class="QLabel" name="label_16"> - <property name="text"> - <string>GF high</string> + <item row="10" column="1"> + <widget class="QComboBox" name="rebreathermode"> + <property name="currentText"> + <string/> + </property> + <property name="maxVisibleItems"> + <number>6</number> </property> </widget> </item> - <item row="4" column="1" colspan="2"> - <widget class="QCheckBox" name="backgasBreaks"> + <item row="5" column="2"> + <widget class="QSpinBox" name="gflow"> + <property name="suffix"> + <string>%</string> + </property> + <property name="minimum"> + <number>1</number> + </property> + <property name="maximum"> + <number>150</number> + </property> + </widget> + </item> + <item row="7" column="1" colspan="2"> + <widget class="QCheckBox" name="drop_stone_mode"> <property name="text"> - <string>Plan backgas breaks</string> + <string>Drop to first depth</string> </property> </widget> </item> - <item row="6" column="1"> + <item row="11" column="1"> <spacer name="verticalSpacer_2"> <property name="orientation"> <enum>Qt::Vertical</enum> @@ -309,40 +339,53 @@ </property> </spacer> </item> - <item row="3" column="1" colspan="2"> - <widget class="QCheckBox" name="lastStop"> + <item row="5" column="1"> + <widget class="QLabel" name="label_15"> <property name="text"> - <string>Last stop at 6m</string> + <string>GF low</string> </property> </widget> </item> - <item row="1" column="2"> - <widget class="QSpinBox" name="gfhigh"> - <property name="suffix"> - <string>%</string> + <item row="0" column="1"> + <widget class="QCheckBox" name="recreational_mode"> + <property name="text"> + <string>Recreational mode</string> </property> - <property name="minimum"> - <number>1</number> + </widget> + </item> + <item row="1" column="1"> + <widget class="QCheckBox" name="safetystop"> + <property name="text"> + <string>Safety stop</string> </property> - <property name="maximum"> - <number>150</number> + <property name="tristate"> + <bool>false</bool> </property> </widget> </item> - <item row="2" column="1" colspan="2"> - <widget class="QCheckBox" name="drop_stone_mode"> + <item row="2" column="1"> + <widget class="QLabel" name="label_3"> <property name="text"> - <string>Drop to first depth</string> + <string>Reserve gas</string> </property> </widget> </item> - <item row="5" column="1"> - <widget class="QComboBox" name="rebreathermode"> - <property name="currentText"> + <item row="2" column="2"> + <widget class="QSpinBox" name="reserve_gas"> + <property name="suffix"> + <string>bar</string> + </property> + <property name="prefix"> <string/> </property> - <property name="maxVisibleItems"> - <number>6</number> + <property name="minimum"> + <number>10</number> + </property> + <property name="maximum"> + <number>99</number> + </property> + <property name="value"> + <number>40</number> </property> </widget> </item> @@ -475,7 +518,7 @@ <item> <widget class="QGroupBox" name="groupBox_6"> <property name="title"> - <string>Dive notes</string> + <string>Notes</string> </property> <layout class="QGridLayout" name="gridLayout_6"> <property name="leftMargin"> @@ -501,9 +544,6 @@ <property name="text"> <string>Display runtime</string> </property> - <property name="checked"> - <bool>true</bool> - </property> </widget> </item> <item row="1" column="0"> @@ -517,9 +557,6 @@ <property name="text"> <string>Display segment duration</string> </property> - <property name="checked"> - <bool>false</bool> - </property> </widget> </item> <item row="2" column="0"> diff --git a/qt-ui/preferences.cpp b/qt-ui/preferences.cpp index ab241f358..aeccc961c 100644 --- a/qt-ui/preferences.cpp +++ b/qt-ui/preferences.cpp @@ -1,5 +1,7 @@ #include "preferences.h" #include "mainwindow.h" +#include "models.h" + #include <QSettings> #include <QFileDialog> #include <QMessageBox> @@ -144,6 +146,8 @@ void PreferencesDialog::setUiFromPrefs() ui.imperial->setChecked(true); else ui.personalize->setChecked(true); + ui.gpsTraditional->setChecked(prefs.coordinates_traditional); + ui.gpsDecimal->setChecked(!prefs.coordinates_traditional); ui.celsius->setChecked(prefs.units.temperature == units::CELSIUS); ui.fahrenheit->setChecked(prefs.units.temperature == units::FAHRENHEIT); @@ -304,6 +308,7 @@ void PreferencesDialog::syncSettings() s.setValue("volume", ui.cuft->isChecked() ? units::CUFT : units::LITER); s.setValue("weight", ui.lbs->isChecked() ? units::LBS : units::KG); s.setValue("vertical_speed_time", ui.vertical_speed_minutes->isChecked() ? units::MINUTES : units::SECONDS); + s.setValue("coordinates", ui.gpsTraditional->isChecked()); s.endGroup(); // Defaults @@ -382,6 +387,7 @@ void PreferencesDialog::loadSettings() GET_UNIT("weight", weight, units::LBS, units::KG); } GET_UNIT("vertical_speed_time", vertical_speed_time, units::MINUTES, units::SECONDS); + GET_BOOL("coordinates", coordinates_traditional); s.endGroup(); s.beginGroup("TecDetails"); GET_BOOL("po2graph", pp_graphs.po2); diff --git a/qt-ui/preferences.ui b/qt-ui/preferences.ui index e67925f56..84c90d7ff 100644 --- a/qt-ui/preferences.ui +++ b/qt-ui/preferences.ui @@ -22,127 +22,6 @@ <item> <layout class="QHBoxLayout" name="horizontalLayout_2"> <item> - <widget class="QListWidget" name="listWidget"> - <property name="sizePolicy"> - <sizepolicy hsizetype="Minimum" vsizetype="Expanding"> - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> - </property> - <property name="minimumSize"> - <size> - <width>80</width> - <height>0</height> - </size> - </property> - <property name="maximumSize"> - <size> - <width>80</width> - <height>16777215</height> - </size> - </property> - <property name="iconSize"> - <size> - <width>40</width> - <height>40</height> - </size> - </property> - <property name="textElideMode"> - <enum>Qt::ElideNone</enum> - </property> - <property name="movement"> - <enum>QListView::Static</enum> - </property> - <property name="isWrapping" stdset="0"> - <bool>true</bool> - </property> - <property name="layoutMode"> - <enum>QListView::Batched</enum> - </property> - <property name="spacing"> - <number>0</number> - </property> - <property name="gridSize"> - <size> - <width>70</width> - <height>60</height> - </size> - </property> - <property name="viewMode"> - <enum>QListView::IconMode</enum> - </property> - <property name="uniformItemSizes"> - <bool>false</bool> - </property> - <property name="wordWrap"> - <bool>true</bool> - </property> - <property name="currentRow"> - <number>-1</number> - </property> - <item> - <property name="text"> - <string>Defaults</string> - </property> - <property name="icon"> - <iconset> - <normalon>:/subsurface-icon</normalon> - </iconset> - </property> - </item> - <item> - <property name="text"> - <string>Units</string> - </property> - <property name="icon"> - <iconset> - <normalon>:/units</normalon> - </iconset> - </property> - </item> - <item> - <property name="text"> - <string>Graph</string> - </property> - <property name="icon"> - <iconset> - <normalon>:/graph</normalon> - </iconset> - </property> - </item> - <item> - <property name="text"> - <string>Language</string> - </property> - <property name="icon"> - <iconset> - <normalon>:/advanced</normalon> - </iconset> - </property> - </item> - <item> - <property name="text"> - <string>Network</string> - </property> - <property name="icon"> - <iconset> - <normalon>:/network</normalon> - </iconset> - </property> - </item> - <item> - <property name="text"> - <string>Facebook</string> - </property> - <property name="icon"> - <iconset> - <normalon>:/facebook</normalon> - </iconset> - </property> - </item> - </widget> - </item> - <item> <widget class="QStackedWidget" name="stackedWidget"> <property name="sizePolicy"> <sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding"> @@ -585,6 +464,42 @@ </layout> </item> <item> + <widget class="QGroupBox" name="groupBox_11"> + <property name="title"> + <string>GPS coordinates</string> + </property> + <layout class="QHBoxLayout" name="horizontalLayout_12"> + <item> + <widget class="QLabel" name="label_27"> + <property name="text"> + <string>Location Display</string> + </property> + </widget> + </item> + <item> + <widget class="QRadioButton" name="gpsTraditional"> + <property name="text"> + <string>traditional (dms)</string> + </property> + <attribute name="buttonGroup"> + <string notr="true">buttonGroup_7</string> + </attribute> + </widget> + </item> + <item> + <widget class="QRadioButton" name="gpsDecimal"> + <property name="text"> + <string>decimal</string> + </property> + <attribute name="buttonGroup"> + <string notr="true">buttonGroup_7</string> + </attribute> + </widget> + </item> + </layout> + </widget> + </item> + <item> <spacer name="verticalSpacer"> <property name="orientation"> <enum>Qt::Vertical</enum> @@ -1077,6 +992,127 @@ </widget> </widget> </item> + <item> + <widget class="QListWidget" name="listWidget"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Minimum" vsizetype="Expanding"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>80</width> + <height>0</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>80</width> + <height>16777215</height> + </size> + </property> + <property name="iconSize"> + <size> + <width>40</width> + <height>40</height> + </size> + </property> + <property name="textElideMode"> + <enum>Qt::ElideNone</enum> + </property> + <property name="movement"> + <enum>QListView::Static</enum> + </property> + <property name="isWrapping" stdset="0"> + <bool>true</bool> + </property> + <property name="layoutMode"> + <enum>QListView::Batched</enum> + </property> + <property name="spacing"> + <number>0</number> + </property> + <property name="gridSize"> + <size> + <width>70</width> + <height>60</height> + </size> + </property> + <property name="viewMode"> + <enum>QListView::IconMode</enum> + </property> + <property name="uniformItemSizes"> + <bool>false</bool> + </property> + <property name="wordWrap"> + <bool>true</bool> + </property> + <property name="currentRow"> + <number>-1</number> + </property> + <item> + <property name="text"> + <string>Defaults</string> + </property> + <property name="icon"> + <iconset> + <normalon>:/subsurface-icon</normalon> + </iconset> + </property> + </item> + <item> + <property name="text"> + <string>Units</string> + </property> + <property name="icon"> + <iconset> + <normalon>:/units</normalon> + </iconset> + </property> + </item> + <item> + <property name="text"> + <string>Graph</string> + </property> + <property name="icon"> + <iconset> + <normalon>:/graph</normalon> + </iconset> + </property> + </item> + <item> + <property name="text"> + <string>Language</string> + </property> + <property name="icon"> + <iconset> + <normalon>:/advanced</normalon> + </iconset> + </property> + </item> + <item> + <property name="text"> + <string>Network</string> + </property> + <property name="icon"> + <iconset> + <normalon>:/network</normalon> + </iconset> + </property> + </item> + <item> + <property name="text"> + <string>Facebook</string> + </property> + <property name="icon"> + <iconset> + <normalon>:/facebook</normalon> + </iconset> + </property> + </item> + </widget> + </item> </layout> </item> <item> @@ -1431,12 +1467,13 @@ </connection> </connections> <buttongroups> - <buttongroup name="buttonGroup"/> + <buttongroup name="verticalSpeed"/> <buttongroup name="buttonGroup_2"/> <buttongroup name="buttonGroup_3"/> <buttongroup name="buttonGroup_4"/> <buttongroup name="buttonGroup_5"/> + <buttongroup name="buttonGroup"/> <buttongroup name="buttonGroup_6"/> - <buttongroup name="verticalSpeed"/> + <buttongroup name="buttonGroup_7"/> </buttongroups> </ui> diff --git a/qt-ui/printlayout.cpp b/qt-ui/printlayout.cpp index 6b88f0d29..4be5fef73 100644 --- a/qt-ui/printlayout.cpp +++ b/qt-ui/printlayout.cpp @@ -3,11 +3,14 @@ #include <QPicture> #include <QMessageBox> #include <QPointer> +#include <QTableView> #include "mainwindow.h" #include "printdialog.h" #include "printlayout.h" #include "modeldelegates.h" +#include "models.h" +#include "profile/profilewidget2.h" PrintLayout::PrintLayout(PrintDialog *dialogPtr, QPrinter *printerPtr, struct print_options *optionsPtr) { @@ -459,7 +462,7 @@ void PrintLayout::addTablePrintDataRow(TablePrintModel *model, int row, struct d model->setData(model->index(row, 3), di.displayDuration(), Qt::DisplayRole); model->setData(model->index(row, 4), dive->divemaster, Qt::DisplayRole); model->setData(model->index(row, 5), dive->buddy, Qt::DisplayRole); - model->setData(model->index(row, 6), dive->location, Qt::DisplayRole); + model->setData(model->index(row, 6), get_dive_location(dive), Qt::DisplayRole); } void PrintLayout::addTablePrintHeadingRow(TablePrintModel *model, int row) const diff --git a/qt-ui/profile/divecartesianaxis.cpp b/qt-ui/profile/divecartesianaxis.cpp index 467a8b978..41d94a9a0 100644 --- a/qt-ui/profile/divecartesianaxis.cpp +++ b/qt-ui/profile/divecartesianaxis.cpp @@ -5,6 +5,8 @@ #include "diveplotdatamodel.h" #include "animationfunctions.h" #include "mainwindow.h" +#include "divelineitem.h" +#include "profilewidget2.h" static QPen gridPen() { diff --git a/qt-ui/profile/diveeventitem.cpp b/qt-ui/profile/diveeventitem.cpp index 700430007..0d81e7b45 100644 --- a/qt-ui/profile/diveeventitem.cpp +++ b/qt-ui/profile/diveeventitem.cpp @@ -135,7 +135,9 @@ bool DiveEventItem::shouldBeHidden() * Don't bother showing those */ struct sample *first_sample = &get_dive_dc(&displayed_dive, dc_number)->sample[0]; - if (!strcmp(event->name, "gaschange") && first_sample && event->time.seconds == first_sample->time.seconds) + if (!strcmp(event->name, "gaschange") && + (event->time.seconds == 0 || + (first_sample && event->time.seconds == first_sample->time.seconds))) return true; for (int i = 0; i < evn_used; i++) { diff --git a/qt-ui/profile/diveprofileitem.cpp b/qt-ui/profile/diveprofileitem.cpp index 3bc79832e..7d29d28b4 100644 --- a/qt-ui/profile/diveprofileitem.cpp +++ b/qt-ui/profile/diveprofileitem.cpp @@ -9,6 +9,9 @@ #include "helpers.h" #include "libdivecomputer/parser.h" #include "mainwindow.h" +#include "maintab.h" +#include "profile/profilewidget2.h" +#include "diveplanner.h" #include <QSettings> @@ -166,7 +169,7 @@ void DiveProfileItem::modelDataChanged(const QModelIndex &topLeft, const QModelI for (int i = 0; i < dataModel->rowCount(); i++, entry++) { int max = maxCeiling(i); // Don't scream if we violate the ceiling by a few cm - if (entry->depth < max - 100) { + if (entry->depth < max - 100 && entry->sec > 0) { profileColor = QColor(Qt::red); if (!eventAdded) { add_event(&displayed_dive.dc, entry->sec, SAMPLE_EVENT_CEILING, -1, max / 1000, "planned waypoint above ceiling"); @@ -682,8 +685,8 @@ void DiveGasPressureItem::modelDataChanged(const QModelIndex &topLeft, const QMo bool offsets_initialised = false; int o2cyl = -1, dilcyl = -1; - QFlags<Qt::AlignmentFlag> alignVar, align_dil = Qt::AlignBottom, align_o2 = Qt::AlignBottom; - double axisRange = (vAxis->maximum() - vAxis->minimum())/1000; + QFlags<Qt::AlignmentFlag> alignVar= Qt::AlignTop, align_dil = Qt::AlignBottom, align_o2 = Qt::AlignTop; + double axisRange = (vAxis->maximum() - vAxis->minimum())/1000; // Convert axis pressure range to bar double axisLog = log10(log10(axisRange)); for (int i = 0, count = dataModel->rowCount(); i < count; i++) { entry = dataModel->data().entry + i; @@ -691,23 +694,17 @@ void DiveGasPressureItem::modelDataChanged(const QModelIndex &topLeft, const QMo if (displayed_dive.dc.divemode == CCR && displayed_dive.oxygen_cylinder_index >= 0) o2mbar = GET_O2CYLINDER_PRESSURE(entry); - if (o2mbar) { + if (o2mbar) { // If there is an o2mbar value then this is a CCR dive. Then do: // The first time an o2 value is detected, see if the oxygen cyl pressure graph starts above or below the dil graph if (!offsets_initialised) { // Initialise the parameters for placing the text correctly near the graph line: o2cyl = displayed_dive.oxygen_cylinder_index; dilcyl = displayed_dive.diluent_cylinder_index; if ((o2mbar > mbar)) { // If above, write o2 start cyl pressure above graph and diluent pressure below graph: - print_y_offset[o2cyl][0] = -7 * axisLog; // y offset for oxygen gas lable (above) - print_y_offset[o2cyl][1] = -0.5; // y offset for oxygen start pressure value (above) + print_y_offset[o2cyl][0] = -7 * axisLog; // y offset for oxygen gas lable (above); pressure offsets=-0.5, already initialised print_y_offset[dilcyl][0] = 5 * axisLog; // y offset for diluent gas lable (below) - print_y_offset[dilcyl][1] = 0; // y offset for diluent start pressure value (below) - align_dil = Qt::AlignBottom; - align_o2 = Qt::AlignTop; - } else { // ... else write o2 start cyl pressure below graph: - print_y_offset[o2cyl][0] = 5 * axisLog; // o2 lable & pressure below graph, - print_y_offset[o2cyl][1] = 0; - print_y_offset[dilcyl][0] = -7 * axisLog; // and diluent lable above graph. - print_y_offset[dilcyl][1] = -0.5; // and diluent pressure above graph. + } else { // ... else write o2 start cyl pressure below graph: + print_y_offset[o2cyl][0] = 5 * axisLog; // o2 lable & pressure below graph; pressure offsets=-0.5, already initialised + print_y_offset[dilcyl][0] = -7.8 * axisLog; // and diluent lable above graph. align_dil = Qt::AlignTop; align_o2 = Qt::AlignBottom; } @@ -722,9 +719,6 @@ void DiveGasPressureItem::modelDataChanged(const QModelIndex &topLeft, const QMo last_pressure[displayed_dive.oxygen_cylinder_index] = o2mbar; last_time[displayed_dive.oxygen_cylinder_index] = entry->sec; alignVar = align_dil; - } else { - alignVar = Qt::AlignBottom; - align_dil = Qt::AlignTop; } if (!mbar) @@ -733,29 +727,19 @@ void DiveGasPressureItem::modelDataChanged(const QModelIndex &topLeft, const QMo if (cyl != entry->cylinderindex) { // Pressure value near the left hand edge of the profile - other cylinders: cyl = entry->cylinderindex; // For each other cylinder, write the gas lable and pressure if (!seen_cyl[cyl]) { - plotPressureValue(mbar, entry->sec, alignVar, print_y_offset[cyl][0]); - plotGasValue(mbar, entry->sec, displayed_dive.cylinder[cyl].gasmix, align_dil, print_y_offset[cyl][1]); + plotPressureValue(mbar, entry->sec, alignVar, print_y_offset[cyl][1]); + plotGasValue(mbar, entry->sec, displayed_dive.cylinder[cyl].gasmix, align_dil, print_y_offset[cyl][0]); seen_cyl[cyl] = true; } } last_pressure[cyl] = mbar; last_time[cyl] = entry->sec; } - // Now, for the cylinder pressure written near the right edge of the profile: - if ((o2cyl >= 0) && (dilcyl >= 0)) { // At first, skip uninitialised values of o2cyl and dilcyl - if (last_pressure[o2cyl] > last_pressure[dilcyl]) { // If oxygen cyl pressure graph ends above diluent graph: - align_dil = Qt::AlignTop; // initialise to write diluent cyl end pressure underneath the graph - align_o2 = Qt::AlignBottom; - } else { - align_dil = Qt::AlignBottom; // else initialise to write diluent cyl end pressure above the graph - align_o2 = Qt::AlignTop; - } - } for (cyl = 0; cyl < MAX_CYLINDERS; cyl++) { // For each cylinder, on right hand side of profile, write cylinder pressure alignVar = ((o2cyl >= 0) && (cyl == displayed_dive.oxygen_cylinder_index)) ? align_o2 : align_dil; if (last_time[cyl]) { - plotPressureValue(last_pressure[cyl], last_time[cyl], (alignVar | Qt::AlignLeft), print_y_offset[cyl][0]); + plotPressureValue(last_pressure[cyl], last_time[cyl], (alignVar | Qt::AlignLeft), print_y_offset[cyl][1]); } } } @@ -808,12 +792,14 @@ DiveCalculatedCeiling::DiveCalculatedCeiling() : is3mIncrement(false), gradientF gradientFactor->setY(0); gradientFactor->setBrush(getColor(PRESSURE_TEXT)); gradientFactor->setAlignment(Qt::AlignHCenter | Qt::AlignBottom); - connect(MainWindow::instance()->information(), SIGNAL(dateTimeChanged()), this, SLOT(recalc())); settingsChanged(); } void DiveCalculatedCeiling::modelDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight) { + if (MainWindow::instance()->information()) + connect(MainWindow::instance()->information(), SIGNAL(dateTimeChanged()), this, SLOT(recalc()), Qt::UniqueConnection); + // We don't have enougth data to calculate things, quit. if (!shouldCalculateStuff(topLeft, bottomRight)) return; diff --git a/qt-ui/profile/divetextitem.cpp b/qt-ui/profile/divetextitem.cpp index 85e046638..4c0137177 100644 --- a/qt-ui/profile/divetextitem.cpp +++ b/qt-ui/profile/divetextitem.cpp @@ -1,5 +1,6 @@ #include "divetextitem.h" #include "mainwindow.h" +#include "profilewidget2.h" DiveTextItem::DiveTextItem(QGraphicsItem *parent) : QGraphicsItemGroup(parent), internalAlignFlags(Qt::AlignHCenter | Qt::AlignVCenter), diff --git a/qt-ui/profile/profilewidget2.cpp b/qt-ui/profile/profilewidget2.cpp index f04d16b3f..a426ceef2 100644 --- a/qt-ui/profile/profilewidget2.cpp +++ b/qt-ui/profile/profilewidget2.cpp @@ -10,12 +10,18 @@ #include "ruleritem.h" #include "tankitem.h" #include "pref.h" +#include "divepicturewidget.h" +#include "models.h" +#include "maintab.h" +#include "diveplanner.h" + #include <libdivecomputer/parser.h> #include <QScrollBar> #include <QtCore/qmath.h> #include <QMessageBox> #include <QInputDialog> #include <QDebug> +#include <QWheelEvent> #ifndef QT_NO_DEBUG #include <QTableView> @@ -644,7 +650,7 @@ void ProfileWidget2::plotDive(struct dive *d, bool force) // so if we are calculation TTS / NDL then let's force that off. if (measureDuration.elapsed() > 1000 && prefs.calcndltts) { MainWindow::instance()->turnOffNdlTts(); - MainWindow::instance()->showError(tr("Show NDL / TTS was disabled because of excessive processing time")); + MainWindow::instance()->getNotificationWidget()->showNotification(tr("Show NDL / TTS was disabled because of excessive processing time"), KMessageWidget::Error); } } @@ -727,6 +733,8 @@ void ProfileWidget2::resizeEvent(QResizeEvent *event) void ProfileWidget2::mousePressEvent(QMouseEvent *event) { + if (zoomLevel) + return; QGraphicsView::mousePressEvent(event); if (currentState == PLAN) shouldCalculateMaxTime = false; @@ -734,18 +742,24 @@ void ProfileWidget2::mousePressEvent(QMouseEvent *event) void ProfileWidget2::divePlannerHandlerClicked() { + if (zoomLevel) + return; shouldCalculateMaxDepth = false; replot(); } void ProfileWidget2::divePlannerHandlerReleased() { + if (zoomLevel) + return; shouldCalculateMaxDepth = true; replot(); } void ProfileWidget2::mouseReleaseEvent(QMouseEvent *event) { + if (zoomLevel) + return; QGraphicsView::mouseReleaseEvent(event); if (currentState == PLAN) { shouldCalculateMaxTime = true; @@ -1039,6 +1053,7 @@ void ProfileWidget2::clearHandlers() if (handles.count()) { foreach (DiveHandler *handle, handles) { scene()->removeItem(handle); + delete handle; } handles.clear(); } @@ -1054,6 +1069,7 @@ void ProfileWidget2::setAddState() if (currentState == ADD) return; + clearHandlers(); setProfileState(); mouseFollowerHorizontal->setVisible(true); mouseFollowerVertical->setVisible(true); @@ -1206,6 +1222,10 @@ void ProfileWidget2::contextMenuEvent(QContextMenuEvent *event) setpointAction->setData(event->globalPos()); QAction *action = m.addAction(tr("Add bookmark"), this, SLOT(addBookmark())); action->setData(event->globalPos()); + + if (same_string(current_dc->model, "manually added dive")) + QAction *editProfileAction = m.addAction(tr("Edit the profile"), MainWindow::instance(), SLOT(editCurrentDive())); + if (DiveEventItem *item = dynamic_cast<DiveEventItem *>(sceneItem)) { action = new QAction(&m); action->setText(tr("Remove event")); @@ -1375,8 +1395,21 @@ void ProfileWidget2::changeGas() // backup the things on the dataModel, since we will clear that out. struct gasmix gasmix; - int seconds = timeAxis->valueAt(scenePos); + qreal sec_val = timeAxis->valueAt(scenePos); + + // no gas changes before the dive starts + unsigned int seconds = (sec_val < 0.0) ? 0 : (unsigned int)sec_val; + // if there is a gas change at this time stamp, remove it before adding the new one + struct event *gasChangeEvent = current_dc->events; + while ((gasChangeEvent = get_next_event(gasChangeEvent, "gaschange")) != NULL) { + if (gasChangeEvent->time.seconds == seconds) { + remove_event(gasChangeEvent); + gasChangeEvent = current_dc->events; + } else { + gasChangeEvent = gasChangeEvent->next; + } + } validate_gas(gas.toUtf8().constData(), &gasmix); QRegExp rx("\\(\\D*(\\d+)"); int tank; @@ -1504,6 +1537,7 @@ void ProfileWidget2::repositionDiveHandlers() { DivePlannerPointsModel *plannerModel = DivePlannerPointsModel::instance(); // Re-position the user generated dive handlers + struct gasmix mix, lastmix; for (int i = 0; i < plannerModel->rowCount(); i++) { struct divedatapoint datapoint = plannerModel->at(i); if (datapoint.time == 0) // those are the magic entries for tanks @@ -1528,8 +1562,9 @@ void ProfileWidget2::repositionDiveHandlers() QLineF line(p1, p2); QPointF pos = line.pointAt(0.5); gases[i]->setPos(pos); - gases[i]->setVisible(datapoint.entered); - gases[i]->setText(dpGasToStr(plannerModel->at(i))); + gases[i]->setText(dpGasToStr(datapoint)); + gases[i]->setVisible(datapoint.entered && + (i == 0 || gases[i]->text() != gases[i-1]->text())); } } diff --git a/qt-ui/profile/ruleritem.cpp b/qt-ui/profile/ruleritem.cpp index c88a3353d..d5742ef1d 100644 --- a/qt-ui/profile/ruleritem.cpp +++ b/qt-ui/profile/ruleritem.cpp @@ -1,6 +1,8 @@ #include "ruleritem.h" #include "preferences.h" #include "mainwindow.h" +#include "profilewidget2.h" +#include "display.h" #include <qgraphicssceneevent.h> @@ -81,7 +83,11 @@ void RulerItem2::settingsChanged() ProfileWidget2 *profWidget = NULL; if (scene() && scene()->views().count()) profWidget = qobject_cast<ProfileWidget2 *>(scene()->views().first()); - setVisible(profWidget->currentState == ProfileWidget2::PROFILE ? prefs.rulergraph : false); + + if (profWidget && profWidget->currentState == ProfileWidget2::PROFILE) + setVisible(prefs.rulergraph); + else + setVisible(false); } void RulerItem2::recalculate() diff --git a/qt-ui/qtwaitingspinner.cpp b/qt-ui/qtwaitingspinner.cpp new file mode 100644 index 000000000..14e8669b0 --- /dev/null +++ b/qt-ui/qtwaitingspinner.cpp @@ -0,0 +1,288 @@ + +/* Original Work Copyright (c) 2012-2014 Alexander Turkin + Modified 2014 by William Hallatt + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ +#include <cmath> +#include <algorithm> + +#include <QPainter> +#include <QTimer> + +#include "qtwaitingspinner.h" + +/*----------------------------------------------------------------------------*/ + +// Defaults +const QColor c_color(Qt::black); +const qreal c_roundness(70.0); +const qreal c_minTrailOpacity(15.0); +const qreal c_trailFadePercentage(70.0); +const int c_lines(12); +const int c_lineLength(10); +const int c_lineWidth(5); +const int c_innerRadius(10); +const int c_revPerSec(1); + +/*----------------------------------------------------------------------------*/ + +QtWaitingSpinner::QtWaitingSpinner(QWidget *parent) + : QWidget(parent), + + // Configurable settings. + m_color(c_color), m_roundness(c_roundness), + m_minTrailOpacity(c_minTrailOpacity), + m_trailFadePercentage(c_trailFadePercentage), m_revPerSec(c_revPerSec), + m_numberOfLines(c_lines), m_lineLength(c_lineLength + c_lineWidth), + m_lineWidth(c_lineWidth), m_innerRadius(c_innerRadius), + + // Other + m_timer(NULL), m_parent(parent), m_centreOnParent(false), + m_currentCounter(0), m_isSpinning(false) { + initialise(); +} + +/*----------------------------------------------------------------------------*/ + +QtWaitingSpinner::QtWaitingSpinner(Qt::WindowModality modality, QWidget *parent, + bool centreOnParent) + : QWidget(parent, Qt::Dialog | Qt::FramelessWindowHint), + + // Configurable settings. + m_color(c_color), m_roundness(c_roundness), + m_minTrailOpacity(c_minTrailOpacity), + m_trailFadePercentage(c_trailFadePercentage), m_revPerSec(c_revPerSec), + m_numberOfLines(c_lines), m_lineLength(c_lineLength + c_lineWidth), + m_lineWidth(c_lineWidth), m_innerRadius(c_innerRadius), + + // Other + m_timer(NULL), m_parent(parent), m_centreOnParent(centreOnParent), + m_currentCounter(0) { + initialise(); + + // We need to set the window modality AFTER we've hidden the + // widget for the first time since changing this property while + // the widget is visible has no effect. + this->setWindowModality(modality); + this->setAttribute(Qt::WA_TranslucentBackground); +} + +/*----------------------------------------------------------------------------*/ + +void QtWaitingSpinner::initialise() { + m_timer = new QTimer(this); + connect(m_timer, SIGNAL(timeout()), this, SLOT(rotate())); + updateSize(); + updateTimer(); + this->hide(); +} + +/*----------------------------------------------------------------------------*/ + +void QtWaitingSpinner::paintEvent(QPaintEvent * /*ev*/) { + QPainter painter(this); + painter.fillRect(this->rect(), Qt::transparent); + painter.setRenderHint(QPainter::Antialiasing, true); + + if (m_currentCounter >= m_numberOfLines) { + m_currentCounter = 0; + } + painter.setPen(Qt::NoPen); + for (int i = 0; i < m_numberOfLines; ++i) { + painter.save(); + painter.translate(m_innerRadius + m_lineLength, + m_innerRadius + m_lineLength); + qreal rotateAngle = + static_cast<qreal>(360 * i) / static_cast<qreal>(m_numberOfLines); + painter.rotate(rotateAngle); + painter.translate(m_innerRadius, 0); + int distance = + lineCountDistanceFromPrimary(i, m_currentCounter, m_numberOfLines); + QColor color = + currentLineColor(distance, m_numberOfLines, m_trailFadePercentage, + m_minTrailOpacity, m_color); + painter.setBrush(color); + // TODO improve the way rounded rect is painted + painter.drawRoundedRect( + QRect(0, -m_lineWidth / 2, m_lineLength, m_lineWidth), m_roundness, + m_roundness, Qt::RelativeSize); + painter.restore(); + } +} + +/*----------------------------------------------------------------------------*/ + +void QtWaitingSpinner::start() { + updatePosition(); + m_isSpinning = true; + this->show(); + if (!m_timer->isActive()) { + m_timer->start(); + m_currentCounter = 0; + } +} + +/*----------------------------------------------------------------------------*/ + +void QtWaitingSpinner::stop() { + m_isSpinning = false; + this->hide(); + if (m_timer->isActive()) { + m_timer->stop(); + m_currentCounter = 0; + } +} + +/*----------------------------------------------------------------------------*/ + +void QtWaitingSpinner::setNumberOfLines(int lines) { + m_numberOfLines = lines; + m_currentCounter = 0; + updateTimer(); +} + +/*----------------------------------------------------------------------------*/ + +void QtWaitingSpinner::setLineLength(int length) { + m_lineLength = length; + updateSize(); +} + +/*----------------------------------------------------------------------------*/ + +void QtWaitingSpinner::setLineWidth(int width) { + m_lineWidth = width; + updateSize(); +} + +/*----------------------------------------------------------------------------*/ + +void QtWaitingSpinner::setInnerRadius(int radius) { + m_innerRadius = radius; + updateSize(); +} + +/*----------------------------------------------------------------------------*/ + +bool QtWaitingSpinner::isSpinning() const { return m_isSpinning; } + +/*----------------------------------------------------------------------------*/ + +void QtWaitingSpinner::setRoundness(qreal roundness) { + m_roundness = std::max(0.0, std::min(100.0, roundness)); +} + +/*----------------------------------------------------------------------------*/ + +void QtWaitingSpinner::setColor(QColor color) { m_color = color; } + +/*----------------------------------------------------------------------------*/ + +void QtWaitingSpinner::setRevolutionsPerSecond(int rps) { + m_revPerSec = rps; + updateTimer(); +} + +/*----------------------------------------------------------------------------*/ + +void QtWaitingSpinner::setTrailFadePercentage(qreal trail) { + m_trailFadePercentage = trail; +} + +/*----------------------------------------------------------------------------*/ + +void QtWaitingSpinner::setMinimumTrailOpacity(qreal minOpacity) { + m_minTrailOpacity = minOpacity; +} + +/*----------------------------------------------------------------------------*/ + +void QtWaitingSpinner::rotate() { + ++m_currentCounter; + if (m_currentCounter >= m_numberOfLines) { + m_currentCounter = 0; + } + update(); +} + +/*----------------------------------------------------------------------------*/ + +void QtWaitingSpinner::updateSize() { + int size = (m_innerRadius + m_lineLength) * 2; + setFixedSize(size, size); +} + +/*----------------------------------------------------------------------------*/ + +void QtWaitingSpinner::updateTimer() { + m_timer->setInterval(calculateTimerInterval(m_numberOfLines, m_revPerSec)); +} + +/*----------------------------------------------------------------------------*/ + +void QtWaitingSpinner::updatePosition() { + if (m_parent && m_centreOnParent) { + this->move(m_parent->frameGeometry().topLeft() + m_parent->rect().center() - + this->rect().center()); + } +} + +/*----------------------------------------------------------------------------*/ + +int QtWaitingSpinner::calculateTimerInterval(int lines, int speed) { + return 1000 / (lines * speed); +} + +/*----------------------------------------------------------------------------*/ + +int QtWaitingSpinner::lineCountDistanceFromPrimary(int current, int primary, + int totalNrOfLines) { + int distance = primary - current; + if (distance < 0) { + distance += totalNrOfLines; + } + return distance; +} + +/*----------------------------------------------------------------------------*/ + +QColor QtWaitingSpinner::currentLineColor(int countDistance, int totalNrOfLines, + qreal trailFadePerc, qreal minOpacity, + QColor color) { + if (countDistance == 0) { + return color; + } + const qreal minAlphaF = minOpacity / 100.0; + int distanceThreshold = + static_cast<int>(ceil((totalNrOfLines - 1) * trailFadePerc / 100.0)); + if (countDistance > distanceThreshold) { + color.setAlphaF(minAlphaF); + } else { + qreal alphaDiff = color.alphaF() - minAlphaF; + qreal gradient = alphaDiff / static_cast<qreal>(distanceThreshold + 1); + qreal resultAlpha = color.alphaF() - gradient * countDistance; + + // If alpha is out of bounds, clip it. + resultAlpha = std::min(1.0, std::max(0.0, resultAlpha)); + color.setAlphaF(resultAlpha); + } + return color; +} + +/*----------------------------------------------------------------------------*/ diff --git a/qt-ui/qtwaitingspinner.h b/qt-ui/qtwaitingspinner.h new file mode 100644 index 000000000..254b52ec7 --- /dev/null +++ b/qt-ui/qtwaitingspinner.h @@ -0,0 +1,103 @@ +/* Original Work Copyright (c) 2012-2014 Alexander Turkin + Modified 2014 by William Hallatt + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ +#ifndef QTWAITINGSPINNER_H +#define QTWAITINGSPINNER_H + +#include <QWidget> + +#include <QTimer> +#include <QColor> + +class QtWaitingSpinner : public QWidget { + Q_OBJECT +public: + /*! Constructor for "standard" widget behaviour - use this + * constructor if you wish to, e.g. embed your widget in another. */ + QtWaitingSpinner(QWidget *parent = 0); + + /*! Constructor - use this constructor to automatically create a modal + * ("blocking") spinner on top of the calling widget/window. If a valid + * parent widget is provided, "centreOnParent" will ensure that + * QtWaitingSpinner automatically centres itself on it, if not, + * "centreOnParent" is ignored. */ + QtWaitingSpinner(Qt::WindowModality modality, QWidget *parent = 0, + bool centreOnParent = true); + +public Q_SLOTS: + void start(); + void stop(); + +public: + void setColor(QColor color); + void setRoundness(qreal roundness); + void setMinimumTrailOpacity(qreal minOpacity); + void setTrailFadePercentage(qreal trail); + void setRevolutionsPerSecond(int rps); + void setNumberOfLines(int lines); + void setLineLength(int length); + void setLineWidth(int width); + void setInnerRadius(int radius); + + bool isSpinning() const; + +private Q_SLOTS: + void rotate(); + +protected: + void paintEvent(QPaintEvent *ev); + +private: + static int calculateTimerInterval(int lines, int speed); + static int lineCountDistanceFromPrimary(int current, int primary, + int totalNrOfLines); + static QColor currentLineColor(int distance, int totalNrOfLines, + qreal trailFadePerc, qreal minOpacity, + QColor color); + + void initialise(); + void updateSize(); + void updateTimer(); + void updatePosition(); + +private: + // Configurable settings. + QColor m_color; + qreal m_roundness; // 0..100 + qreal m_minTrailOpacity; + qreal m_trailFadePercentage; + int m_revPerSec; // revolutions per second + int m_numberOfLines; + int m_lineLength; + int m_lineWidth; + int m_innerRadius; + +private: + QtWaitingSpinner(const QtWaitingSpinner&); + QtWaitingSpinner& operator=(const QtWaitingSpinner&); + + QTimer *m_timer; + QWidget *m_parent; + bool m_centreOnParent; + int m_currentCounter; + bool m_isSpinning; +}; + +#endif // QTWAITINGSPINNER_H diff --git a/qt-ui/shiftimagetimes.ui b/qt-ui/shiftimagetimes.ui index cce51e888..56a222856 100644 --- a/qt-ui/shiftimagetimes.ui +++ b/qt-ui/shiftimagetimes.ui @@ -117,6 +117,31 @@ </widget> </item> <item> + <widget class="QLabel" name="warningLabel"> + <property name="enabled"> + <bool>true</bool> + </property> + <property name="styleSheet"> + <string notr="true">color: red;</string> + </property> + <property name="text"> + <string>Warning! +Not all images have timestamps in the range between +30 minutes before the start and 30 minutes after the end of any selected dive.</string> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="invalidLabel"> + <property name="styleSheet"> + <string notr="true">color: red; </string> + </property> + <property name="text"> + <string/> + </property> + </widget> + </item> + <item> <widget class="QDialogButtonBox" name="buttonBox"> <property name="orientation"> <enum>Qt::Horizontal</enum> diff --git a/qt-ui/simplewidgets.cpp b/qt-ui/simplewidgets.cpp index f8f4c2493..430609c7b 100644 --- a/qt-ui/simplewidgets.cpp +++ b/qt-ui/simplewidgets.cpp @@ -5,12 +5,17 @@ #include <QFileDialog> #include <QShortcut> #include <QCalendarWidget> +#include <QKeyEvent> +#include <QAction> #include "file.h" #include "mainwindow.h" #include "helpers.h" #include "libdivecomputer/parser.h" - +#include "divelistview.h" +#include "display.h" +#include "profile/profilewidget2.h" +#include "undocommands.h" class MinMaxAvgWidgetPrivate { public: @@ -137,8 +142,17 @@ void RenumberDialog::renumberOnlySelected(bool selected) void RenumberDialog::buttonClicked(QAbstractButton *button) { - if (ui.buttonBox->buttonRole(button) == QDialogButtonBox::AcceptRole) - renumber_dives(ui.spinBox->value(), selectedOnly); + if (ui.buttonBox->buttonRole(button) == QDialogButtonBox::AcceptRole) { + QMap<int,int> renumberedDives; + int i; + struct dive *dive = NULL; + for_each_dive (i, dive) { + if (!selectedOnly || dive->selected) + renumberedDives.insert(dive->id, dive->number); + } + UndoRenumberDives *undoCommand = new UndoRenumberDives(renumberedDives, ui.spinBox->value()); + MainWindow::instance()->undoStack->push(undoCommand); + } } RenumberDialog::RenumberDialog(QWidget *parent) : QDialog(parent), selectedOnly(false) @@ -169,7 +183,6 @@ void SetpointDialog::buttonClicked(QAbstractButton *button) add_event(dc, time, SAMPLE_EVENT_PO2, 0, (int)(1000.0 * ui.spinbox->value()), "SP change"); mark_divelist_changed(true); MainWindow::instance()->graphics()->replot(); - } SetpointDialog::SetpointDialog(QWidget *parent) : QDialog(parent) @@ -198,7 +211,16 @@ void ShiftTimesDialog::buttonClicked(QAbstractButton *button) amount *= -1; if (amount != 0) { // DANGER, DANGER - this could get our dive_table unsorted... - shift_times(amount); + int i; + struct dive *dive; + QList<int> affectedDives; + for_each_dive (i, dive) { + if (!dive->selected) + continue; + + affectedDives.append(dive->id); + } + MainWindow::instance()->undoStack->push(new UndoShiftTime(affectedDives, amount)); sort_table(&dive_table); mark_divelist_changed(true); MainWindow::instance()->dive_list()->rememberSelection(); @@ -250,9 +272,6 @@ void ShiftImageTimesDialog::buttonClicked(QAbstractButton *button) void ShiftImageTimesDialog::syncCameraClicked() { - struct memblock mem; - EXIFInfo exiv; - int retval; QPixmap picture; QDateTime dcDateTime = QDateTime(); QStringList fileNames = QFileDialog::getOpenFileNames(this, @@ -268,13 +287,8 @@ void ShiftImageTimesDialog::syncCameraClicked() scene->addPixmap(picture.scaled(ui.DCImage->size())); ui.DCImage->setScene(scene); - if (readfile(fileNames.at(0).toUtf8().data(), &mem) <= 0) - return; - retval = exiv.parseFrom((const unsigned char *)mem.buffer, (unsigned)mem.size); - free(mem.buffer); - if (retval != PARSE_EXIF_SUCCESS) - return; - dcImageEpoch = exiv.epoch(); + + dcImageEpoch = picture_get_timestamp(fileNames.at(0).toUtf8().data()); dcDateTime.setTime_t(dcImageEpoch); ui.dcTime->setDateTime(dcDateTime); connect(ui.dcTime, SIGNAL(dateTimeChanged(const QDateTime &)), this, SLOT(dcDateTimeChanged(const QDateTime &))); @@ -287,11 +301,12 @@ void ShiftImageTimesDialog::dcDateTimeChanged(const QDateTime &newDateTime) setOffset(newDateTime.toTime_t() - dcImageEpoch); } -ShiftImageTimesDialog::ShiftImageTimesDialog(QWidget *parent) : QDialog(parent), m_amount(0) +ShiftImageTimesDialog::ShiftImageTimesDialog(QWidget *parent, QStringList fileNames) : QDialog(parent), fileNames(fileNames), m_amount(0) { ui.setupUi(this); connect(ui.buttonBox, SIGNAL(clicked(QAbstractButton *)), this, SLOT(buttonClicked(QAbstractButton *))); connect(ui.syncCamera, SIGNAL(clicked()), this, SLOT(syncCameraClicked())); + connect(ui.timeEdit, SIGNAL(timeChanged(const QTime &)), this, SLOT(timeEditChanged(const QTime &))); dcImageEpoch = (time_t)0; } @@ -311,6 +326,55 @@ void ShiftImageTimesDialog::setOffset(time_t offset) ui.timeEdit->setTime(QTime(offset / 3600, (offset % 3600) / 60, offset % 60)); } +void ShiftImageTimesDialog::updateInvalid() +{ + timestamp_t timestamp; + QDateTime time; + bool allValid = true; + ui.warningLabel->hide(); + ui.invalidLabel->hide(); + ui.invalidLabel->clear(); + + Q_FOREACH (const QString &fileName, fileNames) { + if (picture_check_valid(fileName.toUtf8().data(), m_amount)) + continue; + + // We've found invalid image + timestamp = picture_get_timestamp(fileName.toUtf8().data()); + dcImageEpoch = timestamp; + time.setTime_t(timestamp + m_amount); + ui.invalidLabel->setText(ui.invalidLabel->text() + fileName + " " + time.toString() + "\n"); + allValid = false; + } + + if (!allValid){ + ui.warningLabel->show(); + ui.invalidLabel->show(); + } +} + +void ShiftImageTimesDialog::timeEditChanged(const QTime &time) +{ + m_amount = time.hour() * 3600 + time.minute() * 60; + if (ui.backwards->isChecked()) + m_amount *= -1; + updateInvalid(); +} + +URLDialog::URLDialog(QWidget *parent) : QDialog(parent) +{ + ui.setupUi(this); + QShortcut *close = new QShortcut(QKeySequence(Qt::CTRL + Qt::Key_W), this); + connect(close, SIGNAL(activated()), this, SLOT(close())); + QShortcut *quit = new QShortcut(QKeySequence(Qt::CTRL + Qt::Key_Q), this); + connect(quit, SIGNAL(activated()), parent, SLOT(close())); +} + +QString URLDialog::url() const +{ + return ui.urlField->toPlainText(); +} + bool isGnome3Session() { #if defined(QT_OS_WIW) || defined(QT_OS_MAC) @@ -454,8 +518,7 @@ DiveComponentSelection::DiveComponentSelection(QWidget *parent, struct dive *tar { ui.setupUi(this); what = _what; - UI_FROM_COMPONENT(location); - UI_FROM_COMPONENT(gps); + UI_FROM_COMPONENT(divesite); UI_FROM_COMPONENT(divemaster); UI_FROM_COMPONENT(buddy); UI_FROM_COMPONENT(rating); @@ -475,8 +538,7 @@ DiveComponentSelection::DiveComponentSelection(QWidget *parent, struct dive *tar void DiveComponentSelection::buttonClicked(QAbstractButton *button) { if (ui.buttonBox->buttonRole(button) == QDialogButtonBox::AcceptRole) { - COMPONENT_FROM_UI(location); - COMPONENT_FROM_UI(gps); + COMPONENT_FROM_UI(divesite); COMPONENT_FROM_UI(divemaster); COMPONENT_FROM_UI(buddy); COMPONENT_FROM_UI(rating); diff --git a/qt-ui/simplewidgets.h b/qt-ui/simplewidgets.h index 8d5b4f73c..17f628392 100644 --- a/qt-ui/simplewidgets.h +++ b/qt-ui/simplewidgets.h @@ -6,6 +6,7 @@ class QAbstractButton; class QNetworkReply; #include <QWidget> +#include <QGroupBox> #include <QDialog> #include <stdint.h> @@ -13,6 +14,7 @@ class QNetworkReply; #include "ui_setpoint.h" #include "ui_shifttimes.h" #include "ui_shiftimagetimes.h" +#include "ui_urldialog.h" #include "ui_divecomponentselection.h" #include "ui_listfilter.h" #include "ui_filterwidget.h" @@ -96,7 +98,7 @@ private: class ShiftImageTimesDialog : public QDialog { Q_OBJECT public: - explicit ShiftImageTimesDialog(QWidget *parent); + explicit ShiftImageTimesDialog(QWidget *parent, QStringList fileNames); time_t amount() const; void setOffset(time_t offset); private @@ -104,13 +106,25 @@ slots: void buttonClicked(QAbstractButton *button); void syncCameraClicked(); void dcDateTimeChanged(const QDateTime &); + void timeEditChanged(const QTime &time); + void updateInvalid(); private: + QStringList fileNames; Ui::ShiftImageTimesDialog ui; time_t m_amount; time_t dcImageEpoch; }; +class URLDialog : public QDialog { + Q_OBJECT +public: + explicit URLDialog(QWidget *parent); + QString url() const; +private: + Ui::URLDialog ui; +}; + class QCalendarWidget; class DateWidget : public QWidget { diff --git a/qt-ui/socialnetworks.cpp b/qt-ui/socialnetworks.cpp index 21ccf9354..6a81d5db7 100644 --- a/qt-ui/socialnetworks.cpp +++ b/qt-ui/socialnetworks.cpp @@ -302,7 +302,7 @@ void SocialNetworkDialog::selectionChanged() tr("min", "abbreviation for minutes"))); } if (ui->Location->isChecked()) { - fullText += tr("Dive location: %1 \n").arg(d->location); + fullText += tr("Dive location: %1 \n").arg(get_dive_location(d)); } if (ui->Buddy->isChecked()) { fullText += tr("Buddy: %1 \n").arg(d->buddy); diff --git a/qt-ui/subsurfacewebservices.cpp b/qt-ui/subsurfacewebservices.cpp index fe7605ad7..fad542de6 100644 --- a/qt-ui/subsurfacewebservices.cpp +++ b/qt-ui/subsurfacewebservices.cpp @@ -1,7 +1,13 @@ #include "subsurfacewebservices.h" +#include "helpers.h" #include "webservice.h" #include "mainwindow.h" #include "usersurvey.h" +#include "divelist.h" +#include "globe.h" +#include "maintab.h" +#include "display.h" +#include "membuffer.h" #include <errno.h> #include <QDir> @@ -26,7 +32,25 @@ #endif struct dive_table gps_location_table; -static bool merge_locations_into_dives(void); + +// we don't overwrite any existing GPS info in the dive +// so get the dive site and if there is none or there is one without GPS fix, add it +static void copy_gps_location(struct dive *from, struct dive *to) +{ + struct dive_site *ds = get_dive_site_for_dive(to); + if (!ds || !dive_site_has_gps_location(ds)) { + struct dive_site *gds = get_dive_site_for_dive(from); + if (!ds) { + // simply link to the one created for the fake dive + to->dive_site_uuid = gds->uuid; + } else { + ds->latitude = gds->latitude; + ds->longitude = gds->longitude; + if (same_string(ds->name, "")) + ds->name = copy_string(gds->name); + } + } +} #define SAME_GROUP 6 * 3600 // six hours //TODO: C Code. static functions are not good if we plan to have a test for them. @@ -39,14 +63,14 @@ static bool merge_locations_into_dives(void) for_each_dive (i, dive) { if (!dive_has_gps_location(dive)) { - for (j = tracer; (gpsfix = get_gps_location(j, &gps_location_table)) !=NULL; j++) { + for (j = tracer; (gpsfix = get_dive_from_table(j, &gps_location_table)) !=NULL; j++) { if (dive_within_time_range (dive, gpsfix->when, SAME_GROUP)) { /* * If position is fixed during dive. This is the good one. * Asign and mark position, and end gps_location loop */ if ((dive->when <= gpsfix->when && gpsfix->when <= dive->when + dive->duration.seconds)) { - copy_gps_location(gpsfix,dive); + copy_gps_location(gpsfix, dive); changed++; tracer = j; break; @@ -54,7 +78,7 @@ static bool merge_locations_into_dives(void) /* * If it is not, check if there are more position fixes in SAME_GROUP range */ - if ((nextgpsfix = get_gps_location(j+1,&gps_location_table)) && + if ((nextgpsfix = get_dive_from_table(j+1,&gps_location_table)) && dive_within_time_range (dive, nextgpsfix->when, SAME_GROUP)) { /* * If distance from gpsfix to end of dive is shorter than distance between @@ -62,7 +86,7 @@ static bool merge_locations_into_dives(void) * If not, simply fail and nextgpsfix will be evaluated in next iteration. */ if ((dive->when + dive->duration.seconds - gpsfix->when) < (nextgpsfix->when - gpsfix->when)) { - copy_gps_location(gpsfix,dive); + copy_gps_location(gpsfix, dive); tracer = j; break; } @@ -70,7 +94,7 @@ static bool merge_locations_into_dives(void) * If no more positions in range, the actual is the one. Asign, mark and end loop. */ } else { - copy_gps_location(gpsfix,dive); + copy_gps_location(gpsfix, dive); changed++; tracer = j; break; @@ -122,11 +146,12 @@ bool DivelogsDeWebServices::prepare_dives_for_divelogs(const QString &tempfile, /* walk the dive list in chronological order */ int i; struct dive *dive; + struct membuffer mb = { 0 }; for_each_dive (i, dive) { FILE *f; char filename[PATH_MAX]; int streamsize; - char *membuf; + const char *membuf; xmlDoc *transformed; struct zip_source *s; @@ -136,29 +161,11 @@ bool DivelogsDeWebServices::prepare_dives_for_divelogs(const QString &tempfile, */ if (selected && !dive->selected) continue; - QString innerTmpFile = tempfile; - QString tmpSuffix = QString::number(qrand() % 9999) + ".tmp"; - innerTmpFile.replace(".dld", tmpSuffix); - f = subsurface_fopen(QFile::encodeName(QDir::toNativeSeparators(innerTmpFile)), "w+"); - if (!f) { - report_error(tr("cannot create temporary file: %s").toUtf8(), qt_error_string().toUtf8().data()); - goto error_close_zip; - } - save_dive(f, dive); - fseek(f, 0, SEEK_END); - streamsize = ftell(f); - rewind(f); - - membuf = (char *)malloc(streamsize + 1); - if (!membuf || (streamsize = fread(membuf, 1, streamsize, f)) == 0) { - report_error(tr("internal error: %s").toUtf8(), qt_error_string().toUtf8().data()); - fclose(f); - free((void *)membuf); - goto error_close_zip; - } - membuf[streamsize] = 0; - fclose(f); - unlink(QFile::encodeName(QDir::toNativeSeparators(innerTmpFile))); + /* make sure the buffer is empty and add the dive */ + mb.len = 0; + save_one_dive_to_mb(&mb, dive); + membuf = mb_cstring(&mb); + streamsize = strlen(membuf); /* * Parse the memory buffer into XML document and * transform it to divelogs.de format, finally dumping @@ -168,7 +175,6 @@ bool DivelogsDeWebServices::prepare_dives_for_divelogs(const QString &tempfile, if (!doc) { qWarning() << errPrefix << "could not parse back into memory the XML file we've just created!"; report_error(tr("internal error").toUtf8()); - free((void *)membuf); goto error_close_zip; } free((void *)membuf); @@ -210,7 +216,7 @@ WebServices::WebServices(QWidget *parent, Qt::WindowFlags f) : QDialog(parent, f ui.buttonBox->button(QDialogButtonBox::Apply)->setEnabled(false); timeout.setSingleShot(true); defaultApplyText = ui.buttonBox->button(QDialogButtonBox::Apply)->text(); - userAgent = UserSurvey::getUserAgent(); + userAgent = getUserAgent(); } void WebServices::hidePassword() @@ -326,10 +332,19 @@ void SubsurfaceWebServices::buttonClicked(QAbstractButton *button) ui.buttonBox->button(QDialogButtonBox::Apply)->setEnabled(false); switch (ui.buttonBox->buttonRole(button)) { case QDialogButtonBox::ApplyRole: { + int i; + struct dive *d; + struct dive_site *ds; clear_table(&gps_location_table); QByteArray url = tr("Webservice").toLocal8Bit(); parse_xml_buffer(url.data(), downloadedData.data(), downloadedData.length(), &gps_location_table, NULL); - + // make sure we mark all the dive sites that were created + for (i = 0; i < gps_location_table.nr; i++) { + d = get_dive_from_table(i, &gps_location_table); + ds = get_dive_site_by_uuid(d->dive_site_uuid); + if (ds) + ds->notes = strdup("SubsurfaceWebservice"); + } /* now merge the data in the gps_location table into the dive_table */ if (merge_locations_into_dives()) { mark_divelist_changed(true); @@ -358,6 +373,16 @@ void SubsurfaceWebServices::buttonClicked(QAbstractButton *button) hide(); close(); resetState(); + /* and now clean up and remove all the extra dive sites that were created */ + QSet<uint32_t> usedUuids; + for_each_dive(i, d) { + if (d->dive_site_uuid) + usedUuids.insert(d->dive_site_uuid); + } + for_each_dive_site(i, ds) { + if (!usedUuids.contains(ds->uuid) && same_string(ds->notes, "SubsurfaceWebservice")) + delete_dive_site(ds->uuid); + } } break; case QDialogButtonBox::RejectRole: if (reply != NULL && reply->isOpen()) { @@ -606,8 +631,10 @@ void DivelogsDeWebServices::prepareDivesForUpload(bool selected) f.remove(); return; } + } else { + report_error("Failed to create upload file %s\n", qPrintable(filename)); } - MainWindow::instance()->showError(get_error_string()); + MainWindow::instance()->getNotificationWidget()->showNotification(get_error_string(), KMessageWidget::Error); } void DivelogsDeWebServices::uploadDives(QIODevice *dldContent) @@ -638,7 +665,9 @@ void DivelogsDeWebServices::uploadDives(QIODevice *dldContent) } } -DivelogsDeWebServices::DivelogsDeWebServices(QWidget *parent, Qt::WindowFlags f) : WebServices(parent, f), uploadMode(false) +DivelogsDeWebServices::DivelogsDeWebServices(QWidget *parent, Qt::WindowFlags f) : WebServices(parent, f), + multipart(NULL), + uploadMode(false) { QSettings s; ui.userID->setText(s.value("divelogde_user").toString()); diff --git a/qt-ui/tableview.cpp b/qt-ui/tableview.cpp index 78a0bce10..e412d77e9 100644 --- a/qt-ui/tableview.cpp +++ b/qt-ui/tableview.cpp @@ -12,11 +12,9 @@ TableView::TableView(QWidget *parent) : QGroupBox(parent) QFontMetrics fm(defaultModelFont()); int text_ht = fm.height(); - int text_em = fm.width('m'); metrics.icon = &defaultIconMetrics(); - metrics.col_width = 7*text_em; metrics.rm_col_width = metrics.icon->sz_small + 2*metrics.icon->spacing; metrics.header_ht = text_ht + 10; // TODO DPI @@ -138,7 +136,8 @@ void TableView::edit(const QModelIndex &index) int TableView::defaultColumnWidth(int col) { - return col == CylindersModel::REMOVE ? metrics.rm_col_width : metrics.col_width; + QString text = ui.tableView->model()->headerData(col, Qt::Horizontal).toString(); + return text.isEmpty() ? metrics.rm_col_width : defaultModelFontMetrics().width(text) + 4; // add small margin } QTableView *TableView::view() diff --git a/qt-ui/tableview.h b/qt-ui/tableview.h index 36eef907a..f72b256ea 100644 --- a/qt-ui/tableview.h +++ b/qt-ui/tableview.h @@ -21,7 +21,6 @@ class TableView : public QGroupBox { struct TableMetrics { const IconMetrics* icon; // icon metrics - int col_width; // generic column width int rm_col_width; // column width of REMOVE column int header_ht; // height of the header }; diff --git a/qt-ui/tagwidget.cpp b/qt-ui/tagwidget.cpp index 8365a2ea7..3b61b492a 100644 --- a/qt-ui/tagwidget.cpp +++ b/qt-ui/tagwidget.cpp @@ -1,5 +1,6 @@ #include "tagwidget.h" #include "mainwindow.h" +#include "maintab.h" #include <QCompleter> TagWidget::TagWidget(QWidget *parent) : GroupedLineEdit(parent), m_completer(NULL), lastFinishedTag(false) diff --git a/qt-ui/undocommands.cpp b/qt-ui/undocommands.cpp new file mode 100644 index 000000000..aad264e24 --- /dev/null +++ b/qt-ui/undocommands.cpp @@ -0,0 +1,123 @@ +#include "undocommands.h" +#include "mainwindow.h" +#include "divelist.h" + +UndoDeleteDive::UndoDeleteDive(QList<dive *> deletedDives) + : diveList(deletedDives) +{ + setText("delete dive"); + if (diveList.count() > 1) + setText(QString("delete %1 dives").arg(QString::number(diveList.count()))); +} + +void UndoDeleteDive::undo() +{ + for (int i = 0; i < diveList.count(); i++) + record_dive(diveList.at(i)); + mark_divelist_changed(true); + MainWindow::instance()->refreshDisplay(); +} + +void UndoDeleteDive::redo() +{ + QList<struct dive*> newList; + for (int i = 0; i < diveList.count(); i++) { + //make a copy of the dive before deleting it + struct dive* d = alloc_dive(); + copy_dive(diveList.at(i), d); + newList.append(d); + //delete the dive + delete_single_dive(get_divenr(diveList.at(i))); + } + mark_divelist_changed(true); + MainWindow::instance()->refreshDisplay(); + diveList.clear(); + diveList = newList; +} + + +UndoShiftTime::UndoShiftTime(QList<int> changedDives, int amount) + : diveList(changedDives), timeChanged(amount) +{ + setText("shift time"); +} + +void UndoShiftTime::undo() +{ + for (int i = 0; i < diveList.count(); i++) { + struct dive* d = get_dive_by_uniq_id(diveList.at(i)); + d->when -= timeChanged; + } + mark_divelist_changed(true); + MainWindow::instance()->refreshDisplay(); +} + +void UndoShiftTime::redo() +{ + for (int i = 0; i < diveList.count(); i++) { + struct dive* d = get_dive_by_uniq_id(diveList.at(i)); + d->when += timeChanged; + } + mark_divelist_changed(true); + MainWindow::instance()->refreshDisplay(); +} + + +UndoRenumberDives::UndoRenumberDives(QMap<int, int> originalNumbers, int startNumber) +{ + oldNumbers = originalNumbers; + start = startNumber; + setText("renumber dive"); + if (oldNumbers.count() > 1) + setText(QString("renumber %1 dives").arg(QString::number(oldNumbers.count()))); +} + +void UndoRenumberDives::undo() +{ + foreach (int key, oldNumbers.keys()) { + struct dive* d = get_dive_by_uniq_id(key); + d->number = oldNumbers.value(key); + } + mark_divelist_changed(true); + MainWindow::instance()->refreshDisplay(); +} + +void UndoRenumberDives::redo() +{ + int i = start; + foreach (int key, oldNumbers.keys()) { + struct dive* d = get_dive_by_uniq_id(key); + d->number = i++; + } + mark_divelist_changed(true); + MainWindow::instance()->refreshDisplay(); +} + + +UndoRemoveDivesFromTrip::UndoRemoveDivesFromTrip(QMap<dive *, dive_trip *> removedDives) +{ + divesToUndo = removedDives; + setText("remove dive(s) from trip"); +} + +void UndoRemoveDivesFromTrip::undo() +{ + QMapIterator<dive*, dive_trip*> i(divesToUndo); + while (i.hasNext()) { + i.next(); + add_dive_to_trip(i.key (), i.value()); + } + mark_divelist_changed(true); + MainWindow::instance()->refreshDisplay(); +} + +void UndoRemoveDivesFromTrip::redo() +{ + QMapIterator<dive*, dive_trip*> i(divesToUndo); + while (i.hasNext()) { + i.next(); + remove_dive_from_trip(i.key(), false); + } + mark_divelist_changed(true); + MainWindow::instance()->refreshDisplay(); +} diff --git a/qt-ui/undocommands.h b/qt-ui/undocommands.h new file mode 100644 index 000000000..bd8530d77 --- /dev/null +++ b/qt-ui/undocommands.h @@ -0,0 +1,50 @@ +#ifndef UNDOCOMMANDS_H +#define UNDOCOMMANDS_H + +#include <QUndoCommand> +#include <QMap> +#include "dive.h" + +class UndoDeleteDive : public QUndoCommand { +public: + UndoDeleteDive(QList<struct dive*> deletedDives); + virtual void undo(); + virtual void redo(); + +private: + QList<struct dive*> diveList; +}; + +class UndoShiftTime : public QUndoCommand { +public: + UndoShiftTime(QList<int> changedDives, int amount); + virtual void undo(); + virtual void redo(); + +private: + QList<int> diveList; + int timeChanged; +}; + +class UndoRenumberDives : public QUndoCommand { +public: + UndoRenumberDives(QMap<int,int> originalNumbers, int startNumber); + virtual void undo(); + virtual void redo(); + +private: + QMap<int,int> oldNumbers; + int start; +}; + +class UndoRemoveDivesFromTrip : public QUndoCommand { +public: + UndoRemoveDivesFromTrip(QMap<struct dive*, dive_trip*> removedDives); + virtual void undo(); + virtual void redo(); + +private: + QMap<struct dive*, dive_trip*> divesToUndo; +}; + +#endif // UNDOCOMMANDS_H diff --git a/qt-ui/updatemanager.cpp b/qt-ui/updatemanager.cpp index 3fff8b45d..8a22400b9 100644 --- a/qt-ui/updatemanager.cpp +++ b/qt-ui/updatemanager.cpp @@ -1,10 +1,11 @@ #include "updatemanager.h" #include "usersurvey.h" +#include "helpers.h" #include <QtNetwork> #include <QMessageBox> #include <QUuid> #include "subsurfacewebservices.h" -#include "ssrf-version.h" +#include "version.h" #include "mainwindow.h" UpdateManager::UpdateManager(QObject *parent) : QObject(parent) @@ -16,9 +17,9 @@ UpdateManager::UpdateManager(QObject *parent) : QObject(parent) return; if (settings.contains("LastVersionUsed")) { // we have checked at least once before - if (settings.value("LastVersionUsed").toString() != GIT_VERSION_STRING) { + if (settings.value("LastVersionUsed").toString() != subsurface_git_version()) { // we have just updated - wait two weeks before you check again - settings.setValue("LastVersionUsed", QString(GIT_VERSION_STRING)); + settings.setValue("LastVersionUsed", QString(subsurface_git_version())); settings.setValue("NextCheck", QDateTime::currentDateTime().addDays(14).toString(Qt::ISODate)); } else { // is it time to check again? @@ -28,7 +29,7 @@ UpdateManager::UpdateManager(QObject *parent) : QObject(parent) return; } } - settings.setValue("LastVersionUsed", QString(GIT_VERSION_STRING)); + settings.setValue("LastVersionUsed", QString(subsurface_git_version())); settings.setValue("NextCheck", QDateTime::currentDateTime().addDays(14).toString(Qt::ISODate)); checkForUpdates(true); } @@ -47,13 +48,13 @@ void UpdateManager::checkForUpdates(bool automatic) os = "unknown"; #endif isAutomaticCheck = automatic; - QString version = CANONICAL_VERSION_STRING; + QString version = subsurface_canonical_version(); QString uuidString = getUUID(); QString url = QString("http://subsurface-divelog.org/updatecheck.html?os=%1&version=%2&uuid=%3").arg(os, version, uuidString); QNetworkRequest request; request.setUrl(url); request.setRawHeader("Accept", "text/xml"); - QString userAgent = UserSurvey::getUserAgent(); + QString userAgent = getUserAgent(); request.setRawHeader("User-Agent", userAgent.toUtf8()); connect(SubsurfaceWebServices::manager()->get(request), SIGNAL(finished()), this, SLOT(requestReceived()), Qt::UniqueConnection); } diff --git a/qt-ui/urldialog.ui b/qt-ui/urldialog.ui new file mode 100644 index 000000000..397f90a64 --- /dev/null +++ b/qt-ui/urldialog.ui @@ -0,0 +1,91 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>URLDialog</class> + <widget class="QDialog" name="URLDialog"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>397</width> + <height>103</height> + </rect> + </property> + <property name="windowTitle"> + <string>Dialog</string> + </property> + <widget class="QDialogButtonBox" name="buttonBox"> + <property name="geometry"> + <rect> + <x>40</x> + <y>60</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="QPlainTextEdit" name="urlField"> + <property name="geometry"> + <rect> + <x>10</x> + <y>30</y> + <width>371</width> + <height>21</height> + </rect> + </property> + </widget> + <widget class="QLabel" name="label"> + <property name="geometry"> + <rect> + <x>10</x> + <y>10</y> + <width>151</width> + <height>16</height> + </rect> + </property> + <property name="text"> + <string>Enter URL for images</string> + </property> + </widget> + </widget> + <resources/> + <connections> + <connection> + <sender>buttonBox</sender> + <signal>accepted()</signal> + <receiver>URLDialog</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>URLDialog</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/usersurvey.cpp b/qt-ui/usersurvey.cpp index 4061d46df..05da582a1 100644 --- a/qt-ui/usersurvey.cpp +++ b/qt-ui/usersurvey.cpp @@ -4,7 +4,7 @@ #include "usersurvey.h" #include "ui_usersurvey.h" -#include "ssrf-version.h" +#include "version.h" #include "subsurfacewebservices.h" #include "updatemanager.h" @@ -22,12 +22,12 @@ UserSurvey::UserSurvey(QWidget *parent) : QDialog(parent), QShortcut *quitKey = new QShortcut(QKeySequence(Qt::CTRL + Qt::Key_Q), this); connect(quitKey, SIGNAL(activated()), parent, SLOT(close())); - os = QString("ssrfVers=%1").arg(VERSION_STRING); + os = QString("ssrfVers=%1").arg(subsurface_version()); os.append(QString("&prettyOsName=%1").arg(SubsurfaceSysInfo::prettyOsName())); - QString arch = SubsurfaceSysInfo::cpuArchitecture(); + QString arch = SubsurfaceSysInfo::buildCpuArchitecture(); os.append(QString("&appCpuArch=%1").arg(arch)); if (arch == "i386") { - QString osArch = SubsurfaceSysInfo::osArch(); + QString osArch = SubsurfaceSysInfo::currentCpuArchitecture(); os.append(QString("&osCpuArch=%1").arg(osArch)); } os.append(QString("&uiLang=%1").arg(uiLanguage(NULL))); @@ -39,32 +39,16 @@ QString UserSurvey::getVersion() { QString arch; // fill in the system data - QString sysInfo = QString("Subsurface %1").arg(VERSION_STRING); + QString sysInfo = QString("Subsurface %1").arg(subsurface_version()); sysInfo.append(tr("\nOperating system: %1").arg(SubsurfaceSysInfo::prettyOsName())); - arch = SubsurfaceSysInfo::cpuArchitecture(); + arch = SubsurfaceSysInfo::buildCpuArchitecture(); sysInfo.append(tr("\nCPU architecture: %1").arg(arch)); if (arch == "i386") - sysInfo.append(tr("\nOS CPU architecture: %1").arg(SubsurfaceSysInfo::osArch())); + sysInfo.append(tr("\nOS CPU architecture: %1").arg(SubsurfaceSysInfo::currentCpuArchitecture())); sysInfo.append(tr("\nLanguage: %1").arg(uiLanguage(NULL))); return sysInfo; } -QString UserSurvey::getUserAgent() -{ - QString arch; - // fill in the system data - use ':' as separator - // replace all other ':' with ' ' so that this is easy to parse - QString userAgent = QString("Subsurface:%1:").arg(VERSION_STRING); - userAgent.append(SubsurfaceSysInfo::prettyOsName().replace(':', ' ') + ":"); - arch = SubsurfaceSysInfo::cpuArchitecture().replace(':', ' '); - userAgent.append(arch); - if (arch == "i386") - userAgent.append("/" + SubsurfaceSysInfo::osArch()); - userAgent.append(":" + uiLanguage(NULL)); - return userAgent; - -} - UserSurvey::~UserSurvey() { delete ui; diff --git a/qt-ui/usersurvey.h b/qt-ui/usersurvey.h index 55140521e..1dd5aaab3 100644 --- a/qt-ui/usersurvey.h +++ b/qt-ui/usersurvey.h @@ -16,7 +16,6 @@ public: explicit UserSurvey(QWidget *parent = 0); ~UserSurvey(); static QString getVersion(); - static QString getUserAgent(); private slots: |