2525#include < stdexcept>
2626
2727#include < QApplication>
28+ #include < QtMath>
2829#include < QColor>
2930#include < QContextMenuEvent>
3031#include < QEvent>
@@ -108,6 +109,8 @@ MapWidget::MapWidget(bool show_help, bool force_antialiasing, QWidget* parent)
108109 , dragging(false )
109110 , pinching(false )
110111 , pinching_factor(1.0 )
112+ , pinching_angle(0.0 )
113+ , auto_rotation_active(false )
111114 , below_template_cache_dirty_rect(rect())
112115 , above_template_cache_dirty_rect(rect())
113116 , map_cache_dirty_rect(rect())
@@ -192,6 +195,11 @@ void MapWidget::setActivity(MapEditorActivity* activity)
192195}
193196
194197
198+ void MapWidget::setAutoRotationActive (bool active)
199+ {
200+ auto_rotation_active = active;
201+ }
202+
195203void MapWidget::setGesturesEnabled (bool enabled)
196204{
197205 gestures_enabled = enabled;
@@ -396,29 +404,50 @@ qreal MapWidget::startPinching(const QPoint& center)
396404 drag_start_pos = center;
397405 pinching_center = center;
398406 pinching_factor = 1.0 ;
407+ pinching_angle = 0.0 ;
399408 return pinching_factor;
400409}
401410
402- void MapWidget::updatePinching (const QPoint& center, qreal factor)
411+ void MapWidget::updatePinching (const QPoint& center, qreal factor, qreal angle )
403412{
404413 Q_ASSERT (pinching);
405414 pinching_center = center;
406415 pinching_factor = factor;
416+ pinching_angle = auto_rotation_active ? 0.0 : angle;
407417 updateZoomDisplay ();
408418 update ();
409419}
410420
411- void MapWidget::finishPinching (const QPoint& center, qreal factor)
421+ void MapWidget::finishPinching (const QPoint& center, qreal factor, qreal angle )
412422{
413423 pinching = false ;
414424 view->finishPanning (center - drag_start_pos);
415425 view->setZoom (factor * view->getZoom (), viewportToView (center));
426+ if (!auto_rotation_active && angle != 0.0 )
427+ {
428+ double delta_rad = qDegreesToRadians (angle);
429+ QPointF rot_center_view = viewportToView (center);
430+ auto rot_center_map = MapCoordF (view->viewToMap (rot_center_view));
431+ auto old_center = MapCoordF (view->center ());
432+
433+ view->setRotation (view->getRotation () + delta_rad);
434+
435+ // Adjust center so the pinch center stays fixed on screen
436+ auto offset = old_center - rot_center_map;
437+ auto cos_d = cos (delta_rad);
438+ auto sin_d = sin (delta_rad);
439+ auto new_center = rot_center_map + MapCoordF (
440+ offset.x () * cos_d + offset.y () * sin_d,
441+ -offset.x () * sin_d + offset.y () * cos_d);
442+ view->setCenter (MapCoord (new_center));
443+ }
416444}
417445
418446void MapWidget::cancelPinching ()
419447{
420448 pinching = false ;
421449 pinching_factor = 1.0 ;
450+ pinching_angle = 0.0 ;
422451 update ();
423452}
424453
@@ -854,6 +883,7 @@ void MapWidget::gestureEvent(QGestureEvent* event)
854883 QPinchGesture* pinch = static_cast <QPinchGesture *>(gesture);
855884 QPoint center = pinch->centerPoint ().toPoint ();
856885 qreal factor = pinch->totalScaleFactor ();
886+ qreal rotation_angle = pinch->totalRotationAngle ();
857887 switch (pinch->state ())
858888 {
859889 case Qt::GestureStarted:
@@ -867,10 +897,10 @@ void MapWidget::gestureEvent(QGestureEvent* event)
867897 pinch->setTotalScaleFactor (factor);
868898 break ;
869899 case Qt::GestureUpdated:
870- updatePinching (center, factor);
900+ updatePinching (center, factor, rotation_angle );
871901 break ;
872902 case Qt::GestureFinished:
873- finishPinching (center, factor);
903+ finishPinching (center, factor, rotation_angle );
874904 break ;
875905 case Qt::GestureCanceled:
876906 cancelPinching ();
@@ -910,23 +940,39 @@ void MapWidget::paintEvent(QPaintEvent* event)
910940 QRect target = exposed;
911941 if (pinching)
912942 {
913- if (pinching_factor < 1.0 )
914- {
915- // Zoom-out: draw the uncovered region first, then overlay
916- // the scaled cache on top.
917- drawPinchUncoveredRegion (painter, exposed);
918- }
919- else
920- {
943+ // Build pinch transform (may include rotation)
944+ QTransform pinch_xf;
945+ pinch_xf.translate (pinching_center.x (), pinching_center.y ());
946+ if (pinching_angle != 0.0 )
947+ pinch_xf.rotate (pinching_angle);
948+ pinch_xf.scale (pinching_factor, pinching_factor);
949+ pinch_xf.translate (-drag_start_pos.x (), -drag_start_pos.y ());
950+
951+ // Check if there are uncovered corners (zoom-out or rotation)
952+ QPolygon covered = pinch_xf.mapToPolygon (exposed);
953+ QRegion uncovered = QRegion (exposed) - QRegion (covered);
954+ if (uncovered.isEmpty () || !drawPinchUncoveredRegion (painter, exposed))
921955 painter.fillRect (exposed, QColor (Qt::gray));
922- }
956+
923957 painter.translate (pinching_center.x (), pinching_center.y ());
958+ if (pinching_angle != 0.0 )
959+ painter.rotate (pinching_angle);
924960 painter.scale (pinching_factor, pinching_factor);
925961 painter.translate (-drag_start_pos.x (), -drag_start_pos.y ());
926962 }
927963 else if (pan_offset != QPoint ())
928964 {
929- drawPanUncoveredRegion (painter, exposed);
965+ if (!drawPanUncoveredRegion (painter, exposed))
966+ {
967+ if (pan_offset.x () > 0 )
968+ painter.fillRect (QRect (0 , pan_offset.y (), pan_offset.x (), height () - pan_offset.y ()), QColor (Qt::gray));
969+ else if (pan_offset.x () < 0 )
970+ painter.fillRect (QRect (width () + pan_offset.x (), pan_offset.y (), -pan_offset.x (), height () - pan_offset.y ()), QColor (Qt::gray));
971+ if (pan_offset.y () > 0 )
972+ painter.fillRect (QRect (0 , 0 , width (), pan_offset.y ()), QColor (Qt::gray));
973+ else if (pan_offset.y () < 0 )
974+ painter.fillRect (QRect (0 , height () + pan_offset.y (), width (), -pan_offset.y ()), QColor (Qt::gray));
975+ }
930976 target.translate (pan_offset);
931977 }
932978
@@ -1421,54 +1467,52 @@ void MapWidget::drawTemplateCache(QPainter& painter, const QImage& cache, const
14211467 painter.restore ();
14221468}
14231469
1424- void MapWidget::drawPinchUncoveredRegion (QPainter& painter, const QRect& exposed) const
1470+ bool MapWidget::drawPinchUncoveredRegion (QPainter& painter, const QRect& exposed) const
14251471{
14261472 Q_ASSERT (pinching);
1427- Q_ASSERT (pinching_factor < 1.0 );
14281473
1429- // The pinch transform scales the cache around pinching_center. When
1430- // zooming out the scaled cache is smaller than the widget, leaving an
1431- // uncovered border. Instead of computing a separate "virtual" view
1432- // state, we use the exact same composite transform that paintEvent
1433- // applies to the cache:
1434- // pinch_xf * translate(w/2, h/2) * worldTransform
1435- // This guarantees that the uncovered region aligns perfectly with the
1436- // scaled cache — no independent center/zoom calculation needed.
1474+ int rendering_level = Settings::getInstance ().getSettingCached (Settings::MapDisplay_GestureExtraRendering).toInt ();
1475+ if (rendering_level <= 0 )
1476+ return false ;
1477+
1478+ // The pinch transform scales (and optionally rotates) the cache around
1479+ // pinching_center. We use the exact same composite transform that
1480+ // paintEvent applies to the cache to guarantee perfect alignment.
14371481
14381482 // 1. Build the pinch-to-viewport transform (same as paintEvent sets on the painter)
14391483 QTransform pinch_xf;
14401484 pinch_xf.translate (pinching_center.x (), pinching_center.y ());
1485+ if (pinching_angle != 0.0 )
1486+ pinch_xf.rotate (pinching_angle);
14411487 pinch_xf.scale (pinching_factor, pinching_factor);
14421488 pinch_xf.translate (-drag_start_pos.x (), -drag_start_pos.y ());
14431489
1444- // 2. Determine the area covered by the scaled cache
1445- QRect cache_rect = exposed; // the cache covers the full widget
1490+ // 2. Determine the area covered by the scaled/rotated cache
1491+ QRect cache_rect = exposed;
14461492 QPolygon covered = pinch_xf.mapToPolygon (cache_rect);
1447- QRegion covered_region (covered);
1448- QRegion uncovered = QRegion (exposed) - covered_region;
1493+ QRegion uncovered = QRegion (exposed) - QRegion (covered);
14491494 if (uncovered.isEmpty ())
1450- return ; // zoom-in or fully covered — nothing to do
1495+ return true ;
14511496
1452- // 3. Set up the composite transform:
1453- // pinch * translate(w/2, h/2) * view->worldTransform()
1454- // This is the same transform the cache content goes through.
1497+ // 3. Set up the composite transform
14551498 auto setupTransform = [&](QPainter& p) {
14561499 p.translate (pinching_center.x (), pinching_center.y ());
1500+ if (pinching_angle != 0.0 )
1501+ p.rotate (pinching_angle);
14571502 p.scale (pinching_factor, pinching_factor);
14581503 p.translate (-drag_start_pos.x (), -drag_start_pos.y ());
14591504 p.translate (width () / 2.0 , height () / 2.0 );
14601505 p.setWorldTransform (view->worldTransform (), true );
14611506 };
14621507
14631508 // Compute map_rect from the inverse of the full composite transform
1464- // We need a temporary transform to get the inverse
14651509 QTransform composite = pinch_xf;
14661510 composite.translate (width () / 2.0 , height () / 2.0 );
14671511 composite = view->worldTransform () * composite;
14681512 bool invertible = false ;
14691513 QTransform inv = composite.inverted (&invertible);
14701514 if (!invertible)
1471- return ;
1515+ return false ;
14721516 QRectF map_rect = inv.mapRect (QRectF (exposed));
14731517
14741518 double ppm = view->calculateFinalZoomFactor () * pinching_factor;
@@ -1495,27 +1539,30 @@ void MapWidget::drawPinchUncoveredRegion(QPainter& painter, const QRect& exposed
14951539 painter.fillRect (exposed, Qt::white);
14961540 }
14971541
1498- // Map objects
1499- const auto map_visibility = view->effectiveMapVisibility ();
1500- if (map_visibility.visible )
1542+ // Map objects (only at full rendering level)
1543+ if (rendering_level >= 2 )
15011544 {
1502- painter.save ();
1503- qreal saved_opacity = painter.opacity ();
1504- painter.setOpacity (map_visibility.opacity );
1505-
1506- RenderConfig::Options options (RenderConfig::Screen | RenderConfig::HelperSymbols);
1507- bool use_antialiasing = force_antialiasing || Settings::getInstance ().getSettingCached (Settings::MapDisplay_Antialiasing).toBool ();
1508- if (use_antialiasing)
1509- painter.setRenderHint (QPainter::Antialiasing);
1510- else
1511- options |= RenderConfig::DisableAntialiasing | RenderConfig::ForceMinSize;
1545+ const auto map_visibility = view->effectiveMapVisibility ();
1546+ if (map_visibility.visible )
1547+ {
1548+ painter.save ();
1549+ qreal saved_opacity = painter.opacity ();
1550+ painter.setOpacity (map_visibility.opacity );
1551+
1552+ RenderConfig::Options options (RenderConfig::Screen | RenderConfig::HelperSymbols);
1553+ bool use_antialiasing = force_antialiasing || Settings::getInstance ().getSettingCached (Settings::MapDisplay_Antialiasing).toBool ();
1554+ if (use_antialiasing)
1555+ painter.setRenderHint (QPainter::Antialiasing);
1556+ else
1557+ options |= RenderConfig::DisableAntialiasing | RenderConfig::ForceMinSize;
15121558
1513- setupTransform (painter);
1514- RenderConfig config = { *map, map_rect, ppm, options, 1.0 };
1515- map->draw (&painter, config);
1559+ setupTransform (painter);
1560+ RenderConfig config = { *map, map_rect, ppm, options, 1.0 };
1561+ map->draw (&painter, config);
15161562
1517- painter.setOpacity (saved_opacity);
1518- painter.restore ();
1563+ painter.setOpacity (saved_opacity);
1564+ painter.restore ();
1565+ }
15191566 }
15201567
15211568 // Above templates
@@ -1529,17 +1576,22 @@ void MapWidget::drawPinchUncoveredRegion(QPainter& painter, const QRect& exposed
15291576 }
15301577
15311578 painter.restore ();
1579+ return true ;
15321580}
15331581
1534- void MapWidget::drawPanUncoveredRegion (QPainter& painter, const QRect& exposed) const
1582+ bool MapWidget::drawPanUncoveredRegion (QPainter& painter, const QRect& exposed) const
15351583{
15361584 Q_ASSERT (pan_offset != QPoint ());
15371585
1586+ int rendering_level = Settings::getInstance ().getSettingCached (Settings::MapDisplay_GestureExtraRendering).toInt ();
1587+ if (rendering_level <= 0 )
1588+ return false ;
1589+
15381590 // The cache is drawn at target = exposed.translated(pan_offset).
15391591 // The uncovered region is what's in exposed but not in the shifted target.
15401592 QRegion uncovered = QRegion (exposed) - QRegion (exposed).translated (pan_offset);
15411593 if (uncovered.isEmpty ())
1542- return ;
1594+ return true ;
15431595
15441596 // Use the same composite transform as the panned cache:
15451597 // translate(pan_offset) * translate(w/2, h/2) * worldTransform
@@ -1557,7 +1609,7 @@ void MapWidget::drawPanUncoveredRegion(QPainter& painter, const QRect& exposed)
15571609 bool invertible = false ;
15581610 QTransform inv = composite.inverted (&invertible);
15591611 if (!invertible)
1560- return ;
1612+ return false ;
15611613 QRectF map_rect = inv.mapRect (QRectF (exposed));
15621614
15631615 double ppm = view->calculateFinalZoomFactor ();
@@ -1583,27 +1635,30 @@ void MapWidget::drawPanUncoveredRegion(QPainter& painter, const QRect& exposed)
15831635 painter.fillRect (exposed, Qt::white);
15841636 }
15851637
1586- // Map objects
1587- const auto map_visibility = view->effectiveMapVisibility ();
1588- if (map_visibility.visible )
1638+ // Map objects (only at full rendering level)
1639+ if (rendering_level >= 2 )
15891640 {
1590- painter.save ();
1591- qreal saved_opacity = painter.opacity ();
1592- painter.setOpacity (map_visibility.opacity );
1593-
1594- RenderConfig::Options options (RenderConfig::Screen | RenderConfig::HelperSymbols);
1595- bool use_antialiasing = force_antialiasing || Settings::getInstance ().getSettingCached (Settings::MapDisplay_Antialiasing).toBool ();
1596- if (use_antialiasing)
1597- painter.setRenderHint (QPainter::Antialiasing);
1598- else
1599- options |= RenderConfig::DisableAntialiasing | RenderConfig::ForceMinSize;
1641+ const auto map_visibility = view->effectiveMapVisibility ();
1642+ if (map_visibility.visible )
1643+ {
1644+ painter.save ();
1645+ qreal saved_opacity = painter.opacity ();
1646+ painter.setOpacity (map_visibility.opacity );
1647+
1648+ RenderConfig::Options options (RenderConfig::Screen | RenderConfig::HelperSymbols);
1649+ bool use_antialiasing = force_antialiasing || Settings::getInstance ().getSettingCached (Settings::MapDisplay_Antialiasing).toBool ();
1650+ if (use_antialiasing)
1651+ painter.setRenderHint (QPainter::Antialiasing);
1652+ else
1653+ options |= RenderConfig::DisableAntialiasing | RenderConfig::ForceMinSize;
16001654
1601- setupTransform (painter);
1602- RenderConfig config = { *map, map_rect, ppm, options, 1.0 };
1603- map->draw (&painter, config);
1655+ setupTransform (painter);
1656+ RenderConfig config = { *map, map_rect, ppm, options, 1.0 };
1657+ map->draw (&painter, config);
16041658
1605- painter.setOpacity (saved_opacity);
1606- painter.restore ();
1659+ painter.setOpacity (saved_opacity);
1660+ painter.restore ();
1661+ }
16071662 }
16081663
16091664 // Above templates
@@ -1617,6 +1672,7 @@ void MapWidget::drawPanUncoveredRegion(QPainter& painter, const QRect& exposed)
16171672 }
16181673
16191674 painter.restore ();
1675+ return true ;
16201676}
16211677
16221678void MapWidget::updateTemplateCache (QImage& cache, QRect& dirty_rect, TemplateCacheViewState& state, int first_template, int last_template, bool use_background)
0 commit comments