HoDA_Radio/app/ui/windows/export_window.py

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)