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>
353 lines
12 KiB
Python
353 lines
12 KiB
Python
"""É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]
|