diff options
Diffstat (limited to 'desktop-widgets/globe.cpp')
-rw-r--r-- | desktop-widgets/globe.cpp | 431 |
1 files changed, 431 insertions, 0 deletions
diff --git a/desktop-widgets/globe.cpp b/desktop-widgets/globe.cpp new file mode 100644 index 000000000..135f195a1 --- /dev/null +++ b/desktop-widgets/globe.cpp @@ -0,0 +1,431 @@ +#include "globe.h" +#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> +#include <marble/GeoDataDocument.h> +#include <marble/MarbleModel.h> +#include <marble/MarbleDirs.h> +#include <marble/MapThemeManager.h> +#include <marble/GeoDataStyle.h> +#include <marble/GeoDataIconStyle.h> +#include <marble/GeoDataTreeModel.h> + +#ifdef MARBLE_SUBSURFACE_BRANCH +#include <marble/MarbleDebug.h> +#endif + +GlobeGPS *GlobeGPS::instance() +{ + static GlobeGPS *self = new GlobeGPS(); + return self; +} + +GlobeGPS::GlobeGPS(QWidget *parent) : MarbleWidget(parent), + loadedDives(0), + messageWidget(new KMessageWidget(this)), + fixZoomTimer(new QTimer(this)), + needResetZoom(false), + editingDiveLocation(false), + doubleClick(false) +{ +#ifdef MARBLE_SUBSURFACE_BRANCH + // we need to make sure this gets called after the command line arguments have + // been processed but before we initialize the rest of Marble + Marble::MarbleDebug::setEnabled(verbose); +#endif + currentZoomLevel = -1; + // check if Google Sat Maps are installed + // if not, check if they are in a known location + MapThemeManager mtm; + QStringList list = mtm.mapThemeIds(); + QString subsurfaceDataPath; + QDir marble; + if (!list.contains("earth/googlesat/googlesat.dgml")) { + subsurfaceDataPath = getSubsurfaceDataPath("marbledata"); + if (subsurfaceDataPath.size()) { + MarbleDirs::setMarbleDataPath(subsurfaceDataPath); + } else { + subsurfaceDataPath = getSubsurfaceDataPath("data"); + if (subsurfaceDataPath.size()) + MarbleDirs::setMarbleDataPath(subsurfaceDataPath); + } + } + messageWidget->setCloseButtonVisible(false); + messageWidget->setHidden(true); + + setMapThemeId("earth/googlesat/googlesat.dgml"); + //setMapThemeId("earth/openstreetmap/openstreetmap.dgml"); + setProjection(Marble::Spherical); + + setAnimationsEnabled(true); + Q_FOREACH (AbstractFloatItem *i, floatItems()) { + i->setVisible(false); + } + + setShowClouds(false); + setShowBorders(false); + setShowPlaces(true); + setShowCrosshairs(false); + setShowGrid(false); + setShowOverviewMap(false); + setShowScaleBar(true); + setShowCompass(false); + connect(this, SIGNAL(mouseClickGeoPosition(qreal, qreal, GeoDataCoordinates::Unit)), + this, SLOT(mouseClicked(qreal, qreal, GeoDataCoordinates::Unit))); + + setMinimumHeight(0); + setMinimumWidth(0); + connect(fixZoomTimer, SIGNAL(timeout()), this, SLOT(fixZoom())); + fixZoomTimer->setSingleShot(true); + installEventFilter(this); +} + +bool GlobeGPS::eventFilter(QObject *obj, QEvent *ev) +{ + // sometimes Marble seems not to notice double clicks and consequently not call + // the right callback - so let's remember here if the last 'click' is a 'double' or not + enum QEvent::Type type = ev->type(); + if (type == QEvent::MouseButtonDblClick) + doubleClick = true; + else if (type == QEvent::MouseButtonPress) + doubleClick = false; + + // This disables Zooming when a double click occours on the scene. + if (type == QEvent::MouseButtonDblClick && !editingDiveLocation) + return true; + // This disables the Marble's Context Menu + // we need to move this to our 'contextMenuEvent' + // if we plan to do a different one in the future. + if (type == QEvent::ContextMenu) { + contextMenuEvent(static_cast<QContextMenuEvent *>(ev)); + return true; + } + if (type == QEvent::MouseButtonPress) { + QMouseEvent *e = static_cast<QMouseEvent *>(ev); + if (e->button() == Qt::RightButton) + return true; + } + return QObject::eventFilter(obj, ev); +} + +void GlobeGPS::contextMenuEvent(QContextMenuEvent *ev) +{ + QMenu m; + QAction *a = m.addAction(tr("Edit selected dive locations"), this, SLOT(prepareForGetDiveCoordinates())); + a->setData(QVariant::fromValue<void *>(&m)); + a->setEnabled(current_dive); + m.exec(ev->globalPos()); +} + +void GlobeGPS::mouseClicked(qreal lon, qreal lat, GeoDataCoordinates::Unit unit) +{ + if (doubleClick) { + // strangely sometimes we don't get the changeDiveGeoPosition callback + // and end up here instead + changeDiveGeoPosition(lon, lat, unit); + return; + } + // don't mess with the selection while the user is editing a dive + if (MainWindow::instance()->information()->isEditing() || messageWidget->isVisible()) + return; + + GeoDataCoordinates here(lon, lat, unit); + long lon_udeg = rint(1000000 * here.longitude(GeoDataCoordinates::Degree)); + long lat_udeg = rint(1000000 * here.latitude(GeoDataCoordinates::Degree)); + + // distance() is in km above the map. + // We're going to use that to decide how + // approximate the dives have to be. + // + // Totally arbitrarily I say that 1km + // distance means that we can resolve + // to about 100m. Which in turn is about + // 1000 udeg. + // + // Trigonometry is hard, but sin x == x + // for small x, so let's just do this as + // a linear thing. + long resolve = rint(distance() * 1000); + + int idx; + struct dive *dive; + bool clear = !(QApplication::keyboardModifiers() & Qt::ControlModifier); + QList<int> selectedDiveIds; + for_each_dive (idx, dive) { + long lat_diff, lon_diff; + struct dive_site *ds = get_dive_site_for_dive(dive); + if (!dive_site_has_gps_location(ds)) + continue; + 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) + lon_diff = 180000000 - lon_diff; + if (lat_diff > resolve || lon_diff > resolve) + continue; + + selectedDiveIds.push_back(idx); + } + if (selectedDiveIds.empty()) + return; + if (clear) + MainWindow::instance()->dive_list()->unselectDives(); + MainWindow::instance()->dive_list()->selectDives(selectedDiveIds); +} + +void GlobeGPS::repopulateLabels() +{ + static GeoDataStyle otherSite, currentSite; + static GeoDataIconStyle darkFlag(QImage(":flagDark")), lightFlag(QImage(":flagLight")); + struct dive_site *ds; + int idx; + QMap<QString, GeoDataPlacemark *> locationMap; + if (loadedDives) { + model()->treeModel()->removeDocument(loadedDives); + delete loadedDives; + } + loadedDives = new GeoDataDocument; + otherSite.setIconStyle(darkFlag); + currentSite.setIconStyle(lightFlag); + + if (displayed_dive_site.uuid && dive_site_has_gps_location(&displayed_dive_site)) { + GeoDataPlacemark *place = new GeoDataPlacemark(displayed_dive_site.name); + place->setStyle(¤tSite); + place->setCoordinate(displayed_dive_site.longitude.udeg / 1000000.0, + displayed_dive_site.latitude.udeg / 1000000.0, 0, GeoDataCoordinates::Degree); + locationMap[QString(displayed_dive_site.name)] = place; + loadedDives->append(place); + } + for_each_dive_site(idx, ds) { + if (ds->uuid == displayed_dive_site.uuid) + continue; + if (dive_site_has_gps_location(ds)) { + GeoDataPlacemark *place = new GeoDataPlacemark(ds->name); + place->setStyle(&otherSite); + 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(ds->name)]) { + GeoDataCoordinates existingLocation = locationMap[QString(ds->name)]->coordinate(); + GeoDataLineString segment = GeoDataLineString(); + segment.append(existingLocation); + GeoDataCoordinates newLocation = place->coordinate(); + segment.append(newLocation); + double dist = segment.length(6371); + // the dist is scaled to the radius given - so with 6371km as radius + // 50m turns into 0.05 as threashold + if (dist < 0.05) + continue; + } + locationMap[QString(ds->name)] = place; + loadedDives->append(place); + } + } + model()->treeModel()->addDocument(loadedDives); + + struct dive_site *center = displayed_dive_site.uuid != 0 ? + &displayed_dive_site : current_dive ? + get_dive_site_by_uuid(current_dive->dive_site_uuid) : NULL; + if(dive_site_has_gps_location(&displayed_dive_site) && center) + centerOn(displayed_dive_site.longitude.udeg / 1000000.0, displayed_dive_site.latitude.udeg / 1000000.0, true); +} + +void GlobeGPS::reload() +{ + editingDiveLocation = false; + messageWidget->hide(); + repopulateLabels(); +} + +void GlobeGPS::centerOnDiveSite(struct dive_site *ds) +{ + if (!dive_site_has_gps_location(ds)) { + // this is not intuitive and at times causes trouble - let's comment it out for now + // zoomOutForNoGPS(); + return; + } + qreal longitude = ds->longitude.udeg / 1000000.0; + qreal latitude = ds->latitude.udeg / 1000000.0; + + if(IS_FP_SAME(longitude, centerLongitude()) && IS_FP_SAME(latitude,centerLatitude())) { + return; + } + + // if no zoom is set up, set the zoom as seen from 3km above + // if we come back from a dive without GPS data, reset to the last zoom value + // otherwise check to make sure we aren't still running an animation and then remember + // the current zoom level + if (currentZoomLevel == -1) { + currentZoomLevel = zoomFromDistance(3.0); + centerOn(longitude, latitude); + fixZoom(true); + return; + } + if (!fixZoomTimer->isActive()) { + if (needResetZoom) { + needResetZoom = false; + fixZoom(); + } else if (zoom() >= 1200) { + currentZoomLevel = zoom(); + } + } + // From the marble source code, the maximum time of + // 'spin and fit' is 2000 miliseconds so wait a bit them zoom again. + fixZoomTimer->stop(); + if (zoom() < 1200 && IS_FP_SAME(centerLatitude(), latitude) && IS_FP_SAME(centerLongitude(), longitude)) { + // create a tiny movement + centerOn(longitude + 0.00001, latitude + 0.00001); + fixZoomTimer->start(300); + } else { + fixZoomTimer->start(2100); + } + centerOn(longitude, latitude, true); +} + +void GlobeGPS::fixZoom(bool now) +{ + setZoom(currentZoomLevel, now ? Marble::Instant : Marble::Linear); +} + +void GlobeGPS::zoomOutForNoGPS() +{ + // this is called if the dive has no GPS location. + // zoom out quite a bit to show the globe and remember that the next time + // we show a dive with GPS location we need to zoom in again + if (!needResetZoom) { + needResetZoom = true; + if (!fixZoomTimer->isActive() && zoom() >= 1500) { + currentZoomLevel = zoom(); + } + } + if (fixZoomTimer->isActive()) + fixZoomTimer->stop(); + // 1000 is supposed to make sure you see the whole globe + setZoom(1000, Marble::Linear); +} + +void GlobeGPS::endGetDiveCoordinates() +{ + messageWidget->animatedHide(); + editingDiveLocation = false; +} + +void GlobeGPS::prepareForGetDiveCoordinates() +{ + messageWidget->setMessageType(KMessageWidget::Warning); + messageWidget->setText(QObject::tr("Move the map and double-click to set the dive location")); + messageWidget->setWordWrap(true); + messageWidget->setCloseButtonVisible(false); + messageWidget->animatedShow(); + editingDiveLocation = true; + // this is not intuitive and at times causes trouble - let's comment it out for now + // if (!dive_has_gps_location(current_dive)) + // zoomOutForNoGPS(); +} + +void GlobeGPS::changeDiveGeoPosition(qreal lon, qreal lat, GeoDataCoordinates::Unit unit) +{ + if (!editingDiveLocation) + return; + + // convert to degrees if in radian. + if (unit == GeoDataCoordinates::Radian) { + lon = lon * 180 / M_PI; + lat = lat * 180 / M_PI; + } + centerOn(lon, lat, true); + + // change the location of the displayed_dive and put the UI in edit mode + displayed_dive_site.latitude.udeg = lrint(lat * 1000000.0); + displayed_dive_site.longitude.udeg = lrint(lon * 1000000.0); + emit coordinatesChanged(); + repopulateLabels(); +} + +void GlobeGPS::mousePressEvent(QMouseEvent *event) +{ + if (event->type() != QEvent::MouseButtonDblClick) + return; + + qreal lat, lon; + bool clickOnGlobe = geoCoordinates(event->pos().x(), event->pos().y(), lon, lat, GeoDataCoordinates::Degree); + + // there could be two scenarios that got us here; let's check if we are editing a dive + if (MainWindow::instance()->information()->isEditing() && clickOnGlobe) { + // + // 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); + } +} + +void GlobeGPS::resizeEvent(QResizeEvent *event) +{ + int size = event->size().width(); + MarbleWidget::resizeEvent(event); + if (size > 600) + messageWidget->setGeometry((size - 600) / 2, 5, 600, 0); + else + messageWidget->setGeometry(5, 5, size - 10, 0); + messageWidget->setMaximumHeight(500); +} + +void GlobeGPS::centerOnIndex(const QModelIndex& idx) +{ + struct dive_site *ds = get_dive_site_by_uuid(idx.model()->index(idx.row(), 0).data().toInt()); + if (!ds || !dive_site_has_gps_location(ds)) + centerOnDiveSite(&displayed_dive_site); + else + centerOnDiveSite(ds); +} +#else + +GlobeGPS *GlobeGPS::instance() +{ + static GlobeGPS *self = new GlobeGPS(); + return self; +} + +GlobeGPS::GlobeGPS(QWidget *parent) +{ + setText("MARBLE DISABLED AT BUILD TIME"); +} +void GlobeGPS::repopulateLabels() +{ +} +void GlobeGPS::centerOnCurrentDive() +{ +} +bool GlobeGPS::eventFilter(QObject *obj, QEvent *ev) +{ + return QObject::eventFilter(obj, ev); +} +void GlobeGPS::prepareForGetDiveCoordinates() +{ +} +void GlobeGPS::endGetDiveCoordinates() +{ +} +void GlobeGPS::reload() +{ +} +void GlobeGPS::centerOnIndex(const QModelIndex& idx) +{ +} +#endif |