forked from AG_QGIS/Plugin_SN_Basis
432 lines
15 KiB
Python
432 lines
15 KiB
Python
"""
|
||
sn_basis/functions/qt_wrapper.py – zentrale Qt-Abstraktion (PyQt6 primär / PyQt5 Fallback / Mock)
|
||
"""
|
||
|
||
from typing import Optional, Type, Any, Callable
|
||
|
||
# Globale Qt-Symbole (werden dynamisch gesetzt)
|
||
QT_VERSION = 0 # 0 = Mock, 5 = PyQt5, 6 = PyQt6
|
||
YES: Optional[Any] = None
|
||
NO: Optional[Any] = None
|
||
CANCEL: Optional[Any] = None
|
||
ICON_QUESTION: Optional[Any] = None
|
||
|
||
# Qt-Klassen (werden dynamisch gesetzt)
|
||
QDockWidget: Type[Any] = object
|
||
QMessageBox: Type[Any] = object
|
||
QFileDialog: Type[Any] = object
|
||
QEventLoop: Type[Any] = object
|
||
QUrl: Type[Any] = object
|
||
QNetworkRequest: Type[Any] = object
|
||
QNetworkReply: Type[Any] = object
|
||
QCoreApplication: Type[Any] = object
|
||
QWidget: Type[Any] = object
|
||
QGridLayout: Type[Any] = object
|
||
QLabel: Type[Any] = object
|
||
QLineEdit: Type[Any] = object
|
||
QGroupBox: Type[Any] = object
|
||
QVBoxLayout: Type[Any] = object
|
||
QPushButton: Type[Any] = object
|
||
QAction: Type[Any] = object
|
||
QMenu: Type[Any] = object
|
||
QToolBar: Type[Any] = object
|
||
QActionGroup: Type[Any] = object
|
||
QTabWidget: Type[Any] = object
|
||
QToolButton: Type[Any] = object
|
||
QSizePolicy: Type[Any] = object
|
||
Qt: Type[Any] = object
|
||
QComboBox: Type[Any] = object
|
||
|
||
def exec_dialog(dialog: Any) -> Any:
|
||
"""Führt Dialog modal aus (Qt6: exec(), Qt5: exec_(), Mock: YES)"""
|
||
raise NotImplementedError("Qt nicht initialisiert")
|
||
|
||
def debug_qt_status() -> None:
|
||
"""Debug: Zeigt Qt-Status für Troubleshooting."""
|
||
print(f"🔍 QT_VERSION: {QT_VERSION}")
|
||
print(f"🔍 QMessageBox Typ: {getattr(QMessageBox, '__name__', type(QMessageBox).__name__)}")
|
||
print(f"🔍 YES Wert: {YES} (Typ: {type(YES) if YES is not None else 'None'})")
|
||
|
||
if QT_VERSION == 0:
|
||
print("❌ MOCK-MODUS AKTIV! Keine Dialoge möglich!")
|
||
elif QT_VERSION == 5:
|
||
print("✅ PyQt5 geladen (Fallback) – Dialoge sollten funktionieren!")
|
||
elif QT_VERSION == 6:
|
||
print("✅ PyQt6 geladen (primär) – Dialoge sollten funktionieren!")
|
||
else:
|
||
print("❓ Unbekannte Qt-Version!")
|
||
|
||
# --------------------------- PYQT6 PRIMÄR ---------------------------
|
||
try:
|
||
from qgis.PyQt.QtWidgets import (
|
||
QMessageBox as _QMessageBox,
|
||
QFileDialog as _QFileDialog,
|
||
QWidget as _QWidget,
|
||
QGridLayout as _QGridLayout,
|
||
QLabel as _QLabel,
|
||
QLineEdit as _QLineEdit,
|
||
QGroupBox as _QGroupBox,
|
||
QVBoxLayout as _QVBoxLayout,
|
||
QPushButton as _QPushButton,
|
||
QAction as _QAction,
|
||
QMenu as _QMenu,
|
||
QToolBar as _QToolBar,
|
||
QActionGroup as _QActionGroup,
|
||
QDockWidget as _QDockWidget,
|
||
QTabWidget as _QTabWidget,
|
||
QToolButton as _QToolButton,
|
||
QSizePolicy as _QSizePolicy,
|
||
QComboBox as _QComboBox,
|
||
)
|
||
from qgis.PyQt.QtCore import (
|
||
QEventLoop as _QEventLoop,
|
||
QUrl as _QUrl,
|
||
QCoreApplication as _QCoreApplication,
|
||
Qt as _Qt,
|
||
)
|
||
from qgis.PyQt.QtNetwork import (
|
||
QNetworkRequest as _QNetworkRequest,
|
||
QNetworkReply as _QNetworkReply,
|
||
)
|
||
|
||
# ✅ ALLE GLOBALS ZUWEISEN
|
||
QT_VERSION = 6
|
||
QMessageBox = _QMessageBox
|
||
QFileDialog = _QFileDialog
|
||
QEventLoop = _QEventLoop
|
||
QUrl = _QUrl
|
||
QNetworkRequest = _QNetworkRequest
|
||
QNetworkReply = _QNetworkReply
|
||
QCoreApplication = _QCoreApplication
|
||
Qt = _Qt
|
||
QDockWidget = _QDockWidget
|
||
QWidget = _QWidget
|
||
QGridLayout = _QGridLayout
|
||
QLabel = _QLabel
|
||
QLineEdit = _QLineEdit
|
||
QGroupBox = _QGroupBox
|
||
QVBoxLayout = _QVBoxLayout
|
||
QPushButton = _QPushButton
|
||
QAction = _QAction
|
||
QMenu = _QMenu
|
||
QToolBar = _QToolBar
|
||
QActionGroup = _QActionGroup
|
||
QTabWidget = _QTabWidget
|
||
QToolButton = _QToolButton
|
||
QSizePolicy = _QSizePolicy
|
||
QComboBox = _QComboBox
|
||
|
||
# ✅ QT6 ENUMS
|
||
YES = QMessageBox.StandardButton.Yes
|
||
NO = QMessageBox.StandardButton.No
|
||
CANCEL = QMessageBox.StandardButton.Cancel
|
||
ICON_QUESTION = QMessageBox.Icon.Question
|
||
|
||
# Qt6 Enum-Aliase
|
||
ToolButtonTextBesideIcon = Qt.ToolButtonStyle.ToolButtonTextBesideIcon
|
||
ArrowDown = Qt.ArrowType.DownArrow
|
||
ArrowRight = Qt.ArrowType.RightArrow
|
||
SizePolicyPreferred = QSizePolicy.Policy.Preferred
|
||
SizePolicyMaximum = QSizePolicy.Policy.Maximum
|
||
DockWidgetMovable = QDockWidget.DockWidgetFeature.DockWidgetMovable
|
||
DockWidgetFloatable = QDockWidget.DockWidgetFeature.DockWidgetFloatable
|
||
DockWidgetClosable = QDockWidget.DockWidgetFeature.DockWidgetClosable
|
||
DockAreaLeft = Qt.DockWidgetArea.LeftDockWidgetArea
|
||
DockAreaRight = Qt.DockWidgetArea.RightDockWidgetArea
|
||
|
||
def exec_dialog(dialog: Any) -> Any:
|
||
return dialog.exec()
|
||
|
||
print(f"✅ qt_wrapper: PyQt6 geladen (QT_VERSION={QT_VERSION})")
|
||
|
||
# --------------------------- PYQT5 FALLBACK ---------------------------
|
||
except (ImportError, AttributeError):
|
||
try:
|
||
from PyQt5.QtWidgets import (
|
||
QMessageBox as _QMessageBox,
|
||
QFileDialog as _QFileDialog,
|
||
QWidget as _QWidget,
|
||
QGridLayout as _QGridLayout,
|
||
QLabel as _QLabel,
|
||
QLineEdit as _QLineEdit,
|
||
QGroupBox as _QGroupBox,
|
||
QVBoxLayout as _QVBoxLayout,
|
||
QPushButton as _QPushButton,
|
||
QAction as _QAction,
|
||
QMenu as _QMenu,
|
||
QToolBar as _QToolBar,
|
||
QActionGroup as _QActionGroup,
|
||
QDockWidget as _QDockWidget,
|
||
QTabWidget as _QTabWidget,
|
||
QToolButton as _QToolButton,
|
||
QSizePolicy as _QSizePolicy,
|
||
QComboBox as _QComboBox,
|
||
)
|
||
from PyQt5.QtCore import (
|
||
QEventLoop as _QEventLoop,
|
||
QUrl as _QUrl,
|
||
QCoreApplication as _QCoreApplication,
|
||
Qt as _Qt,
|
||
)
|
||
from PyQt5.QtNetwork import (
|
||
QNetworkRequest as _QNetworkRequest,
|
||
QNetworkReply as _QNetworkReply,
|
||
)
|
||
|
||
# ✅ ALLE GLOBALS ZUWEISEN
|
||
QT_VERSION = 5
|
||
QMessageBox = _QMessageBox
|
||
QFileDialog = _QFileDialog
|
||
QEventLoop = _QEventLoop
|
||
QUrl = _QUrl
|
||
QNetworkRequest = _QNetworkRequest
|
||
QNetworkReply = _QNetworkReply
|
||
QCoreApplication = _QCoreApplication
|
||
Qt = _Qt
|
||
QDockWidget = _QDockWidget
|
||
QWidget = _QWidget
|
||
QGridLayout = _QGridLayout
|
||
QLabel = _QLabel
|
||
QLineEdit = _QLineEdit
|
||
QGroupBox = _QGroupBox
|
||
QVBoxLayout = _QVBoxLayout
|
||
QPushButton = _QPushButton
|
||
QAction = _QAction
|
||
QMenu = _QMenu
|
||
QToolBar = _QToolBar
|
||
QActionGroup = _QActionGroup
|
||
QTabWidget = _QTabWidget
|
||
QToolButton = _QToolButton
|
||
QSizePolicy = _QSizePolicy
|
||
QComboBox = _QComboBox
|
||
|
||
# ✅ PYQT5 ENUMS
|
||
YES = QMessageBox.Yes
|
||
NO = QMessageBox.No
|
||
CANCEL = QMessageBox.Cancel
|
||
ICON_QUESTION = QMessageBox.Question
|
||
|
||
# PyQt5 Enum-Aliase
|
||
ToolButtonTextBesideIcon = Qt.ToolButtonTextBesideIcon
|
||
ArrowDown = Qt.DownArrow
|
||
ArrowRight = Qt.RightArrow
|
||
SizePolicyPreferred = QSizePolicy.Preferred
|
||
SizePolicyMaximum = QSizePolicy.Maximum
|
||
DockWidgetMovable = QDockWidget.DockWidgetMovable
|
||
DockWidgetFloatable = QDockWidget.DockWidgetFloatable
|
||
DockWidgetClosable = QDockWidget.DockWidgetClosable
|
||
DockAreaLeft = Qt.LeftDockWidgetArea
|
||
DockAreaRight = Qt.RightDockWidgetArea
|
||
|
||
def exec_dialog(dialog: Any) -> Any:
|
||
return dialog.exec_()
|
||
|
||
print(f"✅ qt_wrapper: PyQt5 Fallback geladen (QT_VERSION={QT_VERSION})")
|
||
|
||
# --------------------------- MOCK-MODUS ---------------------------
|
||
except Exception:
|
||
QT_VERSION = 0
|
||
print("⚠️ qt_wrapper: Mock-Modus aktiviert (QT_VERSION=0)")
|
||
|
||
# Fake Enum für Bit-Operationen
|
||
class FakeEnum(int):
|
||
def __or__(self, other: Any) -> "FakeEnum":
|
||
return FakeEnum(int(self) | int(other))
|
||
|
||
YES = FakeEnum(1)
|
||
NO = FakeEnum(2)
|
||
CANCEL = FakeEnum(4)
|
||
ICON_QUESTION = FakeEnum(8)
|
||
|
||
# Im Mock-Block von qt_wrapper.py:
|
||
class _MockQMessageBox:
|
||
Yes = YES
|
||
No = NO
|
||
Cancel = CANCEL
|
||
Question = ICON_QUESTION
|
||
|
||
@classmethod
|
||
def question(cls, parent, title, message, buttons, default_button):
|
||
"""Mock: Gibt immer default_button zurück"""
|
||
print(f"🔍 Mock QMessageBox.question: '{title}' → {default_button}")
|
||
return default_button
|
||
|
||
QMessageBox = _MockQMessageBox
|
||
|
||
|
||
class _MockQFileDialog:
|
||
@staticmethod
|
||
def getOpenFileName(*args, **kwargs): return ("", "")
|
||
@staticmethod
|
||
def getSaveFileName(*args, **kwargs): return ("", "")
|
||
|
||
QFileDialog = _MockQFileDialog
|
||
|
||
class _MockQEventLoop:
|
||
def exec(self) -> int: return 0
|
||
def quit(self) -> None: pass
|
||
|
||
QEventLoop = _MockQEventLoop
|
||
|
||
class _MockQUrl(str):
|
||
def isValid(self) -> bool: return True
|
||
|
||
QUrl = _MockQUrl
|
||
|
||
class _MockQNetworkRequest:
|
||
def __init__(self, url: Any): self.url = url
|
||
|
||
QNetworkRequest = _MockQNetworkRequest
|
||
|
||
class _MockQNetworkReply:
|
||
def error(self) -> int: return 0
|
||
def errorString(self) -> str: return ""
|
||
def readAll(self) -> bytes: return b""
|
||
def deleteLater(self) -> None: pass
|
||
|
||
QNetworkReply = _MockQNetworkReply
|
||
|
||
class _MockWidget: pass
|
||
class _MockLayout:
|
||
def __init__(self, *args, **kwargs): self._widgets = []
|
||
def addWidget(self, widget): self._widgets.append(widget)
|
||
def addLayout(self, layout): pass
|
||
def addStretch(self, *args, **kwargs): pass
|
||
def setSpacing(self, *args, **kwargs): pass
|
||
def setContentsMargins(self, *args, **kwargs): pass
|
||
|
||
class _MockLabel:
|
||
def __init__(self, text: str = ""): self._text = text
|
||
class _MockLineEdit:
|
||
def __init__(self, *args, **kwargs): self._text = ""
|
||
def text(self) -> str: return self._text
|
||
def setText(self, value: str) -> None: self._text = value
|
||
|
||
class _MockButton:
|
||
def __init__(self, *args, **kwargs): self.clicked = lambda *a, **k: None
|
||
|
||
QWidget = _MockWidget
|
||
QGridLayout = _MockLayout
|
||
QLabel = _MockLabel
|
||
QLineEdit = _MockLineEdit
|
||
QGroupBox = _MockWidget
|
||
QVBoxLayout = _MockLayout
|
||
QPushButton = _MockButton
|
||
QCoreApplication = object()
|
||
|
||
class _MockQt:
|
||
ToolButtonTextBesideIcon = 0
|
||
ArrowDown = 1
|
||
ArrowRight = 2
|
||
LeftDockWidgetArea = 1
|
||
RightDockWidgetArea = 2
|
||
|
||
Qt = _MockQt()
|
||
ToolButtonTextBesideIcon = Qt.ToolButtonTextBesideIcon
|
||
ArrowDown = Qt.ArrowDown
|
||
ArrowRight = Qt.ArrowRight
|
||
DockAreaLeft = Qt.LeftDockWidgetArea
|
||
DockAreaRight = Qt.RightDockWidgetArea
|
||
|
||
class _MockQDockWidget(_MockWidget):
|
||
def __init__(self, *args, **kwargs):
|
||
self._object_name = ""
|
||
def setObjectName(self, name: str) -> None: self._object_name = name
|
||
def objectName(self) -> str: return self._object_name
|
||
def show(self) -> None: pass
|
||
def deleteLater(self) -> None: pass
|
||
|
||
QDockWidget = _MockQDockWidget
|
||
|
||
class _MockAction:
|
||
def __init__(self, *args, **kwargs):
|
||
self._checked = False
|
||
self.triggered = lambda *a, **k: None
|
||
def setToolTip(self, text: str) -> None: pass
|
||
def setCheckable(self, value: bool) -> None: pass
|
||
def setChecked(self, value: bool) -> None: self._checked = value
|
||
|
||
class _MockMenu:
|
||
def __init__(self, *args, **kwargs): self._actions = []
|
||
def addAction(self, action): self._actions.append(action)
|
||
def removeAction(self, action):
|
||
if action in self._actions: self._actions.remove(action)
|
||
def clear(self): self._actions.clear()
|
||
def menuAction(self): return self
|
||
|
||
class _MockToolBar:
|
||
def __init__(self, *args, **kwargs): self._actions = []
|
||
def setObjectName(self, name: str) -> None: pass
|
||
def addAction(self, action): self._actions.append(action)
|
||
def removeAction(self, action):
|
||
if action in self._actions: self._actions.remove(action)
|
||
def clear(self): self._actions.clear()
|
||
|
||
class _MockActionGroup:
|
||
def __init__(self, *args, **kwargs): self._actions = []
|
||
def setExclusive(self, value: bool) -> None: pass
|
||
def addAction(self, action): self._actions.append(action)
|
||
|
||
QAction = _MockAction
|
||
QMenu = _MockMenu
|
||
QToolBar = _MockToolBar
|
||
QActionGroup = _MockActionGroup
|
||
|
||
class _MockToolButton(_MockWidget):
|
||
def __init__(self, *args, **kwargs):
|
||
self._checked = False
|
||
self.toggled = lambda *a, **k: None
|
||
def setText(self, text: str) -> None: pass
|
||
def setCheckable(self, value: bool) -> None: pass
|
||
def setChecked(self, value: bool) -> None: self._checked = value
|
||
def setToolButtonStyle(self, *args, **kwargs): pass
|
||
def setArrowType(self, *args, **kwargs): pass
|
||
def setStyleSheet(self, *args, **kwargs): pass
|
||
|
||
QToolButton = _MockToolButton
|
||
|
||
class _MockQSizePolicy:
|
||
Preferred = 3
|
||
Maximum = 2
|
||
|
||
QSizePolicy = _MockQSizePolicy
|
||
SizePolicyPreferred = QSizePolicy.Preferred
|
||
SizePolicyMaximum = QSizePolicy.Maximum
|
||
DockWidgetMovable = 1
|
||
DockWidgetFloatable = 2
|
||
DockWidgetClosable = 4
|
||
|
||
class _MockTabWidget:
|
||
def __init__(self, *args, **kwargs): self._tabs = []
|
||
def addTab(self, widget, title: str): self._tabs.append((widget, title))
|
||
|
||
QTabWidget = _MockTabWidget
|
||
|
||
class _MockComboBox:
|
||
def __init__(self, parent=None):
|
||
self._items = []
|
||
self._index = -1
|
||
self.currentTextChanged = type('Signal', (), {'connect': lambda s, cb: None, 'emit': lambda s, v: None})()
|
||
def addItem(self, text: str) -> None: self._items.append(text)
|
||
def addItems(self, items): [self.addItem(it) for it in items]
|
||
def findText(self, text: str) -> int:
|
||
return self._items.index(text) if text in self._items else -1
|
||
def setCurrentIndex(self, idx: int) -> None:
|
||
if 0 <= idx < len(self._items):
|
||
self._index = idx
|
||
self.currentTextChanged.emit(self.currentText())
|
||
def setCurrentText(self, text: str) -> None:
|
||
idx = self.findText(text)
|
||
if idx >= 0: self.setCurrentIndex(idx)
|
||
def currentText(self) -> str:
|
||
return self._items[self._index] if 0 <= self._index < len(self._items) else ""
|
||
|
||
QComboBox = _MockComboBox
|
||
|
||
def exec_dialog(dialog: Any) -> Any:
|
||
return YES
|
||
|
||
# --------------------------- TEST ---------------------------
|
||
if __name__ == "__main__":
|
||
debug_qt_status()
|