import json from datetime import date from qgis.PyQt.QtWidgets import ( QDialog, QVBoxLayout, QHBoxLayout, QFormLayout, QLabel, QLineEdit, QPushButton, QMessageBox, QComboBox, QDateEdit, QSpinBox, QCheckBox, QScrollArea, QWidget, QListWidget, QListWidgetItem, QInputDialog, QSizePolicy, QFrame, QGroupBox ) from qgis.PyQt.QtCore import Qt, QDate from qgis.PyQt.QtGui import QFont, QColor from qgis.core import QgsCoordinateReferenceSystem, QgsCoordinateTransform, QgsProject from qgis.utils import iface from .map_point_tool import PointCaptureTool # --------------------------------------------------------------------------- # Widgets utilitaires # --------------------------------------------------------------------------- class ComboWithAdd(QWidget): def __init__(self, add_callback, parent=None): super().__init__(parent) self._add_callback = add_callback layout = QHBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) self.combo = QComboBox() self.combo.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) self.btn_add = QPushButton("+") self.btn_add.setFixedWidth(30) self.btn_add.setToolTip("Ajouter un nouvel élément") self.btn_add.clicked.connect(lambda: self._add_callback(self)) layout.addWidget(self.combo) layout.addWidget(self.btn_add) def current_data(self): return self.combo.currentData() def current_text(self): return self.combo.currentText() def populate(self, items, placeholder=None): self.combo.clear() if placeholder: self.combo.addItem(placeholder, None) for code, label in items: self.combo.addItem(label, code) def add_item(self, code, label): self.combo.addItem(label, code) self.combo.setCurrentIndex(self.combo.count() - 1) def set_by_text(self, text): idx = self.combo.findText(text) if idx >= 0: self.combo.setCurrentIndex(idx) def set_by_data(self, data): idx = self.combo.findData(data) if idx >= 0: self.combo.setCurrentIndex(idx) class AnimateursWidget(QWidget): def __init__(self, db, parent=None): super().__init__(parent) self.db = db layout = QVBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) self.list_widget = QListWidget() self.list_widget.setMaximumHeight(90) layout.addWidget(self.list_widget) btn_layout = QHBoxLayout() self.combo = QComboBox() self.combo.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) btn_add = QPushButton("Ajouter") btn_add.clicked.connect(self._add_existing) btn_new = QPushButton("+ Nouveau") btn_new.clicked.connect(self._add_new) btn_remove = QPushButton("Retirer") btn_remove.clicked.connect(self._remove_selected) btn_layout.addWidget(self.combo) btn_layout.addWidget(btn_add) btn_layout.addWidget(btn_new) btn_layout.addWidget(btn_remove) layout.addLayout(btn_layout) self.load_animateurs() def load_animateurs(self): self.combo.clear() for code, nom in self.db.get_animateurs(): self.combo.addItem(nom, code) def _add_existing(self): code = self.combo.currentData() nom = self.combo.currentText() if code is None: return for i in range(self.list_widget.count()): if self.list_widget.item(i).data(Qt.UserRole) == code: return item = QListWidgetItem(nom) item.setData(Qt.UserRole, code) self.list_widget.addItem(item) def _add_new(self): nom, ok = QInputDialog.getText(self, "Nouvel animateur", "Nom :") if ok and nom.strip(): try: code, label = self.db.add_animateur(nom.strip()) self.combo.addItem(label, code) item = QListWidgetItem(label) item.setData(Qt.UserRole, code) self.list_widget.addItem(item) except Exception as e: QMessageBox.critical(self, "Erreur", str(e)) def _remove_selected(self): for item in self.list_widget.selectedItems(): self.list_widget.takeItem(self.list_widget.row(item)) def get_value(self): result = [] for i in range(self.list_widget.count()): item = self.list_widget.item(i) result.append({"code": item.data(Qt.UserRole), "nom": item.text()}) return json.dumps(result, ensure_ascii=False) if result else None def set_value(self, json_str): self.list_widget.clear() if not json_str: return try: data = json.loads(json_str) if isinstance(json_str, str) else json_str for entry in data: item = QListWidgetItem(entry.get("nom", "")) item.setData(Qt.UserRole, entry.get("code")) self.list_widget.addItem(item) except Exception: pass # --------------------------------------------------------------------------- # Dialogue principal # --------------------------------------------------------------------------- class SaisieDialog(QDialog): def __init__(self, db, parent=None): super().__init__(parent) self.db = db self._etab_map = {} # code -> (nom, commune) self._edit_mode = False # True = on édite une animation existante self._edit_code = None # code_animation en cours d'édition self._pending_geom = None # (x, y, crs) en attente pour ajout lieu/etab self._pending_geom_type = None # 'lieu' ou 'etab' self._point_tool = None self.setWindowTitle("Animation – Saisie / Édition") self.setMinimumWidth(720) self.setMinimumHeight(820) self._build_ui() self._load_reference_data() # ----------------------------------------------------------------------- # Construction UI # ----------------------------------------------------------------------- def _build_ui(self): main_layout = QVBoxLayout(self) main_layout.setSpacing(6) # --- Bandeau titre --- self.title_label = QLabel("

