2017-04-27 18:26:05 +00:00
|
|
|
// SPDX-License-Identifier: GPL-2.0
|
2016-04-05 05:02:03 +00:00
|
|
|
#include "desktop-widgets/locationinformation.h"
|
2020-01-19 20:06:50 +00:00
|
|
|
#include "desktop-widgets/importgps.h"
|
2018-05-11 15:25:41 +00:00
|
|
|
#include "core/subsurface-string.h"
|
2016-04-05 05:02:03 +00:00
|
|
|
#include "desktop-widgets/mainwindow.h"
|
|
|
|
#include "desktop-widgets/divelistview.h"
|
|
|
|
#include "core/qthelper.h"
|
2017-07-15 20:52:59 +00:00
|
|
|
#include "desktop-widgets/mapwidget.h"
|
2020-02-04 09:03:41 +00:00
|
|
|
#include "core/color.h"
|
2019-11-17 17:13:55 +00:00
|
|
|
#include "core/divefilter.h"
|
2020-05-01 11:51:29 +00:00
|
|
|
#include "core/divesite.h"
|
2016-04-05 05:02:03 +00:00
|
|
|
#include "core/divesitehelpers.h"
|
|
|
|
#include "desktop-widgets/modeldelegates.h"
|
2020-02-03 18:33:06 +00:00
|
|
|
#include "core/subsurface-qt/divelistnotifier.h"
|
2019-03-15 13:32:55 +00:00
|
|
|
#include "core/taxonomy.h"
|
2019-03-25 21:18:32 +00:00
|
|
|
#include "core/settings/qPrefUnit.h"
|
2019-11-13 14:08:40 +00:00
|
|
|
#include "commands/command.h"
|
2015-08-25 21:45:29 +00:00
|
|
|
|
2015-05-17 19:13:41 +00:00
|
|
|
#include <QShowEvent>
|
2015-09-01 00:11:28 +00:00
|
|
|
#include <QItemSelectionModel>
|
2015-09-01 01:01:25 +00:00
|
|
|
#include <qmessagebox.h>
|
2015-09-01 00:35:17 +00:00
|
|
|
#include <cstdlib>
|
2015-09-21 19:08:58 +00:00
|
|
|
#include <QDesktopWidget>
|
2020-01-19 20:06:50 +00:00
|
|
|
#include <QFileDialog>
|
2015-09-21 19:08:58 +00:00
|
|
|
#include <QScrollBar>
|
2015-05-17 19:13:41 +00:00
|
|
|
|
2019-03-25 21:18:32 +00:00
|
|
|
LocationInformationWidget::LocationInformationWidget(QWidget *parent) : QGroupBox(parent), diveSite(nullptr), closeDistance(0)
|
2015-05-17 19:13:41 +00:00
|
|
|
{
|
|
|
|
ui.setupUi(this);
|
|
|
|
ui.diveSiteMessage->setCloseButtonVisible(false);
|
|
|
|
|
2019-03-14 22:28:45 +00:00
|
|
|
QAction *acceptAction = new QAction(tr("Done"), this);
|
|
|
|
connect(acceptAction, &QAction::triggered, this, &LocationInformationWidget::acceptChanges);
|
2015-05-17 19:13:41 +00:00
|
|
|
|
|
|
|
ui.diveSiteMessage->setText(tr("Dive site management"));
|
2015-06-04 03:18:25 +00:00
|
|
|
ui.diveSiteMessage->addAction(acceptAction);
|
2015-05-17 19:13:41 +00:00
|
|
|
|
2015-08-25 21:45:29 +00:00
|
|
|
connect(ui.geoCodeButton, SIGNAL(clicked()), this, SLOT(reverseGeocode()));
|
2017-11-26 22:26:46 +00:00
|
|
|
ui.diveSiteCoordinates->installEventFilter(this);
|
2015-07-31 01:10:01 +00:00
|
|
|
|
2019-03-12 22:51:39 +00:00
|
|
|
connect(&diveListNotifier, &DiveListNotifier::diveSiteChanged, this, &LocationInformationWidget::diveSiteChanged);
|
2019-09-06 20:01:59 +00:00
|
|
|
connect(&diveListNotifier, &DiveListNotifier::diveSiteDeleted, this, &LocationInformationWidget::diveSiteDeleted);
|
2019-03-25 21:18:32 +00:00
|
|
|
connect(qPrefUnits::instance(), &qPrefUnits::unit_systemChanged, this, &LocationInformationWidget::unitsChanged);
|
|
|
|
unitsChanged();
|
2019-03-12 22:51:39 +00:00
|
|
|
|
2018-10-08 17:01:45 +00:00
|
|
|
ui.diveSiteListView->setModel(&filter_model);
|
2015-09-01 00:03:31 +00:00
|
|
|
ui.diveSiteListView->setModelColumn(LocationInformationModel::NAME);
|
2015-09-01 00:11:28 +00:00
|
|
|
ui.diveSiteListView->installEventFilter(this);
|
2015-05-17 20:14:23 +00:00
|
|
|
}
|
|
|
|
|
2019-03-16 09:44:39 +00:00
|
|
|
void LocationInformationWidget::keyPressEvent(QKeyEvent *e)
|
|
|
|
{
|
|
|
|
if (e->key() == Qt::Key_Escape)
|
|
|
|
MainWindow::instance()->setFocus();
|
|
|
|
return QGroupBox::keyPressEvent(e);
|
|
|
|
}
|
|
|
|
|
2017-11-26 22:26:46 +00:00
|
|
|
bool LocationInformationWidget::eventFilter(QObject *object, QEvent *ev)
|
2015-09-01 00:11:28 +00:00
|
|
|
{
|
2020-01-02 16:52:45 +00:00
|
|
|
Q_UNUSED(object)
|
2015-09-29 23:57:53 +00:00
|
|
|
if (ev->type() == QEvent::ContextMenu) {
|
|
|
|
QContextMenuEvent *ctx = (QContextMenuEvent *)ev;
|
2015-09-04 10:23:19 +00:00
|
|
|
QMenu contextMenu;
|
2019-10-14 18:57:13 +00:00
|
|
|
contextMenu.addAction(tr("Merge into current site"), this, &LocationInformationWidget::mergeSelectedDiveSites);
|
2015-09-04 10:23:19 +00:00
|
|
|
contextMenu.exec(ctx->globalPos());
|
|
|
|
return true;
|
2015-09-01 00:11:28 +00:00
|
|
|
}
|
2015-09-01 02:24:36 +00:00
|
|
|
return false;
|
2015-09-01 00:11:28 +00:00
|
|
|
}
|
|
|
|
|
2017-11-26 22:26:46 +00:00
|
|
|
void LocationInformationWidget::enableLocationButtons(bool enable)
|
|
|
|
{
|
|
|
|
ui.geoCodeButton->setEnabled(enable);
|
|
|
|
}
|
|
|
|
|
2015-09-29 23:57:53 +00:00
|
|
|
void LocationInformationWidget::mergeSelectedDiveSites()
|
|
|
|
{
|
2018-10-13 11:14:48 +00:00
|
|
|
if (!diveSite)
|
|
|
|
return;
|
2015-09-01 01:01:25 +00:00
|
|
|
|
2019-04-01 20:15:19 +00:00
|
|
|
const QModelIndexList selection = ui.diveSiteListView->selectionModel()->selectedIndexes();
|
2019-03-15 16:41:31 +00:00
|
|
|
QVector<dive_site *> selected_dive_sites;
|
2018-10-23 17:40:41 +00:00
|
|
|
selected_dive_sites.reserve(selection.count());
|
2019-04-01 20:15:19 +00:00
|
|
|
for (const QModelIndex &idx: selection) {
|
2018-10-28 20:16:42 +00:00
|
|
|
dive_site *ds = idx.data(LocationInformationModel::DIVESITE_ROLE).value<dive_site *>();
|
2018-10-23 17:40:41 +00:00
|
|
|
if (ds)
|
|
|
|
selected_dive_sites.push_back(ds);
|
2015-09-01 00:35:17 +00:00
|
|
|
}
|
2019-03-15 16:41:31 +00:00
|
|
|
Command::mergeDiveSites(diveSite, selected_dive_sites);
|
2015-09-01 00:35:17 +00:00
|
|
|
}
|
|
|
|
|
2020-02-04 09:03:41 +00:00
|
|
|
// If we can't parse the coordinates, inform the user with a visual clue
|
|
|
|
void LocationInformationWidget::coordinatesSetWarning(bool warn)
|
|
|
|
{
|
|
|
|
QPalette palette;
|
|
|
|
if (warn) {
|
|
|
|
palette.setColor(QPalette::Base, REDORANGE1_MED_TRANS);
|
|
|
|
palette.setColor(QPalette::Text, WHITE1);
|
|
|
|
}
|
|
|
|
ui.diveSiteCoordinates->setPalette(palette);
|
|
|
|
}
|
|
|
|
|
2015-07-25 16:03:14 +00:00
|
|
|
void LocationInformationWidget::updateLabels()
|
2015-05-17 19:13:41 +00:00
|
|
|
{
|
2018-10-13 11:14:48 +00:00
|
|
|
if (!diveSite) {
|
|
|
|
clearLabels();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (diveSite->name)
|
|
|
|
ui.diveSiteName->setText(diveSite->name);
|
2015-05-17 19:13:41 +00:00
|
|
|
else
|
|
|
|
ui.diveSiteName->clear();
|
2019-03-15 13:32:55 +00:00
|
|
|
const char *country = taxonomy_get_country(&diveSite->taxonomy);
|
2017-10-03 06:03:44 +00:00
|
|
|
if (country)
|
|
|
|
ui.diveSiteCountry->setText(country);
|
2017-10-02 15:51:20 +00:00
|
|
|
else
|
|
|
|
ui.diveSiteCountry->clear();
|
2018-10-13 11:14:48 +00:00
|
|
|
if (diveSite->description)
|
|
|
|
ui.diveSiteDescription->setText(diveSite->description);
|
2015-05-17 19:13:41 +00:00
|
|
|
else
|
|
|
|
ui.diveSiteDescription->clear();
|
2018-10-13 11:14:48 +00:00
|
|
|
if (diveSite->notes)
|
|
|
|
ui.diveSiteNotes->setPlainText(diveSite->notes);
|
2015-05-17 19:13:41 +00:00
|
|
|
else
|
|
|
|
ui.diveSiteNotes->clear();
|
2019-03-25 08:05:47 +00:00
|
|
|
if (has_location(&diveSite->location))
|
|
|
|
ui.diveSiteCoordinates->setText(printGPSCoords(&diveSite->location));
|
|
|
|
else
|
2015-05-17 19:13:41 +00:00
|
|
|
ui.diveSiteCoordinates->clear();
|
2020-02-04 09:03:41 +00:00
|
|
|
coordinatesSetWarning(false);
|
2015-10-07 23:00:22 +00:00
|
|
|
|
2019-03-15 13:32:55 +00:00
|
|
|
ui.locationTags->setText(constructLocationTags(&diveSite->taxonomy, false));
|
2015-05-17 19:13:41 +00:00
|
|
|
}
|
|
|
|
|
2019-03-25 21:18:32 +00:00
|
|
|
void LocationInformationWidget::unitsChanged()
|
|
|
|
{
|
|
|
|
if (prefs.units.length == units::METERS) {
|
|
|
|
ui.diveSiteDistanceUnits->setText("m");
|
|
|
|
ui.diveSiteDistance->setText(QString::number(lrint(closeDistance / 1000.0)));
|
|
|
|
} else {
|
|
|
|
ui.diveSiteDistanceUnits->setText("ft");
|
|
|
|
ui.diveSiteDistance->setText(QString::number(lrint(mm_to_feet(closeDistance))));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-03-12 22:51:39 +00:00
|
|
|
void LocationInformationWidget::diveSiteChanged(struct dive_site *ds, int field)
|
|
|
|
{
|
|
|
|
if (diveSite != ds)
|
|
|
|
return; // A different dive site was changed -> do nothing.
|
|
|
|
switch (field) {
|
|
|
|
case LocationInformationModel::NAME:
|
|
|
|
ui.diveSiteName->setText(diveSite->name);
|
2019-03-13 19:10:22 +00:00
|
|
|
return;
|
|
|
|
case LocationInformationModel::DESCRIPTION:
|
|
|
|
ui.diveSiteDescription->setText(diveSite->description);
|
|
|
|
return;
|
2019-03-13 23:00:54 +00:00
|
|
|
case LocationInformationModel::NOTES:
|
|
|
|
ui.diveSiteNotes->setText(diveSite->notes);
|
|
|
|
return;
|
2019-03-14 07:26:50 +00:00
|
|
|
case LocationInformationModel::TAXONOMY:
|
|
|
|
ui.diveSiteCountry->setText(taxonomy_get_country(&diveSite->taxonomy));
|
2019-03-15 13:32:55 +00:00
|
|
|
ui.locationTags->setText(constructLocationTags(&diveSite->taxonomy, false));
|
2019-03-14 07:26:50 +00:00
|
|
|
return;
|
2019-03-14 21:07:48 +00:00
|
|
|
case LocationInformationModel::LOCATION:
|
|
|
|
filter_model.setCoordinates(diveSite->location);
|
|
|
|
if (has_location(&diveSite->location)) {
|
|
|
|
enableLocationButtons(true);
|
|
|
|
ui.diveSiteCoordinates->setText(printGPSCoords(&diveSite->location));
|
|
|
|
} else {
|
|
|
|
enableLocationButtons(false);
|
|
|
|
ui.diveSiteCoordinates->clear();
|
|
|
|
}
|
2020-02-04 09:03:41 +00:00
|
|
|
coordinatesSetWarning(false);
|
2019-09-03 05:11:43 +00:00
|
|
|
return;
|
2019-03-12 22:51:39 +00:00
|
|
|
default:
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-10-06 19:06:13 +00:00
|
|
|
void LocationInformationWidget::clearLabels()
|
|
|
|
{
|
|
|
|
ui.diveSiteName->clear();
|
|
|
|
ui.diveSiteCountry->clear();
|
|
|
|
ui.diveSiteDescription->clear();
|
|
|
|
ui.diveSiteNotes->clear();
|
|
|
|
ui.diveSiteCoordinates->clear();
|
2020-02-04 09:03:41 +00:00
|
|
|
coordinatesSetWarning(false);
|
2017-10-06 19:06:13 +00:00
|
|
|
ui.locationTags->clear();
|
|
|
|
}
|
|
|
|
|
2019-03-14 22:28:45 +00:00
|
|
|
// Parse GPS text into location_t
|
|
|
|
static location_t parseGpsText(const QString &text)
|
2018-10-13 10:30:32 +00:00
|
|
|
{
|
|
|
|
double lat, lon;
|
2020-02-04 09:03:41 +00:00
|
|
|
if (parseGpsText(text.trimmed(), &lat, &lon))
|
2019-03-14 22:28:45 +00:00
|
|
|
return create_location(lat, lon);
|
2020-01-09 04:55:29 +00:00
|
|
|
return zero_location;
|
2018-10-13 10:30:32 +00:00
|
|
|
}
|
|
|
|
|
2020-02-04 09:03:41 +00:00
|
|
|
// Check if GPS text is parseable
|
|
|
|
static bool validateGpsText(const QString &textIn)
|
|
|
|
{
|
|
|
|
double lat, lon;
|
|
|
|
QString text = textIn.trimmed();
|
|
|
|
return text.isEmpty() || parseGpsText(text.trimmed(), &lat, &lon);
|
|
|
|
}
|
|
|
|
|
2019-09-06 20:01:59 +00:00
|
|
|
void LocationInformationWidget::diveSiteDeleted(struct dive_site *ds, int)
|
|
|
|
{
|
|
|
|
// If the currently edited dive site was removed under our feet, close the widget.
|
|
|
|
// This will reset the dangling pointer.
|
|
|
|
if (ds && ds == diveSite)
|
|
|
|
acceptChanges();
|
|
|
|
}
|
|
|
|
|
2015-05-17 19:13:41 +00:00
|
|
|
void LocationInformationWidget::acceptChanges()
|
|
|
|
{
|
2019-09-06 20:01:59 +00:00
|
|
|
closeDistance = 0;
|
|
|
|
|
2019-03-14 22:28:45 +00:00
|
|
|
MainWindow::instance()->diveList->setEnabled(true);
|
|
|
|
MainWindow::instance()->setEnabledToolbar(true);
|
2019-05-10 17:51:43 +00:00
|
|
|
MainWindow::instance()->setApplicationState(ApplicationState::Default);
|
2019-11-17 17:13:55 +00:00
|
|
|
DiveFilter::instance()->stopFilterDiveSites();
|
2019-10-28 05:57:54 +00:00
|
|
|
|
|
|
|
// Subtlety alert: diveSite must be cleared *after* exiting the dive-site mode.
|
|
|
|
// Exiting dive-site mode removes the focus from the active widget and
|
|
|
|
// thus fires the corresponding editingFinished signal, which in turn creates
|
|
|
|
// an undo-command. To set an undo-command, the widget has to know the
|
|
|
|
// currently edited dive-site.
|
|
|
|
diveSite = nullptr;
|
2015-05-17 19:13:41 +00:00
|
|
|
}
|
|
|
|
|
2018-10-13 06:57:46 +00:00
|
|
|
void LocationInformationWidget::initFields(dive_site *ds)
|
2015-05-17 20:14:23 +00:00
|
|
|
{
|
2018-10-13 11:14:48 +00:00
|
|
|
diveSite = ds;
|
2018-10-13 06:57:46 +00:00
|
|
|
if (ds) {
|
2018-10-25 06:02:06 +00:00
|
|
|
filter_model.set(ds, ds->location);
|
2015-07-25 16:03:14 +00:00
|
|
|
updateLabels();
|
2018-10-13 11:14:48 +00:00
|
|
|
enableLocationButtons(dive_site_has_gps_location(ds));
|
2019-11-17 17:13:55 +00:00
|
|
|
DiveFilter::instance()->startFilterDiveSites(QVector<dive_site *>{ ds });
|
2019-04-15 19:47:08 +00:00
|
|
|
filter_model.invalidate();
|
2017-10-06 19:06:13 +00:00
|
|
|
} else {
|
2020-01-09 04:55:29 +00:00
|
|
|
filter_model.set(0, zero_location);
|
2017-10-06 19:06:13 +00:00
|
|
|
clearLabels();
|
2015-07-31 01:10:01 +00:00
|
|
|
}
|
2015-05-17 19:13:41 +00:00
|
|
|
}
|
|
|
|
|
2020-01-19 20:06:50 +00:00
|
|
|
|
|
|
|
void LocationInformationWidget::on_GPSbutton_clicked()
|
|
|
|
{
|
|
|
|
QFileInfo finfo(system_default_directory());
|
|
|
|
QString fileName = QFileDialog::getOpenFileName(this,
|
|
|
|
tr("Select GPS file to open"),
|
|
|
|
finfo.absolutePath(),
|
|
|
|
tr("GPS files (*.gpx *.GPX)"));
|
|
|
|
if (fileName.isEmpty())
|
|
|
|
return;
|
|
|
|
|
|
|
|
ImportGPS GPSDialog(this, fileName, &ui); // Create a GPS import QDialog
|
|
|
|
GPSDialog.coords.start_dive = current_dive->when; // initialise
|
|
|
|
GPSDialog.coords.end_dive = dive_endtime(current_dive);
|
2020-01-22 21:25:58 +00:00
|
|
|
if (getCoordsFromGPXFile(&GPSDialog.coords, fileName) == 0) { // Get coordinates from GPS file
|
2020-01-19 20:06:50 +00:00
|
|
|
GPSDialog.updateUI(); // If successful, put results in Dialog
|
|
|
|
if (!GPSDialog.exec()) // and show QDialog
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-03-14 21:07:48 +00:00
|
|
|
void LocationInformationWidget::on_diveSiteCoordinates_editingFinished()
|
2015-05-17 19:13:41 +00:00
|
|
|
{
|
2020-02-04 09:15:39 +00:00
|
|
|
if (diveSite && validateGpsText(ui.diveSiteCoordinates->text()))
|
2019-06-24 05:02:05 +00:00
|
|
|
Command::editDiveSiteLocation(diveSite, parseGpsText(ui.diveSiteCoordinates->text()));
|
2015-05-17 19:13:41 +00:00
|
|
|
}
|
|
|
|
|
2020-02-04 09:03:41 +00:00
|
|
|
void LocationInformationWidget::on_diveSiteCoordinates_textEdited(const QString &s)
|
|
|
|
{
|
|
|
|
coordinatesSetWarning(!validateGpsText(s));
|
|
|
|
}
|
|
|
|
|
2019-03-14 07:26:50 +00:00
|
|
|
void LocationInformationWidget::on_diveSiteCountry_editingFinished()
|
2017-10-02 15:51:20 +00:00
|
|
|
{
|
2019-03-14 07:26:50 +00:00
|
|
|
if (diveSite)
|
|
|
|
Command::editDiveSiteCountry(diveSite, ui.diveSiteCountry->text());
|
2017-10-02 15:51:20 +00:00
|
|
|
}
|
|
|
|
|
2019-03-13 21:49:34 +00:00
|
|
|
void LocationInformationWidget::on_diveSiteDescription_editingFinished()
|
2015-05-17 19:13:41 +00:00
|
|
|
{
|
2019-03-13 21:49:34 +00:00
|
|
|
if (diveSite)
|
|
|
|
Command::editDiveSiteDescription(diveSite, ui.diveSiteDescription->text());
|
2015-05-17 19:13:41 +00:00
|
|
|
}
|
|
|
|
|
2019-03-13 21:49:34 +00:00
|
|
|
void LocationInformationWidget::on_diveSiteName_editingFinished()
|
2015-05-17 19:13:41 +00:00
|
|
|
{
|
2019-03-13 21:49:34 +00:00
|
|
|
if (diveSite)
|
|
|
|
Command::editDiveSiteName(diveSite, ui.diveSiteName->text());
|
2015-05-17 19:13:41 +00:00
|
|
|
}
|
|
|
|
|
2019-03-13 23:00:54 +00:00
|
|
|
void LocationInformationWidget::on_diveSiteNotes_editingFinished()
|
2015-05-17 19:13:41 +00:00
|
|
|
{
|
2019-03-13 23:00:54 +00:00
|
|
|
if (diveSite)
|
|
|
|
Command::editDiveSiteNotes(diveSite, ui.diveSiteNotes->toPlainText());
|
2015-05-17 19:13:41 +00:00
|
|
|
}
|
|
|
|
|
2019-03-25 21:18:32 +00:00
|
|
|
void LocationInformationWidget::on_diveSiteDistance_textChanged(const QString &s)
|
|
|
|
{
|
|
|
|
bool ok;
|
|
|
|
uint64_t d = s.toLongLong(&ok);
|
|
|
|
if (!ok)
|
|
|
|
d = 0;
|
|
|
|
closeDistance = prefs.units.length == units::METERS ? d * 1000 : feet_to_mm(d);
|
|
|
|
filter_model.setDistance(closeDistance);
|
|
|
|
}
|
|
|
|
|
2015-08-25 21:45:29 +00:00
|
|
|
void LocationInformationWidget::reverseGeocode()
|
|
|
|
{
|
2020-09-02 07:01:34 +00:00
|
|
|
dive_site *ds = diveSite; /* Save local copy; possibility of user closing the widget while reverseGeoLookup is running (see #2930) */
|
2019-03-14 22:28:45 +00:00
|
|
|
location_t location = parseGpsText(ui.diveSiteCoordinates->text());
|
2020-09-02 07:01:34 +00:00
|
|
|
if (!ds || !has_location(&location))
|
2018-10-13 10:30:32 +00:00
|
|
|
return;
|
2020-09-03 03:04:05 +00:00
|
|
|
taxonomy_data taxonomy = reverseGeoLookup(location.lat, location.lon);
|
|
|
|
if (ds != diveSite) {
|
|
|
|
free_taxonomy(&taxonomy);
|
2020-09-02 07:01:34 +00:00
|
|
|
return;
|
2020-09-03 03:04:05 +00:00
|
|
|
}
|
|
|
|
// This call transfers ownership of the taxonomy memory into an EditDiveSiteTaxonomy object
|
2020-09-02 07:01:34 +00:00
|
|
|
Command::editDiveSiteTaxonomy(ds, taxonomy);
|
2015-08-25 21:45:29 +00:00
|
|
|
}
|
2015-09-21 17:01:58 +00:00
|
|
|
|
2020-01-09 04:55:29 +00:00
|
|
|
DiveLocationFilterProxyModel::DiveLocationFilterProxyModel(QObject *) : currentLocation(zero_location)
|
2015-09-21 17:01:58 +00:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2019-04-15 18:15:40 +00:00
|
|
|
void DiveLocationFilterProxyModel::setFilter(const QString &filterIn)
|
|
|
|
{
|
|
|
|
filter = filterIn;
|
|
|
|
invalidate();
|
2019-04-15 20:16:33 +00:00
|
|
|
sort(LocationInformationModel::NAME);
|
2019-04-15 18:15:40 +00:00
|
|
|
}
|
2015-09-21 17:01:58 +00:00
|
|
|
|
2019-04-24 22:26:48 +00:00
|
|
|
void DiveLocationFilterProxyModel::setCurrentLocation(location_t loc)
|
|
|
|
{
|
|
|
|
currentLocation = loc;
|
|
|
|
sort(LocationInformationModel::NAME);
|
|
|
|
}
|
|
|
|
|
2018-05-21 16:09:09 +00:00
|
|
|
bool DiveLocationFilterProxyModel::filterAcceptsRow(int source_row, const QModelIndex&) const
|
2015-09-21 17:01:58 +00:00
|
|
|
{
|
2019-04-15 20:26:11 +00:00
|
|
|
// We don't want to show the first two entries (add dive site with that name)
|
|
|
|
// if there is no filter text.
|
|
|
|
if (filter.isEmpty() && source_row <= 1)
|
|
|
|
return false;
|
|
|
|
|
2015-09-29 23:57:53 +00:00
|
|
|
if (source_row == 0)
|
2015-09-21 17:01:58 +00:00
|
|
|
return true;
|
|
|
|
|
2018-10-09 10:26:58 +00:00
|
|
|
QString sourceString = sourceModel()->index(source_row, LocationInformationModel::NAME).data(Qt::DisplayRole).toString();
|
2019-04-15 18:15:40 +00:00
|
|
|
return sourceString.contains(filter, Qt::CaseInsensitive);
|
2015-09-21 17:01:58 +00:00
|
|
|
}
|
|
|
|
|
2015-09-29 23:57:53 +00:00
|
|
|
bool DiveLocationFilterProxyModel::lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const
|
2015-09-21 18:04:52 +00:00
|
|
|
{
|
2019-04-15 20:16:33 +00:00
|
|
|
// The first two entries are special - we never want to change their order
|
|
|
|
if (source_left.row() <= 1 || source_right.row() <= 1)
|
|
|
|
return source_left.row() < source_right.row();
|
2019-04-24 22:26:48 +00:00
|
|
|
|
|
|
|
// If there is a current location, sort by that - otherwise use the provided column
|
|
|
|
if (has_location(¤tLocation)) {
|
|
|
|
// The dive sites are -2 because of the first two items.
|
|
|
|
struct dive_site *ds1 = get_dive_site(source_left.row() - 2, &dive_site_table);
|
|
|
|
struct dive_site *ds2 = get_dive_site(source_right.row() - 2, &dive_site_table);
|
|
|
|
return get_distance(&ds1->location, ¤tLocation) < get_distance(&ds2->location, ¤tLocation);
|
|
|
|
}
|
2019-04-15 20:16:33 +00:00
|
|
|
return source_left.data().toString().compare(source_right.data().toString(), Qt::CaseInsensitive) < 0;
|
2015-09-21 18:04:52 +00:00
|
|
|
}
|
|
|
|
|
2019-04-15 18:15:40 +00:00
|
|
|
DiveLocationModel::DiveLocationModel(QObject *)
|
2015-09-21 17:01:58 +00:00
|
|
|
{
|
|
|
|
resetModel();
|
|
|
|
}
|
|
|
|
|
|
|
|
void DiveLocationModel::resetModel()
|
|
|
|
{
|
|
|
|
beginResetModel();
|
|
|
|
endResetModel();
|
|
|
|
}
|
|
|
|
|
2015-09-29 23:57:53 +00:00
|
|
|
QVariant DiveLocationModel::data(const QModelIndex &index, int role) const
|
2015-09-21 17:01:58 +00:00
|
|
|
{
|
2017-11-29 09:57:08 +00:00
|
|
|
static const QIcon plusIcon(":list-add-icon");
|
|
|
|
static const QIcon geoCode(":geotag-icon");
|
2015-09-25 15:19:41 +00:00
|
|
|
|
2015-09-29 23:57:53 +00:00
|
|
|
if (index.row() <= 1) { // two special cases.
|
2018-10-25 06:02:06 +00:00
|
|
|
if (index.column() == LocationInformationModel::DIVESITE)
|
2018-10-30 17:34:36 +00:00
|
|
|
return QVariant::fromValue<dive_site *>(RECENTLY_ADDED_DIVESITE);
|
2015-09-29 23:57:53 +00:00
|
|
|
switch (role) {
|
|
|
|
case Qt::DisplayRole:
|
|
|
|
return new_ds_value[index.row()];
|
|
|
|
case Qt::ToolTipRole:
|
2019-04-13 16:54:41 +00:00
|
|
|
return current_dive && current_dive->dive_site ?
|
2015-09-30 22:33:33 +00:00
|
|
|
tr("Create a new dive site, copying relevant information from the current dive.") :
|
|
|
|
tr("Create a new dive site with this name");
|
2015-09-29 23:57:53 +00:00
|
|
|
case Qt::DecorationRole:
|
|
|
|
return plusIcon;
|
2015-09-21 17:01:58 +00:00
|
|
|
}
|
2019-04-13 16:54:41 +00:00
|
|
|
return QVariant();
|
2015-09-21 17:01:58 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// The dive sites are -2 because of the first two items.
|
2019-02-26 21:26:11 +00:00
|
|
|
struct dive_site *ds = get_dive_site(index.row() - 2, &dive_site_table);
|
2018-10-09 10:26:58 +00:00
|
|
|
return LocationInformationModel::getDiveSiteData(ds, index.column(), role);
|
2015-09-21 17:01:58 +00:00
|
|
|
}
|
|
|
|
|
2018-05-21 16:09:09 +00:00
|
|
|
int DiveLocationModel::columnCount(const QModelIndex&) const
|
2015-09-21 17:01:58 +00:00
|
|
|
{
|
2018-10-09 10:26:58 +00:00
|
|
|
return LocationInformationModel::COLUMNS;
|
2015-09-21 17:01:58 +00:00
|
|
|
}
|
|
|
|
|
2018-05-21 16:09:09 +00:00
|
|
|
int DiveLocationModel::rowCount(const QModelIndex&) const
|
2015-09-21 17:01:58 +00:00
|
|
|
{
|
|
|
|
return dive_site_table.nr + 2;
|
|
|
|
}
|
|
|
|
|
2018-05-21 16:09:09 +00:00
|
|
|
bool DiveLocationModel::setData(const QModelIndex &index, const QVariant &value, int)
|
2015-09-21 17:01:58 +00:00
|
|
|
{
|
2015-09-29 23:57:53 +00:00
|
|
|
if (!index.isValid())
|
2015-09-21 17:01:58 +00:00
|
|
|
return false;
|
|
|
|
if (index.row() > 1)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
new_ds_value[index.row()] = value.toString();
|
|
|
|
|
|
|
|
dataChanged(index, index);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2015-09-22 17:31:56 +00:00
|
|
|
DiveLocationLineEdit::DiveLocationLineEdit(QWidget *parent) : QLineEdit(parent),
|
2015-09-29 23:57:53 +00:00
|
|
|
proxy(new DiveLocationFilterProxyModel()),
|
|
|
|
model(new DiveLocationModel()),
|
2015-10-02 19:22:55 +00:00
|
|
|
view(new DiveLocationListView()),
|
2018-10-25 06:02:06 +00:00
|
|
|
currDs(nullptr)
|
2015-09-21 17:01:58 +00:00
|
|
|
{
|
|
|
|
proxy->setSourceModel(model);
|
2018-10-09 10:26:58 +00:00
|
|
|
proxy->setFilterKeyColumn(LocationInformationModel::NAME);
|
2015-09-21 19:51:39 +00:00
|
|
|
|
2015-09-21 18:04:52 +00:00
|
|
|
view->setModel(proxy);
|
2018-10-09 10:26:58 +00:00
|
|
|
view->setModelColumn(LocationInformationModel::NAME);
|
2019-04-25 07:35:46 +00:00
|
|
|
view->setItemDelegate(&delegate);
|
2015-09-21 19:51:39 +00:00
|
|
|
view->setEditTriggers(QAbstractItemView::NoEditTriggers);
|
|
|
|
view->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
|
|
|
view->setSelectionBehavior(QAbstractItemView::SelectRows);
|
|
|
|
view->setSelectionMode(QAbstractItemView::SingleSelection);
|
|
|
|
view->setParent(0, Qt::Popup);
|
|
|
|
view->installEventFilter(this);
|
2015-09-22 17:31:56 +00:00
|
|
|
view->setFocusPolicy(Qt::NoFocus);
|
|
|
|
view->setFocusProxy(this);
|
2015-09-25 16:11:44 +00:00
|
|
|
view->setMouseTracking(true);
|
2015-09-21 19:51:39 +00:00
|
|
|
|
2015-09-21 17:01:58 +00:00
|
|
|
connect(this, &QLineEdit::textEdited, this, &DiveLocationLineEdit::setTemporaryDiveSiteName);
|
2015-09-21 20:18:52 +00:00
|
|
|
connect(view, &QAbstractItemView::activated, this, &DiveLocationLineEdit::itemActivated);
|
2015-09-25 16:11:44 +00:00
|
|
|
connect(view, &QAbstractItemView::entered, this, &DiveLocationLineEdit::entered);
|
2015-09-25 16:21:23 +00:00
|
|
|
connect(view, &DiveLocationListView::currentIndexChanged, this, &DiveLocationLineEdit::currentChanged);
|
2015-09-21 17:01:58 +00:00
|
|
|
}
|
|
|
|
|
2019-04-15 20:16:33 +00:00
|
|
|
bool DiveLocationLineEdit::eventFilter(QObject *, QEvent *e)
|
2015-09-21 19:51:39 +00:00
|
|
|
{
|
2015-09-29 23:57:53 +00:00
|
|
|
if (e->type() == QEvent::KeyPress) {
|
|
|
|
QKeyEvent *keyEv = (QKeyEvent *)e;
|
2015-09-21 19:51:39 +00:00
|
|
|
|
|
|
|
if (keyEv->key() == Qt::Key_Escape) {
|
|
|
|
view->hide();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2015-09-29 23:57:53 +00:00
|
|
|
if (keyEv->key() == Qt::Key_Return ||
|
|
|
|
keyEv->key() == Qt::Key_Enter) {
|
2015-10-25 01:38:16 +00:00
|
|
|
#if __APPLE__
|
|
|
|
// for some reason it seems like on a Mac hitting return/enter
|
|
|
|
// doesn't call 'activated' for that index. so let's do it manually
|
|
|
|
if (view->currentIndex().isValid())
|
|
|
|
itemActivated(view->currentIndex());
|
|
|
|
#endif
|
2015-09-21 19:51:39 +00:00
|
|
|
view->hide();
|
|
|
|
return false;
|
|
|
|
}
|
2015-09-22 19:27:07 +00:00
|
|
|
|
2015-09-29 23:57:53 +00:00
|
|
|
if (keyEv->key() == Qt::Key_Tab) {
|
|
|
|
itemActivated(view->currentIndex());
|
|
|
|
view->hide();
|
|
|
|
return false;
|
|
|
|
}
|
2015-09-21 20:11:59 +00:00
|
|
|
event(e);
|
2015-09-29 23:57:53 +00:00
|
|
|
} else if (e->type() == QEvent::MouseButtonPress) {
|
2015-09-22 17:31:56 +00:00
|
|
|
if (!view->underMouse()) {
|
|
|
|
view->hide();
|
|
|
|
return true;
|
|
|
|
}
|
2015-09-21 19:51:39 +00:00
|
|
|
}
|
2018-06-26 04:17:00 +00:00
|
|
|
else if (e->type() == QEvent::InputMethod) {
|
|
|
|
this->inputMethodEvent(static_cast<QInputMethodEvent *>(e));
|
|
|
|
}
|
2015-09-21 20:11:59 +00:00
|
|
|
|
2015-09-21 19:51:39 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2015-09-29 23:57:53 +00:00
|
|
|
void DiveLocationLineEdit::focusOutEvent(QFocusEvent *ev)
|
2015-09-22 18:23:38 +00:00
|
|
|
{
|
2019-06-30 21:08:02 +00:00
|
|
|
if (!view->isVisible())
|
2015-09-22 18:23:38 +00:00
|
|
|
QLineEdit::focusOutEvent(ev);
|
|
|
|
}
|
|
|
|
|
2015-09-29 23:57:53 +00:00
|
|
|
void DiveLocationLineEdit::itemActivated(const QModelIndex &index)
|
2015-09-21 20:18:52 +00:00
|
|
|
{
|
2015-10-05 21:01:28 +00:00
|
|
|
QModelIndex idx = index;
|
2018-10-25 06:02:06 +00:00
|
|
|
if (index.column() == LocationInformationModel::DIVESITE)
|
2018-10-09 10:26:58 +00:00
|
|
|
idx = index.model()->index(index.row(), LocationInformationModel::NAME);
|
2015-10-05 21:01:28 +00:00
|
|
|
|
2018-10-28 20:16:42 +00:00
|
|
|
dive_site *ds = index.model()->index(index.row(), LocationInformationModel::DIVESITE).data().value<dive_site *>();
|
2018-10-25 06:02:06 +00:00
|
|
|
currDs = ds;
|
2015-10-05 21:01:28 +00:00
|
|
|
setText(idx.data().toString());
|
2015-09-29 23:57:53 +00:00
|
|
|
if (view->isVisible())
|
2015-09-23 16:56:49 +00:00
|
|
|
view->hide();
|
2018-10-24 19:14:57 +00:00
|
|
|
emit diveSiteSelected();
|
2015-09-21 20:18:52 +00:00
|
|
|
}
|
|
|
|
|
2015-09-21 17:01:58 +00:00
|
|
|
void DiveLocationLineEdit::refreshDiveSiteCache()
|
|
|
|
{
|
|
|
|
model->resetModel();
|
|
|
|
}
|
|
|
|
|
2015-09-29 23:57:53 +00:00
|
|
|
static struct dive_site *get_dive_site_name_start_which_str(const QString &str)
|
|
|
|
{
|
2015-09-21 17:01:58 +00:00
|
|
|
struct dive_site *ds;
|
|
|
|
int i;
|
2019-02-26 21:26:11 +00:00
|
|
|
for_each_dive_site (i, ds, &dive_site_table) {
|
2015-09-21 17:01:58 +00:00
|
|
|
QString dsName(ds->name);
|
2015-09-21 18:04:52 +00:00
|
|
|
if (dsName.toLower().startsWith(str.toLower())) {
|
2015-09-21 17:01:58 +00:00
|
|
|
return ds;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2019-04-15 18:15:40 +00:00
|
|
|
void DiveLocationLineEdit::setTemporaryDiveSiteName(const QString &name)
|
2015-09-21 17:01:58 +00:00
|
|
|
{
|
2018-10-08 20:23:47 +00:00
|
|
|
// This function fills the first two entries with potential names of
|
|
|
|
// a dive site to be generated. The first entry is simply the entered
|
|
|
|
// text. The second entry is the first known dive site name starting
|
|
|
|
// with the entered text.
|
2018-10-09 10:26:58 +00:00
|
|
|
QModelIndex i0 = model->index(0, LocationInformationModel::NAME);
|
|
|
|
QModelIndex i1 = model->index(1, LocationInformationModel::NAME);
|
2019-04-15 18:15:40 +00:00
|
|
|
model->setData(i0, name);
|
2015-09-21 17:01:58 +00:00
|
|
|
|
2018-10-08 20:23:47 +00:00
|
|
|
// Note: if i1_name stays empty, the line will automatically
|
|
|
|
// be filtered out by the proxy filter, as it does not contain
|
|
|
|
// the user entered text.
|
|
|
|
QString i1_name;
|
2019-04-15 18:15:40 +00:00
|
|
|
if (struct dive_site *ds = get_dive_site_name_start_which_str(name)) {
|
2015-09-21 18:04:52 +00:00
|
|
|
const QString orig_name = QString(ds->name).toLower();
|
2019-04-15 18:15:40 +00:00
|
|
|
const QString new_name = name.toLower();
|
2015-09-21 18:04:52 +00:00
|
|
|
if (new_name != orig_name)
|
|
|
|
i1_name = QString(ds->name);
|
|
|
|
}
|
|
|
|
|
2015-09-29 23:57:53 +00:00
|
|
|
model->setData(i1, i1_name);
|
2019-04-15 18:15:40 +00:00
|
|
|
proxy->setFilter(name);
|
2015-09-23 19:27:44 +00:00
|
|
|
fixPopupPosition();
|
|
|
|
if (!view->isVisible())
|
|
|
|
view->show();
|
2015-09-21 17:01:58 +00:00
|
|
|
}
|
|
|
|
|
2015-09-21 19:08:58 +00:00
|
|
|
void DiveLocationLineEdit::keyPressEvent(QKeyEvent *ev)
|
|
|
|
{
|
2015-09-21 20:22:31 +00:00
|
|
|
QLineEdit::keyPressEvent(ev);
|
2015-09-29 23:57:53 +00:00
|
|
|
if (ev->key() != Qt::Key_Left &&
|
|
|
|
ev->key() != Qt::Key_Right &&
|
|
|
|
ev->key() != Qt::Key_Escape &&
|
|
|
|
ev->key() != Qt::Key_Return) {
|
2015-09-22 20:20:55 +00:00
|
|
|
|
2019-06-30 21:18:12 +00:00
|
|
|
if (ev->key() != Qt::Key_Up && ev->key() != Qt::Key_Down)
|
2018-10-25 06:02:06 +00:00
|
|
|
currDs = RECENTLY_ADDED_DIVESITE;
|
2019-06-30 21:18:12 +00:00
|
|
|
else
|
2015-09-23 19:27:44 +00:00
|
|
|
showPopup();
|
2015-09-21 19:51:39 +00:00
|
|
|
} else if (ev->key() == Qt::Key_Escape) {
|
|
|
|
view->hide();
|
2015-09-21 19:08:58 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-09-23 19:03:28 +00:00
|
|
|
void DiveLocationLineEdit::fixPopupPosition()
|
2015-09-21 19:08:58 +00:00
|
|
|
{
|
2015-09-22 17:13:51 +00:00
|
|
|
const QRect screen = QApplication::desktop()->availableGeometry(this);
|
|
|
|
const int maxVisibleItems = 5;
|
|
|
|
QPoint pos;
|
|
|
|
int rh, w;
|
|
|
|
int h = (view->sizeHintForRow(0) * qMin(maxVisibleItems, view->model()->rowCount()) + 3) + 3;
|
|
|
|
QScrollBar *hsb = view->horizontalScrollBar();
|
|
|
|
if (hsb && hsb->isVisible())
|
|
|
|
h += view->horizontalScrollBar()->sizeHint().height();
|
|
|
|
|
|
|
|
rh = height();
|
|
|
|
pos = mapToGlobal(QPoint(0, height() - 2));
|
|
|
|
w = width();
|
|
|
|
|
|
|
|
if (w > screen.width())
|
|
|
|
w = screen.width();
|
|
|
|
if ((pos.x() + w) > (screen.x() + screen.width()))
|
|
|
|
pos.setX(screen.x() + screen.width() - w);
|
|
|
|
if (pos.x() < screen.x())
|
|
|
|
pos.setX(screen.x());
|
|
|
|
|
|
|
|
int top = pos.y() - rh - screen.top() + 2;
|
|
|
|
int bottom = screen.bottom() - pos.y();
|
|
|
|
h = qMax(h, view->minimumHeight());
|
|
|
|
if (h > bottom) {
|
|
|
|
h = qMin(qMax(top, bottom), h);
|
|
|
|
if (top > bottom)
|
|
|
|
pos.setY(pos.y() - h - rh + 2);
|
|
|
|
}
|
2015-09-21 19:08:58 +00:00
|
|
|
|
2015-09-22 17:13:51 +00:00
|
|
|
view->setGeometry(pos.x(), pos.y(), w, h);
|
2015-10-01 19:27:22 +00:00
|
|
|
if (!view->currentIndex().isValid() && view->model()->rowCount()) {
|
2016-01-19 19:35:52 +00:00
|
|
|
view->setCurrentIndex(view->model()->index(0, 1));
|
2015-09-23 19:19:58 +00:00
|
|
|
}
|
2015-09-23 19:03:28 +00:00
|
|
|
}
|
2015-09-21 19:08:58 +00:00
|
|
|
|
2019-04-24 22:26:48 +00:00
|
|
|
void DiveLocationLineEdit::setCurrentDiveSite(struct dive *d)
|
2015-09-25 17:51:10 +00:00
|
|
|
{
|
2019-10-27 20:08:50 +00:00
|
|
|
location_t currentLocation;
|
|
|
|
if (d) {
|
|
|
|
currDs = get_dive_site_for_dive(d);
|
|
|
|
currentLocation = dive_get_gps_location(d);
|
|
|
|
} else {
|
|
|
|
currDs = nullptr;
|
2020-01-09 04:55:29 +00:00
|
|
|
currentLocation = zero_location;
|
2019-10-27 20:08:50 +00:00
|
|
|
}
|
2019-06-30 21:18:12 +00:00
|
|
|
if (!currDs)
|
2015-09-25 17:51:10 +00:00
|
|
|
clear();
|
2019-06-30 21:18:12 +00:00
|
|
|
else
|
2019-10-27 20:08:50 +00:00
|
|
|
setText(currDs->name);
|
2019-04-24 22:26:48 +00:00
|
|
|
proxy->setCurrentLocation(currentLocation);
|
2019-04-25 07:35:46 +00:00
|
|
|
delegate.setCurrentLocation(currentLocation);
|
2015-09-25 17:51:10 +00:00
|
|
|
}
|
|
|
|
|
2015-09-23 19:03:28 +00:00
|
|
|
void DiveLocationLineEdit::showPopup()
|
|
|
|
{
|
2019-04-25 11:09:11 +00:00
|
|
|
if (!view->isVisible())
|
2015-09-22 19:27:07 +00:00
|
|
|
setTemporaryDiveSiteName(text());
|
2015-09-21 19:08:58 +00:00
|
|
|
}
|
|
|
|
|
2019-04-25 11:16:30 +00:00
|
|
|
void DiveLocationLineEdit::showAllSites()
|
|
|
|
{
|
|
|
|
if (!view->isVisible()) {
|
|
|
|
// By setting the "temporary dive site name" to the empty string,
|
|
|
|
// all dive sites are shown sorted by distance from the site of
|
|
|
|
// the current dive.
|
|
|
|
setTemporaryDiveSiteName(QString());
|
|
|
|
|
|
|
|
// By selecting the whole text, the user can immediately start
|
|
|
|
// typing to activate the full-text filter.
|
|
|
|
selectAll();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-10-25 06:02:06 +00:00
|
|
|
struct dive_site *DiveLocationLineEdit::currDiveSite() const
|
2015-09-23 17:46:29 +00:00
|
|
|
{
|
2019-06-30 21:08:02 +00:00
|
|
|
// If there is no text, this corresponds to the empty dive site
|
|
|
|
return text().trimmed().isEmpty() ? nullptr : currDs;
|
2015-09-23 17:46:29 +00:00
|
|
|
}
|
|
|
|
|
2018-05-21 16:09:09 +00:00
|
|
|
DiveLocationListView::DiveLocationListView(QWidget*)
|
2015-09-21 17:01:58 +00:00
|
|
|
{
|
|
|
|
}
|
2015-09-25 16:21:23 +00:00
|
|
|
|
2015-09-29 23:57:53 +00:00
|
|
|
void DiveLocationListView::currentChanged(const QModelIndex ¤t, const QModelIndex &previous)
|
2015-09-25 16:21:23 +00:00
|
|
|
{
|
|
|
|
QListView::currentChanged(current, previous);
|
|
|
|
emit currentIndexChanged(current);
|
|
|
|
}
|