diff --git a/src/lib/marble/GeoPainter.cpp b/src/lib/marble/GeoPainter.cpp index d9c77aa0f..058609117 100644 --- a/src/lib/marble/GeoPainter.cpp +++ b/src/lib/marble/GeoPainter.cpp @@ -1,1076 +1,1076 @@ // // This file is part of the Marble Virtual Globe. // // This program is free software licensed under the GNU LGPL. You can // find a copy of this license in LICENSE.txt in the top directory of // the source code. // // Copyright 2006-2009 Torsten Rahn #include "GeoPainter.h" #include "GeoPainter_p.h" #include #include #include #include #include #include "MarbleDebug.h" #include "GeoDataCoordinates.h" #include "GeoDataLatLonAltBox.h" #include "GeoDataLineString.h" #include "GeoDataLinearRing.h" #include "GeoDataPoint.h" #include "GeoDataPolygon.h" #include "MarbleGlobal.h" #include "ViewportParams.h" #include "AbstractProjection.h" // #define MARBLE_DEBUG using namespace Marble; GeoPainterPrivate::GeoPainterPrivate( GeoPainter* q, const ViewportParams *viewport, MapQuality mapQuality ) : m_viewport( viewport ), m_mapQuality( mapQuality ), m_x( new qreal[100] ), m_parent(q) { } GeoPainterPrivate::~GeoPainterPrivate() { delete[] m_x; } void GeoPainterPrivate::createAnnotationLayout ( qreal x, qreal y, const QSizeF& bubbleSize, qreal bubbleOffsetX, qreal bubbleOffsetY, qreal xRnd, qreal yRnd, QPainterPath& path, QRectF& rect ) { // TODO: MOVE this into an own Annotation class qreal arrowPosition = 0.3; qreal arrowWidth = 12.0; qreal width = bubbleSize.width(); qreal height = bubbleSize.height(); qreal dx = ( bubbleOffsetX > 0 ) ? 1.0 : -1.0; // x-Mirror qreal dy = ( bubbleOffsetY < 0 ) ? 1.0 : -1.0; // y-Mirror qreal x0 = ( x + bubbleOffsetX ) - dx * ( 1.0 - arrowPosition ) * ( width - 2.0 * xRnd ) - xRnd *dx; qreal x1 = ( x + bubbleOffsetX ) - dx * ( 1.0 - arrowPosition ) * ( width - 2.0 * xRnd ); qreal x2 = ( x + bubbleOffsetX ) - dx * ( 1.0 - arrowPosition ) * ( width - 2.0 * xRnd ) + xRnd * dx; qreal x3 = ( x + bubbleOffsetX ) - dx * arrowWidth / 2.0; qreal x4 = ( x + bubbleOffsetX ) + dx * arrowWidth / 2.0; qreal x5 = ( x + bubbleOffsetX ) + dx * arrowPosition * ( width - 2.0 * xRnd )- xRnd * dx; qreal x6 = ( x + bubbleOffsetX ) + dx * arrowPosition * ( width - 2.0 * xRnd ); qreal x7 = ( x + bubbleOffsetX ) + dx * arrowPosition * ( width - 2.0 * xRnd ) + xRnd * dx; qreal y0 = ( y + bubbleOffsetY ); qreal y1 = ( y + bubbleOffsetY ) - dy * yRnd; qreal y2 = ( y + bubbleOffsetY ) - dy * 2 * yRnd; qreal y5 = ( y + bubbleOffsetY ) - dy * ( height - 2 * yRnd ); qreal y6 = ( y + bubbleOffsetY ) - dy * ( height - yRnd ); qreal y7 = ( y + bubbleOffsetY ) - dy * height; QPointF p1 ( x, y ); // pointing point QPointF p2 ( x4, y0 ); QPointF p3 ( x6, y0 ); QPointF p4 ( x7, y1 ); QPointF p5 ( x7, y6 ); QPointF p6 ( x6, y7 ); QPointF p7 ( x1, y7 ); QPointF p8 ( x0, y6 ); QPointF p9 ( x0, y1 ); QPointF p10( x1, y0 ); QPointF p11( x3, y0 ); QRectF bubbleBoundingBox( QPointF( x0, y7 ), QPointF( x7, y0 ) ); path.moveTo( p1 ); path.lineTo( p2 ); path.lineTo( p3 ); QRectF bottomRight( QPointF( x5, y2 ), QPointF( x7, y0 ) ); path.arcTo( bottomRight, 270.0, 90.0 ); path.lineTo( p5 ); QRectF topRight( QPointF( x5, y7 ), QPointF( x7, y5 ) ); path.arcTo( topRight, 0.0, 90.0 ); path.lineTo( p7 ); QRectF topLeft( QPointF( x0, y7 ), QPointF( x2, y5 ) ); path.arcTo( topLeft, 90.0, 90.0 ); path.lineTo( p9 ); QRectF bottomLeft( QPointF( x0, y2 ), QPointF( x2, y0 ) ); path.arcTo( bottomLeft, 180.0, 90.0 ); path.lineTo( p10 ); path.lineTo( p11 ); path.lineTo( p1 ); qreal left = ( dx > 0 ) ? x1 : x6; qreal right = ( dx > 0 ) ? x6 : x1; qreal top = ( dy > 0 ) ? y6 : y1; qreal bottom = ( dy > 0 ) ? y1 : y6; rect.setTopLeft( QPointF( left, top ) ); rect.setBottomRight( QPointF( right, bottom ) ); } GeoDataLinearRing GeoPainterPrivate::createLinearRingFromGeoRect( const GeoDataCoordinates & centerCoordinates, qreal width, qreal height ) { qreal lon = 0.0; qreal lat = 0.0; qreal altitude = centerCoordinates.altitude(); centerCoordinates.geoCoordinates( lon, lat, GeoDataCoordinates::Degree ); lon = GeoDataCoordinates::normalizeLon( lon, GeoDataCoordinates::Degree ); lat = GeoDataCoordinates::normalizeLat( lat, GeoDataCoordinates::Degree ); qreal west = GeoDataCoordinates::normalizeLon( lon - width * 0.5, GeoDataCoordinates::Degree ); qreal east = GeoDataCoordinates::normalizeLon( lon + width * 0.5, GeoDataCoordinates::Degree ); qreal north = GeoDataCoordinates::normalizeLat( lat + height * 0.5, GeoDataCoordinates::Degree ); qreal south = GeoDataCoordinates::normalizeLat( lat - height * 0.5, GeoDataCoordinates::Degree ); GeoDataCoordinates southWest( west, south, altitude, GeoDataCoordinates::Degree ); GeoDataCoordinates southEast( east, south, altitude, GeoDataCoordinates::Degree ); GeoDataCoordinates northEast( east, north, altitude, GeoDataCoordinates::Degree ); GeoDataCoordinates northWest( west, north, altitude, GeoDataCoordinates::Degree ); GeoDataLinearRing rectangle( Tessellate | RespectLatitudeCircle ); // If the width of the rect is larger as 180 degree, we have to enforce the long way. if ( width >= 180 ) { qreal center = lon; GeoDataCoordinates southCenter( center, south, altitude, GeoDataCoordinates::Degree ); GeoDataCoordinates northCenter( center, north, altitude, GeoDataCoordinates::Degree ); rectangle << southWest << southCenter << southEast << northEast << northCenter << northWest; } else { rectangle << southWest << southEast << northEast << northWest; } return rectangle; } bool GeoPainterPrivate::doClip( const ViewportParams *viewport ) { if ( !viewport->currentProjection()->isClippedToSphere() ) return true; const qint64 radius = viewport->radius() * viewport->currentProjection()->clippingRadius(); return ( radius > viewport->width() / 2 || radius > viewport->height() / 2 ); } qreal GeoPainterPrivate::normalizeAngle(qreal angle) { angle = fmodf(angle, 360); return angle < 0 ? angle + 360 : angle; } void GeoPainterPrivate::drawTextRotated( const QPointF &startPoint, qreal angle, const QString &text ) { QRectF textRect(startPoint, m_parent->fontMetrics().size( 0, text)); QTransform const oldTransform = m_parent->transform(); m_parent->translate(startPoint); m_parent->rotate(angle); m_parent->translate( -startPoint - QPointF(0.0, m_parent->fontMetrics().height()/2.0) ); m_parent->drawText( textRect, text); m_parent->setTransform(oldTransform); } // ------------------------------------------------------------------------------------------------- GeoPainter::GeoPainter( QPaintDevice* pd, const ViewportParams *viewport, MapQuality mapQuality ) : ClipPainter( pd, GeoPainterPrivate::doClip( viewport ) ), d( new GeoPainterPrivate( this, viewport, mapQuality ) ) { const bool antialiased = mapQuality == HighQuality || mapQuality == PrintQuality; setRenderHint( QPainter::Antialiasing, antialiased ); ClipPainter::setScreenClip(false); } GeoPainter::~GeoPainter() { delete d; } MapQuality GeoPainter::mapQuality() const { return d->m_mapQuality; } void GeoPainter::drawAnnotation( const GeoDataCoordinates & position, const QString & text, QSizeF bubbleSize, qreal bubbleOffsetX, qreal bubbleOffsetY, qreal xRnd, qreal yRnd ) { int pointRepeatNum; qreal y; bool globeHidesPoint; if ( bubbleSize.height() <= 0 ) { QRectF rect = QRectF( QPointF( 0.0, 0.0 ), bubbleSize - QSizeF( 2 * xRnd, 0.0 ) ); qreal idealTextHeight = boundingRect( rect, Qt::TextWordWrap, text ).height(); bubbleSize.setHeight( 2 * yRnd + idealTextHeight ); } bool visible = d->m_viewport->screenCoordinates( position, d->m_x, y, pointRepeatNum, QSizeF(), globeHidesPoint ); if ( visible ) { // Draw all the x-repeat-instances of the point on the screen for( int it = 0; it < pointRepeatNum; ++it ) { QPainterPath path; QRectF rect; d->createAnnotationLayout( d->m_x[it], y, bubbleSize, bubbleOffsetX, bubbleOffsetY, xRnd, yRnd, path, rect ); QPainter::drawPath( path ); QPainter::drawText( rect, Qt::TextWordWrap, text, &rect ); } } } void GeoPainter::drawPoint ( const GeoDataCoordinates & position ) { int pointRepeatNum; qreal y; bool globeHidesPoint; bool visible = d->m_viewport->screenCoordinates( position, d->m_x, y, pointRepeatNum, QSizeF(), globeHidesPoint ); if ( visible ) { // Draw all the x-repeat-instances of the point on the screen for( int it = 0; it < pointRepeatNum; ++it ) { QPainter::drawPoint(QPointF(d->m_x[it], y)); } } } QRegion GeoPainter::regionFromPoint ( const GeoDataCoordinates & position, qreal width ) const { return regionFromRect( position, width, width, false, 3 ); } void GeoPainter::drawPoint( const GeoDataPoint & point ) { drawPoint( point.coordinates() ); } QRegion GeoPainter::regionFromPoint ( const GeoDataPoint & point, qreal width ) const { return regionFromRect( point.coordinates(), width, width, false, 3 ); } void GeoPainter::drawText ( const GeoDataCoordinates & position, const QString & text, qreal xOffset, qreal yOffset, qreal width, qreal height, const QTextOption & option ) { // Of course in theory we could have the "isGeoProjected" parameter used // for drawText as well. However this would require us to convert all // glyphs to PainterPaths / QPolygons. From QPolygons we could create // GeoDataPolygons which could get painted on screen. Any patches appreciated ;-) int pointRepeatNum; qreal y; bool globeHidesPoint; QSizeF textSize( fontMetrics().width( text ), fontMetrics().height() ); bool visible = d->m_viewport->screenCoordinates( position, d->m_x, y, pointRepeatNum, textSize, globeHidesPoint ); if ( visible ) { // Draw all the x-repeat-instances of the point on the screen const qreal posY = y - yOffset; for( int it = 0; it < pointRepeatNum; ++it ) { const qreal posX = d->m_x[it] + xOffset; if (width == 0.0 && height == 0.0) { QPainter::drawText(QPointF(posX, posY), text); } else { const QRectF boundingRect(posX, posY, width, height); QPainter::drawText( boundingRect, text, option ); } } } } void GeoPainter::drawEllipse ( const GeoDataCoordinates & centerPosition, qreal width, qreal height, bool isGeoProjected ) { if ( !isGeoProjected ) { int pointRepeatNum; qreal y; bool globeHidesPoint; bool visible = d->m_viewport->screenCoordinates( centerPosition, d->m_x, y, pointRepeatNum, QSizeF( width, height ), globeHidesPoint ); if ( visible ) { // Draw all the x-repeat-instances of the point on the screen const qreal rx = width / 2.0; const qreal ry = height / 2.0; for( int it = 0; it < pointRepeatNum; ++it ) { QPainter::drawEllipse(QPointF(d->m_x[it], y), rx, ry); } } } else { // Initialize variables const qreal centerLon = centerPosition.longitude( GeoDataCoordinates::Degree ); const qreal centerLat = centerPosition.latitude( GeoDataCoordinates::Degree ); const qreal altitude = centerPosition.altitude(); // Ensure a valid latitude range: if ( centerLat + 0.5 * height > 90.0 || centerLat - 0.5 * height < -90.0 ) { return; } // Don't show the ellipse if it's too small: GeoDataLatLonBox ellipseBox( centerLat + 0.5 * height, centerLat - 0.5 * height, centerLon + 0.5 * width, centerLon - 0.5 * width, GeoDataCoordinates::Degree ); if ( !d->m_viewport->viewLatLonAltBox().intersects( ellipseBox ) || !d->m_viewport->resolves( ellipseBox ) ) return; GeoDataLinearRing ellipse; // Optimizing the precision by determining the size which the // ellipse covers on the screen: const qreal degreeResolution = d->m_viewport->angularResolution() * RAD2DEG; // To create a circle shape even for very small precision we require uneven numbers: const int precision = qMin( width / degreeResolution / 8 + 1, 81 ); // Calculate the shape of the upper half of the ellipse: for ( int i = 0; i <= precision; ++i ) { const qreal t = 1.0 - 2.0 * (qreal)(i) / (qreal)(precision); const qreal lat = centerLat + 0.5 * height * sqrt( 1.0 - t * t ); const qreal lon = centerLon + 0.5 * width * t; ellipse << GeoDataCoordinates( lon, lat, altitude, GeoDataCoordinates::Degree ); } // Calculate the shape of the lower half of the ellipse: for ( int i = 0; i <= precision; ++i ) { const qreal t = 2.0 * (qreal)(i) / (qreal)(precision) - 1.0; const qreal lat = centerLat - 0.5 * height * sqrt( 1.0 - t * t ); const qreal lon = centerLon + 0.5 * width * t; ellipse << GeoDataCoordinates( lon, lat, altitude, GeoDataCoordinates::Degree ); } drawPolygon( ellipse ); } } QRegion GeoPainter::regionFromEllipse ( const GeoDataCoordinates & centerPosition, qreal width, qreal height, bool isGeoProjected, qreal strokeWidth ) const { if ( !isGeoProjected ) { int pointRepeatNum; qreal y; bool globeHidesPoint; bool visible = d->m_viewport->screenCoordinates( centerPosition, d->m_x, y, pointRepeatNum, QSizeF( width, height ), globeHidesPoint ); QRegion regions; if ( visible ) { // only a hint, a backend could still ignore it, but we cannot know more const bool antialiased = testRenderHint(QPainter::Antialiasing); const qreal halfStrokeWidth = strokeWidth/2.0; const int startY = antialiased ? (qFloor(y - halfStrokeWidth)) : (qFloor(y+0.5 - halfStrokeWidth)); const int endY = antialiased ? (qCeil(y + height + halfStrokeWidth)) : (qFloor(y+0.5 + height + halfStrokeWidth)); // Draw all the x-repeat-instances of the point on the screen for( int it = 0; it < pointRepeatNum; ++it ) { const qreal x = d->m_x[it]; const int startX = antialiased ? (qFloor(x - halfStrokeWidth)) : (qFloor(x+0.5 - halfStrokeWidth)); const int endX = antialiased ? (qCeil(x + width + halfStrokeWidth)) : (qFloor(x+0.5 + width + halfStrokeWidth)); regions += QRegion(startX, startY, endX - startX, endY - startY, QRegion::Ellipse); } } return regions; } else { // Initialize variables const qreal centerLon = centerPosition.longitude( GeoDataCoordinates::Degree ); const qreal centerLat = centerPosition.latitude( GeoDataCoordinates::Degree ); const qreal altitude = centerPosition.altitude(); // Ensure a valid latitude range: if ( centerLat + 0.5 * height > 90.0 || centerLat - 0.5 * height < -90.0 ) { return QRegion(); } // Don't show the ellipse if it's too small: GeoDataLatLonBox ellipseBox( centerLat + 0.5 * height, centerLat - 0.5 * height, centerLon + 0.5 * width, centerLon - 0.5 * width, GeoDataCoordinates::Degree ); if ( !d->m_viewport->viewLatLonAltBox().intersects( ellipseBox ) || !d->m_viewport->resolves( ellipseBox ) ) return QRegion(); GeoDataLinearRing ellipse; // Optimizing the precision by determining the size which the // ellipse covers on the screen: const qreal degreeResolution = d->m_viewport->angularResolution() * RAD2DEG; // To create a circle shape even for very small precision we require uneven numbers: const int precision = qMin( width / degreeResolution / 8 + 1, 81 ); // Calculate the shape of the upper half of the ellipse: for ( int i = 0; i <= precision; ++i ) { const qreal t = 1.0 - 2.0 * (qreal)(i) / (qreal)(precision); const qreal lat = centerLat + 0.5 * height * sqrt( 1.0 - t * t ); const qreal lon = centerLon + 0.5 * width * t; ellipse << GeoDataCoordinates( lon, lat, altitude, GeoDataCoordinates::Degree ); } // Calculate the shape of the lower half of the ellipse: for ( int i = 0; i <= precision; ++i ) { const qreal t = 2.0 * (qreal)(i) / (qreal)(precision) - 1.0; const qreal lat = centerLat - 0.5 * height * sqrt( 1.0 - t * t ); const qreal lon = centerLon + 0.5 * width * t; ellipse << GeoDataCoordinates( lon, lat, altitude, GeoDataCoordinates::Degree ); } return regionFromPolygon( ellipse, Qt::OddEvenFill, strokeWidth ); } } void GeoPainter::drawImage ( const GeoDataCoordinates & centerPosition, const QImage & image /*, bool isGeoProjected */ ) { // isGeoProjected = true would project the image/pixmap onto the globe. This // requires to deal with the TextureMapping classes -> should get // implemented later on int pointRepeatNum; qreal y; bool globeHidesPoint; // if ( !isGeoProjected ) { bool visible = d->m_viewport->screenCoordinates( centerPosition, d->m_x, y, pointRepeatNum, image.size(), globeHidesPoint ); if ( visible ) { // Draw all the x-repeat-instances of the point on the screen const qreal posY = y - (image.height() / 2.0); for( int it = 0; it < pointRepeatNum; ++it ) { const qreal posX = d->m_x[it] - (image.width() / 2.0); QPainter::drawImage(QPointF(posX, posY), image); } } // } } void GeoPainter::drawPixmap ( const GeoDataCoordinates & centerPosition, const QPixmap & pixmap /* , bool isGeoProjected */ ) { int pointRepeatNum; qreal y; bool globeHidesPoint; // if ( !isGeoProjected ) { // FIXME: Better visibility detection that takes the circle geometry into account bool visible = d->m_viewport->screenCoordinates( centerPosition, d->m_x, y, pointRepeatNum, pixmap.size(), globeHidesPoint ); if ( visible ) { // Draw all the x-repeat-instances of the point on the screen const qreal posY = y - (pixmap.height() / 2.0); for( int it = 0; it < pointRepeatNum; ++it ) { const qreal posX = d->m_x[it] - (pixmap.width() / 2.0); QPainter::drawPixmap(QPointF(posX, posY), pixmap); } } // } } QRegion GeoPainter::regionFromPixmapRect(const GeoDataCoordinates & centerCoordinates, int width, int height, int margin) const { const int fullWidth = width + 2 * margin; const int fullHeight = height + 2 * margin; int pointRepeatNum; qreal y; bool globeHidesPoint; const bool visible = d->m_viewport->screenCoordinates(centerCoordinates, d->m_x, y, pointRepeatNum, QSizeF(fullWidth, fullHeight), globeHidesPoint); QRegion regions; if (visible) { // cmp. GeoPainter::drawPixmap() position calculation // QPainter::drawPixmap seems to qRound the passed position const int posY = qRound(y - (height / 2.0)) - margin; for (int it = 0; it < pointRepeatNum; ++it) { const int posX = qRound(d->m_x[it] - (width / 2.0)) - margin; regions += QRegion(posX, posY, width, height); } } return regions; } void GeoPainter::polygonsFromLineString( const GeoDataLineString &lineString, - QVector &polygons ) + QVector &polygons ) const { // Immediately leave this method now if: // - the object is not visible in the viewport or if // - the size of the object is below the resolution of the viewport if ( ! d->m_viewport->viewLatLonAltBox().intersects( lineString.latLonAltBox() ) || ! d->m_viewport->resolves( lineString.latLonAltBox() ) ) { // mDebug() << "LineString doesn't get displayed on the viewport"; return; } d->m_viewport->screenCoordinates( lineString, polygons ); } void GeoPainter::drawPolyline ( const GeoDataLineString & lineString, const QString& labelText, LabelPositionFlags labelPositionFlags, const QColor& labelColor) { // no labels to draw? // TODO: !labelColor.isValid() || labelColor.alpha() == 0 does not work, // something injects invalid labelColor for city streets if (labelText.isEmpty() || labelPositionFlags.testFlag(NoLabel) || labelColor == Qt::transparent) { drawPolyline(lineString); return; } QVector polygons; polygonsFromLineString(lineString, polygons); if (polygons.empty()) return; for(const QPolygonF* itPolygon: polygons) { ClipPainter::drawPolyline(*itPolygon); } drawLabelsForPolygons(polygons, labelText, labelPositionFlags, labelColor); qDeleteAll( polygons ); } void GeoPainter::drawLabelsForPolygons( const QVector &polygons, const QString& labelText, LabelPositionFlags labelPositionFlags, const QColor& labelColor ) { if (labelText.isEmpty()) { return; } QPen const oldPen = pen(); if (labelPositionFlags.testFlag(FollowLine)) { const qreal maximumLabelFontSize = 20; qreal fontSize = pen().widthF() * 0.45; fontSize = qMin( fontSize, maximumLabelFontSize ); if (fontSize < 6.0 || labelColor == "transparent") { return; } QFont font = this->font(); font.setPointSizeF(fontSize); setFont(font); int labelWidth = fontMetrics().width( labelText ); if (labelText.size() < 20) { labelWidth *= (20.0 / labelText.size()); } setPen(labelColor); QVector labelNodes; QRectF viewportRect = QRectF(QPointF(0, 0), d->m_viewport->size()); for( QPolygonF* itPolygon: polygons ) { if (!itPolygon->boundingRect().intersects(viewportRect)) { continue; } labelNodes.clear(); QPainterPath path; path.addPolygon(*itPolygon); qreal pathLength = path.length(); if (pathLength == 0) continue; int maxNumLabels = static_cast(pathLength / labelWidth); if (maxNumLabels > 0) { qreal textRelativeLength = labelWidth / pathLength; int numLabels = 1; if (maxNumLabels > 1) { numLabels = maxNumLabels/2; } qreal offset = (1.0 - numLabels*textRelativeLength)/numLabels; qreal startPercent = offset/2.0; for (int k = 0; k < numLabels; ++k, startPercent += textRelativeLength + offset) { QPointF point = path.pointAtPercent(startPercent); QPointF endPoint = path.pointAtPercent(startPercent + textRelativeLength); if ( viewport().contains(point.toPoint()) || viewport().contains(endPoint.toPoint()) ) { qreal angle = -path.angleAtPercent(startPercent); qreal angle2 = -path.angleAtPercent(startPercent + textRelativeLength); angle = GeoPainterPrivate::normalizeAngle(angle); angle2 = GeoPainterPrivate::normalizeAngle(angle2); bool upsideDown = angle > 90.0 && angle < 270.0; if ( qAbs(angle - angle2) < 3.0 ) { if ( upsideDown ) { angle += 180.0; point = path.pointAtPercent(startPercent + textRelativeLength); } d->drawTextRotated(point, angle, labelText); } else { for (int i = 0; i < labelText.length(); ++i) { qreal currentGlyphTextLength = fontMetrics().width(labelText.left(i)) / pathLength; if ( !upsideDown ) { angle = -path.angleAtPercent(startPercent + currentGlyphTextLength); point = path.pointAtPercent(startPercent + currentGlyphTextLength); } else { angle = -path.angleAtPercent(startPercent + textRelativeLength - currentGlyphTextLength) + 180; point = path.pointAtPercent(startPercent + textRelativeLength - currentGlyphTextLength); } d->drawTextRotated(point, angle, labelText.at(i)); } } } } } } } else { setPen(labelColor); int labelWidth = fontMetrics().width( labelText ); int labelAscent = fontMetrics().ascent(); QVector labelNodes; for( QPolygonF* itPolygon: polygons ) { labelNodes.clear(); ClipPainter::labelPosition( *itPolygon, labelNodes, labelPositionFlags ); if (!labelNodes.isEmpty()) { for ( const QPointF& labelNode: labelNodes ) { QPointF labelPosition = labelNode + QPointF( 3.0, -2.0 ); // FIXME: This is a Q&D fix. qreal xmax = viewport().width() - 10.0 - labelWidth; if ( labelPosition.x() > xmax ) labelPosition.setX( xmax ); qreal ymin = 10.0 + labelAscent; if ( labelPosition.y() < ymin ) labelPosition.setY( ymin ); qreal ymax = viewport().height() - 10.0 - labelAscent; if ( labelPosition.y() > ymax ) labelPosition.setY( ymax ); drawText( QRectF( labelPosition, fontMetrics().size( 0, labelText) ), labelText ); } } } } setPen(oldPen); } void GeoPainter::drawPolyline(const GeoDataLineString& lineString) { QVector polygons; polygonsFromLineString(lineString, polygons); if (polygons.empty()) return; for(const QPolygonF* itPolygon: polygons) { ClipPainter::drawPolyline(*itPolygon); } qDeleteAll(polygons); } QRegion GeoPainter::regionFromPolyline ( const GeoDataLineString & lineString, qreal strokeWidth ) const { // Immediately leave this method now if: // - the object is not visible in the viewport or if // - the size of the object is below the resolution of the viewport if ( ! d->m_viewport->viewLatLonAltBox().intersects( lineString.latLonAltBox() ) || ! d->m_viewport->resolves( lineString.latLonAltBox() ) ) { // mDebug() << "LineString doesn't get displayed on the viewport"; return QRegion(); } QPainterPath painterPath; QVector polygons; d->m_viewport->screenCoordinates( lineString, polygons ); for( QPolygonF* itPolygon: polygons ) { painterPath.addPolygon( *itPolygon ); } qDeleteAll( polygons ); QPainterPathStroker stroker; stroker.setWidth( strokeWidth ); QPainterPath strokePath = stroker.createStroke( painterPath ); return QRegion( strokePath.toFillPolygon().toPolygon(), Qt::WindingFill ); } void GeoPainter::drawPolygon ( const GeoDataLinearRing & linearRing, Qt::FillRule fillRule ) { // Immediately leave this method now if: // - the object is not visible in the viewport or if // - the size of the object is below the resolution of the viewport if ( ! d->m_viewport->viewLatLonAltBox().intersects( linearRing.latLonAltBox() ) || ! d->m_viewport->resolves( linearRing.latLonAltBox() ) ) { // mDebug() << "Polygon doesn't get displayed on the viewport"; return; } QVector polygons; d->m_viewport->screenCoordinates( linearRing, polygons ); for( QPolygonF* itPolygon: polygons ) { ClipPainter::drawPolygon( *itPolygon, fillRule ); } qDeleteAll( polygons ); } QRegion GeoPainter::regionFromPolygon ( const GeoDataLinearRing & linearRing, Qt::FillRule fillRule, qreal strokeWidth ) const { // Immediately leave this method now if: // - the object is not visible in the viewport or if // - the size of the object is below the resolution of the viewport if ( ! d->m_viewport->viewLatLonAltBox().intersects( linearRing.latLonAltBox() ) || ! d->m_viewport->resolves( linearRing.latLonAltBox() ) ) { return QRegion(); } QRegion regions; QVector polygons; d->m_viewport->screenCoordinates( linearRing, polygons ); if ( strokeWidth == 0 ) { // This is the faster way for( QPolygonF* itPolygon: polygons ) { regions += QRegion ( (*itPolygon).toPolygon(), fillRule ); } } else { QPainterPath painterPath; for( QPolygonF* itPolygon: polygons ) { painterPath.addPolygon( *itPolygon ); } QPainterPathStroker stroker; stroker.setWidth( strokeWidth ); QPainterPath strokePath = stroker.createStroke( painterPath ); painterPath = painterPath.united( strokePath ); regions = QRegion( painterPath.toFillPolygon().toPolygon() ); } qDeleteAll( polygons ); return regions; } void GeoPainter::drawPolygon ( const GeoDataPolygon & polygon, Qt::FillRule fillRule ) { // If the object is not visible in the viewport return if ( ! d->m_viewport->viewLatLonAltBox().intersects( polygon.outerBoundary().latLonAltBox() ) || // If the size of the object is below the resolution of the viewport then return ! d->m_viewport->resolves( polygon.outerBoundary().latLonAltBox() ) ) { // mDebug() << "Polygon doesn't get displayed on the viewport"; return; } // mDebug() << "Drawing Polygon"; QVector outerPolygons; QVector innerPolygons; d->m_viewport->screenCoordinates( polygon.outerBoundary(), outerPolygons ); QPen const currentPen = pen(); bool const hasInnerBoundaries = !polygon.innerBoundaries().isEmpty(); bool innerBoundariesOnScreen = false; if ( hasInnerBoundaries ) { QVector const & innerBoundaries = polygon.innerBoundaries(); const GeoDataLatLonAltBox & viewLatLonAltBox = d->m_viewport->viewLatLonAltBox(); for( const GeoDataLinearRing& itInnerBoundary: innerBoundaries ) { if ( viewLatLonAltBox.intersects(itInnerBoundary.latLonAltBox()) && d->m_viewport->resolves(itInnerBoundary.latLonAltBox()), 4 ) { innerBoundariesOnScreen = true; break; } } if (innerBoundariesOnScreen) { // Create the inner screen polygons for( const GeoDataLinearRing& itInnerBoundary: innerBoundaries ) { QVector innerPolygonsPerBoundary; d->m_viewport->screenCoordinates( itInnerBoundary, innerPolygonsPerBoundary ); for( QPolygonF* innerPolygonPerBoundary: innerPolygonsPerBoundary ) { innerPolygons << innerPolygonPerBoundary; } } setPen(Qt::NoPen); QVector fillPolygons = createFillPolygons( outerPolygons, innerPolygons ); for( const QPolygonF* fillPolygon: fillPolygons ) { ClipPainter::drawPolygon(*fillPolygon, fillRule); } setPen(currentPen); for( const QPolygonF* outerPolygon: outerPolygons ) { ClipPainter::drawPolyline( *outerPolygon ); } for( const QPolygonF* innerPolygon: innerPolygons ) { ClipPainter::drawPolyline( *innerPolygon ); } qDeleteAll(fillPolygons); } } if ( !hasInnerBoundaries || !innerBoundariesOnScreen ) { drawPolygon( polygon.outerBoundary(), fillRule ); } qDeleteAll(outerPolygons); qDeleteAll(innerPolygons); } QVector GeoPainter::createFillPolygons( const QVector & outerPolygons, const QVector & innerPolygons ) const { QVector fillPolygons; fillPolygons.reserve(outerPolygons.size()); for( const QPolygonF* outerPolygon: outerPolygons ) { QPolygonF* fillPolygon = new QPolygonF; *fillPolygon << *outerPolygon; *fillPolygon << outerPolygon->first(); for( const QPolygonF* innerPolygon: innerPolygons ) { *fillPolygon << *innerPolygon; *fillPolygon << innerPolygon->first(); *fillPolygon << outerPolygon->first(); } fillPolygons << fillPolygon; } return fillPolygons; } void GeoPainter::drawRect ( const GeoDataCoordinates & centerCoordinates, qreal width, qreal height, bool isGeoProjected ) { if ( !isGeoProjected ) { int pointRepeatNum; qreal y; bool globeHidesPoint; bool visible = d->m_viewport->screenCoordinates( centerCoordinates, d->m_x, y, pointRepeatNum, QSizeF( width, height ), globeHidesPoint ); if ( visible ) { // Draw all the x-repeat-instances of the point on the screen const qreal posY = y - height / 2.0; for( int it = 0; it < pointRepeatNum; ++it ) { const qreal posX = d->m_x[it] - width / 2.0; QPainter::drawRect(QRectF(posX, posY, width, height)); } } } else { drawPolygon( d->createLinearRingFromGeoRect( centerCoordinates, width, height ), Qt::OddEvenFill ); } } QRegion GeoPainter::regionFromRect ( const GeoDataCoordinates & centerCoordinates, qreal width, qreal height, bool isGeoProjected, qreal strokeWidth ) const { if ( !isGeoProjected ) { int pointRepeatNum; qreal y; bool globeHidesPoint; bool visible = d->m_viewport->screenCoordinates( centerCoordinates, d->m_x, y, pointRepeatNum, QSizeF( width, height ), globeHidesPoint ); QRegion regions; if ( visible ) { // only a hint, a backend could still ignore it, but we cannot know more const bool antialiased = testRenderHint(QPainter::Antialiasing); const qreal halfStrokeWidth = strokeWidth/2.0; const int startY = antialiased ? (qFloor(y - halfStrokeWidth)) : (qFloor(y+0.5 - halfStrokeWidth)); const int endY = antialiased ? (qCeil(y + height + halfStrokeWidth)) : (qFloor(y+0.5 + height + halfStrokeWidth)); // Draw all the x-repeat-instances of the point on the screen for( int it = 0; it < pointRepeatNum; ++it ) { const qreal x = d->m_x[it]; const int startX = antialiased ? (qFloor(x - halfStrokeWidth)) : (qFloor(x+0.5 - halfStrokeWidth)); const int endX = antialiased ? (qCeil(x + width + halfStrokeWidth)) : (qFloor(x+0.5 + width + halfStrokeWidth)); regions += QRegion(startX, startY, endX - startX, endY - startY); } } return regions; } else { return regionFromPolygon( d->createLinearRingFromGeoRect( centerCoordinates, width, height ), Qt::OddEvenFill, strokeWidth ); } } void GeoPainter::drawRoundedRect(const GeoDataCoordinates ¢erPosition, qreal width, qreal height, qreal xRnd, qreal yRnd) { int pointRepeatNum; qreal y; bool globeHidesPoint; // FIXME: Better visibility detection that takes the circle geometry into account bool visible = d->m_viewport->screenCoordinates( centerPosition, d->m_x, y, pointRepeatNum, QSizeF( width, height ), globeHidesPoint ); if ( visible ) { // Draw all the x-repeat-instances of the point on the screen const qreal posY = y - height / 2.0; for( int it = 0; it < pointRepeatNum; ++it ) { const qreal posX = d->m_x[it] - width / 2.0; QPainter::drawRoundedRect(QRectF(posX, posY, width, height), xRnd, yRnd); } } } void GeoPainter::drawTextFragment(const QPoint &position, const QString &text, const qreal fontSize, const QColor &color, const Frames &flags) { const QString key = text + ":" + QString::number(static_cast(flags)); QPixmap pixmap; if (!QPixmapCache::find(key, &pixmap)) { const bool hasRoundFrame = flags.testFlag(RoundFrame); QPixmap pixmap(10,10); QPainter textPainter; textPainter.begin(&pixmap); const QFontMetrics metrics = textPainter.fontMetrics(); textPainter.end(); const int width = metrics.width(text); const int height = metrics.height(); const QSize size = hasRoundFrame ? QSize(qMax(1.2*width, 1.1*height), 1.2*height) : QSize(width, height); pixmap = QPixmap(size); pixmap.fill(Qt::transparent); const QRect labelRect(QPoint(), size); textPainter.begin(&pixmap); QFont textFont = textPainter.font(); textFont.setPointSize(fontSize); textPainter.setFont(textFont); textPainter.setRenderHint(QPainter::Antialiasing, true); const QColor brushColor = color; if (hasRoundFrame) { QColor lighterColor = brushColor.lighter(110); lighterColor.setAlphaF(0.9); textPainter.setBrush(lighterColor); textPainter.drawRoundedRect(labelRect, 3, 3); } textPainter.setBrush(brushColor); textPainter.drawText(labelRect, Qt::AlignHCenter , text); if (hasRoundFrame) { textPainter.setBrush(brushColor); } textPainter.end(); QPixmapCache::insert(key, pixmap); } QPainter::drawPixmap(position.x() - pixmap.width()/2, position.y() - pixmap.height()/2, pixmap); } diff --git a/src/lib/marble/GeoPainter.h b/src/lib/marble/GeoPainter.h index 1dd388186..63b6a454f 100644 --- a/src/lib/marble/GeoPainter.h +++ b/src/lib/marble/GeoPainter.h @@ -1,530 +1,530 @@ // // This file is part of the Marble Virtual Globe. // // This program is free software licensed under the GNU LGPL. You can // find a copy of this license in LICENSE.txt in the top directory of // the source code. // // Copyright 2008-2009 Torsten Rahn // #ifndef MARBLE_GEOPAINTER_H #define MARBLE_GEOPAINTER_H #include "marble_export.h" // Marble #include "MarbleGlobal.h" #include "ClipPainter.h" #include class QImage; class QPaintDevice; class QRegion; class QString; namespace Marble { class ViewportParams; class GeoPainterPrivate; class GeoDataCoordinates; class GeoDataLineString; class GeoDataLinearRing; class GeoDataPoint; class GeoDataPolygon; /*! \class GeoPainter \brief A painter that allows to draw geometric primitives on the map. This class allows application developers to draw simple geometric shapes and objects onto the map. The API is modeled after the QPainter API. The GeoPainter provides a wide range of methods that are using geographic ("geodesic") coordinates to position the item. For example a point or the nodes of a polygon can fully be described in geographic coordinates. In all these cases the position of the object is specified in geographic coordinates. There are however some cases where there are two viable cases: \li the shape of the object could still use screen coordinates (like a label or an icon). \li Alternatively the shape of the object can get projected according to the current projection (e.g. a texture projected onto the spherical surface) If screen coordinates are used then e.g. width and height are assumed to be expressed in pixels, otherwise degrees are used. Painter transformations (e.g. translate) always happen in screen coordinates. Like in QPainter drawing objects onto a widget should always considered to be a volatile operation. This means that e.g. placemarks don't get added to the globe permanently. So the drawing needs to be done on every repaint to prevent that drawings will disappear during the next paint event. So if you want to add placemarks to your map widget permanently (i.e. you don't want to take care of repainting) then you need to use other solutions such as the KML import of the Marble framework or Marble's GeoGraphicsItems. \note By default the GeoPainter automatically filters geographical content in order to provide fast painting: \li Geographically positioned objects which are outside the viewport are not drawn at all. Parts of objects which are specified through geographic coordinates (like polygons, ellipses etc.) get cut off if they are not placed within the viewport. \li Objects which have a shape that is specified through geographic coordinates get filtered according to the viewport resolution: If the object is much smaller than a pixel then it won't get drawn at all. */ class MARBLE_EXPORT GeoPainter : public ClipPainter { public: enum Frame { NoOptions = 0x0, RoundFrame = 0x1 }; Q_DECLARE_FLAGS(Frames, Frame) /*! \brief Creates a new geo painter. To create a geo painter it's necessary to provide \a paintDevice as a canvas and the viewportParams to specify the map projection inside the viewport. */ GeoPainter( QPaintDevice * paintDevice, const ViewportParams *viewportParams, MapQuality mapQuality = NormalQuality ); /*! \brief Destroys the geo painter. */ ~GeoPainter(); /*! \brief Returns the map quality. \return The map quality that got assigned to the painter. */ MapQuality mapQuality() const; /*! \brief Draws a text annotation that points to a geodesic position. The annotation consists of a bubble with the specified \a text inside. By choosing an appropriate pen for the painter it's possible to change the color and line style of the bubble outline and the text. The brush chosen for the painter is used to paint the background of the bubble The optional parameters which describe the layout of the bubble are similar to those used by QPainter::drawRoundedRect(). Unlike in QPainter the rounded corners are not specified in percentage but in pixels to provide for optimal aesthetics. By choosing a positive or negative bubbleOffset it's possible to place the annotation on top, bottom, left or right of the annotated position. \param position The geodesic position \param text The text contained by the bubble \param bubbleSize The size of the bubble that holds the annotation text. A height of 0 can be used to have the height calculated automatically to fit the needed text height. \param bubbleOffsetX The x-axis offset between the annotated position and the "root" of the speech bubble's "arrow". \param bubbleOffsetY The y-axis offset between the annotated position and the "root" of the speech bubble's "arrow". \param xRnd Specifies the geometry of the rounded corners in pixels along the x-axis. \param yRnd Specifies the geometry of the rounded corners in pixels along the y-axis. \see GeoDataCoordinates */ void drawAnnotation( const GeoDataCoordinates & position, const QString & text, QSizeF bubbleSize = QSizeF( 130, 100 ), qreal bubbleOffsetX = -10, qreal bubbleOffsetY = -30, qreal xRnd = 5, qreal yRnd = 5 ); /*! \brief Draws a single point at a given geographic position. The point is drawn using the painter's pen color. \see GeoDataCoordinates */ void drawPoint ( const GeoDataCoordinates & position ); /*! \brief Creates a region for a given geographic position. A QRegion object is created that represents the area covered by GeoPainter::drawPoint( GeoDataCoordinates ). It can be used e.g. for input event handling of objects that have been painted using GeoPainter::drawPoint( GeoDataCoordinates ). The width allows to set the "stroke width" for the region. For input event handling it's always advisable to use a width that is slightly bigger than the width of the painter's pen. \see GeoDataCoordinates */ QRegion regionFromPoint ( const GeoDataCoordinates & position, qreal strokeWidth = 3) const; /*! \brief Draws a single point at a given geographic position. The point is drawn using the painter's pen color. \see GeoDataPoint */ void drawPoint ( const GeoDataPoint & point ); /*! \brief Create a region for a given geographic position. A QRegion object is created that represents the area covered by GeoPainter::drawPoint( GeoDataPoint ). It can be used e.g. for input event handling of objects that have been painted using GeoPainter::drawPoint( GeoDataPoint ). The width allows to set the "stroke width" for the region. For input event handling it's always advisable to use a width that is slightly bigger than the width of the painter's pen. */ QRegion regionFromPoint ( const GeoDataPoint & point, qreal strokeWidth = 3) const; /*! \brief Draws the given text at a given geographic position. The \a text is drawn starting at the given \a position using the painter's font property. The text rendering is performed in screen coordinates and is not subject to the current projection. An offset given in screenPixels can be provided via xOffset and yOffset in order to tweak the text position. By optionally adding a width, height and text options the text flow can be further influenced. */ void drawText ( const GeoDataCoordinates & position, const QString & text, qreal xOffset = 0.0, qreal yOffset = 0.0, qreal width = 0.0, qreal height = 0.0, const QTextOption & option = QTextOption() ); /*! \brief Draws an ellipse at the given position. The ellipse is placed with its center located at the given \a centerPosition. For the outline it uses the painter's pen and for the background the painter's brush. If \a isGeoProjected is true then the outline of the ellipse is drawn in geographic coordinates. In this case the \a width and the \a height are interpreted to be degrees. If \a isGeoProjected is false then the outline of the ellipse is drawn in screen coordinates. In this case the \a width and the \a height are interpreted to be pixels. \see GeoDataCoordinates */ void drawEllipse ( const GeoDataCoordinates & centerPosition, qreal width, qreal height, bool isGeoProjected = false ); /*! \brief Creates a region for an ellipse at a given position A QRegion object is created that represents the area covered by GeoPainter::drawEllipse(). As such it can be used e.g. for input event handling for objects that have been painted using GeoPainter::drawEllipse(). The \a strokeWidth allows to extrude the QRegion by half the amount of "stroke width" pixels. For input event handling it's always advisable to use a width that is slightly bigger than the width of the painter's pen. \see GeoDataCoordinates */ QRegion regionFromEllipse ( const GeoDataCoordinates & centerPosition, qreal width, qreal height, bool isGeoProjected = false, qreal strokeWidth = 3 ) const; /*! \brief Draws an image at the given position. The image is placed with its center located at the given \a centerPosition. The image rendering is performed in screen coordinates and is not subject to the current projection. \see GeoDataCoordinates */ void drawImage ( const GeoDataCoordinates & centerPosition, const QImage & image /* , bool isGeoProjected = false */ ); /*! \brief Draws a pixmap at the given position. The pixmap is placed with its center located at the given \a centerPosition. The image rendering is performed in screen coordinates and is not subject to the current projection. \see GeoDataCoordinates */ void drawPixmap ( const GeoDataCoordinates & centerPosition, const QPixmap & pixmap /*, bool isGeoProjected = false */ ); /*! \brief Creates a region for a rectangle for a pixmap at a given position. A QRegion object is created that represents the area covered by GeoPainter::drawPixmap(). This can be used e.g. for input event handling for objects that have been painted using GeoPainter::drawPixmap(). The \a margin allows to extrude the QRegion by "margin" pixels on every side. \see GeoDataCoordinates */ QRegion regionFromPixmapRect(const GeoDataCoordinates ¢erCoordinates, int width, int height, int margin = 0) const; /*! \brief Helper method for safe and quick linestring conversion. In general drawPolyline() should be used instead. However in situations where the same linestring is supposed to be drawn multiple times it's a good idea to cache the screen polygons using this method. \see GeoDataLineString */ void polygonsFromLineString( const GeoDataLineString &lineString, - QVector &polygons ); + QVector &polygons) const; /*! \brief Draws a given line string (a "polyline") with a label. The \a lineString is drawn using the current pen. It's possible to provide a \a labelText for the \a lineString. The text is rendered using the painter's font property. The position of the \a labelText can be specified using the \a labelPositionFlags. \see GeoDataLineString */ void drawPolyline ( const GeoDataLineString & lineString, const QString& labelText, LabelPositionFlags labelPositionFlags = LineCenter, const QColor& labelcolor = Qt::black); /*! \brief Draws Labels for a given set of screen polygons. In common cases the drawPolyline overload can be used instead. However in certain more complex cases this particular method might be helpful for further optimization. */ void drawLabelsForPolygons( const QVector &polygons, const QString& labelText, LabelPositionFlags labelPositionFlags, const QColor& labelColor ); /*! \brief Draws a given line string (a "polyline"). The \a lineString is drawn using the current pen. \see GeoDataLineString */ void drawPolyline(const GeoDataLineString & lineString); /*! \brief Creates a region for a given line string (a "polyline"). A QRegion object is created that represents the area covered by GeoPainter::drawPolyline( GeoDataLineString ). As such it can be used e.g. for input event handling for objects that have been painted using GeoPainter::drawPolyline( GeoDataLineString ). The \a strokeWidth allows to extrude the QRegion by half the amount of "stroke width" pixels. For input event handling it's always advisable to use a width that is slightly bigger than the width of the painter's pen. \see GeoDataLineString */ QRegion regionFromPolyline ( const GeoDataLineString & lineString, qreal strokeWidth = 3 ) const; /*! \brief Draws a given linear ring (a "polygon without holes"). The outline of the \a linearRing is drawn using the current pen. The background is painted using the current brush of the painter. Like in QPainter::drawPolygon() the \a fillRule specifies the fill algorithm that is used to fill the polygon. \see GeoDataLinearRing */ void drawPolygon ( const GeoDataLinearRing & linearRing, Qt::FillRule fillRule = Qt::OddEvenFill ); /*! \brief Creates a region for a given linear ring (a "polygon without holes"). A QRegion object is created that represents the area covered by GeoPainter::drawPolygon( GeoDataLinearRing ). As such it can be used e.g. for input event handling for objects that have been painted using GeoPainter::drawPolygon( GeoDataLinearRing ). Like in drawPolygon() the \a fillRule specifies the fill algorithm that is used to fill the polygon. The \a strokeWidth allows to extrude the QRegion by half the amount of "stroke width" pixels. For input event handling it's always advisable to use a width that is slightly bigger than the width of the painter's pen. For the polygon case a "cosmetic" strokeWidth of zero should provide the best performance. \see GeoDataLinearRing */ QRegion regionFromPolygon ( const GeoDataLinearRing & linearRing, Qt::FillRule fillRule, qreal strokeWidth = 3 ) const; /*! \brief Draws a given polygon (which may contain holes). The outline of the \a polygon is drawn using the current pen. The background is painted using the current brush of the painter. Like in QPainter::drawPolygon() the \a fillRule specifies the fill algorithm that is used to fill the polygon. \see GeoDataPolygon */ void drawPolygon ( const GeoDataPolygon & polygon, Qt::FillRule fillRule = Qt::OddEvenFill ); QVector createFillPolygons( const QVector & outerPolygons, const QVector & innerPolygons ) const; /*! \brief Draws a rectangle at the given position. The rectangle is placed with its center located at the given \a centerPosition. For the outline it uses the painter's pen and for the background the painter's brush. If \a isGeoProjected is true then the outline of the rectangle is drawn in geographic coordinates. In this case the \a width and the \a height are interpreted to be degrees. If \a isGeoProjected is false then the outline of the rectangle is drawn in screen coordinates. In this case the \a width and the \a height are interpreted to be pixels. \see GeoDataCoordinates */ void drawRect ( const GeoDataCoordinates & centerPosition, qreal width, qreal height, bool isGeoProjected = false ); /*! \brief Creates a region for a rectangle at a given position. A QRegion object is created that represents the area covered by GeoPainter::drawRect(). This can be used e.g. for input event handling for objects that have been painted using GeoPainter::drawRect(). The isGeoProjected parameter is used the same way as for GeoPainter::drawRect(). The \a strokeWidth allows to extrude the QRegion by half the amount of "stroke width" pixels. For input event handling it's always advisable to use a width that is slightly bigger than the width of the painter's pen. This is especially true for small objects. \see GeoDataCoordinates */ QRegion regionFromRect ( const GeoDataCoordinates & centerPosition, qreal width, qreal height, bool isGeoProjected = false, qreal strokeWidth = 3 ) const; /*! \brief Draws a rectangle with rounded corners at the given position. The rectangle is placed with its center located at the given \a centerPosition. For the outline it uses the painter's pen and for the background the painter's brush. Unlike in QPainter::drawRoundedRect() the rounded corners are not specified in percentage but in pixels to provide for optimal aesthetics. \param width Width of the rectangle in pixels \param height Height of the rectangle in pixels \param xRnd Specifies the geometry of the rounded corners in pixels along the x-axis. \param yRnd Specifies the geometry of the rounded corners in pixels along the y-axis. \see GeoDataCoordinates */ void drawRoundedRect(const GeoDataCoordinates ¢erPosition, qreal width, qreal height, qreal xRnd = 25.0, qreal yRnd = 25.0); void drawTextFragment(const QPoint &position, const QString &text, const qreal fontSize, const QColor &color = Qt::black, const Frames &flags = 0); // Reenabling QPainter+ClipPainter methods. using QPainter::drawText; using QPainter::drawEllipse; using QPainter::drawImage; using QPainter::drawPixmap; using QPainter::drawPoint; using ClipPainter::drawPolyline; using ClipPainter::drawPolygon; using QPainter::drawRect; using QPainter::drawRoundedRect; private: Q_DISABLE_COPY( GeoPainter ) GeoPainterPrivate * const d; }; } #endif diff --git a/src/lib/marble/MarbleInputHandler.cpp b/src/lib/marble/MarbleInputHandler.cpp index 7999fc11c..f99b82d79 100644 --- a/src/lib/marble/MarbleInputHandler.cpp +++ b/src/lib/marble/MarbleInputHandler.cpp @@ -1,994 +1,994 @@ // // This file is part of the Marble Virtual Globe. // // This program is free software licensed under the GNU LGPL. You can // find a copy of this license in LICENSE.txt in the top directory of // the source code. // // Copyright 2006-2007 Torsten Rahn // Copyright 2007 Inge Wallin // Copyright 2014 Adam Dabrowski // #include "MarbleInputHandler.h" #include #include #include #include #include #include #include #include #include "kineticmodel.h" #include "MarbleGlobal.h" #include "MarbleDebug.h" #include "MarbleMap.h" #include "GeoDataCoordinates.h" #include "MarbleAbstractPresenter.h" #include "ViewportParams.h" #include "AbstractFloatItem.h" #include "AbstractDataPluginItem.h" #include "RenderPlugin.h" namespace Marble { const int TOOLTIP_START_INTERVAL = 1000; class Q_DECL_HIDDEN MarbleInputHandler::Protected { public: Protected(MarbleAbstractPresenter *marblePresenter); MarbleAbstractPresenter *const m_marblePresenter; bool m_positionSignalConnected; QTimer *m_mouseWheelTimer; Qt::MouseButtons m_disabledMouseButtons; qreal m_wheelZoomTargetDistance; bool m_panViaArrowsEnabled; bool m_inertialEarthRotation; int m_steps; const int m_discreteZoomSteps = 120; }; MarbleInputHandler::Protected::Protected(MarbleAbstractPresenter *marblePresenter) : m_marblePresenter( marblePresenter ), m_positionSignalConnected( false ), m_mouseWheelTimer( 0 ), m_disabledMouseButtons( Qt::NoButton ), m_wheelZoomTargetDistance( 0.0 ), m_panViaArrowsEnabled( true ), m_inertialEarthRotation( true ), m_steps(0) { } MarbleInputHandler::MarbleInputHandler(MarbleAbstractPresenter *marblePresenter) : d(new Protected(marblePresenter)) { d->m_mouseWheelTimer = new QTimer( this ); connect(d->m_mouseWheelTimer, SIGNAL(timeout()), this, SLOT(restoreViewContext())); connect(d->m_marblePresenter->map(), SIGNAL(renderPluginInitialized(RenderPlugin*)), this, SLOT(installPluginEventFilter(RenderPlugin*))); } MarbleInputHandler::~MarbleInputHandler() { delete d->m_mouseWheelTimer; delete d; } void MarbleInputHandler::setPositionSignalConnected(bool connected) { d->m_positionSignalConnected = connected; } bool MarbleInputHandler::isPositionSignalConnected() const { return d->m_positionSignalConnected; } void MarbleInputHandler::setMouseButtonPopupEnabled(Qt::MouseButton mouseButton, bool enabled) { if (enabled) { d->m_disabledMouseButtons &= ~Qt::MouseButtons(mouseButton); } else { d->m_disabledMouseButtons |= mouseButton; } } bool MarbleInputHandler::isMouseButtonPopupEnabled(Qt::MouseButton mouseButton) const { return !(d->m_disabledMouseButtons & mouseButton); } void MarbleInputHandler::setPanViaArrowsEnabled(bool enabled) { d->m_panViaArrowsEnabled = enabled; } bool MarbleInputHandler::panViaArrowsEnabled() const { return d->m_panViaArrowsEnabled; } void MarbleInputHandler::setInertialEarthRotationEnabled(bool enabled) { d->m_inertialEarthRotation = enabled; } bool MarbleInputHandler::inertialEarthRotationEnabled() const { return d->m_inertialEarthRotation; } class Q_DECL_HIDDEN MarbleDefaultInputHandler::Private { public: Private(); ~Private(); QPixmap m_curpmtl; QPixmap m_curpmtc; QPixmap m_curpmtr; QPixmap m_curpmcr; QPixmap m_curpmcl; QPixmap m_curpmbl; QPixmap m_curpmbc; QPixmap m_curpmbr; QCursor m_arrowCur[3][3]; // Indicates if the left mouse button has been pressed already. bool m_leftPressed; // Indicates if the middle mouse button has been pressed already. bool m_midPressed; // The mouse pointer x position when the left mouse button has been pressed. int m_leftPressedX; // The mouse pointer y position when the left mouse button has been pressed. int m_leftPressedY; // The mouse pointer y position when the middle mouse button has been pressed. int m_midPressedY; int m_startingRadius; // Indicates if the right mouse button has been pressed already. bool m_rightPressed; // Point where the right mouse button has been pressed on. QPoint m_rightOrigin; // Position to calculate the heading. // Indicates previous position since mouse has been moved. QPoint m_rightPosition; // Indicates the heading when the right mouse button has been pressed // and mouse is moving. qreal m_heading; // The center longitude in radian when the left mouse button has been pressed. qreal m_leftPressedLon; // The center latitude in radian when the left mouse button has been pressed. qreal m_leftPressedLat; int m_dragThreshold; QTimer m_lmbTimer; QTimer m_pressAndHoldTimer; // Models to handle the kinetic spinning. KineticModel m_kineticSpinning; QPoint m_selectionOrigin; QPointer m_lastToolTipItem; QTimer m_toolTipTimer; QPoint m_toolTipPosition; }; MarbleDefaultInputHandler::Private::Private() : m_leftPressed(false), m_midPressed(false), m_rightPressed(false), m_heading(0), m_dragThreshold(MarbleGlobal::getInstance()->profiles() & MarbleGlobal::SmallScreen ? 15 : 3) { m_curpmtl.load(QStringLiteral(":/marble/cursor/tl.png")); m_curpmtc.load(QStringLiteral(":/marble/cursor/tc.png")); m_curpmtr.load(QStringLiteral(":/marble/cursor/tr.png")); m_curpmcr.load(QStringLiteral(":/marble/cursor/cr.png")); m_curpmcl.load(QStringLiteral(":/marble/cursor/cl.png")); m_curpmbl.load(QStringLiteral(":/marble/cursor/bl.png")); m_curpmbc.load(QStringLiteral(":/marble/cursor/bc.png")); m_curpmbr.load(QStringLiteral(":/marble/cursor/br.png")); m_arrowCur[0][0] = QCursor( m_curpmtl, 2, 2 ); m_arrowCur[1][0] = QCursor( m_curpmtc, 10, 3 ); m_arrowCur[2][0] = QCursor( m_curpmtr, 19, 2 ); m_arrowCur[0][1] = QCursor( m_curpmcl, 3, 10 ); m_arrowCur[1][1] = QCursor( Qt::OpenHandCursor ); m_arrowCur[2][1] = QCursor( m_curpmcr, 18, 10 ); m_arrowCur[0][2] = QCursor( m_curpmbl, 2, 19 ); m_arrowCur[1][2] = QCursor( m_curpmbc, 11, 18 ); m_arrowCur[2][2] = QCursor( m_curpmbr, 19, 19 ); } MarbleDefaultInputHandler::Private::~Private() { } MarbleDefaultInputHandler::MarbleDefaultInputHandler(MarbleAbstractPresenter *marblePresenter) : MarbleInputHandler(marblePresenter), d(new Private()) { d->m_toolTipTimer.setSingleShot(true); d->m_toolTipTimer.setInterval(TOOLTIP_START_INTERVAL); connect(&d->m_toolTipTimer, SIGNAL(timeout()), this, SLOT(openItemToolTip())); d->m_lmbTimer.setSingleShot(true); connect(&d->m_lmbTimer, SIGNAL(timeout()), this, SLOT(lmbTimeout())); d->m_kineticSpinning.setUpdateInterval(35); connect(&d->m_kineticSpinning, SIGNAL(positionChanged(qreal,qreal)), MarbleInputHandler::d->m_marblePresenter, SLOT(centerOn(qreal,qreal))); connect(&d->m_kineticSpinning, SIGNAL(headingChanged(qreal)), MarbleInputHandler::d->m_marblePresenter, SLOT(headingOn(qreal))); connect(&d->m_kineticSpinning, SIGNAL(finished()), SLOT(restoreViewContext())); // Left and right mouse button signals. connect(this, SIGNAL(rmbRequest(int,int)), this, SLOT(showRmbMenu(int,int))); connect(this, SIGNAL(lmbRequest(int,int)), this, SLOT(showLmbMenu(int,int))); d->m_pressAndHoldTimer.setInterval(800); d->m_pressAndHoldTimer.setSingleShot(true); connect(&d->m_pressAndHoldTimer, SIGNAL(timeout()), this, SLOT(handlePressAndHold())); } MarbleDefaultInputHandler::~MarbleDefaultInputHandler() { delete d; } void MarbleDefaultInputHandler::lmbTimeout() { if (!selectionRubber()->isVisible()) { qreal clickedLon = 0; qreal clickedLat = 0; bool isPointOnGlobe = MarbleInputHandler::d->m_marblePresenter->map()->geoCoordinates( d->m_leftPressedX, d->m_leftPressedY, clickedLon, clickedLat, GeoDataCoordinates::Degree ); emit lmbRequest(d->m_leftPressedX, d->m_leftPressedY); /** * emit mouse click only when the clicked * position is within the globe. */ if ( isPointOnGlobe ) { emit mouseClickGeoPosition( clickedLon, clickedLat, GeoDataCoordinates::Degree ); } } } void MarbleInputHandler::restoreViewContext() { // Needs to stop the timer since it repeats otherwise. d->m_mouseWheelTimer->stop(); // Redraw the map with the quality set for Still (if necessary). d->m_marblePresenter->setViewContext(Still); d->m_marblePresenter->map()->viewport()->resetFocusPoint(); d->m_wheelZoomTargetDistance = 0.0; } void MarbleDefaultInputHandler::hideSelectionIfCtrlReleased(QEvent *e) { if (selectionRubber()->isVisible() && e->type() == QEvent::MouseMove) { QMouseEvent *event = static_cast(e); if (!(event->modifiers() & Qt::ControlModifier)) { selectionRubber()->hide(); } } } bool MarbleDefaultInputHandler::handleDoubleClick(QMouseEvent *event) { qreal mouseLon; qreal mouseLat; const bool isMouseAboveMap = MarbleInputHandler::d->m_marblePresenter->map()->geoCoordinates(event->x(), event->y(), mouseLon, mouseLat, GeoDataCoordinates::Radian); if(isMouseAboveMap) { d->m_pressAndHoldTimer.stop(); d->m_lmbTimer.stop(); MarbleInputHandler::d->m_marblePresenter->moveTo(event->pos(), 0.67); } return acceptMouse(); } bool MarbleDefaultInputHandler::handleWheel(QWheelEvent *wheelevt) { MarbleAbstractPresenter *marblePresenter = MarbleInputHandler::d->m_marblePresenter; marblePresenter->setViewContext(Animation); if( (MarbleInputHandler::d->m_steps > 0 && wheelevt->delta() < 0) || (MarbleInputHandler::d->m_steps < 0 && wheelevt->delta() > 0) ) { MarbleInputHandler::d->m_steps = wheelevt->delta(); } else { MarbleInputHandler::d->m_steps += wheelevt->delta(); } if (marblePresenter->map()->discreteZoom()) { if(qAbs(MarbleInputHandler::d->m_steps) >= MarbleInputHandler::d->m_discreteZoomSteps) { marblePresenter->zoomAtBy(wheelevt->pos(), MarbleInputHandler::d->m_steps); MarbleInputHandler::d->m_steps = 0; } } else { qreal zoom = marblePresenter->zoom(); qreal target = MarbleInputHandler::d->m_wheelZoomTargetDistance; if (marblePresenter->animationsEnabled() && target > 0.0) { // Do not use intermediate (interpolated) distance values caused by animations zoom = marblePresenter->zoomFromDistance(target); } qreal newDistance = marblePresenter->distanceFromZoom(zoom + MarbleInputHandler::d->m_steps); MarbleInputHandler::d->m_wheelZoomTargetDistance = newDistance; marblePresenter->zoomAt(wheelevt->pos(), newDistance); if (MarbleInputHandler::d->m_inertialEarthRotation) { d->m_kineticSpinning.jumpToPosition(MarbleInputHandler::d->m_marblePresenter->centerLongitude(), MarbleInputHandler::d->m_marblePresenter->centerLatitude()); } MarbleInputHandler::d->m_steps = 0; } MarbleInputHandler::d->m_mouseWheelTimer->start(400); return true; } bool MarbleDefaultInputHandler::handlePinch(const QPointF ¢er, qreal scaleFactor, Qt::GestureState state) { qreal destLat; qreal destLon; MarbleAbstractPresenter *marblePresenter = MarbleInputHandler::d->m_marblePresenter; bool isValid = marblePresenter->map()->geoCoordinates(center.x(), center.y(), destLon, destLat, GeoDataCoordinates::Radian ); if (isValid) { marblePresenter->map()->viewport()->setFocusPoint(GeoDataCoordinates(destLon, destLat)); } qreal zoom, target, newDistance; qreal zoomDelta = scaleFactor > 1.0 ? scaleFactor : -1.0/scaleFactor; switch (state) { case Qt::NoGesture: break; case Qt::GestureStarted: marblePresenter->setViewContext(Animation); d->m_pressAndHoldTimer.stop(); d->m_lmbTimer.stop(); d->m_midPressed = false; d->m_leftPressed = false; break; case Qt::GestureUpdated: zoom = marblePresenter->zoom(); target = MarbleInputHandler::d->m_wheelZoomTargetDistance; if (marblePresenter->animationsEnabled() && target > 0.0) { // Do not use intermediate (interpolated) distance values caused by animations zoom = marblePresenter->zoomFromDistance(target); } newDistance = marblePresenter->distanceFromZoom(zoom + 20 * zoomDelta); MarbleInputHandler::d->m_wheelZoomTargetDistance = newDistance; marblePresenter->zoomAt(center.toPoint(), newDistance); break; case Qt::GestureFinished: marblePresenter->map()->viewport()->resetFocusPoint(); marblePresenter->setViewContext(Still); break; case Qt::GestureCanceled: marblePresenter->map()->viewport()->resetFocusPoint(); marblePresenter->setViewContext(Still); break; } return true; } bool MarbleDefaultInputHandler::handleGesture(QGestureEvent *ge) { QPinchGesture *pinch = static_cast(ge->gesture(Qt::PinchGesture)); if (!pinch) { return false; } qreal scaleFactor = pinch->scaleFactor(); QPointF center = pinch->centerPoint(); return handlePinch(center, scaleFactor, pinch->state()); } void MarbleDefaultInputHandler::checkReleasedMove(QMouseEvent *event) { // To prevent error from lost MouseButtonRelease events if (event->type() == QEvent::MouseMove && !(event->buttons() & Qt::LeftButton)) { if (d->m_leftPressed) { d->m_leftPressed = false; if (MarbleInputHandler::d->m_inertialEarthRotation) { d->m_kineticSpinning.start(); } else { MarbleInputHandler::d->m_marblePresenter->setViewContext(Still); } } } if (event->type() == QEvent::MouseMove && !(event->buttons() & Qt::MidButton)) { d->m_midPressed = false; } } void MarbleDefaultInputHandler::handleMouseButtonPress(QMouseEvent *event) { if (event->button() == Qt::LeftButton ) { d->m_pressAndHoldTimer.start(); handleLeftMouseButtonPress(event); } if ( event->button() == Qt::MidButton ) { handleMiddleMouseButtonPress(event); } if ( event->button() == Qt::RightButton ) { handleRightMouseButtonPress(event); } } void MarbleDefaultInputHandler::handleLeftMouseButtonPress(QMouseEvent *event) { // silently enable the animation context without triggering a repaint MarbleInputHandler::d->m_marblePresenter->map()->blockSignals(true); MarbleInputHandler::d->m_marblePresenter->setViewContext(Animation); MarbleInputHandler::d->m_marblePresenter->map()->blockSignals(false); if (isMouseButtonPopupEnabled(Qt::LeftButton)) { d->m_lmbTimer.start(400); } d->m_leftPressed = true; d->m_midPressed = false; selectionRubber()->hide(); // On the single event of a mouse button press these // values get stored, to enable us to e.g. calculate the // distance of a mouse drag while the mouse button is // still down. d->m_leftPressedX = event->x(); d->m_leftPressedY = event->y(); // Calculate translation of center point d->m_leftPressedLon = MarbleInputHandler::d->m_marblePresenter->centerLongitude(); d->m_leftPressedLat = MarbleInputHandler::d->m_marblePresenter->centerLatitude(); if (MarbleInputHandler::d->m_inertialEarthRotation) { d->m_kineticSpinning.stop(); d->m_kineticSpinning.setPosition(d->m_leftPressedLon, d->m_leftPressedLat); } if (event->modifiers() & Qt::ControlModifier) { mDebug() << Q_FUNC_INFO << "Starting selection"; d->m_pressAndHoldTimer.stop(); d->m_lmbTimer.stop(); d->m_selectionOrigin = event->pos(); selectionRubber()->setGeometry(QRect(d->m_selectionOrigin, QSize())); selectionRubber()->show(); } } void MarbleDefaultInputHandler::handleMiddleMouseButtonPress(QMouseEvent *event) { d->m_midPressed = true; d->m_leftPressed = false; d->m_startingRadius = MarbleInputHandler::d->m_marblePresenter->radius(); d->m_midPressedY = event->y(); if (MarbleInputHandler::d->m_inertialEarthRotation) { d->m_kineticSpinning.start(); } selectionRubber()->hide(); MarbleInputHandler::d->m_marblePresenter->setViewContext(Animation); } void MarbleDefaultInputHandler::handleRightMouseButtonPress(QMouseEvent *event) { d->m_rightPressed = true; d->m_rightOrigin = event->pos(); d->m_rightPosition = event->pos(); d->m_heading = MarbleInputHandler::d->m_marblePresenter->map()->heading(); if (MarbleInputHandler::d->m_inertialEarthRotation) { d->m_kineticSpinning.stop(); d->m_kineticSpinning.setHeading(d->m_heading); } } void MarbleDefaultInputHandler::handleMouseButtonRelease(QMouseEvent *event) { if (event->button() == Qt::LeftButton) { d->m_pressAndHoldTimer.stop(); //emit current coordinates to be interpreted //as requested emit mouseClickScreenPosition(d->m_leftPressedX, d->m_leftPressedY); d->m_leftPressed = false; if (MarbleInputHandler::d->m_inertialEarthRotation) { d->m_kineticSpinning.start(); } else { MarbleInputHandler::d->m_marblePresenter->setViewContext(Still); } } if (event->button() == Qt::MidButton) { d->m_midPressed = false; MarbleInputHandler::d->m_marblePresenter->setViewContext(Still); } if (event->type() == QEvent::MouseButtonRelease && event->button() == Qt::RightButton) { if (d->m_rightOrigin == event->pos()) { emit rmbRequest(event->x(), event->y()); } d->m_rightPressed = false; if (MarbleInputHandler::d->m_inertialEarthRotation) { d->m_kineticSpinning.start(); } else { MarbleInputHandler::d->m_marblePresenter->setViewContext(Still); } } if (event->type() == QEvent::MouseButtonRelease && event->button() == Qt::LeftButton && selectionRubber()->isVisible()) { mDebug() << Q_FUNC_INFO << "Leaving selection"; MarbleInputHandler::d->m_marblePresenter->setSelection(selectionRubber()->geometry()); selectionRubber()->hide(); } } void MarbleDefaultInputHandler::notifyPosition(bool isMouseAboveMap, qreal mouseLon, qreal mouseLat) { // emit the position string only if the signal got attached if (MarbleInputHandler::d->m_positionSignalConnected) { if (!isMouseAboveMap) { emit mouseMoveGeoPosition(QCoreApplication::translate( "Marble", NOT_AVAILABLE)); } else { QString position = GeoDataCoordinates(mouseLon, mouseLat).toString(); emit mouseMoveGeoPosition(position); } } } void MarbleDefaultInputHandler::adjustCursorShape(const QPoint &mousePosition, const QPoint &mouseDirection) { // Find out if there are data items and if one has defined an action QList dataItems = MarbleInputHandler::d->m_marblePresenter->map()->whichItemAt(mousePosition); bool dataAction = false; QPointer toolTipItem; QList::iterator it = dataItems.begin(); QList::iterator const end = dataItems.end(); for (; it != end && dataAction == false && toolTipItem.isNull(); ++it) { if ((*it)->action()) { dataAction = true; } if (!(*it)->toolTip().isNull() && toolTipItem.isNull()) { toolTipItem = (*it); } } if (toolTipItem.isNull()) { d->m_toolTipTimer.stop(); } else if (!( d->m_lastToolTipItem.data() == toolTipItem.data())) { d->m_toolTipTimer.start(); d->m_lastToolTipItem = toolTipItem; d->m_toolTipPosition = mousePosition; } else { if (!d->m_toolTipTimer.isActive()) { d->m_toolTipTimer.start(); } d->m_toolTipPosition = mousePosition; } if (!dataAction && !MarbleInputHandler::d->m_marblePresenter->map()->hasFeatureAt(mousePosition)) { if (!d->m_leftPressed) { d->m_arrowCur [1][1] = QCursor(Qt::OpenHandCursor); } else { d->m_arrowCur [1][1] = QCursor(Qt::ClosedHandCursor); } } else { if (!d->m_leftPressed) { d->m_arrowCur [1][1] = QCursor(Qt::PointingHandCursor); } } if (panViaArrowsEnabled()) { setCursor(d->m_arrowCur[mouseDirection.x()+1][mouseDirection.y()+1]); } else { setCursor(d->m_arrowCur[1][1]); } } QPoint MarbleDefaultInputHandler::mouseMovedOutside(QMouseEvent *event) { //Returns a 2d vector representing the direction in which the mouse left int dirX = 0; int dirY = 0; int polarity = MarbleInputHandler::d->m_marblePresenter->viewport()->polarity(); if (d->m_leftPressed) { d->m_leftPressed = false; if (MarbleInputHandler::d->m_inertialEarthRotation) { d->m_kineticSpinning.start(); } } QRect boundingRect = MarbleInputHandler::d->m_marblePresenter->viewport()->mapRegion().boundingRect(); if (boundingRect.width() != 0) { dirX = (int)( 3 * (event->x() - boundingRect.left()) / boundingRect.width()) - 1; } if (dirX > 1) { dirX = 1; } if (dirX < -1) { dirX = -1; } if (boundingRect.height() != 0) { dirY = (int)(3 * (event->y() - boundingRect.top()) / boundingRect.height()) - 1; } if (dirY > 1) { dirY = 1; } if (dirY < -1) { dirY = -1; } if (event->button() == Qt::LeftButton && event->type() == QEvent::MouseButtonPress && panViaArrowsEnabled() && !d->m_kineticSpinning.hasVelocity()) { d->m_pressAndHoldTimer.stop(); d->m_lmbTimer.stop(); qreal moveStep = MarbleInputHandler::d->m_marblePresenter->moveStep(); if (polarity < 0) { MarbleInputHandler::d->m_marblePresenter->rotateBy(-moveStep * (qreal)(+dirX), moveStep * (qreal)(+dirY)); } else { MarbleInputHandler::d->m_marblePresenter->rotateBy(-moveStep * (qreal)(-dirX), moveStep * (qreal)(+dirY)); } } if (!MarbleInputHandler::d->m_inertialEarthRotation) { MarbleInputHandler::d->m_marblePresenter->setViewContext(Still); } return QPoint(dirX, dirY); } bool MarbleDefaultInputHandler::handleMouseEvent(QMouseEvent *event) { QPoint direction; checkReleasedMove(event); // Do not handle (and therefore eat) mouse press and release events // that occur above visible float items. Mouse motion events are still // handled, however. if (event->type() != QEvent::MouseMove && !selectionRubber()->isVisible()) { auto const floatItems = MarbleInputHandler::d->m_marblePresenter->map()->floatItems(); for (AbstractFloatItem *floatItem: floatItems) { if ( floatItem->enabled() && floatItem->visible() && floatItem->contains( event->pos() ) ) { d->m_pressAndHoldTimer.stop(); d->m_lmbTimer.stop(); return false; } } } qreal mouseLon; qreal mouseLat; const bool isMouseAboveMap = MarbleInputHandler::d->m_marblePresenter->map()->geoCoordinates(event->x(), event->y(), mouseLon, mouseLat, GeoDataCoordinates::Radian); notifyPosition(isMouseAboveMap, mouseLon, mouseLat); QPoint mousePosition(event->x(), event->y()); if (isMouseAboveMap || selectionRubber()->isVisible() || MarbleInputHandler::d->m_marblePresenter->map()->hasFeatureAt(mousePosition)) { if (event->type() == QEvent::MouseButtonPress) { handleMouseButtonPress(event); } if (event->type() == QEvent::MouseButtonRelease) { handleMouseButtonRelease(event); } // Regarding all kinds of mouse moves: if (d->m_leftPressed && !selectionRubber()->isVisible()) { qreal radius = (qreal)(MarbleInputHandler::d->m_marblePresenter->radius()); qreal deltax = event->x() - d->m_leftPressedX; qreal deltay = event->y() - d->m_leftPressedY; if (qAbs(deltax) > d->m_dragThreshold || qAbs(deltay) > d->m_dragThreshold || !d->m_lmbTimer.isActive()) { MarbleInputHandler::d->m_marblePresenter->setViewContext(Animation); d->m_pressAndHoldTimer.stop(); d->m_lmbTimer.stop(); const Quaternion rotation = Quaternion::fromEuler( 0, 0, MarbleInputHandler::d->m_marblePresenter->map()->heading() * DEG2RAD ); Quaternion quat = Quaternion::fromSpherical( - M_PI/2 * deltax / radius, + M_PI/2 * deltay / radius ); quat.rotateAroundAxis( rotation ); qreal lon, lat; quat.getSpherical( lon, lat ); const qreal posLon = d->m_leftPressedLon + RAD2DEG * lon; const qreal posLat = d->m_leftPressedLat + RAD2DEG * lat; MarbleInputHandler::d->m_marblePresenter->centerOn(posLon, posLat); if (MarbleInputHandler::d->m_inertialEarthRotation) { d->m_kineticSpinning.setPosition(posLon, posLat); } } } if (d->m_midPressed) { int eventy = event->y(); int dy = d->m_midPressedY - eventy; MarbleInputHandler::d->m_marblePresenter->setRadius(d->m_startingRadius * pow(1.005, dy)); } if (d->m_rightPressed) { qreal centerX, centerY; MarbleInputHandler::d->m_marblePresenter->map()->screenCoordinates( MarbleInputHandler::d->m_marblePresenter->centerLongitude(), MarbleInputHandler::d->m_marblePresenter->centerLatitude(), centerX, centerY); // Deltas from previous position. int dx = event->x() - d->m_rightPosition.x(); int dy = event->y() - d->m_rightPosition.y(); d->m_rightPosition = event->pos(); // Moving on the bottom should be opposite direction. int sign = event->y() > centerY ? -1 : 1; // Left top and right bottom sides for y axis should be opposite direction. if ((event->x() < centerX && event->y() < centerY) || (event->x() > centerX && event->y() > centerY)) { dy *= -1; } const qreal speedFactor = 0.3; d->m_heading += (dx + dy) * sign * speedFactor; MarbleInputHandler::d->m_marblePresenter->map()->setHeading(d->m_heading); if (MarbleInputHandler::d->m_inertialEarthRotation) { d->m_kineticSpinning.setHeading(d->m_heading); } } if (selectionRubber()->isVisible()) { // We change selection. selectionRubber()->setGeometry(QRect(d->m_selectionOrigin, event->pos()).normalized()); } } else { direction = mouseMovedOutside(event); } if (MarbleInputHandler::d->m_marblePresenter->viewContext() != Animation) { adjustCursorShape(mousePosition, direction); } return acceptMouse(); } bool MarbleDefaultInputHandler::acceptMouse() { // let others, especially float items, still process the event // Note: This caused a bug in combination with oxygen, see https://bugs.kde.org/show_bug.cgi?id=242414 // and changing it a related regression, see https://bugs.kde.org/show_bug.cgi?id=324862 return false; } bool MarbleDefaultInputHandler::eventFilter(QObject* o, QEvent* e) { Q_UNUSED(o); if (layersEventFilter(o, e)) { return true; } hideSelectionIfCtrlReleased(e); switch (e->type()) { case QEvent::TouchBegin: case QEvent::TouchUpdate: case QEvent::TouchEnd: return handleTouch(static_cast(e)); case QEvent::KeyPress: return handleKeyPress(static_cast(e)); case QEvent::Gesture: return handleGesture(static_cast(e)); case QEvent::Wheel: return handleWheel(static_cast(e)); case QEvent::MouseButtonDblClick: return handleDoubleClick(static_cast(e)); case QEvent::MouseButtonPress: case QEvent::MouseButtonRelease: case QEvent::MouseMove: return handleMouseEvent(static_cast(e)); default: return false; } } bool MarbleDefaultInputHandler::handleTouch(QTouchEvent*) { return false; //reimplement to handle in cases of QML and PinchArea element } bool MarbleDefaultInputHandler::handleKeyPress(QKeyEvent* event) { if ( event->type() == QEvent::KeyPress ) { MarbleAbstractPresenter *marblePresenter = MarbleInputHandler::d->m_marblePresenter; bool handled = true; switch ( event->key() ) { case Qt::Key_Left: marblePresenter->moveByStep(-1, 0); break; case Qt::Key_Right: marblePresenter->moveByStep(1, 0); break; case Qt::Key_Up: marblePresenter->moveByStep(0, -1); break; case Qt::Key_Down: marblePresenter->moveByStep(0, 1); break; case Qt::Key_Plus: if (event->modifiers() != Qt::ControlModifier) { marblePresenter->zoomIn(); } break; case Qt::Key_Minus: if (event->modifiers() != Qt::ControlModifier) { marblePresenter->zoomOut(); } break; case Qt::Key_Home: marblePresenter->goHome(); break; default: handled = false; break; } return handled; } return false; } void MarbleDefaultInputHandler::handleMouseButtonPressAndHold(const QPoint &) { // Default implementation does nothing } void MarbleDefaultInputHandler::handlePressAndHold() { handleMouseButtonPressAndHold(QPoint(d->m_leftPressedX, d->m_leftPressedY)); } -QPointer MarbleDefaultInputHandler::lastToolTipItem() +const AbstractDataPluginItem *MarbleDefaultInputHandler::lastToolTipItem() const { return d->m_lastToolTipItem; } QTimer* MarbleDefaultInputHandler::toolTipTimer() { return &d->m_toolTipTimer; } -QPoint MarbleDefaultInputHandler::toolTipPosition() +QPoint MarbleDefaultInputHandler::toolTipPosition() const { return d->m_toolTipPosition; } } #include "moc_MarbleInputHandler.cpp" diff --git a/src/lib/marble/MarbleInputHandler.h b/src/lib/marble/MarbleInputHandler.h index 0d3b87e6a..68fb0f156 100644 --- a/src/lib/marble/MarbleInputHandler.h +++ b/src/lib/marble/MarbleInputHandler.h @@ -1,183 +1,183 @@ // // This file is part of the Marble Virtual Globe. // // This program is free software licensed under the GNU LGPL. You can // find a copy of this license in LICENSE.txt in the top directory of // the source code. // // Copyright 2005-2007 Torsten Rahn // Copyright 2007 Inge Wallin // Copyright 2014 Adam Dabrowski // #ifndef MARBLE_MARBLEINPUTHANDLER_H #define MARBLE_MARBLEINPUTHANDLER_H #include #include "marble_export.h" #include "GeoDataCoordinates.h" class QEvent; class QKeyEvent; class QMouseEvent; class QTouchEvent; class QWheelEvent; class QGestureEvent; class QCursor; class QTimer; class QString; class QRect; namespace Marble { class MarbleAbstractPresenter; class AbstractDataPluginItem; class RenderPlugin; class MARBLE_EXPORT MarbleInputHandler : public QObject { Q_OBJECT public: explicit MarbleInputHandler( MarbleAbstractPresenter* ); ~MarbleInputHandler() override; void setPositionSignalConnected( bool connected ); bool isPositionSignalConnected() const; /** // // The MarbleInputHandler handles mouse and keyboard input. // * @brief Set whether a popup menu appears on a click (not drag) with the left mouse button * @param enabled True to enable the popup menu (default), false to disable it */ void setMouseButtonPopupEnabled( Qt::MouseButton mouseButton, bool enabled ); /** * @brief Return whether the left mouse button popup menu is active * @return True iff a popup menu is shown on left mouse button clicks */ bool isMouseButtonPopupEnabled( Qt::MouseButton mouseButton ) const; void setPanViaArrowsEnabled( bool enabled ); bool panViaArrowsEnabled() const; void setInertialEarthRotationEnabled( bool enabled ); /** * @brief Returns true iff dragging the map with the mouse keeps spinning * in the chosen direction for a slightly longer time than the mouse is * actually performing the drag operation */ bool inertialEarthRotationEnabled() const; Q_SIGNALS: // Mouse button menus void lmbRequest( int, int ); void rmbRequest( int, int ); //Gps coordinates void mouseClickScreenPosition( int, int ); void mouseMoveGeoPosition( const QString& ); /* * To detect mouse click followed by mouse move * with no mouse move in between */ void mouseClickGeoPosition( qreal, qreal, GeoDataCoordinates::Unit ); protected Q_SLOTS: void restoreViewContext(); protected: class Protected; Protected * const d; private Q_SLOTS: virtual void installPluginEventFilter( RenderPlugin *renderPlugin ) = 0; private: Q_DISABLE_COPY( MarbleInputHandler ) }; class AbstractSelectionRubber { public: virtual ~AbstractSelectionRubber() {} virtual void show() = 0; virtual void hide() = 0; virtual bool isVisible() const = 0; virtual const QRect &geometry() const = 0; virtual void setGeometry(const QRect &geometry) = 0; }; class MARBLE_EXPORT MarbleDefaultInputHandler : public MarbleInputHandler { Q_OBJECT public: explicit MarbleDefaultInputHandler( MarbleAbstractPresenter* marblePresenter); ~MarbleDefaultInputHandler() override; protected: bool eventFilter( QObject *, QEvent * ) override; bool handleMouseEvent(QMouseEvent *e); bool handlePinch(const QPointF ¢er, qreal scaleFactor, Qt::GestureState state); //FIXME - refactor (abstraction & composition) - QPointer lastToolTipItem(); + const AbstractDataPluginItem *lastToolTipItem() const; QTimer* toolTipTimer(); - QPoint toolTipPosition(); + QPoint toolTipPosition() const; virtual bool handleKeyPress(QKeyEvent *e); virtual void handleMouseButtonPressAndHold(const QPoint &position); private Q_SLOTS: void installPluginEventFilter( RenderPlugin *renderPlugin ) override = 0; virtual void showLmbMenu( int, int ) = 0; virtual void showRmbMenu( int, int ) = 0; void handlePressAndHold(); virtual void openItemToolTip() = 0; virtual void setCursor(const QCursor &) = 0; void lmbTimeout(); private: virtual AbstractSelectionRubber *selectionRubber() = 0; virtual bool layersEventFilter(QObject *, QEvent *) = 0; virtual bool handleTouch(QTouchEvent *e); virtual bool handleDoubleClick(QMouseEvent *e); virtual bool handleWheel(QWheelEvent *e); virtual bool handleGesture(QGestureEvent *e); virtual void handleMouseButtonPress(QMouseEvent *e); virtual void handleLeftMouseButtonPress(QMouseEvent *e); virtual void handleRightMouseButtonPress(QMouseEvent *e); virtual void handleMiddleMouseButtonPress(QMouseEvent *e); virtual void handleMouseButtonRelease(QMouseEvent *e); virtual void hideSelectionIfCtrlReleased(QEvent *e); virtual void checkReleasedMove(QMouseEvent *e); //Returns whatever should be returned from mouse event handling loop virtual bool acceptMouse(); void notifyPosition(bool isAboveMap, qreal mouseLon, qreal mouseLat); QPoint mouseMovedOutside(QMouseEvent *event); void adjustCursorShape(const QPoint& mousePosition, const QPoint& mouseDirection); Q_DISABLE_COPY(MarbleDefaultInputHandler) class Private; Private * const d; }; } #endif //MARBLE_MARBLEINPUTHANDLER_H diff --git a/src/lib/marble/MarbleMap.cpp b/src/lib/marble/MarbleMap.cpp index e8f4996e6..cb19e7356 100644 --- a/src/lib/marble/MarbleMap.cpp +++ b/src/lib/marble/MarbleMap.cpp @@ -1,1492 +1,1492 @@ // // This file is part of the Marble Virtual Globe. // // This program is free software licensed under the GNU LGPL. You can // find a copy of this license in LICENSE.txt in the top directory of // the source code. // // Copyright 2006-2009 Torsten Rahn // Copyright 2007 Inge Wallin // Copyright 2008 Carlos Licea // Copyright 2009 Jens-Michael Hoffmann // Copyright 2010-2012 Bernhard Beschow // // Own #include "MarbleMap.h" // Posix #include // Qt #include #include #include // Marble #include "layers/FloatItemsLayer.h" #include "layers/FogLayer.h" #include "layers/FpsLayer.h" #include "layers/GeometryLayer.h" #include "layers/GroundLayer.h" #include "layers/MarbleSplashLayer.h" #include "layers/PlacemarkLayer.h" #include "layers/TextureLayer.h" #include "layers/VectorTileLayer.h" #include "AbstractFloatItem.h" #include "DgmlAuxillaryDictionary.h" #include "FileManager.h" #include "GeoDataTreeModel.h" #include "GeoPainter.h" #include "GeoSceneDocument.h" #include "GeoSceneFilter.h" #include "GeoSceneGeodata.h" #include "GeoSceneHead.h" #include "GeoSceneLayer.h" #include "GeoSceneMap.h" #include "GeoScenePalette.h" #include "GeoSceneSettings.h" #include "GeoSceneVector.h" #include "GeoSceneVectorTileDataset.h" #include "GeoSceneTextureTileDataset.h" #include "GeoSceneZoom.h" #include "GeoDataDocument.h" #include "GeoDataFeature.h" #include "GeoDataStyle.h" #include "GeoDataStyleMap.h" #include "LayerManager.h" #include "MapThemeManager.h" #include "MarbleDebug.h" #include "MarbleDirs.h" #include "MarbleModel.h" #include "PluginManager.h" #include "RenderPlugin.h" #include "StyleBuilder.h" #include "SunLocator.h" #include "TileId.h" #include "TileCoordsPyramid.h" #include "TileCreator.h" #include "TileCreatorDialog.h" #include "TileLoader.h" #include "ViewParams.h" #include "ViewportParams.h" #include "RenderState.h" #include "BookmarkManager.h" namespace Marble { class MarbleMap::CustomPaintLayer : public LayerInterface { public: explicit CustomPaintLayer( MarbleMap *map ) : m_map( map ) { } QStringList renderPosition() const override { return QStringList() << "USER_TOOLS"; } bool render( GeoPainter *painter, ViewportParams *viewport, const QString &renderPos, GeoSceneLayer *layer ) override { Q_UNUSED( viewport ); Q_UNUSED( renderPos ); Q_UNUSED( layer ); m_map->customPaint( painter ); return true; } qreal zValue() const override { return 1.0e6; } RenderState renderState() const override { return RenderState(QStringLiteral("Custom Map Paint")); } QString runtimeTrace() const override { return QStringLiteral("CustomPaint"); } private: MarbleMap *const m_map; }; class MarbleMapPrivate { friend class MarbleWidget; public: explicit MarbleMapPrivate( MarbleMap *parent, MarbleModel *model ); void updateMapTheme(); void updateProperty( const QString &, bool ); void setDocument( const QString& key ); void updateTileLevel(); void addPlugins(); MarbleMap *const q; // The model we are showing. MarbleModel *const m_model; bool m_modelIsOwned; // Parameters for the maps appearance. ViewParams m_viewParams; ViewportParams m_viewport; bool m_showFrameRate; bool m_showDebugPolygons; bool m_showDebugBatchRender; GeoDataRelation::RelationTypes m_visibleRelationTypes; StyleBuilder m_styleBuilder; QList m_renderPlugins; LayerManager m_layerManager; MarbleSplashLayer m_marbleSplashLayer; MarbleMap::CustomPaintLayer m_customPaintLayer; GeometryLayer m_geometryLayer; FloatItemsLayer m_floatItemsLayer; FogLayer m_fogLayer; GroundLayer m_groundLayer; TextureLayer m_textureLayer; PlacemarkLayer m_placemarkLayer; VectorTileLayer m_vectorTileLayer; bool m_isLockedToSubSolarPoint; bool m_isSubSolarPointIconVisible; RenderState m_renderState; }; MarbleMapPrivate::MarbleMapPrivate( MarbleMap *parent, MarbleModel *model ) : q( parent ), m_model( model ), m_viewParams(), m_showFrameRate( false ), m_showDebugPolygons( false ), m_showDebugBatchRender( false ), m_visibleRelationTypes(GeoDataRelation::RouteFerry), m_styleBuilder(), m_layerManager( parent ), m_customPaintLayer( parent ), m_geometryLayer(model->treeModel(), &m_styleBuilder), m_floatItemsLayer(parent), m_textureLayer( model->downloadManager(), model->pluginManager(), model->sunLocator(), model->groundOverlayModel() ), m_placemarkLayer( model->placemarkModel(), model->placemarkSelectionModel(), model->clock(), &m_styleBuilder ), m_vectorTileLayer( model->downloadManager(), model->pluginManager(), model->treeModel() ), m_isLockedToSubSolarPoint( false ), m_isSubSolarPointIconVisible( false ) { m_layerManager.addLayer(&m_floatItemsLayer); m_layerManager.addLayer( &m_fogLayer ); m_layerManager.addLayer( &m_groundLayer ); m_layerManager.addLayer( &m_geometryLayer ); m_layerManager.addLayer( &m_placemarkLayer ); m_layerManager.addLayer( &m_customPaintLayer ); m_model->bookmarkManager()->setStyleBuilder(&m_styleBuilder); QObject::connect( m_model, SIGNAL(themeChanged(QString)), parent, SLOT(updateMapTheme()) ); QObject::connect( m_model->fileManager(), SIGNAL(fileAdded(QString)), parent, SLOT(setDocument(QString)) ); QObject::connect( &m_placemarkLayer, SIGNAL(repaintNeeded()), parent, SIGNAL(repaintNeeded())); QObject::connect ( &m_layerManager, SIGNAL(pluginSettingsChanged()), parent, SIGNAL(pluginSettingsChanged()) ); QObject::connect ( &m_layerManager, SIGNAL(repaintNeeded(QRegion)), parent, SIGNAL(repaintNeeded(QRegion)) ); QObject::connect ( &m_layerManager, SIGNAL(renderPluginInitialized(RenderPlugin*)), parent, SIGNAL(renderPluginInitialized(RenderPlugin*)) ); QObject::connect ( &m_layerManager, SIGNAL(visibilityChanged(QString,bool)), parent, SLOT(setPropertyValue(QString,bool)) ); QObject::connect( &m_geometryLayer, SIGNAL(repaintNeeded()), parent, SIGNAL(repaintNeeded())); /* * Slot handleHighlight finds all placemarks * that contain the clicked point. * The placemarks under the clicked position may * have their styleUrl set to a style map which * doesn't specify any highlight styleId. Such * placemarks will be fletered out in GeoGraphicsScene * and will not be highlighted. */ QObject::connect( parent, SIGNAL(highlightedPlacemarksChanged(qreal,qreal,GeoDataCoordinates::Unit)), &m_geometryLayer, SLOT(handleHighlight(qreal,qreal,GeoDataCoordinates::Unit)) ); QObject::connect(&m_floatItemsLayer, SIGNAL(repaintNeeded(QRegion)), parent, SIGNAL(repaintNeeded(QRegion))); QObject::connect(&m_floatItemsLayer, SIGNAL(renderPluginInitialized(RenderPlugin*)), parent, SIGNAL(renderPluginInitialized(RenderPlugin*))); QObject::connect(&m_floatItemsLayer, SIGNAL(visibilityChanged(QString,bool)), parent, SLOT(setPropertyValue(QString,bool))); QObject::connect(&m_floatItemsLayer, SIGNAL(pluginSettingsChanged()), parent, SIGNAL(pluginSettingsChanged())); QObject::connect( &m_textureLayer, SIGNAL(tileLevelChanged(int)), parent, SLOT(updateTileLevel()) ); QObject::connect( &m_vectorTileLayer, SIGNAL(tileLevelChanged(int)), parent, SLOT(updateTileLevel()) ); QObject::connect( parent, SIGNAL(radiusChanged(int)), parent, SLOT(updateTileLevel()) ); QObject::connect( &m_textureLayer, SIGNAL(repaintNeeded()), parent, SIGNAL(repaintNeeded()) ); QObject::connect( parent, SIGNAL(visibleLatLonAltBoxChanged(GeoDataLatLonAltBox)), parent, SIGNAL(repaintNeeded()) ); addPlugins(); QObject::connect(model->pluginManager(), SIGNAL(renderPluginsChanged()), parent, SLOT(addPlugins())); } void MarbleMapPrivate::updateProperty( const QString &name, bool show ) { // earth if (name == QLatin1String("places")) { m_placemarkLayer.setShowPlaces( show ); } else if (name == QLatin1String("cities")) { m_placemarkLayer.setShowCities( show ); } else if (name == QLatin1String("terrain")) { m_placemarkLayer.setShowTerrain( show ); } else if (name == QLatin1String("otherplaces")) { m_placemarkLayer.setShowOtherPlaces( show ); } // other planets else if (name == QLatin1String("landingsites")) { m_placemarkLayer.setShowLandingSites( show ); } else if (name == QLatin1String("craters")) { m_placemarkLayer.setShowCraters( show ); } else if (name == QLatin1String("maria")) { m_placemarkLayer.setShowMaria( show ); } else if (name == QLatin1String("relief")) { m_textureLayer.setShowRelief( show ); } for(RenderPlugin *renderPlugin: m_renderPlugins) { if ( name == renderPlugin->nameId() ) { if ( renderPlugin->visible() == show ) { break; } renderPlugin->setVisible( show ); break; } } } void MarbleMapPrivate::addPlugins() { for (const RenderPlugin *factory: m_model->pluginManager()->renderPlugins()) { bool alreadyCreated = false; for(const RenderPlugin *existing: m_renderPlugins) { if (existing->nameId() == factory->nameId()) { alreadyCreated = true; break; } } if (alreadyCreated) { continue; } RenderPlugin *const renderPlugin = factory->newInstance(m_model); Q_ASSERT(renderPlugin && "Plugin must not return null when requesting a new instance."); m_renderPlugins << renderPlugin; if (AbstractFloatItem *const floatItem = qobject_cast(renderPlugin)) { m_floatItemsLayer.addFloatItem(floatItem); } else { m_layerManager.addRenderPlugin(renderPlugin); } } } // ---------------------------------------------------------------- MarbleMap::MarbleMap() : d( new MarbleMapPrivate( this, new MarbleModel( this ) ) ) { // nothing to do } MarbleMap::MarbleMap(MarbleModel *model) : d( new MarbleMapPrivate( this, model ) ) { d->m_modelIsOwned = false; } MarbleMap::~MarbleMap() { MarbleModel *model = d->m_modelIsOwned ? d->m_model : 0; d->m_layerManager.removeLayer( &d->m_customPaintLayer ); d->m_layerManager.removeLayer( &d->m_geometryLayer ); d->m_layerManager.removeLayer(&d->m_floatItemsLayer); d->m_layerManager.removeLayer( &d->m_fogLayer ); d->m_layerManager.removeLayer( &d->m_placemarkLayer ); d->m_layerManager.removeLayer( &d->m_textureLayer ); d->m_layerManager.removeLayer( &d->m_groundLayer ); qDeleteAll(d->m_renderPlugins); delete d; delete model; // delete the model after private data } MarbleModel *MarbleMap::model() const { return d->m_model; } ViewportParams *MarbleMap::viewport() { return &d->m_viewport; } const ViewportParams *MarbleMap::viewport() const { return &d->m_viewport; } void MarbleMap::setMapQualityForViewContext( MapQuality quality, ViewContext viewContext ) { d->m_viewParams.setMapQualityForViewContext( quality, viewContext ); // Update texture map during the repaint that follows: d->m_textureLayer.setNeedsUpdate(); } MapQuality MarbleMap::mapQuality( ViewContext viewContext ) const { return d->m_viewParams.mapQuality( viewContext ); } MapQuality MarbleMap::mapQuality() const { return d->m_viewParams.mapQuality(); } void MarbleMap::setViewContext( ViewContext viewContext ) { if ( d->m_viewParams.viewContext() == viewContext ) { return; } const MapQuality oldQuality = d->m_viewParams.mapQuality(); d->m_viewParams.setViewContext( viewContext ); emit viewContextChanged( viewContext ); if ( d->m_viewParams.mapQuality() != oldQuality ) { // Update texture map during the repaint that follows: d->m_textureLayer.setNeedsUpdate(); emit repaintNeeded(); } } ViewContext MarbleMap::viewContext() const { return d->m_viewParams.viewContext(); } void MarbleMap::setSize( int width, int height ) { setSize( QSize( width, height ) ); } void MarbleMap::setSize( const QSize& size ) { d->m_viewport.setSize( size ); emit visibleLatLonAltBoxChanged( d->m_viewport.viewLatLonAltBox() ); } QSize MarbleMap::size() const { return QSize( d->m_viewport.width(), d->m_viewport.height() ); } int MarbleMap::width() const { return d->m_viewport.width(); } int MarbleMap::height() const { return d->m_viewport.height(); } int MarbleMap::radius() const { return d->m_viewport.radius(); } void MarbleMap::setRadius( int radius ) { const int oldRadius = d->m_viewport.radius(); d->m_viewport.setRadius( radius ); if ( oldRadius != d->m_viewport.radius() ) { emit radiusChanged( radius ); emit visibleLatLonAltBoxChanged( d->m_viewport.viewLatLonAltBox() ); } } -int MarbleMap::preferredRadiusCeil( int radius ) +int MarbleMap::preferredRadiusCeil(int radius) const { return d->m_textureLayer.preferredRadiusCeil( radius ); } -int MarbleMap::preferredRadiusFloor( int radius ) +int MarbleMap::preferredRadiusFloor(int radius) const { return d->m_textureLayer.preferredRadiusFloor( radius ); } int MarbleMap::tileZoomLevel() const { auto const tileZoomLevel = qMax(d->m_textureLayer.tileZoomLevel(), d->m_vectorTileLayer.tileZoomLevel()); return tileZoomLevel >= 0 ? tileZoomLevel : qMin(qMax(qLn(d->m_viewport.radius()*4/256)/qLn(2.0), 1), d->m_styleBuilder.maximumZoomLevel()); } qreal MarbleMap::centerLatitude() const { // Calculate translation of center point const qreal centerLat = d->m_viewport.centerLatitude(); return centerLat * RAD2DEG; } bool MarbleMap::hasFeatureAt(const QPoint &position) const { return d->m_placemarkLayer.hasPlacemarkAt(position) || d->m_geometryLayer.hasFeatureAt(position, viewport()); } qreal MarbleMap::centerLongitude() const { // Calculate translation of center point const qreal centerLon = d->m_viewport.centerLongitude(); return centerLon * RAD2DEG; } int MarbleMap::minimumZoom() const { if ( d->m_model->mapTheme() ) return d->m_model->mapTheme()->head()->zoom()->minimum(); return 950; } int MarbleMap::maximumZoom() const { if ( d->m_model->mapTheme() ) return d->m_model->mapTheme()->head()->zoom()->maximum(); return 2100; } bool MarbleMap::discreteZoom() const { if ( d->m_model->mapTheme() ) return d->m_model->mapTheme()->head()->zoom()->discrete(); return false; } QVector MarbleMap::whichFeatureAt( const QPoint& curpos ) const { return d->m_placemarkLayer.whichPlacemarkAt( curpos ) + d->m_geometryLayer.whichFeatureAt( curpos, viewport() ); } void MarbleMap::reload() { d->m_textureLayer.reload(); d->m_vectorTileLayer.reload(); } void MarbleMap::downloadRegion( QVector const & pyramid ) { Q_ASSERT( textureLayer() ); Q_ASSERT( !pyramid.isEmpty() ); QTime t; t.start(); // When downloading a region (the author of these lines thinks) most users probably expect // the download to begin with the low resolution tiles and then procede level-wise to // higher resolution tiles. In order to achieve this, we start requesting downloads of // high resolution tiles and request the low resolution tiles at the end because // DownloadQueueSet (silly name) is implemented as stack. int const first = 0; int tilesCount = 0; for ( int level = pyramid[first].bottomLevel(); level >= pyramid[first].topLevel(); --level ) { QSet tileIdSet; for( int i = 0; i < pyramid.size(); ++i ) { QRect const coords = pyramid[i].coords( level ); mDebug() << "MarbleMap::downloadRegion level:" << level << "tile coords:" << coords; int x1, y1, x2, y2; coords.getCoords( &x1, &y1, &x2, &y2 ); for ( int x = x1; x <= x2; ++x ) { for ( int y = y1; y <= y2; ++y ) { TileId const stackedTileId( 0, level, x, y ); tileIdSet.insert( stackedTileId ); // FIXME: use lazy evaluation to not generate up to 100k tiles in one go // this can take considerable time even on very fast systems // in contrast generating the TileIds on the fly when they are needed // does not seem to affect download speed. } } } QSetIterator i( tileIdSet ); while( i.hasNext() ) { TileId const tileId = i.next(); d->m_textureLayer.downloadStackedTile( tileId ); } tilesCount += tileIdSet.count(); } // Needed for downloading unique tiles only. Much faster than if tiles for each level is downloaded separately int const elapsedMs = t.elapsed(); mDebug() << "MarbleMap::downloadRegion:" << tilesCount << "tiles, " << elapsedMs << "ms"; } void MarbleMap::highlightRouteRelation(qint64 osmId, bool enabled) { d->m_geometryLayer.highlightRouteRelation(osmId, enabled); } bool MarbleMap::propertyValue( const QString& name ) const { bool value; if ( d->m_model->mapTheme() ) { d->m_model->mapTheme()->settings()->propertyValue( name, value ); } else { value = false; mDebug() << "WARNING: Failed to access a map theme! Property: " << name; } return value; } bool MarbleMap::showOverviewMap() const { return propertyValue(QStringLiteral("overviewmap")); } bool MarbleMap::showScaleBar() const { return propertyValue(QStringLiteral("scalebar")); } bool MarbleMap::showCompass() const { return propertyValue(QStringLiteral("compass")); } bool MarbleMap::showGrid() const { return propertyValue(QStringLiteral("coordinate-grid")); } bool MarbleMap::showClouds() const { return d->m_viewParams.showClouds(); } bool MarbleMap::showSunShading() const { return d->m_textureLayer.showSunShading(); } bool MarbleMap::showCityLights() const { return d->m_textureLayer.showCityLights(); } bool MarbleMap::isLockedToSubSolarPoint() const { return d->m_isLockedToSubSolarPoint; } bool MarbleMap::isSubSolarPointIconVisible() const { return d->m_isSubSolarPointIconVisible; } bool MarbleMap::showAtmosphere() const { return d->m_viewParams.showAtmosphere(); } bool MarbleMap::showCrosshairs() const { bool visible = false; QList pluginList = renderPlugins(); QList::const_iterator i = pluginList.constBegin(); QList::const_iterator const end = pluginList.constEnd(); for (; i != end; ++i ) { if ((*i)->nameId() == QLatin1String("crosshairs")) { visible = (*i)->visible(); } } return visible; } bool MarbleMap::showPlaces() const { return propertyValue(QStringLiteral("places")); } bool MarbleMap::showCities() const { return propertyValue(QStringLiteral("cities")); } bool MarbleMap::showTerrain() const { return propertyValue(QStringLiteral("terrain")); } bool MarbleMap::showOtherPlaces() const { return propertyValue(QStringLiteral("otherplaces")); } bool MarbleMap::showRelief() const { return propertyValue(QStringLiteral("relief")); } bool MarbleMap::showIceLayer() const { return propertyValue(QStringLiteral("ice")); } bool MarbleMap::showBorders() const { return propertyValue(QStringLiteral("borders")); } bool MarbleMap::showRivers() const { return propertyValue(QStringLiteral("rivers")); } bool MarbleMap::showLakes() const { return propertyValue(QStringLiteral("lakes")); } bool MarbleMap::showFrameRate() const { return d->m_showFrameRate; } bool MarbleMap::showBackground() const { return d->m_layerManager.showBackground(); } GeoDataRelation::RelationTypes MarbleMap::visibleRelationTypes() const { return d->m_visibleRelationTypes; } quint64 MarbleMap::volatileTileCacheLimit() const { return d->m_textureLayer.volatileCacheLimit(); } void MarbleMap::rotateBy(qreal deltaLon, qreal deltaLat) { centerOn( d->m_viewport.centerLongitude() * RAD2DEG + deltaLon, d->m_viewport.centerLatitude() * RAD2DEG + deltaLat ); } void MarbleMap::centerOn( const qreal lon, const qreal lat ) { d->m_viewport.centerOn( lon * DEG2RAD, lat * DEG2RAD ); emit visibleLatLonAltBoxChanged( d->m_viewport.viewLatLonAltBox() ); } void MarbleMap::setCenterLatitude( qreal lat ) { centerOn( centerLongitude(), lat ); } void MarbleMap::setCenterLongitude( qreal lon ) { centerOn( lon, centerLatitude() ); } Projection MarbleMap::projection() const { return d->m_viewport.projection(); } void MarbleMap::setProjection( Projection projection ) { if ( d->m_viewport.projection() == projection ) return; emit projectionChanged( projection ); d->m_viewport.setProjection( projection ); d->m_textureLayer.setProjection( projection ); emit visibleLatLonAltBoxChanged( d->m_viewport.viewLatLonAltBox() ); } bool MarbleMap::screenCoordinates( qreal lon, qreal lat, qreal& x, qreal& y ) const { return d->m_viewport.screenCoordinates( lon * DEG2RAD, lat * DEG2RAD, x, y ); } bool MarbleMap::geoCoordinates( int x, int y, qreal& lon, qreal& lat, GeoDataCoordinates::Unit unit ) const { return d->m_viewport.geoCoordinates( x, y, lon, lat, unit ); } void MarbleMapPrivate::setDocument( const QString& key ) { if ( !m_model->mapTheme() ) { // Happens if no valid map theme is set or at application startup // if a file is passed via command line parameters and the last // map theme has not been loaded yet /** * @todo Do we need to queue the document and process it once a map * theme becomes available? */ return; } GeoDataDocument* doc = m_model->fileManager()->at( key ); for ( const GeoSceneLayer *layer: m_model->mapTheme()->map()->layers() ) { if ( layer->backend() != dgml::dgmlValue_geodata && layer->backend() != dgml::dgmlValue_vector ) continue; // look for documents for ( const GeoSceneAbstractDataset *dataset: layer->datasets() ) { const GeoSceneGeodata *data = static_cast( dataset ); QString containername = data->sourceFile(); QString colorize = data->colorize(); if( key == containername ) { if (colorize == QLatin1String("land")) { m_textureLayer.addLandDocument( doc ); } if (colorize == QLatin1String("sea")) { m_textureLayer.addSeaDocument( doc ); } // set visibility according to theme property if( !data->property().isEmpty() ) { bool value; m_model->mapTheme()->settings()->propertyValue( data->property(), value ); doc->setVisible( value ); m_model->treeModel()->updateFeature( doc ); } } } } } void MarbleMapPrivate::updateTileLevel() { auto const tileZoomLevel = q->tileZoomLevel(); m_geometryLayer.setTileLevel(tileZoomLevel); m_placemarkLayer.setTileLevel(tileZoomLevel); emit q->tileLevelChanged(tileZoomLevel); } // Used to be paintEvent() void MarbleMap::paint( GeoPainter &painter, const QRect &dirtyRect ) { Q_UNUSED( dirtyRect ); if (d->m_showDebugPolygons ) { if (viewContext() == Animation) { painter.setDebugPolygonsLevel(1); } else { painter.setDebugPolygonsLevel(2); } } painter.setDebugBatchRender(d->m_showDebugBatchRender); if ( !d->m_model->mapTheme() ) { mDebug() << "No theme yet!"; d->m_marbleSplashLayer.render( &painter, &d->m_viewport ); return; } QTime t; t.start(); RenderStatus const oldRenderStatus = d->m_renderState.status(); d->m_layerManager.renderLayers( &painter, &d->m_viewport ); d->m_renderState = d->m_layerManager.renderState(); bool const parsing = d->m_model->fileManager()->pendingFiles() > 0; d->m_renderState.addChild(RenderState(QStringLiteral("Files"), parsing ? WaitingForData : Complete)); RenderStatus const newRenderStatus = d->m_renderState.status(); if ( oldRenderStatus != newRenderStatus ) { emit renderStatusChanged( newRenderStatus ); } emit renderStateChanged( d->m_renderState ); if ( d->m_showFrameRate ) { FpsLayer fpsPainter( &t ); fpsPainter.paint( &painter ); } const qreal fps = 1000.0 / (qreal)( t.elapsed() ); emit framesPerSecond( fps ); } void MarbleMap::customPaint( GeoPainter *painter ) { Q_UNUSED( painter ); } QString MarbleMap::mapThemeId() const { return d->m_model->mapThemeId(); } void MarbleMap::setMapThemeId( const QString& mapThemeId ) { d->m_model->setMapThemeId( mapThemeId ); } void MarbleMapPrivate::updateMapTheme() { m_layerManager.removeLayer( &m_textureLayer ); // FIXME Find a better way to do this reset. Maybe connect to themeChanged SIGNAL? m_vectorTileLayer.reset(); m_layerManager.removeLayer( &m_vectorTileLayer ); m_layerManager.removeLayer( &m_groundLayer ); QObject::connect( m_model->mapTheme()->settings(), SIGNAL(valueChanged(QString,bool)), q, SLOT(updateProperty(QString,bool)) ); QObject::connect( m_model->mapTheme()->settings(), SIGNAL(valueChanged(QString,bool)), m_model, SLOT(updateProperty(QString,bool)) ); q->setPropertyValue(QStringLiteral("clouds_data"), m_viewParams.showClouds()); m_groundLayer.setColor( m_model->mapTheme()->map()->backgroundColor() ); // Check whether there is a texture layer and vectortile layer available: if ( m_model->mapTheme()->map()->hasTextureLayers() ) { const GeoSceneSettings *const settings = m_model->mapTheme()->settings(); const GeoSceneGroup *const textureLayerSettings = settings ? settings->group( "Texture Layers" ) : 0; const GeoSceneGroup *const vectorTileLayerSettings = settings ? settings->group( "VectorTile Layers" ) : 0; bool textureLayersOk = true; bool vectorTileLayersOk = true; // textures will contain texture layers and // vectorTiles vectortile layers QVector textures; QVector vectorTiles; for( GeoSceneLayer* layer: m_model->mapTheme()->map()->layers() ){ if ( layer->backend() == dgml::dgmlValue_texture ){ for ( const GeoSceneAbstractDataset *pos: layer->datasets() ) { const GeoSceneTextureTileDataset *const texture = dynamic_cast( pos ); if ( !texture ) continue; const QString sourceDir = texture->sourceDir(); const QString installMap = texture->installMap(); const QString role = layer->role(); // If the tiles aren't already there, put up a progress dialog // while creating them. if ( !TileLoader::baseTilesAvailable( *texture ) && !installMap.isEmpty() ) { mDebug() << "Base tiles not available. Creating Tiles ... \n" << "SourceDir: " << sourceDir << "InstallMap:" << installMap; TileCreator *tileCreator = new TileCreator( sourceDir, installMap, (role == QLatin1String("dem")) ? "true" : "false" ); tileCreator->setTileFormat( texture->fileFormat().toLower() ); QPointer tileCreatorDlg = new TileCreatorDialog( tileCreator, 0 ); tileCreatorDlg->setSummary( m_model->mapTheme()->head()->name(), m_model->mapTheme()->head()->description() ); tileCreatorDlg->exec(); if ( TileLoader::baseTilesAvailable( *texture ) ) { mDebug() << "Base tiles for" << sourceDir << "successfully created."; } else { qWarning() << "Some or all base tiles for" << sourceDir << "could not be created."; } delete tileCreatorDlg; } if ( TileLoader::baseTilesAvailable( *texture ) ) { textures.append( texture ); } else { qWarning() << "Base tiles for" << sourceDir << "not available. Skipping all texture layers."; textureLayersOk = false; } } } else if ( layer->backend() == dgml::dgmlValue_vectortile ){ for ( const GeoSceneAbstractDataset *pos: layer->datasets() ) { const GeoSceneVectorTileDataset *const vectorTile = dynamic_cast( pos ); if ( !vectorTile ) continue; const QString sourceDir = vectorTile->sourceDir(); const QString installMap = vectorTile->installMap(); const QString role = layer->role(); // If the tiles aren't already there, put up a progress dialog // while creating them. if ( !TileLoader::baseTilesAvailable( *vectorTile ) && !installMap.isEmpty() ) { mDebug() << "Base tiles not available. Creating Tiles ... \n" << "SourceDir: " << sourceDir << "InstallMap:" << installMap; TileCreator *tileCreator = new TileCreator( sourceDir, installMap, (role == QLatin1String("dem")) ? "true" : "false" ); tileCreator->setTileFormat( vectorTile->fileFormat().toLower() ); QPointer tileCreatorDlg = new TileCreatorDialog( tileCreator, 0 ); tileCreatorDlg->setSummary( m_model->mapTheme()->head()->name(), m_model->mapTheme()->head()->description() ); tileCreatorDlg->exec(); if ( TileLoader::baseTilesAvailable( *vectorTile ) ) { qDebug() << "Base tiles for" << sourceDir << "successfully created."; } else { qDebug() << "Some or all base tiles for" << sourceDir << "could not be created."; } delete tileCreatorDlg; } if ( TileLoader::baseTilesAvailable( *vectorTile ) ) { vectorTiles.append( vectorTile ); } else { qWarning() << "Base tiles for" << sourceDir << "not available. Skipping all texture layers."; vectorTileLayersOk = false; } } } } QString seafile, landfile; if( !m_model->mapTheme()->map()->filters().isEmpty() ) { const GeoSceneFilter *filter= m_model->mapTheme()->map()->filters().first(); if (filter->type() == QLatin1String("colorize")) { //no need to look up with MarbleDirs twice so they are left null for now QList palette = filter->palette(); for (const GeoScenePalette *curPalette: palette ) { if (curPalette->type() == QLatin1String("sea")) { seafile = MarbleDirs::path( curPalette->file() ); } else if (curPalette->type() == QLatin1String("land")) { landfile = MarbleDirs::path( curPalette->file() ); } } //look up locations if they are empty if( seafile.isEmpty() ) seafile = MarbleDirs::path(QStringLiteral("seacolors.leg")); if( landfile.isEmpty() ) landfile = MarbleDirs::path(QStringLiteral("landcolors.leg")); } } m_textureLayer.setMapTheme( textures, textureLayerSettings, seafile, landfile ); m_textureLayer.setProjection( m_viewport.projection() ); m_textureLayer.setShowRelief( q->showRelief() ); m_vectorTileLayer.setMapTheme( vectorTiles, vectorTileLayerSettings ); if (m_textureLayer.textureLayerCount() == 0) { m_layerManager.addLayer( &m_groundLayer ); } if ( textureLayersOk ) m_layerManager.addLayer( &m_textureLayer ); if ( vectorTileLayersOk && !vectorTiles.isEmpty() ) m_layerManager.addLayer( &m_vectorTileLayer ); } else { m_layerManager.addLayer( &m_groundLayer ); m_textureLayer.setMapTheme( QVector(), 0, "", "" ); m_vectorTileLayer.setMapTheme( QVector(), 0 ); } // earth m_placemarkLayer.setShowPlaces( q->showPlaces() ); m_placemarkLayer.setShowCities( q->showCities() ); m_placemarkLayer.setShowTerrain( q->showTerrain() ); m_placemarkLayer.setShowOtherPlaces( q->showOtherPlaces() ); m_placemarkLayer.setShowLandingSites(q->propertyValue(QStringLiteral("landingsites"))); m_placemarkLayer.setShowCraters(q->propertyValue(QStringLiteral("craters"))); m_placemarkLayer.setShowMaria(q->propertyValue(QStringLiteral("maria"))); m_styleBuilder.setDefaultLabelColor(m_model->mapTheme()->map()->labelColor()); m_placemarkLayer.requestStyleReset(); for (RenderPlugin *renderPlugin: m_renderPlugins) { bool propertyAvailable = false; m_model->mapTheme()->settings()->propertyAvailable( renderPlugin->nameId(), propertyAvailable ); bool propertyValue = false; m_model->mapTheme()->settings()->propertyValue( renderPlugin->nameId(), propertyValue ); if ( propertyAvailable ) { renderPlugin->setVisible( propertyValue ); } } emit q->themeChanged( m_model->mapTheme()->head()->mapThemeId() ); } void MarbleMap::setPropertyValue( const QString& name, bool value ) { mDebug() << "In MarbleMap the property " << name << "was set to " << value; if ( d->m_model->mapTheme() ) { d->m_model->mapTheme()->settings()->setPropertyValue( name, value ); d->m_textureLayer.setNeedsUpdate(); } else { mDebug() << "WARNING: Failed to access a map theme! Property: " << name; } if (d->m_textureLayer.textureLayerCount() == 0) { d->m_layerManager.addLayer( &d->m_groundLayer ); } else { d->m_layerManager.removeLayer( &d->m_groundLayer ); } } void MarbleMap::setShowOverviewMap( bool visible ) { setPropertyValue(QStringLiteral("overviewmap"), visible); } void MarbleMap::setShowScaleBar( bool visible ) { setPropertyValue(QStringLiteral("scalebar"), visible); } void MarbleMap::setShowCompass( bool visible ) { setPropertyValue(QStringLiteral("compass"), visible); } void MarbleMap::setShowAtmosphere( bool visible ) { for ( RenderPlugin *plugin: renderPlugins() ) { if (plugin->nameId() == QLatin1String("atmosphere")) { plugin->setVisible( visible ); } } d->m_viewParams.setShowAtmosphere( visible ); } void MarbleMap::setShowCrosshairs( bool visible ) { QList pluginList = renderPlugins(); QList::const_iterator i = pluginList.constBegin(); QList::const_iterator const end = pluginList.constEnd(); for (; i != end; ++i ) { if ((*i)->nameId() == QLatin1String("crosshairs")) { (*i)->setVisible( visible ); } } } void MarbleMap::setShowClouds( bool visible ) { d->m_viewParams.setShowClouds( visible ); setPropertyValue(QStringLiteral("clouds_data"), visible); } void MarbleMap::setShowSunShading( bool visible ) { d->m_textureLayer.setShowSunShading( visible ); } void MarbleMap::setShowCityLights( bool visible ) { d->m_textureLayer.setShowCityLights( visible ); setPropertyValue(QStringLiteral("citylights"), visible); } void MarbleMap::setLockToSubSolarPoint( bool visible ) { disconnect( d->m_model->sunLocator(), SIGNAL(positionChanged(qreal,qreal)), this, SLOT(centerOn(qreal,qreal)) ); if( isLockedToSubSolarPoint() != visible ) { d->m_isLockedToSubSolarPoint = visible; } if ( isLockedToSubSolarPoint() ) { connect( d->m_model->sunLocator(), SIGNAL(positionChanged(qreal,qreal)), this, SLOT(centerOn(qreal,qreal)) ); centerOn( d->m_model->sunLocator()->getLon(), d->m_model->sunLocator()->getLat() ); } else if ( visible ) { mDebug() << "Ignoring centering on sun, since the sun plugin is not loaded."; } } void MarbleMap::setSubSolarPointIconVisible( bool visible ) { if ( isSubSolarPointIconVisible() != visible ) { d->m_isSubSolarPointIconVisible = visible; } } void MarbleMap::setShowTileId( bool visible ) { d->m_textureLayer.setShowTileId( visible ); } void MarbleMap::setShowGrid( bool visible ) { setPropertyValue(QStringLiteral("coordinate-grid"), visible); } void MarbleMap::setShowPlaces( bool visible ) { setPropertyValue(QStringLiteral("places"), visible); } void MarbleMap::setShowCities( bool visible ) { setPropertyValue(QStringLiteral("cities"), visible); } void MarbleMap::setShowTerrain( bool visible ) { setPropertyValue(QStringLiteral("terrain"), visible); } void MarbleMap::setShowOtherPlaces( bool visible ) { setPropertyValue(QStringLiteral("otherplaces"), visible); } void MarbleMap::setShowRelief( bool visible ) { setPropertyValue(QStringLiteral("relief"), visible); } void MarbleMap::setShowIceLayer( bool visible ) { setPropertyValue(QStringLiteral("ice"), visible); } void MarbleMap::setShowBorders( bool visible ) { setPropertyValue(QStringLiteral("borders"), visible); } void MarbleMap::setShowRivers( bool visible ) { setPropertyValue(QStringLiteral("rivers"), visible); } void MarbleMap::setShowLakes( bool visible ) { setPropertyValue(QStringLiteral("lakes"), visible); } void MarbleMap::setShowFrameRate( bool visible ) { d->m_showFrameRate = visible; } void MarbleMap::setShowRuntimeTrace( bool visible ) { if (visible != d->m_layerManager.showRuntimeTrace()) { d->m_layerManager.setShowRuntimeTrace(visible); emit repaintNeeded(); } } bool MarbleMap::showRuntimeTrace() const { return d->m_layerManager.showRuntimeTrace(); } void MarbleMap::setShowDebugPolygons( bool visible) { if (visible != d->m_showDebugPolygons) { d->m_showDebugPolygons = visible; emit repaintNeeded(); } } bool MarbleMap::showDebugPolygons() const { return d->m_showDebugPolygons; } void MarbleMap::setShowDebugBatchRender( bool visible) { qDebug() << Q_FUNC_INFO << visible; if (visible != d->m_showDebugBatchRender) { d->m_showDebugBatchRender = visible; emit repaintNeeded(); } } bool MarbleMap::showDebugBatchRender() const { return d->m_showDebugBatchRender; } void MarbleMap::setShowDebugPlacemarks( bool visible) { if (visible != d->m_placemarkLayer.isDebugModeEnabled()) { d->m_placemarkLayer.setDebugModeEnabled(visible); emit repaintNeeded(); } } bool MarbleMap::showDebugPlacemarks() const { return d->m_placemarkLayer.isDebugModeEnabled(); } void MarbleMap::setLevelTagDebugModeEnabled(bool visible) { if (visible != d->m_geometryLayer.levelTagDebugModeEnabled()) { d->m_geometryLayer.setLevelTagDebugModeEnabled(visible); d->m_placemarkLayer.setLevelTagDebugModeEnabled(visible); emit repaintNeeded(); } } bool MarbleMap::levelTagDebugModeEnabled() const { return d->m_geometryLayer.levelTagDebugModeEnabled() && d->m_placemarkLayer.levelTagDebugModeEnabled(); } void MarbleMap::setDebugLevelTag(int level) { d->m_geometryLayer.setDebugLevelTag(level); d->m_placemarkLayer.setDebugLevelTag(level); } int MarbleMap::debugLevelTag() const { return d->m_geometryLayer.debugLevelTag(); } void MarbleMap::setShowBackground( bool visible ) { d->m_layerManager.setShowBackground( visible ); } void MarbleMap::setVisibleRelationTypes(GeoDataRelation::RelationTypes relationTypes) { if (d->m_visibleRelationTypes != relationTypes) { d->m_visibleRelationTypes = relationTypes; d->m_geometryLayer.setVisibleRelationTypes(relationTypes); emit visibleRelationTypesChanged(d->m_visibleRelationTypes); } } void MarbleMap::notifyMouseClick( int x, int y ) { qreal lon = 0; qreal lat = 0; const bool valid = geoCoordinates( x, y, lon, lat, GeoDataCoordinates::Radian ); if ( valid ) { emit mouseClickGeoPosition( lon, lat, GeoDataCoordinates::Radian ); } } void MarbleMap::clearVolatileTileCache() { d->m_vectorTileLayer.reset(); d->m_textureLayer.reset(); mDebug() << "Cleared Volatile Cache!"; } void MarbleMap::setVolatileTileCacheLimit( quint64 kilobytes ) { mDebug() << "kiloBytes" << kilobytes; d->m_textureLayer.setVolatileCacheLimit( kilobytes ); } AngleUnit MarbleMap::defaultAngleUnit() const { if ( GeoDataCoordinates::defaultNotation() == GeoDataCoordinates::Decimal ) { return DecimalDegree; } else if ( GeoDataCoordinates::defaultNotation() == GeoDataCoordinates::UTM ) { return UTM; } return DMSDegree; } void MarbleMap::setDefaultAngleUnit( AngleUnit angleUnit ) { if ( angleUnit == DecimalDegree ) { GeoDataCoordinates::setDefaultNotation( GeoDataCoordinates::Decimal ); return; } else if ( angleUnit == UTM ) { GeoDataCoordinates::setDefaultNotation( GeoDataCoordinates::UTM ); return; } GeoDataCoordinates::setDefaultNotation( GeoDataCoordinates::DMS ); } QFont MarbleMap::defaultFont() const { return d->m_styleBuilder.defaultFont(); } void MarbleMap::setDefaultFont( const QFont& font ) { d->m_styleBuilder.setDefaultFont(font); d->m_placemarkLayer.requestStyleReset(); } QList MarbleMap::renderPlugins() const { return d->m_renderPlugins; } QList MarbleMap::floatItems() const { return d->m_floatItemsLayer.floatItems(); } AbstractFloatItem * MarbleMap::floatItem( const QString &nameId ) const { for ( AbstractFloatItem * floatItem: floatItems() ) { if ( floatItem && floatItem->nameId() == nameId ) { return floatItem; } } return 0; // No item found } QList MarbleMap::dataPlugins() const { return d->m_layerManager.dataPlugins(); } QList MarbleMap::whichItemAt( const QPoint& curpos ) const { return d->m_layerManager.whichItemAt( curpos ); } void MarbleMap::addLayer( LayerInterface *layer ) { d->m_layerManager.addLayer(layer); } void MarbleMap::removeLayer( LayerInterface *layer ) { d->m_layerManager.removeLayer(layer); } RenderStatus MarbleMap::renderStatus() const { return d->m_layerManager.renderState().status(); } RenderState MarbleMap::renderState() const { return d->m_layerManager.renderState(); } QString MarbleMap::addTextureLayer(GeoSceneTextureTileDataset *texture) { return textureLayer()->addTextureLayer(texture); } void MarbleMap::removeTextureLayer(const QString &key) { textureLayer()->removeTextureLayer(key); } // this method will only temporarily "pollute" the MarbleModel class TextureLayer *MarbleMap::textureLayer() const { return &d->m_textureLayer; } const StyleBuilder* MarbleMap::styleBuilder() const { return &d->m_styleBuilder; } qreal MarbleMap::heading() const { return d->m_viewport.heading() * RAD2DEG; } void MarbleMap::setHeading( qreal heading ) { d->m_viewport.setHeading( heading * DEG2RAD ); d->m_textureLayer.setNeedsUpdate(); emit visibleLatLonAltBoxChanged( d->m_viewport.viewLatLonAltBox() ); } } #include "moc_MarbleMap.cpp" diff --git a/src/lib/marble/MarbleMap.h b/src/lib/marble/MarbleMap.h index 19ae1be78..a10b9f29a 100644 --- a/src/lib/marble/MarbleMap.h +++ b/src/lib/marble/MarbleMap.h @@ -1,809 +1,809 @@ // // This file is part of the Marble Virtual Globe. // // This program is free software licensed under the GNU LGPL. You can // find a copy of this license in LICENSE.txt in the top directory of // the source code. // // Copyright 2006-2008 Torsten Rahn // Copyright 2007 Inge Wallin // Copyright 2009 Jens-Michael Hoffmann // #ifndef MARBLE_MARBLEMAP_H #define MARBLE_MARBLEMAP_H /** @file * This file contains the headers for MarbleMap. * * @author Torsten Rahn * @author Inge Wallin */ #include "marble_export.h" #include "GeoDataCoordinates.h" // In geodata/data/ #include "GeoDataRelation.h" // Qt #include #include class QFont; class QString; namespace Marble { // MarbleMap class MarbleMapPrivate; // Marble class GeoDataLatLonAltBox; class GeoDataFeature; class MarbleModel; class ViewportParams; class GeoPainter; class LayerInterface; class RenderPlugin; class RenderState; class AbstractDataPlugin; class AbstractDataPluginItem; class AbstractFloatItem; class TextureLayer; class TileCoordsPyramid; class GeoSceneTextureTileDataset; class StyleBuilder; /** * @short A class that can paint a view of the earth. * * FIXME: Change this description when we are done. * * This class can paint a view of the earth or any other globe, * depending on which dataset is used. It can be used to show the * globe in a widget like MarbleWidget does, or on any other * QPaintDevice. * * The projection and other view parameters that control how MarbleMap * paints the map is given through the class ViewParams. If the * programmer wants to allow the user to control the map, he/she has * to provide a way for the user to interact with it. An example of * this can be seen in the class MarbleWidgetInputHandler, that lets * the user control a MarbleWidget that uses MarbleMap internally. * * The MarbleMap needs to be provided with a data model to * work. This model is contained in the MarbleModel class. The widget * can also construct its own model if none is given to the * constructor. This data model contains 3 separate datatypes: * tiles which provide the background, vectors which * provide things like country borders and coastlines and * placemarks which can show points of interest, such as * cities, mountain tops or the poles. * * @see MarbleWidget * @see MarbleModel */ class MARBLE_EXPORT MarbleMap : public QObject { Q_OBJECT Q_CLASSINFO("D-Bus Interface", "org.kde.MarbleMap") public: friend class MarbleWidget; /** * @brief Construct a new MarbleMap. * * This constructor should be used when you will only use one * MarbleMap. The widget will create its own MarbleModel when * created. */ MarbleMap(); /** * @brief Construct a new MarbleMap. * @param model the data model for the widget. * * This constructor should be used when you plan to use more than * one MarbleMap for the same MarbleModel (not yet supported, * but will be soon). */ explicit MarbleMap( MarbleModel *model ); ~MarbleMap() override; /** * @brief Return the model that this view shows. */ MarbleModel *model() const; // Accessors to internal objects; ViewportParams *viewport(); const ViewportParams *viewport() const; /** * @brief Get the Projection used for the map * @return @c Spherical a Globe * @return @c Equirectangular a flat map * @return @c Mercator another flat map */ Projection projection() const; /** * @brief Get the ID of the current map theme * To ensure that a unique identifier is being used the theme does NOT * get represented by its name but the by relative location of the file * that specifies the theme: * * Example: * maptheme = "earth/bluemarble/bluemarble.dgml" */ QString mapThemeId() const; void setMapQualityForViewContext( MapQuality qualityForViewContext, ViewContext viewContext ); MapQuality mapQuality( ViewContext viewContext ) const; /** * @brief Return the current map quality. */ MapQuality mapQuality() const; void setViewContext( ViewContext viewContext ); ViewContext viewContext() const; void setSize( int width, int height ); void setSize( const QSize& size ); QSize size() const; int width() const; int height() const; /** * @brief Return the radius of the globe in pixels. */ int radius() const; - int preferredRadiusCeil( int radius ); - int preferredRadiusFloor( int radius ); + int preferredRadiusCeil(int radius) const; + int preferredRadiusFloor(int radius) const; int tileZoomLevel() const; /** * @brief return the minimum zoom value for the current map theme. */ int minimumZoom() const; /** * @brief return the minimum zoom value for the current map theme. */ int maximumZoom() const; bool discreteZoom() const; /** * @brief Get the screen coordinates corresponding to geographical coordinates in the map. * @param lon the lon coordinate of the requested pixel position * @param lat the lat coordinate of the requested pixel position * @param x the x coordinate of the pixel is returned through this parameter * @param y the y coordinate of the pixel is returned through this parameter * @return @c true if the geographical coordinates are visible on the screen * @c false if the geographical coordinates are not visible on the screen */ bool screenCoordinates( qreal lon, qreal lat, qreal& x, qreal& y ) const; /** * @brief Get the earth coordinates corresponding to a pixel in the map. * @param x the x coordinate of the pixel * @param y the y coordinate of the pixel * @param lon the longitude angle is returned through this parameter * @param lat the latitude angle is returned through this parameter * @return @c true if the pixel (x, y) is within the globe * @c false if the pixel (x, y) is outside the globe, i.e. in space. */ bool geoCoordinates( int x, int y, qreal& lon, qreal& lat, GeoDataCoordinates::Unit = GeoDataCoordinates::Degree ) const; /** * @brief Return the longitude of the center point. * @return The longitude of the center point in degree. */ qreal centerLongitude() const; /** * @brief Return the latitude of the center point. * @return The latitude of the center point in degree. */ qreal centerLatitude() const; qreal heading() const; /** * @since 0.26.0 */ bool hasFeatureAt(const QPoint&) const; QVector whichFeatureAt( const QPoint& ) const; /** * @brief Return the property value by name. * @return The property value (usually: visibility). */ bool propertyValue( const QString& name ) const; /** * @brief Return whether the overview map is visible. * @return The overview map visibility. */ bool showOverviewMap() const; /** * @brief Return whether the scale bar is visible. * @return The scale bar visibility. */ bool showScaleBar() const; /** * @brief Return whether the compass bar is visible. * @return The compass visibility. */ bool showCompass() const; /** * @brief Return whether the cloud cover is visible. * @return The cloud cover visibility. */ bool showClouds() const; /** * @brief Return whether the night shadow is visible. * @return visibility of night shadow */ bool showSunShading() const; /** * @brief Return whether the city lights are shown instead of the night shadow. * @return visibility of city lights */ bool showCityLights() const; /** * @brief Return whether the globe is locked to the sub solar point * @return if globe is locked to sub solar point */ bool isLockedToSubSolarPoint() const; /** * @brief Return whether the sun icon is shown in the sub solar point. * @return visibility of the sun icon in the sub solar point */ bool isSubSolarPointIconVisible() const; /** * @brief Return whether the atmospheric glow is visible. * @return The cloud cover visibility. */ bool showAtmosphere() const; /** * @brief Return whether the crosshairs are visible. * @return The crosshairs' visibility. */ bool showCrosshairs() const; /** * @brief Return whether the coordinate grid is visible. * @return The coordinate grid visibility. */ bool showGrid() const; /** * @brief Return whether the place marks are visible. * @return The place mark visibility. */ bool showPlaces() const; /** * @brief Return whether the city place marks are visible. * @return The city place mark visibility. */ bool showCities() const; /** * @brief Return whether the terrain place marks are visible. * @return The terrain place mark visibility. */ bool showTerrain() const; /** * @brief Return whether other places are visible. * @return The visibility of other places. */ bool showOtherPlaces() const; /** * @brief Return whether the relief is visible. * @return The relief visibility. */ bool showRelief() const; /** * @brief Return whether the ice layer is visible. * @return The ice layer visibility. */ bool showIceLayer() const; /** * @brief Return whether the borders are visible. * @return The border visibility. */ bool showBorders() const; /** * @brief Return whether the rivers are visible. * @return The rivers' visibility. */ bool showRivers() const; /** * @brief Return whether the lakes are visible. * @return The lakes' visibility. */ bool showLakes() const; /** * @brief Return whether the frame rate gets displayed. * @return the frame rates visibility */ bool showFrameRate() const; bool showBackground() const; GeoDataRelation::RelationTypes visibleRelationTypes() const; /** * @brief Returns the limit in kilobytes of the volatile (in RAM) tile cache. * @return the limit of volatile tile cache in kilobytes. */ quint64 volatileTileCacheLimit() const; /** * @brief Returns a list of all RenderPlugins in the model, this includes float items * @return the list of RenderPlugins */ QList renderPlugins() const; QList floatItems() const; /** * @brief Returns a list of all FloatItems in the model * @return the list of the floatItems */ AbstractFloatItem * floatItem( const QString &nameId ) const; /** * @brief Returns a list of all DataPlugins on the layer * @return the list of DataPlugins */ QList dataPlugins() const; /** * @brief Returns all widgets of dataPlugins on the position curpos */ QList whichItemAt( const QPoint& curpos ) const; AngleUnit defaultAngleUnit() const; QFont defaultFont() const; TextureLayer *textureLayer() const; /** * @brief Add a layer to be included in rendering. */ void addLayer( LayerInterface *layer ); /** * @brief Adds a texture sublayer * @return Returns a key that identifies the texture sublayer */ QString addTextureLayer(GeoSceneTextureTileDataset *texture); /** * @brief Removes a texture sublayer * @param Key that was returned from corresponding addTextureLayer */ void removeTextureLayer(const QString &key); /** * @brief Remove a layer from being included in rendering. */ void removeLayer( LayerInterface *layer ); RenderStatus renderStatus() const; RenderState renderState() const; /** * @since 0.26.0 */ const StyleBuilder* styleBuilder() const; public Q_SLOTS: /** * @brief Paint the map using a give painter. * @param painter The painter to use. * @param dirtyRect the rectangle that actually needs repainting. */ void paint( GeoPainter &painter, const QRect &dirtyRect ); /** * @brief Set the radius of the globe in pixels. * @param radius The new globe radius value in pixels. */ void setRadius( int radius ); void setHeading( qreal heading ); /** * @brief Rotate the view by the two angles phi and theta. * @param deltaLon an angle that specifies the change in terms of longitude * @param deltaLat an angle that specifies the change in terms of latitude * * This function rotates the view by two angles, * deltaLon ("theta") and deltaLat ("phi"). * If we start at (0, 0), the result will be the exact equivalent * of (lon, lat), otherwise the resulting angle will be the sum of * the previous position and the two offsets. */ void rotateBy(qreal deltaLon, qreal deltaLat); /** * @brief Center the view on a geographical point * @param lat an angle parallel to the latitude lines * +90(N) - -90(S) * @param lon an angle parallel to the longitude lines * +180(W) - -180(E) */ void centerOn( const qreal lon, const qreal lat ); /** * @brief Set the latitude for the center point * @param lat the new value for the latitude in degree */ void setCenterLatitude( qreal lat ); /** * @brief Set the longitude for the center point * @param lon the new value for the longitude in degree */ void setCenterLongitude( qreal lon ); /** * @brief Set the Projection used for the map * @param projection projection type (e.g. Spherical, Equirectangular, Mercator) */ void setProjection( Projection projection ); /** * @brief Set a new map theme * @param maptheme The ID of the new maptheme. To ensure that a unique * identifier is being used the theme does NOT get represented by its * name but the by relative location of the file that specifies the theme: * * Example: * maptheme = "earth/bluemarble/bluemarble.dgml" */ void setMapThemeId( const QString& maptheme ); /** * @brief Sets the value of a map theme property * @param value value of the property (usually: visibility) * * Later on we might add a "setPropertyType and a QVariant * if needed. */ void setPropertyValue( const QString& name, bool value ); /** * @brief Set whether the overview map overlay is visible * @param visible visibility of the overview map */ void setShowOverviewMap( bool visible ); /** * @brief Set whether the scale bar overlay is visible * @param visible visibility of the scale bar */ void setShowScaleBar( bool visible ); /** * @brief Set whether the compass overlay is visible * @param visible visibility of the compass */ void setShowCompass( bool visible ); /** * @brief Set whether the cloud cover is visible * @param visible visibility of the cloud cover */ void setShowClouds( bool visible ); /** * @brief Set whether the night shadow is visible. * @param visibile visibility of shadow */ void setShowSunShading( bool visible ); /** * @brief Set whether city lights instead of night shadow are visible. * @param visible visibility of city lights */ void setShowCityLights( bool visible ); /** * @brief Set the globe locked to the sub solar point * @param vsible if globe is locked to the sub solar point */ void setLockToSubSolarPoint( bool visible ); /** * @brief Set whether the sun icon is shown in the sub solar point * @param visible if the sun icon is shown in the sub solar point */ void setSubSolarPointIconVisible( bool visible ); /** * @brief Set whether the is tile is visible * NOTE: This is part of the transitional debug API * and might be subject to changes until Marble 0.8 * @param visible visibility of the tile */ void setShowTileId( bool visible ); /** * @brief Set whether the atmospheric glow is visible * @param visible visibility of the atmospheric glow */ void setShowAtmosphere( bool visible ); /** * @brief Set whether the crosshairs are visible * @param visible visibility of the crosshairs */ void setShowCrosshairs( bool visible ); /** * @brief Set whether the coordinate grid overlay is visible * @param visible visibility of the coordinate grid */ void setShowGrid( bool visible ); /** * @brief Set whether the place mark overlay is visible * @param visible visibility of the place marks */ void setShowPlaces( bool visible ); /** * @brief Set whether the city place mark overlay is visible * @param visible visibility of the city place marks */ void setShowCities( bool visible ); /** * @brief Set whether the terrain place mark overlay is visible * @param visible visibility of the terrain place marks */ void setShowTerrain( bool visible ); /** * @brief Set whether the other places overlay is visible * @param visible visibility of other places */ void setShowOtherPlaces( bool visible ); /** * @brief Set whether the relief is visible * @param visible visibility of the relief */ void setShowRelief( bool visible ); /** * @brief Set whether the ice layer is visible * @param visible visibility of the ice layer */ void setShowIceLayer( bool visible ); /** * @brief Set whether the borders visible * @param visible visibility of the borders */ void setShowBorders( bool visible ); /** * @brief Set whether the rivers are visible * @param visible visibility of the rivers */ void setShowRivers( bool visible ); /** * @brief Set whether the lakes are visible * @param visible visibility of the lakes */ void setShowLakes( bool visible ); /** * @brief Set whether the frame rate gets shown * @param visible visibility of the frame rate */ void setShowFrameRate( bool visible ); void setShowRuntimeTrace( bool visible ); bool showRuntimeTrace() const; /** * @brief Set whether to enter the debug mode for * polygon node drawing * @param visible visibility of the node debug mode */ void setShowDebugPolygons( bool visible); bool showDebugPolygons() const; /** * @brief Set whether to enter the debug mode for * visualizing batch rendering * @param visible visibility of the batch rendering */ void setShowDebugBatchRender( bool visible); bool showDebugBatchRender() const; /** * @brief Set whether to enter the debug mode for * placemark drawing * @param visible visibility of the node debug mode */ void setShowDebugPlacemarks(bool visible); bool showDebugPlacemarks() const; /** * @brief Set whether to enter the debug mode for * level tags * @param visible visibility according to OSM level tags */ void setLevelTagDebugModeEnabled(bool visible); bool levelTagDebugModeEnabled() const; void setDebugLevelTag(int level); int debugLevelTag() const; void setShowBackground( bool visible ); void setVisibleRelationTypes(GeoDataRelation::RelationTypes relationTypes); /** * @brief used to notify about the position of the mouse click */ void notifyMouseClick( int x, int y ); void clearVolatileTileCache(); /** * @brief Set the limit of the volatile (in RAM) tile cache. * @param bytes The limit in kilobytes. */ void setVolatileTileCacheLimit( quint64 kiloBytes ); void setDefaultAngleUnit( AngleUnit angleUnit ); void setDefaultFont( const QFont& font ); /** * @brief Reload the currently displayed map by reloading texture tiles * from the Internet. In the future this should be extended to all * kinds of data which is used in the map. */ void reload(); void downloadRegion( QVector const & ); void highlightRouteRelation(qint64 osmId, bool enabled); Q_SIGNALS: void tileLevelChanged( int level ); /** * @brief Signal that the theme has changed * @param theme Name of the new theme. */ void themeChanged( const QString& theme ); void projectionChanged( Projection ); void radiusChanged( int radius ); void mouseMoveGeoPosition( const QString& ); void mouseClickGeoPosition( qreal lon, qreal lat, GeoDataCoordinates::Unit ); void framesPerSecond( qreal fps ); /** * This signal is emitted when the repaint of the view was requested. * If available with the @p dirtyRegion which is the region the view will change in. * If dirtyRegion.isEmpty() returns true, the whole viewport has to be repainted. */ void repaintNeeded( const QRegion& dirtyRegion = QRegion() ); /** * This signal is emitted when the visible region of the map changes. This typically happens * when the user moves the map around or zooms. */ void visibleLatLonAltBoxChanged( const GeoDataLatLonAltBox& visibleLatLonAltBox ); /** * @brief This signal is emit when the settings of a plugin changed. */ void pluginSettingsChanged(); /** * @brief Signal that a render item has been initialized */ void renderPluginInitialized( RenderPlugin *renderPlugin ); /** * @brief Emitted when the layer rendering status has changed * @param status New render status */ void renderStatusChanged( RenderStatus status ); void renderStateChanged( const RenderState &state ); void highlightedPlacemarksChanged( qreal, qreal, GeoDataCoordinates::Unit ); void viewContextChanged(ViewContext viewContext); void visibleRelationTypesChanged(GeoDataRelation::RelationTypes relationTypes); protected: /** * @brief Enables custom drawing onto the MarbleMap straight after * @brief the globe and before all other layers have been rendered. * @param painter * * @deprecated implement LayerInterface and add it using @p addLayer() */ virtual void customPaint( GeoPainter *painter ); private: Q_PRIVATE_SLOT( d, void updateMapTheme() ) Q_PRIVATE_SLOT( d, void updateProperty( const QString &, bool ) ) Q_PRIVATE_SLOT( d, void setDocument(QString) ) Q_PRIVATE_SLOT( d, void updateTileLevel() ) Q_PRIVATE_SLOT(d, void addPlugins()) private: Q_DISABLE_COPY( MarbleMap ) MarbleMapPrivate * const d; friend class MarbleMapPrivate; class CustomPaintLayer; friend class CustomPaintLayer; }; } #endif diff --git a/src/lib/marble/MarbleWidgetInputHandler.cpp b/src/lib/marble/MarbleWidgetInputHandler.cpp index efd4726b9..7410c67cd 100644 --- a/src/lib/marble/MarbleWidgetInputHandler.cpp +++ b/src/lib/marble/MarbleWidgetInputHandler.cpp @@ -1,231 +1,231 @@ // This file is part of the Marble Virtual Globe. // // This program is free software licensed under the GNU LGPL. You can // find a copy of this license in LICENSE.txt in the top directory of // the source code. // // Copyright 2006-2007 Torsten Rahn // Copyright 2007 Inge Wallin // Copyright 2014 Adam Dabrowski // #include "MarbleWidgetInputHandler.h" #include #include #include #include #include "MarbleGlobal.h" #include "MarbleDebug.h" #include "MarbleWidget.h" #include "AbstractDataPluginItem.h" #include "MarbleWidgetPopupMenu.h" #include "PopupLayer.h" #include "RenderPlugin.h" #include "RoutingLayer.h" namespace Marble { class MarbleWidgetInputHandlerPrivate { class MarbleWidgetSelectionRubber : public AbstractSelectionRubber { public: explicit MarbleWidgetSelectionRubber(MarbleWidget *widget) : m_rubberBand(QRubberBand::Rectangle, widget) { m_rubberBand.hide(); } void show() override { m_rubberBand.show(); } void hide() override { m_rubberBand.hide(); } bool isVisible() const override { return m_rubberBand.isVisible(); } const QRect &geometry() const override { return m_rubberBand.geometry(); } void setGeometry(const QRect &geometry) override { m_rubberBand.setGeometry(geometry); } private: QRubberBand m_rubberBand; }; public: MarbleWidgetInputHandlerPrivate(MarbleWidgetInputHandler *handler, MarbleWidget *widget) : m_inputHandler(handler) ,m_marbleWidget(widget) ,m_selectionRubber(widget) ,m_debugModeEnabled(false) { for(RenderPlugin *renderPlugin: widget->renderPlugins()) { if(renderPlugin->isInitialized()) { installPluginEventFilter(renderPlugin); } } m_marbleWidget->grabGesture(Qt::PinchGesture); } void setCursor(const QCursor &cursor) { m_marbleWidget->setCursor(cursor); } bool layersEventFilter(QObject *o, QEvent *e) { //FIXME - this should go up in hierarchy to MarbleInputHandler if (m_marbleWidget->popupLayer()->eventFilter(o, e)) { return true; } if (m_marbleWidget->routingLayer()->eventFilter(o, e)) { return true; } return false; } void installPluginEventFilter(RenderPlugin *renderPlugin) { m_marbleWidget->installEventFilter(renderPlugin); } MarbleWidgetInputHandler *m_inputHandler; MarbleWidget *m_marbleWidget; MarbleWidgetSelectionRubber m_selectionRubber; bool m_debugModeEnabled; }; void MarbleWidgetInputHandler::setCursor(const QCursor &cursor) { d->setCursor(cursor); } bool MarbleWidgetInputHandler::handleKeyPress(QKeyEvent *event) { if (d->m_debugModeEnabled) { if (event->modifiers() == Qt::ControlModifier && d->m_marbleWidget->debugLevelTags()) { switch(event->key()) { case Qt::Key_0: d->m_marbleWidget->setLevelToDebug(0); break; case Qt::Key_1: d->m_marbleWidget->setLevelToDebug(1); break; case Qt::Key_2: d->m_marbleWidget->setLevelToDebug(2); break; case Qt::Key_3: d->m_marbleWidget->setLevelToDebug(3); break; case Qt::Key_4: d->m_marbleWidget->setLevelToDebug(4); break; case Qt::Key_5: d->m_marbleWidget->setLevelToDebug(5); break; case Qt::Key_6: d->m_marbleWidget->setLevelToDebug(6); break; case Qt::Key_7: d->m_marbleWidget->setLevelToDebug(7); break; case Qt::Key_8: d->m_marbleWidget->setLevelToDebug(8); break; case Qt::Key_9: d->m_marbleWidget->setLevelToDebug(9); break; case Qt::Key_Plus: d->m_marbleWidget->setLevelToDebug(d->m_marbleWidget->levelToDebug() + 1); break; case Qt::Key_Minus: d->m_marbleWidget->setLevelToDebug(d->m_marbleWidget->levelToDebug() - 1); break; } } else { switch(event->key()) { case Qt::Key_I: MarbleDebug::setEnabled(!MarbleDebug::isEnabled()); break; case Qt::Key_R: d->m_marbleWidget->setShowRuntimeTrace(!d->m_marbleWidget->showRuntimeTrace()); break; case Qt::Key_O: d->m_marbleWidget->setShowDebugPlacemarks(!d->m_marbleWidget->showDebugPlacemarks()); break; case Qt::Key_P: d->m_marbleWidget->setShowDebugPolygons(!d->m_marbleWidget->showDebugPolygons()); break; case Qt::Key_B: d->m_marbleWidget->setShowDebugBatchRender(!d->m_marbleWidget->showDebugBatchRender()); break; case Qt::Key_L: d->m_marbleWidget->setDebugLevelTags(!d->m_marbleWidget->debugLevelTags()); break; } } } return MarbleDefaultInputHandler::handleKeyPress(event); } AbstractSelectionRubber *MarbleWidgetInputHandler::selectionRubber() { return &d->m_selectionRubber; } bool MarbleWidgetInputHandler::layersEventFilter(QObject *o, QEvent *e) { return d->layersEventFilter(o, e); } void MarbleWidgetInputHandler::installPluginEventFilter(RenderPlugin *renderPlugin) { d->installPluginEventFilter(renderPlugin); } MarbleWidgetInputHandler::MarbleWidgetInputHandler(MarbleAbstractPresenter *marblePresenter, MarbleWidget *widget) : MarbleDefaultInputHandler(marblePresenter) ,d(new MarbleWidgetInputHandlerPrivate(this, widget)) { } void MarbleWidgetInputHandler::setDebugModeEnabled(bool enabled) { d->m_debugModeEnabled = enabled; } //FIXME - these should be moved to superclass and popupMenu should be abstracted in MarbleAbstractPresenter void MarbleWidgetInputHandler::showLmbMenu(int x, int y) { if (isMouseButtonPopupEnabled(Qt::LeftButton)) { d->m_marbleWidget->popupMenu()->showLmbMenu(x, y); toolTipTimer()->stop(); } } void MarbleWidgetInputHandler::showRmbMenu(int x, int y) { if (isMouseButtonPopupEnabled(Qt::RightButton)) { d->m_marbleWidget->popupMenu()->showRmbMenu(x, y); } } void MarbleWidgetInputHandler::openItemToolTip() { - if (!lastToolTipItem().isNull()) + if (lastToolTipItem()) { QToolTip::showText(d->m_marbleWidget->mapToGlobal(toolTipPosition()), lastToolTipItem()->toolTip(), d->m_marbleWidget, lastToolTipItem()->containsRect(toolTipPosition()).toRect()); } } } #include "moc_MarbleWidgetInputHandler.cpp" diff --git a/src/lib/marble/declarative/MarbleQuickItem.cpp b/src/lib/marble/declarative/MarbleQuickItem.cpp index 619bb3f30..1b6e340ba 100644 --- a/src/lib/marble/declarative/MarbleQuickItem.cpp +++ b/src/lib/marble/declarative/MarbleQuickItem.cpp @@ -1,1127 +1,1127 @@ // // This file is part of the Marble Virtual Globe. // // This program is free software licensed under the GNU LGPL. You can // find a copy of this license in LICENSE.txt in the top directory of // the source code. // // Copyright 2014 Adam Dabrowski // #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "GeoDataRelation.h" #include "osm/OsmPlacemarkData.h" #include "GeoDataDocument.h" namespace Marble { //TODO - move to separate files class QuickItemSelectionRubber : public AbstractSelectionRubber { //TODO: support rubber selection in MarbleQuickItem public: QuickItemSelectionRubber(); void show() override { m_visible = true; } void hide() override { m_visible = false; } bool isVisible() const override { return m_visible; } const QRect &geometry() const override { return m_geometry; } void setGeometry(const QRect &/*geometry*/) override {} private: QRect m_geometry; bool m_visible; }; //TODO - implement missing functionalities class MarbleQuickInputHandler : public MarbleDefaultInputHandler { public: MarbleQuickInputHandler(MarbleAbstractPresenter *marblePresenter, MarbleQuickItem *marbleQuick) : MarbleDefaultInputHandler(marblePresenter) ,m_marbleQuick(marbleQuick) { setInertialEarthRotationEnabled(false); //Disabled by default, it's buggy. TODO - fix } bool acceptMouse() override { return true; } void pinch(QPointF center, qreal scale, Qt::GestureState state) { //TODO - this whole thing should be moved to MarbleAbstractPresenter (void)handlePinch(center, scale, state); } void handleMouseButtonPressAndHold(const QPoint &position) override { m_marbleQuick->reverseGeocoding(position); } private Q_SLOTS: void showLmbMenu(int x, int y) override { m_marbleQuick->selectPlacemarkAt(x, y); } void showRmbMenu(int, int) override {} void openItemToolTip() override {} void setCursor(const QCursor &cursor) override { m_marbleQuick->setCursor(cursor); } private Q_SLOTS: void installPluginEventFilter(RenderPlugin *) override {} private: bool layersEventFilter(QObject *o, QEvent *e) override { return m_marbleQuick->layersEventFilter(o, e); } //empty - don't check. It would be invalid with quick items void checkReleasedMove(QMouseEvent *) override {} bool handleTouch(QTouchEvent *event) override { if (event->touchPoints().count() > 1) { //not handling multi-touch at all, let PinchArea or MultiPointTouchArea take care of it return false; } if (event->touchPoints().count() == 1) { //handle - but do not accept. I.e. pinchArea still needs to get this QTouchEvent::TouchPoint p = event->touchPoints().at(0); if (event->type() == QEvent::TouchBegin) { QMouseEvent press(QMouseEvent::MouseButtonPress, p.pos(), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier); handleMouseEvent(&press); } else if (event->type() == QEvent::TouchUpdate) { QMouseEvent move(QMouseEvent::MouseMove, p.pos(), Qt::NoButton, Qt::LeftButton, Qt::NoModifier); handleMouseEvent(&move); } else if (event->type() == QEvent::TouchEnd) { QMouseEvent release(QMouseEvent::MouseButtonRelease, p.pos(), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier); handleMouseEvent(&release); } } return false; } AbstractSelectionRubber *selectionRubber() override { return &m_selectionRubber; } MarbleQuickItem *m_marbleQuick; QuickItemSelectionRubber m_selectionRubber; bool m_usePinchArea; }; class MarbleQuickItemPrivate { public: explicit MarbleQuickItemPrivate(MarbleQuickItem *marble) : m_marble(marble), m_model(), m_map(&m_model), m_presenter(&m_map), m_positionVisible(false), m_currentPosition(marble), m_inputHandler(&m_presenter, marble), m_placemarkDelegate(nullptr), m_placemarkItem(nullptr), m_placemark(nullptr), m_reverseGeocoding(&m_model), m_showScaleBar(false), m_enabledRelationTypes(GeoDataRelation::RouteFerry | GeoDataRelation::RouteTrain | GeoDataRelation::RouteSubway | GeoDataRelation::RouteTram | GeoDataRelation::RouteBus | GeoDataRelation::RouteTrolleyBus | GeoDataRelation::RouteHiking), m_showPublicTransport(false), m_showOutdoorActivities(false) { m_currentPosition.setName(QObject::tr("Current Location")); m_relationTypeConverter["road"] = GeoDataRelation::RouteRoad; m_relationTypeConverter["detour"] = GeoDataRelation::RouteDetour; m_relationTypeConverter["ferry"] = GeoDataRelation::RouteFerry; m_relationTypeConverter["train"] = GeoDataRelation::RouteTrain; m_relationTypeConverter["subway"] = GeoDataRelation::RouteSubway; m_relationTypeConverter["tram"] = GeoDataRelation::RouteTram; m_relationTypeConverter["bus"] = GeoDataRelation::RouteBus; m_relationTypeConverter["trolley-bus"] = GeoDataRelation::RouteTrolleyBus; m_relationTypeConverter["bicycle"] = GeoDataRelation::RouteBicycle; m_relationTypeConverter["mountainbike"] = GeoDataRelation::RouteMountainbike; m_relationTypeConverter["foot"] = GeoDataRelation::RouteFoot; m_relationTypeConverter["hiking"] = GeoDataRelation::RouteHiking; m_relationTypeConverter["horse"] = GeoDataRelation::RouteHorse; m_relationTypeConverter["inline-skates"] = GeoDataRelation::RouteInlineSkates; m_relationTypeConverter["downhill"] = GeoDataRelation::RouteSkiDownhill; m_relationTypeConverter["ski-nordic"] = GeoDataRelation::RouteSkiNordic; m_relationTypeConverter["skitour"] = GeoDataRelation::RouteSkitour; m_relationTypeConverter["sled"] = GeoDataRelation::RouteSled; } void updateVisibleRoutes(); private: MarbleQuickItem *m_marble; friend class MarbleQuickItem; MarbleModel m_model; MarbleMap m_map; MarbleAbstractPresenter m_presenter; bool m_positionVisible; Placemark m_currentPosition; MarbleQuickInputHandler m_inputHandler; QQmlComponent* m_placemarkDelegate; QQuickItem* m_placemarkItem; Placemark* m_placemark; ReverseGeocodingRunnerManager m_reverseGeocoding; bool m_showScaleBar; QMap m_relationTypeConverter; GeoDataRelation::RelationTypes m_enabledRelationTypes; bool m_showPublicTransport; bool m_showOutdoorActivities; }; MarbleQuickItem::MarbleQuickItem(QQuickItem *parent) : QQuickPaintedItem(parent) ,d(new MarbleQuickItemPrivate(this)) { setRenderTarget(QQuickPaintedItem::FramebufferObject); setOpaquePainting(true); qRegisterMetaType("Placemark*"); for (AbstractFloatItem *item: d->m_map.floatItems()) { if (item->nameId() == QLatin1String("license")) { item->setPosition(QPointF(5.0, -10.0)); } else { item->hide(); } } d->m_model.positionTracking()->setTrackVisible(false); connect(&d->m_map, SIGNAL(repaintNeeded(QRegion)), this, SLOT(update())); connect(this, &MarbleQuickItem::widthChanged, this, &MarbleQuickItem::resizeMap); connect(this, &MarbleQuickItem::heightChanged, this, &MarbleQuickItem::resizeMap); connect(&d->m_map, &MarbleMap::visibleLatLonAltBoxChanged, this, &MarbleQuickItem::updatePositionVisibility); connect(&d->m_map, &MarbleMap::visibleLatLonAltBoxChanged, this, &MarbleQuickItem::visibleLatLonAltBoxChanged); connect(&d->m_map, &MarbleMap::radiusChanged, this, &MarbleQuickItem::radiusChanged); connect(&d->m_map, &MarbleMap::radiusChanged, this, &MarbleQuickItem::zoomChanged); connect(&d->m_reverseGeocoding, SIGNAL(reverseGeocodingFinished(GeoDataCoordinates,GeoDataPlacemark)), this, SLOT(handleReverseGeocoding(GeoDataCoordinates,GeoDataPlacemark))); setAcceptedMouseButtons(Qt::AllButtons); installEventFilter(&d->m_inputHandler); } void MarbleQuickItem::resizeMap() { d->m_map.setSize(qMax(100, int(width())), qMax(100, int(height()))); update(); updatePositionVisibility(); } void MarbleQuickItem::positionDataStatusChanged(PositionProviderStatus status) { bool const positionAvailable = status == PositionProviderStatusAvailable; emit positionAvailableChanged(positionAvailable); updatePositionVisibility(); } void MarbleQuickItem::positionChanged(const GeoDataCoordinates &, GeoDataAccuracy) { updatePositionVisibility(); } void MarbleQuickItem::updatePositionVisibility() { updatePlacemarks(); bool isVisible = false; if ( positionAvailable() ) { qreal x, y; bool globeHidesPoint; bool const valid = d->m_map.viewport()->screenCoordinates(d->m_model.positionTracking()->currentLocation(), x, y, globeHidesPoint); isVisible = valid && !globeHidesPoint; } if ( isVisible != d->m_positionVisible ) { d->m_positionVisible = isVisible; emit positionVisibleChanged( isVisible ); } } void MarbleQuickItem::updateCurrentPosition(const GeoDataCoordinates &coordinates) { d->m_currentPosition.placemark().setCoordinate(coordinates); emit currentPositionChanged(&d->m_currentPosition); } void MarbleQuickItem::updatePlacemarks() { if (!d->m_placemarkDelegate || !d->m_placemark) { return; } if (!d->m_placemarkItem) { QQmlContext * context = new QQmlContext(qmlContext(d->m_placemarkDelegate)); QObject * component = d->m_placemarkDelegate->create(context); d->m_placemarkItem = qobject_cast( component ); if (d->m_placemarkItem) { d->m_placemarkItem->setParentItem( this ); d->m_placemarkItem->setProperty("placemark", QVariant::fromValue(d->m_placemark)); } else { delete component; return; } } qreal x = 0; qreal y = 0; const bool visible = d->m_map.viewport()->screenCoordinates(d->m_placemark->placemark().coordinate(), x, y); d->m_placemarkItem->setVisible(visible); if (visible) { d->m_placemarkItem->setProperty("xPos", QVariant(x)); d->m_placemarkItem->setProperty("yPos", QVariant(y)); } } void MarbleQuickItem::handleReverseGeocoding(const GeoDataCoordinates &coordinates, const GeoDataPlacemark &placemark) { if (d->m_placemark && d->m_placemark->placemark().coordinate() == coordinates) { d->m_placemark->setGeoDataPlacemark(placemark); updatePlacemarks(); } } void MarbleQuickItem::paint(QPainter *painter) { //TODO - much to be done here still, i.e paint !enabled version QPaintDevice *paintDevice = painter->device(); QRect rect = contentsBoundingRect().toRect(); painter->end(); { GeoPainter geoPainter(paintDevice, d->m_map.viewport(), d->m_map.mapQuality()); d->m_map.paint(geoPainter, rect); } painter->begin(paintDevice); } void MarbleQuickItem::classBegin() { } void MarbleQuickItem::componentComplete() { } int MarbleQuickItem::mapWidth() const { return d->m_map.width(); } int MarbleQuickItem::mapHeight() const { return d->m_map.height(); } bool MarbleQuickItem::showFrameRate() const { return d->m_map.showFrameRate(); } MarbleQuickItem::Projection MarbleQuickItem::projection() const { return Projection(d->m_map.projection()); } QString MarbleQuickItem::mapThemeId() const { return d->m_map.mapThemeId(); } bool MarbleQuickItem::showAtmosphere() const { return d->m_map.showAtmosphere(); } bool MarbleQuickItem::showCompass() const { return d->m_map.showCompass(); } bool MarbleQuickItem::showClouds() const { return d->m_map.showClouds(); } bool MarbleQuickItem::showCrosshairs() const { return d->m_map.showCrosshairs(); } bool MarbleQuickItem::showGrid() const { return d->m_map.showGrid(); } bool MarbleQuickItem::showOverviewMap() const { return d->m_map.showOverviewMap(); } bool MarbleQuickItem::showOtherPlaces() const { return d->m_map.showOtherPlaces(); } bool MarbleQuickItem::showScaleBar() const { return d->m_showScaleBar; } bool MarbleQuickItem::showBackground() const { return d->m_map.showBackground(); } bool MarbleQuickItem::showPositionMarker() const { QList plugins = d->m_map.renderPlugins(); for (const RenderPlugin * plugin: plugins) { if (plugin->nameId() == QLatin1String("positionMarker")) { return plugin->visible(); } } return false; } bool MarbleQuickItem::showPublicTransport() const { return d->m_showPublicTransport; } bool MarbleQuickItem::showOutdoorActivities() const { return d->m_showOutdoorActivities; } QString MarbleQuickItem::positionProvider() const { if ( d->m_model.positionTracking()->positionProviderPlugin() ) { return d->m_model.positionTracking()->positionProviderPlugin()->nameId(); } return QString(); } MarbleModel* MarbleQuickItem::model() { return &d->m_model; } const MarbleModel* MarbleQuickItem::model() const { return &d->m_model; } MarbleMap* MarbleQuickItem::map() { return &d->m_map; } const MarbleMap* MarbleQuickItem::map() const { return &d->m_map; } bool MarbleQuickItem::inertialGlobeRotation() const { return d->m_inputHandler.inertialEarthRotationEnabled(); } bool MarbleQuickItem::animationViewContext() const { return d->m_map.viewContext() == Animation; } QQmlComponent *MarbleQuickItem::placemarkDelegate() const { return d->m_placemarkDelegate; } void MarbleQuickItem::reverseGeocoding(const QPoint &point) { qreal lon, lat; d->m_map.viewport()->geoCoordinates(point.x(), point.y(), lon, lat); auto const coordinates = GeoDataCoordinates(lon, lat, 0.0, GeoDataCoordinates::Degree); delete d->m_placemarkItem; d->m_placemarkItem = nullptr; delete d->m_placemark; d->m_placemark = new Placemark(this); d->m_placemark->placemark().setCoordinate(coordinates); d->m_reverseGeocoding.reverseGeocoding(coordinates); } qreal MarbleQuickItem::speed() const { return d->m_model.positionTracking()->speed(); } qreal MarbleQuickItem::angle() const { bool routeExists = d->m_model.routingManager()->routingModel()->route().distance() != 0.0; bool onRoute = !d->m_model.routingManager()->routingModel()->deviatedFromRoute(); if ( routeExists && onRoute) { GeoDataCoordinates curPoint = d->m_model.positionTracking()->positionProviderPlugin()->position(); return d->m_model.routingManager()->routingModel()->route().currentSegment().projectedDirection(curPoint); } else { return d->m_model.positionTracking()->direction(); } } bool MarbleQuickItem::positionAvailable() const { return d->m_model.positionTracking()->status() == PositionProviderStatusAvailable; } - bool MarbleQuickItem::positionVisible() + bool MarbleQuickItem::positionVisible() const { return d->m_positionVisible; } qreal MarbleQuickItem::distanceFromPointToCurrentLocation(const QPoint & position) const { if ( positionAvailable() ) { qreal lon1; qreal lat1; d->m_map.viewport()->geoCoordinates(position.x(), position.y(), lon1, lat1, GeoDataCoordinates::Radian ); GeoDataCoordinates currentCoordinates = d->m_model.positionTracking()->currentLocation(); qreal lon2 = currentCoordinates.longitude(); qreal lat2 = currentCoordinates.latitude(); return distanceSphere(lon1, lat1, lon2, lat2) * d->m_model.planetRadius(); } return 0; } qreal MarbleQuickItem::angleFromPointToCurrentLocation( const QPoint & position ) const { if ( positionAvailable() ) { qreal x, y; PositionTracking const * positionTracking = d->m_model.positionTracking(); map()->viewport()->screenCoordinates( positionTracking->currentLocation(), x, y ); return atan2( y-position.y(), x-position.x() ) * RAD2DEG; } return 0; } Placemark * MarbleQuickItem::currentPosition() const { return &d->m_currentPosition; } QPointF MarbleQuickItem::screenCoordinatesFromCoordinate(Coordinate * coordinate) const { qreal x; qreal y; d->m_map.viewport()->screenCoordinates(coordinate->coordinates(), x, y); return QPointF(x, y); } void MarbleQuickItem::setRadius(int radius) { d->m_map.setRadius(radius); } void MarbleQuickItem::setZoom(int newZoom, FlyToMode mode) { d->m_presenter.setZoom(newZoom, mode); } void MarbleQuickItem::setZoomToMaximumLevel() { d->m_presenter.setZoom(d->m_map.maximumZoom()); } void MarbleQuickItem::centerOn(const GeoDataPlacemark& placemark, bool animated) { d->m_presenter.centerOn(placemark, animated); } void MarbleQuickItem::centerOn(const GeoDataLatLonBox& box, bool animated) { d->m_presenter.centerOn(box, animated); } void MarbleQuickItem::centerOn(const GeoDataCoordinates &coordinate) { GeoDataLookAt target = d->m_presenter.lookAt(); target.setCoordinates(coordinate); d->m_presenter.flyTo(target, Automatic); } void MarbleQuickItem::centerOn(qreal longitude, qreal latitude) { d->m_presenter.centerOn(longitude, latitude); } void MarbleQuickItem::centerOnCoordinates(qreal longitude, qreal latitude) { centerOn(longitude, latitude); } void MarbleQuickItem::centerOnCurrentPosition() { GeoDataCoordinates coordinates = d->m_model.positionTracking()->currentLocation(); if ( coordinates == GeoDataCoordinates() ) { return; } d->m_presenter.centerOn(coordinates, true); if (d->m_presenter.zoom() < 3000) { d->m_presenter.setZoom(3500); } } void MarbleQuickItem::selectPlacemarkAt(int x, int y) { auto features = d->m_map.whichFeatureAt(QPoint(x, y)); QVector placemarks; for(auto feature: features) { if (const auto placemark = geodata_cast(feature)) { placemarks << placemark; } } for(auto placemark: placemarks) { if (d->m_placemark && placemark->coordinate() == d->m_placemark->placemark().coordinate()) { d->m_placemark->deleteLater(); d->m_placemark = nullptr; } else { if (d->m_placemark) { d->m_placemark->deleteLater(); } d->m_placemark = new Placemark(this); d->m_placemark->setGeoDataPlacemark(*placemark); } delete d->m_placemarkItem; d->m_placemarkItem = nullptr; updatePlacemarks(); return; } if (d->m_placemark) { d->m_placemark->deleteLater(); d->m_placemark = nullptr; delete d->m_placemarkItem; d->m_placemarkItem = nullptr; updatePlacemarks(); } } void MarbleQuickItem::goHome() { d->m_presenter.goHome(); } void MarbleQuickItem::zoomIn(FlyToMode mode) { d->m_presenter.zoomIn(mode); } void MarbleQuickItem::zoomOut(FlyToMode mode) { d->m_presenter.zoomOut(mode); } void MarbleQuickItem::handlePinchStarted(const QPointF &point) { pinch(point, 1, Qt::GestureStarted); } void MarbleQuickItem::handlePinchFinished(const QPointF &point) { pinch(point, 1, Qt::GestureFinished); } void MarbleQuickItem::handlePinchUpdated(const QPointF &point, qreal scale) { scale = sqrt(sqrt(scale)); scale = qBound(static_cast(0.5), scale, static_cast(2.0)); pinch(point, scale, Qt::GestureUpdated); } void MarbleQuickItem::setMapWidth(int mapWidth) { if (d->m_map.width() == mapWidth) { return; } d->m_map.setSize(mapWidth, mapHeight()); emit mapWidthChanged(mapWidth); } void MarbleQuickItem::setMapHeight(int mapHeight) { if (this->mapHeight() == mapHeight) { return; } d->m_map.setSize(mapWidth(), mapHeight); emit mapHeightChanged(mapHeight); } void MarbleQuickItem::setShowFrameRate(bool showFrameRate) { if (this->showFrameRate() == showFrameRate) { return; } d->m_map.setShowFrameRate(showFrameRate); emit showFrameRateChanged(showFrameRate); } void MarbleQuickItem::setProjection(Projection projection) { if (this->projection() == projection) { return; } d->m_map.setProjection(Marble::Projection(projection)); emit projectionChanged(projection); } void MarbleQuickItem::setMapThemeId(const QString& mapThemeId) { if (this->mapThemeId() == mapThemeId) { return; } bool const showCompass = d->m_map.showCompass(); bool const showOverviewMap = d->m_map.showOverviewMap(); bool const showOtherPlaces = d->m_map.showOtherPlaces(); bool const showGrid = d->m_map.showGrid(); d->m_map.setMapThemeId(mapThemeId); // Map themes are allowed to change properties. Enforce ours. d->m_map.setShowCompass(showCompass); d->m_map.setShowOverviewMap(showOverviewMap); d->m_map.setShowOtherPlaces(showOtherPlaces); d->m_map.setShowGrid(showGrid); d->m_map.setShowScaleBar(d->m_showScaleBar); emit mapThemeIdChanged(mapThemeId); } void MarbleQuickItem::setShowAtmosphere(bool showAtmosphere) { if (this->showAtmosphere() == showAtmosphere) { return; } d->m_map.setShowAtmosphere(showAtmosphere); emit showAtmosphereChanged(showAtmosphere); } void MarbleQuickItem::setShowCompass(bool showCompass) { if (this->showCompass() == showCompass) { return; } d->m_map.setShowCompass(showCompass); emit showCompassChanged(showCompass); } void MarbleQuickItem::setShowClouds(bool showClouds) { if (this->showClouds() == showClouds) { return; } d->m_map.setShowClouds(showClouds); emit showCloudsChanged(showClouds); } void MarbleQuickItem::setShowCrosshairs(bool showCrosshairs) { if (this->showCrosshairs() == showCrosshairs) { return; } d->m_map.setShowCrosshairs(showCrosshairs); emit showCrosshairsChanged(showCrosshairs); } void MarbleQuickItem::setShowGrid(bool showGrid) { if (this->showGrid() == showGrid) { return; } d->m_map.setShowGrid(showGrid); emit showGridChanged(showGrid); } void MarbleQuickItem::setShowOverviewMap(bool showOverviewMap) { if (this->showOverviewMap() == showOverviewMap) { return; } d->m_map.setShowOverviewMap(showOverviewMap); emit showOverviewMapChanged(showOverviewMap); } void MarbleQuickItem::setShowOtherPlaces(bool showOtherPlaces) { if (this->showOtherPlaces() == showOtherPlaces) { return; } d->m_map.setShowOtherPlaces(showOtherPlaces); emit showOtherPlacesChanged(showOtherPlaces); } void MarbleQuickItem::setShowScaleBar(bool showScaleBar) { if (d->m_showScaleBar == showScaleBar) { return; } d->m_showScaleBar = showScaleBar; d->m_map.setShowScaleBar(d->m_showScaleBar); emit showScaleBarChanged(showScaleBar); } void MarbleQuickItem::setShowBackground(bool showBackground) { if (this->showBackground() == showBackground) { return; } d->m_map.setShowBackground(showBackground); emit showBackgroundChanged(showBackground); } void MarbleQuickItem::setShowPositionMarker(bool showPositionMarker) { if (this->showPositionMarker() == showPositionMarker) { return; } QList plugins = d->m_map.renderPlugins(); for ( RenderPlugin * plugin: plugins ) { if (plugin->nameId() == QLatin1String("positionMarker")) { plugin->setVisible(showPositionMarker); break; } } emit showPositionMarkerChanged(showPositionMarker); } void MarbleQuickItem::setShowPublicTransport(bool enabled) { if (d->m_showPublicTransport != enabled) { d->m_showPublicTransport = enabled; d->updateVisibleRoutes(); emit showPublicTransportChanged(enabled); } } void MarbleQuickItem::setShowOutdoorActivities(bool showOutdoorActivities) { if (d->m_showOutdoorActivities != showOutdoorActivities) { d->m_showOutdoorActivities = showOutdoorActivities; d->updateVisibleRoutes(); emit showOutdoorActivitiesChanged(showOutdoorActivities); } } void MarbleQuickItem::setPositionProvider(const QString &positionProvider) { QString name; if ( d->m_model.positionTracking()->positionProviderPlugin() ) { name = d->m_model.positionTracking()->positionProviderPlugin()->nameId(); if ( name == positionProvider ) { return; } } if ( positionProvider.isEmpty() ) { d->m_model.positionTracking()->setPositionProviderPlugin( nullptr ); return; } QList plugins = d->m_model.pluginManager()->positionProviderPlugins(); for (const PositionProviderPlugin* plugin: plugins) { if ( plugin->nameId() == positionProvider) { PositionProviderPlugin * newPlugin = plugin->newInstance(); d->m_model.positionTracking()->setPositionProviderPlugin(newPlugin); connect(newPlugin, SIGNAL(statusChanged(PositionProviderStatus)), this, SLOT(positionDataStatusChanged(PositionProviderStatus))); connect(newPlugin, SIGNAL(positionChanged(GeoDataCoordinates,GeoDataAccuracy)), this, SLOT(updateCurrentPosition(GeoDataCoordinates))); connect(newPlugin, SIGNAL(positionChanged(GeoDataCoordinates,GeoDataAccuracy)), this, SIGNAL(speedChanged())); connect(newPlugin, SIGNAL(positionChanged(GeoDataCoordinates,GeoDataAccuracy)), this, SIGNAL(angleChanged())); emit positionProviderChanged(positionProvider); break; } } } void MarbleQuickItem::setInertialGlobeRotation(bool inertialGlobeRotation) { if (inertialGlobeRotation == d->m_inputHandler.inertialEarthRotationEnabled()) { return; } d->m_inputHandler.setInertialEarthRotationEnabled(inertialGlobeRotation); emit inertialGlobeRotationChanged(inertialGlobeRotation); } void MarbleQuickItem::setAnimationViewContext(bool animationViewContext) { d->m_map.setViewContext(animationViewContext ? Animation : Still ); emit inertialGlobeRotationChanged(animationViewContext); } void MarbleQuickItem::setPluginSetting(const QString &pluginId, const QString &key, const QString &value) { for (RenderPlugin* plugin: d->m_map.renderPlugins()) { if (plugin->nameId() == pluginId) { plugin->setSetting(key, value); } } } void MarbleQuickItem::setPropertyEnabled(const QString &property, bool enabled) { d->m_map.setPropertyValue(property, enabled); } bool MarbleQuickItem::isPropertyEnabled(const QString &property) const { return d->m_map.propertyValue(property); } void MarbleQuickItem::setShowRuntimeTrace(bool showRuntimeTrace) { d->m_map.setShowRuntimeTrace(showRuntimeTrace); update(); } void MarbleQuickItem::setShowDebugPolygons(bool showDebugPolygons) { d->m_map.setShowDebugPolygons(showDebugPolygons); update(); } void MarbleQuickItem::setShowDebugPlacemarks(bool showDebugPlacemarks) { d->m_map.setShowDebugPlacemarks(showDebugPlacemarks); update(); } void MarbleQuickItem::setShowDebugBatches(bool showDebugBatches) { d->m_map.setShowDebugBatchRender(showDebugBatches); update(); } void MarbleQuickItem::setPlacemarkDelegate(QQmlComponent *placemarkDelegate) { if (d->m_placemarkDelegate == placemarkDelegate) { return; } delete d->m_placemarkItem; d->m_placemarkItem = nullptr; d->m_placemarkDelegate = placemarkDelegate; emit placemarkDelegateChanged(placemarkDelegate); } void MarbleQuickItem::loadSettings() { QSettings settings; settings.beginGroup(QStringLiteral("MarbleQuickItem")); double lon = settings.value(QStringLiteral("centerLon"), QVariant(0.0)).toDouble(); double lat = settings.value(QStringLiteral("centerLat"), QVariant(0.0)).toDouble(); if (lat == 0.0 && lon == 0.0) { centerOnCurrentPosition(); } else { centerOn(lon, lat); } int const zoom = settings.value(QStringLiteral("zoom"), QVariant(0)).toInt(); if (zoom > 0) { setZoom(zoom); } auto const defaultRelationTypes = QStringList() << "ferry" << "train" << "subway" << "tram" << "bus" << "trolley-bus" << "hiking"; auto const visibleRelationTypes = settings.value(QStringLiteral("visibleRelationTypes"), defaultRelationTypes).toStringList(); d->m_enabledRelationTypes = GeoDataRelation::UnknownType; for (auto const &route: visibleRelationTypes) { d->m_enabledRelationTypes |= d->m_relationTypeConverter.value(route, GeoDataRelation::UnknownType); } setShowPublicTransport(settings.value(QStringLiteral("showPublicTransport"), false).toBool()); setShowOutdoorActivities(settings.value(QStringLiteral("showOutdoorActivities"), false).toBool()); settings.endGroup(); d->m_model.routingManager()->readSettings(); d->m_model.bookmarkManager()->loadFile(QStringLiteral("bookmarks/bookmarks.kml")); d->m_model.bookmarkManager()->setShowBookmarks(true); d->updateVisibleRoutes(); } void MarbleQuickItem::writeSettings() { QSettings settings; settings.beginGroup(QStringLiteral("MarbleQuickItem")); settings.setValue(QStringLiteral("centerLon"), QVariant(d->m_map.centerLongitude())); settings.setValue(QStringLiteral("centerLat"), QVariant(d->m_map.centerLatitude())); settings.setValue(QStringLiteral("zoom"), QVariant(zoom())); QStringList enabledRoutes; QMap relationConverter; for (auto iter = d->m_relationTypeConverter.cbegin(), end = d->m_relationTypeConverter.cend(); iter != end; ++iter) { relationConverter[iter.value()] = iter.key(); } for (auto iter = relationConverter.cbegin(), end = relationConverter.cend(); iter != end; ++iter) { if (d->m_enabledRelationTypes & iter.key()) { enabledRoutes << iter.value(); } } settings.setValue(QStringLiteral("visibleRelationTypes"), enabledRoutes); settings.setValue(QStringLiteral("showPublicTransport"), d->m_showPublicTransport); settings.setValue(QStringLiteral("showOutdoorActivities"), d->m_showOutdoorActivities); settings.endGroup(); d->m_model.routingManager()->writeSettings(); } void MarbleQuickItem::reloadTiles() { d->m_map.reload(); } void MarbleQuickItem::highlightRouteRelation(qint64 osmId, bool enabled) { d->m_map.highlightRouteRelation(osmId, enabled); } void MarbleQuickItem::setRelationTypeVisible(const QString &relationType, bool visible) { auto const relation = d->m_relationTypeConverter.value(relationType, GeoDataRelation::UnknownType); if (visible) { d->m_enabledRelationTypes |= relation; } else { d->m_enabledRelationTypes &= ~relation; } d->updateVisibleRoutes(); } bool MarbleQuickItem::isRelationTypeVisible(const QString &relationType) const { auto const relation = d->m_relationTypeConverter.value(relationType, GeoDataRelation::UnknownType); return d->m_enabledRelationTypes & relation; } QObject *MarbleQuickItem::getEventFilter() const { //We would want to install the same event filter for abstract layer QuickItems such as PinchArea return &d->m_inputHandler; } void MarbleQuickItem::pinch(const QPointF& center, qreal scale, Qt::GestureState state) { d->m_inputHandler.pinch(center, scale, state); } MarbleInputHandler *MarbleQuickItem::inputHandler() { return &d->m_inputHandler; } int MarbleQuickItem::radius() const { return d->m_map.radius(); } int MarbleQuickItem::zoom() const { return d->m_presenter.logzoom(); } bool MarbleQuickItem::layersEventFilter(QObject *, QEvent *) { //Does nothing, but can be reimplemented in a subclass return false; } QuickItemSelectionRubber::QuickItemSelectionRubber() : m_visible(false) { // nothing to do } void MarbleQuickItemPrivate::updateVisibleRoutes() { GeoDataRelation::RelationTypes relationTypes = m_enabledRelationTypes; if (!m_showPublicTransport) { relationTypes &= ~GeoDataRelation::RouteTrain; relationTypes &= ~GeoDataRelation::RouteSubway; relationTypes &= ~GeoDataRelation::RouteTram; relationTypes &= ~GeoDataRelation::RouteBus; relationTypes &= ~GeoDataRelation::RouteTrolleyBus; } if (!m_showOutdoorActivities) { relationTypes &= ~GeoDataRelation::RouteBicycle; relationTypes &= ~GeoDataRelation::RouteMountainbike; relationTypes &= ~GeoDataRelation::RouteFoot; relationTypes &= ~GeoDataRelation::RouteHiking; relationTypes &= ~GeoDataRelation::RouteHorse; relationTypes &= ~GeoDataRelation::RouteInlineSkates; relationTypes &= ~GeoDataRelation::RouteSkiDownhill; relationTypes &= ~GeoDataRelation::RouteSkiNordic; relationTypes &= ~GeoDataRelation::RouteSkitour; relationTypes &= ~GeoDataRelation::RouteSled; } m_map.setVisibleRelationTypes(relationTypes); } } diff --git a/src/lib/marble/declarative/MarbleQuickItem.h b/src/lib/marble/declarative/MarbleQuickItem.h index 0074f5b80..d013a1cdc 100644 --- a/src/lib/marble/declarative/MarbleQuickItem.h +++ b/src/lib/marble/declarative/MarbleQuickItem.h @@ -1,251 +1,251 @@ // // This file is part of the Marble Virtual Globe. // // This program is free software licensed under the GNU LGPL. You can // find a copy of this license in LICENSE.txt in the top directory of // the source code. // // Copyright 2014 Adam Dabrowski // #ifndef MARBLEQUICKITEM_H #define MARBLEQUICKITEM_H #include "marble_declarative_export.h" #include #include #include "GeoDataAccuracy.h" #include "MarbleGlobal.h" #include "PositionProviderPluginInterface.h" #include "MarbleMap.h" #include "Placemark.h" #include "Coordinate.h" namespace Marble { class GeoDataLatLonBox; class GeoDataPlacemark; class MarbleModel; class MarbleInputHandler; class MarbleQuickItemPrivate; //Class is still being developed class MARBLE_DECLARATIVE_EXPORT MarbleQuickItem : public QQuickPaintedItem { Q_OBJECT Q_ENUMS(Projection) Q_PROPERTY(int mapWidth READ mapWidth WRITE setMapWidth NOTIFY mapWidthChanged) Q_PROPERTY(int mapHeight READ mapHeight WRITE setMapHeight NOTIFY mapHeightChanged) Q_PROPERTY(int zoom READ zoom WRITE setZoom NOTIFY zoomChanged) Q_PROPERTY(int radius READ radius WRITE setRadius NOTIFY radiusChanged) Q_PROPERTY(bool showFrameRate READ showFrameRate WRITE setShowFrameRate NOTIFY showFrameRateChanged) Q_PROPERTY(Projection projection READ projection WRITE setProjection NOTIFY projectionChanged) Q_PROPERTY(QString mapThemeId READ mapThemeId WRITE setMapThemeId NOTIFY mapThemeIdChanged) Q_PROPERTY(bool showAtmosphere READ showAtmosphere WRITE setShowAtmosphere NOTIFY showAtmosphereChanged) Q_PROPERTY(bool showCompass READ showCompass WRITE setShowCompass NOTIFY showCompassChanged) Q_PROPERTY(bool showClouds READ showClouds WRITE setShowClouds NOTIFY showCloudsChanged) Q_PROPERTY(bool showCrosshairs READ showCrosshairs WRITE setShowCrosshairs NOTIFY showCrosshairsChanged) Q_PROPERTY(bool showGrid READ showGrid WRITE setShowGrid NOTIFY showGridChanged) Q_PROPERTY(bool showOverviewMap READ showOverviewMap WRITE setShowOverviewMap NOTIFY showOverviewMapChanged) Q_PROPERTY(bool showOtherPlaces READ showOtherPlaces WRITE setShowOtherPlaces NOTIFY showOtherPlacesChanged) Q_PROPERTY(bool showScaleBar READ showScaleBar WRITE setShowScaleBar NOTIFY showScaleBarChanged) Q_PROPERTY(bool showBackground READ showBackground WRITE setShowBackground NOTIFY showBackgroundChanged) Q_PROPERTY(bool showPositionMarker READ showPositionMarker WRITE setShowPositionMarker NOTIFY showPositionMarkerChanged) Q_PROPERTY(bool showPublicTransport READ showPublicTransport WRITE setShowPublicTransport NOTIFY showPublicTransportChanged) Q_PROPERTY(bool showOutdoorActivities READ showOutdoorActivities WRITE setShowOutdoorActivities NOTIFY showOutdoorActivitiesChanged) Q_PROPERTY(QString positionProvider READ positionProvider WRITE setPositionProvider NOTIFY positionProviderChanged) Q_PROPERTY(bool positionAvailable READ positionAvailable NOTIFY positionAvailableChanged) Q_PROPERTY(bool positionVisible READ positionVisible NOTIFY positionVisibleChanged) Q_PROPERTY(MarbleMap* marbleMap READ map NOTIFY marbleMapChanged) Q_PROPERTY(Placemark* currentPosition READ currentPosition NOTIFY currentPositionChanged) Q_PROPERTY(qreal speed READ speed NOTIFY speedChanged) Q_PROPERTY(qreal angle READ angle NOTIFY angleChanged) Q_PROPERTY(bool inertialGlobeRotation READ inertialGlobeRotation WRITE setInertialGlobeRotation NOTIFY inertialGlobeRotationChanged) Q_PROPERTY(bool animationViewContext READ animationViewContext WRITE setAnimationViewContext NOTIFY animationViewContextChanged) Q_PROPERTY(QQmlComponent* placemarkDelegate READ placemarkDelegate WRITE setPlacemarkDelegate NOTIFY placemarkDelegateChanged) public: explicit MarbleQuickItem(QQuickItem *parent = 0); enum Projection{ Spherical = Marble::Spherical, Equirectangular = Marble::Equirectangular, Mercator = Marble::Mercator, Gnomonic = Marble::Gnomonic, Stereographic = Marble::Stereographic, LambertAzimuthal = Marble::LambertAzimuthal, AzimuthalEquidistant = Marble::AzimuthalEquidistant, VerticalPerspective = Marble::VerticalPerspective }; MarbleInputHandler *inputHandler(); int zoom() const; int radius() const; public Q_SLOTS: void goHome(); void setZoom(int zoom, FlyToMode mode = Instant); Q_INVOKABLE void setZoomToMaximumLevel(); void setRadius(int radius); void centerOn(const GeoDataPlacemark& placemark, bool animated = false); void centerOn(const GeoDataLatLonBox& box, bool animated = false); void centerOn(const GeoDataCoordinates& coordinate); void centerOn(qreal longitude, qreal latitude); Q_INVOKABLE void centerOnCoordinates(qreal longitude, qreal latitude); Q_INVOKABLE void centerOnCurrentPosition(); Q_INVOKABLE void selectPlacemarkAt(int x, int y); void zoomIn(FlyToMode mode = Automatic); void zoomOut(FlyToMode mode = Automatic); Q_INVOKABLE void handlePinchStarted(const QPointF &point); Q_INVOKABLE void handlePinchFinished(const QPointF &point); Q_INVOKABLE void handlePinchUpdated(const QPointF &point, qreal scale); void setMapWidth(int mapWidth); void setMapHeight(int mapHeight); void setShowFrameRate(bool showFrameRate); void setProjection(Projection projection); void setMapThemeId(const QString& mapThemeId); void setShowAtmosphere(bool showAtmosphere); void setShowCompass(bool showCompass); void setShowClouds(bool showClouds); void setShowCrosshairs(bool showCrosshairs); void setShowGrid(bool showGrid); void setShowOverviewMap(bool showOverviewMap); void setShowOtherPlaces(bool showOtherPlaces); void setShowScaleBar(bool showScaleBar); void setShowBackground(bool showBackground); void setShowPositionMarker(bool showPositionMarker); void setShowPublicTransport(bool showPublicTransport); void setShowOutdoorActivities(bool showOutdoorActivities); void setPositionProvider(const QString & positionProvider); void setInertialGlobeRotation(bool inertialGlobeRotation); void setAnimationViewContext(bool animationViewContext); void setPluginSetting(const QString &plugin, const QString &key, const QString &value); void setPropertyEnabled(const QString &property, bool enabled); bool isPropertyEnabled(const QString &property) const; Q_INVOKABLE void setShowRuntimeTrace(bool showRuntimeTrace); Q_INVOKABLE void setShowDebugPolygons(bool showDebugPolygons); Q_INVOKABLE void setShowDebugPlacemarks(bool showDebugPlacemarks); Q_INVOKABLE void setShowDebugBatches(bool showDebugBatches); void setPlacemarkDelegate(QQmlComponent* placemarkDelegate); Q_INVOKABLE void loadSettings(); Q_INVOKABLE void writeSettings(); Q_INVOKABLE void reloadTiles(); Q_INVOKABLE void highlightRouteRelation(qint64 osmId, bool enabled); Q_INVOKABLE void setRelationTypeVisible(const QString &relationType, bool visible); Q_INVOKABLE bool isRelationTypeVisible(const QString &relationType) const; public: void paint(QPainter *painter) override; // QQmlParserStatus interface public: void classBegin() override; void componentComplete() override; public: virtual bool layersEventFilter(QObject *o, QEvent *e); int mapWidth() const; int mapHeight() const; bool showFrameRate() const; Projection projection() const; QString mapThemeId() const; bool showAtmosphere() const; bool showCompass() const; bool showClouds() const; bool showCrosshairs() const; bool showGrid() const; bool showOverviewMap() const; bool showOtherPlaces() const; bool showScaleBar() const; bool showBackground() const; bool showPositionMarker() const; bool showPublicTransport() const; bool showOutdoorActivities() const; QString positionProvider() const; bool positionAvailable() const; - bool positionVisible(); + bool positionVisible() const; Q_INVOKABLE qreal distanceFromPointToCurrentLocation(const QPoint & position) const; Q_INVOKABLE qreal angleFromPointToCurrentLocation(const QPoint & position) const; Placemark* currentPosition() const; Q_INVOKABLE QPointF screenCoordinatesFromCoordinate(Coordinate * coordinate) const; qreal speed() const; qreal angle() const; MarbleModel* model(); const MarbleModel* model() const; MarbleMap* map(); const MarbleMap* map() const; bool inertialGlobeRotation() const; bool animationViewContext() const; QQmlComponent* placemarkDelegate() const; void reverseGeocoding(const QPoint &point); Q_SIGNALS: void mapWidthChanged(int mapWidth); void mapHeightChanged(int mapHeight); void showFrameRateChanged(bool showFrameRate); void projectionChanged(Projection projection); void mapThemeIdChanged(const QString& mapThemeId); void showAtmosphereChanged(bool showAtmosphere); void showCompassChanged(bool showCompass); void showCloudsChanged(bool showClouds); void showCrosshairsChanged(bool showCrosshairs); void showGridChanged(bool showGrid); void showOverviewMapChanged(bool showOverviewMap); void showOtherPlacesChanged(bool showOtherPlaces); void showScaleBarChanged(bool showScaleBar); void showBackgroundChanged(bool showBackground); void showPositionMarkerChanged(bool showPositionMarker); void showPublicTransportChanged(bool showPublicTransport); void showOutdoorActivitiesChanged(bool showOutdoorActivities); void positionProviderChanged(const QString & positionProvider); void positionAvailableChanged(bool positionAvailable); void positionVisibleChanged(bool positionVisible); void marbleMapChanged(); void visibleLatLonAltBoxChanged(); void currentPositionChanged(Placemark* currentPosition); void angleChanged(); void speedChanged(); void zoomChanged(); void radiusChanged(int radius); void inertialGlobeRotationChanged(bool inertialGlobeRotation); void animationViewContextChanged(bool animationViewContext); void placemarkDelegateChanged(QQmlComponent* placemarkDelegate); protected: QObject *getEventFilter() const; void pinch(const QPointF& center, qreal scale, Qt::GestureState state); private Q_SLOTS: void resizeMap(); void positionDataStatusChanged(PositionProviderStatus status); void positionChanged(const GeoDataCoordinates &, GeoDataAccuracy); void updatePositionVisibility(); void updateCurrentPosition(const GeoDataCoordinates & coordinates); void updatePlacemarks(); void handleReverseGeocoding(const GeoDataCoordinates &coordinates, const GeoDataPlacemark &placemark); private: typedef QSharedPointer MarbleQuickItemPrivatePtr; MarbleQuickItemPrivatePtr d; friend class MarbleQuickItemPrivate; }; } #endif // MARBLEQUICKITEM_H diff --git a/src/lib/marble/declarative/Navigation.cpp b/src/lib/marble/declarative/Navigation.cpp index 1b7bf9a96..c9625b8ff 100644 --- a/src/lib/marble/declarative/Navigation.cpp +++ b/src/lib/marble/declarative/Navigation.cpp @@ -1,355 +1,355 @@ // // This file is part of the Marble Virtual Globe. // // This program is free software licensed under the GNU LGPL. You can // find a copy of this license in LICENSE.txt in the top directory of // the source code. // // Copyright 2011 Dennis Nienhüser // #include "Navigation.h" #include "Planet.h" #include "MarbleModel.h" #include "MarbleQuickItem.h" #include "routing/Route.h" #include "routing/RoutingManager.h" #include "routing/RoutingModel.h" #include "PositionTracking.h" #include "MarbleMath.h" #include "AutoNavigation.h" #include "routing/VoiceNavigationModel.h" #include "ViewportParams.h" #include "GeoDataAccuracy.h" namespace Marble { class NavigationPrivate { public: NavigationPrivate(); MarbleQuickItem * m_marbleQuickItem; bool m_muted; RouteSegment m_currentSegment; AutoNavigation* m_autoNavigation; VoiceNavigationModel m_voiceNavigation; qreal m_nextInstructionDistance; qreal m_destinationDistance; double m_screenAccuracy; QPointF m_screenPosition; - RouteSegment nextRouteSegment(); + RouteSegment nextRouteSegment() const; void updateNextInstructionDistance( const Route &route ); MarbleModel * model() const; QPointF positionOnRoute() const; QPointF currentPosition() const; RouteSegment m_secondLastSegment; RouteSegment m_lastSegment; }; NavigationPrivate::NavigationPrivate() : m_marbleQuickItem( nullptr ), m_muted( false ), m_autoNavigation( 0 ), m_nextInstructionDistance( 0.0 ), m_destinationDistance( 0.0 ), m_screenAccuracy(0) { // nothing to do } void NavigationPrivate::updateNextInstructionDistance( const Route &route ) { const GeoDataCoordinates position = route.position(); const GeoDataCoordinates interpolated = route.positionOnRoute(); const GeoDataCoordinates onRoute = route.currentWaypoint(); qreal planetRadius = 0; if (model()){ planetRadius = model()->planet()->radius(); } qreal distance = planetRadius * ( distanceSphere( position, interpolated ) + distanceSphere( interpolated, onRoute ) ); qreal remaining = 0.0; const RouteSegment &segment = route.currentSegment(); for ( int i=0; imodel() : nullptr; } -RouteSegment NavigationPrivate::nextRouteSegment() +RouteSegment NavigationPrivate::nextRouteSegment() const { // Not using m_currentSegment on purpose return m_marbleQuickItem ? model()->routingManager()->routingModel()->route().currentSegment().nextRouteSegment() : RouteSegment(); } Navigation::Navigation( QObject* parent) : QObject( parent ), d( new NavigationPrivate ) { connect( &d->m_voiceNavigation, SIGNAL(instructionChanged()), this, SIGNAL(voiceNavigationAnnouncementChanged()) ); } Navigation::~Navigation() { delete d; } bool Navigation::guidanceModeEnabled() const { return d->m_marbleQuickItem ? d->model()->routingManager()->guidanceModeEnabled() : false; } void Navigation::setGuidanceModeEnabled( bool enabled ) { if ( d->m_marbleQuickItem ) { d->model()->routingManager()->setGuidanceModeEnabled( enabled ); d->m_autoNavigation->setAutoZoom( enabled ); d->m_autoNavigation->setRecenter( enabled ? AutoNavigation::RecenterOnBorder : AutoNavigation::DontRecenter ); if ( enabled && !d->m_muted ) { //d->m_audio.announceStart(); } } } bool Navigation::muted() const { return d->m_muted; } void Navigation::setMuted(bool enabled) { d->m_muted = enabled; } QString Navigation::nextInstructionText() const { return d->nextRouteSegment().maneuver().instructionText(); } QString Navigation::nextRoad() const { return d->nextRouteSegment().maneuver().roadName(); } QString Navigation::nextInstructionImage() const { switch ( d->nextRouteSegment().maneuver().direction() ) { case Maneuver::Continue: return QStringLiteral("qrc:/marble/turn-continue.svg"); case Maneuver::Merge: return QStringLiteral("qrc:/marble/turn-merge.svg"); case Maneuver::Straight: return QStringLiteral("qrc:/marble/turn-continue.svg"); case Maneuver::SlightRight: return QStringLiteral("qrc:/marble/turn-slight-right.svg"); case Maneuver::Right: return QStringLiteral("qrc:/marble/turn-right.svg"); case Maneuver::SharpRight: return QStringLiteral("qrc:/marble/turn-sharp-right.svg"); case Maneuver::TurnAround: return QStringLiteral("qrc:/marble/turn-around.svg"); case Maneuver::SharpLeft: return QStringLiteral("qrc:/marble/turn-sharp-left.svg"); case Maneuver::Left: return QStringLiteral("qrc:/marble/turn-left.svg"); case Maneuver::SlightLeft: return QStringLiteral("qrc:/marble/turn-slight-left.svg"); case Maneuver::RoundaboutFirstExit: return QStringLiteral("qrc:/marble/turn-roundabout-first.svg"); case Maneuver::RoundaboutSecondExit: return QStringLiteral("qrc:/marble/turn-roundabout-second.svg"); case Maneuver::RoundaboutThirdExit: return QStringLiteral("qrc:/marble/turn-roundabout-third.svg"); case Maneuver::RoundaboutExit: return QStringLiteral("qrc:/marble/turn-roundabout-far.svg"); case Maneuver::ExitLeft: return QStringLiteral("qrc:/marble/turn-exit-left.svg"); case Maneuver::ExitRight: return QStringLiteral("qrc:/marble/turn-exit-right.svg"); case Maneuver::Unknown: default: return QString(); } } qreal Navigation::nextInstructionDistance() const { return d->m_nextInstructionDistance; } qreal Navigation::destinationDistance() const { return d->m_destinationDistance; } QString Navigation::voiceNavigationAnnouncement() const { return d->m_voiceNavigation.instruction(); } QString Navigation::speaker() const { return d->m_voiceNavigation.speaker(); } void Navigation::setSpeaker( const QString &speaker ) { d->m_voiceNavigation.setSpeaker( speaker ); } bool Navigation::deviated() const { if ( d->m_marbleQuickItem ) { RoutingModel const * routingModel = d->model()->routingManager()->routingModel(); return routingModel->deviatedFromRoute(); } return true; } MarbleQuickItem *Navigation::marbleQuickItem() const { return d->m_marbleQuickItem; } QPointF NavigationPrivate::positionOnRoute() const { RoutingModel const * routingModel = model()->routingManager()->routingModel(); GeoDataCoordinates coordinates = routingModel->route().positionOnRoute(); qreal x = 0; qreal y = 0; if (coordinates.isValid()) { m_marbleQuickItem->map()->viewport()->screenCoordinates(coordinates, x, y); } return QPointF(x,y); } QPointF NavigationPrivate::currentPosition() const { GeoDataCoordinates coordinates = model()->positionTracking()->currentLocation(); qreal x = 0; qreal y = 0; m_marbleQuickItem->map()->viewport()->screenCoordinates(coordinates, x, y); return QPointF(x,y); } QPointF Navigation::screenPosition() const { return d->m_screenPosition; } double Navigation::screenAccuracy() const { return d->m_screenAccuracy; } void Navigation::setMarbleQuickItem(MarbleQuickItem *marbleQuickItem) { if ( d->m_marbleQuickItem == marbleQuickItem) { return; } if (d->m_marbleQuickItem) { disconnect( d->model()->routingManager()->routingModel(), SIGNAL(positionChanged()), this, SLOT(update()) ); disconnect( d->m_autoNavigation, SIGNAL(zoomIn(FlyToMode)), d->m_marbleQuickItem, SLOT(zoomIn()) ); disconnect( d->m_autoNavigation, SIGNAL(zoomOut(FlyToMode)), d->m_marbleQuickItem, SLOT(zoomOut()) ); disconnect( d->m_autoNavigation, SIGNAL(centerOn(GeoDataCoordinates,bool)), d->m_marbleQuickItem, SLOT(centerOn(GeoDataCoordinates)) ); disconnect( d->m_marbleQuickItem, SIGNAL(visibleLatLonAltBoxChanged()), d->m_autoNavigation, SLOT(inhibitAutoAdjustments()) ); } d->m_marbleQuickItem = marbleQuickItem; if ( d->m_marbleQuickItem ) { d->model()->routingManager()->setShowGuidanceModeStartupWarning( false ); connect( d->model()->routingManager()->routingModel(), SIGNAL(positionChanged()), this, SLOT(update()) ); connect( d->model()->routingManager()->routingModel(), SIGNAL(deviatedFromRoute(bool)), this, SIGNAL(deviationChanged()) ); delete d->m_autoNavigation; d->m_autoNavigation = new AutoNavigation( d->model(), d->m_marbleQuickItem->map()->viewport(), this ); connect( d->m_autoNavigation, SIGNAL(zoomIn(FlyToMode)), d->m_marbleQuickItem, SLOT(zoomIn()) ); connect( d->m_autoNavigation, SIGNAL(zoomOut(FlyToMode)), d->m_marbleQuickItem, SLOT(zoomOut()) ); connect( d->m_autoNavigation, SIGNAL(centerOn(GeoDataCoordinates,bool)), d->m_marbleQuickItem, SLOT(centerOn(GeoDataCoordinates)) ); connect( d->m_marbleQuickItem, SIGNAL(visibleLatLonAltBoxChanged()), d->m_autoNavigation, SLOT(inhibitAutoAdjustments()) ); connect( d->m_marbleQuickItem, SIGNAL(visibleLatLonAltBoxChanged()), this, SLOT(updateScreenPosition()) ); connect( d->model()->positionTracking(), SIGNAL(gpsLocation(GeoDataCoordinates,qreal)), this, SLOT(updateScreenPosition()) ); connect( d->model()->positionTracking(), SIGNAL(statusChanged(PositionProviderStatus)), this, SLOT(updateScreenPosition()) ); } emit marbleQuickItemChanged(marbleQuickItem); } void Navigation::update() { if (!d->model()) { return; } RoutingModel const * routingModel = d->model()->routingManager()->routingModel(); d->updateNextInstructionDistance( routingModel->route() ); emit nextInstructionDistanceChanged(); emit destinationDistanceChanged(); RouteSegment segment = routingModel->route().currentSegment(); if ( !d->m_muted ) { d->m_voiceNavigation.update( routingModel->route(), d->m_nextInstructionDistance, d->m_destinationDistance, routingModel->deviatedFromRoute() ); } if ( segment != d->m_currentSegment ) { d->m_currentSegment = segment; emit nextInstructionTextChanged(); emit nextInstructionImageChanged(); emit nextRoadChanged(); } updateScreenPosition(); } void Navigation::updateScreenPosition() { if(d->m_marbleQuickItem) { double distanceMeter = d->model()->positionTracking()->accuracy().horizontal; d->m_screenAccuracy = distanceMeter * d->m_marbleQuickItem->map()->radius() / d->model()->planetRadius(); emit screenAccuracyChanged(); d->m_screenPosition = deviated() ? d->currentPosition() : d->positionOnRoute(); emit screenPositionChanged(); } } } #include "moc_Navigation.cpp" diff --git a/src/lib/marble/geodata/graphicsitem/AbstractGeoPolygonGraphicsItem.cpp b/src/lib/marble/geodata/graphicsitem/AbstractGeoPolygonGraphicsItem.cpp index 7e5aba664..b05e608a5 100644 --- a/src/lib/marble/geodata/graphicsitem/AbstractGeoPolygonGraphicsItem.cpp +++ b/src/lib/marble/geodata/graphicsitem/AbstractGeoPolygonGraphicsItem.cpp @@ -1,240 +1,240 @@ // // This file is part of the Marble Virtual Globe. // // This program is free software licensed under the GNU LGPL. You can // find a copy of this license in LICENSE.txt in the top directory of // the source code. // // Copyright 2011 Konstantin Oblaukhov // #include "AbstractGeoPolygonGraphicsItem.h" #include "GeoDataLinearRing.h" #include "GeoDataPolygon.h" #include "GeoDataBuilding.h" #include "GeoPainter.h" #include "GeoDataLatLonAltBox.h" #include "GeoDataStyle.h" #include "GeoDataIconStyle.h" #include "GeoDataLineStyle.h" #include "GeoDataPlacemark.h" #include "GeoDataPolyStyle.h" #include "GeoDataTypes.h" #include "OsmPlacemarkData.h" #include "MarbleDebug.h" #include "ViewportParams.h" #include #include #include namespace Marble { const void *AbstractGeoPolygonGraphicsItem::s_previousStyle = 0; AbstractGeoPolygonGraphicsItem::AbstractGeoPolygonGraphicsItem(const GeoDataPlacemark *placemark, const GeoDataPolygon *polygon) : GeoGraphicsItem(placemark), m_polygon(polygon), m_ring(0), m_building(0) { } AbstractGeoPolygonGraphicsItem::AbstractGeoPolygonGraphicsItem(const GeoDataPlacemark *placemark, const GeoDataLinearRing *ring) : GeoGraphicsItem(placemark), m_polygon(0), m_ring(ring), m_building(0) { } AbstractGeoPolygonGraphicsItem::AbstractGeoPolygonGraphicsItem(const GeoDataPlacemark *placemark, const GeoDataBuilding *building) : GeoGraphicsItem(placemark), m_polygon(0), m_ring(0), m_building(building) { } AbstractGeoPolygonGraphicsItem::~AbstractGeoPolygonGraphicsItem() { } const GeoDataLatLonAltBox& AbstractGeoPolygonGraphicsItem::latLonAltBox() const { if(m_polygon) { return m_polygon->latLonAltBox(); } else if (m_ring) { return m_ring->latLonAltBox(); } return m_building->latLonAltBox(); } void AbstractGeoPolygonGraphicsItem::paint( GeoPainter* painter, const ViewportParams* viewport, const QString &layer, int tileZoomLevel) { Q_UNUSED(layer); Q_UNUSED(tileZoomLevel); bool isValid = true; if (s_previousStyle != style().data()) { - isValid = configurePainter(painter, viewport); + isValid = configurePainter(painter, *viewport); } s_previousStyle = style().data(); if (!isValid) return; if ( m_polygon ) { bool innerResolved = false; for(auto const & ring : m_polygon->innerBoundaries()) { if (viewport->resolves(ring.latLonAltBox(), 4)) { innerResolved = true; break; } } if (innerResolved) { painter->drawPolygon(*m_polygon); } else { painter->drawPolygon(m_polygon->outerBoundary()); } } else if ( m_ring ) { painter->drawPolygon( *m_ring ); } } bool AbstractGeoPolygonGraphicsItem::contains(const QPoint &screenPosition, const ViewportParams *viewport) const { auto const visualCategory = static_cast(feature())->visualCategory(); if (visualCategory == GeoDataPlacemark::Landmass || visualCategory == GeoDataPlacemark::UrbanArea || (visualCategory >= GeoDataPlacemark::LanduseAllotments && visualCategory <= GeoDataPlacemark::LanduseVineyard)) { return false; } double lon, lat; viewport->geoCoordinates(screenPosition.x(), screenPosition.y(), lon, lat, GeoDataCoordinates::Radian); auto const coordinates = GeoDataCoordinates(lon, lat); if (m_polygon) { return m_polygon->contains(coordinates); } else if (m_ring) { return m_ring->contains(coordinates); } return false; } -bool AbstractGeoPolygonGraphicsItem::configurePainter(GeoPainter *painter, const ViewportParams *viewport) +bool AbstractGeoPolygonGraphicsItem::configurePainter(GeoPainter *painter, const ViewportParams &viewport) const { QPen currentPen = painter->pen(); GeoDataStyle::ConstPtr style = this->style(); if (!style) { painter->setPen( QPen() ); // "style-less" polygons: a 1px black solid line } else { const GeoDataPolyStyle& polyStyle = style->polyStyle(); if (polyStyle.outline()) { const GeoDataLineStyle& lineStyle = style->lineStyle(); // To save performance we avoid making changes to the painter's pen. // So we first take a copy of the actual painter pen, make changes to it // and only if the resulting pen is different from the actual pen // we replace the painter's pen with our new pen. // We want to avoid the mandatory detach in QPen::setColor(), // so we carefully check whether applying the setter is needed currentPen.setColor(lineStyle.paintedColor()); currentPen.setWidthF(lineStyle.width()); currentPen.setCapStyle(lineStyle.capStyle()); currentPen.setStyle(lineStyle.penStyle()); if (painter->pen().color() != currentPen.color()) { painter->setPen(currentPen); } } else { // polygons without outline: Qt::NoPen (not drawn) if (currentPen.style() != Qt::NoPen) { painter->setPen(Qt::NoPen); } } if (!polyStyle.fill()) { painter->setBrush(Qt::transparent); } else { const QColor paintedColor = polyStyle.paintedColor(); if (painter->brush().color() != paintedColor || painter->brush().style() != polyStyle.brushStyle()) { if (!polyStyle.texturePath().isEmpty() || !polyStyle.textureImage().isNull()) { GeoDataCoordinates coords = latLonAltBox().center(); qreal x, y; - viewport->screenCoordinates(coords, x, y); + viewport.screenCoordinates(coords, x, y); QBrush brush(texture(polyStyle.texturePath(), paintedColor)); painter->setBrush(brush); painter->setBrushOrigin(QPoint(x,y)); } else { painter->setBrush(QBrush(paintedColor, polyStyle.brushStyle())); } } } } return true; } int AbstractGeoPolygonGraphicsItem::extractElevation(const GeoDataPlacemark &placemark) { int elevation = 0; const OsmPlacemarkData &osmData = placemark.osmData(); const auto tagIter = osmData.findTag(QStringLiteral("ele")); if (tagIter != osmData.tagsEnd()) { elevation = tagIter.value().toInt(); } return elevation; } -QPixmap AbstractGeoPolygonGraphicsItem::texture(const QString &texturePath, const QColor &color) +QPixmap AbstractGeoPolygonGraphicsItem::texture(const QString &texturePath, const QColor &color) const { QString const key = QString::number(color.rgba()) + '/' + texturePath; QPixmap texture; if (!QPixmapCache::find(key, texture)) { QImageReader imageReader(style()->polyStyle().resolvePath(texturePath)); texture = QPixmap::fromImageReader(&imageReader); if (texture.hasAlphaChannel()) { QPixmap pixmap (texture.size()); pixmap.fill(color); QPainter imagePainter(&pixmap); imagePainter.drawPixmap(0, 0, texture); imagePainter.end(); texture = pixmap; } QPixmapCache::insert(key, texture); } return texture; } void AbstractGeoPolygonGraphicsItem::setLinearRing(GeoDataLinearRing *ring) { Q_ASSERT(m_building); Q_ASSERT(!m_polygon); m_ring = ring; } void AbstractGeoPolygonGraphicsItem::setPolygon(GeoDataPolygon *polygon) { Q_ASSERT(m_building); Q_ASSERT(!m_ring); m_polygon = polygon; } } diff --git a/src/lib/marble/geodata/graphicsitem/AbstractGeoPolygonGraphicsItem.h b/src/lib/marble/geodata/graphicsitem/AbstractGeoPolygonGraphicsItem.h index 4ac53eab6..df95c19a1 100644 --- a/src/lib/marble/geodata/graphicsitem/AbstractGeoPolygonGraphicsItem.h +++ b/src/lib/marble/geodata/graphicsitem/AbstractGeoPolygonGraphicsItem.h @@ -1,67 +1,67 @@ // // This file is part of the Marble Virtual Globe. // // This program is free software licensed under the GNU LGPL. You can // find a copy of this license in LICENSE.txt in the top directory of // the source code. // // Copyright 2011 Konstantin Oblaukhov // #ifndef MARBLE_ABSTRACTGEOPOLYGONGRAPHICSITEM_H #define MARBLE_ABSTRACTGEOPOLYGONGRAPHICSITEM_H #include "GeoGraphicsItem.h" #include "marble_export.h" #include #include namespace Marble { class GeoDataLinearRing; class GeoDataPlacemark; class GeoDataPolygon; class GeoDataBuilding; class MARBLE_EXPORT AbstractGeoPolygonGraphicsItem : public GeoGraphicsItem { protected: AbstractGeoPolygonGraphicsItem(const GeoDataPlacemark *placemark, const GeoDataPolygon *polygon); AbstractGeoPolygonGraphicsItem(const GeoDataPlacemark *placemark, const GeoDataLinearRing *ring); AbstractGeoPolygonGraphicsItem(const GeoDataPlacemark *placemark, const GeoDataBuilding *building); ~AbstractGeoPolygonGraphicsItem() override; public: const GeoDataLatLonAltBox& latLonAltBox() const override; void paint(GeoPainter* painter, const ViewportParams *viewport, const QString &layer, int tileZoomLevel) override; bool contains(const QPoint &screenPosition, const ViewportParams *viewport) const override; void setLinearRing(GeoDataLinearRing* ring); void setPolygon(GeoDataPolygon* polygon); static const void *s_previousStyle; protected: - bool configurePainter(GeoPainter* painter, const ViewportParams *viewport); + bool configurePainter(GeoPainter* painter, const ViewportParams &viewport) const; inline const GeoDataPolygon *polygon() const { return m_polygon; } inline const GeoDataLinearRing *ring() const { return m_ring; } inline const GeoDataBuilding *building() const { return m_building; } static int extractElevation(const GeoDataPlacemark &placemark); private: - QPixmap texture(const QString &path, const QColor &color); + QPixmap texture(const QString &path, const QColor &color) const; const GeoDataPolygon * m_polygon; const GeoDataLinearRing * m_ring; const GeoDataBuilding *const m_building; }; } #endif diff --git a/src/lib/marble/geodata/graphicsitem/BuildingGraphicsItem.cpp b/src/lib/marble/geodata/graphicsitem/BuildingGraphicsItem.cpp index 0c9c39c7d..2f9364d75 100644 --- a/src/lib/marble/geodata/graphicsitem/BuildingGraphicsItem.cpp +++ b/src/lib/marble/geodata/graphicsitem/BuildingGraphicsItem.cpp @@ -1,503 +1,503 @@ // // This file is part of the Marble Virtual Globe. // // This program is free software licensed under the GNU LGPL. You can // find a copy of this license in LICENSE.txt in the top directory of // the source code. // // Copyright 2011 Konstantin Oblaukhov // #include "BuildingGraphicsItem.h" #include "MarbleDebug.h" #include "ViewportParams.h" #include "GeoDataTypes.h" #include "GeoDataPlacemark.h" #include "GeoDataLinearRing.h" #include "GeoDataPolygon.h" #include "GeoDataBuilding.h" #include "GeoDataMultiGeometry.h" #include "GeoDataPolyStyle.h" #include "OsmPlacemarkData.h" #include "GeoPainter.h" #include #include namespace Marble { BuildingGraphicsItem::BuildingGraphicsItem(const GeoDataPlacemark *placemark, const GeoDataBuilding *building) : AbstractGeoPolygonGraphicsItem(placemark, building) { if (const auto ring = geodata_cast(&building->multiGeometry()->at(0))) { setLinearRing(ring); } else if (const auto poly = geodata_cast(&building->multiGeometry()->at(0))) { setPolygon(poly); } setZValue(building->height()); Q_ASSERT(building->height() > 0.0); QStringList paintLayers; paintLayers << QStringLiteral("Polygon/Building/frame") << QStringLiteral("Polygon/Building/roof"); setPaintLayers(paintLayers); } BuildingGraphicsItem::~BuildingGraphicsItem() { qDeleteAll(m_cachedOuterPolygons); qDeleteAll(m_cachedInnerPolygons); qDeleteAll(m_cachedOuterRoofPolygons); qDeleteAll(m_cachedInnerRoofPolygons); } void BuildingGraphicsItem::initializeBuildingPainting(const GeoPainter* painter, const ViewportParams *viewport, bool &drawAccurate3D, bool &isCameraAboveBuilding ) const { drawAccurate3D = false; isCameraAboveBuilding = false; auto const screen = QApplication::screens().first(); double const physicalSize = 1.0; // mm int const pixelSize = qRound(physicalSize * screen->physicalDotsPerInch() / (IN2M * M2MM)); QPointF offsetAtCorner = buildingOffset(QPointF(0, 0), viewport, &isCameraAboveBuilding); qreal maxOffset = qMax( qAbs( offsetAtCorner.x() ), qAbs( offsetAtCorner.y() ) ); drawAccurate3D = painter->mapQuality() == HighQuality ? maxOffset > pixelSize : maxOffset > 1.5 * pixelSize; } -void BuildingGraphicsItem::updatePolygons(const ViewportParams *viewport, +void BuildingGraphicsItem::updatePolygons(const ViewportParams &viewport, QVector& outerPolygons, QVector& innerPolygons, - bool &hasInnerBoundaries ) + bool &hasInnerBoundaries) const { // Since subtracting one fully contained polygon from another results in a single // polygon with a "connecting line" between the inner and outer part we need // to first paint the inner area with no pen and then the outlines with the correct pen. hasInnerBoundaries = polygon() ? !polygon()->innerBoundaries().isEmpty() : false; if (polygon()) { if (hasInnerBoundaries) { - screenPolygons(*viewport, polygon(), innerPolygons, outerPolygons); + screenPolygons(viewport, polygon(), innerPolygons, outerPolygons); } else { - viewport->screenCoordinates(polygon()->outerBoundary(), outerPolygons); + viewport.screenCoordinates(polygon()->outerBoundary(), outerPolygons); } } else if (ring()) { - viewport->screenCoordinates(*ring(), outerPolygons); + viewport.screenCoordinates(*ring(), outerPolygons); } } QPointF BuildingGraphicsItem::centroid(const QPolygonF &polygon, double &area) { auto centroid = QPointF(0.0, 0.0); area = 0.0; for (auto i=0, n=polygon.size(); iheight() > 0.0); qreal const buildingFactor = building()->height() / EARTH_RADIUS; qreal const cameraHeightPixel = viewport->width() * cameraFactor; qreal buildingHeightPixel = viewport->radius() * buildingFactor; qreal const cameraDistance = cameraHeightPixel-buildingHeightPixel; if (isCameraAboveBuilding) { *isCameraAboveBuilding = cameraDistance > 0; } qreal const cc = cameraDistance * cameraHeightPixel; qreal const cb = cameraDistance * buildingHeightPixel; // The following lines calculate the same result, but are potentially slower due // to using more trigonometric method calls // qreal const alpha1 = atan2(offsetX, cameraHeightPixel); // qreal const alpha2 = atan2(offsetX, cameraHeightPixel-buildingHeightPixel); // qreal const shiftX = 2 * (cameraHeightPixel-buildingHeightPixel) * sin(0.5*(alpha2-alpha1)); qreal const offsetX = point.x() - viewport->width() / 2.0; qreal const offsetY = point.y() - viewport->height() / 2.0; qreal const shiftX = offsetX * cb / (cc + offsetX); qreal const shiftY = offsetY * cb / (cc + offsetY); return QPointF(shiftX, shiftY); } void BuildingGraphicsItem::paint(GeoPainter* painter, const ViewportParams* viewport, const QString &layer, int tileZoomLevel) { // Just display flat buildings for tile level 17 if (tileZoomLevel == 17) { setZValue(0.0); if (layer.endsWith(QLatin1String("/roof"))) { AbstractGeoPolygonGraphicsItem::paint(painter, viewport, layer, tileZoomLevel ); } return; } setZValue(building()->height()); // For level 18, 19 .. render 3D buildings in perspective if (layer.endsWith(QLatin1String("/frame"))) { qDeleteAll(m_cachedOuterPolygons); qDeleteAll(m_cachedInnerPolygons); qDeleteAll(m_cachedOuterRoofPolygons); qDeleteAll(m_cachedInnerRoofPolygons); m_cachedOuterPolygons.clear(); m_cachedInnerPolygons.clear(); m_cachedOuterRoofPolygons.clear(); m_cachedInnerRoofPolygons.clear(); - updatePolygons(viewport, m_cachedOuterPolygons, + updatePolygons(*viewport, m_cachedOuterPolygons, m_cachedInnerPolygons, m_hasInnerBoundaries); if (m_cachedOuterPolygons.isEmpty()) { return; } paintFrame(painter, viewport); } else if (layer.endsWith(QLatin1String("/roof"))) { if (m_cachedOuterPolygons.isEmpty()) { return; } paintRoof(painter, viewport); } else { mDebug() << "Didn't expect to have to paint layer " << layer << ", ignoring it."; } } void BuildingGraphicsItem::paintRoof(GeoPainter* painter, const ViewportParams* viewport) { bool drawAccurate3D; bool isCameraAboveBuilding; initializeBuildingPainting(painter, viewport, drawAccurate3D, isCameraAboveBuilding); if (!isCameraAboveBuilding) { return; // do not render roof if we look inside the building } bool isValid = true; if (s_previousStyle != style().data()) { - isValid = configurePainter(painter, viewport); + isValid = configurePainter(painter, *viewport); QFont font = painter->font(); // TODO: better font configuration if (font.pointSize() != 10) { font.setPointSize( 10 ); painter->setFont(font); } } s_previousStyle = style().data(); if (!isValid) return; // first paint the area (and the outline if there are no inner boundaries) if ( drawAccurate3D) { if (m_hasInnerBoundaries) { QPen const currentPen = painter->pen(); painter->setPen(Qt::NoPen); QVector fillPolygons = painter->createFillPolygons( m_cachedOuterRoofPolygons, m_cachedInnerRoofPolygons ); for( const QPolygonF* fillPolygon: fillPolygons ) { painter->drawPolygon(*fillPolygon); } painter->setPen(currentPen); for( const QPolygonF* outerRoof: m_cachedOuterRoofPolygons ) { painter->drawPolyline( *outerRoof ); } for( const QPolygonF* innerRoof: m_cachedInnerRoofPolygons ) { painter->drawPolyline( *innerRoof ); } qDeleteAll(fillPolygons); } else { for( const QPolygonF* outerRoof: m_cachedOuterRoofPolygons ) { painter->drawPolygon( *outerRoof ); } } } else { QPointF const offset = buildingOffset(m_cachedOuterPolygons[0]->boundingRect().center(), viewport); painter->translate(offset); if (m_hasInnerBoundaries) { QPen const currentPen = painter->pen(); painter->setPen(Qt::NoPen); QVector fillPolygons = painter->createFillPolygons( m_cachedOuterPolygons, m_cachedInnerPolygons ); for( const QPolygonF* fillPolygon: fillPolygons ) { painter->drawPolygon(*fillPolygon); } painter->setPen(currentPen); for( const QPolygonF* outerPolygon: m_cachedOuterPolygons ) { painter->drawPolyline( *outerPolygon ); } for( const QPolygonF* innerPolygon: m_cachedInnerPolygons ) { painter->drawPolyline( *innerPolygon ); } qDeleteAll(fillPolygons); } else { for( const QPolygonF* outerPolygon: m_cachedOuterPolygons ) { painter->drawPolygon( *outerPolygon ); } } painter->translate(-offset); } qreal maxSize(0.0); double maxArea = 0.0; for (int i = 0; i < m_cachedOuterRoofPolygons.size(); ++i) { const QPolygonF *outerRoof = m_cachedOuterRoofPolygons[i]; QPointF roofCenter; // Label position calculation if (!building()->name().isEmpty() || !building()->entries().isEmpty()) { QSizeF const polygonSize = outerRoof->boundingRect().size(); qreal size = polygonSize.width() * polygonSize.height(); if (size > maxSize) { maxSize = size; double area; roofCenter = centroid(*outerRoof, area); maxArea = qMax(area, maxArea); } } // Draw the housenumber labels if (drawAccurate3D && !building()->name().isEmpty() && !roofCenter.isNull()) { double const w2 = 0.5 * painter->fontMetrics().width(building()->name()); double const ascent = painter->fontMetrics().ascent(); double const descent = painter->fontMetrics().descent(); double const a2 = 0.5 * painter->fontMetrics().ascent(); QPointF const textPosition = roofCenter - QPointF(w2, -a2); if (outerRoof->containsPoint(textPosition + QPointF(-2, -ascent), Qt::OddEvenFill) && outerRoof->containsPoint(textPosition + QPointF(-2, descent), Qt::OddEvenFill) && outerRoof->containsPoint(textPosition + QPointF(2+2*w2, descent), Qt::OddEvenFill) && outerRoof->containsPoint(textPosition + QPointF(2+2*w2, -ascent), Qt::OddEvenFill) ) { painter->drawTextFragment(roofCenter.toPoint(), building()->name(), painter->font().pointSize(), painter->brush().color()); } } } // Render additional housenumbers at building entries if (!building()->entries().isEmpty() && maxArea > 1600 * building()->entries().size()) { for(const auto &entry: building()->entries()) { qreal x, y; viewport->screenCoordinates(entry.point, x, y); QPointF point(x, y); point += buildingOffset(point, viewport); painter->drawTextFragment(point.toPoint(), building()->name(), painter->font().pointSize(), painter->brush().color(), GeoPainter::RoundFrame); } } } void BuildingGraphicsItem::paintFrame(GeoPainter *painter, const ViewportParams *viewport) { // TODO: how does this match the Q_ASSERT in the constructor? if (building()->height() == 0.0) { return; } if ((polygon() && !viewport->resolves(polygon()->outerBoundary().latLonAltBox(), 4)) || (ring() && !viewport->resolves(ring()->latLonAltBox(), 4))) { return; } bool drawAccurate3D; bool isCameraAboveBuilding; initializeBuildingPainting(painter, viewport, drawAccurate3D, isCameraAboveBuilding); bool isValid = true; if (s_previousStyle != style().data()) { isValid = configurePainterForFrame(painter); } s_previousStyle = style().data(); if (!isValid) return; if ( drawAccurate3D && isCameraAboveBuilding ) { for (const QPolygonF *outline: m_cachedOuterPolygons) { if (outline->isEmpty()) { continue; } // draw the building sides int const size = outline->size(); QPolygonF * outerRoof = new QPolygonF; outerRoof->reserve(outline->size()); QPointF a = (*outline)[0]; QPointF shiftA = a + buildingOffset(a, viewport); outerRoof->append(shiftA); for (int i=1; i= 0; if (!backface) { QPolygonF buildingSide; buildingSide.reserve(4); buildingSide << a << shiftA << shiftB << b; painter->drawPolygon(buildingSide); } a = b; shiftA = shiftB; outerRoof->append(shiftA); } m_cachedOuterRoofPolygons.append(outerRoof); } for (const QPolygonF *outline: m_cachedInnerPolygons) { if (outline->isEmpty()) { continue; } // draw the building sides int const size = outline->size(); QPolygonF * innerRoof = new QPolygonF; innerRoof->reserve(outline->size()); QPointF a = (*outline)[0]; QPointF shiftA = a + buildingOffset(a, viewport); innerRoof->append(shiftA); for (int i=1; i= 0; if (backface) { QPolygonF buildingSide; buildingSide.reserve(4); buildingSide << a << shiftA << shiftB << b; painter->drawPolygon(buildingSide); } a = b; shiftA = shiftB; innerRoof->append(shiftA); } m_cachedInnerRoofPolygons.append(innerRoof); } } else { // don't draw the building sides - just draw the base frame instead QVector fillPolygons = painter->createFillPolygons( m_cachedOuterPolygons, m_cachedInnerPolygons ); for( QPolygonF* fillPolygon: fillPolygons ) { painter->drawPolygon(*fillPolygon); } qDeleteAll(fillPolygons); } } void BuildingGraphicsItem::screenPolygons(const ViewportParams &viewport, const GeoDataPolygon *polygon, QVector &innerPolygons, QVector &outerPolygons ) { Q_ASSERT(polygon); viewport.screenCoordinates(polygon->outerBoundary(), outerPolygons); QVector const & innerBoundaries = polygon->innerBoundaries(); for (const GeoDataLinearRing &innerBoundary: innerBoundaries) { QVector innerPolygonsPerBoundary; viewport.screenCoordinates(innerBoundary, innerPolygonsPerBoundary); innerPolygons.reserve(innerPolygons.size() + innerPolygonsPerBoundary.size()); for( QPolygonF* innerPolygonPerBoundary: innerPolygonsPerBoundary ) { innerPolygons << innerPolygonPerBoundary; } } } bool BuildingGraphicsItem::contains(const QPoint &screenPosition, const ViewportParams *viewport) const { if (m_cachedOuterPolygons.isEmpty()) { // Level 17 return AbstractGeoPolygonGraphicsItem::contains(screenPosition, viewport); } QPointF const point = screenPosition; for (auto polygon: m_cachedOuterRoofPolygons) { if (polygon->containsPoint(point, Qt::OddEvenFill)) { for (auto polygon: m_cachedInnerRoofPolygons) { if (polygon->containsPoint(point, Qt::OddEvenFill)) { return false; } } return true; } } for (auto polygon: m_cachedOuterPolygons) { if (polygon->containsPoint(point, Qt::OddEvenFill)) { for (auto polygon: m_cachedInnerPolygons) { if (polygon->containsPoint(point, Qt::OddEvenFill)) { return false; } } return true; } } return false; } bool BuildingGraphicsItem::configurePainterForFrame(GeoPainter *painter) const { QPen currentPen = painter->pen(); GeoDataStyle::ConstPtr style = this->style(); if (!style) { painter->setPen( QPen() ); } else { const GeoDataPolyStyle& polyStyle = style->polyStyle(); if (currentPen.style() != Qt::NoPen) { painter->setPen(Qt::NoPen); } if (!polyStyle.fill()) { return false; } else { const QColor paintedColor = polyStyle.paintedColor().darker(150); if (painter->brush().color() != paintedColor) { painter->setBrush(paintedColor); } } } return true; } } diff --git a/src/lib/marble/geodata/graphicsitem/BuildingGraphicsItem.h b/src/lib/marble/geodata/graphicsitem/BuildingGraphicsItem.h index 14652c6dc..bc6801dba 100644 --- a/src/lib/marble/geodata/graphicsitem/BuildingGraphicsItem.h +++ b/src/lib/marble/geodata/graphicsitem/BuildingGraphicsItem.h @@ -1,64 +1,64 @@ // // This file is part of the Marble Virtual Globe. // // This program is free software licensed under the GNU LGPL. You can // find a copy of this license in LICENSE.txt in the top directory of // the source code. // // Copyright 2011 Konstantin Oblaukhov // #ifndef MARBLE_BUILDINGGEOPOLYGONGRAPHICSITEM_H #define MARBLE_BUILDINGGEOPOLYGONGRAPHICSITEM_H #include "AbstractGeoPolygonGraphicsItem.h" #include "GeoDataCoordinates.h" class QPointF; namespace Marble { class MARBLE_EXPORT BuildingGraphicsItem : public AbstractGeoPolygonGraphicsItem { public: BuildingGraphicsItem(const GeoDataPlacemark *placemark, const GeoDataBuilding *building); ~BuildingGraphicsItem() override; public: void paint(GeoPainter* painter, const ViewportParams *viewport, const QString &layer, int tileZoomLevel) override; private: void paintFrame(GeoPainter* painter, const ViewportParams *viewport); void paintRoof(GeoPainter* painter, const ViewportParams *viewport); bool configurePainterForFrame(GeoPainter *painter) const; void initializeBuildingPainting(const GeoPainter* painter, const ViewportParams *viewport, bool &drawAccurate3D, bool &isCameraAboveBuilding) const; - void updatePolygons( const ViewportParams *viewport, + void updatePolygons(const ViewportParams &viewport, QVector& outlinePolygons, QVector& innerPolygons, - bool &hasInnerBoundaries); + bool &hasInnerBoundaries) const; QPointF buildingOffset(const QPointF &point, const ViewportParams *viewport, bool* isCameraAboveBuilding = nullptr) const; static QPointF centroid(const QPolygonF &polygon, double &area); static void screenPolygons(const ViewportParams &viewport, const GeoDataPolygon *polygon, QVector &polygons, QVector &outlines ); bool contains(const QPoint &screenPosition, const ViewportParams *viewport) const override; private: QVector m_cachedOuterPolygons; QVector m_cachedInnerPolygons; QVector m_cachedOuterRoofPolygons; QVector m_cachedInnerRoofPolygons; bool m_hasInnerBoundaries; }; } #endif diff --git a/src/lib/marble/layers/PlacemarkLayer.cpp b/src/lib/marble/layers/PlacemarkLayer.cpp index cd921a42f..4ae991856 100644 --- a/src/lib/marble/layers/PlacemarkLayer.cpp +++ b/src/lib/marble/layers/PlacemarkLayer.cpp @@ -1,376 +1,376 @@ // // This file is part of the Marble Virtual Globe. // // This program is free software licensed under the GNU LGPL. You can // find a copy of this license in LICENSE.txt in the top directory of // the source code. // // Copyright 2006-2007 Torsten Rahn // Copyright 2007-2008 Inge Wallin // Copyright 2011-2012 Bernhard Beschow // #include "PlacemarkLayer.h" #include #include #include "MarbleDebug.h" #include "AbstractProjection.h" #include "GeoDataStyle.h" #include "GeoPainter.h" #include "GeoDataLatLonAltBox.h" #include "ViewportParams.h" #include "VisiblePlacemark.h" #include "RenderState.h" #include "osm/OsmPlacemarkData.h" #define BATCH_RENDERING using namespace Marble; bool PlacemarkLayer::m_useXWorkaround = false; PlacemarkLayer::PlacemarkLayer(QAbstractItemModel *placemarkModel, QItemSelectionModel *selectionModel, MarbleClock *clock, const StyleBuilder *styleBuilder, QObject *parent ) : QObject( parent ), m_layout( placemarkModel, selectionModel, clock, styleBuilder ), m_debugModeEnabled(false), m_levelTagDebugModeEnabled(false), m_tileLevel(0), m_debugLevelTag(0) { m_useXWorkaround = testXBug(); mDebug() << "Use workaround: " << ( m_useXWorkaround ? "1" : "0" ); connect( &m_layout, SIGNAL(repaintNeeded()), SIGNAL(repaintNeeded()) ); } PlacemarkLayer::~PlacemarkLayer() { } QStringList PlacemarkLayer::renderPosition() const { return QStringList(QStringLiteral("PLACEMARKS")); } qreal PlacemarkLayer::zValue() const { return 2.0; } bool PlacemarkLayer::render( GeoPainter *geoPainter, ViewportParams *viewport, const QString &renderPos, GeoSceneLayer *layer ) { Q_UNUSED( renderPos ) Q_UNUSED( layer ) QVector visiblePlacemarks = m_layout.generateLayout( viewport, m_tileLevel ); // draw placemarks less important first QVector::const_iterator visit = visiblePlacemarks.constEnd(); QVector::const_iterator itEnd = visiblePlacemarks.constBegin(); QPainter *const painter = geoPainter; bool const repeatableX = viewport->currentProjection()->repeatableX(); int const radius4 = 4 * viewport->radius(); #ifdef BATCH_RENDERING QHash hash; #endif while ( visit != itEnd ) { --visit; VisiblePlacemark *const mark = *visit; if (m_levelTagDebugModeEnabled) { if (mark->placemark()->hasOsmData()) { QHash::const_iterator tagIter = mark->placemark()->osmData().findTag(QStringLiteral("level")); if (tagIter != mark->placemark()->osmData().tagsEnd()) { const int val = tagIter.value().toInt(); if (val != m_debugLevelTag) { continue; } } } } // Intentionally converting positions from floating point to pixel aligned screen grid below QRect labelRect( mark->labelRect().toRect() ); QPoint symbolPos( mark->symbolPosition().toPoint()); // when the map is such zoomed out that a given place // appears many times, we draw one placemark at each if (repeatableX) { const int symbolX = symbolPos.x(); const int textX = labelRect.x(); for (int i = symbolX % radius4, width = viewport->width(); i <= width; i += radius4) { labelRect.moveLeft(i - symbolX + textX); symbolPos.setX(i); if (!mark->symbolPixmap().isNull()) { #ifdef BATCH_RENDERING QRect symbolRect = mark->symbolPixmap().rect(); QPainter::PixmapFragment pixmapFragment = QPainter::PixmapFragment::create(QPointF(symbolPos+symbolRect.center()),QRectF(symbolRect)); auto iter = hash.find(mark->symbolId()); if (iter == hash.end()) { Fragment fragment; fragment.pixmap = mark->symbolPixmap(); fragment.fragments << pixmapFragment; hash.insert(mark->symbolId(), fragment); } else { auto & fragment = iter.value(); fragment.fragments << pixmapFragment; } #else painter->drawPixmap( symbolPos, mark->symbolPixmap() ); #endif } if (!mark->labelPixmap().isNull()) { painter->drawPixmap( labelRect, mark->labelPixmap() ); } } } else { // simple case, one draw per placemark if (!mark->symbolPixmap().isNull()) { #ifdef BATCH_RENDERING QRect symbolRect = mark->symbolPixmap().rect(); QPainter::PixmapFragment pixmapFragment = QPainter::PixmapFragment::create(QPointF(symbolPos+symbolRect.center()),QRectF(symbolRect)); auto iter = hash.find(mark->symbolId()); if (iter == hash.end()) { Fragment fragment; fragment.pixmap = mark->symbolPixmap(); fragment.fragments << pixmapFragment; hash.insert(mark->symbolId(), fragment); } else { auto & fragment = iter.value(); fragment.fragments << pixmapFragment; } #else painter->drawPixmap( symbolPos, mark->symbolPixmap() ); #endif } if (!mark->labelPixmap().isNull()) { painter->drawPixmap( labelRect, mark->labelPixmap() ); } } } #ifdef BATCH_RENDERING for (auto iter = hash.begin(), end = hash.end(); iter != end; ++iter) { auto const & fragment = iter.value(); if (m_debugModeEnabled) { QPixmap debugPixmap(fragment.pixmap.size()); QColor backgroundColor; QString idStr = iter.key().section('/', -1); if (idStr.length() > 2) { idStr.remove("shop_"); backgroundColor = QColor( (10 * (int)(idStr[0].toLatin1()))%255, (10 * (int)(idStr[1].toLatin1()))%255, (10 * (int)(idStr[2].toLatin1()))%255 ); } else { backgroundColor = QColor((quint64)(&iter.key())); } debugPixmap.fill(backgroundColor); QPainter pixpainter; pixpainter.begin(&debugPixmap); pixpainter.drawPixmap(0, 0, fragment.pixmap); pixpainter.end(); iter.value().pixmap = debugPixmap; } painter->drawPixmapFragments(fragment.fragments.data(), fragment.fragments.size(), fragment.pixmap); } #endif if (m_debugModeEnabled) { renderDebug(geoPainter, viewport, visiblePlacemarks); } return true; } RenderState PlacemarkLayer::renderState() const { return RenderState(QStringLiteral("Placemarks")); } QString PlacemarkLayer::runtimeTrace() const { return m_layout.runtimeTrace(); } QVector PlacemarkLayer::whichPlacemarkAt( const QPoint &pos ) { return m_layout.whichPlacemarkAt( pos ); } bool PlacemarkLayer::hasPlacemarkAt(const QPoint &pos) { return m_layout.hasPlacemarkAt(pos); } bool PlacemarkLayer::isDebugModeEnabled() const { return m_debugModeEnabled; } void PlacemarkLayer::setDebugModeEnabled(bool enabled) { m_debugModeEnabled = enabled; } void PlacemarkLayer::setShowPlaces( bool show ) { m_layout.setShowPlaces( show ); } void PlacemarkLayer::setShowCities( bool show ) { m_layout.setShowCities( show ); } void PlacemarkLayer::setShowTerrain( bool show ) { m_layout.setShowTerrain( show ); } void PlacemarkLayer::setShowOtherPlaces( bool show ) { m_layout.setShowOtherPlaces( show ); } void PlacemarkLayer::setShowLandingSites( bool show ) { m_layout.setShowLandingSites( show ); } void PlacemarkLayer::setShowCraters( bool show ) { m_layout.setShowCraters( show ); } void PlacemarkLayer::setShowMaria( bool show ) { m_layout.setShowMaria( show ); } void PlacemarkLayer::requestStyleReset() { m_layout.requestStyleReset(); } void PlacemarkLayer::setTileLevel(int tileLevel) { m_tileLevel = tileLevel; } // Test if there a bug in the X server which makes // text fully transparent if it gets written on // QPixmaps that were initialized by filling them // with Qt::transparent bool PlacemarkLayer::testXBug() { QString testchar( "K" ); QFont font( "Sans Serif", 10 ); int fontheight = QFontMetrics( font ).height(); int fontwidth = QFontMetrics( font ).width(testchar); int fontascent = QFontMetrics( font ).ascent(); QPixmap pixmap( fontwidth, fontheight ); pixmap.fill( Qt::transparent ); QPainter textpainter; textpainter.begin( &pixmap ); textpainter.setPen( QColor( 0, 0, 0, 255 ) ); textpainter.setFont( font ); textpainter.drawText( 0, fontascent, testchar ); textpainter.end(); QImage image = pixmap.toImage(); for ( int x = 0; x < fontwidth; ++x ) { for ( int y = 0; y < fontheight; ++y ) { if ( qAlpha( image.pixel( x, y ) ) > 0 ) return false; } } return true; } -void PlacemarkLayer::renderDebug(GeoPainter *painter, ViewportParams *viewport, const QVector &placemarks) +void PlacemarkLayer::renderDebug(GeoPainter *painter, ViewportParams *viewport, const QVector &placemarks) const { painter->save(); painter->setFont(QFont(QStringLiteral("Sans Serif"), 7)); painter->setBrush(QBrush(Qt::NoBrush)); auto const latLonAltBox = viewport->viewLatLonAltBox(); typedef QSet Placemarks; Placemarks const hidden = Placemarks::fromList(m_layout.visiblePlacemarks()).subtract(Placemarks::fromList(placemarks.toList())); for (auto placemark: hidden) { bool const inside = latLonAltBox.contains(placemark->coordinates()); painter->setPen(QPen(QColor(inside ? Qt::red : Qt::darkYellow))); painter->drawRect(placemark->boundingBox()); } painter->setPen(QPen(QColor(Qt::blue))); for (auto placemark: placemarks) { painter->drawRect(placemark->boundingBox()); } painter->setPen(QPen(QColor(Qt::green))); for (auto placemark: placemarks) { painter->drawRect(placemark->labelRect()); painter->drawRect(placemark->symbolRect()); } auto const height = painter->fontMetrics().height(); painter->setPen(QPen(QColor(Qt::black))); for (auto placemark: placemarks) { QPoint position = placemark->symbolRect().bottomLeft().toPoint() + QPoint(0, qRound(0.8 * height)); auto const popularity = placemark->placemark()->popularity(); painter->drawText(position, QStringLiteral("p: %1").arg(popularity)); position -= QPoint(0, placemark->symbolRect().height() + height); auto const zoomLevel = placemark->placemark()->zoomLevel(); painter->drawText(position, QStringLiteral("z: %1").arg(zoomLevel)); } painter->restore(); } void PlacemarkLayer::setLevelTagDebugModeEnabled(bool enabled) { if (m_levelTagDebugModeEnabled != enabled) { m_levelTagDebugModeEnabled = enabled; emit repaintNeeded(); } } bool PlacemarkLayer::levelTagDebugModeEnabled() const { return m_levelTagDebugModeEnabled; } void PlacemarkLayer::setDebugLevelTag(int level) { if (m_debugLevelTag != level) { m_debugLevelTag = level; emit repaintNeeded(); } } #include "moc_PlacemarkLayer.cpp" diff --git a/src/lib/marble/layers/PlacemarkLayer.h b/src/lib/marble/layers/PlacemarkLayer.h index 14d0f7702..d9c6318dd 100644 --- a/src/lib/marble/layers/PlacemarkLayer.h +++ b/src/lib/marble/layers/PlacemarkLayer.h @@ -1,128 +1,128 @@ // // This file is part of the Marble Virtual Globe. // // This program is free software licensed under the GNU LGPL. You can // find a copy of this license in LICENSE.txt in the top directory of // the source code. // // Copyright 2006-2007 Torsten Rahn // Copyright 2007-2008 Inge Wallin // Copyright 2012 Bernhard Beschow // // // PlacemarkLayer is responsible for drawing the Placemarks on the map // #ifndef MARBLE_PLACEMARKLAYER_H #define MARBLE_PLACEMARKLAYER_H #include #include "LayerInterface.h" #include #include #include "PlacemarkLayout.h" class QAbstractItemModel; class QItemSelectionModel; class QString; namespace Marble { class GeoPainter; class GeoSceneLayer; class MarbleClock; class ViewportParams; class StyleBuilder; struct Fragment { QVarLengthArray fragments; QPixmap pixmap; }; class PlacemarkLayer : public QObject, public LayerInterface { Q_OBJECT public: PlacemarkLayer( QAbstractItemModel *placemarkModel, QItemSelectionModel *selectionModel, MarbleClock *clock, const StyleBuilder *styleBuilder, QObject *parent = 0 ); ~PlacemarkLayer() override; /** * @reimp */ QStringList renderPosition() const override; /** * @reimp */ qreal zValue() const override; /** * @reimp */ bool render( GeoPainter *painter, ViewportParams *viewport, const QString &renderPos = QLatin1String("NONE"), GeoSceneLayer *layer = 0 ) override; RenderState renderState() const override; QString runtimeTrace() const override; /** * Returns a list of model indexes that are at position @p pos. */ QVector whichPlacemarkAt( const QPoint &pos ); bool hasPlacemarkAt(const QPoint &pos); bool isDebugModeEnabled() const; void setDebugModeEnabled(bool enabled); void setLevelTagDebugModeEnabled(bool enabled); bool levelTagDebugModeEnabled() const; void setDebugLevelTag(int level); static bool m_useXWorkaround; // Indicates need for an X windows workaround. public Q_SLOTS: // earth void setShowPlaces( bool show ); void setShowCities( bool show ); void setShowTerrain( bool show ); void setShowOtherPlaces( bool show ); // other planets void setShowLandingSites( bool show ); void setShowCraters( bool show ); void setShowMaria( bool show ); void requestStyleReset(); void setTileLevel(int tileLevel); Q_SIGNALS: void repaintNeeded(); private: - void renderDebug(GeoPainter *painter, ViewportParams *viewport, const QVector & placemarks); + void renderDebug(GeoPainter *painter, ViewportParams *viewport, const QVector & placemarks) const; static bool testXBug(); PlacemarkLayout m_layout; bool m_debugModeEnabled; bool m_levelTagDebugModeEnabled; int m_tileLevel; int m_debugLevelTag; }; } #endif diff --git a/src/plugins/render/annotate/AnnotatePlugin.cpp b/src/plugins/render/annotate/AnnotatePlugin.cpp index 64b4423e8..8cf73926f 100644 --- a/src/plugins/render/annotate/AnnotatePlugin.cpp +++ b/src/plugins/render/annotate/AnnotatePlugin.cpp @@ -1,1792 +1,1792 @@ // // This file is part of the Marble Virtual Globe. // // This program is free software licensed under the GNU LGPL. You can // find a copy of this license in LICENSE.txt in the top directory of // the source code. // // Copyright 2009 Andrew Manson // Copyright 2013 Thibaut Gridel // Copyright 2014 Calin Cruceru // // Self #include "AnnotatePlugin.h" // Qt #include #include #include #include #include #include // Marble #include "MarbleDebug.h" #include "EditGroundOverlayDialog.h" #include "EditPlacemarkDialog.h" #include "EditPolygonDialog.h" #include "GeoDataDocument.h" #include "GeoDataGroundOverlay.h" #include "GeoDataLatLonBox.h" #include "GeoDataParser.h" #include "GeoDataPlacemark.h" #include "GeoDataStyle.h" #include "GeoDataLabelStyle.h" #include "GeoDataLineStyle.h" #include "GeoDataPoint.h" #include "GeoDataPolyStyle.h" #include "GeoDataTreeModel.h" #include "GeoPainter.h" #include "GeoDataLatLonAltBox.h" #include "GeoDataLinearRing.h" #include "GeoDataPolygon.h" #include "GeoWriter.h" #include "KmlElementDictionary.h" #include "MarbleDirs.h" #include "MarbleModel.h" #include "MarblePlacemarkModel.h" #include "MarbleWidget.h" #include "AreaAnnotation.h" #include "PlacemarkTextAnnotation.h" #include "TextureLayer.h" #include "SceneGraphicsTypes.h" #include "MergingPolygonNodesAnimation.h" #include "MergingPolylineNodesAnimation.h" #include "MarbleWidgetPopupMenu.h" #include "PolylineAnnotation.h" #include "EditPolylineDialog.h" #include "ParsingRunnerManager.h" #include "ViewportParams.h" #include "osm/OsmPlacemarkData.h" #include "DownloadOsmDialog.h" namespace Marble { AnnotatePlugin::AnnotatePlugin( const MarbleModel *model ) : RenderPlugin( model ), m_isInitialized( false ), m_widgetInitialized( false ), m_marbleWidget( 0 ), m_overlayRmbMenu(nullptr), m_polygonRmbMenu(nullptr), m_nodeRmbMenu(nullptr), m_textAnnotationRmbMenu(nullptr), m_polylineRmbMenu(nullptr), m_annotationDocument(nullptr), m_movedItem( 0 ), m_focusItem( 0 ), m_polylinePlacemark( 0 ), m_polygonPlacemark( 0 ), m_clipboardItem( 0 ), m_drawingPolygon( false ), m_drawingPolyline( false ), m_addingPlacemark( false ), m_editingDialogIsShown( false ) { setEnabled( true ); setVisible( true ); connect( this, SIGNAL(visibilityChanged(bool,QString)), SLOT(enableModel(bool)) ); } AnnotatePlugin::~AnnotatePlugin() { qDeleteAll( m_graphicsItems ); if ( m_marbleWidget ) { m_marbleWidget->model()->treeModel()->removeDocument( m_annotationDocument ); } delete m_overlayRmbMenu; delete m_polygonRmbMenu; delete m_nodeRmbMenu; delete m_textAnnotationRmbMenu; delete m_polylineRmbMenu; delete m_annotationDocument; // delete m_networkAccessManager; delete m_clipboardItem; qDeleteAll(m_actions); disconnect( this, SIGNAL(mouseMoveGeoPosition(QString)), m_marbleWidget, SIGNAL(mouseMoveGeoPosition(QString)) ); } QStringList AnnotatePlugin::backendTypes() const { return QStringList(QStringLiteral("annotation")); } QString AnnotatePlugin::renderPolicy() const { return QStringLiteral("ALWAYS"); } QStringList AnnotatePlugin::renderPosition() const { return QStringList(QStringLiteral("ALWAYS_ON_TOP")); } QString AnnotatePlugin::name() const { return tr( "Annotation" ); } QString AnnotatePlugin::guiString() const { return tr( "&Annotation" ); } QString AnnotatePlugin::nameId() const { return QStringLiteral("annotation"); } QString AnnotatePlugin::description() const { return tr( "Draws annotations on maps with placemarks or polygons." ); } QString AnnotatePlugin::version() const { return QStringLiteral("1.0"); } QString AnnotatePlugin::copyrightYears() const { return QStringLiteral("2009, 2013"); } QVector AnnotatePlugin::pluginAuthors() const { return QVector() << PluginAuthor(QStringLiteral("Andrew Manson"), QStringLiteral("g.real.ate@gmail.com")) << PluginAuthor(QStringLiteral("Thibaut Gridel"), QStringLiteral("tgridel@free.fr")) << PluginAuthor(QStringLiteral("Calin Cruceru"), QStringLiteral("crucerucalincristian@gmail.com")); } QIcon AnnotatePlugin::icon() const { return QIcon(QStringLiteral(":/icons/draw-placemark.png")); } void AnnotatePlugin::initialize() { if ( !m_isInitialized ) { m_widgetInitialized = false; delete m_polygonPlacemark; m_polygonPlacemark = 0; delete m_movedItem; m_movedItem = 0; m_drawingPolygon = false; m_drawingPolyline = false; m_addingPlacemark = false; delete m_annotationDocument; m_annotationDocument = new GeoDataDocument; m_annotationDocument->setName( tr("Annotations") ); m_annotationDocument->setDocumentRole( UserDocument ); // Default polygon style GeoDataStyle::Ptr defaultPolygonStyle(new GeoDataStyle); GeoDataPolyStyle polyStyle; GeoDataLineStyle edgeStyle; GeoDataLabelStyle labelStyle; QColor polygonColor = QApplication::palette().highlight().color(); QColor edgeColor = QApplication::palette().light().color(); QColor labelColor = QApplication::palette().brightText().color(); polygonColor.setAlpha( 80 ); polyStyle.setColor( polygonColor ); edgeStyle.setColor( edgeColor ); labelStyle.setColor( labelColor ); defaultPolygonStyle->setId(QStringLiteral("polygon")); defaultPolygonStyle->setPolyStyle( polyStyle ); defaultPolygonStyle->setLineStyle( edgeStyle ); defaultPolygonStyle->setLabelStyle( labelStyle ); m_annotationDocument->addStyle( defaultPolygonStyle ); // Default polyline style GeoDataStyle::Ptr defaultPolylineStyle(new GeoDataStyle); GeoDataLineStyle lineStyle; QColor polylineColor = Qt::white; lineStyle.setColor( polylineColor ); lineStyle.setWidth( 1 ); defaultPolylineStyle->setId(QStringLiteral("polyline")); defaultPolylineStyle->setLineStyle( lineStyle ); defaultPolylineStyle->setLabelStyle( labelStyle ); m_annotationDocument->addStyle( defaultPolylineStyle ); m_isInitialized = true; } } bool AnnotatePlugin::isInitialized() const { return m_isInitialized; } QString AnnotatePlugin::runtimeTrace() const { return QStringLiteral("Annotate Items: %1").arg(m_annotationDocument->size()); } const QList *AnnotatePlugin::actionGroups() const { return &m_actions; } bool AnnotatePlugin::render(GeoPainter *painter, ViewportParams *viewport, const QString &renderPos, GeoSceneLayer *layer) { Q_UNUSED( renderPos ); Q_UNUSED( layer ); QListIterator iter( m_graphicsItems ); while ( iter.hasNext() ) { iter.next()->paint( painter, viewport, "Annotation", -1 ); } return true; } void AnnotatePlugin::enableModel( bool enabled ) { if ( enabled ) { if ( m_marbleWidget ) { setupActions( m_marbleWidget ); m_marbleWidget->model()->treeModel()->addDocument( m_annotationDocument ); } } else { setupActions( 0 ); if ( m_marbleWidget ) { m_marbleWidget->model()->treeModel()->removeDocument( m_annotationDocument ); } } } void AnnotatePlugin::setAddingPolygonHole( bool enabled ) { if ( enabled ) { announceStateChanged( SceneGraphicsItem::AddingPolygonHole ); } else { announceStateChanged( SceneGraphicsItem::Editing ); } } void AnnotatePlugin::setAddingNodes( bool enabled ) { if ( enabled ) { announceStateChanged( SceneGraphicsItem::AddingNodes ); } else { announceStateChanged( SceneGraphicsItem::Editing ); } } void AnnotatePlugin::setAreaAvailable() { static_cast( m_focusItem )->setBusy( false ); announceStateChanged( SceneGraphicsItem::Editing ); enableAllActions( m_actions.first() ); disableFocusActions(); enableActionsOnItemType( SceneGraphicsTypes::SceneGraphicAreaAnnotation ); emit repaintNeeded(); } void AnnotatePlugin::setPolylineAvailable() { static_cast( m_focusItem )->setBusy( false ); announceStateChanged( SceneGraphicsItem::Editing ); enableAllActions( m_actions.first() ); disableFocusActions(); enableActionsOnItemType( SceneGraphicsTypes::SceneGraphicPolylineAnnotation ); emit repaintNeeded(); } void AnnotatePlugin::askToRemoveFocusItem() { const int result = QMessageBox::question( m_marbleWidget, QObject::tr( "Remove current item" ), QObject::tr( "Are you sure you want to remove the current item?" ), QMessageBox::Yes | QMessageBox::No ); if ( result == QMessageBox::Yes ) { removeFocusItem(); } } void AnnotatePlugin::removeFocusItem() { // Ground Overlays will always be a special case.. if ( m_focusItem->graphicType() == SceneGraphicsTypes::SceneGraphicGroundOverlay ) { for ( int i = 0; i < m_groundOverlayModel.rowCount(); ++i ) { const QModelIndex index = m_groundOverlayModel.index( i, 0 ); GeoDataGroundOverlay *overlay = dynamic_cast( qvariant_cast( index.data( MarblePlacemarkModel::ObjectPointerRole ) ) ); m_marbleWidget->model()->treeModel()->removeFeature( overlay ); } clearOverlayFrames(); } else { disableFocusActions(); m_graphicsItems.removeAll( m_focusItem ); m_marbleWidget->model()->treeModel()->removeFeature( m_focusItem->placemark() ); delete m_focusItem->placemark(); delete m_focusItem; m_movedItem = 0; m_focusItem = 0; } } void AnnotatePlugin::clearAnnotations() { const int result = QMessageBox::question( m_marbleWidget, QObject::tr( "Clear all annotations" ), QObject::tr( "Are you sure you want to clear all annotations?" ), QMessageBox::Yes | QMessageBox::Cancel ); if ( result == QMessageBox::Yes ) { disableFocusActions(); qDeleteAll( m_graphicsItems ); m_graphicsItems.clear(); m_marbleWidget->model()->treeModel()->removeDocument( m_annotationDocument ); m_annotationDocument->clear(); m_marbleWidget->model()->treeModel()->addDocument( m_annotationDocument ); m_movedItem = 0; m_focusItem = 0; } } void AnnotatePlugin::saveAnnotationFile() { const QString filename = QFileDialog::getSaveFileName( 0, tr("Save Annotation File"), QString(), tr("All Supported Files (*.kml *.osm);;" "KML file (*.kml);;" "Open Street Map file (*.osm)") ); if ( !filename.isNull() ) { GeoWriter writer; // FIXME: This should be consistent with the way the loading is done. if (filename.endsWith(QLatin1String(".kml"), Qt::CaseInsensitive)) { writer.setDocumentType( kml::kmlTag_nameSpaceOgc22 ); } else if (filename.endsWith(QLatin1String(".osm"), Qt::CaseInsensitive)) { // "0.6" is the current version of osm, it is used to identify the osm writer // The reference value is kept in plugins/runner/osm/OsmElementDictionary.hz writer.setDocumentType( "0.6" ); } QFile file( filename ); file.open( QIODevice::WriteOnly ); if ( !writer.write( &file, m_annotationDocument ) ) { mDebug() << "Could not write the file " << filename; } file.close(); } } void AnnotatePlugin::loadAnnotationFile() { const QString filename = QFileDialog::getOpenFileName( 0, tr("Open Annotation File"), QString(), tr("All Supported Files (*.kml *.osm);;" "Kml Annotation file (*.kml);;" "Open Street Map file (*.osm)") ); if ( filename.isNull() ) { return; } else{ openAnnotationFile(filename); } } void AnnotatePlugin::downloadOsm() { QPointer dialog=new DownloadOsmDialog(m_marbleWidget,this); dialog->show(); } void AnnotatePlugin::openAnnotationFile(const QString& filename) { if ( filename.isNull() ) { return; } ParsingRunnerManager manager( m_marbleWidget->model()->pluginManager(),this ); GeoDataDocument *document = manager.openFile( filename); Q_ASSERT( document ); // FIXME: The same problem as in the case of copying/cutting graphic items applies here: // the files do not load properly because the geometry copy is not a deep copy. foreach ( GeoDataFeature *feature, document->featureList() ) { if (const GeoDataPlacemark *placemark = geodata_cast(feature)) { GeoDataPlacemark *newPlacemark = new GeoDataPlacemark( *placemark ); if (geodata_cast(placemark->geometry())) { PlacemarkTextAnnotation *placemark = new PlacemarkTextAnnotation( newPlacemark ); m_graphicsItems.append( placemark ); } else if (geodata_cast(placemark->geometry())) { newPlacemark->setParent( m_annotationDocument ); if ( !placemark->styleUrl().isEmpty() ) { newPlacemark->setStyleUrl( placemark->styleUrl() ); } AreaAnnotation *polygonAnnotation = new AreaAnnotation( newPlacemark ); m_graphicsItems.append( polygonAnnotation ); } else if (geodata_cast(placemark->geometry())) { newPlacemark->setParent( m_annotationDocument ); if ( !placemark->styleUrl().isEmpty() ) { newPlacemark->setStyleUrl( placemark->styleUrl() ); } PolylineAnnotation *polylineAnnotation = new PolylineAnnotation( newPlacemark ); m_graphicsItems.append( polylineAnnotation ); } m_marbleWidget->model()->treeModel()->addFeature( m_annotationDocument, newPlacemark ); } else if (const GeoDataGroundOverlay *overlay = geodata_cast(feature)) { GeoDataGroundOverlay *newOverlay = new GeoDataGroundOverlay( *overlay ); m_marbleWidget->model()->treeModel()->addFeature( m_annotationDocument, newOverlay ); displayOverlayFrame( newOverlay ); } } m_marbleWidget->centerOn( document->latLonAltBox() ); delete document; emit repaintNeeded( QRegion() ); } bool AnnotatePlugin::eventFilter( QObject *watched, QEvent *event ) { if ( !m_widgetInitialized ) { MarbleWidget *marbleWidget = qobject_cast( watched ); if ( marbleWidget ) { m_marbleWidget = marbleWidget; addContextItems(); setupGroundOverlayModel(); setupOverlayRmbMenu(); setupPolygonRmbMenu(); setupPolylineRmbMenu(); setupNodeRmbMenu(); setupTextAnnotationRmbMenu(); setupActions( marbleWidget ); m_marbleWidget->model()->treeModel()->addDocument( m_annotationDocument ); m_widgetInitialized = true; connect( this, SIGNAL(mouseMoveGeoPosition(QString)), m_marbleWidget, SIGNAL(mouseMoveGeoPosition(QString)) ); return true; } return false; } // Accept mouse and key press events. if ( event->type() != QEvent::MouseButtonPress && event->type() != QEvent::MouseButtonRelease && event->type() != QEvent::MouseMove && event->type() != QEvent::KeyPress && event->type() != QEvent::KeyRelease ) { return false; } // Handle key press events. if ( event->type() == QEvent::KeyPress || event->type() == QEvent::KeyRelease ) { QKeyEvent * const keyEvent = static_cast( event ); Q_ASSERT( keyEvent ); if ( m_focusItem && ( m_focusItem->graphicType() == SceneGraphicsTypes::SceneGraphicAreaAnnotation || m_focusItem->graphicType() == SceneGraphicsTypes::SceneGraphicPolylineAnnotation ) ) { if ( keyEvent->type() == QEvent::KeyPress && keyEvent->key() == Qt::Key_Control ) { announceStateChanged( SceneGraphicsItem::MergingNodes ); } if ( keyEvent->type() == QEvent::KeyRelease && keyEvent->key() == Qt::Key_Control ) { if ( ( m_focusItem->graphicType() == SceneGraphicsTypes::SceneGraphicAreaAnnotation && static_cast( m_focusItem )->isBusy() ) || ( m_focusItem->graphicType() == SceneGraphicsTypes::SceneGraphicPolylineAnnotation && static_cast( m_focusItem )->isBusy() ) ) { return true; } announceStateChanged( SceneGraphicsItem::Editing ); } } // If we have an item which has the focus and the Escape key is pressed, set item's focus // to false. if ( m_focusItem && keyEvent->type() == QEvent::KeyPress && keyEvent->key() == Qt::Key_Escape && !m_editingDialogIsShown ) { disableFocusActions(); m_focusItem->setFocus( false ); m_marbleWidget->model()->treeModel()->updateFeature( m_focusItem->placemark() ); m_focusItem = 0; return true; } // If we have an item which has the focus and the Delete key is pressed, delete the item if ( m_focusItem && keyEvent->type() == QEvent::KeyPress && keyEvent->key() == Qt::Key_Delete && !m_editingDialogIsShown ) { askToRemoveFocusItem(); return true; } return false; } // Handle mouse events. QMouseEvent * const mouseEvent = dynamic_cast( event ); Q_ASSERT( mouseEvent ); // Get the geocoordinates from mouse pos screen coordinates. qreal lon, lat; const bool isOnGlobe = m_marbleWidget->geoCoordinates( mouseEvent->pos().x(), mouseEvent->pos().y(), lon, lat, GeoDataCoordinates::Radian ); if ( !isOnGlobe ) { return false; } // Deal with adding polygons and polylines. if ( ( m_drawingPolygon && handleDrawingPolygon( mouseEvent ) ) || ( m_drawingPolyline && handleDrawingPolyline( mouseEvent ) ) ) { return true; } // It is important to deal with Ground Overlay mouse release event here because it uses the // texture layer in order to make the rendering more efficient. if ( mouseEvent->type() == QEvent::MouseButtonRelease && m_groundOverlayModel.rowCount() ) { handleReleaseOverlay( mouseEvent ); } // It is important to deal with the MouseMove event here because it changes the state of the // selected item irrespective of the longitude/latitude the cursor moved to (excepting when // it is outside the globe, which is treated above). if ( mouseEvent->type() == QEvent::MouseMove && m_movedItem && handleMovingSelectedItem( mouseEvent ) ) { setupCursor( m_movedItem ); return true; } // Pass the event to Graphic Items. for ( SceneGraphicsItem *item: m_graphicsItems ) { if ( !item->containsPoint( mouseEvent->pos() ) ) { continue; } // If an edit dialog is visible, do not permit right clicking on items. if ( m_editingDialogIsShown && mouseEvent->type() == QEvent::MouseButtonPress && mouseEvent->button() == Qt::RightButton) { return true; } if ( !item->hasFocus() && item->graphicType() != SceneGraphicsTypes::SceneGraphicGroundOverlay ) { if ( mouseEvent->type() == QEvent::MouseButtonPress && mouseEvent->button() == Qt::LeftButton ) { item->setFocus( true ); disableFocusActions(); enableActionsOnItemType( item->graphicType() ); if ( m_focusItem && m_focusItem != item ) { m_focusItem->setFocus( false ); if ( m_focusItem->graphicType() == SceneGraphicsTypes::SceneGraphicGroundOverlay ) { clearOverlayFrames(); } } m_focusItem = item; m_marbleWidget->model()->treeModel()->updateFeature( item->placemark() ); return true; } return false; } if ( item->sceneEvent( event ) ) { setupCursor( item ); if ( mouseEvent->type() == QEvent::MouseButtonPress ) { handleSuccessfulPressEvent( mouseEvent, item ); } else if ( mouseEvent->type() == QEvent::MouseMove ) { handleSuccessfulHoverEvent( mouseEvent, item ); } else if ( mouseEvent->type() == QEvent::MouseButtonRelease ) { handleSuccessfulReleaseEvent( mouseEvent, item ); } handleRequests( mouseEvent, item ); return true; } } // If the event gets here, it most probably means it is a map interaction event, or something // that has nothing to do with the annotate plugin items. We "deal" with this situation because, // for example, we may need to deselect some selected items. handleUncaughtEvents( mouseEvent ); return false; } bool AnnotatePlugin::handleDrawingPolygon( QMouseEvent *mouseEvent ) { const GeoDataCoordinates coords = mouseGeoDataCoordinates( mouseEvent ); if ( mouseEvent->type() == QEvent::MouseMove ) { setupCursor( 0 ); emit mouseMoveGeoPosition( coords.toString() ); return true; } else if ( mouseEvent->button() == Qt::LeftButton && mouseEvent->type() == QEvent::MouseButtonPress ) { m_marbleWidget->model()->treeModel()->removeFeature( m_polygonPlacemark ); GeoDataPolygon *poly = dynamic_cast( m_polygonPlacemark->geometry() ); poly->outerBoundary().append( coords ); m_marbleWidget->model()->treeModel()->addFeature( m_annotationDocument, m_polygonPlacemark ); emit nodeAdded( coords ); return true; } return false; } bool AnnotatePlugin::handleDrawingPolyline( QMouseEvent *mouseEvent ) { const GeoDataCoordinates coords = mouseGeoDataCoordinates( mouseEvent ); if ( mouseEvent->type() == QEvent::MouseMove ) { setupCursor( 0 ); emit mouseMoveGeoPosition( coords.toString() ); return true; } else if ( mouseEvent->button() == Qt::LeftButton && mouseEvent->type() == QEvent::MouseButtonPress ) { m_marbleWidget->model()->treeModel()->removeFeature( m_polylinePlacemark ); GeoDataLineString *line = dynamic_cast( m_polylinePlacemark->geometry() ); line->append( coords ); m_marbleWidget->model()->treeModel()->addFeature( m_annotationDocument, m_polylinePlacemark ); emit nodeAdded( coords ); return true; } return false; } void AnnotatePlugin::handleReleaseOverlay( QMouseEvent *mouseEvent ) { const GeoDataCoordinates coords = mouseGeoDataCoordinates( mouseEvent ); for ( int i = 0; i < m_groundOverlayModel.rowCount(); ++i ) { const QModelIndex index = m_groundOverlayModel.index( i, 0 ); GeoDataGroundOverlay *overlay = dynamic_cast( qvariant_cast( index.data( MarblePlacemarkModel::ObjectPointerRole ) ) ); if ( overlay->latLonBox().contains( coords ) ) { if ( mouseEvent->button() == Qt::LeftButton ) { displayOverlayFrame( overlay ); } else if ( mouseEvent->button() == Qt::RightButton ) { showOverlayRmbMenu( overlay, mouseEvent->x(), mouseEvent->y() ); } } } } bool AnnotatePlugin::handleMovingSelectedItem( QMouseEvent *mouseEvent ) { // Handling easily the mouse move by calling for each scene graphic item their own mouseMoveEvent // handler and updating their feature. if ( m_movedItem->sceneEvent( mouseEvent ) ) { m_marbleWidget->model()->treeModel()->updateFeature( m_movedItem->placemark() ); emit itemMoved( m_movedItem->placemark() ); if ( m_movedItem->graphicType() == SceneGraphicsTypes::SceneGraphicTextAnnotation ) { emit placemarkMoved(); } const GeoDataCoordinates coords = mouseGeoDataCoordinates( mouseEvent ); emit mouseMoveGeoPosition( coords.toString() ); return true; } return false; } void AnnotatePlugin::handleSuccessfulPressEvent( QMouseEvent *mouseEvent, SceneGraphicsItem *item ) { Q_UNUSED( mouseEvent ); // Update the item's placemark. m_marbleWidget->model()->treeModel()->updateFeature( item->placemark() ); // Store a pointer to the item for possible following move events only if its state is // either 'Editing' or 'AddingNodes' and the mouse left button has been used. if ( ( item->state() == SceneGraphicsItem::Editing || item->state() == SceneGraphicsItem::AddingNodes ) && mouseEvent->button() == Qt::LeftButton ) { m_movedItem = item; } } void AnnotatePlugin::handleSuccessfulHoverEvent( QMouseEvent *mouseEvent, SceneGraphicsItem *item ) { Q_UNUSED( mouseEvent ); m_marbleWidget->model()->treeModel()->updateFeature( item->placemark() ); } void AnnotatePlugin::handleSuccessfulReleaseEvent( QMouseEvent *mouseEvent, SceneGraphicsItem *item ) { Q_UNUSED( mouseEvent ); // The item gets 'deselected' (from moving) at mouse release. m_movedItem = 0; // Update the item's placemark. m_marbleWidget->model()->treeModel()->updateFeature( item->placemark() ); } void AnnotatePlugin::handleRequests( QMouseEvent *mouseEvent, SceneGraphicsItem *item ) { if ( item->graphicType() == SceneGraphicsTypes::SceneGraphicAreaAnnotation ) { AreaAnnotation * const area = static_cast( item ); if ( area->request() == SceneGraphicsItem::ShowPolygonRmbMenu ) { showPolygonRmbMenu( mouseEvent->pos().x(), mouseEvent->pos().y() ); } else if ( area->request() == SceneGraphicsItem::ShowNodeRmbMenu ) { showNodeRmbMenu( mouseEvent->pos().x(), mouseEvent->pos().y() ); } else if ( area->request() == SceneGraphicsItem::StartPolygonAnimation ) { QPointer animation = area->animation(); connect( animation, SIGNAL(nodesMoved()), this, SIGNAL(repaintNeeded()) ); connect( animation, SIGNAL(animationFinished()), this, SLOT(setAreaAvailable()) ); area->setBusy( true ); disableActions( m_actions.first() ); animation->startAnimation(); } else if ( area->request() == SceneGraphicsItem::OuterInnerMergingWarning ) { QMessageBox::warning( m_marbleWidget, tr( "Operation not permitted" ), tr( "Cannot merge a node from polygon's outer boundary " "with a node from one of its inner boundaries." ) ); } else if ( area->request() == SceneGraphicsItem::InnerInnerMergingWarning ) { QMessageBox::warning( m_marbleWidget, tr( "Operation not permitted" ), tr( "Cannot merge two nodes from two different inner " "boundaries." ) ); } else if ( area->request() == SceneGraphicsItem::InvalidShapeWarning ) { QMessageBox::warning( m_marbleWidget, tr( "Operation not permitted" ), tr( "Cannot merge the selected nodes. Most probably " "this would make the polygon's outer boundary not " "contain all its inner boundary nodes." ) ); } else if ( area->request() == SceneGraphicsItem::RemovePolygonRequest ) { removeFocusItem(); } else if ( area->request() == SceneGraphicsItem::ChangeCursorPolygonNodeHover ) { m_marbleWidget->setCursor( Qt::PointingHandCursor ); } else if ( area->request() == SceneGraphicsItem::ChangeCursorPolygonBodyHover ) { m_marbleWidget->setCursor( Qt::SizeAllCursor ); } } else if ( item->graphicType() == SceneGraphicsTypes::SceneGraphicPolylineAnnotation ) { PolylineAnnotation * const polyline = static_cast( item ); if ( polyline->request() == SceneGraphicsItem::ShowPolylineRmbMenu ) { showPolylineRmbMenu( mouseEvent->x(), mouseEvent->y() ); } else if ( polyline->request() == SceneGraphicsItem::ShowNodeRmbMenu ) { showNodeRmbMenu( mouseEvent->x(), mouseEvent->y() ); } else if ( polyline->request() == SceneGraphicsItem::StartPolylineAnimation ) { QPointer animation = polyline->animation(); connect( animation, SIGNAL(nodesMoved()), this, SIGNAL(repaintNeeded()) ); connect( animation, SIGNAL(animationFinished()), this, SLOT(setPolylineAvailable()) ); polyline->setBusy( true ); disableActions( m_actions.first() ); animation->startAnimation(); } else if ( polyline->request() == SceneGraphicsItem::RemovePolylineRequest ) { removeFocusItem(); } else if ( polyline->request() == SceneGraphicsItem::ChangeCursorPolylineNodeHover ) { m_marbleWidget->setCursor( Qt::PointingHandCursor ); } else if ( polyline->request() == SceneGraphicsItem::ChangeCursorPolylineLineHover ) { m_marbleWidget->setCursor( Qt::SizeAllCursor ); } } else if ( item->graphicType() == SceneGraphicsTypes::SceneGraphicTextAnnotation ) { PlacemarkTextAnnotation * const textAnnotation = static_cast( item ); if ( textAnnotation->request() == SceneGraphicsItem::ShowPlacemarkRmbMenu ) { showTextAnnotationRmbMenu( mouseEvent->x(), mouseEvent->y() ); } else if ( textAnnotation->request() == SceneGraphicsItem::ChangeCursorPlacemarkHover ) { m_marbleWidget->setCursor( Qt::SizeAllCursor ); } } else if ( item->graphicType() == SceneGraphicsTypes::SceneGraphicGroundOverlay ){ GroundOverlayFrame * const groundOverlay = static_cast( item ); if ( groundOverlay->request() == SceneGraphicsItem::ChangeCursorOverlayVerticalHover ) { m_marbleWidget->setCursor( Qt::SizeVerCursor ); } else if ( groundOverlay->request() == SceneGraphicsItem::ChangeCursorOverlayHorizontalHover ) { m_marbleWidget->setCursor( Qt::SizeHorCursor ); } else if ( groundOverlay->request() == SceneGraphicsItem::ChangeCursorOverlayBDiagHover ) { m_marbleWidget->setCursor( Qt::SizeBDiagCursor ); } else if ( groundOverlay->request() == SceneGraphicsItem::ChangeCursorOverlayFDiagHover ) { m_marbleWidget->setCursor( Qt::SizeFDiagCursor ); } else if ( groundOverlay->request() == SceneGraphicsItem::ChangeCursorOverlayBodyHover ) { m_marbleWidget->setCursor( Qt::SizeAllCursor ); } else if ( groundOverlay->request() == SceneGraphicsItem::ChangeCursorOverlayRotateHover ) { m_marbleWidget->setCursor( Qt::CrossCursor ); } } } void AnnotatePlugin::handleUncaughtEvents( QMouseEvent *mouseEvent ) { Q_UNUSED( mouseEvent ); // If the event is not caught by any of the annotate plugin specific items, clear the frames // (which have the meaning of deselecting the overlay). if ( !m_groundOverlayFrames.isEmpty() && mouseEvent->type() != QEvent::MouseMove && mouseEvent->type() != QEvent::MouseButtonRelease ) { clearOverlayFrames(); } if ( m_focusItem && m_focusItem->graphicType() != SceneGraphicsTypes::SceneGraphicGroundOverlay ) { if ( ( m_focusItem->graphicType() == SceneGraphicsTypes::SceneGraphicAreaAnnotation && static_cast( m_focusItem )->isBusy() ) || ( m_focusItem->graphicType() == SceneGraphicsTypes::SceneGraphicPolylineAnnotation && static_cast( m_focusItem )->isBusy() ) ) { return; } m_focusItem->dealWithItemChange( 0 ); m_marbleWidget->model()->treeModel()->updateFeature( m_focusItem->placemark() ); if ( mouseEvent->type() == QEvent::MouseButtonPress ) { m_focusItem->setFocus( false ); disableFocusActions(); announceStateChanged( SceneGraphicsItem::Editing ); m_marbleWidget->model()->treeModel()->updateFeature( m_focusItem->placemark() ); m_focusItem = 0; } } } void AnnotatePlugin::setupActions( MarbleWidget *widget ) { qDeleteAll( m_actions ); m_actions.clear(); if ( !widget ) { return; } QActionGroup *group = new QActionGroup( 0 ); group->setExclusive( true ); QAction *selectItem = new QAction( QIcon(QStringLiteral(":/icons/edit-select.png")), tr("Select Item"), this ); selectItem->setCheckable( true ); selectItem->setChecked( true ); QAction *drawPolygon = new QAction( QIcon(QStringLiteral(":/icons/draw-polygon.png")), tr("Add Polygon"), this ); connect( drawPolygon, SIGNAL(triggered()), this, SLOT(addPolygon()) ); QAction *addHole = new QAction( QIcon(QStringLiteral(":/icons/polygon-draw-hole.png")), tr("Add Polygon Hole"), this ); addHole->setCheckable( true ); addHole->setEnabled( false ); connect( addHole, SIGNAL(toggled(bool)), this, SLOT(setAddingPolygonHole(bool)) ); QAction *addNodes = new QAction( QIcon(QStringLiteral(":/icons/polygon-add-nodes.png")), tr("Add Nodes"), this ); addNodes->setCheckable( true ); addNodes->setEnabled( false ); connect( addNodes, SIGNAL(toggled(bool)), this, SLOT(setAddingNodes(bool)) ); QAction *addTextAnnotation = new QAction( QIcon(QStringLiteral(":/icons/add-placemark.png")), tr("Add Placemark"), this ); connect( addTextAnnotation, SIGNAL(triggered()), this, SLOT(addTextAnnotation()) ); QAction *addPath = new QAction( QIcon(QStringLiteral(":/icons/draw-path.png")), tr("Add Path"), this ); connect( addPath, SIGNAL(triggered()), this, SLOT(addPolyline()) ); QAction *addOverlay = new QAction( QIcon(QStringLiteral(":/icons/draw-overlay.png")), tr("Add Ground Overlay"), this ); connect( addOverlay, SIGNAL(triggered()), this, SLOT(addOverlay()) ); QAction *removeItem = new QAction( QIcon(QStringLiteral(":/icons/edit-delete-shred.png")), tr("Remove Item"), this ); removeItem->setEnabled( false ); connect( removeItem, SIGNAL(triggered()), this, SLOT(askToRemoveFocusItem()) ); QAction *loadAnnotationFile = new QAction( QIcon(QStringLiteral(":/icons/open-for-editing.png")), tr("Load Annotation File" ), this ); connect( loadAnnotationFile, SIGNAL(triggered()), this, SLOT(loadAnnotationFile()) ); QAction *saveAnnotationFile = new QAction( QIcon(QStringLiteral(":/icons/document-save-as.png")), tr("Save Annotation File"), this ); connect( saveAnnotationFile, SIGNAL(triggered()), this, SLOT(saveAnnotationFile()) ); QAction *clearAnnotations = new QAction( QIcon(QStringLiteral(":/icons/remove.png")), tr("Clear all Annotations"), this ); QAction *downloadOsm = new QAction( QIcon(":/icons/download.png"), tr("Download OpenStreetMap Data"), this ); connect( downloadOsm, SIGNAL(triggered(bool)), this, SLOT(downloadOsm()) ); downloadOsm->setToolTip(tr("Download OpenStreetMap data of the visible region")); connect( drawPolygon, SIGNAL(toggled(bool)), clearAnnotations, SLOT(setDisabled(bool)) ); connect( clearAnnotations, SIGNAL(triggered()), this, SLOT(clearAnnotations()) ); QAction *sep1 = new QAction( this ); sep1->setSeparator( true ); QAction *sep2 = new QAction( this ); sep2->setSeparator( true ); sep2->setObjectName( "toolbarSeparator" ); QAction *sep3 = new QAction( this ); sep3->setSeparator( true ); QAction *sep4 = new QAction( this ); sep4->setSeparator( true ); group->addAction( loadAnnotationFile ); group->addAction( saveAnnotationFile ); group->addAction( sep1 ); group->addAction( addTextAnnotation ); group->addAction( drawPolygon ); group->addAction( addPath ); group->addAction( addOverlay ); group->addAction( sep2 ); group->addAction( selectItem ); group->addAction( addHole ); group->addAction( addNodes ); group->addAction( removeItem ); group->addAction( sep3 ); group->addAction( clearAnnotations ); group->addAction(downloadOsm); group->addAction( sep4 ); m_actions.append( group ); emit actionGroupsChanged(); } void AnnotatePlugin::disableActions( QActionGroup *group ) { for ( int i = 0; i < group->actions().size(); ++i ) { if ( group->actions().at(i)->text() != tr("Select Item") ) { group->actions().at(i)->setEnabled( false ); } else { group->actions().at(i)->setEnabled( true ); } } } void AnnotatePlugin::enableAllActions( QActionGroup *group ) { for ( int i = 0; i < group->actions().size(); ++i ) { group->actions().at(i)->setEnabled( true ); } } void AnnotatePlugin::enableActionsOnItemType( const QString &type ) { if ( type == SceneGraphicsTypes::SceneGraphicAreaAnnotation ) { m_actions.first()->actions().at(9)->setEnabled( true ); m_actions.first()->actions().at(10)->setEnabled( true ); } else if ( type == SceneGraphicsTypes::SceneGraphicPolylineAnnotation ) { m_actions.first()->actions().at(10)->setEnabled( true ); } m_actions.first()->actions().at(11)->setEnabled( true ); } void AnnotatePlugin::disableFocusActions() { m_actions.first()->actions().at(8)->setChecked( true ); m_actions.first()->actions().at(9)->setEnabled( false ); m_actions.first()->actions().at(10)->setEnabled( false ); m_actions.first()->actions().at(11)->setEnabled( false ); } void AnnotatePlugin::addContextItems() { MarbleWidgetPopupMenu * const menu = m_marbleWidget->popupMenu(); m_pasteGraphicItem = new QAction( tr( "Paste" ), this ); m_pasteGraphicItem->setVisible( false ); connect( m_pasteGraphicItem, SIGNAL(triggered()), SLOT(pasteItem()) ); QAction *separator = new QAction( this ); separator->setSeparator( true ); bool const smallScreen = MarbleGlobal::getInstance()->profiles() & MarbleGlobal::SmallScreen; if ( !smallScreen ) { menu->addAction( Qt::RightButton, m_pasteGraphicItem ); menu->addAction( Qt::RightButton, separator ); } } void AnnotatePlugin::setupTextAnnotationRmbMenu() { delete m_textAnnotationRmbMenu; m_textAnnotationRmbMenu = new QMenu; QAction *cutItem = new QAction( tr( "Cut"), m_textAnnotationRmbMenu ); m_textAnnotationRmbMenu->addAction( cutItem ); connect( cutItem, SIGNAL(triggered()), this, SLOT(cutItem()) ); QAction *copyItem = new QAction( tr( "Copy"), m_textAnnotationRmbMenu ); m_textAnnotationRmbMenu->addAction( copyItem ); connect( copyItem, SIGNAL(triggered()), this, SLOT(copyItem()) ); QAction *removeItem = new QAction( tr( "Remove" ), m_textAnnotationRmbMenu ); m_textAnnotationRmbMenu->addAction( removeItem ); connect( removeItem, SIGNAL(triggered()), this, SLOT(askToRemoveFocusItem()) ); m_textAnnotationRmbMenu->addSeparator(); QAction *properties = new QAction( tr( "Properties" ), m_textAnnotationRmbMenu ); m_textAnnotationRmbMenu->addAction( properties ); connect( properties, SIGNAL(triggered()), this, SLOT(editTextAnnotation()) ); } void AnnotatePlugin::showTextAnnotationRmbMenu( qreal x, qreal y ) { m_textAnnotationRmbMenu->popup( m_marbleWidget->mapToGlobal( QPoint( x, y ) ) ); } void AnnotatePlugin::editTextAnnotation() { QPointer dialog = new EditPlacemarkDialog( m_focusItem->placemark(), &m_osmRelations, m_marbleWidget ); connect( dialog, SIGNAL(textAnnotationUpdated(GeoDataFeature*)), m_marbleWidget->model()->treeModel(), SLOT(updateFeature(GeoDataFeature*)) ); connect( this, SIGNAL(placemarkMoved()), dialog, SLOT(updateDialogFields()) ); connect( dialog, SIGNAL(finished(int)), this, SLOT(stopEditingTextAnnotation(int)) ); connect( dialog, SIGNAL( relationCreated( const OsmPlacemarkData& ) ), this, SLOT( addRelation( const OsmPlacemarkData& ) ) ); dialog->setLabelColor(dynamic_cast(m_focusItem)->labelColor()); disableActions( m_actions.first() ); dialog->show(); m_editingDialogIsShown = true; m_editedItem = m_focusItem; } void AnnotatePlugin::addTextAnnotation() { m_addingPlacemark = true; // Get the normalized coordinates of the focus point. There will be automatically added a new // placemark. qreal lat = m_marbleWidget->focusPoint().latitude(); qreal lon = m_marbleWidget->focusPoint().longitude(); GeoDataCoordinates::normalizeLonLat( lon, lat ); GeoDataPlacemark *placemark = new GeoDataPlacemark; placemark->setCoordinate( lon, lat ); placemark->setVisible( true ); placemark->setBalloonVisible( false ); m_marbleWidget->model()->treeModel()->addFeature( m_annotationDocument, placemark ); PlacemarkTextAnnotation *textAnnotation = new PlacemarkTextAnnotation( placemark ); textAnnotation->setFocus( true ); m_graphicsItems.append( textAnnotation ); QPointer dialog = new EditPlacemarkDialog( placemark, &m_osmRelations, m_marbleWidget ); connect( dialog, SIGNAL(textAnnotationUpdated(GeoDataFeature*)), m_marbleWidget->model()->treeModel(), SLOT(updateFeature(GeoDataFeature*)) ); connect( this, SIGNAL(placemarkMoved()), dialog, SLOT(updateDialogFields()) ); connect( dialog, SIGNAL(finished(int)), this, SLOT(stopEditingTextAnnotation(int)) ); connect( dialog, SIGNAL( relationCreated( const OsmPlacemarkData& ) ), this, SLOT( addRelation( const OsmPlacemarkData& ) ) ); if ( m_focusItem ) { m_focusItem->setFocus( false ); if ( m_focusItem->graphicType() == SceneGraphicsTypes::SceneGraphicGroundOverlay ) { clearOverlayFrames(); } } m_focusItem = textAnnotation; m_editedItem = textAnnotation; disableActions( m_actions.first() ); dialog->move( m_marbleWidget->mapToGlobal( QPoint( 0, 0 ) ) ); dialog->show(); m_editingDialogIsShown = true; } void AnnotatePlugin::stopEditingTextAnnotation( int result ) { m_focusItem = m_editedItem; m_editedItem = 0; announceStateChanged( SceneGraphicsItem::Editing ); enableAllActions( m_actions.first() ); disableFocusActions(); if ( !result && m_addingPlacemark ) { removeFocusItem(); } else { enableActionsOnItemType( SceneGraphicsTypes::SceneGraphicTextAnnotation ); } m_addingPlacemark = false; m_editingDialogIsShown = false; } void AnnotatePlugin::setupGroundOverlayModel() { m_editingDialogIsShown = false; m_groundOverlayModel.setSourceModel( m_marbleWidget->model()->groundOverlayModel() ); m_groundOverlayModel.setDynamicSortFilter( true ); m_groundOverlayModel.setSortRole( MarblePlacemarkModel::PopularityIndexRole ); m_groundOverlayModel.sort( 0, Qt::AscendingOrder ); } void AnnotatePlugin::setupOverlayRmbMenu() { delete m_overlayRmbMenu; m_overlayRmbMenu = new QMenu; QAction *editOverlay = new QAction( tr( "Properties" ), m_overlayRmbMenu ); m_overlayRmbMenu->addAction( editOverlay ); connect( editOverlay, SIGNAL(triggered()), this, SLOT(editOverlay()) ); m_overlayRmbMenu->addSeparator(); QAction *removeOverlay = new QAction( tr( "Remove" ), m_overlayRmbMenu ); m_overlayRmbMenu->addAction( removeOverlay ); connect( removeOverlay, SIGNAL(triggered()), this, SLOT(removeOverlay()) ); } void AnnotatePlugin::addOverlay() { GeoDataGroundOverlay *overlay = new GeoDataGroundOverlay(); qreal centerLongitude = m_marbleWidget->viewport()->centerLongitude()*RAD2DEG; qreal centerLatitude = m_marbleWidget->viewport()->centerLatitude()*RAD2DEG; GeoDataLatLonAltBox box = m_marbleWidget->viewport()->viewLatLonAltBox(); qreal maxDelta = 20; qreal deltaLongitude = qMin(box.width(GeoDataCoordinates::Degree), maxDelta); qreal deltaLatitude = qMin(box.height(GeoDataCoordinates::Degree), maxDelta); qreal north = centerLatitude + deltaLatitude/4; qreal south = centerLatitude - deltaLatitude/4; qreal west = centerLongitude - deltaLongitude/4; qreal east = centerLongitude + deltaLongitude/4; overlay->latLonBox().setBoundaries( north, south, east, west, GeoDataCoordinates::Degree ); overlay->setName( tr( "Untitled Ground Overlay" ) ); QPointer dialog = new EditGroundOverlayDialog( overlay, m_marbleWidget->textureLayer(), m_marbleWidget ); dialog->exec(); if( dialog->result() ) { m_marbleWidget->model()->treeModel()->addFeature( m_annotationDocument, overlay ); displayOverlayFrame( overlay ); } else { delete overlay; } delete dialog; } void AnnotatePlugin::showOverlayRmbMenu( GeoDataGroundOverlay *overlay, qreal x, qreal y ) { m_rmbOverlay = overlay; m_overlayRmbMenu->popup( m_marbleWidget->mapToGlobal( QPoint( x, y ) ) ); } void AnnotatePlugin::editOverlay() { displayOverlayFrame( m_rmbOverlay ); QPointer dialog = new EditGroundOverlayDialog( m_rmbOverlay, m_marbleWidget->textureLayer(), m_marbleWidget ); connect( dialog, SIGNAL(groundOverlayUpdated(GeoDataGroundOverlay*)), this, SLOT(updateOverlayFrame(GeoDataGroundOverlay*)) ); dialog->exec(); delete dialog; } void AnnotatePlugin::removeOverlay() { m_marbleWidget->model()->treeModel()->removeFeature( m_rmbOverlay ); clearOverlayFrames(); } void AnnotatePlugin::displayOverlayFrame( GeoDataGroundOverlay *overlay ) { if ( m_groundOverlayFrames.keys().contains( overlay ) ) { return; } GeoDataPolygon *polygon = new GeoDataPolygon( Tessellate ); polygon->outerBoundary().setTessellate( true ); GeoDataPlacemark *rectangle_placemark = new GeoDataPlacemark; rectangle_placemark->setGeometry( polygon ); rectangle_placemark->setParent( m_annotationDocument ); rectangle_placemark->setStyleUrl(QStringLiteral("#polygon")); m_marbleWidget->model()->treeModel()->addFeature( m_annotationDocument, rectangle_placemark ); GroundOverlayFrame *frame = new GroundOverlayFrame( rectangle_placemark, overlay, m_marbleWidget->textureLayer() ); m_graphicsItems.append( frame ); m_groundOverlayFrames.insert( overlay, frame ); if ( m_focusItem ) { m_focusItem->setFocus( false ); } m_focusItem = frame; enableActionsOnItemType( SceneGraphicsTypes::SceneGraphicGroundOverlay ); } void AnnotatePlugin::updateOverlayFrame( GeoDataGroundOverlay *overlay ) { GroundOverlayFrame *frame = static_cast( m_groundOverlayFrames.value( overlay ) ); if ( frame ) { frame->update(); } } void AnnotatePlugin::clearOverlayFrames() { for ( GeoDataGroundOverlay *overlay: m_groundOverlayFrames.keys() ) { GroundOverlayFrame *frame = static_cast( m_groundOverlayFrames.value( overlay ) ); m_graphicsItems.removeAll( m_groundOverlayFrames.value( overlay ) ); m_marbleWidget->model()->treeModel()->removeFeature( frame->placemark() ); delete frame->placemark(); delete frame; } m_groundOverlayFrames.clear(); m_focusItem = 0; disableFocusActions(); } void AnnotatePlugin::setupPolygonRmbMenu() { delete m_polygonRmbMenu; m_polygonRmbMenu = new QMenu; QAction *deselectNodes = new QAction( tr( "Deselect All Nodes" ), m_polygonRmbMenu ); m_polygonRmbMenu->addAction( deselectNodes ); connect( deselectNodes, SIGNAL(triggered()), this, SLOT(deselectNodes()) ); QAction *deleteAllSelected = new QAction( tr( "Delete All Selected Nodes" ), m_polygonRmbMenu ); m_polygonRmbMenu->addAction( deleteAllSelected ); connect( deleteAllSelected, SIGNAL(triggered()), this, SLOT(deleteSelectedNodes()) ); m_polygonRmbMenu->addSeparator(); QAction *cutPolygon = new QAction( tr( "Cut"), m_polygonRmbMenu ); m_polygonRmbMenu->addAction( cutPolygon ); connect( cutPolygon, SIGNAL(triggered()), this, SLOT(cutItem()) ); QAction *copyPolygon = new QAction( tr( "Copy"), m_polygonRmbMenu ); m_polygonRmbMenu->addAction( copyPolygon ); connect( copyPolygon, SIGNAL(triggered()), this, SLOT(copyItem()) ); QAction *removePolygon = new QAction( tr( "Remove" ), m_polygonRmbMenu ); m_polygonRmbMenu->addAction( removePolygon ); connect( removePolygon, SIGNAL(triggered()), this, SLOT(askToRemoveFocusItem()) ); m_polygonRmbMenu->addSeparator(); QAction *showEditDialog = new QAction( tr( "Properties" ), m_polygonRmbMenu ); m_polygonRmbMenu->addAction( showEditDialog ); connect( showEditDialog, SIGNAL(triggered()), this, SLOT(editPolygon()) ); } void AnnotatePlugin::showPolygonRmbMenu( qreal x, qreal y ) { // We need to store the coordinates from where the rmb menu is shown so that in case of // selecting Copy/Cut, we can move the polygon. qreal lon, lat; m_marbleWidget->geoCoordinates( x, y, lon, lat, GeoDataCoordinates::Radian ); m_fromWhereToCopy = GeoDataCoordinates( lon, lat ); if ( !static_cast( m_focusItem )->hasNodesSelected() ) { m_polygonRmbMenu->actions().at(1)->setEnabled( false ); m_polygonRmbMenu->actions().at(0)->setEnabled( false ); } else { m_polygonRmbMenu->actions().at(1)->setEnabled( true ); m_polygonRmbMenu->actions().at(0)->setEnabled( true ); } m_polygonRmbMenu->popup( m_marbleWidget->mapToGlobal( QPoint( x, y ) ) ); } void AnnotatePlugin::addPolygon() { m_drawingPolygon = true; GeoDataPolygon *poly = new GeoDataPolygon( Tessellate ); poly->outerBoundary().setTessellate( true ); m_polygonPlacemark = new GeoDataPlacemark; m_polygonPlacemark->setGeometry( poly ); m_polygonPlacemark->setParent( m_annotationDocument ); m_polygonPlacemark->setStyleUrl(QStringLiteral("#polygon")); m_marbleWidget->model()->treeModel()->addFeature( m_annotationDocument, m_polygonPlacemark ); AreaAnnotation *polygon = new AreaAnnotation( m_polygonPlacemark ); polygon->setState( SceneGraphicsItem::DrawingPolygon ); polygon->setFocus( true ); m_graphicsItems.append( polygon ); m_marbleWidget->update(); QPointer dialog = new EditPolygonDialog( m_polygonPlacemark, &m_osmRelations, m_marbleWidget ); connect( dialog, SIGNAL(polygonUpdated(GeoDataFeature*)), m_marbleWidget->model()->treeModel(), SLOT(updateFeature(GeoDataFeature*)) ); connect( dialog, SIGNAL(finished(int)), this, SLOT(stopEditingPolygon(int)) ); connect( this, SIGNAL(nodeAdded(GeoDataCoordinates)), dialog, SLOT(handleAddingNode(GeoDataCoordinates)) ); connect( dialog, SIGNAL( relationCreated( const OsmPlacemarkData& ) ), this, SLOT( addRelation( const OsmPlacemarkData& ) ) ); // If there is another graphic item marked as 'selected' when pressing 'Add Polygon', change the focus of // that item. if ( m_focusItem ) { m_focusItem->setFocus( false ); if ( m_focusItem->graphicType() == SceneGraphicsTypes::SceneGraphicGroundOverlay ) { clearOverlayFrames(); } } m_focusItem = polygon; m_editedItem = polygon; disableActions( m_actions.first() ); dialog->move( m_marbleWidget->mapToGlobal( QPoint( 0, 0 ) ) ); dialog->show(); m_editingDialogIsShown = true; } void AnnotatePlugin::stopEditingPolygon( int result ) { m_focusItem = m_editedItem; m_editedItem = 0; announceStateChanged( SceneGraphicsItem::Editing ); enableAllActions( m_actions.first() ); disableFocusActions(); if ( !result && m_drawingPolygon ) { removeFocusItem(); } else { enableActionsOnItemType( SceneGraphicsTypes::SceneGraphicAreaAnnotation ); } m_editingDialogIsShown = false; m_drawingPolygon = false; m_polygonPlacemark = 0; } void AnnotatePlugin::deselectNodes() { if ( m_focusItem->graphicType() == SceneGraphicsTypes::SceneGraphicAreaAnnotation ) { AreaAnnotation * const area = static_cast( m_focusItem ); area->deselectAllNodes(); if ( area->request() == SceneGraphicsItem::NoRequest ) { m_marbleWidget->model()->treeModel()->updateFeature( area->placemark() ); } } else if ( m_focusItem->graphicType() == SceneGraphicsTypes::SceneGraphicPolylineAnnotation ) { PolylineAnnotation * const polyline = static_cast( m_focusItem ); polyline->deselectAllNodes(); if ( polyline->request() == SceneGraphicsItem::NoRequest ) { m_marbleWidget->model()->treeModel()->updateFeature( polyline->placemark() ); } } } void AnnotatePlugin::deleteSelectedNodes() { if ( m_focusItem->graphicType() == SceneGraphicsTypes::SceneGraphicAreaAnnotation ) { AreaAnnotation * const area = static_cast( m_focusItem ); area->deleteAllSelectedNodes(); } else if ( m_focusItem->graphicType() == SceneGraphicsTypes::SceneGraphicPolylineAnnotation ) { PolylineAnnotation * const polyline = static_cast( m_focusItem ); polyline->deleteAllSelectedNodes(); } if ( m_focusItem->request() == SceneGraphicsItem::NoRequest ) { m_marbleWidget->model()->treeModel()->updateFeature( m_focusItem->placemark() ); } else if ( m_focusItem->request() == SceneGraphicsItem::RemovePolygonRequest || m_focusItem->request() == SceneGraphicsItem::RemovePolylineRequest ) { removeFocusItem(); } else if ( m_focusItem->request() == SceneGraphicsItem::InvalidShapeWarning ) { QMessageBox::warning( m_marbleWidget, tr( "Operation not permitted" ), tr( "Cannot delete one of the selected nodes. Most probably " "this would make the polygon's outer boundary not " "contain all its inner boundary nodes." ) ); } } void AnnotatePlugin::editPolygon() { EditPolygonDialog *dialog = new EditPolygonDialog( m_focusItem->placemark(), &m_osmRelations, m_marbleWidget ); connect( dialog, SIGNAL(polygonUpdated(GeoDataFeature*)), m_marbleWidget->model()->treeModel(), SLOT(updateFeature(GeoDataFeature*)) ); connect( dialog, SIGNAL(finished(int)), this, SLOT(stopEditingPolygon(int)) ); connect( this, SIGNAL(itemMoved(GeoDataPlacemark*)), dialog, SLOT(handleItemMoving(GeoDataPlacemark*)) ); connect( dialog, SIGNAL( relationCreated( const OsmPlacemarkData& ) ), this, SLOT( addRelation( const OsmPlacemarkData& ) ) ); disableActions( m_actions.first() ); dialog->move( m_marbleWidget->mapToGlobal( QPoint( 0, 0 ) ) ); dialog->show(); m_editingDialogIsShown = true; m_editedItem = m_focusItem; } void AnnotatePlugin::setupNodeRmbMenu() { delete m_nodeRmbMenu; m_nodeRmbMenu = new QMenu; QAction *selectNode = new QAction( tr( "Select Node" ), m_nodeRmbMenu ); m_nodeRmbMenu->addAction( selectNode ); connect( selectNode, SIGNAL(triggered()), this, SLOT(selectNode()) ); QAction *deleteNode = new QAction( tr( "Delete Node" ), m_nodeRmbMenu ); m_nodeRmbMenu->addAction( deleteNode ); connect( deleteNode, SIGNAL(triggered()), this, SLOT(deleteNode()) ); } void AnnotatePlugin::showNodeRmbMenu( qreal x, qreal y ) { // Check whether the node is already selected; we change the text of the action // accordingly. bool isSelected = false; if ( ( m_focusItem->graphicType() == SceneGraphicsTypes::SceneGraphicAreaAnnotation && static_cast( m_focusItem )->clickedNodeIsSelected() ) || ( m_focusItem->graphicType() == SceneGraphicsTypes::SceneGraphicPolylineAnnotation && static_cast( m_focusItem )->clickedNodeIsSelected() ) ) { isSelected = true; } m_nodeRmbMenu->actions().first()->setText( isSelected ? tr("Deselect Node") : tr("Select Node") ); m_nodeRmbMenu->popup( m_marbleWidget->mapToGlobal( QPoint( x, y ) ) ); } void AnnotatePlugin::selectNode() { if ( m_focusItem->graphicType() == SceneGraphicsTypes::SceneGraphicAreaAnnotation ) { AreaAnnotation * const area = static_cast( m_focusItem ); area->changeClickedNodeSelection(); } else if ( m_focusItem->graphicType() == SceneGraphicsTypes::SceneGraphicPolylineAnnotation ) { PolylineAnnotation *const polyline = static_cast( m_focusItem ); polyline->changeClickedNodeSelection(); } if ( m_focusItem->request() == SceneGraphicsItem::NoRequest ) { m_marbleWidget->model()->treeModel()->updateFeature( m_focusItem->placemark() ); } } void AnnotatePlugin::deleteNode() { if ( m_focusItem->graphicType() == SceneGraphicsTypes::SceneGraphicAreaAnnotation ) { AreaAnnotation *area = static_cast( m_focusItem ); area->deleteClickedNode(); } else if ( m_focusItem->graphicType() == SceneGraphicsTypes::SceneGraphicPolylineAnnotation ) { PolylineAnnotation *polyline = static_cast( m_focusItem ); polyline->deleteClickedNode(); } if ( m_focusItem->request() == SceneGraphicsItem::NoRequest ) { m_marbleWidget->model()->treeModel()->updateFeature( m_focusItem->placemark() ); } else if ( m_focusItem->request() == SceneGraphicsItem::RemovePolygonRequest || m_focusItem->request() == SceneGraphicsItem::RemovePolylineRequest ) { removeFocusItem(); } else if ( m_focusItem->request() == SceneGraphicsItem::InvalidShapeWarning ) { QMessageBox::warning( m_marbleWidget, tr( "Operation not permitted" ), tr( "Cannot delete one of the selected nodes. Most probably " "this would make the polygon's outer boundary not " "contain all its inner boundary nodes." ) ); } } void AnnotatePlugin::setupPolylineRmbMenu() { delete m_polylineRmbMenu; m_polylineRmbMenu = new QMenu; QAction *deselectNodes = new QAction( tr( "Deselect All Nodes" ), m_polylineRmbMenu ); m_polylineRmbMenu->addAction( deselectNodes ); connect( deselectNodes, SIGNAL(triggered()), this, SLOT(deselectNodes()) ); QAction *deleteAllSelected = new QAction( tr( "Delete All Selected Nodes" ), m_polylineRmbMenu ); m_polylineRmbMenu->addAction( deleteAllSelected ); connect( deleteAllSelected, SIGNAL(triggered()), this, SLOT(deleteSelectedNodes()) ); m_polylineRmbMenu->addSeparator(); QAction *cutItem = new QAction( tr( "Cut"), m_polylineRmbMenu ); m_polylineRmbMenu->addAction( cutItem ); connect( cutItem, SIGNAL(triggered()), this, SLOT(cutItem()) ); QAction *copyItem = new QAction( tr( "Copy"), m_polylineRmbMenu ); m_polylineRmbMenu->addAction( copyItem ); connect( copyItem, SIGNAL(triggered()), this, SLOT(copyItem()) ); QAction *removeItem = new QAction( tr( "Remove" ), m_polylineRmbMenu ); m_polylineRmbMenu->addAction( removeItem ); connect( removeItem, SIGNAL(triggered()), this, SLOT(askToRemoveFocusItem()) ); m_polylineRmbMenu->addSeparator(); QAction *properties = new QAction( tr( "Properties" ), m_polylineRmbMenu ); m_polylineRmbMenu->addAction( properties ); connect( properties, SIGNAL(triggered()), this, SLOT(editPolyline()) ); } void AnnotatePlugin::showPolylineRmbMenu( qreal x, qreal y ) { qreal lon, lat; m_marbleWidget->geoCoordinates( x, y, lon, lat, GeoDataCoordinates::Radian ); m_fromWhereToCopy = GeoDataCoordinates( lon, lat ); if ( !static_cast( m_focusItem )->hasNodesSelected() ) { m_polylineRmbMenu->actions().at(1)->setEnabled( false ); m_polylineRmbMenu->actions().at(0)->setEnabled( false ); } else { m_polylineRmbMenu->actions().at(1)->setEnabled( true ); m_polylineRmbMenu->actions().at(0)->setEnabled( true ); } m_polylineRmbMenu->popup( m_marbleWidget->mapToGlobal( QPoint( x, y ) ) ); } void AnnotatePlugin::editPolyline() { QPointer dialog = new EditPolylineDialog( m_focusItem->placemark(), &m_osmRelations, m_marbleWidget ); connect( dialog, SIGNAL(polylineUpdated(GeoDataFeature*)), m_marbleWidget->model()->treeModel(), SLOT(updateFeature(GeoDataFeature*)) ); connect( dialog, SIGNAL(finished(int)), this, SLOT(stopEditingPolyline(int)) ); connect( this, SIGNAL(itemMoved(GeoDataPlacemark*)), dialog, SLOT(handleItemMoving(GeoDataPlacemark*)) ); connect( dialog, SIGNAL( relationCreated( const OsmPlacemarkData& ) ), this, SLOT( addRelation( const OsmPlacemarkData& ) ) ); disableActions( m_actions.first() ); dialog->show(); m_editingDialogIsShown = true; m_editedItem = m_focusItem; } void AnnotatePlugin::addPolyline() { m_drawingPolyline = true; m_polylinePlacemark = new GeoDataPlacemark; m_polylinePlacemark->setGeometry( new GeoDataLineString( Tessellate ) ); m_polylinePlacemark->setParent( m_annotationDocument ); m_polylinePlacemark->setStyleUrl(QStringLiteral("#polyline")); m_marbleWidget->model()->treeModel()->addFeature( m_annotationDocument, m_polylinePlacemark ); PolylineAnnotation *polyline = new PolylineAnnotation( m_polylinePlacemark ); polyline->setState( SceneGraphicsItem::DrawingPolyline ); polyline->setFocus( true ); m_graphicsItems.append( polyline ); m_marbleWidget->update(); QPointer dialog = new EditPolylineDialog( m_polylinePlacemark, &m_osmRelations, m_marbleWidget ); connect( dialog, SIGNAL(polylineUpdated(GeoDataFeature*)), m_marbleWidget->model()->treeModel(), SLOT(updateFeature(GeoDataFeature*)) ); connect( dialog, SIGNAL(finished(int)), this, SLOT(stopEditingPolyline(int)) ); connect( this, SIGNAL(nodeAdded(GeoDataCoordinates)), dialog, SLOT(handleAddingNode(GeoDataCoordinates)) ); connect( dialog, SIGNAL( relationCreated( const OsmPlacemarkData& ) ), this, SLOT( addRelation( const OsmPlacemarkData& ) ) ); if ( m_focusItem ) { m_focusItem->setFocus( false ); if ( m_focusItem->graphicType() == SceneGraphicsTypes::SceneGraphicGroundOverlay ) { clearOverlayFrames(); } } m_focusItem = polyline; m_editedItem = m_focusItem; disableActions( m_actions.first() ); dialog->move( m_marbleWidget->mapToGlobal( QPoint( 0, 0 ) ) ); dialog->show(); m_editingDialogIsShown = true; } void AnnotatePlugin::stopEditingPolyline( int result ) { m_focusItem = m_editedItem; m_editedItem = 0; announceStateChanged( SceneGraphicsItem::Editing ); enableAllActions( m_actions.first() ); disableFocusActions(); if ( !result && m_drawingPolyline ) { removeFocusItem(); } else { enableActionsOnItemType( SceneGraphicsTypes::SceneGraphicPolylineAnnotation ); } m_editingDialogIsShown = false; m_drawingPolyline = false; m_polylinePlacemark = 0; } void AnnotatePlugin::addRelation( const OsmPlacemarkData &relationData ) { m_osmRelations.insert( relationData.id(), relationData ); } void AnnotatePlugin::announceStateChanged( SceneGraphicsItem::ActionState newState ) { for ( SceneGraphicsItem *item: m_graphicsItems ) { item->setState( newState ); m_marbleWidget->model()->treeModel()->updateFeature( item->placemark() ); } } void AnnotatePlugin::setupCursor( SceneGraphicsItem *item ) { if ( !item || item->state() == SceneGraphicsItem::AddingNodes ) { m_marbleWidget->setCursor( Qt::DragCopyCursor ); } else { // Nothing to do. The other cursor changes were moved to the handleRequests() section. } } void AnnotatePlugin::cutItem() { disableFocusActions(); // If there is already an item copied/cut, free its memory and replace it with this one. // The same applies when copying. if ( m_clipboardItem ) { delete m_clipboardItem->placemark(); delete m_clipboardItem; m_clipboardItem = 0; } m_clipboardItem = m_focusItem; m_pasteGraphicItem->setVisible( true ); m_graphicsItems.removeAll( m_focusItem ); m_marbleWidget->model()->treeModel()->removeFeature( m_focusItem->placemark() ); m_focusItem = 0; } void AnnotatePlugin::copyItem() { if ( m_clipboardItem ) { delete m_clipboardItem->placemark(); delete m_clipboardItem; m_clipboardItem = 0; } // Just copy the placemark and instantiate a new object based on its graphic type. // FIXME: Here is obvious a problem in the case of AreaAnnotation (when copying a // placemark which has a GeoDataPolygon geometry?). Later Edit: The same applies for // polylines (GeoDataLineString geometries). GeoDataPlacemark *placemark = new GeoDataPlacemark( *m_focusItem->placemark() ); if ( m_focusItem->graphicType() == SceneGraphicsTypes::SceneGraphicAreaAnnotation ) { m_clipboardItem = new AreaAnnotation( placemark ); } else if ( m_focusItem->graphicType() == SceneGraphicsTypes::SceneGraphicTextAnnotation ) { m_clipboardItem = new PlacemarkTextAnnotation( placemark ); } else if ( m_focusItem->graphicType() == SceneGraphicsTypes::SceneGraphicPolylineAnnotation ) { m_clipboardItem = new PolylineAnnotation( placemark ); } m_pasteGraphicItem->setVisible( true ); } void AnnotatePlugin::pasteItem() { const QPoint eventPoint = m_marbleWidget->popupMenu()->mousePosition(); qreal lon, lat; m_marbleWidget->geoCoordinates( eventPoint.x(), eventPoint.y(), lon, lat, GeoDataCoordinates::Radian ); const GeoDataCoordinates newCoords( lon, lat ); m_clipboardItem->move( m_fromWhereToCopy, newCoords ); m_marbleWidget->model()->treeModel()->addFeature( m_annotationDocument, m_clipboardItem->placemark() ); m_graphicsItems.append( m_clipboardItem ); m_clipboardItem->setFocus( true ); enableActionsOnItemType( m_clipboardItem->graphicType() ); m_focusItem = m_clipboardItem; m_clipboardItem = 0; m_pasteGraphicItem->setVisible( false ); } -const GeoDataCoordinates AnnotatePlugin::mouseGeoDataCoordinates( QMouseEvent *mouseEvent ) +const GeoDataCoordinates AnnotatePlugin::mouseGeoDataCoordinates(QMouseEvent *mouseEvent) const { qreal lon, lat; m_marbleWidget->geoCoordinates( mouseEvent->pos().x(), mouseEvent->pos().y(), lon, lat, GeoDataCoordinates::Radian ); return GeoDataCoordinates( lon, lat ); } } #include "moc_AnnotatePlugin.cpp" diff --git a/src/plugins/render/annotate/AnnotatePlugin.h b/src/plugins/render/annotate/AnnotatePlugin.h index 69f900726..cd4d39fb6 100644 --- a/src/plugins/render/annotate/AnnotatePlugin.h +++ b/src/plugins/render/annotate/AnnotatePlugin.h @@ -1,217 +1,217 @@ // // This file is part of the Marble Virtual Globe. // // This program is free software licensed under the GNU LGPL. You can // find a copy of this license in LICENSE.txt in the top directory of // the source code. // // Copyright 2009 Andrew Manson // Copyright 2013 Thibaut Gridel // Copyright 2014 Calin Cruceru // #ifndef MARBLE_ANNOTATEPLUGIN_H #define MARBLE_ANNOTATEPLUGIN_H #include "RenderPlugin.h" #include "SceneGraphicsItem.h" #include "GeoDataGroundOverlay.h" #include "GroundOverlayFrame.h" #include #include namespace Marble { class MarbleWidget; class GeoDataDocument; class GeoDataLinearRing; class AreaAnnotation; class PolylineAnnotation; class PlacemarkTextAnnotation; class OsmPlacemarkData; /** * @brief This class specifies the Marble layer interface of a plugin which * annotates maps with polygons and placemarks. */ class AnnotatePlugin : public RenderPlugin { Q_OBJECT Q_PLUGIN_METADATA(IID "org.kde.marble.AnnotatePlugin") Q_INTERFACES( Marble::RenderPluginInterface ) MARBLE_PLUGIN( AnnotatePlugin ) public: explicit AnnotatePlugin(const MarbleModel *model = 0); ~AnnotatePlugin() override; QStringList backendTypes() const override; QString renderPolicy() const override; QStringList renderPosition() const override; QString name() const override; QString guiString() const override; QString nameId() const override; QString version() const override; QString description() const override; QIcon icon () const override; QString copyrightYears() const override; QVector pluginAuthors() const override; void initialize () override; bool isInitialized () const override; QString runtimeTrace() const override; const QList *actionGroups() const override; bool render( GeoPainter *painter, ViewportParams *viewport, const QString &renderPos, GeoSceneLayer *layer = 0 ) override; Q_SIGNALS: void placemarkMoved(); void nodeAdded( const GeoDataCoordinates &coordinates ); void itemMoved( GeoDataPlacemark *placemark ); void mouseMoveGeoPosition( const QString& ); private Q_SLOTS: void enableModel( bool enabled ); void askToRemoveFocusItem(); void removeFocusItem(); void clearAnnotations(); void saveAnnotationFile(); void loadAnnotationFile(); void openAnnotationFile(const QString&); void copyItem(); void cutItem(); void pasteItem(); void addTextAnnotation(); void editTextAnnotation(); void stopEditingTextAnnotation( int result ); void addOverlay(); void editOverlay(); void removeOverlay(); void updateOverlayFrame( GeoDataGroundOverlay *overlay ); void addPolygon(); void stopEditingPolygon( int result ); void setAddingPolygonHole( bool enabled ); void setAddingNodes( bool enabled ); void editPolygon(); void selectNode(); void deleteNode(); void deselectNodes(); void deleteSelectedNodes(); void setAreaAvailable(); void addPolyline(); void editPolyline(); void stopEditingPolyline( int result ); void setPolylineAvailable(); void addRelation( const OsmPlacemarkData &relationOsmData ); void downloadOsm(); protected: bool eventFilter( QObject *watched, QEvent *event ) override; private: void addContextItems(); void setupActions( MarbleWidget *marbleWidget ); static void disableActions(QActionGroup *group); static void enableAllActions(QActionGroup *group); void enableActionsOnItemType( const QString &type ); void disableFocusActions(); void setupTextAnnotationRmbMenu(); void showTextAnnotationRmbMenu( qreal x, qreal y ); void setupGroundOverlayModel(); void setupOverlayRmbMenu(); void showOverlayRmbMenu( GeoDataGroundOverlay *overlay, qreal x, qreal y ); void displayOverlayFrame( GeoDataGroundOverlay *overlay ); void clearOverlayFrames(); void setupPolygonRmbMenu(); void setupNodeRmbMenu(); void showPolygonRmbMenu( qreal x, qreal y ); void showNodeRmbMenu( qreal x, qreal y ); void setupPolylineRmbMenu(); void showPolylineRmbMenu( qreal x, qreal y ); void handleUncaughtEvents( QMouseEvent *mouseEvent ); void handleReleaseOverlay( QMouseEvent *mouseEvent ); bool handleDrawingPolyline( QMouseEvent *mouseEvent ); bool handleDrawingPolygon( QMouseEvent *mouseEvent ); bool handleMovingSelectedItem( QMouseEvent *mouseEvent ); void handleRequests( QMouseEvent *mouseEvent, SceneGraphicsItem *item ); void handleSuccessfulPressEvent( QMouseEvent *mouseEvent, SceneGraphicsItem *item ); void handleSuccessfulHoverEvent( QMouseEvent *mouseEvent, SceneGraphicsItem *item ); void handleSuccessfulReleaseEvent( QMouseEvent *mouseEvent, SceneGraphicsItem *item ); void announceStateChanged( SceneGraphicsItem::ActionState newState ); void setupCursor( SceneGraphicsItem *item ); - const GeoDataCoordinates mouseGeoDataCoordinates( QMouseEvent *mouseEvent ); + const GeoDataCoordinates mouseGeoDataCoordinates(QMouseEvent *mouseEvent) const; bool m_isInitialized; bool m_widgetInitialized; MarbleWidget *m_marbleWidget; QMenu *m_overlayRmbMenu; QMenu *m_polygonRmbMenu; QMenu *m_nodeRmbMenu; QMenu *m_textAnnotationRmbMenu; QMenu *m_polylineRmbMenu; QList m_actions; QSortFilterProxyModel m_groundOverlayModel; QMap m_groundOverlayFrames; // A list of all osm relations QHash m_osmRelations; GeoDataDocument* m_annotationDocument; QList m_graphicsItems; SceneGraphicsItem *m_movedItem; SceneGraphicsItem *m_focusItem; SceneGraphicsItem *m_editedItem; GeoDataGroundOverlay *m_rmbOverlay; GeoDataPlacemark *m_polylinePlacemark; GeoDataPlacemark *m_polygonPlacemark; GeoDataCoordinates m_fromWhereToCopy; SceneGraphicsItem *m_clipboardItem; QAction *m_pasteGraphicItem; bool m_drawingPolygon; bool m_drawingPolyline; bool m_addingPlacemark; bool m_editingDialogIsShown; }; } #endif // MARBLE_ANNOTATEPLUGIN_H diff --git a/src/plugins/render/graticule/GraticulePlugin.cpp b/src/plugins/render/graticule/GraticulePlugin.cpp index 0555787fd..60ea27856 100644 --- a/src/plugins/render/graticule/GraticulePlugin.cpp +++ b/src/plugins/render/graticule/GraticulePlugin.cpp @@ -1,793 +1,793 @@ // // This file is part of the Marble Virtual Globe. // // This program is free software licensed under the GNU LGPL. You can // find a copy of this license in LICENSE.txt in the top directory of // the source code. // // Copyright 2009 Torsten Rahn // #include "GraticulePlugin.h" #include "ui_GraticuleConfigWidget.h" #include "MarbleDebug.h" #include "MarbleDirs.h" #include "GeoPainter.h" #include "GeoDataLineString.h" #include "Planet.h" #include "MarbleModel.h" #include "PluginAboutDialog.h" #include "ViewportParams.h" #include "GeoDataLatLonAltBox.h" // Qt #include #include #include #include #include #include namespace Marble { GraticulePlugin::GraticulePlugin() : RenderPlugin( 0 ), m_showPrimaryLabels( true ), m_showSecondaryLabels( true ), m_isInitialized( false ), ui_configWidget( 0 ), m_configDialog( 0 ) { } GraticulePlugin::GraticulePlugin( const MarbleModel *marbleModel ) : RenderPlugin( marbleModel ), m_equatorCirclePen( Qt::yellow ), m_tropicsCirclePen( Qt::yellow ), m_gridCirclePen( Qt::white ), m_showPrimaryLabels( true ), m_showSecondaryLabels( true ), m_isInitialized( false ), ui_configWidget( 0 ), m_configDialog( 0 ) { } QStringList GraticulePlugin::backendTypes() const { return QStringList(QStringLiteral("graticule")); } QString GraticulePlugin::renderPolicy() const { return QStringLiteral("ALWAYS"); } QStringList GraticulePlugin::renderPosition() const { return QStringList(QStringLiteral("GRATICULE")); } QString GraticulePlugin::name() const { return tr( "Coordinate Grid" ); } QString GraticulePlugin::guiString() const { return tr( "Coordinate &Grid" ); } QString GraticulePlugin::nameId() const { return QStringLiteral("coordinate-grid"); } QString GraticulePlugin::version() const { return QStringLiteral("1.0"); } QString GraticulePlugin::description() const { return tr( "A plugin that shows a coordinate grid." ); } QString GraticulePlugin::copyrightYears() const { return QStringLiteral("2009"); } QVector GraticulePlugin::pluginAuthors() const { return QVector() << PluginAuthor(QStringLiteral("Torsten Rahn"), QStringLiteral("tackat@kde.org")); } QIcon GraticulePlugin::icon () const { return QIcon(QStringLiteral(":/icons/coordinate.png")); } void GraticulePlugin::initialize () { // Initialize range maps that map the zoom to the number of coordinate grid lines. initLineMaps( GeoDataCoordinates::defaultNotation() ); m_isInitialized = true; } bool GraticulePlugin::isInitialized () const { return m_isInitialized; } QDialog *GraticulePlugin::configDialog() { if ( !m_configDialog ) { m_configDialog = new QDialog(); ui_configWidget = new Ui::GraticuleConfigWidget; ui_configWidget->setupUi( m_configDialog ); connect( ui_configWidget->gridPushButton, SIGNAL(clicked()), this, SLOT(gridGetColor()) ); connect( ui_configWidget->tropicsPushButton, SIGNAL(clicked()), this, SLOT(tropicsGetColor()) ); connect( ui_configWidget->equatorPushButton, SIGNAL(clicked()), this, SLOT(equatorGetColor()) ); connect( ui_configWidget->m_buttonBox, SIGNAL(accepted()), this, SLOT(writeSettings()) ); connect( ui_configWidget->m_buttonBox->button( QDialogButtonBox::Reset ), SIGNAL(clicked()), SLOT(restoreDefaultSettings()) ); QPushButton *applyButton = ui_configWidget->m_buttonBox->button( QDialogButtonBox::Apply ); connect( applyButton, SIGNAL(clicked()), this, SLOT(writeSettings()) ); } readSettings(); return m_configDialog; } QHash GraticulePlugin::settings() const { QHash settings = RenderPlugin::settings(); settings.insert(QStringLiteral("gridColor"), m_gridCirclePen.color().name()); settings.insert(QStringLiteral("tropicsColor"), m_tropicsCirclePen.color().name()); settings.insert(QStringLiteral("equatorColor"), m_equatorCirclePen.color().name()); settings.insert(QStringLiteral("primaryLabels"), m_showPrimaryLabels); settings.insert(QStringLiteral("secondaryLabels"), m_showSecondaryLabels); return settings; } void GraticulePlugin::setSettings( const QHash &settings ) { RenderPlugin::setSettings( settings ); const QColor gridColor = settings.value(QStringLiteral("gridColor"), QColor(Qt::white)).value(); const QColor tropicsColor = settings.value(QStringLiteral("tropicsColor"), QColor(Qt::yellow)).value(); const QColor equatorColor = settings.value(QStringLiteral("equatorColor"), QColor(Qt::yellow)).value(); bool primaryLabels = settings.value(QStringLiteral("primaryLabels"), true).toBool(); bool secondaryLabels = settings.value(QStringLiteral("secondaryLabels"), true).toBool(); m_gridCirclePen.setColor( gridColor ); m_tropicsCirclePen.setColor( tropicsColor ); m_equatorCirclePen.setColor( equatorColor ); m_showPrimaryLabels = primaryLabels; m_showSecondaryLabels = secondaryLabels; readSettings(); } void GraticulePlugin::readSettings() { if ( !m_configDialog ) return; QPalette gridPalette; gridPalette.setColor( QPalette::Button, m_gridCirclePen.color() ); ui_configWidget->gridPushButton->setPalette( gridPalette ); QPalette tropicsPalette; tropicsPalette.setColor( QPalette::Button, m_tropicsCirclePen.color() ); ui_configWidget->tropicsPushButton->setPalette( tropicsPalette ); QPalette equatorPalette; equatorPalette.setColor( QPalette::Button, m_equatorCirclePen.color() ); ui_configWidget->equatorPushButton->setPalette( equatorPalette ); ui_configWidget->primaryCheckBox->setChecked( m_showPrimaryLabels ); ui_configWidget->secondaryCheckBox->setChecked( m_showSecondaryLabels ); } void GraticulePlugin::gridGetColor() { const QColor c = QColorDialog::getColor( m_gridCirclePen.color(), 0, tr("Please choose the color for the coordinate grid.") ); if ( c.isValid() ) { QPalette palette = ui_configWidget->gridPushButton->palette(); palette.setColor( QPalette::Button, c ); ui_configWidget->gridPushButton->setPalette( palette ); } } void GraticulePlugin::tropicsGetColor() { const QColor c = QColorDialog::getColor( m_tropicsCirclePen.color(), 0, tr("Please choose the color for the tropic circles.") ); if ( c.isValid() ) { QPalette palette = ui_configWidget->tropicsPushButton->palette(); palette.setColor( QPalette::Button, c ); ui_configWidget->tropicsPushButton->setPalette( palette ); } } void GraticulePlugin::equatorGetColor() { const QColor c = QColorDialog::getColor( m_equatorCirclePen.color(), 0, tr("Please choose the color for the equator.") ); if ( c.isValid() ) { QPalette palette = ui_configWidget->equatorPushButton->palette(); palette.setColor( QPalette::Button, c ); ui_configWidget->equatorPushButton->setPalette( palette ); } } void GraticulePlugin::writeSettings() { m_equatorCirclePen.setColor( ui_configWidget->equatorPushButton->palette().color( QPalette::Button ) ); m_tropicsCirclePen.setColor( ui_configWidget->tropicsPushButton->palette().color( QPalette::Button ) ); m_gridCirclePen.setColor( ui_configWidget->gridPushButton->palette().color( QPalette::Button) ); m_showPrimaryLabels = ui_configWidget->primaryCheckBox->isChecked(); m_showSecondaryLabels = ui_configWidget->secondaryCheckBox->isChecked(); emit settingsChanged( nameId() ); } bool GraticulePlugin::render( GeoPainter *painter, ViewportParams *viewport, const QString& renderPos, GeoSceneLayer * layer ) { Q_UNUSED( layer ) Q_UNUSED( renderPos ) if ( m_currentNotation != GeoDataCoordinates::defaultNotation() ) { initLineMaps( GeoDataCoordinates::defaultNotation() ); } // Setting the label font for the coordinate lines. #ifdef Q_OS_MACX int defaultFontSize = 10; #else int defaultFontSize = 8; #endif QFont gridFont("Sans Serif"); gridFont.setPointSize( defaultFontSize ); gridFont.setBold( true ); painter->save(); painter->setFont( gridFont ); renderGrid( painter, viewport, m_equatorCirclePen, m_tropicsCirclePen, m_gridCirclePen ); painter->restore(); return true; } qreal GraticulePlugin::zValue() const { return 1.0; } void GraticulePlugin::renderGrid( GeoPainter *painter, ViewportParams *viewport, const QPen& equatorCirclePen, const QPen& tropicsCirclePen, const QPen& gridCirclePen ) { GeoDataLatLonAltBox viewLatLonAltBox = viewport->viewLatLonAltBox(); painter->setPen( equatorCirclePen ); LabelPositionFlags mainPosition(NoLabel); if ( m_showPrimaryLabels ) { mainPosition = LineCenter; } // Render the equator renderLatitudeLine( painter, 0.0, viewLatLonAltBox, tr( "Equator" ), mainPosition ); // Render the Prime Meridian and Antimeridian GeoDataCoordinates::Notation notation = GeoDataCoordinates::defaultNotation(); if (marbleModel()->planet()->id() != QLatin1String("sky") && notation != GeoDataCoordinates::Astro) { renderLongitudeLine( painter, 0.0, viewLatLonAltBox, 0.0, 0.0, tr( "Prime Meridian" ), mainPosition ); renderLongitudeLine( painter, 180.0, viewLatLonAltBox, 0.0, 0.0, tr( "Antimeridian" ), mainPosition ); } painter->setPen( gridCirclePen ); // painter->setPen( QPen( QBrush( Qt::white ), 0.75 ) ); // Render UTM grid zones if ( m_currentNotation == GeoDataCoordinates::UTM ) { renderLatitudeLine( painter, 84.0, viewLatLonAltBox ); renderLongitudeLines( painter, viewLatLonAltBox, 6.0, 0.0, 18.0, 154.0, LineEnd | IgnoreXMargin ); renderLongitudeLines( painter, viewLatLonAltBox, 6.0, 0.0, 34.0, 10.0, LineStart | IgnoreXMargin ); // Paint longitudes with exceptions renderLongitudeLines( painter, viewLatLonAltBox, 6.0, 0.0, 6.0, 162.0, LineEnd | IgnoreXMargin ); renderLongitudeLines( painter, viewLatLonAltBox, 6.0, 0.0, 26.0, 146.0, LineEnd | IgnoreXMargin ); renderLatitudeLines( painter, viewLatLonAltBox, 8.0, 0.0 /*, LineStart | IgnoreYMargin */ ); return; } // Render the normal grid // calculate the angular distance between coordinate lines of the normal grid qreal normalDegreeStep = 360.0 / m_normalLineMap.lowerBound(viewport->radius()).value(); LabelPositionFlags labelXPosition(NoLabel), labelYPosition(NoLabel); if ( m_showSecondaryLabels ) { labelXPosition = LineStart | IgnoreXMargin; labelYPosition = LineStart | IgnoreYMargin; } qreal boldDegreeStep = 360.0 / m_boldLineMap.lowerBound(viewport->radius()).value(); renderLongitudeLines( painter, viewLatLonAltBox, normalDegreeStep, boldDegreeStep, normalDegreeStep, normalDegreeStep, labelXPosition ); renderLatitudeLines( painter, viewLatLonAltBox, normalDegreeStep, boldDegreeStep, labelYPosition ); // Render some non-cut off longitude lines .. renderLongitudeLine( painter, +90.0, viewLatLonAltBox ); renderLongitudeLine( painter, -90.0, viewLatLonAltBox ); // Render the bold grid if ( painter->mapQuality() == HighQuality || painter->mapQuality() == PrintQuality ) { QPen boldPen = gridCirclePen; boldPen.setWidthF( 2.0 ); painter->setPen( boldPen ); } // calculate the angular distance between coordinate lines of the bold grid renderLongitudeLines( painter, viewLatLonAltBox, boldDegreeStep, 0.0, normalDegreeStep, normalDegreeStep, NoLabel ); renderLatitudeLines( painter, viewLatLonAltBox, boldDegreeStep, 0.0, NoLabel ); QPen tropicsPen = tropicsCirclePen; if ( painter->mapQuality() != OutlineQuality && painter->mapQuality() != LowQuality ) { tropicsPen.setStyle( Qt::DotLine ); } painter->setPen( tropicsPen ); // Determine the planet's axial tilt qreal axialTilt = RAD2DEG * marbleModel()->planet()->epsilon(); if ( axialTilt > 0 ) { // Render the tropics renderLatitudeLine( painter, +axialTilt, viewLatLonAltBox, tr( "Tropic of Cancer" ), mainPosition ); renderLatitudeLine( painter, -axialTilt, viewLatLonAltBox, tr( "Tropic of Capricorn" ), mainPosition ); // Render the arctics renderLatitudeLine( painter, +90.0 - axialTilt, viewLatLonAltBox, tr( "Arctic Circle" ), mainPosition ); renderLatitudeLine( painter, -90.0 + axialTilt, viewLatLonAltBox, tr( "Antarctic Circle" ), mainPosition ); } } void GraticulePlugin::renderLatitudeLine( GeoPainter *painter, qreal latitude, const GeoDataLatLonAltBox& viewLatLonAltBox, const QString& lineLabel, LabelPositionFlags labelPositionFlags ) { qreal fromSouthLat = viewLatLonAltBox.south( GeoDataCoordinates::Degree ); qreal toNorthLat = viewLatLonAltBox.north( GeoDataCoordinates::Degree ); // Coordinate line is not displayed inside the viewport if ( latitude < fromSouthLat || toNorthLat < latitude ) { // mDebug() << "Lat: Out of View"; return; } GeoDataLineString line( Tessellate | RespectLatitudeCircle ) ; qreal fromWestLon = viewLatLonAltBox.west( GeoDataCoordinates::Degree ); qreal toEastLon = viewLatLonAltBox.east( GeoDataCoordinates::Degree ); if ( fromWestLon < toEastLon ) { qreal step = ( toEastLon - fromWestLon ) * 0.25; for ( int i = 0; i < 5; ++i ) { line << GeoDataCoordinates( fromWestLon + i * step, latitude, 0.0, GeoDataCoordinates::Degree ); } } else { qreal step = ( +180.0 - toEastLon ) * 0.25; for ( int i = 0; i < 5; ++i ) { line << GeoDataCoordinates( toEastLon + i * step, latitude, 0.0, GeoDataCoordinates::Degree ); } step = ( +180 + fromWestLon ) * 0.25; for ( int i = 0; i < 5; ++i ) { line << GeoDataCoordinates( -180 + i * step, latitude, 0.0, GeoDataCoordinates::Degree ); } } painter->drawPolyline( line, lineLabel, labelPositionFlags, painter->pen().color() ); } void GraticulePlugin::renderLongitudeLine( GeoPainter *painter, qreal longitude, const GeoDataLatLonAltBox& viewLatLonAltBox, qreal northPolarGap, qreal southPolarGap, const QString& lineLabel, LabelPositionFlags labelPositionFlags ) { const qreal fromWestLon = viewLatLonAltBox.west(); const qreal toEastLon = viewLatLonAltBox.east(); // Coordinate line is not displayed inside the viewport if ( ( !viewLatLonAltBox.crossesDateLine() && ( longitude * DEG2RAD < fromWestLon || toEastLon < longitude * DEG2RAD ) ) || ( viewLatLonAltBox.crossesDateLine() && longitude * DEG2RAD < toEastLon && fromWestLon < longitude * DEG2RAD && fromWestLon != -M_PI && toEastLon != +M_PI ) ) { // mDebug() << "Lon: Out of View:" << viewLatLonAltBox.toString() << " Crossing: "<< viewLatLonAltBox.crossesDateLine() << "Longitude: " << longitude; return; } qreal fromSouthLat = viewLatLonAltBox.south( GeoDataCoordinates::Degree ); qreal toNorthLat = viewLatLonAltBox.north( GeoDataCoordinates::Degree ); qreal southLat = ( fromSouthLat < -90.0 + southPolarGap ) ? -90.0 + southPolarGap : fromSouthLat; qreal northLat = ( toNorthLat > +90.0 - northPolarGap ) ? +90.0 - northPolarGap : toNorthLat; GeoDataCoordinates n1( longitude, southLat, 0.0, GeoDataCoordinates::Degree ); GeoDataCoordinates n3( longitude, northLat, 0.0, GeoDataCoordinates::Degree ); GeoDataLineString line( Tessellate ); if ( northLat > 0 && southLat < 0 ) { GeoDataCoordinates n2( longitude, 0.0, 0.0, GeoDataCoordinates::Degree ); line << n1 << n2 << n3; } else { line << n1 << n3; } painter->drawPolyline( line, lineLabel, labelPositionFlags, painter->pen().color() ); } void GraticulePlugin::renderLatitudeLines( GeoPainter *painter, const GeoDataLatLonAltBox& viewLatLonAltBox, qreal step, qreal skipStep, LabelPositionFlags labelPositionFlags ) { if ( step <= 0 ) { return; } // Latitude qreal southLat = viewLatLonAltBox.south( GeoDataCoordinates::Degree ); qreal northLat = viewLatLonAltBox.north( GeoDataCoordinates::Degree ); qreal southLineLat = step * static_cast( southLat / step ); qreal northLineLat = step * ( static_cast( northLat / step ) + 1 ); if ( m_currentNotation == GeoDataCoordinates::UTM ) { if ( northLineLat > 84.0 ) { northLineLat = 76.0; } if ( southLineLat < -80.0 ) { southLineLat = -80.0; } } qreal itStep = southLineLat; GeoDataCoordinates::Notation notation = GeoDataCoordinates::defaultNotation(); while ( itStep < northLineLat ) { // Create a matching label QString label = GeoDataCoordinates::latToString( itStep, notation, GeoDataCoordinates::Degree, -1, 'g' ); // No additional labels for the equator if ( labelPositionFlags.testFlag( LineCenter ) && itStep == 0.0 ) { label.clear(); } // Paint all latitude coordinate lines except for the equator if ( itStep != 0.0 && fmod(itStep, skipStep) != 0 ) { renderLatitudeLine( painter, itStep, viewLatLonAltBox, label, labelPositionFlags ); } itStep += step; } } void GraticulePlugin::renderUtmExceptions( GeoPainter *painter, const GeoDataLatLonAltBox& viewLatLonAltBox, qreal itStep, qreal northPolarGap, qreal southPolarGap, const QString & label, LabelPositionFlags labelPositionFlags ) { // This code renders the so called "exceptions" in the UTM coordinate grid // See: http://en.wikipedia.org/wiki/Universal_Transverse_Mercator_coordinate_system#Exceptions if ( northPolarGap == 6.0 && southPolarGap == 162.0) { if (label == QLatin1String("33")) { renderLongitudeLine( painter, itStep-3.0, viewLatLonAltBox, northPolarGap, southPolarGap, label, labelPositionFlags ); } else if (label == QLatin1String("35")) { renderLongitudeLine( painter, itStep-3.0, viewLatLonAltBox, northPolarGap, southPolarGap, label, labelPositionFlags ); } else if (label == QLatin1String("37")) { renderLongitudeLine( painter, itStep-3.0, viewLatLonAltBox, northPolarGap, southPolarGap, label, labelPositionFlags ); } else if (label == QLatin1String("32") || label == QLatin1String("34") || label == QLatin1String("36")) { // paint nothing } else { renderLongitudeLine( painter, itStep, viewLatLonAltBox, northPolarGap, southPolarGap, label, labelPositionFlags ); } } else if ( northPolarGap == 26.0 && southPolarGap == 146.0 ) { if (label == QLatin1String("32")) { renderLongitudeLine( painter, itStep-3.0, viewLatLonAltBox, northPolarGap, southPolarGap, label, labelPositionFlags ); } else { renderLongitudeLine( painter, itStep, viewLatLonAltBox, northPolarGap, southPolarGap, label, labelPositionFlags ); } } else { renderLongitudeLine( painter, itStep, viewLatLonAltBox, northPolarGap, southPolarGap, label, labelPositionFlags ); } } void GraticulePlugin::renderLongitudeLines( GeoPainter *painter, const GeoDataLatLonAltBox& viewLatLonAltBox, qreal step, qreal skipStep, qreal northPolarGap, qreal southPolarGap, - LabelPositionFlags labelPositionFlags ) + LabelPositionFlags labelPositionFlags) const { if ( step <= 0 ) { return; } const bool isSky = (marbleModel()->planet()->id() == QLatin1String("sky")); const GeoDataCoordinates::Notation notation = isSky ? GeoDataCoordinates::Astro : GeoDataCoordinates::defaultNotation(); // Set precision to 0 in UTM in order to show only zone number. int precision = (notation == GeoDataCoordinates::UTM) ? 0 : -1; // Longitude qreal westLon = viewLatLonAltBox.west( GeoDataCoordinates::Degree ); qreal eastLon = viewLatLonAltBox.east( GeoDataCoordinates::Degree ); qreal westLineLon = step * static_cast( westLon / step ); qreal eastLineLon = step * ( static_cast( eastLon / step ) + 1 ); if ( !viewLatLonAltBox.crossesDateLine() || ( westLon == -180.0 && eastLon == +180.0 ) ) { qreal itStep = westLineLon; while ( itStep < eastLineLon ) { // Create a matching label QString label = GeoDataCoordinates::lonToString( itStep, notation, GeoDataCoordinates::Degree, precision, 'g' ); // No additional labels for the prime meridian and the antimeridian if ( labelPositionFlags.testFlag( LineCenter ) && ( itStep == 0.0 || itStep == 180.0 || itStep == -180.0 ) ) { label.clear(); } // Paint all longitude coordinate lines (except for the meridians in non-UTM mode) if (notation == GeoDataCoordinates::UTM ) { renderUtmExceptions( painter, viewLatLonAltBox, itStep, northPolarGap, southPolarGap, label, labelPositionFlags ); } else if ( itStep != 0.0 && itStep != 180.0 && itStep != -180.0 ) { if (fmod(itStep, skipStep) != 0 || skipStep == 0.0) { renderLongitudeLine( painter, itStep, viewLatLonAltBox, northPolarGap, southPolarGap, label, labelPositionFlags ); } } itStep += step; } } else { qreal itStep = eastLineLon; while ( itStep < 180.0 ) { // Create a matching label QString label = GeoDataCoordinates::lonToString( itStep, notation, GeoDataCoordinates::Degree, precision, 'g' ); // No additional labels for the prime meridian and the antimeridian if ( labelPositionFlags.testFlag( LineCenter ) && ( itStep == 0.0 || itStep == 180.0 || itStep == -180.0 ) ) { label.clear(); } // Paint all longitude coordinate lines (except for the meridians in non-UTM mode) if (notation == GeoDataCoordinates::UTM ) { renderUtmExceptions( painter, viewLatLonAltBox, itStep, northPolarGap, southPolarGap, label, labelPositionFlags ); } else if ( itStep != 0.0 && itStep != 180.0 && itStep != -180.0 ) { if (fmod((itStep), skipStep) != 0 || skipStep == 0.0) { renderLongitudeLine( painter, itStep, viewLatLonAltBox, northPolarGap, southPolarGap, label, labelPositionFlags ); } } itStep += step; } itStep = -180.0; while ( itStep < westLineLon ) { // Create a matching label QString label = GeoDataCoordinates::lonToString( itStep, notation, GeoDataCoordinates::Degree, precision, 'g' ); // No additional labels for the prime meridian and the antimeridian if ( labelPositionFlags.testFlag( LineCenter ) && ( itStep == 0.0 || itStep == 180.0 || itStep == -180.0 ) ) { label.clear(); } // Paint all longitude coordinate lines (except for the meridians in non-UTM mode) if (notation == GeoDataCoordinates::UTM ) { renderUtmExceptions( painter, viewLatLonAltBox, itStep, northPolarGap, southPolarGap, label, labelPositionFlags ); } else if ( itStep != 0.0 && itStep != 180.0 && itStep != -180.0 ) { if (fmod((itStep+180), skipStep) != 0 || skipStep == 0.0) { renderLongitudeLine( painter, itStep, viewLatLonAltBox, northPolarGap, southPolarGap, label, labelPositionFlags ); } } itStep += step; } } } void GraticulePlugin::initLineMaps( GeoDataCoordinates::Notation notation) { /* Define Upper Bound keys and associated values: The key number is the globe radius in pixel. The value number is the amount of grid lines for the full range. Example: up to a 100 pixel radius the globe is covered with 4 longitude lines (4 half-circles). */ if (marbleModel()->planet()->id() == QLatin1String("sky") || notation == GeoDataCoordinates::Astro) { m_normalLineMap[100] = 4; // 6h m_normalLineMap[1000] = 12; // 2h m_normalLineMap[2000] = 24; // 1h m_normalLineMap[4000] = 48; // 30 min m_normalLineMap[8000] = 96; // 15 min m_normalLineMap[16000] = 288; // 5 min m_normalLineMap[100000] = 24 * 60; // 1 min m_normalLineMap[200000] = 24 * 60 * 2; // 30 sec m_normalLineMap[400000] = 24 * 60 * 4; // 15 sec m_normalLineMap[1200000] = 24 * 60 * 12; // 5 sec m_normalLineMap[6000000] = 24 * 60 * 60; // 1 sec m_normalLineMap[12000000] = 24 * 60 * 60 * 2; // 0.5 sec m_normalLineMap[24000000] = 24 * 60 * 60 * 4; // 0.25 sec m_boldLineMap[1000] = 0; // 0h m_boldLineMap[2000] = 4; // 6h m_boldLineMap[16000] = 24; // 30 deg return; } m_normalLineMap[100] = 4; // 90 deg m_normalLineMap[1000] = 12; // 30 deg m_normalLineMap[4000] = 36; // 10 deg m_normalLineMap[16000] = 72; // 5 deg m_normalLineMap[64000] = 360; // 1 deg m_normalLineMap[128000] = 720; // 0.5 deg m_boldLineMap[1000] = 0; // 0 deg m_boldLineMap[4000] = 12; // 30 deg m_boldLineMap[16000] = 36; // 10 deg switch ( notation ) { case GeoDataCoordinates::Decimal : m_normalLineMap[512000] = 360 * 10; // 0.1 deg m_normalLineMap[2048000] = 360 * 20; // 0.05 deg m_normalLineMap[8192000] = 360 * 100; // 0.01 deg m_normalLineMap[16384000] = 360 * 200; // 0.005 deg m_normalLineMap[32768000] = 360 * 1000; // 0.001 deg m_normalLineMap[131072000] = 360 * 2000; // 0.0005 deg m_normalLineMap[524288000] = 360 * 10000; // 0.00001 deg m_boldLineMap[512000] = 360; // 0.1 deg m_boldLineMap[2048000] = 720; // 0.05 deg m_boldLineMap[8192000] = 360 * 10; // 0.01 deg m_boldLineMap[1638400] = 360 * 20; // 0.005 deg m_boldLineMap[32768000] = 360 * 100; // 0.001 deg m_boldLineMap[131072000] = 360 * 200; // 0.0005 deg m_boldLineMap[524288000] = 360 * 1000; // 0.00001 deg break; default: case GeoDataCoordinates::DMS : m_normalLineMap[512000] = 360 * 6; // 10' m_normalLineMap[1024000] = 360 * 12; // 5' m_normalLineMap[4096000] = 360 * 60; // 1' m_normalLineMap[8192000] = 360 * 60 * 2; // 30" m_normalLineMap[16384000] = 360 * 60 * 6; // 10" m_normalLineMap[65535000] = 360 * 60 * 12; // 5" m_normalLineMap[524288000] = 360 * 60 * 60; // 1" m_boldLineMap[512000] = 360; // 10' m_boldLineMap[1024000] = 720; // 5' m_boldLineMap[4096000] = 360 * 6; // 1' m_boldLineMap[8192000] = 360 * 12; // 30" m_boldLineMap[16384000] = 360 * 60; // 10" m_boldLineMap[65535000] = 360 * 60 * 2; // 5" m_boldLineMap[524288000] = 360 * 60 * 6; // 1" break; } m_normalLineMap[999999999] = m_normalLineMap.value(262144000); // last m_boldLineMap[999999999] = m_boldLineMap.value(262144000); // last m_currentNotation = notation; } } #include "moc_GraticulePlugin.cpp" diff --git a/src/plugins/render/graticule/GraticulePlugin.h b/src/plugins/render/graticule/GraticulePlugin.h index 9d55d0152..a02a10b7f 100644 --- a/src/plugins/render/graticule/GraticulePlugin.h +++ b/src/plugins/render/graticule/GraticulePlugin.h @@ -1,230 +1,230 @@ // // This file is part of the Marble Virtual Globe. // // This program is free software licensed under the GNU LGPL. You can // find a copy of this license in LICENSE.txt in the top directory of // the source code. // // Copyright 2009 Torsten Rahn // // // This class is a graticule plugin. // #ifndef MARBLEGRATICULEPLUGIN_H #define MARBLEGRATICULEPLUGIN_H #include #include #include #include #include #include "DialogConfigurationInterface.h" #include "RenderPlugin.h" #include "GeoDataCoordinates.h" #include "GeoDataLatLonAltBox.h" namespace Ui { class GraticuleConfigWidget; } namespace Marble { class GeoDataLatLonAltBox; /** * @brief A plugin that creates a coordinate grid on top of the map. * Unlike in all other classes we are using degree by default in this class. * This choice was made due to the fact that all common coordinate grids focus fully * on the degree system. */ class GraticulePlugin : public RenderPlugin, public DialogConfigurationInterface { Q_OBJECT Q_PLUGIN_METADATA(IID "org.kde.marble.GraticulePlugin") Q_INTERFACES( Marble::RenderPluginInterface ) Q_INTERFACES( Marble::DialogConfigurationInterface ) MARBLE_PLUGIN( GraticulePlugin ) public: GraticulePlugin(); explicit GraticulePlugin( const MarbleModel *marbleModel ); QStringList backendTypes() const override; QString renderPolicy() const override; QStringList renderPosition() const override; QString name() const override; QString guiString() const override; QString nameId() const override; QString version() const override; QString description() const override; QString copyrightYears() const override; QVector pluginAuthors() const override; QIcon icon () const override; QDialog *configDialog() override; void initialize () override; bool isInitialized () const override; bool render( GeoPainter *painter, ViewportParams *viewport, const QString& renderPos, GeoSceneLayer * layer = 0 ) override; qreal zValue() const override; QHash settings() const override; void setSettings( const QHash &settings ) override; public Q_SLOTS: void readSettings(); void writeSettings(); void gridGetColor(); void tropicsGetColor(); void equatorGetColor(); private: /** * @brief Renders the coordinate grid within the defined view bounding box. * @param painter the painter used to draw the grid * @param viewport the viewport */ void renderGrid( GeoPainter *painter, ViewportParams *viewport, const QPen& equatorCirclePen, const QPen& tropicsCirclePen, const QPen& gridCirclePen ); /** * @brief Renders a latitude line within the defined view bounding box. * @param painter the painter used to draw the latitude line * @param latitude the latitude of the coordinate line measured in degree . * @param viewLatLonAltBox the latitude longitude bounding box that is covered by the view. */ static void renderLatitudeLine( GeoPainter *painter, qreal latitude, const GeoDataLatLonAltBox& viewLatLonAltBox = GeoDataLatLonAltBox(), const QString& lineLabel = QString(), LabelPositionFlags labelPositionFlags = LineCenter ); /** * @brief Renders a longitude line within the defined view bounding box. * @param painter the painter used to draw the latitude line * @param longitude the longitude of the coordinate line measured in degree . * @param viewLatLonAltBox the latitude longitude bounding box that is covered by the view. * @param polarGap the area around the poles in which most longitude lines are not drawn * for reasons of aesthetics and clarity of the map. The polarGap avoids narrow * concurring lines around the poles which obstruct the view onto the surface. * The radius of the polarGap area is measured in degrees. * @param lineLabel draws a label using the font and color properties set for the painter. */ static void renderLongitudeLine( GeoPainter *painter, qreal longitude, const GeoDataLatLonAltBox& viewLatLonAltBox = GeoDataLatLonAltBox(), qreal northPolarGap = 0.0, qreal southPolarGap = 0.0, const QString& lineLabel = QString(), LabelPositionFlags labelPositionFlags = LineCenter ); /** * @brief Renders the latitude lines that are visible within the defined view bounding box. * @param painter the painter used to draw the latitude lines * @param viewLatLonAltBox the latitude longitude bounding box that is covered by the view. * @param step the angular distance between the lines measured in degrees . */ void renderLatitudeLines( GeoPainter *painter, const GeoDataLatLonAltBox& viewLatLonAltBox, qreal step, qreal skipStep, LabelPositionFlags labelPositionFlags = LineCenter ); /** * @brief Renders the longitude lines that are visible within the defined view bounding box. * @param painter the painter used to draw the latitude lines * @param viewLatLonAltBox the latitude longitude bounding box that is covered by the view. * @param step the angular distance between the lines measured in degrees . * @param northPolarGap the area around the north pole in which most longitude lines are not drawn * for reasons of aesthetics and clarity of the map. The polarGap avoids narrow * concurring lines around the poles which obstruct the view onto the surface. * The radius of the polarGap area is measured in degrees. * @param southPolarGap the area around the south pole in which most longitude lines are not drawn * for reasons of aesthetics and clarity of the map. The polarGap avoids narrow * concurring lines around the poles which obstruct the view onto the surface. * The radius of the polarGap area is measured in degrees. */ void renderLongitudeLines( GeoPainter *painter, const GeoDataLatLonAltBox& viewLatLonAltBox, qreal step, qreal skipStep, qreal northPolarGap = 0.0, qreal southPolarGap = 0.0, LabelPositionFlags labelPositionFlags = LineCenter - ); + ) const; /** * @brief Renders UTM exceptions that are visible within the defined view bounding box. * @param painter the painter used to draw the latitude lines * @param viewLatLonAltBox the latitude longitude bounding box that is covered by the view. * @param step the angular distance between the lines measured in degrees . * @param northPolarGap the area around the north pole in which most longitude lines are not drawn * for reasons of aesthetics and clarity of the map. The polarGap avoids narrow * concurring lines around the poles which obstruct the view onto the surface. * The radius of the polarGap area is measured in degrees. * @param southPolarGap the area around the south pole in which most longitude lines are not drawn * for reasons of aesthetics and clarity of the map. The polarGap avoids narrow * concurring lines around the poles which obstruct the view onto the surface. * The radius of the polarGap area is measured in degrees. */ static void renderUtmExceptions( GeoPainter *painter, const GeoDataLatLonAltBox& viewLatLonAltBox, qreal step, qreal northPolarGap, qreal southPolarGap, const QString & label, LabelPositionFlags labelPositionFlags ); /** * @brief Maps the number of coordinate lines per 360 deg against the globe radius on the screen. * @param notation Determines whether the graticule is according to the DMS or Decimal system. */ void initLineMaps( GeoDataCoordinates::Notation notation ); GeoDataCoordinates::Notation m_currentNotation; // Maps the zoom factor to the amount of lines per 360 deg QMap m_boldLineMap; QMap m_normalLineMap; QPen m_equatorCirclePen; QPen m_tropicsCirclePen; QPen m_gridCirclePen; bool m_showPrimaryLabels; bool m_showSecondaryLabels; bool m_isInitialized; QIcon m_icon; Ui::GraticuleConfigWidget *ui_configWidget; QDialog *m_configDialog; }; } #endif // MARBLEGRATICULEPLUGIN_H