justified test and two finger scrolling
All checks were successful
Python CI / test (push) Successful in 1m27s
Lint / lint (push) Successful in 1m6s
Tests / test (3.11) (push) Successful in 1m42s
Tests / test (3.12) (push) Successful in 1m43s
Tests / test (3.13) (push) Successful in 1m37s
Tests / test (3.14) (push) Successful in 1m16s

This commit is contained in:
Duncan Tourolle 2025-12-01 20:40:11 +01:00
parent 80d7d291f3
commit 8f1e906884
6 changed files with 193 additions and 3 deletions

View File

@ -77,6 +77,12 @@ class GLWidget(
self.setFocusPolicy(Qt.FocusPolicy.StrongFocus) self.setFocusPolicy(Qt.FocusPolicy.StrongFocus)
self.setFocus() self.setFocus()
# Enable gesture support for pinch-to-zoom
self.grabGesture(Qt.GestureType.PinchGesture)
# Track pinch gesture state
self._pinch_scale_factor = 1.0
def window(self): def window(self):
"""Override window() to return stored main_window reference. """Override window() to return stored main_window reference.
@ -154,3 +160,183 @@ class GLWidget(
else: else:
super().keyPressEvent(event) super().keyPressEvent(event)
def event(self, event):
"""Handle gesture events for pinch-to-zoom"""
from PyQt6.QtCore import QEvent, Qt as QtCore
from PyQt6.QtWidgets import QPinchGesture
from PyQt6.QtGui import QNativeGestureEvent
# Handle native touchpad gestures (Linux, macOS)
if event.type() == QEvent.Type.NativeGesture:
native_event = event
gesture_type = native_event.gestureType()
print(f"DEBUG: Native gesture detected - type: {gesture_type}")
# Check for zoom/pinch gesture
if gesture_type == QtCore.NativeGestureType.ZoomNativeGesture:
# Get zoom value (typically a delta around 0)
value = native_event.value()
print(f"DEBUG: Zoom value: {value}")
# Convert to scale factor (value is typically small, like -0.1 to 0.1)
# Positive value = zoom in, negative = zoom out
scale_factor = 1.0 + value
# Get the position of the gesture
pos = native_event.position()
mouse_x = pos.x()
mouse_y = pos.y()
self._apply_zoom_at_point(mouse_x, mouse_y, scale_factor)
return True
# Check for pan gesture (two-finger drag)
elif gesture_type == QtCore.NativeGestureType.PanNativeGesture:
# Get the pan delta
delta = native_event.delta()
dx = delta.x()
dy = delta.y()
print(f"DEBUG: Pan delta: dx={dx}, dy={dy}")
# Apply pan
self.pan_offset[0] += dx
self.pan_offset[1] += dy
# Clamp pan offset to content bounds
if hasattr(self, "clamp_pan_offset"):
self.clamp_pan_offset()
self.update()
# Update scrollbars if available
main_window = self.window()
if hasattr(main_window, "update_scrollbars"):
main_window.update_scrollbars()
return True
# Handle Qt gesture events (fallback for other platforms)
elif event.type() == QEvent.Type.Gesture:
print("DEBUG: Qt Gesture event detected")
gesture_event = event
pinch = gesture_event.gesture(Qt.GestureType.PinchGesture)
if pinch:
print(f"DEBUG: Pinch gesture detected - state: {pinch.state()}, scale: {pinch.totalScaleFactor()}")
self._handle_pinch_gesture(pinch)
return True
return super().event(event)
def _handle_pinch_gesture(self, pinch):
"""Handle pinch gesture for zooming"""
from PyQt6.QtCore import Qt as QtCore
# Check gesture state
state = pinch.state()
if state == QtCore.GestureState.GestureStarted:
# Reset scale factor at gesture start
self._pinch_scale_factor = 1.0
return
elif state == QtCore.GestureState.GestureUpdated:
# Get current total scale factor
current_scale = pinch.totalScaleFactor()
# Calculate incremental change from last update
if current_scale > 0:
scale_change = current_scale / self._pinch_scale_factor
self._pinch_scale_factor = current_scale
# Get the center point of the pinch gesture
center_point = pinch.centerPoint()
mouse_x = center_point.x()
mouse_y = center_point.y()
# Calculate world coordinates at the pinch center
world_x = (mouse_x - self.pan_offset[0]) / self.zoom_level
world_y = (mouse_y - self.pan_offset[1]) / self.zoom_level
# Apply incremental zoom change
new_zoom = self.zoom_level * scale_change
# Clamp zoom level to reasonable bounds
if 0.1 <= new_zoom <= 5.0:
old_pan_x = self.pan_offset[0]
old_pan_y = self.pan_offset[1]
self.zoom_level = new_zoom
# Adjust pan offset to keep the pinch center point fixed
self.pan_offset[0] = mouse_x - world_x * self.zoom_level
self.pan_offset[1] = mouse_y - world_y * self.zoom_level
# If dragging, adjust drag_start_pos to account for pan_offset change
if hasattr(self, 'is_dragging') and self.is_dragging and hasattr(self, 'drag_start_pos') and self.drag_start_pos:
pan_delta_x = self.pan_offset[0] - old_pan_x
pan_delta_y = self.pan_offset[1] - old_pan_y
self.drag_start_pos = (self.drag_start_pos[0] + pan_delta_x, self.drag_start_pos[1] + pan_delta_y)
# Clamp pan offset to content bounds
if hasattr(self, "clamp_pan_offset"):
self.clamp_pan_offset()
self.update()
# Update status bar
main_window = self.window()
if hasattr(main_window, "status_bar"):
main_window.status_bar.showMessage(f"Zoom: {int(self.zoom_level * 100)}%", 2000)
# Update scrollbars if available
if hasattr(main_window, "update_scrollbars"):
main_window.update_scrollbars()
elif state == QtCore.GestureState.GestureFinished or state == QtCore.GestureState.GestureCanceled:
# Reset on gesture end
self._pinch_scale_factor = 1.0
def _apply_zoom_at_point(self, mouse_x, mouse_y, scale_factor):
"""Apply zoom centered at a specific point"""
# Calculate world coordinates at the zoom center
world_x = (mouse_x - self.pan_offset[0]) / self.zoom_level
world_y = (mouse_y - self.pan_offset[1]) / self.zoom_level
# Apply zoom
new_zoom = self.zoom_level * scale_factor
# Clamp zoom level to reasonable bounds
if 0.1 <= new_zoom <= 5.0:
old_pan_x = self.pan_offset[0]
old_pan_y = self.pan_offset[1]
self.zoom_level = new_zoom
# Adjust pan offset to keep the zoom center point fixed
self.pan_offset[0] = mouse_x - world_x * self.zoom_level
self.pan_offset[1] = mouse_y - world_y * self.zoom_level
# If dragging, adjust drag_start_pos to account for pan_offset change
if hasattr(self, 'is_dragging') and self.is_dragging and hasattr(self, 'drag_start_pos') and self.drag_start_pos:
pan_delta_x = self.pan_offset[0] - old_pan_x
pan_delta_y = self.pan_offset[1] - old_pan_y
self.drag_start_pos = (self.drag_start_pos[0] + pan_delta_x, self.drag_start_pos[1] + pan_delta_y)
# Clamp pan offset to content bounds
if hasattr(self, "clamp_pan_offset"):
self.clamp_pan_offset()
self.update()
# Update status bar
main_window = self.window()
if hasattr(main_window, "status_bar"):
main_window.status_bar.showMessage(f"Zoom: {int(self.zoom_level * 100)}%", 2000)
# Update scrollbars if available
if hasattr(main_window, "update_scrollbars"):
main_window.update_scrollbars()

View File

@ -281,6 +281,8 @@ class RenderingMixin:
alignment = Qt.AlignmentFlag.AlignHCenter | Qt.AlignmentFlag.AlignTop alignment = Qt.AlignmentFlag.AlignHCenter | Qt.AlignmentFlag.AlignTop
elif element.alignment == "right": elif element.alignment == "right":
alignment = Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignTop alignment = Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignTop
elif element.alignment == "justify":
alignment = Qt.AlignmentFlag.AlignJustify | Qt.AlignmentFlag.AlignTop
text_flags = Qt.TextFlag.TextWordWrap text_flags = Qt.TextFlag.TextWordWrap

View File

@ -10,7 +10,7 @@ from reportlab.pdfgen import canvas
from reportlab.lib.utils import ImageReader from reportlab.lib.utils import ImageReader
from reportlab.platypus import Paragraph from reportlab.platypus import Paragraph
from reportlab.lib.styles import ParagraphStyle from reportlab.lib.styles import ParagraphStyle
from reportlab.lib.enums import TA_LEFT, TA_CENTER, TA_RIGHT from reportlab.lib.enums import TA_LEFT, TA_CENTER, TA_RIGHT, TA_JUSTIFY
from PIL import Image from PIL import Image
import math import math
from pyPhotoAlbum.models import ImageData, TextBoxData, PlaceholderData from pyPhotoAlbum.models import ImageData, TextBoxData, PlaceholderData
@ -575,6 +575,7 @@ class PDFExporter:
"left": TA_LEFT, "left": TA_LEFT,
"center": TA_CENTER, "center": TA_CENTER,
"right": TA_RIGHT, "right": TA_RIGHT,
"justify": TA_JUSTIFY,
} }
text_alignment = alignment_map.get(text_element.alignment, TA_LEFT) text_alignment = alignment_map.get(text_element.alignment, TA_LEFT)