Nouvelle animation

") self.title_label.setAlignment(Qt.AlignCenter) main_layout.addWidget(self.title_label) # --- Sélecteur d'animation existante --- grp_sel = QGroupBox("Charger / modifier une animation existante") sel_layout = QHBoxLayout(grp_sel) self.anim_combo = QComboBox() self.anim_combo.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) self.anim_combo.setPlaceholderText("── Nouvelle saisie ──") self.anim_combo.addItem("── Nouvelle saisie ──", None) self.anim_combo.currentIndexChanged.connect(self._on_anim_selected) sel_layout.addWidget(QLabel("Animation :")) sel_layout.addWidget(self.anim_combo) btn_refresh = QPushButton("🔄") btn_refresh.setFixedWidth(32) btn_refresh.setToolTip("Rafraîchir la liste") btn_refresh.clicked.connect(self._reload_animations_list) sel_layout.addWidget(btn_refresh) main_layout.addWidget(grp_sel) # --- Zone scrollable du formulaire --- scroll = QScrollArea() scroll.setWidgetResizable(True) container = QWidget() self.fl = QFormLayout(container) self.fl.setLabelAlignment(Qt.AlignRight) self.fl.setSpacing(8) scroll.setWidget(container) main_layout.addWidget(scroll) fl = self.fl # ---- Date & Durée ---- self._section(fl, "Date et durée") self.date_edit = QDateEdit(QDate.currentDate()) self.date_edit.setCalendarPopup(True) self.date_edit.setDisplayFormat("dd/MM/yyyy") fl.addRow("Date *:", self.date_edit) dur_w = QWidget() dur_l = QHBoxLayout(dur_w) dur_l.setContentsMargins(0, 0, 0, 0) self.duree_h = QSpinBox() self.duree_h.setRange(0, 24) self.duree_h.setSuffix(" h") self.duree_min = QSpinBox() self.duree_min.setRange(0, 59) self.duree_min.setSuffix(" min") dur_l.addWidget(self.duree_h) dur_l.addWidget(self.duree_min) dur_l.addStretch() fl.addRow("Durée :", dur_w) self.prepa_spin = QSpinBox() self.prepa_spin.setRange(0, 9999) self.prepa_spin.setSuffix(" min") for w in (self.duree_h, self.duree_min, self.prepa_spin): w.valueChanged.connect(self._update_temps_total) fl.addRow("Prépa / déplacement :", self.prepa_spin) self.temps_total_label = QLabel("0 min") fl.addRow("Temps total (calculé) :", self.temps_total_label) # ---- Animateurs ---- self._sep(fl) self._section(fl, "Animateurs") self.animateurs_widget = AnimateursWidget(self.db) fl.addRow("Animateurs :", self.animateurs_widget) # ---- Groupe ---- self._sep(fl) self._section(fl, "Groupe") self.type_groupe_combo = ComboWithAdd(self._add_type_groupe) fl.addRow("Type de groupe :", self.type_groupe_combo) self.precisions_groupe_edit = QLineEdit() fl.addRow("Précisions groupe :", self.precisions_groupe_edit) # ---- Établissement ---- self._sep(fl) self._section(fl, "Établissement / Provenance") self.etab_combo = ComboWithAdd(self._add_etablissement) self.etab_combo.combo.currentIndexChanged.connect(self._on_etab_changed) fl.addRow("Établissement :", self.etab_combo) self.commune_label = QLabel("") self.commune_label.setStyleSheet("color:#555; font-style:italic;") fl.addRow("Commune (auto) :", self.commune_label) # ---- Participants ---- self._sep(fl) self._section(fl, "Participants") self.enfants_spin = QSpinBox() self.enfants_spin.setRange(0, 9999) self.enfants_spin.valueChanged.connect(self._update_total_pers) fl.addRow("Enfants :", self.enfants_spin) self.adultes_spin = QSpinBox() self.adultes_spin.setRange(0, 9999) self.adultes_spin.valueChanged.connect(self._update_total_pers) fl.addRow("Adultes :", self.adultes_spin) self.total_pers_label = QLabel("0") fl.addRow("Total (calculé) :", self.total_pers_label) # ---- Lieu ---- self._sep(fl) self._section(fl, "Lieu") self.lieux_combo = ComboWithAdd(self._add_lieu) fl.addRow("Lieu :", self.lieux_combo) self.precisions_lieux_edit = QLineEdit() fl.addRow("Précisions lieu :", self.precisions_lieux_edit) self.accueil_pin_check = QCheckBox("Oui") fl.addRow("Accueil PIN :", self.accueil_pin_check) # ---- Thème ---- self._sep(fl) self._section(fl, "Thème") self.theme_combo = ComboWithAdd(self._add_theme) fl.addRow("Thème :", self.theme_combo) self.theme_detaille_edit = QLineEdit() fl.addRow("Thème détaillé :", self.theme_detaille_edit) # ---- Statuts ---- self._sep(fl) self._section(fl, "Statuts") self.payant_check = QCheckBox("Oui") fl.addRow("Payant :", self.payant_check) self.annul_combo = ComboWithAdd(self._add_annulation) fl.addRow("Annulation :", self.annul_combo) self.dossier_combo = ComboWithAdd(self._add_dossier) fl.addRow("Dossier enseignant :", self.dossier_combo) self.tiers_check = QCheckBox("Oui") self.tiers_check.stateChanged.connect(self._on_tiers_changed) fl.addRow("Réalisé par tiers :", self.tiers_check) self.detail_tiers_edit = QLineEdit() self.detail_tiers_edit.setEnabled(False) fl.addRow("Détail tiers :", self.detail_tiers_edit) # ---- Remarques ---- self._sep(fl) self.remarques_edit = QLineEdit() fl.addRow("Remarques :", self.remarques_edit) # --- Boutons d'action --- btn_layout = QHBoxLayout() btn_cancel = QPushButton("Fermer") btn_cancel.clicked.connect(self.reject) self.btn_new_form = QPushButton("✚ Nouveau") self.btn_new_form.setToolTip("Réinitialiser pour une nouvelle saisie") self.btn_new_form.clicked.connect(self._new_form) self.btn_save_new = QPushButton("Enregistrer et nouveau") self.btn_save_new.clicked.connect(self._save_and_new) self.btn_save = QPushButton("Enregistrer") self.btn_save.setDefault(True) self.btn_save.clicked.connect(self._save) btn_layout.addWidget(btn_cancel) btn_layout.addWidget(self.btn_new_form) btn_layout.addStretch() btn_layout.addWidget(self.btn_save_new) btn_layout.addWidget(self.btn_save) main_layout.addLayout(btn_layout) def _section(self, fl, title): lbl = QLabel(f"{title}") fl.addRow(lbl) def _sep(self, fl): line = QFrame() line.setFrameShape(QFrame.HLine) line.setStyleSheet("color:#ccc;") fl.addRow(line) # ----------------------------------------------------------------------- # Chargement données de référence # ----------------------------------------------------------------------- def _load_reference_data(self): try: self.type_groupe_combo.populate(self.db.get_types_groupe(), "-- Sélectionner --") etabs = self.db.get_etablissements() self._etab_map = {c: (n, com) for c, n, com in etabs} self.etab_combo.populate( [(c, f"{n} ({com})" if com else n) for c, n, com in etabs], "-- Sélectionner --" ) self.lieux_combo.populate(self.db.get_lieux(), "-- Sélectionner --") self.theme_combo.populate(self.db.get_themes(), "-- Sélectionner --") self.annul_combo.populate(self.db.get_codes_annulation(), "Non") self._set_default(self.annul_combo, "Non") self.dossier_combo.populate(self.db.get_type_dossier_ens(), "Non") self._set_default(self.dossier_combo, "Non") self._reload_animations_list() except Exception as e: QMessageBox.critical(self, "Erreur chargement", str(e)) def _set_default(self, combo_widget, text): idx = combo_widget.combo.findText(text) if idx >= 0: combo_widget.combo.setCurrentIndex(idx) def _reload_animations_list(self): self.anim_combo.blockSignals(True) self.anim_combo.clear() self.anim_combo.addItem("── Nouvelle saisie ──", None) try: rows = self.db.get_all_animations() for code, date_anim, lieux, theme in rows: date_str = date_anim.strftime("%d/%m/%Y") if date_anim else "?" lieux_str = lieux or "Lieu ?" theme_str = theme or "Thème ?" label = f"{date_str} | {lieux_str} | {theme_str} [#{code}]" self.anim_combo.addItem(label, code) except Exception as e: QMessageBox.warning(self, "Erreur", f"Impossible de charger les animations : {e}") self.anim_combo.blockSignals(False) # ----------------------------------------------------------------------- # Événements # ----------------------------------------------------------------------- def _on_anim_selected(self, idx): code = self.anim_combo.itemData(idx) if code is None: self._new_form() return try: data = self.db.get_animation_by_id(code) if data: self._edit_mode = True self._edit_code = code self.title_label.setText(f"

