Feat: Interaktiver XSL-Abhängigkeitsgraph mit vis.js und THIRD_PARTY_LICENSES aktualisiert

XslDependencyDialog mit zwei Tabs: Baumansicht (vorwärts/rückwärts-Abhängigkeiten)
und interaktiver Netzwerkgraph (vis.js in QWebEngineView mit Physics-Simulation,
Hover-Tooltips, Nachbar-Hervorhebung). Graceful Fallback wenn WebEngine fehlt.
THIRD_PARTY_LICENSES um psutil, PyInstaller, Pillow, vis-network ergänzt und
Versionen aktualisiert.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-14 20:50:09 +01:00
parent 71fa48a514
commit 36911b111d
7 changed files with 965 additions and 14 deletions
+40
View File
@@ -381,6 +381,15 @@ class MainWindow(
else:
self.ui.menuProjekt.addAction(self.action_worker_metrics)
# Menü-Aktion "Abhängigkeitsgraph" zum Aktion-Menü hinzufügen
from PySide6.QtGui import QIcon as _QIcon
self.action_xsl_dependencies = QAction("XSL-Abhängigkeitsgraph", self)
self.action_xsl_dependencies.setIcon(_QIcon(_QIcon.fromTheme("view-list-tree")))
self.action_xsl_dependencies.triggered.connect(self._show_xsl_dependency_dialog)
self.ui.menuAktion.addSeparator()
self.ui.menuAktion.addAction(self.action_xsl_dependencies)
# Menü-Aktion "Aus Datenbank laden" verbinden (macht das Gleiche wie Button)
self.ui.actionAus_Datenbank_laden.triggered.connect(self.on_load_from_fn2_clicked)
@@ -402,6 +411,37 @@ class MainWindow(
except Exception as e:
logger.error(f"Fehler beim Öffnen des Einstellungen-Dialogs: {e}")
def _show_xsl_dependency_dialog(self):
"""Öffnet den XSL-Abhängigkeitsgraph-Dialog."""
try:
if not self.project:
from PySide6.QtWidgets import QMessageBox
QMessageBox.warning(self, "Fehler", "Kein Projekt geöffnet")
return
xsl_dir = next((xd for xd in app_settings.xsl_dirs if xd.id == self.project.xsl_dir_id), None)
if not xsl_dir or not xsl_dir.path_to_root_dir.exists():
from PySide6.QtWidgets import QMessageBox
QMessageBox.warning(self, "Fehler", "XSL-Verzeichnis nicht konfiguriert oder nicht vorhanden")
return
# Verwende den bestehenden Abhängigkeitsgraph (lazy init)
if not hasattr(self, "xsl_dependency_graph") or self.xsl_dependency_graph is None:
from xsl_dependencies import XslDependencyGraph
self.xsl_dependency_graph = XslDependencyGraph()
from ui.XslDependencyDialog import XslDependencyDialog
# open() statt exec() verwenden — QWebEngineView verträgt keinen verschachtelten Event-Loop
self._xsl_dep_dialog = XslDependencyDialog(self, xsl_dir.path_to_root_dir, self.xsl_dependency_graph)
self._xsl_dep_dialog.setAttribute(Qt.WidgetAttribute.WA_DeleteOnClose)
self._xsl_dep_dialog.open()
except Exception as e:
logger.error(f"Fehler beim Öffnen des Abhängigkeitsgraph-Dialogs: {e}")
def open_new_project_dialog(self):
"""Öffnet Pdf-Projekt-Dialog."""
try:
+479
View File
@@ -0,0 +1,479 @@
"""
XslDependencyDialog - Dialog zur Anzeige des XSL-Abhängigkeitsgraphen.
Zeigt alle XSL-Dateien im konfigurierten Verzeichnis mit ihren
Import/Include-Beziehungen (vorwärts und rückwärts).
Bietet zwei Ansichten: Baumansicht und interaktiver Netzwerkgraph (vis.js).
"""
import json
import logging
import tempfile
from pathlib import Path
from PySide6.QtCore import QUrl, Qt
from PySide6.QtGui import QIcon
from PySide6.QtWidgets import QDialog, QTreeWidgetItem
from ui.XslDependencyDialog_ui import Ui_XslDependencyDialog
from xsl_dependencies import XslDependencyGraph
logger = logging.getLogger(__name__)
# Prüfe ob QWebEngineView verfügbar ist
try:
from PySide6.QtWebEngineWidgets import QWebEngineView
HAS_WEBENGINE = True
except ImportError:
HAS_WEBENGINE = False
logger.warning("PySide6-WebEngine nicht verfügbar — Netzwerkgraph-Tab deaktiviert")
class XslDependencyDialog(QDialog):
"""Dialog zur Anzeige des vollständigen XSL-Abhängigkeitsgraphen."""
def __init__(self, parent, xsl_root_dir: Path, dependency_graph: XslDependencyGraph):
"""
Args:
parent: Eltern-Widget
xsl_root_dir: Wurzelverzeichnis der XSL-Dateien
dependency_graph: XSL-Abhängigkeitsgraph-Instanz
"""
super().__init__(parent)
self.xsl_root_dir = xsl_root_dir
self.dependency_graph = dependency_graph
self._web_view: "QWebEngineView | None" = None
self._graph_loaded = False
self._temp_html_file: tempfile.NamedTemporaryFile | None = None
self.ui = Ui_XslDependencyDialog()
self.ui.setupUi(self)
# Netzwerkgraph-Tab ausblenden wenn WebEngine nicht verfügbar
if not HAS_WEBENGINE:
self.ui.tabWidget.removeTab(1)
# Spalten konfigurieren
self.ui.fileTree.setHeaderLabels(["XSL-Datei", "Imports", "Importiert von"])
self.ui.fileTree.setColumnWidth(0, 300)
self.ui.fileTree.setColumnWidth(1, 60)
self.ui.fileTree.setColumnWidth(2, 60)
self.ui.depTree.setHeaderLabels(["Datei", "Typ"])
self.ui.depTree.setColumnWidth(0, 350)
# Graph aufbauen und anzeigen
self._full_graph = self.dependency_graph.build_full_graph(self.xsl_root_dir)
self._reverse_map = self._build_reverse_map()
self._populate_file_tree()
# Signale verbinden
self.ui.fileTree.currentItemChanged.connect(self._on_file_selected)
self.ui.searchEdit.textChanged.connect(self._on_search_changed)
self.ui.tabWidget.currentChanged.connect(self._on_tab_changed)
def closeEvent(self, event):
"""Räumt temporäre Dateien und QWebEngineView auf."""
if self._web_view is not None:
self._web_view.setUrl(QUrl("about:blank"))
self._web_view.deleteLater()
self._web_view = None
if self._temp_html_file is not None:
try:
Path(self._temp_html_file.name).unlink(missing_ok=True)
except Exception:
pass
self._temp_html_file = None
super().closeEvent(event)
def _build_reverse_map(self) -> dict[Path, set[Path]]:
"""Baut eine Reverse-Map auf: Welche Dateien importieren eine gegebene Datei?"""
reverse: dict[Path, set[Path]] = {}
for xsl_file, deps in self._full_graph.items():
for dep in deps:
if dep not in reverse:
reverse[dep] = set()
reverse[dep].add(xsl_file)
return reverse
def _rel_path(self, abs_path: Path) -> str:
"""Gibt den relativen Pfad zur XSL-Root zurück."""
try:
return str(abs_path.relative_to(self.xsl_root_dir))
except ValueError:
return abs_path.name
def _populate_file_tree(self):
"""Befüllt den Dateibaum mit allen XSL-Dateien und Abhängigkeitszahlen."""
self.ui.fileTree.clear()
xsl_icon = QIcon.fromTheme("text-x-generic")
for xsl_file in sorted(self._full_graph.keys(), key=lambda p: self._rel_path(p).lower()):
deps_count = len(self._full_graph.get(xsl_file, set()))
reverse_count = len(self._reverse_map.get(xsl_file, set()))
item = QTreeWidgetItem()
item.setText(0, self._rel_path(xsl_file))
item.setText(1, str(deps_count) if deps_count > 0 else "")
item.setText(2, str(reverse_count) if reverse_count > 0 else "")
item.setData(0, Qt.ItemDataRole.UserRole, xsl_file)
item.setIcon(0, xsl_icon)
# Hervorhebung für Dateien mit vielen Abhängigkeiten
if deps_count > 5 or reverse_count > 10:
font = item.font(0)
font.setBold(True)
item.setFont(0, font)
self.ui.fileTree.addTopLevelItem(item)
total = len(self._full_graph)
with_deps = sum(1 for deps in self._full_graph.values() if deps)
self.ui.statusLabel.setText(f"{total} XSL-Dateien, davon {with_deps} mit Abhängigkeiten")
def _on_file_selected(self, current: QTreeWidgetItem | None, _previous: QTreeWidgetItem | None):
"""Zeigt Abhängigkeitsdetails für die ausgewählte Datei."""
self.ui.depTree.clear()
if current is None:
self.ui.rightLabel.setText("Abhängigkeiten")
return
xsl_file: Path = current.data(0, Qt.ItemDataRole.UserRole)
if xsl_file is None:
return
rel_name = self._rel_path(xsl_file)
self.ui.rightLabel.setText(f"Abhängigkeiten: {rel_name}")
import_icon = QIcon.fromTheme("go-down")
imported_by_icon = QIcon.fromTheme("go-up")
# Sektion: "Importiert" (forward dependencies)
deps = self._full_graph.get(xsl_file, set())
if deps:
imports_root = QTreeWidgetItem()
imports_root.setText(0, f"Importiert ({len(deps)})")
imports_root.setIcon(0, import_icon)
font = imports_root.font(0)
font.setBold(True)
imports_root.setFont(0, font)
for dep in sorted(deps, key=lambda p: self._rel_path(p).lower()):
dep_item = QTreeWidgetItem()
dep_item.setText(0, self._rel_path(dep))
dep_item.setText(1, "import/include")
dep_item.setData(0, Qt.ItemDataRole.UserRole, dep)
imports_root.addChild(dep_item)
self.ui.depTree.addTopLevelItem(imports_root)
imports_root.setExpanded(True)
# Sektion: "Wird importiert von" (reverse dependencies)
reverse_deps = self._reverse_map.get(xsl_file, set())
if reverse_deps:
imported_by_root = QTreeWidgetItem()
imported_by_root.setText(0, f"Wird importiert von ({len(reverse_deps)})")
imported_by_root.setIcon(0, imported_by_icon)
font = imported_by_root.font(0)
font.setBold(True)
imported_by_root.setFont(0, font)
for rev in sorted(reverse_deps, key=lambda p: self._rel_path(p).lower()):
rev_item = QTreeWidgetItem()
rev_item.setText(0, self._rel_path(rev))
rev_item.setText(1, "importiert diese Datei")
rev_item.setData(0, Qt.ItemDataRole.UserRole, rev)
imported_by_root.addChild(rev_item)
self.ui.depTree.addTopLevelItem(imported_by_root)
imported_by_root.setExpanded(True)
if not deps and not reverse_deps:
no_deps_item = QTreeWidgetItem()
no_deps_item.setText(0, "Keine Abhängigkeiten")
self.ui.depTree.addTopLevelItem(no_deps_item)
def _on_search_changed(self, text: str):
"""Filtert die Dateiliste nach dem Suchbegriff."""
search_lower = text.lower()
for i in range(self.ui.fileTree.topLevelItemCount()):
item = self.ui.fileTree.topLevelItem(i)
matches = search_lower in item.text(0).lower()
item.setHidden(not matches)
# === Netzwerkgraph (vis.js) ===
def _on_tab_changed(self, index: int):
"""Lazy-Init des Netzwerkgraphs beim ersten Tab-Wechsel."""
if not HAS_WEBENGINE:
return
# Tab 1 = Netzwerkgraph
if index == 1 and not self._graph_loaded:
self._setup_network_graph()
def _setup_network_graph(self):
"""Erstellt QWebEngineView und lädt den vis.js Netzwerkgraph."""
self._web_view = QWebEngineView(self.ui.graphContainer)
self.ui.graphContainerLayout.addWidget(self._web_view)
# Graph-Daten aufbauen (nur direkte Abhängigkeiten für Kanten)
nodes_json, edges_json = self._build_graph_data()
html = self._generate_html(nodes_json, edges_json)
# HTML in temporäre Datei schreiben und per URL laden
# (setHtml() hat Größenlimit und kann bei großen Inhalten einfrieren)
self._temp_html_file = tempfile.NamedTemporaryFile(mode="w", suffix=".html", encoding="utf-8", delete=False)
self._temp_html_file.write(html)
self._temp_html_file.close()
self._web_view.setUrl(QUrl.fromLocalFile(self._temp_html_file.name))
self._graph_loaded = True
logger.info("Netzwerkgraph geladen")
def _build_graph_data(self) -> tuple[str, str]:
"""
Konvertiert den Abhängigkeitsgraph in vis.js-kompatible JSON-Strukturen.
Returns:
tuple[str, str]: (nodes_json, edges_json)
"""
# Direkten Graph verwenden (nicht transitiv)
direct_graph = self.dependency_graph.build_direct_graph(self.xsl_root_dir)
# Pfad → ID Mapping
all_paths = sorted(direct_graph.keys(), key=lambda p: self._rel_path(p).lower())
path_to_id: dict[Path, int] = {path: idx for idx, path in enumerate(all_paths)}
# Nodes
nodes = []
for path, node_id in path_to_id.items():
rel = self._rel_path(path)
label = path.name
direct_deps = len(direct_graph.get(path, set()))
reverse_count = len(self._reverse_map.get(path, set()))
title = f"<b>{rel}</b><br>Importiert: {direct_deps}<br>Importiert von: {reverse_count}"
# Knotengröße basierend auf Verbindungsanzahl
value = direct_deps + reverse_count
nodes.append(
{
"id": node_id,
"label": label,
"title": title,
"value": max(value, 1),
}
)
# Edges (nur direkte Abhängigkeiten)
edges = []
for path, deps in direct_graph.items():
from_id = path_to_id.get(path)
if from_id is None:
continue
for dep in deps:
to_id = path_to_id.get(dep)
if to_id is not None:
edges.append({"from": from_id, "to": to_id})
return json.dumps(nodes, ensure_ascii=False), json.dumps(edges, ensure_ascii=False)
def _generate_html(self, nodes_json: str, edges_json: str) -> str:
"""
Generiert die HTML-Seite mit inline vis.js und Graph-Daten.
Args:
nodes_json: vis.js Nodes als JSON-String
edges_json: vis.js Edges als JSON-String
Returns:
str: Vollständige HTML-Seite
"""
# vis.js Bibliothek einlesen
vis_js_path = Path(__file__).parent.parent / "res" / "vis-network.min.js"
try:
vis_js_content = vis_js_path.read_text(encoding="utf-8")
except Exception as e:
logger.error(f"Konnte vis-network.min.js nicht laden: {e}")
return f"<html><body><h2>Fehler: vis-network.min.js nicht gefunden</h2><p>{e}</p></body></html>"
# Hintergrundfarbe aus Qt-Palette ableiten
palette = self.palette()
bg_color = palette.window().color()
text_color = palette.windowText().color()
bg_hex = bg_color.name()
text_hex = text_color.name()
return f"""<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<style>
html, body {{
margin: 0;
padding: 0;
width: 100%;
height: 100%;
overflow: hidden;
background-color: {bg_hex};
color: {text_hex};
font-family: sans-serif;
}}
#graph-container {{
width: 100%;
height: 100%;
}}
.vis-tooltip {{
background-color: {bg_hex} !important;
color: {text_hex} !important;
border: 1px solid #888 !important;
border-radius: 4px !important;
padding: 8px !important;
font-size: 13px !important;
box-shadow: 2px 2px 6px rgba(0,0,0,0.3) !important;
}}
</style>
<script type="text/javascript">
{vis_js_content}
</script>
</head>
<body>
<div id="graph-container"></div>
<script type="text/javascript">
var nodesData = {nodes_json};
var edgesData = {edges_json};
var nodes = new vis.DataSet(nodesData);
var edges = new vis.DataSet(edgesData);
var container = document.getElementById('graph-container');
var data = {{ nodes: nodes, edges: edges }};
var options = {{
nodes: {{
shape: 'dot',
scaling: {{
min: 8,
max: 28,
label: {{ enabled: true, min: 10, max: 16 }}
}},
font: {{
size: 12,
color: '{text_hex}',
strokeWidth: 2,
strokeColor: '{bg_hex}'
}},
color: {{
background: '#4a90d9',
border: '#2c5f9e',
highlight: {{ background: '#ff8c00', border: '#cc7000' }},
hover: {{ background: '#5da0e9', border: '#3a6fae' }}
}}
}},
edges: {{
arrows: {{ to: {{ enabled: true, scaleFactor: 0.5 }} }},
color: {{
color: '#888888',
highlight: '#ff8c00',
hover: '#aaaaaa',
opacity: 0.7
}},
smooth: {{ type: 'continuous' }}
}},
physics: {{
solver: 'barnesHut',
barnesHut: {{
gravitationalConstant: -3000,
centralGravity: 0.3,
springLength: 150,
springConstant: 0.04,
damping: 0.09
}},
stabilization: {{
enabled: true,
iterations: 200,
updateInterval: 25
}}
}},
interaction: {{
hover: true,
tooltipDelay: 200,
navigationButtons: true,
keyboard: true
}}
}};
var network = new vis.Network(container, data, options);
// Nachbar-Hervorhebung bei Klick
var allNodes = nodes.get();
var originalColors = {{}};
allNodes.forEach(function(node) {{
originalColors[node.id] = {{
color: node.color || options.nodes.color,
font: node.font || options.nodes.font
}};
}});
network.on("selectNode", function(params) {{
var selectedId = params.nodes[0];
var connectedNodes = network.getConnectedNodes(selectedId);
var connectedSet = new Set(connectedNodes);
connectedSet.add(selectedId);
var updates = [];
allNodes.forEach(function(node) {{
if (connectedSet.has(node.id)) {{
updates.push({{
id: node.id,
opacity: 1.0
}});
}} else {{
updates.push({{
id: node.id,
opacity: 0.15
}});
}}
}});
nodes.update(updates);
// Kanten dimmen
var allEdges = edges.get();
var connectedEdges = network.getConnectedEdges(selectedId);
var connectedEdgeSet = new Set(connectedEdges);
var edgeUpdates = [];
allEdges.forEach(function(edge) {{
if (connectedEdgeSet.has(edge.id)) {{
edgeUpdates.push({{ id: edge.id, color: {{ opacity: 1.0 }} }});
}} else {{
edgeUpdates.push({{ id: edge.id, color: {{ opacity: 0.08 }} }});
}}
}});
edges.update(edgeUpdates);
}});
network.on("deselectNode", function() {{
// Alle Knoten zurücksetzen
var updates = [];
allNodes.forEach(function(node) {{
updates.push({{
id: node.id,
opacity: 1.0
}});
}});
nodes.update(updates);
// Alle Kanten zurücksetzen
var allEdges = edges.get();
var edgeUpdates = [];
allEdges.forEach(function(edge) {{
edgeUpdates.push({{ id: edge.id, color: {{ opacity: 0.7 }} }});
}});
edges.update(edgeUpdates);
}});
</script>
</body>
</html>"""
+165
View File
@@ -0,0 +1,165 @@
# -*- coding: utf-8 -*-
################################################################################
## Form generated from reading UI file 'XslDependencyDialog.ui'
##
## Created by: Qt User Interface Compiler version 6.9.2
##
## WARNING! All changes made in this file will be lost when recompiling UI file!
################################################################################
from PySide6.QtCore import QCoreApplication, QMetaObject, Qt
from PySide6.QtWidgets import (
QAbstractItemView,
QDialogButtonBox,
QHBoxLayout,
QLabel,
QLineEdit,
QSplitter,
QTabWidget,
QTreeWidget,
QVBoxLayout,
QWidget,
)
class Ui_XslDependencyDialog(object):
def setupUi(self, XslDependencyDialog):
if not XslDependencyDialog.objectName():
XslDependencyDialog.setObjectName("XslDependencyDialog")
XslDependencyDialog.resize(1000, 700)
self.verticalLayout = QVBoxLayout(XslDependencyDialog)
self.verticalLayout.setObjectName("verticalLayout")
# TabWidget
self.tabWidget = QTabWidget(XslDependencyDialog)
self.tabWidget.setObjectName("tabWidget")
# === Tab 0: Baumansicht ===
self.treeTab = QWidget()
self.treeTab.setObjectName("treeTab")
self.treeTabLayout = QVBoxLayout(self.treeTab)
self.treeTabLayout.setObjectName("treeTabLayout")
# Suchfeld
self.searchLayout = QHBoxLayout()
self.searchLayout.setObjectName("searchLayout")
self.searchLabel = QLabel(self.treeTab)
self.searchLabel.setObjectName("searchLabel")
self.searchLayout.addWidget(self.searchLabel)
self.searchEdit = QLineEdit(self.treeTab)
self.searchEdit.setObjectName("searchEdit")
self.searchEdit.setClearButtonEnabled(True)
self.searchLayout.addWidget(self.searchEdit)
self.treeTabLayout.addLayout(self.searchLayout)
# Splitter mit zwei Bäumen
self.splitter = QSplitter(self.treeTab)
self.splitter.setObjectName("splitter")
self.splitter.setOrientation(Qt.Orientation.Horizontal)
# Linke Seite: XSL-Dateiliste
self.leftWidget = QWidget(self.splitter)
self.leftWidget.setObjectName("leftWidget")
self.leftLayout = QVBoxLayout(self.leftWidget)
self.leftLayout.setObjectName("leftLayout")
self.leftLayout.setContentsMargins(0, 0, 0, 0)
self.leftLabel = QLabel(self.leftWidget)
self.leftLabel.setObjectName("leftLabel")
self.leftLayout.addWidget(self.leftLabel)
self.fileTree = QTreeWidget(self.leftWidget)
self.fileTree.setObjectName("fileTree")
self.fileTree.setHeaderHidden(False)
self.fileTree.setRootIsDecorated(True)
self.fileTree.setSelectionMode(QAbstractItemView.SelectionMode.SingleSelection)
self.fileTree.setAlternatingRowColors(True)
self.leftLayout.addWidget(self.fileTree)
self.splitter.addWidget(self.leftWidget)
# Rechte Seite: Abhängigkeitsdetails
self.rightWidget = QWidget(self.splitter)
self.rightWidget.setObjectName("rightWidget")
self.rightLayout = QVBoxLayout(self.rightWidget)
self.rightLayout.setObjectName("rightLayout")
self.rightLayout.setContentsMargins(0, 0, 0, 0)
self.rightLabel = QLabel(self.rightWidget)
self.rightLabel.setObjectName("rightLabel")
self.rightLayout.addWidget(self.rightLabel)
self.depTree = QTreeWidget(self.rightWidget)
self.depTree.setObjectName("depTree")
self.depTree.setHeaderHidden(False)
self.depTree.setRootIsDecorated(True)
self.depTree.setAlternatingRowColors(True)
self.rightLayout.addWidget(self.depTree)
self.splitter.addWidget(self.rightWidget)
self.treeTabLayout.addWidget(self.splitter)
self.tabWidget.addTab(self.treeTab, "")
# === Tab 1: Netzwerkgraph ===
self.graphTab = QWidget()
self.graphTab.setObjectName("graphTab")
self.graphTabLayout = QVBoxLayout(self.graphTab)
self.graphTabLayout.setObjectName("graphTabLayout")
self.graphTabLayout.setContentsMargins(0, 0, 0, 0)
self.graphContainer = QWidget(self.graphTab)
self.graphContainer.setObjectName("graphContainer")
self.graphContainerLayout = QVBoxLayout(self.graphContainer)
self.graphContainerLayout.setObjectName("graphContainerLayout")
self.graphContainerLayout.setContentsMargins(0, 0, 0, 0)
self.graphTabLayout.addWidget(self.graphContainer)
self.tabWidget.addTab(self.graphTab, "")
self.verticalLayout.addWidget(self.tabWidget)
# Statuszeile
self.statusLabel = QLabel(XslDependencyDialog)
self.statusLabel.setObjectName("statusLabel")
self.verticalLayout.addWidget(self.statusLabel)
# Button-Box
self.buttonBox = QDialogButtonBox(XslDependencyDialog)
self.buttonBox.setObjectName("buttonBox")
self.buttonBox.setOrientation(Qt.Orientation.Horizontal)
self.buttonBox.setStandardButtons(QDialogButtonBox.StandardButton.Close)
self.buttonBox.setCenterButtons(True)
self.verticalLayout.addWidget(self.buttonBox)
self.retranslateUi(XslDependencyDialog)
self.buttonBox.rejected.connect(XslDependencyDialog.reject)
self.tabWidget.setCurrentIndex(0)
QMetaObject.connectSlotsByName(XslDependencyDialog)
# setupUi
def retranslateUi(self, XslDependencyDialog):
XslDependencyDialog.setWindowTitle(
QCoreApplication.translate("XslDependencyDialog", "XSL-Abhängigkeitsgraph", None)
)
self.searchLabel.setText(QCoreApplication.translate("XslDependencyDialog", "Suche:", None))
self.searchEdit.setPlaceholderText(
QCoreApplication.translate("XslDependencyDialog", "XSL-Datei filtern...", None)
)
self.leftLabel.setText(QCoreApplication.translate("XslDependencyDialog", "XSL-Dateien", None))
self.rightLabel.setText(QCoreApplication.translate("XslDependencyDialog", "Abhängigkeiten", None))
self.tabWidget.setTabText(0, QCoreApplication.translate("XslDependencyDialog", "Baumansicht", None))
self.tabWidget.setTabText(1, QCoreApplication.translate("XslDependencyDialog", "Netzwerkgraph", None))
self.statusLabel.setText("")
# retranslateUi