From 8a9a2a73f4ff0a3b925f0b2d861f224e9be4589f Mon Sep 17 00:00:00 2001 From: Duncan Tourolle Date: Fri, 24 Oct 2025 23:40:04 +0200 Subject: [PATCH] Fix runner and fox issue where text editor did not open --- pyPhotoAlbum/gl_widget.py | 49 ++++++++-- pyPhotoAlbum/pdf_exporter.py | 5 +- pyPhotoAlbum/text_edit_dialog.py | 155 +++++++++++++++++++++++++++++++ 3 files changed, 199 insertions(+), 10 deletions(-) create mode 100644 pyPhotoAlbum/text_edit_dialog.py diff --git a/pyPhotoAlbum/gl_widget.py b/pyPhotoAlbum/gl_widget.py index d13f6fa..bdbfc60 100644 --- a/pyPhotoAlbum/gl_widget.py +++ b/pyPhotoAlbum/gl_widget.py @@ -356,15 +356,18 @@ class GLWidget(QOpenGLWidget): # No rotation - draw normally rect = QRectF(screen_x, screen_y, screen_w, screen_h) - # Set text alignment - alignment = Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignVCenter + # Set text alignment with proper support for multiline text + alignment = Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignTop if element.alignment == 'center': - alignment = Qt.AlignmentFlag.AlignCenter | Qt.AlignmentFlag.AlignVCenter + alignment = Qt.AlignmentFlag.AlignHCenter | Qt.AlignmentFlag.AlignTop elif element.alignment == 'right': - alignment = Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter + alignment = Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignTop - # Draw the text - painter.drawText(rect, alignment, element.text_content) + # Enable word wrapping for multiline text support + text_flags = Qt.TextFlag.TextWordWrap + + # Draw the text with wrapping enabled + painter.drawText(rect, int(alignment | text_flags), element.text_content) # Restore painter state if we rotated if element.rotation != 0: @@ -373,6 +376,40 @@ class GLWidget(QOpenGLWidget): finally: painter.end() + def mouseDoubleClickEvent(self, event): + """Handle mouse double-click events""" + if event.button() == Qt.MouseButton.LeftButton: + x, y = event.position().x(), event.position().y() + element = self._get_element_at(x, y) + + # Check if double-clicked on a TextBoxData element + from pyPhotoAlbum.models import TextBoxData + if isinstance(element, TextBoxData): + self._edit_text_element(element) + return + + # Call parent implementation for default behavior + super().mouseDoubleClickEvent(event) + + def _edit_text_element(self, text_element): + """Open dialog to edit text element""" + from pyPhotoAlbum.text_edit_dialog import TextEditDialog + + dialog = TextEditDialog(text_element, self) + if dialog.exec() == TextEditDialog.DialogCode.Accepted: + # Get new values + values = dialog.get_values() + + # Update text element + text_element.text_content = values['text_content'] + text_element.font_settings = values['font_settings'] + text_element.alignment = values['alignment'] + + # Update view + self.update() + + print(f"Updated text element: {values['text_content'][:50]}...") + def mousePressEvent(self, event): """Handle mouse press events""" if event.button() == Qt.MouseButton.LeftButton: diff --git a/pyPhotoAlbum/pdf_exporter.py b/pyPhotoAlbum/pdf_exporter.py index 7ac1159..081951e 100644 --- a/pyPhotoAlbum/pdf_exporter.py +++ b/pyPhotoAlbum/pdf_exporter.py @@ -9,6 +9,7 @@ from reportlab.pdfgen import canvas from reportlab.lib.utils import ImageReader from PIL import Image import math +from pyPhotoAlbum.models import ImageData, TextBoxData, PlaceholderData class PDFExporter: @@ -172,8 +173,6 @@ class PDFExporter: page_height_pt: Page height in points page_number: Current page number (for error messages) """ - from pyPhotoAlbum.models import ImageData, TextBoxData, PlaceholderData - # Skip placeholders if isinstance(element, PlaceholderData): return @@ -219,8 +218,6 @@ class PDFExporter: page_number: Current page number side: 'left' or 'right' """ - from pyPhotoAlbum.models import ImageData, TextBoxData, PlaceholderData - # Skip placeholders if isinstance(element, PlaceholderData): return diff --git a/pyPhotoAlbum/text_edit_dialog.py b/pyPhotoAlbum/text_edit_dialog.py new file mode 100644 index 0000000..34edfe8 --- /dev/null +++ b/pyPhotoAlbum/text_edit_dialog.py @@ -0,0 +1,155 @@ +""" +Text editing dialog for pyPhotoAlbum +""" + +from PyQt6.QtWidgets import ( + QDialog, QVBoxLayout, QHBoxLayout, QPushButton, + QTextEdit, QLabel, QComboBox, QSpinBox, QColorDialog +) +from PyQt6.QtCore import Qt +from PyQt6.QtGui import QFont, QColor + + +class TextEditDialog(QDialog): + """Dialog for editing text box content and properties""" + + def __init__(self, text_element, parent=None): + super().__init__(parent) + self.text_element = text_element + self.setWindowTitle("Edit Text") + self.resize(500, 400) + + # Create UI + self._init_ui() + + # Load current values + self._load_values() + + def _init_ui(self): + """Initialize the user interface""" + layout = QVBoxLayout() + + # Text editor + text_label = QLabel("Text:") + self.text_edit = QTextEdit() + self.text_edit.setAcceptRichText(False) # Plain text only + layout.addWidget(text_label) + layout.addWidget(self.text_edit) + + # Font settings + font_layout = QHBoxLayout() + + # Font family + font_layout.addWidget(QLabel("Font:")) + self.font_combo = QComboBox() + self.font_combo.addItems([ + "Arial", "Times New Roman", "Courier New", + "Helvetica", "Verdana", "Georgia", "Comic Sans MS" + ]) + font_layout.addWidget(self.font_combo) + + # Font size + font_layout.addWidget(QLabel("Size:")) + self.font_size_spin = QSpinBox() + self.font_size_spin.setRange(6, 72) + self.font_size_spin.setValue(12) + font_layout.addWidget(self.font_size_spin) + + # Text color + self.color_button = QPushButton("Color") + self.color_button.clicked.connect(self._choose_color) + self.current_color = QColor(0, 0, 0) # Default black + font_layout.addWidget(self.color_button) + + font_layout.addStretch() + layout.addLayout(font_layout) + + # Alignment + alignment_layout = QHBoxLayout() + alignment_layout.addWidget(QLabel("Alignment:")) + self.alignment_combo = QComboBox() + self.alignment_combo.addItems(["left", "center", "right"]) + alignment_layout.addWidget(self.alignment_combo) + alignment_layout.addStretch() + layout.addLayout(alignment_layout) + + # Buttons + button_layout = QHBoxLayout() + button_layout.addStretch() + + cancel_button = QPushButton("Cancel") + cancel_button.clicked.connect(self.reject) + button_layout.addWidget(cancel_button) + + ok_button = QPushButton("OK") + ok_button.clicked.connect(self.accept) + ok_button.setDefault(True) + button_layout.addWidget(ok_button) + + layout.addLayout(button_layout) + + self.setLayout(layout) + + def _load_values(self): + """Load current values from text element""" + # Load text content + self.text_edit.setPlainText(self.text_element.text_content) + + # Load font settings + font_family = self.text_element.font_settings.get('family', 'Arial') + index = self.font_combo.findText(font_family) + if index >= 0: + self.font_combo.setCurrentIndex(index) + + font_size = self.text_element.font_settings.get('size', 12) + self.font_size_spin.setValue(int(font_size)) + + # Load color + color = self.text_element.font_settings.get('color', (0, 0, 0)) + if all(isinstance(c, int) and c > 1 for c in color): + # Color in 0-255 range + self.current_color = QColor(*color) + else: + # Color in 0-1 range + self.current_color = QColor( + int(color[0] * 255), + int(color[1] * 255), + int(color[2] * 255) + ) + self._update_color_button() + + # Load alignment + alignment = self.text_element.alignment + index = self.alignment_combo.findText(alignment) + if index >= 0: + self.alignment_combo.setCurrentIndex(index) + + def _choose_color(self): + """Open color picker dialog""" + color = QColorDialog.getColor(self.current_color, self, "Choose Text Color") + if color.isValid(): + self.current_color = color + self._update_color_button() + + def _update_color_button(self): + """Update color button appearance""" + self.color_button.setStyleSheet( + f"background-color: {self.current_color.name()}; " + f"color: {'white' if self.current_color.lightness() < 128 else 'black'};" + ) + + def get_values(self): + """Get the edited values""" + return { + 'text_content': self.text_edit.toPlainText(), + 'font_settings': { + 'family': self.font_combo.currentText(), + 'size': self.font_size_spin.value(), + 'color': ( + self.current_color.red(), + self.current_color.green(), + self.current_color.blue() + ) + }, + 'alignment': self.alignment_combo.currentText() + }