generated from LouisMazin/PythonApplicationTemplate
372 lines
16 KiB
Python
372 lines
16 KiB
Python
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) |