Files
photostation/src/ui/screens/import_screen.py
Jules c2c27b1892 Tarification + écran récap avec prix
Config admin :
- Tarification activable avec paliers (ex: 1-5 = 0.50€, 6-10 = 0.40€)
- Fonction calculate_price() utilitaire

Galerie :
- Affiche "X impressions — Y.YY €" quand tarification activée

Écran impression/récap :
- Galerie des photos sélectionnées avec miniatures + quantité (xN)
- Prix total en haut
- Choix du format d'impression
- Bouton IMPRIMER

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 23:54:10 +01:00

353 lines
12 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Écran d'import — galerie avec quantités et recadrage."""
from PyQt6.QtWidgets import (
QWidget, QVBoxLayout, QHBoxLayout, QPushButton,
QLabel, QScrollArea, QGridLayout, QFrame
)
from PyQt6.QtCore import Qt, QSize
from PyQt6.QtGui import QFont, QPixmap, QIcon
from ui.style import scale, scaled_font, Sizes, back_btn_style, green_btn
from ui.widgets.config_dialog import load_config, calculate_price
class PhotoCard(QFrame):
"""Carte photo : clic sur photo = éditeur, +/- centré, pastille orange si ratio ≠ 3:2."""
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(4, 4, 4, 4)
layout.setSpacing(4)
# Miniature cliquable
self.thumb_btn = QPushButton()
self.thumb_btn.setFixedSize(200, 140)
self.thumb_btn.setCursor(Qt.CursorShape.PointingHandCursor)
self.thumb_btn.setStyleSheet("""
QPushButton {
background: #f8f8f8;
border: none;
border-radius: 6px;
}
QPushButton:pressed { background: #eee; }
""")
self.thumb_btn.clicked.connect(self._edit)
# Charger une version réduite (pas l'originale en pleine résolution)
full = QPixmap(self.path)
if not full.isNull() and full.width() > 400:
self._pixmap_original = full.scaledToWidth(400, Qt.TransformationMode.SmoothTransformation)
else:
self._pixmap_original = full
self._update_thumbnail()
layout.addWidget(self.thumb_btn)
# Pastille orange (ratio ≠ 3:2)
self.ratio_dot = QLabel()
self.ratio_dot.setFixedSize(14, 14)
self.ratio_dot.setStyleSheet("""
QLabel {
background: rgba(255, 152, 0, 0.85);
border-radius: 7px;
}
""")
self.ratio_dot.hide()
self.ratio_dot.setParent(self.thumb_btn)
self.ratio_dot.move(182, 4)
self._check_ratio()
# Barre du bas : − quantité + (centré)
bar = QHBoxLayout()
bar.setSpacing(6)
bar.addStretch()
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)
bar.addStretch()
layout.addLayout(bar)
def _check_ratio(self):
"""Affiche la pastille orange si ratio ≠ 3:2."""
if self._pixmap_original.isNull():
return
w = self._pixmap_original.width()
h = self._pixmap_original.height()
if w == 0 or h == 0:
return
ratio = max(w, h) / min(w, h)
if abs(ratio - 1.5) > 0.075:
self.ratio_dot.show()
else:
self.ratio_dot.hide()
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 _update_thumbnail(self):
"""Redimensionne la miniature et la centre sur le bouton."""
if self._pixmap_original.isNull():
return
w = self.thumb_btn.width()
h = self.thumb_btn.height()
if w > 10 and h > 10:
scaled = self._pixmap_original.scaled(
w - 4, h - 4,
Qt.AspectRatioMode.KeepAspectRatio,
Qt.TransformationMode.SmoothTransformation
)
self.thumb_btn.setIcon(QIcon(scaled))
self.thumb_btn.setIconSize(QSize(w - 4, h - 4))
def apply_responsive(self, window_height):
"""Adapte les tailles au responsive."""
thumb_h = max(100, int(window_height * 0.16))
thumb_w = max(140, int(thumb_h * 1.45))
self.thumb_btn.setFixedSize(thumb_w, thumb_h)
self._update_thumbnail()
# Pastille orange
dot_size = max(10, int(window_height * 0.018))
self.ratio_dot.setFixedSize(dot_size, dot_size)
self.ratio_dot.setParent(self.thumb_btn)
self.ratio_dot.move(thumb_w - dot_size - 4, 4)
self.ratio_dot.setStyleSheet(f"""
QLabel {{
background: rgba(255, 152, 0, 0.85);
border-radius: {dot_size // 2}px;
}}
""")
self.ratio_dot.raise_()
btn_size = max(28, int(window_height * 0.04))
font_size = max(12, int(window_height * 0.02))
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):
def __init__(self, main_window):
super().__init__()
self.main_window = main_window
self.photos = []
self.cards = []
self._setup_ui()
def _setup_ui(self):
layout = QVBoxLayout(self)
layout.setContentsMargins(10, 10, 10, 10)
layout.setSpacing(8)
# Header
header = QHBoxLayout()
self.back_btn = QPushButton("< Retour")
self.back_btn.clicked.connect(self.main_window.show_home)
header.addWidget(self.back_btn)
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)
layout.addLayout(header)
# 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.setWidgetResizable(True)
scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
scroll.setStyleSheet("QScrollArea { border: none; }")
self.grid_widget = QWidget()
self.grid_layout = QGridLayout(self.grid_widget)
self.grid_layout.setSpacing(8)
scroll.setWidget(self.grid_widget)
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):
"""Affiche les photos dans la grille."""
while self.grid_layout.count():
item = self.grid_layout.takeAt(0)
if item.widget():
item.widget().deleteLater()
self.photos = photo_paths
self.cards = []
cols = 4
for i, path in enumerate(photo_paths):
row, col = divmod(i, cols)
card = PhotoCard(path, self.main_window)
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)
self._update_summary()
def _update_summary(self):
total = sum(c.quantity for c in self.cards)
selected = sum(1 for c in self.cards if c.quantity > 0)
if total > 0:
config = load_config()
pricing = config.get("pricing", {})
if pricing.get("enabled", False):
price = calculate_price(total, config)
self.summary_label.setText(
f"{total} impression{'s' if total > 1 else ''} — {price:.2f} €"
)
else:
self.summary_label.setText(
f"{selected} photo{'s' if selected > 1 else ''} — {total} impression{'s' if total > 1 else ''}"
)
self.summary_label.setStyleSheet("color: #4CAF50; font-weight: bold;")
else:
self.summary_label.setText("Appuyez sur + pour sélectionner des photos à imprimer")
self.summary_label.setStyleSheet("color: #999;")
def _validate(self):
"""Envoie les photos sélectionnées à l'écran d'impression."""
to_print = []
for card in self.cards:
if card.quantity > 0:
to_print.append((card.path, card.quantity))
if not to_print:
return
self.main_window.print_screen.set_print_list(to_print)
self.main_window.show_print()
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]