Édition animation #{code}

") self.btn_save.setText("Mettre à jour") self.btn_save_new.setVisible(False) self._populate_form(data) except Exception as e: QMessageBox.critical(self, "Erreur", str(e)) def _populate_form(self, data): """Remplit le formulaire avec les données d'une animation existante.""" if data.get('date_anim'): d = data['date_anim'] self.date_edit.setDate(QDate(d.year, d.month, d.day)) duree = data.get('duree_anim', '0h00') or '0h00' try: if 'h' in duree: h, m = duree.split('h') self.duree_h.setValue(int(h)) self.duree_min.setValue(int(m) if m else 0) except Exception: pass self.prepa_spin.setValue(data.get('prepa_deplacemt') or 0) self.animateurs_widget.set_value(data.get('animateurs')) self.type_groupe_combo.set_by_data(data.get('code_groupe')) self.precisions_groupe_edit.setText(data.get('precisions_type_de_groupe') or '') # Établissement : chercher par nom nom_etab = data.get('nom_etablissement') or '' for code, (nom, com) in self._etab_map.items(): if nom == nom_etab: self.etab_combo.set_by_data(code) break self.enfants_spin.setValue(data.get('nbre_pers_enfants') or 0) self.adultes_spin.setValue(data.get('nbre_pers_adultes') or 0) self.lieux_combo.set_by_text(data.get('lieux') or '') self.precisions_lieux_edit.setText(data.get('precisions_lieux_anim') or '') self.accueil_pin_check.setChecked(bool(data.get('accueil_pin'))) self.theme_detaille_edit.setText(data.get('theme_detaille') or '') self.theme_combo.set_by_data(data.get('code_type_anim')) self.payant_check.setChecked(bool(data.get('payant'))) self.annul_combo.set_by_text(data.get('type_annul') or 'Non') self.remarques_edit.setText(data.get('remarques') or '') self.dossier_combo.set_by_text(data.get('type_dossier_ens') or 'Non') realise = bool(data.get('realise_par_tiers')) self.tiers_check.setChecked(realise) self.detail_tiers_edit.setText(data.get('detail_tiers') or '') self.detail_tiers_edit.setEnabled(realise) def _new_form(self): self._edit_mode = False self._edit_code = None self.title_label.setText("

