from PyQt6.QtWidgets import QWidget, QVBoxLayout, QLabel, QPushButton, QComboBox, QDialog, QTreeWidget, QTreeWidgetItem, QHeaderView, QGridLayout, QFileDialog from PyQt6.QtCore import Qt, QEvent, QThread, pyqtSignal from app.core.main_manager import MainManager, NotificationType from app.core.export_manager import ExportCategory from app.ui.widgets.loading_bar import LoadingBar class ExportWorker(QThread): progress_changed = pyqtSignal(int) phase_changed = pyqtSignal(str) export_finished = pyqtSignal(bool) def __init__(self, export_manager, export_type, export_format): super().__init__() self.export_manager = export_manager self.export_type = export_type self.export_format = export_format def run(self): try: # Connect export manager signals self.export_manager.progress_changed.connect(self.progress_changed.emit) self.export_manager.phase_changed.connect(self.phase_changed.emit) self.export_manager.export_finished.connect(self.export_finished.emit) # Determine which export method to call success = False if self.export_type == 0: # Images if self.export_format == 0: # PDF success = self.export_manager.export_images_as_pdf() elif self.export_format == 1: # PNG success = self.export_manager.export_images_as_png() elif self.export_format == 2: # DICOMDIR success = self.export_manager.export_images_as_dicomdir() elif self.export_format == 3: # DCM success = self.export_manager.export_images_as_dcm() elif self.export_type == 1: # Metadata if self.export_format == 0: # JSON success = self.export_manager.export_metadata_as_json() elif self.export_format == 1: # XLS success = self.export_manager.export_metadata_as_xls() # Emit final result if not already emitted by export manager if not success: self.export_finished.emit(False) except Exception as e: self.export_finished.emit(False) finally: # Disconnect signals try: self.export_manager.progress_changed.disconnect() self.export_manager.phase_changed.disconnect() self.export_manager.export_finished.disconnect() except: pass class ExportWindow(QWidget): def __init__(self, parent=None): super().__init__(parent) self.main_manager = MainManager.get_instance() self.language_manager = self.main_manager.get_language_manager() self.export_manager = self.main_manager.get_export_manager() self.observer_manager = self.main_manager.get_observer_manager() self.alert_manager = self.main_manager.get_alert_manager() self.observer_manager.subscribe(NotificationType.LANGUAGE, self.update_texts) self.export_type_combo = None self.export_format_combo = None self.export_worker = None self.progress_dialog = None self.setup_ui() def setup_ui(self): layout = QVBoxLayout(self) layout.setAlignment(Qt.AlignmentFlag.AlignTop) layout.setSpacing(20) layout.setContentsMargins(20, 20, 20, 20) self.export_label = QLabel(self.language_manager.get_text("export_label"), self) layout.addWidget(self.export_label) # Add combo box for export type self.export_type_combo = QComboBox(self) self.export_type_combo.addItems([self.language_manager.get_text("export_type_images"), self.language_manager.get_text("export_type_metadata")]) self.export_type_combo.currentIndexChanged.connect(self.update_export_formats) layout.addWidget(self.export_type_combo) # Add combo box for export format self.export_format_combo = QComboBox(self) self.update_export_formats() # Initialize formats based on default type layout.addWidget(self.export_format_combo) self.export_button = QPushButton(self.language_manager.get_text("export_button"), self) self.export_button.clicked.connect(self.show_dicom_selection_popup) layout.addWidget(self.export_button) def update_texts(self): self.export_label.setText(self.language_manager.get_text("export_label")) self.export_button.setText(self.language_manager.get_text("export_button")) self.export_type_combo.setItemText(0, self.language_manager.get_text("export_type_images")) self.export_type_combo.setItemText(1, self.language_manager.get_text("export_type_metadata")) def update_export_formats(self): self.export_format_combo.clear() if self.export_type_combo.currentIndex() < 1: # Images self.export_format_combo.addItems(["PDF", "PNG", "DICOMDIR", "DCM"]) else: # Metadata self.export_format_combo.addItems(["JSON", "XLS"]) def show_dicom_selection_popup(self): dicom_list = self.export_manager.get_export_dicom_list() if not dicom_list: # Afficher une alerte si aucune donnée n'est disponible self.alert_manager.show_error("no_dicoms_to_export") return popup = QDialog(self) popup.setWindowTitle(self.language_manager.get_text("select_dicom_to_export")) popup.setLayout(QVBoxLayout()) popup.resize(400, 600) # Set initial size with increased height tree = QTreeWidget(popup) tree.setHeaderHidden(False) tree.setColumnCount(2) tree.setHeaderLabels([self.language_manager.get_text("dicom_name"), self.language_manager.get_text("group")]) # disable automatic stretch and fit col 1 to its contents tree.header().setStretchLastSection(False) tree.header().setSectionResizeMode(1, QHeaderView.ResizeMode.ResizeToContents) popup.layout().addWidget(tree) for dicom, sub_dicoms in dicom_list: parent_item = QTreeWidgetItem([dicom.get_name()]) parent_item.setCheckState(0, Qt.CheckState.Unchecked) # Add checkbox to parent item parent_item.setData(0, Qt.ItemDataRole.UserRole, dicom) # Link parent item to its original object tree.addTopLevelItem(parent_item) # Handle case where there are no subfolders (direct files) if not sub_dicoms and dicom.get_files(): # Create a single child for the DICOM itself child_item = QTreeWidgetItem([dicom.get_name()]) child_item.setCheckState(0, Qt.CheckState.Unchecked) child_item.setData(0, Qt.ItemDataRole.UserRole, dicom) parent_item.addChild(child_item) # Create color selection button color_button = QPushButton(parent=popup) color_button.setFixedSize(20, 20) colors = ExportCategory.list() # Set initial color color_button.setStyleSheet(f""" QPushButton {{ background-color: rgb{dicom.get_category()}; border: none; padding: 0px; margin: 0px; }} """) color_button.clicked.connect(lambda _, btn=color_button, item=child_item, cols=colors: self.show_color_dialog(item, btn, cols)) tree.setItemWidget(child_item, 1, color_button) else: # Handle subfolders as before for sub_dicom in sub_dicoms: child_item = QTreeWidgetItem([sub_dicom.get_name()]) child_item.setCheckState(0, Qt.CheckState.Unchecked) # Add checkbox to child item child_item.setData(0, Qt.ItemDataRole.UserRole, sub_dicom) # Link child item to its original object parent_item.addChild(child_item) # Create color selection button instead of combo box color_button = QPushButton(parent=popup) color_button.setFixedSize(20, 20) colors = ExportCategory.list() # Set initial color color_button.setStyleSheet(f""" QPushButton {{ background-color: rgb{sub_dicom.get_category()}; border: none; padding: 0px; margin: 0px; }} """) # Connect to custom color dialog color_button.clicked.connect(lambda _, btn=color_button, item=child_item, cols=colors: self.show_color_dialog(item, btn, cols)) tree.setItemWidget(child_item, 1, color_button) # single connection to handle both directions tree.itemChanged.connect(self.on_item_changed) tree.resizeColumnToContents(0) # Resize first column to fit content tree.setColumnWidth(0, max(200, tree.columnWidth(0))) # Ensure minimum 200px for first column tree.setColumnWidth(1, 20) # Exact width for button # Adjust dialog size to fit content popup.adjustSize() popup.setMinimumHeight(600) # Ensure minimum height confirm_button = QPushButton(self.language_manager.get_text("confirm"), popup) confirm_button.clicked.connect(lambda: (self.export_data(), popup.accept())) popup.layout().addWidget(confirm_button) popup.exec() def toggle_child_items(self, item): if item.childCount() > 0: # Check if the item has children for i in range(item.childCount()): child = item.child(i) child.setCheckState(0, item.checkState(0)) # Set child state to match parent state def show_color_dialog(self, item, button, colors): dialog = QDialog(self) dialog.setWindowFlags(Qt.WindowType.Popup) dialog.setFixedSize(200, 40) # Increased height for labels # Position dialog to the left of the button button_pos = button.mapToGlobal(button.rect().topLeft()) dialog.move(button_pos.x() - 200, button_pos.y()) layout = QGridLayout(dialog) layout.setSpacing(0) layout.setContentsMargins(0, 0, 0, 0) for i, color in enumerate(colors): color_btn = QPushButton() color_btn.setFixedSize(20, 20) color_btn.setStyleSheet(f""" QPushButton {{ background-color: rgb{color}; border: none; padding: 0px; margin: 0px; }} """) # Add tooltip with category name color_btn.setToolTip(ExportCategory.get_name(color)) color_btn.clicked.connect(lambda _, c=color: self.select_color(item, button, c, dialog)) layout.addWidget(color_btn, 0, i) # Add label below button label = QLabel(ExportCategory.get_name(color)[:3]) # First 3 letters label.setAlignment(Qt.AlignmentFlag.AlignCenter) label.setStyleSheet("font-size: 8px; color: black;") layout.addWidget(label, 1, i) dialog.exec() def select_color(self, item, button, color, dialog): button.setStyleSheet(f""" QPushButton {{ background-color: rgb{color}; border: none; padding: 0px; margin: 0px; }} """) item.setData(1, Qt.ItemDataRole.UserRole, color) item.data(0, Qt.ItemDataRole.UserRole).set_category(color) dialog.accept() def export_data(self): """Start the export process with progress tracking""" # Check if there's data to export data = self.export_manager.fetch_export_data() if not data: self.alert_manager.show_error("no_data_to_export") return # Choose export destination destination = QFileDialog.getExistingDirectory( self, self.language_manager.get_text("choose_export_destination"), "" ) if not destination: return # User cancelled # Set export destination self.export_manager.set_export_destination(destination) # Create and show progress dialog self.progress_dialog = QDialog(self) self.progress_dialog.setWindowTitle(self.language_manager.get_text("export_label")) self.progress_dialog.setModal(True) self.progress_dialog.setFixedSize(400, 120) layout = QVBoxLayout(self.progress_dialog) self.progress_bar = LoadingBar(self.language_manager.get_text("preparing_export"), self.progress_dialog) layout.addWidget(self.progress_bar) # Cancel button cancel_button = QPushButton(self.language_manager.get_text("cancel"), self.progress_dialog) cancel_button.clicked.connect(self.cancel_export) layout.addWidget(cancel_button) self.progress_dialog.show() # Start export worker export_type = self.export_type_combo.currentIndex() export_format = self.export_format_combo.currentIndex() self.export_worker = ExportWorker(self.export_manager, export_type, export_format) self.export_worker.progress_changed.connect(self.progress_bar.set_progress) self.export_worker.phase_changed.connect(self.update_export_phase) self.export_worker.export_finished.connect(self.on_export_finished) self.export_worker.start() def update_export_phase(self, phase): """Update the progress bar label based on current phase""" if hasattr(self, 'progress_bar') and self.progress_bar: self.progress_bar.set_label(self.language_manager.get_text(phase)) def cancel_export(self): """Cancel the export process""" if self.export_worker and self.export_worker.isRunning(): self.export_worker.terminate() self.export_worker.wait() if self.progress_dialog: self.progress_dialog.close() self.progress_dialog = None def on_export_finished(self, success): """Handle export completion""" if self.progress_dialog: self.progress_dialog.close() self.progress_dialog = None if self.export_worker: self.export_worker.deleteLater() self.export_worker = None if success: self.alert_manager.show_success("export_success") else: self.alert_manager.show_error("export_error") def eventFilter(self, source, event): if isinstance(source, QComboBox) and event.type() == QEvent.Type.Wheel: return True return super().eventFilter(source, event) def on_item_changed(self, item, column): if column != 0: return tree = item.treeWidget() tree.blockSignals(True) # Set export status based on check state is_checked = item.checkState(0) == Qt.CheckState.Checked item.data(0, Qt.ItemDataRole.UserRole).set_export(is_checked) # if parent toggled → toggle all children if item.childCount() > 0: state = item.checkState(0) for i in range(item.childCount()): child = item.child(i) child.setCheckState(0, state) # Set export status for children too child.data(0, Qt.ItemDataRole.UserRole).set_export(state == Qt.CheckState.Checked) # if child toggled → update parent parent = item.parent() if parent: all_checked = all(parent.child(i).checkState(0) == Qt.CheckState.Checked for i in range(parent.childCount())) parent.setCheckState(0, Qt.CheckState.Checked if all_checked else Qt.CheckState.Unchecked) # Set export status for parent parent.data(0, Qt.ItemDataRole.UserRole).set_export(all_checked) tree.blockSignals(False)