summaryrefslogtreecommitdiffstats
path: root/desktop-widgets/locationinformation.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'desktop-widgets/locationinformation.cpp')
-rw-r--r--desktop-widgets/locationinformation.cpp618
1 files changed, 618 insertions, 0 deletions
diff --git a/desktop-widgets/locationinformation.cpp b/desktop-widgets/locationinformation.cpp
new file mode 100644
index 000000000..aee0b7328
--- /dev/null
+++ b/desktop-widgets/locationinformation.cpp
@@ -0,0 +1,618 @@
+#include "locationinformation.h"
+#include "dive.h"
+#include "mainwindow.h"
+#include "divelistview.h"
+#include "qthelper.h"
+#include "globe.h"
+#include "filtermodels.h"
+#include "divelocationmodel.h"
+#include "divesitehelpers.h"
+#include "modeldelegates.h"
+
+#include <QDebug>
+#include <QShowEvent>
+#include <QItemSelectionModel>
+#include <qmessagebox.h>
+#include <cstdlib>
+#include <QDesktopWidget>
+#include <QScrollBar>
+
+LocationInformationWidget::LocationInformationWidget(QWidget *parent) : QGroupBox(parent), modified(false)
+{
+ ui.setupUi(this);
+ ui.diveSiteMessage->setCloseButtonVisible(false);
+
+ 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(acceptAction);
+ ui.diveSiteMessage->addAction(rejectAction);
+
+ connect(this, SIGNAL(startFilterDiveSite(uint32_t)), MultiFilterSortModel::instance(), SLOT(startFilterDiveSite(uint32_t)));
+ connect(this, SIGNAL(stopFilterDiveSite()), MultiFilterSortModel::instance(), SLOT(stopFilterDiveSite()));
+ connect(ui.geoCodeButton, SIGNAL(clicked()), this, SLOT(reverseGeocode()));
+
+ SsrfSortFilterProxyModel *filter_model = new SsrfSortFilterProxyModel(this);
+ filter_model->setSourceModel(LocationInformationModel::instance());
+ filter_model->setFilterRow(filter_same_gps_cb);
+ ui.diveSiteListView->setModel(filter_model);
+ ui.diveSiteListView->setModelColumn(LocationInformationModel::NAME);
+ ui.diveSiteListView->installEventFilter(this);
+#ifndef NO_MARBLE
+ // Globe Management Code.
+ connect(this, &LocationInformationWidget::requestCoordinates,
+ GlobeGPS::instance(), &GlobeGPS::prepareForGetDiveCoordinates);
+ connect(this, &LocationInformationWidget::endRequestCoordinates,
+ GlobeGPS::instance(), &GlobeGPS::endGetDiveCoordinates);
+ connect(GlobeGPS::instance(), &GlobeGPS::coordinatesChanged,
+ this, &LocationInformationWidget::updateGpsCoordinates);
+ connect(this, &LocationInformationWidget::endEditDiveSite,
+ GlobeGPS::instance(), &GlobeGPS::repopulateLabels);
+#endif
+}
+
+bool LocationInformationWidget::eventFilter(QObject *, QEvent *ev)
+{
+ if (ev->type() == QEvent::ContextMenu) {
+ QContextMenuEvent *ctx = (QContextMenuEvent *)ev;
+ QMenu contextMenu;
+ contextMenu.addAction(tr("Merge into current site"), this, SLOT(mergeSelectedDiveSites()));
+ contextMenu.exec(ctx->globalPos());
+ return true;
+ }
+ return false;
+}
+
+void LocationInformationWidget::mergeSelectedDiveSites()
+{
+ if (QMessageBox::warning(MainWindow::instance(), tr("Merging dive sites"),
+ tr("You are about to merge dive sites, you can't undo that action \n Are you sure you want to continue?"),
+ QMessageBox::Ok, QMessageBox::Cancel) != QMessageBox::Ok)
+ return;
+
+ QModelIndexList selection = ui.diveSiteListView->selectionModel()->selectedIndexes();
+ uint32_t *selected_dive_sites = (uint32_t *)malloc(sizeof(uint32_t) * selection.count());
+ int i = 0;
+ Q_FOREACH (const QModelIndex &idx, selection) {
+ selected_dive_sites[i] = (uint32_t)idx.data(LocationInformationModel::UUID_ROLE).toInt();
+ i++;
+ }
+ merge_dive_sites(displayed_dive_site.uuid, selected_dive_sites, i);
+ LocationInformationModel::instance()->update();
+ QSortFilterProxyModel *m = (QSortFilterProxyModel *)ui.diveSiteListView->model();
+ m->invalidate();
+ free(selected_dive_sites);
+}
+
+void LocationInformationWidget::updateLabels()
+{
+ 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) {
+ const char *coords = printGPSCoords(displayed_dive_site.latitude.udeg, displayed_dive_site.longitude.udeg);
+ ui.diveSiteCoordinates->setText(coords);
+ free((void *)coords);
+ } else {
+ ui.diveSiteCoordinates->clear();
+ }
+
+ ui.locationTags->setText(constructLocationTags(displayed_dive_site.uuid));
+
+ emit startFilterDiveSite(displayed_dive_site.uuid);
+ emit startEditDiveSite(displayed_dive_site.uuid);
+}
+
+void LocationInformationWidget::updateGpsCoordinates()
+{
+ QString oldText = ui.diveSiteCoordinates->text();
+ const char *coords = printGPSCoords(displayed_dive_site.latitude.udeg, displayed_dive_site.longitude.udeg);
+ ui.diveSiteCoordinates->setText(coords);
+ free((void *)coords);
+ if (oldText != ui.diveSiteCoordinates->text())
+ markChangedWidget(ui.diveSiteCoordinates);
+}
+
+void LocationInformationWidget::acceptChanges()
+{
+ char *uiString;
+ struct dive_site *currentDs;
+ uiString = ui.diveSiteName->text().toUtf8().data();
+
+ if (get_dive_site_by_uuid(displayed_dive_site.uuid) != NULL)
+ currentDs = get_dive_site_by_uuid(displayed_dive_site.uuid);
+ else
+ currentDs = get_dive_site_by_uuid(create_dive_site_from_current_dive(uiString));
+
+ currentDs->latitude = displayed_dive_site.latitude;
+ currentDs->longitude = displayed_dive_site.longitude;
+ 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 (!ui.diveSiteCoordinates->text().isEmpty()) {
+ double lat, lon;
+ parseGpsText(ui.diveSiteCoordinates->text(), &lat, &lon);
+ currentDs->latitude.udeg = lat * 1000000.0;
+ currentDs->longitude.udeg = lon * 1000000.0;
+ }
+ if (dive_site_is_empty(currentDs)) {
+ LocationInformationModel::instance()->removeRow(get_divesite_idx(currentDs));
+ displayed_dive.dive_site_uuid = 0;
+ }
+ copy_dive_site(currentDs, &displayed_dive_site);
+ mark_divelist_changed(true);
+ resetState();
+ emit endRequestCoordinates();
+ emit endEditDiveSite();
+ emit stopFilterDiveSite();
+ emit coordinatesChanged();
+}
+
+void LocationInformationWidget::rejectChanges()
+{
+ resetState();
+ emit endRequestCoordinates();
+ emit stopFilterDiveSite();
+ emit endEditDiveSite();
+ emit coordinatesChanged();
+}
+
+void LocationInformationWidget::showEvent(QShowEvent *ev)
+{
+ if (displayed_dive_site.uuid) {
+ updateLabels();
+ ui.geoCodeButton->setEnabled(dive_site_has_gps_location(&displayed_dive_site));
+ QSortFilterProxyModel *m = qobject_cast<QSortFilterProxyModel *>(ui.diveSiteListView->model());
+ emit startFilterDiveSite(displayed_dive_site.uuid);
+ if (m)
+ m->invalidate();
+ }
+ emit requestCoordinates();
+
+ 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"));
+}
+
+void LocationInformationWidget::enableEdition()
+{
+ MainWindow::instance()->dive_list()->setEnabled(false);
+ MainWindow::instance()->setEnabledToolbar(false);
+ ui.diveSiteMessage->setText(tr("You are editing a dive site"));
+}
+
+void LocationInformationWidget::on_diveSiteCoordinates_textChanged(const QString &text)
+{
+ uint lat = displayed_dive_site.latitude.udeg;
+ uint lon = displayed_dive_site.longitude.udeg;
+ const char *coords = printGPSCoords(lat, lon);
+ if (!same_string(qPrintable(text), coords)) {
+ double latitude, longitude;
+ if (parseGpsText(text, &latitude, &longitude)) {
+ displayed_dive_site.latitude.udeg = latitude * 1000000;
+ displayed_dive_site.longitude.udeg = longitude * 1000000;
+ markChangedWidget(ui.diveSiteCoordinates);
+ emit coordinatesChanged();
+ ui.geoCodeButton->setEnabled(latitude != 0 && longitude != 0);
+ } else {
+ ui.geoCodeButton->setEnabled(false);
+ }
+ }
+ free((void *)coords);
+}
+
+void LocationInformationWidget::on_diveSiteDescription_textChanged(const QString &text)
+{
+ if (!same_string(qPrintable(text), displayed_dive_site.description))
+ markChangedWidget(ui.diveSiteDescription);
+}
+
+void LocationInformationWidget::on_diveSiteName_textChanged(const QString &text)
+{
+ if (!same_string(qPrintable(text), displayed_dive_site.name))
+ markChangedWidget(ui.diveSiteName);
+}
+
+void LocationInformationWidget::on_diveSiteNotes_textChanged()
+{
+ if (!same_string(qPrintable(ui.diveSiteNotes->toPlainText()), displayed_dive_site.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);
+}
+
+void LocationInformationWidget::reverseGeocode()
+{
+ ReverseGeoLookupThread *geoLookup = ReverseGeoLookupThread::instance();
+ geoLookup->lookup(&displayed_dive_site);
+ updateLabels();
+}
+
+DiveLocationFilterProxyModel::DiveLocationFilterProxyModel(QObject *parent)
+{
+}
+
+DiveLocationLineEdit *location_line_edit = 0;
+
+bool DiveLocationFilterProxyModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const
+{
+ if (source_row == 0)
+ return true;
+
+ QString sourceString = sourceModel()->index(source_row, DiveLocationModel::NAME).data(Qt::DisplayRole).toString();
+ return sourceString.toLower().startsWith(location_line_edit->text().toLower());
+}
+
+bool DiveLocationFilterProxyModel::lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const
+{
+ return source_left.data().toString() <= source_right.data().toString();
+}
+
+
+DiveLocationModel::DiveLocationModel(QObject *o)
+{
+ resetModel();
+}
+
+void DiveLocationModel::resetModel()
+{
+ beginResetModel();
+ endResetModel();
+}
+
+QVariant DiveLocationModel::data(const QModelIndex &index, int role) const
+{
+ static const QIcon plusIcon(":plus");
+ static const QIcon geoCode(":geocode");
+
+ if (index.row() <= 1) { // two special cases.
+ if (index.column() == UUID) {
+ return RECENTLY_ADDED_DIVESITE;
+ }
+ switch (role) {
+ case Qt::DisplayRole:
+ return new_ds_value[index.row()];
+ case Qt::ToolTipRole:
+ return displayed_dive_site.uuid ?
+ tr("Create a new dive site, copying relevant information from the current dive.") :
+ tr("Create a new dive site with this name");
+ case Qt::DecorationRole:
+ return plusIcon;
+ }
+ }
+
+ // The dive sites are -2 because of the first two items.
+ struct dive_site *ds = get_dive_site(index.row() - 2);
+ switch (role) {
+ case Qt::EditRole:
+ case Qt::DisplayRole:
+ switch (index.column()) {
+ case UUID:
+ return ds->uuid;
+ case NAME:
+ return ds->name;
+ case LATITUDE:
+ return ds->latitude.udeg;
+ case LONGITUDE:
+ return ds->longitude.udeg;
+ case DESCRIPTION:
+ return ds->description;
+ case NOTES:
+ return ds->name;
+ }
+ break;
+ case Qt::DecorationRole: {
+ if (dive_site_has_gps_location(ds))
+ return geoCode;
+ }
+ }
+ return QVariant();
+}
+
+int DiveLocationModel::columnCount(const QModelIndex &parent) const
+{
+ return COLUMNS;
+}
+
+int DiveLocationModel::rowCount(const QModelIndex &parent) const
+{
+ return dive_site_table.nr + 2;
+}
+
+bool DiveLocationModel::setData(const QModelIndex &index, const QVariant &value, int role)
+{
+ if (!index.isValid())
+ return false;
+ if (index.row() > 1)
+ return false;
+
+ new_ds_value[index.row()] = value.toString();
+
+ dataChanged(index, index);
+ return true;
+}
+
+DiveLocationLineEdit::DiveLocationLineEdit(QWidget *parent) : QLineEdit(parent),
+ proxy(new DiveLocationFilterProxyModel()),
+ model(new DiveLocationModel()),
+ view(new DiveLocationListView()),
+ currType(NO_DIVE_SITE)
+{
+ currUuid = 0;
+ location_line_edit = this;
+
+ proxy->setSourceModel(model);
+ proxy->setFilterKeyColumn(DiveLocationModel::NAME);
+
+ view->setModel(proxy);
+ view->setModelColumn(DiveLocationModel::NAME);
+ view->setItemDelegate(new LocationFilterDelegate());
+ view->setEditTriggers(QAbstractItemView::NoEditTriggers);
+ view->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
+ view->setSelectionBehavior(QAbstractItemView::SelectRows);
+ view->setSelectionMode(QAbstractItemView::SingleSelection);
+ view->setParent(0, Qt::Popup);
+ view->installEventFilter(this);
+ view->setFocusPolicy(Qt::NoFocus);
+ view->setFocusProxy(this);
+ view->setMouseTracking(true);
+
+ connect(this, &QLineEdit::textEdited, this, &DiveLocationLineEdit::setTemporaryDiveSiteName);
+ connect(view, &QAbstractItemView::activated, this, &DiveLocationLineEdit::itemActivated);
+ connect(view, &QAbstractItemView::entered, this, &DiveLocationLineEdit::entered);
+ connect(view, &DiveLocationListView::currentIndexChanged, this, &DiveLocationLineEdit::currentChanged);
+}
+
+bool DiveLocationLineEdit::eventFilter(QObject *o, QEvent *e)
+{
+ if (e->type() == QEvent::KeyPress) {
+ QKeyEvent *keyEv = (QKeyEvent *)e;
+
+ if (keyEv->key() == Qt::Key_Escape) {
+ view->hide();
+ return true;
+ }
+
+ if (keyEv->key() == Qt::Key_Return ||
+ keyEv->key() == Qt::Key_Enter) {
+#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
+ view->hide();
+ return false;
+ }
+
+ if (keyEv->key() == Qt::Key_Tab) {
+ itemActivated(view->currentIndex());
+ view->hide();
+ return false;
+ }
+ event(e);
+ } else if (e->type() == QEvent::MouseButtonPress) {
+ if (!view->underMouse()) {
+ view->hide();
+ return true;
+ }
+ }
+
+ return false;
+}
+
+void DiveLocationLineEdit::focusOutEvent(QFocusEvent *ev)
+{
+ if (!view->isVisible()) {
+ QLineEdit::focusOutEvent(ev);
+ }
+}
+
+void DiveLocationLineEdit::itemActivated(const QModelIndex &index)
+{
+ QModelIndex idx = index;
+ if (index.column() == DiveLocationModel::UUID)
+ idx = index.model()->index(index.row(), DiveLocationModel::NAME);
+
+ QModelIndex uuidIndex = index.model()->index(index.row(), DiveLocationModel::UUID);
+ uint32_t uuid = uuidIndex.data().toInt();
+ currType = uuid == 1 ? NEW_DIVE_SITE : EXISTING_DIVE_SITE;
+ currUuid = uuid;
+ setText(idx.data().toString());
+ if (currUuid == NEW_DIVE_SITE)
+ qDebug() << "Setting a New dive site";
+ else
+ qDebug() << "Setting a Existing dive site";
+ if (view->isVisible())
+ view->hide();
+ emit diveSiteSelected(currUuid);
+}
+
+void DiveLocationLineEdit::refreshDiveSiteCache()
+{
+ model->resetModel();
+}
+
+static struct dive_site *get_dive_site_name_start_which_str(const QString &str)
+{
+ struct dive_site *ds;
+ int i;
+ for_each_dive_site (i, ds) {
+ QString dsName(ds->name);
+ if (dsName.toLower().startsWith(str.toLower())) {
+ return ds;
+ }
+ }
+ return NULL;
+}
+
+void DiveLocationLineEdit::setTemporaryDiveSiteName(const QString &s)
+{
+ QModelIndex i0 = model->index(0, DiveLocationModel::NAME);
+ QModelIndex i1 = model->index(1, DiveLocationModel::NAME);
+ model->setData(i0, text());
+
+ QString i1_name = INVALID_DIVE_SITE_NAME;
+ if (struct dive_site *ds = get_dive_site_name_start_which_str(text())) {
+ const QString orig_name = QString(ds->name).toLower();
+ const QString new_name = text().toLower();
+ if (new_name != orig_name)
+ i1_name = QString(ds->name);
+ }
+
+ model->setData(i1, i1_name);
+ proxy->invalidate();
+ fixPopupPosition();
+ if (!view->isVisible())
+ view->show();
+}
+
+void DiveLocationLineEdit::keyPressEvent(QKeyEvent *ev)
+{
+ QLineEdit::keyPressEvent(ev);
+ if (ev->key() != Qt::Key_Left &&
+ ev->key() != Qt::Key_Right &&
+ ev->key() != Qt::Key_Escape &&
+ ev->key() != Qt::Key_Return) {
+
+ if (ev->key() != Qt::Key_Up && ev->key() != Qt::Key_Down) {
+ currType = NEW_DIVE_SITE;
+ currUuid = RECENTLY_ADDED_DIVESITE;
+ } else {
+ showPopup();
+ }
+ } else if (ev->key() == Qt::Key_Escape) {
+ view->hide();
+ }
+}
+
+void DiveLocationLineEdit::fixPopupPosition()
+{
+ const QRect screen = QApplication::desktop()->availableGeometry(this);
+ const int maxVisibleItems = 5;
+ Qt::LayoutDirection dir = layoutDirection();
+ 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);
+ }
+
+ view->setGeometry(pos.x(), pos.y(), w, h);
+ if (!view->currentIndex().isValid() && view->model()->rowCount()) {
+ view->setCurrentIndex(view->model()->index(0, 0));
+ }
+}
+
+void DiveLocationLineEdit::setCurrentDiveSiteUuid(uint32_t uuid)
+{
+ currUuid = uuid;
+ if (uuid == 0) {
+ currType = NO_DIVE_SITE;
+ }
+ struct dive_site *ds = get_dive_site_by_uuid(uuid);
+ if (!ds)
+ clear();
+ else
+ setText(ds->name);
+}
+
+void DiveLocationLineEdit::showPopup()
+{
+ fixPopupPosition();
+ if (!view->isVisible()) {
+ setTemporaryDiveSiteName(text());
+ proxy->invalidate();
+ view->show();
+ }
+}
+
+DiveLocationLineEdit::DiveSiteType DiveLocationLineEdit::currDiveSiteType() const
+{
+ return currType;
+}
+
+uint32_t DiveLocationLineEdit::currDiveSiteUuid() const
+{
+ return currUuid;
+}
+
+DiveLocationListView::DiveLocationListView(QWidget *parent)
+{
+}
+
+void DiveLocationListView::currentChanged(const QModelIndex &current, const QModelIndex &previous)
+{
+ QListView::currentChanged(current, previous);
+ emit currentIndexChanged(current);
+}