Nouvelle animation

") self.btn_save.setText("Enregistrer") self.btn_save_new.setVisible(True) self.anim_combo.blockSignals(True) self.anim_combo.setCurrentIndex(0) self.anim_combo.blockSignals(False) self._reset_form() def _on_etab_changed(self, idx): code = self.etab_combo.combo.itemData(idx) if code and code in self._etab_map: self.commune_label.setText(self._etab_map[code][1] or "") else: self.commune_label.setText("") def _on_tiers_changed(self, state): self.detail_tiers_edit.setEnabled(state == Qt.Checked) def _update_temps_total(self): total = self.duree_h.value() * 60 + self.duree_min.value() + self.prepa_spin.value() self.temps_total_label.setText(f"{total} min") def _update_total_pers(self): self.total_pers_label.setText(str(self.enfants_spin.value() + self.adultes_spin.value())) # ----------------------------------------------------------------------- # Capture géométrie sur carte # ----------------------------------------------------------------------- def _start_point_capture(self, geom_type, on_captured_callback): """ Lance l'outil de clic sur la carte. geom_type = 'lieu' ou 'etab'. Après le clic, appelle on_captured_callback(wkt, srid). """ canvas = iface.mapCanvas() if not canvas: QMessageBox.warning(self, "Erreur", "Impossible d'accéder à la carte QGIS.") return prev_tool = canvas.mapTool() tool = PointCaptureTool(canvas, prev_tool) def on_point(x, y, crs): # Convertir en WGS84 (4326) si nécessaire wgs84 = QgsCoordinateReferenceSystem("EPSG:4326") if crs != wgs84: transform = QgsCoordinateTransform(crs, wgs84, QgsProject.instance()) from qgis.core import QgsPointXY pt = transform.transform(x, y) x, y = pt.x(), pt.y() wkt = f"POINT({x} {y})" on_captured_callback(wkt, 4326) self.setWindowState(self.windowState() & ~Qt.WindowMinimized | Qt.WindowActive) self.activateWindow() tool.pointCaptured.connect(on_point) self._point_tool = tool # Minimiser le dialogue et activer l'outil self.showMinimized() canvas.setMapTool(tool) # ----------------------------------------------------------------------- # Callbacks ajout nouveaux éléments # ----------------------------------------------------------------------- def _add_type_groupe(self, widget): QMessageBox.information(self, "Info", "L'ajout d'un type de groupe nécessite un code entier.\n" "Veuillez contacter l'administrateur de la base.") def _add_lieu(self, widget): val, ok = QInputDialog.getText(self, "Nouveau lieu", "Nom du lieu :") if not ok or not val.strip(): return nom = val.strip() reply = QMessageBox.question(self, "Géométrie", "Souhaitez-vous localiser ce lieu sur la carte ?", QMessageBox.Yes | QMessageBox.No) if reply == QMessageBox.Yes: def on_geom(wkt, srid): try: code, label = self.db.add_lieux(nom, geom_wkt=wkt, srid=srid) widget.add_item(code, label) QMessageBox.information(self, "Succès", f"Lieu « {label} » ajouté avec géométrie.") except Exception as e: QMessageBox.critical(self, "Erreur", str(e)) self._start_point_capture('lieu', on_geom) else: try: code, label = self.db.add_lieux(nom) widget.add_item(code, label) except Exception as e: QMessageBox.critical(self, "Erreur", str(e)) def _add_theme(self, widget): val, ok = QInputDialog.getText(self, "Nouveau thème", "Libellé :") if ok and val.strip(): try: code, label = self.db.add_theme(val.strip()) widget.add_item(code, label) except Exception as e: QMessageBox.critical(self, "Erreur", str(e)) def _add_annulation(self, widget): val, ok = QInputDialog.getText(self, "Nouveau type d'annulation", "Libellé :") if ok and val.strip(): try: code, label = self.db.add_annulation(val.strip()) widget.add_item(code, label) except Exception as e: QMessageBox.critical(self, "Erreur", str(e)) def _add_dossier(self, widget): val, ok = QInputDialog.getText(self, "Nouveau type de dossier", "Libellé :") if ok and val.strip(): try: code, label = self.db.add_type_dossier(val.strip()) widget.add_item(code, label) except Exception as e: QMessageBox.critical(self, "Erreur", str(e)) def _add_etablissement(self, widget): nom, ok = QInputDialog.getText(self, "Nouvel établissement", "Nom :") if not ok or not nom.strip(): return commune, ok2 = QInputDialog.getText(self, "Nouvel établissement", "Commune :") if not ok2: return nom = nom.strip() commune = commune.strip() reply = QMessageBox.question(self, "Géométrie", "Souhaitez-vous localiser cet établissement sur la carte ?", QMessageBox.Yes | QMessageBox.No) if reply == QMessageBox.Yes: def on_geom(wkt, srid): try: code, label, com = self.db.add_etablissement(nom, commune, geom_wkt=wkt, srid=srid) self._etab_map[code] = (label, com) display = f"{label} ({com})" if com else label widget.add_item(code, display) QMessageBox.information(self, "Succès", f"Établissement « {label} » ajouté avec géométrie.") except Exception as e: QMessageBox.critical(self, "Erreur", str(e)) self._start_point_capture('etab', on_geom) else: try: code, label, com = self.db.add_etablissement(nom, commune) self._etab_map[code] = (label, com) display = f"{label} ({com})" if com else label widget.add_item(code, display) except Exception as e: QMessageBox.critical(self, "Erreur", str(e)) # ----------------------------------------------------------------------- # Collecte, validation, enregistrement # ----------------------------------------------------------------------- def _collect_data(self): duree_str = f"{self.duree_h.value()}h{self.duree_min.value():02d}" duree_min_total = self.duree_h.value() * 60 + self.duree_min.value() temps_total = duree_min_total + self.prepa_spin.value() or None etab_code = self.etab_combo.current_data() nom_etab, commune_prov = (None, None) if etab_code and etab_code in self._etab_map: nom_etab, commune_prov = self._etab_map[etab_code] lieux_txt = self.lieux_combo.current_text() if self.lieux_combo.current_data() else None type_annul = self.annul_combo.current_text() if self.annul_combo.current_data() else None type_dossier = self.dossier_combo.current_text() if self.dossier_combo.current_data() else None realise_tiers = self.tiers_check.isChecked() return { "date_anim": self.date_edit.date().toPyDate(), "duree_anim": duree_str, "animateurs": self.animateurs_widget.get_value(), "prepa_deplacemt": self.prepa_spin.value() or None, "temps_total": temps_total, "code_groupe": self.type_groupe_combo.current_data(), "precisions_type_de_groupe": self.precisions_groupe_edit.text().strip() or None, "commune_provenance": commune_prov, "nom_etablissement": nom_etab, "nbre_pers_enfants": self.enfants_spin.value() or None, "nbre_pers_adultes": self.adultes_spin.value() or None, "nbre_pers_total": (self.enfants_spin.value() + self.adultes_spin.value()) or None, "lieux": lieux_txt, "precisions_lieux_anim": self.precisions_lieux_edit.text().strip() or None, "accueil_pin": self.accueil_pin_check.isChecked(), "theme_detaille": self.theme_detaille_edit.text().strip() or None, "code_type_anim": self.theme_combo.current_data(), "payant": self.payant_check.isChecked(), "type_annul": type_annul, "remarques": self.remarques_edit.text().strip() or None, "type_dossier_ens": type_dossier, "realise_par_tiers": realise_tiers, "detail_tiers": self.detail_tiers_edit.text().strip() if realise_tiers else None, } def _reset_form(self): self.date_edit.setDate(QDate.currentDate()) self.duree_h.setValue(0) self.duree_min.setValue(0) self.prepa_spin.setValue(0) self.animateurs_widget.list_widget.clear() self.type_groupe_combo.combo.setCurrentIndex(0) self.precisions_groupe_edit.clear() self.etab_combo.combo.setCurrentIndex(0) self.commune_label.setText("") self.enfants_spin.setValue(0) self.adultes_spin.setValue(0) self.lieux_combo.combo.setCurrentIndex(0) self.precisions_lieux_edit.clear() self.accueil_pin_check.setChecked(False) self.theme_combo.combo.setCurrentIndex(0) self.theme_detaille_edit.clear() self.payant_check.setChecked(False) self.annul_combo.combo.setCurrentIndex(0) self.remarques_edit.clear() self.dossier_combo.combo.setCurrentIndex(0) self.tiers_check.setChecked(False) self.detail_tiers_edit.clear() def _save(self): try: data = self._collect_data() if self._edit_mode: self.db.update_donnees_animation(self._edit_code, data) QMessageBox.information(self, "Succès", f"Animation #{self._edit_code} mise à jour.") self._reload_animations_list() else: code = self.db.get_next_code_animation() data['code_animation'] = code self.db.insert_donnees_animation(data) QMessageBox.information(self, "Succès", f"Animation #{code} enregistrée.") self._reload_animations_list() self.accept() except Exception as e: QMessageBox.critical(self, "Erreur", str(e)) def _save_and_new(self): try: data = self._collect_data() code = self.db.get_next_code_animation() data['code_animation'] = code self.db.insert_donnees_animation(data) QMessageBox.information(self, "Succès", f"Animation #{code} enregistrée.") self._reload_animations_list() self._new_form() except Exception as e: QMessageBox.critical(self, "Erreur", str(e))