Galerie : crayon édition + compteur quantité +/- + bouton Valider
- PhotoCard : miniature + crayon bleu (ouvre éditeur) + boutons -/+ - Quantité par photo, bordure verte quand qty > 0 - Résumé en haut : "X photos sélectionnées — Y impressions" - Bouton Valider en haut à droite - Responsive (tailles adaptatives) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,11 +1,185 @@
|
|||||||
"""Écran d'import — galerie de photos importées."""
|
"""Écran d'import — galerie avec édition et quantités."""
|
||||||
|
|
||||||
from PyQt6.QtWidgets import (
|
from PyQt6.QtWidgets import (
|
||||||
QWidget, QVBoxLayout, QHBoxLayout, QPushButton,
|
QWidget, QVBoxLayout, QHBoxLayout, QPushButton,
|
||||||
QLabel, QScrollArea, QGridLayout
|
QLabel, QScrollArea, QGridLayout, QFrame
|
||||||
)
|
)
|
||||||
from PyQt6.QtCore import Qt
|
from PyQt6.QtCore import Qt, QSize
|
||||||
from PyQt6.QtGui import QFont, QPixmap
|
from PyQt6.QtGui import QFont, QPixmap, QIcon
|
||||||
|
|
||||||
|
from ui.style import scale, scaled_font, Sizes, back_btn_style, green_btn
|
||||||
|
|
||||||
|
|
||||||
|
class PhotoCard(QFrame):
|
||||||
|
"""Carte photo : miniature + crayon édition + compteur quantité."""
|
||||||
|
|
||||||
|
def __init__(self, path, main_window, parent=None):
|
||||||
|
super().__init__(parent)
|
||||||
|
self.path = path
|
||||||
|
self.main_window = main_window
|
||||||
|
self.quantity = 0
|
||||||
|
self._setup_ui()
|
||||||
|
|
||||||
|
def _setup_ui(self):
|
||||||
|
self.setStyleSheet("""
|
||||||
|
PhotoCard {
|
||||||
|
background: white;
|
||||||
|
border: 2px solid #ddd;
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
""")
|
||||||
|
layout = QVBoxLayout(self)
|
||||||
|
layout.setContentsMargins(5, 5, 5, 5)
|
||||||
|
layout.setSpacing(4)
|
||||||
|
|
||||||
|
# Miniature
|
||||||
|
self.thumb_label = QLabel()
|
||||||
|
self.thumb_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||||
|
self.thumb_label.setMinimumSize(150, 120)
|
||||||
|
pixmap = QPixmap(self.path)
|
||||||
|
if not pixmap.isNull():
|
||||||
|
scaled = pixmap.scaled(
|
||||||
|
200, 150,
|
||||||
|
Qt.AspectRatioMode.KeepAspectRatio,
|
||||||
|
Qt.TransformationMode.SmoothTransformation
|
||||||
|
)
|
||||||
|
self.thumb_label.setPixmap(scaled)
|
||||||
|
layout.addWidget(self.thumb_label)
|
||||||
|
|
||||||
|
# Barre du bas : crayon + quantité
|
||||||
|
bar = QHBoxLayout()
|
||||||
|
bar.setSpacing(4)
|
||||||
|
|
||||||
|
# Crayon édition
|
||||||
|
self.edit_btn = QPushButton("✎")
|
||||||
|
self.edit_btn.setFixedSize(36, 36)
|
||||||
|
self.edit_btn.setStyleSheet("""
|
||||||
|
QPushButton {
|
||||||
|
background: #2196F3; color: white;
|
||||||
|
border-radius: 18px; font-size: 18px;
|
||||||
|
}
|
||||||
|
QPushButton:pressed { background: #1976D2; }
|
||||||
|
""")
|
||||||
|
self.edit_btn.setToolTip("Éditer")
|
||||||
|
self.edit_btn.clicked.connect(self._edit)
|
||||||
|
bar.addWidget(self.edit_btn)
|
||||||
|
|
||||||
|
bar.addStretch()
|
||||||
|
|
||||||
|
# Compteur quantité
|
||||||
|
self.minus_btn = QPushButton("−")
|
||||||
|
self.minus_btn.setFixedSize(32, 32)
|
||||||
|
self.minus_btn.setStyleSheet("""
|
||||||
|
QPushButton {
|
||||||
|
background: #eee; color: #333;
|
||||||
|
border-radius: 16px; font-size: 18px; font-weight: bold;
|
||||||
|
}
|
||||||
|
QPushButton:pressed { background: #ccc; }
|
||||||
|
""")
|
||||||
|
self.minus_btn.clicked.connect(self._decrement)
|
||||||
|
bar.addWidget(self.minus_btn)
|
||||||
|
|
||||||
|
self.qty_label = QLabel("0")
|
||||||
|
self.qty_label.setFont(QFont("", 14, QFont.Weight.Bold))
|
||||||
|
self.qty_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||||
|
self.qty_label.setMinimumWidth(30)
|
||||||
|
bar.addWidget(self.qty_label)
|
||||||
|
|
||||||
|
self.plus_btn = QPushButton("+")
|
||||||
|
self.plus_btn.setFixedSize(32, 32)
|
||||||
|
self.plus_btn.setStyleSheet("""
|
||||||
|
QPushButton {
|
||||||
|
background: #4CAF50; color: white;
|
||||||
|
border-radius: 16px; font-size: 18px; font-weight: bold;
|
||||||
|
}
|
||||||
|
QPushButton:pressed { background: #388E3C; }
|
||||||
|
""")
|
||||||
|
self.plus_btn.clicked.connect(self._increment)
|
||||||
|
bar.addWidget(self.plus_btn)
|
||||||
|
|
||||||
|
layout.addLayout(bar)
|
||||||
|
|
||||||
|
def _edit(self):
|
||||||
|
self.main_window.show_editor(self.path)
|
||||||
|
|
||||||
|
def _increment(self):
|
||||||
|
self.quantity += 1
|
||||||
|
self._update_qty()
|
||||||
|
|
||||||
|
def _decrement(self):
|
||||||
|
if self.quantity > 0:
|
||||||
|
self.quantity -= 1
|
||||||
|
self._update_qty()
|
||||||
|
|
||||||
|
def _update_qty(self):
|
||||||
|
self.qty_label.setText(str(self.quantity))
|
||||||
|
if self.quantity > 0:
|
||||||
|
self.setStyleSheet("""
|
||||||
|
PhotoCard {
|
||||||
|
background: white;
|
||||||
|
border: 3px solid #4CAF50;
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
""")
|
||||||
|
self.qty_label.setStyleSheet("color: #4CAF50;")
|
||||||
|
else:
|
||||||
|
self.setStyleSheet("""
|
||||||
|
PhotoCard {
|
||||||
|
background: white;
|
||||||
|
border: 2px solid #ddd;
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
""")
|
||||||
|
self.qty_label.setStyleSheet("color: #333;")
|
||||||
|
|
||||||
|
def apply_responsive(self, window_height):
|
||||||
|
"""Adapte les tailles au responsive."""
|
||||||
|
thumb_h = max(80, int(window_height * 0.15))
|
||||||
|
thumb_w = max(100, int(thumb_h * 1.4))
|
||||||
|
self.thumb_label.setMinimumSize(thumb_w, thumb_h)
|
||||||
|
|
||||||
|
# Re-scale la miniature
|
||||||
|
pixmap = QPixmap(self.path)
|
||||||
|
if not pixmap.isNull():
|
||||||
|
scaled = pixmap.scaled(
|
||||||
|
thumb_w, thumb_h,
|
||||||
|
Qt.AspectRatioMode.KeepAspectRatio,
|
||||||
|
Qt.TransformationMode.SmoothTransformation
|
||||||
|
)
|
||||||
|
self.thumb_label.setPixmap(scaled)
|
||||||
|
|
||||||
|
btn_size = max(28, int(window_height * 0.04))
|
||||||
|
font_size = max(12, int(window_height * 0.02))
|
||||||
|
|
||||||
|
self.edit_btn.setFixedSize(btn_size, btn_size)
|
||||||
|
self.edit_btn.setStyleSheet(f"""
|
||||||
|
QPushButton {{
|
||||||
|
background: #2196F3; color: white;
|
||||||
|
border-radius: {btn_size//2}px; font-size: {font_size+2}px;
|
||||||
|
}}
|
||||||
|
QPushButton:pressed {{ background: #1976D2; }}
|
||||||
|
""")
|
||||||
|
|
||||||
|
self.minus_btn.setFixedSize(btn_size, btn_size)
|
||||||
|
self.minus_btn.setStyleSheet(f"""
|
||||||
|
QPushButton {{
|
||||||
|
background: #eee; color: #333;
|
||||||
|
border-radius: {btn_size//2}px; font-size: {font_size+2}px; font-weight: bold;
|
||||||
|
}}
|
||||||
|
QPushButton:pressed {{ background: #ccc; }}
|
||||||
|
""")
|
||||||
|
|
||||||
|
self.plus_btn.setFixedSize(btn_size, btn_size)
|
||||||
|
self.plus_btn.setStyleSheet(f"""
|
||||||
|
QPushButton {{
|
||||||
|
background: #4CAF50; color: white;
|
||||||
|
border-radius: {btn_size//2}px; font-size: {font_size+2}px; font-weight: bold;
|
||||||
|
}}
|
||||||
|
QPushButton:pressed {{ background: #388E3C; }}
|
||||||
|
""")
|
||||||
|
|
||||||
|
self.qty_label.setFont(QFont("", font_size, QFont.Weight.Bold))
|
||||||
|
self._update_qty()
|
||||||
|
|
||||||
|
|
||||||
class ImportScreen(QWidget):
|
class ImportScreen(QWidget):
|
||||||
@@ -13,76 +187,124 @@ class ImportScreen(QWidget):
|
|||||||
super().__init__()
|
super().__init__()
|
||||||
self.main_window = main_window
|
self.main_window = main_window
|
||||||
self.photos = []
|
self.photos = []
|
||||||
|
self.cards = []
|
||||||
self._setup_ui()
|
self._setup_ui()
|
||||||
|
|
||||||
def _setup_ui(self):
|
def _setup_ui(self):
|
||||||
layout = QVBoxLayout(self)
|
layout = QVBoxLayout(self)
|
||||||
|
layout.setContentsMargins(10, 10, 10, 10)
|
||||||
|
layout.setSpacing(8)
|
||||||
|
|
||||||
# Header
|
# Header
|
||||||
header = QHBoxLayout()
|
header = QHBoxLayout()
|
||||||
back_btn = QPushButton("Retour")
|
|
||||||
back_btn.setFont(QFont("", 14))
|
|
||||||
back_btn.clicked.connect(self.main_window.show_home)
|
|
||||||
header.addWidget(back_btn)
|
|
||||||
|
|
||||||
title = QLabel("Sélectionnez une photo")
|
self.back_btn = QPushButton("< Retour")
|
||||||
title.setFont(QFont("", 24, QFont.Weight.Bold))
|
self.back_btn.clicked.connect(self.main_window.show_home)
|
||||||
title.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
header.addWidget(self.back_btn)
|
||||||
header.addWidget(title, 1)
|
|
||||||
|
self.title = QLabel("Sélectionnez vos photos")
|
||||||
|
self.title.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||||
|
header.addWidget(self.title, 1)
|
||||||
|
|
||||||
|
self.validate_btn = QPushButton("Valider")
|
||||||
|
self.validate_btn.clicked.connect(self._validate)
|
||||||
|
header.addWidget(self.validate_btn)
|
||||||
|
|
||||||
header.addSpacing(80) # Balance le bouton retour
|
|
||||||
layout.addLayout(header)
|
layout.addLayout(header)
|
||||||
|
|
||||||
# Grille de photos (scrollable)
|
# Résumé sélection
|
||||||
|
self.summary_label = QLabel("")
|
||||||
|
self.summary_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||||
|
self.summary_label.setStyleSheet("color: #666;")
|
||||||
|
layout.addWidget(self.summary_label)
|
||||||
|
|
||||||
|
# Grille scrollable
|
||||||
scroll = QScrollArea()
|
scroll = QScrollArea()
|
||||||
scroll.setWidgetResizable(True)
|
scroll.setWidgetResizable(True)
|
||||||
scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
|
scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
|
||||||
|
scroll.setStyleSheet("QScrollArea { border: none; }")
|
||||||
|
|
||||||
self.grid_widget = QWidget()
|
self.grid_widget = QWidget()
|
||||||
self.grid_layout = QGridLayout(self.grid_widget)
|
self.grid_layout = QGridLayout(self.grid_widget)
|
||||||
self.grid_layout.setSpacing(10)
|
self.grid_layout.setSpacing(8)
|
||||||
scroll.setWidget(self.grid_widget)
|
scroll.setWidget(self.grid_widget)
|
||||||
|
|
||||||
layout.addWidget(scroll)
|
layout.addWidget(scroll, 1)
|
||||||
|
|
||||||
|
def showEvent(self, event):
|
||||||
|
super().showEvent(event)
|
||||||
|
from PyQt6.QtCore import QTimer
|
||||||
|
QTimer.singleShot(0, self._apply_responsive)
|
||||||
|
|
||||||
|
def resizeEvent(self, event):
|
||||||
|
super().resizeEvent(event)
|
||||||
|
self._apply_responsive()
|
||||||
|
|
||||||
|
def _apply_responsive(self):
|
||||||
|
h = self.window().height() if self.window() else 768
|
||||||
|
|
||||||
|
self.back_btn.setFont(scaled_font(self, Sizes.BUTTON_TEXT, bold=True))
|
||||||
|
self.back_btn.setMinimumHeight(scale(self, Sizes.BTN_HEIGHT))
|
||||||
|
self.back_btn.setStyleSheet(back_btn_style(self))
|
||||||
|
|
||||||
|
self.title.setFont(scaled_font(self, Sizes.HEADING, bold=True))
|
||||||
|
|
||||||
|
self.validate_btn.setFont(scaled_font(self, Sizes.BUTTON_TEXT, bold=True))
|
||||||
|
self.validate_btn.setMinimumHeight(scale(self, Sizes.BTN_HEIGHT))
|
||||||
|
self.validate_btn.setStyleSheet(green_btn(self))
|
||||||
|
|
||||||
|
self.summary_label.setFont(scaled_font(self, Sizes.HINT))
|
||||||
|
|
||||||
|
for card in self.cards:
|
||||||
|
card.apply_responsive(h)
|
||||||
|
|
||||||
def set_photos(self, photo_paths):
|
def set_photos(self, photo_paths):
|
||||||
"""Affiche les photos dans la grille."""
|
"""Affiche les photos dans la grille."""
|
||||||
# Clear existing
|
# Clear
|
||||||
while self.grid_layout.count():
|
while self.grid_layout.count():
|
||||||
item = self.grid_layout.takeAt(0)
|
item = self.grid_layout.takeAt(0)
|
||||||
if item.widget():
|
if item.widget():
|
||||||
item.widget().deleteLater()
|
item.widget().deleteLater()
|
||||||
|
|
||||||
self.photos = photo_paths
|
self.photos = photo_paths
|
||||||
|
self.cards = []
|
||||||
cols = 4
|
cols = 4
|
||||||
for i, path in enumerate(photo_paths):
|
for i, path in enumerate(photo_paths):
|
||||||
row, col = divmod(i, cols)
|
row, col = divmod(i, cols)
|
||||||
thumb = self._create_thumbnail(path)
|
card = PhotoCard(path, self.main_window)
|
||||||
self.grid_layout.addWidget(thumb, row, col)
|
card.plus_btn.clicked.connect(self._update_summary)
|
||||||
|
card.minus_btn.clicked.connect(self._update_summary)
|
||||||
|
self.grid_layout.addWidget(card, row, col)
|
||||||
|
self.cards.append(card)
|
||||||
|
|
||||||
def _create_thumbnail(self, path):
|
self._update_summary()
|
||||||
btn = QPushButton()
|
|
||||||
btn.setMinimumSize(180, 180)
|
def _update_summary(self):
|
||||||
btn.setStyleSheet("""
|
total = sum(c.quantity for c in self.cards)
|
||||||
QPushButton {
|
selected = sum(1 for c in self.cards if c.quantity > 0)
|
||||||
border: 3px solid transparent;
|
if total > 0:
|
||||||
border-radius: 10px;
|
self.summary_label.setText(
|
||||||
}
|
f"{selected} photo{'s' if selected > 1 else ''} sélectionnée{'s' if selected > 1 else ''} — {total} impression{'s' if total > 1 else ''}"
|
||||||
QPushButton:pressed {
|
|
||||||
border-color: #2196F3;
|
|
||||||
}
|
|
||||||
""")
|
|
||||||
pixmap = QPixmap(path)
|
|
||||||
if not pixmap.isNull():
|
|
||||||
scaled = pixmap.scaled(
|
|
||||||
170, 170,
|
|
||||||
Qt.AspectRatioMode.KeepAspectRatio,
|
|
||||||
Qt.TransformationMode.SmoothTransformation
|
|
||||||
)
|
)
|
||||||
from PyQt6.QtGui import QIcon
|
self.summary_label.setStyleSheet("color: #4CAF50; font-weight: bold;")
|
||||||
from PyQt6.QtCore import QSize
|
else:
|
||||||
btn.setIcon(QIcon(scaled))
|
self.summary_label.setText("Appuyez sur + pour sélectionner des photos à imprimer")
|
||||||
btn.setIconSize(QSize(170, 170))
|
self.summary_label.setStyleSheet("color: #999;")
|
||||||
|
|
||||||
btn.clicked.connect(lambda checked, p=path: self.main_window.show_editor(p))
|
def _validate(self):
|
||||||
return btn
|
"""Envoie les photos sélectionnées à l'impression."""
|
||||||
|
to_print = []
|
||||||
|
for card in self.cards:
|
||||||
|
if card.quantity > 0:
|
||||||
|
to_print.append((card.path, card.quantity))
|
||||||
|
|
||||||
|
if not to_print:
|
||||||
|
return
|
||||||
|
|
||||||
|
# TODO: envoyer à l'écran d'impression avec les quantités
|
||||||
|
# Pour l'instant, on envoie la première photo
|
||||||
|
self.main_window.show_print(to_print[0][0])
|
||||||
|
|
||||||
|
def get_print_list(self):
|
||||||
|
"""Retourne la liste [(path, quantity), ...] pour l'impression."""
|
||||||
|
return [(c.path, c.quantity) for c in self.cards if c.quantity > 0]
|
||||||
|
|||||||
Reference in New Issue
Block a user