Progress Bar und Diff-PDF-Icon im TreeWidget implementiert
Neue Features: - Progress Bar in Spalte 2 während XML-Transformationen - Diff-PDF-Icon erscheint nach Transformation bei vorhandener Diff-PDF - Doppelklick auf Icon öffnet Diff-PDF mit System-Viewer - Initial-Laden von Icons für existierende Diff-PDFs beim Projektstart Technische Implementierung: - XML-Item-Mapping mit eindeutigem Key-Format: "xml_path|xsl_id" - Unterstützt mehrfache Verwendung derselben XML bei verschiedenen XSL-Dateien - TransformationThread-Signale erweitert um XSL-ID-Parameter - Widget-Factory-Methoden für zentrierte Progress Bar und klickbare Icons - Result-Dictionary in transform.py enthält jetzt xsl_id UI-Anpassungen: - TreeWidget Spaltenanzahl von 2 auf 3 erhöht - setItemWidget() für dynamische Widget-Verwaltung in Spalte 2 Dateien: - src/ui/MainWindow.py: Hauptimplementierung mit Signal-Handlern - src/transform.py: xsl_id im Result-Dictionary - src/ui/MainWinddow.ui: Spalte 3 hinzugefügt - src/ui/MainWinddow_ui.py: Auto-generiert aus UI-Datei 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
+19
-9
@@ -34,7 +34,7 @@ class TransformationJob:
|
||||
apache_fop_dir: Path,
|
||||
diff_pdf_path: Path,
|
||||
diff_pdf_params: list[str],
|
||||
xsl_id: tuple | None = None
|
||||
xsl_id: tuple | None = None,
|
||||
):
|
||||
"""
|
||||
Initialisiert einen Transformations-Job.
|
||||
@@ -92,6 +92,7 @@ class TransformationJob:
|
||||
|
||||
# Apache FOP Binaries (plattformabhängig)
|
||||
import sys
|
||||
|
||||
if sys.platform == "win32":
|
||||
self.fop_cmd = self.apache_fop_dir / "fop.cmd"
|
||||
else:
|
||||
@@ -158,11 +159,13 @@ class TransformationJob:
|
||||
|
||||
# Sammle alle JAR-Dateien im Saxon-Verzeichnis für den Classpath
|
||||
import glob
|
||||
|
||||
saxon_dir = self.saxon_jar_path.parent
|
||||
all_jars = glob.glob(str(saxon_dir / "*.jar"))
|
||||
|
||||
# Verwende alle JARs im Classpath (getrennt durch : auf Linux/Mac, ; auf Windows)
|
||||
import sys
|
||||
|
||||
classpath_separator = ";" if sys.platform == "win32" else ":"
|
||||
classpath = classpath_separator.join(all_jars)
|
||||
|
||||
@@ -187,14 +190,16 @@ class TransformationJob:
|
||||
cmd_line,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=120 # 2 Minuten Timeout
|
||||
timeout=120, # 2 Minuten Timeout
|
||||
)
|
||||
|
||||
if result.returncode == 0:
|
||||
logger.info(f"Saxon-Transformation erfolgreich: {self.xml_file.name}")
|
||||
return True, "Erfolgreich"
|
||||
else:
|
||||
error_msg = f"Saxon-Fehler (Exit {result.returncode}):\nStdOut: {result.stdout}\nStdErr: {result.stderr}"
|
||||
error_msg = (
|
||||
f"Saxon-Fehler (Exit {result.returncode}):\nStdOut: {result.stdout}\nStdErr: {result.stderr}"
|
||||
)
|
||||
logger.error(error_msg)
|
||||
return False, error_msg
|
||||
|
||||
@@ -230,10 +235,13 @@ class TransformationJob:
|
||||
# Apache FOP Kommandozeile
|
||||
cmd_line = [
|
||||
str(self.fop_cmd),
|
||||
"-c", str(self.fop_conf) if self.fop_conf.exists() else "",
|
||||
"-c",
|
||||
str(self.fop_conf) if self.fop_conf.exists() else "",
|
||||
"-r",
|
||||
"-fo", str(self.temp_fo),
|
||||
"-pdf", str(self.new_pdf),
|
||||
"-fo",
|
||||
str(self.temp_fo),
|
||||
"-pdf",
|
||||
str(self.new_pdf),
|
||||
]
|
||||
|
||||
# Entferne leere Config-Parameter wenn fop.xconf nicht existiert
|
||||
@@ -248,7 +256,7 @@ class TransformationJob:
|
||||
cmd_line,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=180 # 3 Minuten Timeout
|
||||
timeout=180, # 3 Minuten Timeout
|
||||
)
|
||||
|
||||
# Temporäre FO-Datei löschen
|
||||
@@ -264,6 +272,7 @@ class TransformationJob:
|
||||
if not self.ref_pdf.exists():
|
||||
try:
|
||||
import shutil
|
||||
|
||||
shutil.copy2(self.new_pdf, self.ref_pdf)
|
||||
logger.info(f"Ref-PDF erstellt: {self.ref_pdf}")
|
||||
except Exception as e:
|
||||
@@ -320,7 +329,7 @@ class TransformationJob:
|
||||
cmd_compare,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=60 # 1 Minute Timeout
|
||||
timeout=60, # 1 Minute Timeout
|
||||
)
|
||||
|
||||
if result.returncode == 0:
|
||||
@@ -355,7 +364,7 @@ class TransformationJob:
|
||||
cmd_diff,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=90 # 1.5 Minuten Timeout
|
||||
timeout=90, # 1.5 Minuten Timeout
|
||||
)
|
||||
|
||||
if result_diff.returncode == 0 or self.diff_pdf.exists():
|
||||
@@ -392,6 +401,7 @@ class TransformationJob:
|
||||
result = {
|
||||
"success": False,
|
||||
"xml_file": str(self.xml_file),
|
||||
"xsl_id": self.xsl_id,
|
||||
"steps": {},
|
||||
"duration": None,
|
||||
"new_pdf": str(self.new_pdf) if self.new_pdf.exists() else None,
|
||||
|
||||
+11
-6
@@ -64,7 +64,7 @@
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="columnCount">
|
||||
<number>2</number>
|
||||
<number>3</number>
|
||||
</property>
|
||||
<attribute name="headerHighlightSections">
|
||||
<bool>true</bool>
|
||||
@@ -82,6 +82,11 @@
|
||||
<string notr="true">2</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string notr="true">3</string>
|
||||
</property>
|
||||
</column>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
@@ -171,8 +176,8 @@
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>54</width>
|
||||
<height>718</height>
|
||||
<width>68</width>
|
||||
<height>728</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
@@ -349,8 +354,8 @@
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>649</width>
|
||||
<height>690</height>
|
||||
<width>625</width>
|
||||
<height>700</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_3">
|
||||
@@ -396,7 +401,7 @@
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>1263</width>
|
||||
<height>33</height>
|
||||
<height>22</height>
|
||||
</rect>
|
||||
</property>
|
||||
<widget class="QMenu" name="menuProjekt">
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
################################################################################
|
||||
## Form generated from reading UI file 'MainWinddow.ui'
|
||||
##
|
||||
## Created by: Qt User Interface Compiler version 6.9.1
|
||||
## Created by: Qt User Interface Compiler version 6.9.2
|
||||
##
|
||||
## WARNING! All changes made in this file will be lost when recompiling UI file!
|
||||
################################################################################
|
||||
@@ -69,6 +69,7 @@ class Ui_MainWindow(object):
|
||||
self.verticalLayout.setContentsMargins(-1, -1, -1, 0)
|
||||
self.treeWidget = QTreeWidget(self.frame)
|
||||
__qtreewidgetitem = QTreeWidgetItem()
|
||||
__qtreewidgetitem.setText(2, u"3");
|
||||
__qtreewidgetitem.setText(1, u"2");
|
||||
__qtreewidgetitem.setText(0, u"1");
|
||||
self.treeWidget.setHeaderItem(__qtreewidgetitem)
|
||||
@@ -78,7 +79,7 @@ class Ui_MainWindow(object):
|
||||
sizePolicy1.setVerticalStretch(0)
|
||||
sizePolicy1.setHeightForWidth(self.treeWidget.sizePolicy().hasHeightForWidth())
|
||||
self.treeWidget.setSizePolicy(sizePolicy1)
|
||||
self.treeWidget.setColumnCount(2)
|
||||
self.treeWidget.setColumnCount(3)
|
||||
self.treeWidget.header().setHighlightSections(True)
|
||||
self.treeWidget.header().setStretchLastSection(True)
|
||||
|
||||
@@ -131,7 +132,7 @@ class Ui_MainWindow(object):
|
||||
self.scrollArea.setWidgetResizable(True)
|
||||
self.scrollAreaWidgetContents = QWidget()
|
||||
self.scrollAreaWidgetContents.setObjectName(u"scrollAreaWidgetContents")
|
||||
self.scrollAreaWidgetContents.setGeometry(QRect(0, 0, 54, 718))
|
||||
self.scrollAreaWidgetContents.setGeometry(QRect(0, 0, 68, 728))
|
||||
self.verticalLayout_2 = QVBoxLayout(self.scrollAreaWidgetContents)
|
||||
self.verticalLayout_2.setObjectName(u"verticalLayout_2")
|
||||
self.label = QLabel(self.scrollAreaWidgetContents)
|
||||
@@ -216,7 +217,7 @@ class Ui_MainWindow(object):
|
||||
self.scrollArea_2.setWidgetResizable(True)
|
||||
self.scrollAreaWidgetContents_2 = QWidget()
|
||||
self.scrollAreaWidgetContents_2.setObjectName(u"scrollAreaWidgetContents_2")
|
||||
self.scrollAreaWidgetContents_2.setGeometry(QRect(0, 0, 649, 690))
|
||||
self.scrollAreaWidgetContents_2.setGeometry(QRect(0, 0, 625, 700))
|
||||
self.verticalLayout_3 = QVBoxLayout(self.scrollAreaWidgetContents_2)
|
||||
self.verticalLayout_3.setObjectName(u"verticalLayout_3")
|
||||
self.verticalLayout_3.setContentsMargins(0, 0, 0, 0)
|
||||
@@ -241,7 +242,7 @@ class Ui_MainWindow(object):
|
||||
MainWindow.setCentralWidget(self.centralwidget)
|
||||
self.menubar = QMenuBar(MainWindow)
|
||||
self.menubar.setObjectName(u"menubar")
|
||||
self.menubar.setGeometry(QRect(0, 0, 1263, 33))
|
||||
self.menubar.setGeometry(QRect(0, 0, 1263, 22))
|
||||
self.menuProjekt = QMenu(self.menubar)
|
||||
self.menuProjekt.setObjectName(u"menuProjekt")
|
||||
self.menuThema = QMenu(self.menubar)
|
||||
|
||||
+365
-135
@@ -9,7 +9,19 @@ from typing import List
|
||||
|
||||
from PySide6.QtCore import Qt, QSize, QThread, Signal
|
||||
from PySide6.QtGui import QCursor, QPixmap, QPainter, QAction, QIcon, QDragEnterEvent, QDropEvent
|
||||
from PySide6.QtWidgets import QLabel, QMainWindow, QApplication, QStyleFactory, QMenu, QTreeWidgetItem, QMessageBox, QFileDialog
|
||||
from PySide6.QtWidgets import (
|
||||
QLabel,
|
||||
QMainWindow,
|
||||
QApplication,
|
||||
QStyleFactory,
|
||||
QMenu,
|
||||
QTreeWidgetItem,
|
||||
QMessageBox,
|
||||
QFileDialog,
|
||||
QWidget,
|
||||
QHBoxLayout,
|
||||
QProgressBar,
|
||||
)
|
||||
from PySide6.QtPdf import QPdfDocument
|
||||
|
||||
from ui.MainWinddow_ui import Ui_MainWindow
|
||||
@@ -100,7 +112,7 @@ class XmlHashCalculatorThread(QThread):
|
||||
return None
|
||||
|
||||
# Datei binär lesen und Hash berechnen
|
||||
with open(file_path, 'rb') as f:
|
||||
with open(file_path, "rb") as f:
|
||||
file_content = f.read()
|
||||
hash_obj = hashlib.blake2b(file_content)
|
||||
hash_hex = hash_obj.hexdigest()
|
||||
@@ -119,9 +131,9 @@ class TransformationThread(QThread):
|
||||
"""
|
||||
|
||||
# Signale für die Kommunikation mit dem Haupt-Thread
|
||||
job_started = Signal(str) # xml_file_name
|
||||
job_started = Signal(str, str) # xml_file_name, xsl_id_str
|
||||
job_finished = Signal(dict) # result_dict
|
||||
job_error = Signal(str, str) # xml_file_name, error_message
|
||||
job_error = Signal(str, str, str) # xml_file_name, xsl_id_str, error_message
|
||||
all_jobs_finished = Signal(int, int) # successful_count, total_count
|
||||
|
||||
def __init__(self, jobs: list[TransformationJob], force: bool = False):
|
||||
@@ -145,8 +157,9 @@ class TransformationThread(QThread):
|
||||
|
||||
for job in self.jobs:
|
||||
try:
|
||||
# Sende Start-Signal
|
||||
self.job_started.emit(str(job.xml_file))
|
||||
# Sende Start-Signal mit XSL-ID
|
||||
xsl_id_str = "_".join(str(x) for x in job.xsl_id) if job.xsl_id else ""
|
||||
self.job_started.emit(str(job.xml_file), xsl_id_str)
|
||||
|
||||
# Führe Transformations-Pipeline aus
|
||||
result = job.run_full_pipeline(force=self.force)
|
||||
@@ -160,7 +173,8 @@ class TransformationThread(QThread):
|
||||
except Exception as e:
|
||||
error_msg = f"Unerwarteter Fehler bei Transformation: {str(e)}"
|
||||
logger.error(error_msg)
|
||||
self.job_error.emit(str(job.xml_file), error_msg)
|
||||
xsl_id_str = "_".join(str(x) for x in job.xsl_id) if job.xsl_id else ""
|
||||
self.job_error.emit(str(job.xml_file), xsl_id_str, error_msg)
|
||||
|
||||
# Sende Abschluss-Signal für alle Jobs
|
||||
self.all_jobs_finished.emit(self.successful_count, len(self.jobs))
|
||||
@@ -219,6 +233,9 @@ class MainWindow(QMainWindow):
|
||||
# Transformations-Thread
|
||||
self.transformation_thread = None
|
||||
|
||||
# Mapping: xml_file_path_str → QTreeWidgetItem (für Progress Bar und Icon Updates)
|
||||
self.xml_item_map = {}
|
||||
|
||||
# Theme-Menü initialisieren
|
||||
self._setup_theme_menu()
|
||||
|
||||
@@ -226,10 +243,10 @@ class MainWindow(QMainWindow):
|
||||
self._setup_projects_menu()
|
||||
|
||||
#
|
||||
if (theme := app_settings.theme):
|
||||
if theme := app_settings.theme:
|
||||
self.change_theme(theme)
|
||||
else:
|
||||
self.change_theme('Fusion')
|
||||
self.change_theme("Fusion")
|
||||
|
||||
# Bilder laden
|
||||
self._load_images()
|
||||
@@ -292,9 +309,7 @@ class MainWindow(QMainWindow):
|
||||
project_action.setToolTip(f"Projekt-Ordner: {project.project_dir}")
|
||||
|
||||
# Verbinde die Aktion mit der Projekt-Öffnen-Funktion
|
||||
project_action.triggered.connect(
|
||||
lambda checked, proj=project: self.open_existing_project(proj)
|
||||
)
|
||||
project_action.triggered.connect(lambda checked, proj=project: self.open_existing_project(proj))
|
||||
|
||||
projects_menu.addAction(project_action)
|
||||
|
||||
@@ -317,7 +332,7 @@ class MainWindow(QMainWindow):
|
||||
|
||||
try:
|
||||
# Prüfe ob project.yaml existiert und nicht leer ist
|
||||
project_yaml_path = Path(project.project_dir) / 'project.yaml'
|
||||
project_yaml_path = Path(project.project_dir) / "project.yaml"
|
||||
|
||||
if project_yaml_path.exists() and project_yaml_path.stat().st_size > 0:
|
||||
# Versuche die Projekt-Einstellungen zu laden
|
||||
@@ -338,6 +353,9 @@ class MainWindow(QMainWindow):
|
||||
# Starte Hash-Berechnung für alle XML-Dateien
|
||||
self._start_xml_hash_calculation()
|
||||
|
||||
# Setze Icons für bereits existierende Diff-PDFs
|
||||
self._update_diff_icons_for_existing_pdfs()
|
||||
|
||||
except Exception as e:
|
||||
print(f"Fehler beim Laden des Projekts '{project.name}': {e}")
|
||||
# Fallback: Erstelle Standard-Einstellungen
|
||||
@@ -436,18 +454,16 @@ class MainWindow(QMainWindow):
|
||||
new_doc.load(new_pdf_path)
|
||||
|
||||
# Warten bis PDFs geladen sind
|
||||
if (diff_doc.status() != QPdfDocument.Status.Ready or
|
||||
ref_doc.status() != QPdfDocument.Status.Ready or
|
||||
new_doc.status() != QPdfDocument.Status.Ready):
|
||||
if (
|
||||
diff_doc.status() != QPdfDocument.Status.Ready
|
||||
or ref_doc.status() != QPdfDocument.Status.Ready
|
||||
or new_doc.status() != QPdfDocument.Status.Ready
|
||||
):
|
||||
print(f"Fehler beim Laden der PDFs für {pdf_filename}")
|
||||
continue
|
||||
|
||||
# PDF-Dokumente für später speichern
|
||||
self.pdf_documents[pdf_filename] = {
|
||||
'diff': diff_doc,
|
||||
'ref': ref_doc,
|
||||
'new': new_doc
|
||||
}
|
||||
self.pdf_documents[pdf_filename] = {"diff": diff_doc, "ref": ref_doc, "new": new_doc}
|
||||
|
||||
print(f"PDFs geladen: {pdf_filename}")
|
||||
print(f" diff: {diff_doc.pageCount()} Seiten")
|
||||
@@ -469,8 +485,9 @@ class MainWindow(QMainWindow):
|
||||
scale_factor = 200.0 / page_size.width() # 200 Pixel Breite für Thumbnail
|
||||
|
||||
# Seite rendern
|
||||
page_image = diff_doc.render(page_num, QSize(int(page_size.width() * scale_factor),
|
||||
int(page_size.height() * scale_factor)))
|
||||
page_image = diff_doc.render(
|
||||
page_num, QSize(int(page_size.width() * scale_factor), int(page_size.height() * scale_factor))
|
||||
)
|
||||
|
||||
diff_pixmap = QPixmap.fromImage(page_image)
|
||||
|
||||
@@ -488,10 +505,7 @@ class MainWindow(QMainWindow):
|
||||
self.ui.verticalLayout_2.addWidget(thumbnail_info)
|
||||
|
||||
# Beziehung zwischen Thumbnail und Seitennummer speichern
|
||||
self.thumbnail_to_page[thumbnail] = {
|
||||
'pdf_filename': pdf_filename,
|
||||
'page_num': page_num
|
||||
}
|
||||
self.thumbnail_to_page[thumbnail] = {"pdf_filename": pdf_filename, "page_num": page_num}
|
||||
|
||||
# Click-Event für das Thumbnail einrichten
|
||||
thumbnail.mousePressEvent = lambda event, t=thumbnail: self.on_thumbnail_clicked(event, t)
|
||||
@@ -519,7 +533,9 @@ class MainWindow(QMainWindow):
|
||||
# Drag-to-Scroll Events für das große Bild einrichten
|
||||
self.fullsize_label.mousePressEvent = lambda event: self.on_fullsize_mouse_press(event, self.fullsize_label)
|
||||
self.fullsize_label.mouseMoveEvent = lambda event: self.on_fullsize_mouse_move(event, self.fullsize_label)
|
||||
self.fullsize_label.mouseReleaseEvent = lambda event: self.on_fullsize_mouse_release(event, self.fullsize_label)
|
||||
self.fullsize_label.mouseReleaseEvent = lambda event: self.on_fullsize_mouse_release(
|
||||
event, self.fullsize_label
|
||||
)
|
||||
|
||||
# Zeige die erste Seite initial an
|
||||
if self.current_pdf:
|
||||
@@ -546,13 +562,12 @@ class MainWindow(QMainWindow):
|
||||
docs = self.pdf_documents[pdf_filename]
|
||||
|
||||
# Diff-Seite laden (bestimmt die Abmessungen)
|
||||
diff_doc = docs['diff']
|
||||
diff_doc = docs["diff"]
|
||||
page_size = diff_doc.pagePointSize(page_num)
|
||||
|
||||
# Hohe Auflösung für Vollbild (entspricht ca. Matrix(2.0, 2.0) in PyMuPDF)
|
||||
scale_factor = 2.0
|
||||
render_size = QSize(int(page_size.width() * scale_factor),
|
||||
int(page_size.height() * scale_factor))
|
||||
render_size = QSize(int(page_size.width() * scale_factor), int(page_size.height() * scale_factor))
|
||||
|
||||
# Diff-Seite rendern (immer vorhanden)
|
||||
diff_image = diff_doc.render(page_num, render_size)
|
||||
@@ -563,7 +578,7 @@ class MainWindow(QMainWindow):
|
||||
diff_height = diff_pixmap.height()
|
||||
|
||||
# Ref-Seite prüfen und rendern oder weiße Seite erstellen
|
||||
ref_doc = docs['ref']
|
||||
ref_doc = docs["ref"]
|
||||
if page_num < ref_doc.pageCount():
|
||||
ref_image = ref_doc.render(page_num, render_size)
|
||||
ref_pixmap = QPixmap.fromImage(ref_image)
|
||||
@@ -575,7 +590,7 @@ class MainWindow(QMainWindow):
|
||||
print(f"Weiße Ref-Seite {page_num + 1} erstellt")
|
||||
|
||||
# New-Seite prüfen und rendern oder weiße Seite erstellen
|
||||
new_doc = docs['new']
|
||||
new_doc = docs["new"]
|
||||
if page_num < new_doc.pageCount():
|
||||
new_image = new_doc.render(page_num, render_size)
|
||||
new_pixmap = QPixmap.fromImage(new_image)
|
||||
@@ -587,11 +602,7 @@ class MainWindow(QMainWindow):
|
||||
print(f"Weiße New-Seite {page_num + 1} erstellt")
|
||||
|
||||
# Cache die gerenderten Pixmaps für schnelle Alpha/Zoom-Operationen
|
||||
self.current_rendered_pixmaps = {
|
||||
'ref': ref_pixmap,
|
||||
'diff': diff_pixmap,
|
||||
'new': new_pixmap
|
||||
}
|
||||
self.current_rendered_pixmaps = {"ref": ref_pixmap, "diff": diff_pixmap, "new": new_pixmap}
|
||||
|
||||
# Aktualisiere aktuelle Seite
|
||||
self.current_page = page_num
|
||||
@@ -620,9 +631,9 @@ class MainWindow(QMainWindow):
|
||||
return
|
||||
|
||||
# Hole die gecachten Pixmaps
|
||||
ref_pixmap = self.current_rendered_pixmaps['ref']
|
||||
diff_pixmap = self.current_rendered_pixmaps['diff']
|
||||
new_pixmap = self.current_rendered_pixmaps['new']
|
||||
ref_pixmap = self.current_rendered_pixmaps["ref"]
|
||||
diff_pixmap = self.current_rendered_pixmaps["diff"]
|
||||
new_pixmap = self.current_rendered_pixmaps["new"]
|
||||
|
||||
# Erstelle das überlagerte Bild mit aktuellem Alpha-Wert
|
||||
alpha_value = self.ui.alpha.value()
|
||||
@@ -886,7 +897,7 @@ class MainWindow(QMainWindow):
|
||||
if node_type == "TreeNode":
|
||||
# Kontextmenü für TreeNode
|
||||
action_add_child = QAction("Unterknoten hinzufügen", self)
|
||||
action_add_child.setIcon(QIcon(QIcon.fromTheme(u"folder-new")))
|
||||
action_add_child.setIcon(QIcon(QIcon.fromTheme("folder-new")))
|
||||
action_add_child.triggered.connect(lambda: self._add_tree_node_child(item))
|
||||
menu.addAction(action_add_child)
|
||||
|
||||
@@ -967,7 +978,7 @@ class MainWindow(QMainWindow):
|
||||
else:
|
||||
# Unbekannter Typ oder leerer Bereich - Menü für Root-Elemente
|
||||
action_add_tree_node = QAction("Unterknoten hinzufügen", self)
|
||||
action_add_tree_node.setIcon(QIcon(QIcon.fromTheme(u"folder-new")))
|
||||
action_add_tree_node.setIcon(QIcon(QIcon.fromTheme("folder-new")))
|
||||
action_add_tree_node.triggered.connect(lambda: self._add_root_tree_node())
|
||||
menu.addAction(action_add_tree_node)
|
||||
|
||||
@@ -1019,14 +1030,16 @@ class MainWindow(QMainWindow):
|
||||
# Erstelle PdfProject-Objekt
|
||||
new_project = Project(
|
||||
id=new_id,
|
||||
name=project_data['name'],
|
||||
project_dir=Path(project_data['project_dir']),
|
||||
java_vm_id=project_data['java_vm_id'] if project_data['java_vm_id'] != -1 else 1,
|
||||
diff_pdf_id=project_data['diff_pdf_id'] if project_data['diff_pdf_id'] != -1 else 1,
|
||||
saxon_jar_id=project_data['saxon_jar_id'] if project_data['saxon_jar_id'] != -1 else 1,
|
||||
apache_fop_id=project_data['apache_fop_id'] if project_data['apache_fop_id'] != -1 else 1,
|
||||
xsl_dir_id=project_data['xsl_dir_id'] if project_data['xsl_dir_id'] != -1 else 1,
|
||||
postgre_sql_db_id=project_data['postgre_sql_db_id'] if project_data['postgre_sql_db_id'] != -1 else 1,
|
||||
name=project_data["name"],
|
||||
project_dir=Path(project_data["project_dir"]),
|
||||
java_vm_id=project_data["java_vm_id"] if project_data["java_vm_id"] != -1 else 1,
|
||||
diff_pdf_id=project_data["diff_pdf_id"] if project_data["diff_pdf_id"] != -1 else 1,
|
||||
saxon_jar_id=project_data["saxon_jar_id"] if project_data["saxon_jar_id"] != -1 else 1,
|
||||
apache_fop_id=project_data["apache_fop_id"] if project_data["apache_fop_id"] != -1 else 1,
|
||||
xsl_dir_id=project_data["xsl_dir_id"] if project_data["xsl_dir_id"] != -1 else 1,
|
||||
postgre_sql_db_id=project_data["postgre_sql_db_id"]
|
||||
if project_data["postgre_sql_db_id"] != -1
|
||||
else 1,
|
||||
)
|
||||
|
||||
# Erstelle Projekt-Ordnerstruktur
|
||||
@@ -1059,13 +1072,13 @@ class MainWindow(QMainWindow):
|
||||
project_dir = Path(project.project_dir)
|
||||
|
||||
# Erstelle Unterordner
|
||||
subdirs = ['xml', 'new', 'diff', 'ref', 'tmp']
|
||||
subdirs = ["xml", "new", "diff", "ref", "tmp"]
|
||||
for subdir in subdirs:
|
||||
subdir_path = project_dir / subdir
|
||||
subdir_path.mkdir(parents=True, exist_ok=True)
|
||||
print(f"Ordner erstellt: {subdir_path}")
|
||||
|
||||
project_yaml_path = project_dir / 'project.yaml'
|
||||
project_yaml_path = project_dir / "project.yaml"
|
||||
|
||||
# Erstelle Standard-Projekt-Einstellungen und speichere sie
|
||||
if not project_yaml_path.exists():
|
||||
@@ -1096,8 +1109,8 @@ class MainWindow(QMainWindow):
|
||||
"""
|
||||
page_info = self.thumbnail_to_page.get(thumbnail)
|
||||
if page_info:
|
||||
pdf_filename = page_info['pdf_filename']
|
||||
page_num = page_info['page_num']
|
||||
pdf_filename = page_info["pdf_filename"]
|
||||
page_num = page_info["page_num"]
|
||||
|
||||
print(f"Thumbnail für Seite {page_num + 1} von {pdf_filename} wurde angeklickt")
|
||||
|
||||
@@ -1164,8 +1177,11 @@ class MainWindow(QMainWindow):
|
||||
# TreeWidget leeren
|
||||
self.ui.treeWidget.clear()
|
||||
|
||||
# Lösche XML-Item-Map
|
||||
self.xml_item_map.clear()
|
||||
|
||||
# Prüfe ob pdf_project existiert und Nodes hat
|
||||
if not hasattr(self, 'pdf_project') or not self.pdf_project:
|
||||
if not hasattr(self, "pdf_project") or not self.pdf_project:
|
||||
print("Keine Projekt-Einstellungen verfügbar")
|
||||
return
|
||||
|
||||
@@ -1245,6 +1261,14 @@ class MainWindow(QMainWindow):
|
||||
|
||||
item.addChild(xml_item)
|
||||
|
||||
# Speichere XML-Item für spätere Widget-Updates (Progress Bar, Icon)
|
||||
# Key: "xml_path|xsl_id" um mehrfache Verwendung derselben XML zu unterstützen
|
||||
xml_path_str = str(xml.xml)
|
||||
xsl_id_str = "_".join(str(x) for x in node.id)
|
||||
map_key = f"{xml_path_str}|{xsl_id_str}"
|
||||
self.xml_item_map[map_key] = xml_item
|
||||
logger.debug(f"XML-Item zur Map hinzugefügt: '{map_key}'")
|
||||
|
||||
return item
|
||||
|
||||
except Exception as e:
|
||||
@@ -1255,6 +1279,144 @@ class MainWindow(QMainWindow):
|
||||
fallback_item.setText(1, str(e))
|
||||
return fallback_item
|
||||
|
||||
def _create_centered_progress_bar(self) -> tuple[QWidget, QProgressBar]:
|
||||
"""
|
||||
Erstellt eine zentrierte Progress Bar in einem Container-Widget.
|
||||
|
||||
Returns:
|
||||
tuple: (container_widget, progress_bar)
|
||||
"""
|
||||
# Container-Widget erstellen
|
||||
container = QWidget()
|
||||
layout = QHBoxLayout(container)
|
||||
layout.setContentsMargins(0, 0, 0, 0)
|
||||
layout.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
|
||||
# Progress Bar erstellen (indeterminate mode für pulsierenden Effekt)
|
||||
progress_bar = QProgressBar()
|
||||
progress_bar.setMinimum(0)
|
||||
progress_bar.setMaximum(0) # Pulsierend
|
||||
progress_bar.setMaximumWidth(80) # Kompakte Breite
|
||||
progress_bar.setMaximumHeight(16) # Kompakte Höhe
|
||||
progress_bar.setTextVisible(False)
|
||||
|
||||
layout.addWidget(progress_bar)
|
||||
|
||||
return container, progress_bar
|
||||
|
||||
def _create_centered_diff_icon(self, xml_file_path: Path) -> QWidget:
|
||||
"""
|
||||
Erstellt ein zentriertes, klickbares Icon für Diff-PDF.
|
||||
|
||||
Args:
|
||||
xml_file_path: Pfad zur XML-Datei (für Event-Handler)
|
||||
|
||||
Returns:
|
||||
QWidget: Container mit klickbarem Icon
|
||||
"""
|
||||
# Container-Widget
|
||||
container = QWidget()
|
||||
layout = QHBoxLayout(container)
|
||||
layout.setContentsMargins(0, 0, 0, 0)
|
||||
layout.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
|
||||
# Icon-Label
|
||||
icon_label = QLabel()
|
||||
icon_label.setPixmap(QIcon.fromTheme("document-preview").pixmap(16, 16))
|
||||
icon_label.setCursor(QCursor(Qt.CursorShape.PointingHandCursor))
|
||||
icon_label.setToolTip("Diff-PDF öffnen (Doppelklick)")
|
||||
|
||||
# Klick-Event für Icon (Doppelklick öffnet PDF)
|
||||
icon_label.mouseDoubleClickEvent = lambda event: self._open_diff_pdf(xml_file_path)
|
||||
|
||||
layout.addWidget(icon_label)
|
||||
|
||||
return container
|
||||
|
||||
def _open_diff_pdf(self, xml_file_path: Path):
|
||||
"""
|
||||
Öffnet die Diff-PDF für eine XML-Datei mit dem Standard-PDF-Viewer.
|
||||
|
||||
Args:
|
||||
xml_file_path: Pfad zur XML-Datei (relativ)
|
||||
"""
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
try:
|
||||
# Ermittle Diff-PDF-Pfad basierend auf XML-Datei
|
||||
# WICHTIG: Berücksichtige XSL-ID im Dateinamen!
|
||||
# Vereinfachung: Suche nach allen PDFs die mit xml_stem beginnen
|
||||
xml_stem = xml_file_path.stem
|
||||
diff_dir = self.project.project_dir / "diff"
|
||||
|
||||
# Finde passende Diff-PDF (könnte mehrere geben bei verschiedenen XSL-IDs)
|
||||
matching_pdfs = list(diff_dir.glob(f"{xml_stem}*.pdf"))
|
||||
|
||||
if not matching_pdfs:
|
||||
QMessageBox.information(self, "Keine Diff-PDF", f"Keine Diff-PDF für {xml_file_path.name} gefunden")
|
||||
return
|
||||
|
||||
# Bei mehreren: Nehme neueste
|
||||
diff_pdf = max(matching_pdfs, key=lambda p: p.stat().st_mtime)
|
||||
|
||||
logger.info(f"Öffne Diff-PDF: {diff_pdf}")
|
||||
|
||||
# Öffne PDF mit Plattform-spezifischem Befehl
|
||||
if sys.platform == "win32":
|
||||
subprocess.Popen(["start", str(diff_pdf)], shell=True)
|
||||
elif sys.platform == "darwin":
|
||||
subprocess.Popen(["open", str(diff_pdf)])
|
||||
else:
|
||||
subprocess.Popen(["xdg-open", str(diff_pdf)])
|
||||
|
||||
logger.info(f"Diff-PDF geöffnet: {diff_pdf}")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Fehler beim Öffnen der Diff-PDF: {e}")
|
||||
QMessageBox.critical(self, "Fehler", f"Konnte Diff-PDF nicht öffnen: {str(e)}")
|
||||
|
||||
def _update_diff_icons_for_existing_pdfs(self):
|
||||
"""
|
||||
Durchläuft alle XML-Items und setzt Icons für bereits existierende Diff-PDFs.
|
||||
Wird nach dem Laden eines Projekts aufgerufen.
|
||||
"""
|
||||
if not hasattr(self, "project") or not self.project:
|
||||
logger.debug("Kein Projekt geladen, überspringe Diff-Icon-Update")
|
||||
return
|
||||
|
||||
diff_dir = self.project.project_dir / "diff"
|
||||
if not diff_dir.exists():
|
||||
logger.debug(f"Diff-Ordner existiert nicht: {diff_dir}")
|
||||
return
|
||||
|
||||
logger.info("Aktualisiere Diff-Icons für existierende PDFs...")
|
||||
logger.info(f"XML-Item-Map hat {len(self.xml_item_map)} Einträge")
|
||||
|
||||
# Durchlaufe alle XML-Items in der Map
|
||||
icon_count = 0
|
||||
for map_key, tree_item in self.xml_item_map.items():
|
||||
# Map-Key hat Format "xml_path|xsl_id"
|
||||
parts = map_key.split("|")
|
||||
if len(parts) != 2:
|
||||
continue
|
||||
|
||||
xml_path_str, xsl_id_str = parts
|
||||
xml_path = Path(xml_path_str)
|
||||
xml_stem = xml_path.stem
|
||||
|
||||
# Diff-PDF-Dateiname: "{xml_stem}_xsl_{xsl_id_str}.pdf"
|
||||
expected_pdf = diff_dir / f"{xml_stem}_xsl_{xsl_id_str}.pdf"
|
||||
|
||||
if expected_pdf.exists():
|
||||
# Icon setzen
|
||||
icon_widget = self._create_centered_diff_icon(xml_path)
|
||||
self.ui.treeWidget.setItemWidget(tree_item, 2, icon_widget)
|
||||
icon_count += 1
|
||||
logger.debug(f"Diff-Icon für existierende PDF gesetzt: {map_key}")
|
||||
|
||||
logger.info(f"{icon_count} Diff-Icons für existierende PDFs gesetzt")
|
||||
|
||||
# Kontextmenü-Aktionen für TreeNode
|
||||
def _add_tree_node_child(self, parent_item):
|
||||
"""Fügt einen Unterknoten zu einem TreeNode hinzu."""
|
||||
@@ -1297,8 +1459,8 @@ class MainWindow(QMainWindow):
|
||||
data = dialog.get_data()
|
||||
if data:
|
||||
# Aktualisiere den Node
|
||||
node.bez = data['bez']
|
||||
node.xslt_params = data['xslt_params']
|
||||
node.bez = data["bez"]
|
||||
node.xslt_params = data["xslt_params"]
|
||||
|
||||
print(f"TreeNode '{node.bez}' wurde aktualisiert")
|
||||
print(f"XSLT-Parameter: {node.xslt_params}")
|
||||
@@ -1333,11 +1495,11 @@ class MainWindow(QMainWindow):
|
||||
|
||||
try:
|
||||
# Prüfe ob ein Projekt geladen ist
|
||||
if not hasattr(self, 'project') or not self.project:
|
||||
if not hasattr(self, "project") or not self.project:
|
||||
QMessageBox.warning(self, "Warnung", "Kein Projekt geladen. Bitte öffnen Sie zuerst ein Projekt.")
|
||||
return
|
||||
|
||||
if not hasattr(self, 'pdf_project') or not self.pdf_project:
|
||||
if not hasattr(self, "pdf_project") or not self.pdf_project:
|
||||
QMessageBox.warning(self, "Warnung", "Keine Projekt-Einstellungen geladen.")
|
||||
return
|
||||
|
||||
@@ -1349,10 +1511,7 @@ class MainWindow(QMainWindow):
|
||||
|
||||
# Öffne Datei-Dialog zum Auswählen der XML-Datei
|
||||
xml_file_path, _ = QFileDialog.getOpenFileName(
|
||||
self,
|
||||
"XML-Datei auswählen",
|
||||
"",
|
||||
"XML-Dateien (*.xml);;Alle Dateien (*)"
|
||||
self, "XML-Datei auswählen", "", "XML-Dateien (*.xml);;Alle Dateien (*)"
|
||||
)
|
||||
|
||||
if not xml_file_path:
|
||||
@@ -1381,7 +1540,7 @@ class MainWindow(QMainWindow):
|
||||
f"Eine XML-Datei mit dem Namen '{xml_file_path.name}' existiert bereits im xml-Ordner.\n\n"
|
||||
"Möchten Sie sie überschreiben?",
|
||||
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
|
||||
QMessageBox.StandardButton.No
|
||||
QMessageBox.StandardButton.No,
|
||||
)
|
||||
|
||||
if reply != QMessageBox.StandardButton.Yes:
|
||||
@@ -1405,7 +1564,7 @@ class MainWindow(QMainWindow):
|
||||
QMessageBox.information(
|
||||
self,
|
||||
"XML-Datei bereits vorhanden",
|
||||
f"Die XML-Datei '{xml_file_path.name}' ist bereits in dieser XSL-Datei enthalten."
|
||||
f"Die XML-Datei '{xml_file_path.name}' ist bereits in dieser XSL-Datei enthalten.",
|
||||
)
|
||||
return
|
||||
|
||||
@@ -1461,8 +1620,8 @@ class MainWindow(QMainWindow):
|
||||
data = dialog.get_data()
|
||||
if data:
|
||||
# Aktualisiere den Node
|
||||
node.bez = data['bez']
|
||||
node.xslt_params = data['xslt_params']
|
||||
node.bez = data["bez"]
|
||||
node.xslt_params = data["xslt_params"]
|
||||
|
||||
print(f"XslFile '{node.bez}' wurde aktualisiert")
|
||||
print(f"XSLT-Parameter: {node.xslt_params}")
|
||||
@@ -1502,11 +1661,11 @@ class MainWindow(QMainWindow):
|
||||
|
||||
try:
|
||||
# Prüfe ob ein Projekt geladen ist
|
||||
if not hasattr(self, 'project') or not self.project:
|
||||
if not hasattr(self, "project") or not self.project:
|
||||
QMessageBox.warning(self, "Warnung", "Kein Projekt geladen. Bitte öffnen Sie zuerst ein Projekt.")
|
||||
return
|
||||
|
||||
if not hasattr(self, 'pdf_project') or not self.pdf_project:
|
||||
if not hasattr(self, "pdf_project") or not self.pdf_project:
|
||||
QMessageBox.warning(self, "Warnung", "Keine Projekt-Einstellungen geladen.")
|
||||
return
|
||||
|
||||
@@ -1536,7 +1695,7 @@ class MainWindow(QMainWindow):
|
||||
f"Möchten Sie die XML-Datei '{xml_filename}' aus der XSL-Datei '{xsl_file_obj.bez}' entfernen?\n\n"
|
||||
"Die XML-Datei wird nur aus der Zuordnung entfernt, nicht physisch gelöscht.",
|
||||
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
|
||||
QMessageBox.StandardButton.No
|
||||
QMessageBox.StandardButton.No,
|
||||
)
|
||||
|
||||
if reply != QMessageBox.StandardButton.Yes:
|
||||
@@ -1567,7 +1726,7 @@ class MainWindow(QMainWindow):
|
||||
f"Die XML-Datei '{xml_filename}' wird in keiner anderen XSL-Datei verwendet.\n\n"
|
||||
"Möchten Sie auch die physische Datei aus dem xml-Ordner löschen?",
|
||||
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
|
||||
QMessageBox.StandardButton.No
|
||||
QMessageBox.StandardButton.No,
|
||||
)
|
||||
|
||||
if delete_reply == QMessageBox.StandardButton.Yes:
|
||||
@@ -1575,13 +1734,11 @@ class MainWindow(QMainWindow):
|
||||
xml_file_path.unlink()
|
||||
print(f"Physische XML-Datei gelöscht: {xml_file_path}")
|
||||
except Exception as e:
|
||||
QMessageBox.warning(
|
||||
self,
|
||||
"Warnung",
|
||||
f"Fehler beim Löschen der physischen Datei:\n{str(e)}"
|
||||
)
|
||||
QMessageBox.warning(self, "Warnung", f"Fehler beim Löschen der physischen Datei:\n{str(e)}")
|
||||
else:
|
||||
print(f"XML-Datei '{xml_filename}' wird noch in anderen XSL-Dateien verwendet - physische Datei nicht gelöscht")
|
||||
print(
|
||||
f"XML-Datei '{xml_filename}' wird noch in anderen XSL-Dateien verwendet - physische Datei nicht gelöscht"
|
||||
)
|
||||
|
||||
# Speichere die aktualisierten Projekt-Einstellungen
|
||||
self._save_project_settings()
|
||||
@@ -1657,7 +1814,7 @@ class MainWindow(QMainWindow):
|
||||
|
||||
try:
|
||||
# Prüfe ob ein Projekt geladen ist
|
||||
if not hasattr(self, 'project') or not self.project:
|
||||
if not hasattr(self, "project") or not self.project:
|
||||
QMessageBox.warning(self, "Warnung", "Kein Projekt geladen. Bitte öffnen Sie zuerst ein Projekt.")
|
||||
return
|
||||
|
||||
@@ -1727,13 +1884,14 @@ class MainWindow(QMainWindow):
|
||||
QMessageBox.critical(self, "Fehler", f"SQL-Datei nicht gefunden: {sql_file_path}")
|
||||
return None
|
||||
|
||||
with open(sql_file_path, 'r', encoding='utf-8') as f:
|
||||
with open(sql_file_path, "r", encoding="utf-8") as f:
|
||||
sql_query = f.read()
|
||||
|
||||
print(f"SQL-Abfrage geladen: {len(sql_query)} Zeichen")
|
||||
|
||||
# Verbindung zur PostgreSQL-Datenbank herstellen
|
||||
connection_string = ("postgresql://"
|
||||
connection_string = (
|
||||
"postgresql://"
|
||||
f"{db_config.username}:"
|
||||
f"{db_config.password}@"
|
||||
f"{db_config.host}:"
|
||||
@@ -1744,7 +1902,9 @@ class MainWindow(QMainWindow):
|
||||
|
||||
print(f"Verbinde zu PostgreSQL: {db_config.host}:{db_config.port}/{db_config.database}")
|
||||
|
||||
df = pl.read_database_uri(sql_query, connection_string, engine='connectorx').sort(["reporttyp_bez", "report_bez", "repfile_bez"])
|
||||
df = pl.read_database_uri(sql_query, connection_string, engine="connectorx").sort(
|
||||
["reporttyp_bez", "report_bez", "repfile_bez"]
|
||||
)
|
||||
return df
|
||||
except Exception as e:
|
||||
error_msg = f"Fehler beim Ausführen der SQL-Abfrage: {str(e)}"
|
||||
@@ -1784,14 +1944,16 @@ class MainWindow(QMainWindow):
|
||||
|
||||
for r2 in r1_children.rows(named=True):
|
||||
tn_2 = TreeNode(id=(r2["reporttyp"], r2["report"]), bez=r2["report_bez"], children=[])
|
||||
r2_children = ebene_3.filter((pl.col("reporttyp") == r1["reporttyp"]) & (pl.col("report") == r2["report"]))
|
||||
r2_children = ebene_3.filter(
|
||||
(pl.col("reporttyp") == r1["reporttyp"]) & (pl.col("report") == r2["report"])
|
||||
)
|
||||
|
||||
for r3 in r2_children.rows(named=True):
|
||||
x = XslFile(
|
||||
id=(r3["reporttyp"], r3["report"], r3["repfile"]),
|
||||
bez=r3["repfile_bez"],
|
||||
xsl_file=Path(r3["xsl_datei"]),
|
||||
xmls=[]
|
||||
xmls=[],
|
||||
)
|
||||
|
||||
tn_2.children.append(x)
|
||||
@@ -1875,13 +2037,17 @@ class MainWindow(QMainWindow):
|
||||
|
||||
# Aktualisiere nur die Bezeichnung, falls sie sich geändert hat
|
||||
if existing_node.bez != new_node.bez:
|
||||
print(f"Aktualisiere Bezeichnung für Node {existing_node.id}: '{existing_node.bez}' -> '{new_node.bez}'")
|
||||
print(
|
||||
f"Aktualisiere Bezeichnung für Node {existing_node.id}: '{existing_node.bez}' -> '{new_node.bez}'"
|
||||
)
|
||||
existing_node.bez = new_node.bez
|
||||
|
||||
# Für XslFile: Aktualisiere xsl_file Pfad
|
||||
if isinstance(existing_node, XslFile) and isinstance(new_node, XslFile):
|
||||
if existing_node.xsl_file != new_node.xsl_file:
|
||||
print(f"Aktualisiere XSL-Datei für Node {existing_node.id}: '{existing_node.xsl_file}' -> '{new_node.xsl_file}'")
|
||||
print(
|
||||
f"Aktualisiere XSL-Datei für Node {existing_node.id}: '{existing_node.xsl_file}' -> '{new_node.xsl_file}'"
|
||||
)
|
||||
existing_node.xsl_file = new_node.xsl_file
|
||||
|
||||
# Rekursiv für Knoten (nur bei TreeNode)
|
||||
@@ -1918,7 +2084,7 @@ class MainWindow(QMainWindow):
|
||||
# Hole das Node-Objekt
|
||||
parent_node = current_item.data(0, Qt.ItemDataRole.UserRole)
|
||||
|
||||
if parent_node and hasattr(parent_node, 'xslt_params') and parent_node.xslt_params:
|
||||
if parent_node and hasattr(parent_node, "xslt_params") and parent_node.xslt_params:
|
||||
# Füge die Parameter des Eltern-Nodes hinzu
|
||||
# Eltern-Parameter haben niedrigere Priorität (werden überschrieben)
|
||||
for key, value in parent_node.xslt_params.items():
|
||||
@@ -1994,8 +2160,7 @@ class MainWindow(QMainWindow):
|
||||
urls = event.mimeData().urls()
|
||||
|
||||
# Prüfe ob mindestens eine XML-Datei dabei ist
|
||||
xml_files = [url.toLocalFile() for url in urls
|
||||
if url.toLocalFile().lower().endswith('.xml')]
|
||||
xml_files = [url.toLocalFile() for url in urls if url.toLocalFile().lower().endswith(".xml")]
|
||||
|
||||
if xml_files:
|
||||
event.acceptProposedAction()
|
||||
@@ -2024,8 +2189,7 @@ class MainWindow(QMainWindow):
|
||||
urls = event.mimeData().urls()
|
||||
|
||||
# Prüfe ob mindestens eine XML-Datei dabei ist
|
||||
xml_files = [url.toLocalFile() for url in urls
|
||||
if url.toLocalFile().lower().endswith('.xml')]
|
||||
xml_files = [url.toLocalFile() for url in urls if url.toLocalFile().lower().endswith(".xml")]
|
||||
|
||||
if xml_files:
|
||||
event.acceptProposedAction()
|
||||
@@ -2047,12 +2211,12 @@ class MainWindow(QMainWindow):
|
||||
"""
|
||||
try:
|
||||
# Prüfe ob ein Projekt geladen ist
|
||||
if not hasattr(self, 'project') or not self.project:
|
||||
if not hasattr(self, "project") or not self.project:
|
||||
QMessageBox.warning(self, "Warnung", "Kein Projekt geladen. Bitte öffnen Sie zuerst ein Projekt.")
|
||||
event.ignore()
|
||||
return
|
||||
|
||||
if not hasattr(self, 'pdf_project') or not self.pdf_project:
|
||||
if not hasattr(self, "pdf_project") or not self.pdf_project:
|
||||
QMessageBox.warning(self, "Warnung", "Keine Projekt-Einstellungen geladen.")
|
||||
event.ignore()
|
||||
return
|
||||
@@ -2068,7 +2232,7 @@ class MainWindow(QMainWindow):
|
||||
# Sammle alle XML-Dateien
|
||||
for url in urls:
|
||||
file_path = url.toLocalFile()
|
||||
if file_path.lower().endswith('.xml'):
|
||||
if file_path.lower().endswith(".xml"):
|
||||
xml_files.append(Path(file_path))
|
||||
|
||||
if not xml_files:
|
||||
@@ -2112,9 +2276,7 @@ class MainWindow(QMainWindow):
|
||||
|
||||
# Öffne den Dialog zur Zuordnung zu XSL-Knoten
|
||||
dialog = XmlToXslAssignDialog(
|
||||
parent=self,
|
||||
xml_file_path=xml_file_path,
|
||||
project_nodes=self.pdf_project.nodes
|
||||
parent=self, xml_file_path=xml_file_path, project_nodes=self.pdf_project.nodes
|
||||
)
|
||||
|
||||
if dialog.exec() == XmlToXslAssignDialog.DialogCode.Accepted:
|
||||
@@ -2173,7 +2335,7 @@ class MainWindow(QMainWindow):
|
||||
Startet die asynchrone Hash-Berechnung für alle XML-Dateien im Projekt.
|
||||
"""
|
||||
try:
|
||||
if not hasattr(self, 'pdf_project') or not self.pdf_project:
|
||||
if not hasattr(self, "pdf_project") or not self.pdf_project:
|
||||
logger.debug("Keine Projekt-Einstellungen verfügbar für Hash-Berechnung")
|
||||
return
|
||||
|
||||
@@ -2198,8 +2360,7 @@ class MainWindow(QMainWindow):
|
||||
|
||||
# Erstelle und starte neuen Hash-Berechnungs-Thread
|
||||
self.hash_calculator_thread = XmlHashCalculatorThread(
|
||||
project_dir=Path(self.project.project_dir),
|
||||
xml_files=xml_files
|
||||
project_dir=Path(self.project.project_dir), xml_files=xml_files
|
||||
)
|
||||
|
||||
# Verbinde Signale
|
||||
@@ -2321,7 +2482,7 @@ class MainWindow(QMainWindow):
|
||||
return
|
||||
|
||||
# Datei binär lesen und Hash berechnen
|
||||
with open(xml_file_path, 'rb') as f:
|
||||
with open(xml_file_path, "rb") as f:
|
||||
file_content = f.read()
|
||||
hash_obj = hashlib.blake2b(file_content)
|
||||
hash_hex = hash_obj.hexdigest()
|
||||
@@ -2481,7 +2642,7 @@ class MainWindow(QMainWindow):
|
||||
return None
|
||||
|
||||
# Datei binär lesen und Hash berechnen
|
||||
with open(file_path, 'rb') as f:
|
||||
with open(file_path, "rb") as f:
|
||||
file_content = f.read()
|
||||
hash_obj = hashlib.blake2b(file_content)
|
||||
hash_hex = hash_obj.hexdigest()
|
||||
@@ -2531,13 +2692,13 @@ class MainWindow(QMainWindow):
|
||||
self,
|
||||
"XML-Datei zugeordnet",
|
||||
f"Eine XML-Datei mit gleichem Inhalt war bereits im Projekt vorhanden.\n\n"
|
||||
f"Die vorhandene Datei '{existing_xml.xml.name}' wurde automatisch zu {added_count} XSL-Knoten zugeordnet."
|
||||
f"Die vorhandene Datei '{existing_xml.xml.name}' wurde automatisch zu {added_count} XSL-Knoten zugeordnet.",
|
||||
)
|
||||
else:
|
||||
QMessageBox.information(
|
||||
self,
|
||||
"Bereits zugeordnet",
|
||||
"Die XML-Datei mit gleichem Inhalt ist bereits in allen ausgewählten XSL-Knoten vorhanden."
|
||||
"Die XML-Datei mit gleichem Inhalt ist bereits in allen ausgewählten XSL-Knoten vorhanden.",
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
@@ -2620,16 +2781,20 @@ class MainWindow(QMainWindow):
|
||||
self._load_nodes_to_tree()
|
||||
|
||||
# Zeige Erfolgsmeldung
|
||||
success_msg = f"XML-Datei '{target_xml_path.name}' wurde erfolgreich zu {added_count} XSL-Knoten hinzugefügt."
|
||||
success_msg = (
|
||||
f"XML-Datei '{target_xml_path.name}' wurde erfolgreich zu {added_count} XSL-Knoten hinzugefügt."
|
||||
)
|
||||
if target_xml_path.name != xml_file_path.name:
|
||||
success_msg += f"\n\nDie Datei wurde umbenannt von '{xml_file_path.name}' zu '{target_xml_path.name}'."
|
||||
success_msg += (
|
||||
f"\n\nDie Datei wurde umbenannt von '{xml_file_path.name}' zu '{target_xml_path.name}'."
|
||||
)
|
||||
|
||||
QMessageBox.information(self, "Erfolg", success_msg)
|
||||
else:
|
||||
QMessageBox.information(
|
||||
self,
|
||||
"Information",
|
||||
f"XML-Datei '{target_xml_path.name}' war bereits in allen ausgewählten XSL-Knoten vorhanden."
|
||||
f"XML-Datei '{target_xml_path.name}' war bereits in allen ausgewählten XSL-Knoten vorhanden.",
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
@@ -2649,7 +2814,15 @@ class MainWindow(QMainWindow):
|
||||
Path|None: Ausgewählter Pfad oder None bei Abbruch
|
||||
"""
|
||||
try:
|
||||
from PySide6.QtWidgets import QDialog, QVBoxLayout, QLabel, QRadioButton, QButtonGroup, QPushButton, QHBoxLayout
|
||||
from PySide6.QtWidgets import (
|
||||
QDialog,
|
||||
QVBoxLayout,
|
||||
QLabel,
|
||||
QRadioButton,
|
||||
QButtonGroup,
|
||||
QPushButton,
|
||||
QHBoxLayout,
|
||||
)
|
||||
|
||||
dialog = QDialog(self)
|
||||
dialog.setWindowTitle("Dateiname auswählen")
|
||||
@@ -2659,8 +2832,10 @@ class MainWindow(QMainWindow):
|
||||
layout = QVBoxLayout(dialog)
|
||||
|
||||
# Erklärungstext
|
||||
info_label = QLabel(f"Eine Datei mit dem Namen '{original_name}' existiert bereits.\n\n"
|
||||
"Bitte wählen Sie einen alternativen Dateinamen:")
|
||||
info_label = QLabel(
|
||||
f"Eine Datei mit dem Namen '{original_name}' existiert bereits.\n\n"
|
||||
"Bitte wählen Sie einen alternativen Dateinamen:"
|
||||
)
|
||||
layout.addWidget(info_label)
|
||||
|
||||
# Radio-Buttons für alternative Namen
|
||||
@@ -2807,16 +2982,19 @@ class MainWindow(QMainWindow):
|
||||
# Prüfe ob alle Konfigurationen vorhanden sind
|
||||
if not all([java_vm, saxon_jar, apache_fop, diff_pdf, xsl_dir]):
|
||||
missing = []
|
||||
if not java_vm: missing.append("Java VM")
|
||||
if not saxon_jar: missing.append("Saxon JAR")
|
||||
if not apache_fop: missing.append("Apache FOP")
|
||||
if not diff_pdf: missing.append("diff-pdf")
|
||||
if not xsl_dir: missing.append("XSL-Verzeichnis")
|
||||
if not java_vm:
|
||||
missing.append("Java VM")
|
||||
if not saxon_jar:
|
||||
missing.append("Saxon JAR")
|
||||
if not apache_fop:
|
||||
missing.append("Apache FOP")
|
||||
if not diff_pdf:
|
||||
missing.append("diff-pdf")
|
||||
if not xsl_dir:
|
||||
missing.append("XSL-Verzeichnis")
|
||||
|
||||
QMessageBox.warning(
|
||||
self,
|
||||
"Fehlende Konfiguration",
|
||||
f"Folgende Konfigurationen fehlen: {', '.join(missing)}"
|
||||
self, "Fehlende Konfiguration", f"Folgende Konfigurationen fehlen: {', '.join(missing)}"
|
||||
)
|
||||
return None
|
||||
|
||||
@@ -2839,7 +3017,7 @@ class MainWindow(QMainWindow):
|
||||
apache_fop_dir=apache_fop.path_to_dir,
|
||||
diff_pdf_path=diff_pdf.path_to_binary_file,
|
||||
diff_pdf_params=diff_pdf.default_params,
|
||||
xsl_id=xsl_file_obj.id
|
||||
xsl_id=xsl_file_obj.id,
|
||||
)
|
||||
|
||||
return job
|
||||
@@ -2882,16 +3060,37 @@ class MainWindow(QMainWindow):
|
||||
logger.error(f"Fehler beim Starten der Transformation: {e}")
|
||||
QMessageBox.critical(self, "Fehler", f"Fehler beim Starten: {str(e)}")
|
||||
|
||||
def _on_transformation_job_started(self, xml_file_name: str):
|
||||
def _on_transformation_job_started(self, xml_file_name: str, xsl_id_str: str):
|
||||
"""
|
||||
Signal-Handler: Ein Job wurde gestartet.
|
||||
|
||||
Args:
|
||||
xml_file_name: Name der XML-Datei
|
||||
xsl_id_str: XSL-ID als String (z.B. "2002_1_128")
|
||||
"""
|
||||
logger.info(f"Transformation gestartet: {xml_file_name}")
|
||||
logger.info(f"Transformation gestartet: {xml_file_name} (XSL-ID: {xsl_id_str})")
|
||||
self.statusBar().showMessage(f"Transformiere: {xml_file_name}")
|
||||
|
||||
# Progress Bar anzeigen
|
||||
map_key = f"{xml_file_name}|{xsl_id_str}"
|
||||
if map_key not in self.xml_item_map and self.xml_item_map:
|
||||
# Zeige erste Keys zur Diagnose
|
||||
sample_keys = list(self.xml_item_map.keys())[:3]
|
||||
logger.info(f"Suche TreeWidget-Item für: '{map_key}'")
|
||||
logger.info(f"Map hat {len(self.xml_item_map)} Einträge")
|
||||
tree_item = self.xml_item_map.get(map_key)
|
||||
if tree_item:
|
||||
# Entferne vorhandenes Widget (falls Icon vorhanden)
|
||||
self.ui.treeWidget.removeItemWidget(tree_item, 2)
|
||||
|
||||
# Erstelle und setze Progress Bar
|
||||
progress_widget, progress_bar = self._create_centered_progress_bar()
|
||||
self.ui.treeWidget.setItemWidget(tree_item, 2, progress_widget)
|
||||
|
||||
logger.debug(f"Progress Bar für {xml_file_name} gesetzt")
|
||||
else:
|
||||
logger.warning(f"Kein TreeWidget-Item für {xml_file_name} gefunden")
|
||||
|
||||
def _on_transformation_job_finished(self, result: dict):
|
||||
"""
|
||||
Signal-Handler: Ein Job wurde abgeschlossen.
|
||||
@@ -2921,22 +3120,49 @@ class MainWindow(QMainWindow):
|
||||
|
||||
error_text = "\n".join(error_msgs) if error_msgs else "Unbekannter Fehler"
|
||||
QMessageBox.critical(
|
||||
self,
|
||||
"Transformation fehlgeschlagen",
|
||||
f"XML-Datei: {xml_file}\n\nFehler:\n{error_text}"
|
||||
self, "Transformation fehlgeschlagen", f"XML-Datei: {xml_file}\n\nFehler:\n{error_text}"
|
||||
)
|
||||
|
||||
def _on_transformation_job_error(self, xml_file_name: str, error_message: str):
|
||||
# Update Widget in Spalte 2: Entferne Progress Bar, zeige Icon wenn Diff-PDF existiert
|
||||
xml_file_str = result.get("xml_file", "")
|
||||
xsl_id = result.get("xsl_id", None)
|
||||
xsl_id_str = "_".join(str(x) for x in xsl_id) if xsl_id else ""
|
||||
map_key = f"{xml_file_str}|{xsl_id_str}"
|
||||
diff_pdf_str = result.get("diff_pdf", None)
|
||||
tree_item = self.xml_item_map.get(map_key)
|
||||
|
||||
if tree_item:
|
||||
# Entferne Progress Bar
|
||||
self.ui.treeWidget.removeItemWidget(tree_item, 2)
|
||||
|
||||
# Wenn Diff-PDF existiert, zeige Icon
|
||||
if diff_pdf_str and Path(diff_pdf_str).exists():
|
||||
xml_file_path = Path(xml_file_str)
|
||||
icon_widget = self._create_centered_diff_icon(xml_file_path)
|
||||
self.ui.treeWidget.setItemWidget(tree_item, 2, icon_widget)
|
||||
logger.debug(f"Diff-Icon für {xml_file_str} gesetzt")
|
||||
else:
|
||||
logger.debug(f"Keine Diff-PDF für {xml_file_str}, kein Icon gesetzt")
|
||||
|
||||
def _on_transformation_job_error(self, xml_file_name: str, xsl_id_str: str, error_message: str):
|
||||
"""
|
||||
Signal-Handler: Ein Job ist mit einem Fehler abgebrochen.
|
||||
|
||||
Args:
|
||||
xml_file_name: Name der XML-Datei
|
||||
xsl_id_str: XSL-ID als String
|
||||
error_message: Fehlermeldung
|
||||
"""
|
||||
logger.error(f"Transformation-Fehler bei {xml_file_name}: {error_message}")
|
||||
logger.error(f"Transformation-Fehler bei {xml_file_name} (XSL-ID: {xsl_id_str}): {error_message}")
|
||||
QMessageBox.critical(self, "Fehler", f"Fehler bei {xml_file_name}:\n{error_message}")
|
||||
|
||||
# Entferne Progress Bar bei Fehler
|
||||
map_key = f"{xml_file_name}|{xsl_id_str}"
|
||||
tree_item = self.xml_item_map.get(map_key)
|
||||
if tree_item:
|
||||
self.ui.treeWidget.removeItemWidget(tree_item, 2)
|
||||
logger.debug(f"Progress Bar für {map_key} entfernt (Fehler)")
|
||||
|
||||
def _on_all_transformations_finished(self, successful_count: int, total_count: int):
|
||||
"""
|
||||
Signal-Handler: Alle Jobs wurden abgeschlossen.
|
||||
@@ -2950,32 +3176,36 @@ class MainWindow(QMainWindow):
|
||||
if successful_count == total_count:
|
||||
self.statusBar().showMessage(f"✓ Alle {total_count} Transformationen erfolgreich", 5000)
|
||||
QMessageBox.information(
|
||||
self,
|
||||
"Abgeschlossen",
|
||||
f"Alle {total_count} Transformationen wurden erfolgreich abgeschlossen"
|
||||
self, "Abgeschlossen", f"Alle {total_count} Transformationen wurden erfolgreich abgeschlossen"
|
||||
)
|
||||
else:
|
||||
failed_count = total_count - successful_count
|
||||
self.statusBar().showMessage(
|
||||
f"⚠ {successful_count}/{total_count} erfolgreich, {failed_count} fehlgeschlagen",
|
||||
5000
|
||||
f"⚠ {successful_count}/{total_count} erfolgreich, {failed_count} fehlgeschlagen", 5000
|
||||
)
|
||||
QMessageBox.warning(
|
||||
self,
|
||||
"Abgeschlossen mit Fehlern",
|
||||
f"{successful_count} von {total_count} Transformationen erfolgreich\n"
|
||||
f"{failed_count} fehlgeschlagen"
|
||||
f"{successful_count} von {total_count} Transformationen erfolgreich\n{failed_count} fehlgeschlagen",
|
||||
)
|
||||
|
||||
def closeEvent(self, event):
|
||||
"""Wird beim Schließen der Anwendung aufgerufen."""
|
||||
# Stoppe Hash-Berechnungs-Thread falls noch aktiv
|
||||
if hasattr(self, 'hash_calculator_thread') and self.hash_calculator_thread and self.hash_calculator_thread.isRunning():
|
||||
if (
|
||||
hasattr(self, "hash_calculator_thread")
|
||||
and self.hash_calculator_thread
|
||||
and self.hash_calculator_thread.isRunning()
|
||||
):
|
||||
self.hash_calculator_thread.quit()
|
||||
self.hash_calculator_thread.wait()
|
||||
|
||||
# Stoppe Transformations-Thread falls noch aktiv
|
||||
if hasattr(self, 'transformation_thread') and self.transformation_thread and self.transformation_thread.isRunning():
|
||||
if (
|
||||
hasattr(self, "transformation_thread")
|
||||
and self.transformation_thread
|
||||
and self.transformation_thread.isRunning()
|
||||
):
|
||||
self.transformation_thread.quit()
|
||||
self.transformation_thread.wait()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user