View File

@ -74,7 +74,7 @@ class TextEditDialog(QDialog):
alignment_layout = QHBoxLayout() alignment_layout = QHBoxLayout()
alignment_layout.addWidget(QLabel("Alignment:")) alignment_layout.addWidget(QLabel("Alignment:"))
self.alignment_combo = QComboBox() self.alignment_combo = QComboBox()
self.alignment_combo.addItems(["left", "center", "right"]) self.alignment_combo.addItems(["left", "center", "right", "justify"])
alignment_layout.addWidget(self.alignment_combo) alignment_layout.addWidget(self.alignment_combo)
alignment_layout.addStretch() alignment_layout.addStretch()
layout.addLayout(alignment_layout) layout.addLayout(alignment_layout)

View File

@ -496,7 +496,7 @@ class TestRenderTextOverlays:
qtbot.addWidget(widget) qtbot.addWidget(widget)
widget.resize(1000, 800) widget.resize(1000, 800)
for alignment in ["left", "center", "right"]: for alignment in ["left", "center", "right", "justify"]:
# Create text element with alignment # Create text element with alignment
text_element = TextBoxData( text_element = TextBoxData(
x=50, y=50, width=200, height=100, x=50, y=50, width=200, height=100,

View File

@ -178,6 +178,7 @@ class TestTextEditDialogUI:
assert "left" in options assert "left" in options
assert "center" in options assert "center" in options
assert "right" in options assert "right" in options
assert "justify" in options
def test_font_options(self, qtbot): def test_font_options(self, qtbot):
"""Test that font combo has expected fonts""" """Test that font combo has expected fonts"""