diff --git trackma/ui/qt/__init__.py trackma/ui/qt/__init__.py
index 1df20c2..e1f6a33 100644
--- trackma/ui/qt/__init__.py
+++ trackma/ui/qt/__init__.py
@@ -39,10 +39,12 @@ def main():
debug = True
try:
- from PyQt6.QtWidgets import QApplication, QMessageBox
+ from trackma.ui.qt.compat import QtWidgets, exec_, QT_VERSION
+ QApplication = QtWidgets.QApplication
+ QMessageBox = QtWidgets.QMessageBox
except ImportError:
- print("Couldn't import Qt6 dependencies. "
- "Make sure you installed the PyQt6 package.")
+ print("Couldn't import Qt dependencies. "
+ "Make sure you installed PyQt4, PyQt5, or PyQt6.")
try:
from PIL import Image
@@ -53,7 +55,9 @@ def main():
app = QApplication(sys.argv)
app.setApplicationName("trackma")
- app.setDesktopFileName("trackma-qt")
+ # setDesktopFileName was introduced in Qt 5.7, not available in Qt 4
+ if QT_VERSION >= 5:
+ app.setDesktopFileName("trackma-qt")
if os.name == "nt":
import ctypes
myappid = 'trackma' + utils.VERSION
@@ -61,6 +65,22 @@ def main():
try:
# keep the variable around to prevent it from being gc'ed
main_window = MainWindow(debug)
- sys.exit(app.exec())
+ ret = exec_(app)
+
+ # Explicit cleanup to avoid PyQt4 crash on exit (especially on macOS)
+ # This helps prevent segfaults during Python finalization
+ # Note: PyQt4/SIP may still crash in Py_FinalizeEx on macOS during cleanup
+ # This is a known SIP/PyQt4 issue with object deletion order
+ if QT_VERSION == 4:
+ try:
+ # Ensure window is closed and deleted before Python cleanup
+ if main_window:
+ main_window.close()
+ del main_window
+ except:
+ pass
+
+ sys.exit(ret)
except utils.TrackmaFatal as e:
- QMessageBox.critical(None, 'Fatal Error', "{0}".format(e), QMessageBox.StandardButton.Ok)
+ from trackma.ui.qt.compat import QtCompat
+ QMessageBox.critical(None, 'Fatal Error', "{0}".format(e), QtCompat.Ok())
diff --git trackma/ui/qt/accounts.py trackma/ui/qt/accounts.py
index 22a1032..3f313d8 100644
--- trackma/ui/qt/accounts.py
+++ trackma/ui/qt/accounts.py
@@ -14,12 +14,26 @@
# along with this program. If not, see .
#
-from PyQt6 import QtCore, QtGui
-from PyQt6.QtWidgets import (QAbstractItemView, QCheckBox, QComboBox, QDialog, QDialogButtonBox, QFormLayout,
- QHBoxLayout, QHeaderView, QLabel, QLineEdit, QMessageBox, QPushButton, QTableWidget,
- QTableWidgetItem, QVBoxLayout)
+from trackma.ui.qt.compat import QtCore, QtGui, exec_
+from trackma.ui.qt.compat import QtWidgets
+QAbstractItemView = QtWidgets.QAbstractItemView
+QCheckBox = QtWidgets.QCheckBox
+QComboBox = QtWidgets.QComboBox
+QDialog = QtWidgets.QDialog
+QDialogButtonBox = QtWidgets.QDialogButtonBox
+QFormLayout = QtWidgets.QFormLayout
+QHBoxLayout = QtWidgets.QHBoxLayout
+QHeaderView = QtWidgets.QHeaderView
+QLabel = QtWidgets.QLabel
+QLineEdit = QtWidgets.QLineEdit
+QMessageBox = QtWidgets.QMessageBox
+QPushButton = QtWidgets.QPushButton
+QTableWidget = QtWidgets.QTableWidget
+QTableWidgetItem = QtWidgets.QTableWidgetItem
+QVBoxLayout = QtWidgets.QVBoxLayout
from trackma import utils
+from trackma.ui.qt.compat import QtCompat
class AccountDialog(QDialog):
@@ -40,11 +54,11 @@ class AccountDialog(QDialog):
# Create list
self.table = QTableWidget()
self.table.horizontalHeader().setHighlightSections(False)
- self.table.setSelectionMode(QAbstractItemView.SelectionMode.SingleSelection)
- self.table.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows)
- self.table.setEditTriggers(QAbstractItemView.EditTrigger.NoEditTriggers)
+ self.table.setSelectionMode(QtCompat.SingleSelection())
+ self.table.setSelectionBehavior(QtCompat.SelectRows())
+ self.table.setEditTriggers(QtCompat.NoEditTriggers())
self.table.verticalHeader().hide()
- self.table.setGridStyle(QtCore.Qt.PenStyle.NoPen)
+ self.table.setGridStyle(QtCompat.NoPen())
self.table.doubleClicked.connect(self.select)
self.table.itemSelectionChanged.connect(self.update_selection)
@@ -61,11 +75,11 @@ class AccountDialog(QDialog):
self.edit_btns.addItem('Delete')
self.edit_btns.addItem('Purge')
self.edit_btns.setItemData(
- 1, 'Change the local password/PIN for this account', QtCore.Qt.ItemDataRole.ToolTipRole)
+ 1, 'Change the local password/PIN for this account', QtCompat.ToolTipRole())
self.edit_btns.setItemData(
- 2, 'Remove this account from Trackma', QtCore.Qt.ItemDataRole.ToolTipRole)
+ 2, 'Remove this account from Trackma', QtCompat.ToolTipRole())
self.edit_btns.setItemData(
- 3, 'Clear local DB for this account', QtCore.Qt.ItemDataRole.ToolTipRole)
+ 3, 'Clear local DB for this account', QtCompat.ToolTipRole())
self.edit_btns.setCurrentIndex(0)
self.edit_btns.blockSignals(False)
self.edit_btns.activated.connect(self.s_edit)
@@ -137,17 +151,17 @@ class AccountDialog(QDialog):
def delete(self):
reply = QMessageBox.question(
- self, 'Confirmation', 'Do you want to delete the selected account?', QMessageBox.StandardButton.Yes, QMessageBox.StandardButton.No)
+ self, 'Confirmation', 'Do you want to delete the selected account?', QtCompat.Yes(), QtCompat.No())
- if reply == QMessageBox.StandardButton.Yes:
+ if reply == QtCompat.Yes():
self.accountman.delete_account(self.selected_account_num)
self.rebuild()
def purge(self):
reply = QMessageBox.question(
- self, 'Confirmation', 'Do you want to purge the selected account\'s local data?', QMessageBox.StandardButton.Yes, QMessageBox.StandardButton.No)
+ self, 'Confirmation', 'Do you want to purge the selected account\'s local data?', QtCompat.Yes(), QtCompat.No())
- if reply == QMessageBox.StandardButton.Yes:
+ if reply == QtCompat.Yes():
self.accountman.purge_account(self.selected_account_num)
self.rebuild()
@@ -167,7 +181,7 @@ class AccountDialog(QDialog):
self.table.setItem(i, 1, AccountItem(
k, account['api'], self.icons.get(account['api'])))
- self.table.horizontalHeader().setSectionResizeMode(0, QHeaderView.ResizeMode.Stretch)
+ self.table.horizontalHeader().setSectionResizeMode(0, QtCompat.Stretch())
def select(self, checked):
if not self.selected_account_num:
@@ -182,7 +196,7 @@ class AccountDialog(QDialog):
self.close()
def _error(self, msg):
- QMessageBox.critical(self, 'Error', str(msg), QMessageBox.StandardButton.Ok)
+ QMessageBox.critical(self, 'Error', str(msg), QtCompat.Ok())
class AccountItem(QTableWidgetItem):
@@ -230,8 +244,8 @@ class AccountAddDialog(QDialog):
formlayout.addRow(self.lbl_password, pin_layout)
bottombox = QDialogButtonBox()
- bottombox.addButton(QDialogButtonBox.StandardButton.Save)
- bottombox.addButton(QDialogButtonBox.StandardButton.Cancel)
+ bottombox.addButton(QtWidgets.QDialogButtonBox.Save)
+ bottombox.addButton(QtWidgets.QDialogButtonBox.Cancel)
bottombox.accepted.connect(self.validate)
bottombox.rejected.connect(self.reject)
@@ -242,7 +256,7 @@ class AccountAddDialog(QDialog):
if self.edit:
self.username.setEnabled(False)
self.api.setCurrentIndex(
- self.api.findData(api, QtCore.Qt.ItemDataRole.UserRole))
+ self.api.findData(api, QtCompat.UserRole()))
self.api.setEnabled(False)
# Finish layouts
@@ -281,25 +295,25 @@ class AccountAddDialog(QDialog):
if self.adding_api[2] in [utils.Login.OAUTH, utils.Login.OAUTH_PKCE]:
self.lbl_username.setText('Name:')
self.lbl_password.setText('PIN:')
- self.password.setEchoMode(QLineEdit.EchoMode.Normal)
+ self.password.setEchoMode(QtCompat.Normal())
self.api_auth.show()
self.adding_allow = False
else:
self.lbl_username.setText('Username:')
self.lbl_password.setText('Password:')
- self.password.setEchoMode(QLineEdit.EchoMode.Password)
+ self.password.setEchoMode(QtCompat.Password())
self.api_auth.hide()
self.adding_allow = True
def _error(self, msg):
- QMessageBox.critical(self, 'Error', msg, QMessageBox.StandardButton.Ok)
+ QMessageBox.critical(self, 'Error', msg, QtCompat.Ok())
@staticmethod
def do(parent=None, icons=None, edit=False, username='', password='', api='', extra={}):
dialog = AccountAddDialog(parent, icons, edit, username, password, api)
- result = dialog.exec()
+ result = exec_(dialog)
- if result == QDialog.DialogCode.Accepted:
+ if result == QtWidgets.QDialog.Accepted:
currentIndex = dialog.api.currentIndex()
return (
str(dialog.username.text()),
diff --git trackma/ui/qt/add.py trackma/ui/qt/add.py
index 60f0530..10f28fa 100644
--- trackma/ui/qt/add.py
+++ trackma/ui/qt/add.py
@@ -16,13 +16,25 @@
from datetime import date
-from PyQt6 import QtCore
-from PyQt6.QtWidgets import (QComboBox, QDialog, QDialogButtonBox, QHBoxLayout, QLineEdit, QMessageBox, QPushButton,
- QRadioButton, QSpinBox, QSplitter, QStackedWidget, QVBoxLayout)
+from trackma.ui.qt.compat import QtCore
+from trackma.ui.qt.compat import QtWidgets
+QComboBox = QtWidgets.QComboBox
+QDialog = QtWidgets.QDialog
+QDialogButtonBox = QtWidgets.QDialogButtonBox
+QHBoxLayout = QtWidgets.QHBoxLayout
+QLineEdit = QtWidgets.QLineEdit
+QMessageBox = QtWidgets.QMessageBox
+QPushButton = QtWidgets.QPushButton
+QRadioButton = QtWidgets.QRadioButton
+QSpinBox = QtWidgets.QSpinBox
+QSplitter = QtWidgets.QSplitter
+QStackedWidget = QtWidgets.QStackedWidget
+QVBoxLayout = QtWidgets.QVBoxLayout
from trackma import utils
from trackma.ui.qt.details import DetailsDialog
from trackma.ui.qt.widgets import AddCardView, AddTableDetailsView
+from trackma.ui.qt.compat import QtCompat
class AddDialog(QDialog):
@@ -63,7 +75,7 @@ class AddDialog(QDialog):
top_layout.addWidget(self.search_rad)
top_layout.addWidget(self.search_txt)
else:
- top_layout.setAlignment(QtCore.Qt.AlignmentFlag.AlignRight)
+ top_layout.setAlignment(QtCompat.AlignRight())
top_layout.addWidget(self.search_btn)
@@ -91,10 +103,10 @@ class AddDialog(QDialog):
filters_layout.addWidget(self.season_combo)
filters_layout.addWidget(self.season_year)
- filters_layout.setAlignment(QtCore.Qt.AlignmentFlag.AlignLeft)
+ filters_layout.setAlignment(QtCompat.AlignLeft())
filters_layout.addWidget(QSplitter())
else:
- filters_layout.setAlignment(QtCore.Qt.AlignmentFlag.AlignRight)
+ filters_layout.setAlignment(QtCompat.AlignRight())
view_combo = QComboBox()
view_combo.addItem('Card view')
@@ -121,9 +133,9 @@ class AddDialog(QDialog):
# self.set_results([{'id': 1, 'title': 'Hola', 'image': 'https://omaera.org/icon.png'}])
bottom_buttons = QDialogButtonBox()
- bottom_buttons.addButton("Cancel", QDialogButtonBox.ButtonRole.RejectRole)
+ bottom_buttons.addButton("Cancel", QtCompat.RejectRole())
self.add_btn = bottom_buttons.addButton(
- "Add", QDialogButtonBox.ButtonRole.AcceptRole)
+ "Add", QtCompat.AcceptRole())
self.add_btn.setEnabled(False)
bottom_buttons.accepted.connect(self.s_add)
bottom_buttons.rejected.connect(self.close)
diff --git trackma/ui/qt/compat.py trackma/ui/qt/compat.py
new file mode 100644
index 0000000..e0293cb
--- /dev/null
+++ trackma/ui/qt/compat.py
@@ -0,0 +1,927 @@
+# This file is part of Trackma.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+#
+
+"""
+PyQt compatibility layer for supporting PyQt4, PyQt5, and PyQt6.
+"""
+
+try:
+ from PyQt6 import QtCore, QtGui, QtWidgets, QtNetwork
+ from PyQt6.QtCore import Qt, QVariant
+ from PyQt6.QtGui import QAction, QActionGroup
+ QT_VERSION = 6
+
+ # Enum access helpers for PyQt6
+ class QtCompat:
+ @staticmethod
+ def AlignCenter():
+ return Qt.AlignmentFlag.AlignCenter
+
+ @staticmethod
+ def AlignTop():
+ return Qt.AlignmentFlag.AlignTop
+
+ @staticmethod
+ def AlignVCenter():
+ return Qt.AlignmentFlag.AlignVCenter
+
+ @staticmethod
+ def AlignHCenter():
+ return Qt.AlignmentFlag.AlignHCenter
+
+ @staticmethod
+ def Key_Delete():
+ return Qt.Key.Key_Delete
+
+ @staticmethod
+ def AscendingOrder():
+ return Qt.SortOrder.AscendingOrder
+
+ @staticmethod
+ def CaseSensitive():
+ return Qt.CaseSensitivity.CaseSensitive
+
+ @staticmethod
+ def CaseInsensitive():
+ return Qt.CaseSensitivity.CaseInsensitive
+
+ @staticmethod
+ def Horizontal():
+ return Qt.Orientation.Horizontal
+
+ @staticmethod
+ def MiddleButton():
+ return Qt.MouseButton.MiddleButton
+
+ @staticmethod
+ def CustomContextMenu():
+ return Qt.ContextMenuPolicy.CustomContextMenu
+
+ @staticmethod
+ def NoPen():
+ return Qt.PenStyle.NoPen
+
+ @staticmethod
+ def DisplayRole():
+ return Qt.ItemDataRole.DisplayRole
+
+ @staticmethod
+ def DecorationRole():
+ return Qt.ItemDataRole.DecorationRole
+
+ @staticmethod
+ def BackgroundRole():
+ return Qt.ItemDataRole.BackgroundRole
+
+ @staticmethod
+ def TextAlignmentRole():
+ return Qt.ItemDataRole.TextAlignmentRole
+
+ @staticmethod
+ def ToolTipRole():
+ return Qt.ItemDataRole.ToolTipRole
+
+ @staticmethod
+ def EditRole():
+ return Qt.ItemDataRole.EditRole
+
+ @staticmethod
+ def UserRole():
+ return Qt.ItemDataRole.UserRole
+
+ @staticmethod
+ def ItemIsSelectable():
+ return Qt.ItemFlag.ItemIsSelectable
+
+ @staticmethod
+ def ItemIsEnabled():
+ return Qt.ItemFlag.ItemIsEnabled
+
+ @staticmethod
+ def ItemNeverHasChildren():
+ return Qt.ItemFlag.ItemNeverHasChildren
+
+ @staticmethod
+ def ItemIsEditable():
+ return Qt.ItemFlag.ItemIsEditable
+
+ @staticmethod
+ def KeepAspectRatio():
+ return Qt.AspectRatioMode.KeepAspectRatio
+
+ @staticmethod
+ def SmoothTransformation():
+ return Qt.TransformationMode.SmoothTransformation
+
+ @staticmethod
+ def ElideRight():
+ return Qt.TextElideMode.ElideRight
+
+ @staticmethod
+ def RichText():
+ return Qt.TextFormat.RichText
+
+ @staticmethod
+ def TextBrowserInteraction():
+ return Qt.TextInteractionFlag.TextBrowserInteraction
+
+ @staticmethod
+ def Light():
+ return QtGui.QPalette.ColorRole.Light
+
+ @staticmethod
+ def WA_ShowWithoutActivating():
+ return Qt.WidgetAttribute.WA_ShowWithoutActivating
+
+ @staticmethod
+ def Question():
+ return QtWidgets.QMessageBox.Icon.Question
+
+ @staticmethod
+ def Ok():
+ return QtWidgets.QMessageBox.StandardButton.Ok
+
+ @staticmethod
+ def Yes():
+ return QtWidgets.QMessageBox.StandardButton.Yes
+
+ @staticmethod
+ def No():
+ return QtWidgets.QMessageBox.StandardButton.No
+
+ @staticmethod
+ def Cancel():
+ return QtWidgets.QMessageBox.StandardButton.Cancel
+
+ @staticmethod
+ def State_On():
+ return QtWidgets.QStyle.StateFlag.State_On
+
+ @staticmethod
+ def State_NoChange():
+ return QtWidgets.QStyle.StateFlag.State_NoChange
+
+ @staticmethod
+ def State_Off():
+ return QtWidgets.QStyle.StateFlag.State_Off
+
+ @staticmethod
+ def PE_IndicatorMenuCheckMark():
+ return QtWidgets.QStyle.PrimitiveElement.PE_IndicatorMenuCheckMark
+
+ @staticmethod
+ def GlobalColor_transparent():
+ return Qt.GlobalColor.transparent
+
+ @staticmethod
+ def Stretch():
+ return QtWidgets.QHeaderView.ResizeMode.Stretch
+
+ @staticmethod
+ def Fixed():
+ return QtWidgets.QHeaderView.ResizeMode.Fixed
+
+ @staticmethod
+ def SingleSelection():
+ return QtWidgets.QAbstractItemView.SelectionMode.SingleSelection
+
+ @staticmethod
+ def SelectRows():
+ return QtWidgets.QAbstractItemView.SelectionBehavior.SelectRows
+
+ @staticmethod
+ def AllEditTriggers():
+ return QtWidgets.QAbstractItemView.EditTrigger.AllEditTriggers
+
+ @staticmethod
+ def NoEditTriggers():
+ return QtWidgets.QAbstractItemView.EditTrigger.NoEditTriggers
+
+ @staticmethod
+ def Trigger():
+ return QtWidgets.QSystemTrayIcon.ActivationReason.Trigger
+
+ @staticmethod
+ def LeftToRight():
+ return QtWidgets.QListView.Flow.LeftToRight
+
+ # PyQt6 enum compatibility wrappers
+ @staticmethod
+ def AlternateBase():
+ return QtGui.QPalette.ColorRole.AlternateBase
+
+ @staticmethod
+ def WindowText():
+ return QtGui.QPalette.ColorRole.WindowText
+
+ @staticmethod
+ def Window():
+ return QtGui.QPalette.ColorRole.Window
+
+ @staticmethod
+ def Bold():
+ return QtGui.QFont.Weight.Bold
+
+ @staticmethod
+ def Password():
+ return QtWidgets.QLineEdit.EchoMode.Password
+
+ @staticmethod
+ def Normal():
+ return QtWidgets.QLineEdit.EchoMode.Normal
+
+ @staticmethod
+ def RejectRole():
+ return QtWidgets.QDialogButtonBox.ButtonRole.RejectRole
+
+ @staticmethod
+ def AcceptRole():
+ return QtWidgets.QDialogButtonBox.ButtonRole.AcceptRole
+
+ @staticmethod
+ def CompositionMode_Overlay():
+ return QtGui.QPainter.CompositionMode.CompositionMode_Overlay
+
+ @staticmethod
+ def CompositionMode_Source():
+ return QtGui.QPainter.CompositionMode.CompositionMode_Source
+
+ @staticmethod
+ def CompositionMode_SourceOver():
+ return QtGui.QPainter.CompositionMode.CompositionMode_SourceOver
+
+ @staticmethod
+ def CompositionMode_SourceAtop():
+ return QtGui.QPainter.CompositionMode.CompositionMode_SourceAtop
+
+ @staticmethod
+ def CE_ProgressBar():
+ return QtWidgets.QStyle.ControlElement.CE_ProgressBar
+
+ @staticmethod
+ def CE_ProgressBarLabel():
+ return QtWidgets.QStyle.ControlElement.CE_ProgressBarLabel
+
+ @staticmethod
+ def TextWordWrap():
+ return Qt.TextFlag.TextWordWrap
+
+ @staticmethod
+ def State_Selected():
+ return QtWidgets.QStyle.StateFlag.State_Selected
+
+ @staticmethod
+ def AlignRight():
+ return Qt.AlignmentFlag.AlignRight
+
+ @staticmethod
+ def AlignLeft():
+ return Qt.AlignmentFlag.AlignLeft
+
+ @staticmethod
+ def NoFocus():
+ return Qt.FocusPolicy.NoFocus
+
+except ImportError:
+ try:
+ from PyQt5 import QtCore, QtGui, QtWidgets, QtNetwork
+ from PyQt5.QtCore import Qt, QVariant
+ from PyQt5.QtWidgets import QAction, QActionGroup
+ QT_VERSION = 5
+
+ # PyQt5 uses the same enum syntax as PyQt4
+ class QtCompat:
+ @staticmethod
+ def AlignCenter():
+ return Qt.AlignCenter
+
+ @staticmethod
+ def AlignTop():
+ return Qt.AlignTop
+
+ @staticmethod
+ def AlignVCenter():
+ return Qt.AlignVCenter
+
+ @staticmethod
+ def AlignHCenter():
+ return Qt.AlignHCenter
+
+ @staticmethod
+ def Key_Delete():
+ return Qt.Key_Delete
+
+ @staticmethod
+ def AscendingOrder():
+ return Qt.AscendingOrder
+
+ @staticmethod
+ def CaseSensitive():
+ return Qt.CaseSensitive
+
+ @staticmethod
+ def CaseInsensitive():
+ return Qt.CaseInsensitive
+
+ @staticmethod
+ def Horizontal():
+ return Qt.Horizontal
+
+ @staticmethod
+ def MiddleButton():
+ return Qt.MiddleButton
+
+ @staticmethod
+ def CustomContextMenu():
+ return Qt.CustomContextMenu
+
+ @staticmethod
+ def NoPen():
+ return Qt.NoPen
+
+ @staticmethod
+ def DisplayRole():
+ return Qt.DisplayRole
+
+ @staticmethod
+ def DecorationRole():
+ return Qt.DecorationRole
+
+ @staticmethod
+ def BackgroundRole():
+ return Qt.BackgroundRole
+
+ @staticmethod
+ def TextAlignmentRole():
+ return Qt.TextAlignmentRole
+
+ @staticmethod
+ def ToolTipRole():
+ return Qt.ToolTipRole
+
+ @staticmethod
+ def EditRole():
+ return Qt.EditRole
+
+ @staticmethod
+ def UserRole():
+ return Qt.UserRole
+
+ @staticmethod
+ def ItemIsSelectable():
+ return Qt.ItemIsSelectable
+
+ @staticmethod
+ def ItemIsEnabled():
+ return Qt.ItemIsEnabled
+
+ @staticmethod
+ def ItemNeverHasChildren():
+ return Qt.ItemNeverHasChildren
+
+ @staticmethod
+ def ItemIsEditable():
+ return Qt.ItemIsEditable
+
+ @staticmethod
+ def KeepAspectRatio():
+ return Qt.KeepAspectRatio
+
+ @staticmethod
+ def SmoothTransformation():
+ return Qt.SmoothTransformation
+
+ @staticmethod
+ def ElideRight():
+ return Qt.ElideRight
+
+ @staticmethod
+ def RichText():
+ return Qt.RichText
+
+ @staticmethod
+ def TextBrowserInteraction():
+ return Qt.TextBrowserInteraction
+
+ @staticmethod
+ def Light():
+ return QtGui.QPalette.Light
+
+ @staticmethod
+ def WA_ShowWithoutActivating():
+ return Qt.WA_ShowWithoutActivating
+
+ @staticmethod
+ def Question():
+ return QtWidgets.QMessageBox.Question
+
+ @staticmethod
+ def Ok():
+ return QtWidgets.QMessageBox.Ok
+
+ @staticmethod
+ def Yes():
+ return QtWidgets.QMessageBox.Yes
+
+ @staticmethod
+ def No():
+ return QtWidgets.QMessageBox.No
+
+ @staticmethod
+ def Cancel():
+ return QtWidgets.QMessageBox.Cancel
+
+ @staticmethod
+ def State_On():
+ return QtWidgets.QStyle.State_On
+
+ @staticmethod
+ def State_NoChange():
+ return QtWidgets.QStyle.State_NoChange
+
+ @staticmethod
+ def State_Off():
+ return QtWidgets.QStyle.State_Off
+
+ @staticmethod
+ def PE_IndicatorMenuCheckMark():
+ return QtWidgets.QStyle.PE_IndicatorMenuCheckMark
+
+ @staticmethod
+ def GlobalColor_transparent():
+ return Qt.transparent
+
+ @staticmethod
+ def Stretch():
+ return QtWidgets.QHeaderView.Stretch
+
+ @staticmethod
+ def Fixed():
+ return QtWidgets.QHeaderView.Fixed
+
+ @staticmethod
+ def SingleSelection():
+ return QtWidgets.QAbstractItemView.SingleSelection
+
+ @staticmethod
+ def SelectRows():
+ return QtWidgets.QAbstractItemView.SelectRows
+
+ @staticmethod
+ def AllEditTriggers():
+ return QtWidgets.QAbstractItemView.AllEditTriggers
+
+ @staticmethod
+ def NoEditTriggers():
+ return QtWidgets.QAbstractItemView.NoEditTriggers
+
+ @staticmethod
+ def Trigger():
+ return QtWidgets.QSystemTrayIcon.Trigger
+
+ @staticmethod
+ def LeftToRight():
+ return QtWidgets.QListView.LeftToRight
+
+ # PyQt6 enum compatibility wrappers
+ @staticmethod
+ def AlternateBase():
+ return QtGui.QPalette.AlternateBase
+
+ @staticmethod
+ def WindowText():
+ return QtGui.QPalette.WindowText
+
+ @staticmethod
+ def Window():
+ return QtGui.QPalette.Window
+
+ @staticmethod
+ def Bold():
+ return QtGui.QFont.Bold
+
+ @staticmethod
+ def Password():
+ return QtWidgets.QLineEdit.Password
+
+ @staticmethod
+ def Normal():
+ return QtWidgets.QLineEdit.Normal
+
+ @staticmethod
+ def RejectRole():
+ return QtWidgets.QDialogButtonBox.RejectRole
+
+ @staticmethod
+ def AcceptRole():
+ return QtWidgets.QDialogButtonBox.AcceptRole
+
+ @staticmethod
+ def CompositionMode_Overlay():
+ return QtGui.QPainter.CompositionMode_Overlay
+
+ @staticmethod
+ def CompositionMode_Source():
+ return QtGui.QPainter.CompositionMode_Source
+
+ @staticmethod
+ def CompositionMode_SourceOver():
+ return QtGui.QPainter.CompositionMode_SourceOver
+
+ @staticmethod
+ def CompositionMode_SourceAtop():
+ return QtGui.QPainter.CompositionMode_SourceAtop
+
+ @staticmethod
+ def CE_ProgressBar():
+ return QtWidgets.QStyle.CE_ProgressBar
+
+ @staticmethod
+ def CE_ProgressBarLabel():
+ return QtWidgets.QStyle.CE_ProgressBarLabel
+
+ @staticmethod
+ def TextWordWrap():
+ return Qt.TextWordWrap
+
+ @staticmethod
+ def State_Selected():
+ return QtWidgets.QStyle.State_Selected
+
+ @staticmethod
+ def AlignRight():
+ return Qt.AlignRight
+
+ @staticmethod
+ def AlignLeft():
+ return Qt.AlignLeft
+
+ @staticmethod
+ def NoFocus():
+ return Qt.NoFocus
+
+ except ImportError:
+ from PyQt4 import QtCore, QtGui, QtNetwork
+ from PyQt4.QtCore import Qt, QVariant
+ from PyQt4.QtGui import QAction, QActionGroup
+ QT_VERSION = 4
+
+ # In PyQt4, QtWidgets is part of QtGui
+ QtWidgets = QtGui
+
+ # In PyQt4, QSortFilterProxyModel is in QtGui, not QtCore
+ # Add it to QtCore for compatibility
+ QtCore.QSortFilterProxyModel = QtGui.QSortFilterProxyModel
+
+ # PyQt4 uses specific Qt wrapper types (ItemFlags, Alignment) that need proper construction
+ # When ORing flags together, we need to ensure the result is the right type
+ # Store the Qt types for use in QtCompat methods
+ _ItemFlags = QtCore.Qt.ItemFlags
+ _Alignment = QtCore.Qt.Alignment
+
+ # In PyQt4, several QHeaderView methods were renamed in Qt 5
+ # Create proper wrapper methods for compatibility
+ _original_setMovable = QtGui.QHeaderView.setMovable
+ _original_setResizeMode = QtGui.QHeaderView.setResizeMode
+
+ def setSectionsMovable(self, movable):
+ return _original_setMovable(self, movable)
+
+ def setSectionResizeMode(self, *args):
+ return _original_setResizeMode(self, *args)
+
+ QtGui.QHeaderView.setSectionsMovable = setSectionsMovable
+ QtGui.QHeaderView.setSectionResizeMode = setSectionResizeMode
+
+ # In PyQt4, QLineEdit.setClearButtonEnabled doesn't exist (introduced in Qt 5.2)
+ # Add a no-op method for compatibility
+ if not hasattr(QtGui.QLineEdit, 'setClearButtonEnabled'):
+ def setClearButtonEnabled(self, enable):
+ # No-op for Qt 4 - this feature doesn't exist
+ pass
+ QtGui.QLineEdit.setClearButtonEnabled = setClearButtonEnabled
+
+ # In PyQt4, QIcon.fromTheme doesn't properly support fallback icons
+ # Wrap it to ensure fallback works correctly
+ _original_fromTheme = QtGui.QIcon.fromTheme
+ @staticmethod
+ def fromTheme_compat(name, fallback=None):
+ icon = _original_fromTheme(name)
+ # If theme icon is null/empty and we have a fallback, use it
+ if fallback and icon.isNull():
+ return fallback
+ return icon
+ QtGui.QIcon.fromTheme = fromTheme_compat
+
+ # PyQt4 uses the same enum syntax as PyQt5
+ class QtCompat:
+ @staticmethod
+ def AlignCenter():
+ return Qt.AlignCenter
+
+ @staticmethod
+ def AlignTop():
+ return Qt.AlignTop
+
+ @staticmethod
+ def AlignVCenter():
+ return Qt.AlignVCenter
+
+ @staticmethod
+ def AlignHCenter():
+ return Qt.AlignHCenter
+
+ @staticmethod
+ def Key_Delete():
+ return Qt.Key_Delete
+
+ @staticmethod
+ def AscendingOrder():
+ return Qt.AscendingOrder
+
+ @staticmethod
+ def CaseSensitive():
+ return Qt.CaseSensitive
+
+ @staticmethod
+ def CaseInsensitive():
+ return Qt.CaseInsensitive
+
+ @staticmethod
+ def Horizontal():
+ return Qt.Horizontal
+
+ @staticmethod
+ def MiddleButton():
+ return Qt.MiddleButton
+
+ @staticmethod
+ def CustomContextMenu():
+ return Qt.CustomContextMenu
+
+ @staticmethod
+ def NoPen():
+ return Qt.NoPen
+
+ @staticmethod
+ def DisplayRole():
+ return Qt.DisplayRole
+
+ @staticmethod
+ def DecorationRole():
+ return Qt.DecorationRole
+
+ @staticmethod
+ def BackgroundRole():
+ return Qt.BackgroundRole
+
+ @staticmethod
+ def TextAlignmentRole():
+ return Qt.TextAlignmentRole
+
+ @staticmethod
+ def ToolTipRole():
+ return Qt.ToolTipRole
+
+ @staticmethod
+ def EditRole():
+ return Qt.EditRole
+
+ @staticmethod
+ def UserRole():
+ return Qt.UserRole
+
+ @staticmethod
+ def ItemIsSelectable():
+ return Qt.ItemIsSelectable
+
+ @staticmethod
+ def ItemIsEnabled():
+ return Qt.ItemIsEnabled
+
+ @staticmethod
+ def ItemNeverHasChildren():
+ # ItemNeverHasChildren was introduced in Qt 5.0, not available in Qt 4.x
+ # Return 0 (no flag) for PyQt4 compatibility
+ return 0
+
+ @staticmethod
+ def ItemIsEditable():
+ return Qt.ItemIsEditable
+
+ @staticmethod
+ def KeepAspectRatio():
+ return Qt.KeepAspectRatio
+
+ @staticmethod
+ def SmoothTransformation():
+ return Qt.SmoothTransformation
+
+ @staticmethod
+ def ElideRight():
+ return Qt.ElideRight
+
+ @staticmethod
+ def RichText():
+ return Qt.RichText
+
+ @staticmethod
+ def TextBrowserInteraction():
+ return Qt.TextBrowserInteraction
+
+ @staticmethod
+ def Light():
+ return QtGui.QPalette.Light
+
+ @staticmethod
+ def WA_ShowWithoutActivating():
+ return Qt.WA_ShowWithoutActivating
+
+ @staticmethod
+ def Question():
+ return QtGui.QMessageBox.Question
+
+ @staticmethod
+ def Ok():
+ return QtGui.QMessageBox.Ok
+
+ @staticmethod
+ def Yes():
+ return QtGui.QMessageBox.Yes
+
+ @staticmethod
+ def No():
+ return QtGui.QMessageBox.No
+
+ @staticmethod
+ def Cancel():
+ return QtGui.QMessageBox.Cancel
+
+ @staticmethod
+ def State_On():
+ return QtGui.QStyle.State_On
+
+ @staticmethod
+ def State_NoChange():
+ return QtGui.QStyle.State_NoChange
+
+ @staticmethod
+ def State_Off():
+ return QtGui.QStyle.State_Off
+
+ @staticmethod
+ def PE_IndicatorMenuCheckMark():
+ return QtGui.QStyle.PE_IndicatorMenuCheckMark
+
+ @staticmethod
+ def GlobalColor_transparent():
+ return Qt.transparent
+
+ @staticmethod
+ def Stretch():
+ return QtGui.QHeaderView.Stretch
+
+ @staticmethod
+ def Fixed():
+ return QtGui.QHeaderView.Fixed
+
+ @staticmethod
+ def SingleSelection():
+ return QtGui.QAbstractItemView.SingleSelection
+
+ @staticmethod
+ def SelectRows():
+ return QtGui.QAbstractItemView.SelectRows
+
+ @staticmethod
+ def AllEditTriggers():
+ return QtGui.QAbstractItemView.AllEditTriggers
+
+ @staticmethod
+ def NoEditTriggers():
+ return QtGui.QAbstractItemView.NoEditTriggers
+
+ @staticmethod
+ def Trigger():
+ return QtGui.QSystemTrayIcon.Trigger
+
+ @staticmethod
+ def LeftToRight():
+ return QtGui.QListView.LeftToRight
+
+ # PyQt6 enum compatibility wrappers
+ @staticmethod
+ def AlternateBase():
+ return QtGui.QPalette.AlternateBase
+
+ @staticmethod
+ def WindowText():
+ return QtGui.QPalette.WindowText
+
+ @staticmethod
+ def Window():
+ return QtGui.QPalette.Window
+
+ @staticmethod
+ def Bold():
+ return QtGui.QFont.Bold
+
+ @staticmethod
+ def Password():
+ return QtGui.QLineEdit.Password
+
+ @staticmethod
+ def Normal():
+ return QtGui.QLineEdit.Normal
+
+ @staticmethod
+ def RejectRole():
+ return QtGui.QDialogButtonBox.RejectRole
+
+ @staticmethod
+ def AcceptRole():
+ return QtGui.QDialogButtonBox.AcceptRole
+
+ @staticmethod
+ def CompositionMode_Overlay():
+ return QtGui.QPainter.CompositionMode_Overlay
+
+ @staticmethod
+ def CompositionMode_Source():
+ return QtGui.QPainter.CompositionMode_Source
+
+ @staticmethod
+ def CompositionMode_SourceOver():
+ return QtGui.QPainter.CompositionMode_SourceOver
+
+ @staticmethod
+ def CompositionMode_SourceAtop():
+ return QtGui.QPainter.CompositionMode_SourceAtop
+
+ @staticmethod
+ def CE_ProgressBar():
+ return QtGui.QStyle.CE_ProgressBar
+
+ @staticmethod
+ def CE_ProgressBarLabel():
+ return QtGui.QStyle.CE_ProgressBarLabel
+
+ @staticmethod
+ def TextWordWrap():
+ return Qt.TextWordWrap
+
+ @staticmethod
+ def State_Selected():
+ return QtGui.QStyle.State_Selected
+
+ @staticmethod
+ def AlignRight():
+ return Qt.AlignRight
+
+ @staticmethod
+ def AlignLeft():
+ return Qt.AlignLeft
+
+ @staticmethod
+ def NoFocus():
+ return Qt.NoFocus
+
+
+# Helper function for exec compatibility
+def exec_(widget):
+ """Call exec() or exec_() depending on Qt version."""
+ if QT_VERSION >= 6:
+ return widget.exec()
+ else:
+ return widget.exec_()
+
+
+# Helper functions for PyQt4 type wrapping
+# PyQt4 requires specific Qt wrapper types (ItemFlags, Alignment) instead of raw ints
+def ItemFlags(flags):
+ """Wrap item flags in the proper Qt type for PyQt4 compatibility."""
+ if QT_VERSION == 4:
+ return QtCore.Qt.ItemFlags(flags)
+ else:
+ return flags
+
+
+def Alignment(alignment):
+ """Wrap alignment in the proper Qt type for PyQt4 compatibility."""
+ if QT_VERSION == 4:
+ return QtCore.Qt.Alignment(alignment)
+ else:
+ return alignment
diff --git trackma/ui/qt/delegates.py trackma/ui/qt/delegates.py
index 527ea00..dd467b2 100644
--- trackma/ui/qt/delegates.py
+++ trackma/ui/qt/delegates.py
@@ -1,7 +1,12 @@
-from PyQt6 import QtCore, QtGui
-from PyQt6.QtWidgets import QDoubleSpinBox, QStyle, QStyleOptionProgressBar, QStyledItemDelegate
+from trackma.ui.qt.compat import QtCore, QtGui
+from trackma.ui.qt.compat import QtWidgets
+QDoubleSpinBox = QtWidgets.QDoubleSpinBox
+QStyle = QtWidgets.QStyle
+QStyleOptionProgressBar = QtWidgets.QStyleOptionProgressBar
+QStyledItemDelegate = QtWidgets.QStyledItemDelegate
from trackma.ui.qt.util import getColor
+from trackma.ui.qt.compat import QtCompat
MARGIN = 5
PADDING = 5
@@ -25,9 +30,9 @@ class AddListDelegate(QStyledItemDelegate):
# Get theme colors
palette = QtGui.QPalette()
- self.alternatebasecolor = palette.color(palette.ColorRole.AlternateBase)
- self.windowtextcolor = palette.color(palette.ColorRole.WindowText)
- self.windowcolor = palette.color(palette.ColorRole.Window)
+ self.alternatebasecolor = palette.color(QtCompat.AlternateBase())
+ self.windowtextcolor = palette.color(QtCompat.WindowText())
+ self.windowcolor = palette.color(QtCompat.Window())
super().__init__(parent)
@@ -41,11 +46,11 @@ class AddListDelegate(QStyledItemDelegate):
QtCore.QMargins(MARGIN, MARGIN, MARGIN, MARGIN)
data = index.data()
- thumb = index.data(QtCore.Qt.ItemDataRole.DecorationRole)
+ thumb = index.data(QtCompat.DecorationRole())
painter.save()
- color = index.data(QtCore.Qt.ItemDataRole.BackgroundRole)
+ color = index.data(QtCompat.BackgroundRole())
# Draw background box
painter.setPen(QtGui.QPen(self.alternatebasecolor))
@@ -55,7 +60,7 @@ class AddListDelegate(QStyledItemDelegate):
# Prepare to draw inside
baseRect = outerRect - \
QtCore.QMargins(PADDING, PADDING, PADDING, PADDING)
- painter.setPen(QtCore.Qt.PenStyle.NoPen)
+ painter.setPen(QtCompat.NoPen())
# Draw thumbnail (if any)
if thumb:
@@ -69,7 +74,7 @@ class AddListDelegate(QStyledItemDelegate):
# Set our font to bold
bfont = QtGui.QFont(self.font)
- bfont.setWeight(QtGui.QFont.Weight.Bold)
+ bfont.setWeight(QtCompat.Bold())
painter.setFont(bfont)
painter.setPen(QtGui.QPen(QtGui.QColor(10, 10, 10)))
@@ -78,7 +83,7 @@ class AddListDelegate(QStyledItemDelegate):
textRect -= QtCore.QMargins(5, 0, 5, 0)
# Draw title
- painter.drawText(textRect, QtCore.Qt.AlignmentFlag.AlignVCenter, data['title'])
+ painter.drawText(textRect, QtCompat.AlignVCenter(), data['title'])
painter.setPen(QtGui.QPen(self.windowtextcolor))
@@ -87,9 +92,9 @@ class AddListDelegate(QStyledItemDelegate):
dataRect = textRect.adjusted(75, 0, 0, 0)
textRect.translate(0, self.fh + 10)
- painter.drawText(textRect, QtCore.Qt.AlignmentFlag.AlignTop, "Date")
+ painter.drawText(textRect, QtCompat.AlignTop(), "Date")
textRect.translate(0, self.fh + 5)
- painter.drawText(textRect, QtCore.Qt.AlignmentFlag.AlignTop, "Episodes")
+ painter.drawText(textRect, QtCompat.AlignTop(), "Episodes")
# Draw data
painter.setFont(self.font)
@@ -105,10 +110,10 @@ class AddListDelegate(QStyledItemDelegate):
d_end = '?'
dataRect.translate(0, self.fh + 10)
- painter.drawText(dataRect, QtCore.Qt.AlignmentFlag.AlignTop,
+ painter.drawText(dataRect, QtCompat.AlignTop(),
"{} to {}".format(d_from, d_end))
dataRect.translate(0, self.fh + 5)
- painter.drawText(dataRect, QtCore.Qt.AlignmentFlag.AlignTop,
+ painter.drawText(dataRect, QtCompat.AlignTop(),
str(data.get('total') or '?'))
# Draw synopsis
@@ -116,12 +121,12 @@ class AddListDelegate(QStyledItemDelegate):
textRect.setBottomRight(baseRect.bottomRight())
if 'extra' in data:
- painter.drawText(textRect, QtCore.Qt.AlignmentFlag.AlignTop | QtCore.Qt.TextFlag.TextWordWrap, self._get_extra(
+ painter.drawText(textRect, QtCompat.AlignTop() | QtCompat.TextWordWrap(), self._get_extra(
data['extra'], 'Synopsis'))
# Draw select box
- if option.state & QStyle.StateFlag.State_Selected:
- painter.setCompositionMode(QtGui.QPainter.CompositionMode.CompositionMode_Overlay)
+ if option.state & QtCompat.State_Selected():
+ painter.setCompositionMode(QtCompat.CompositionMode_Overlay())
# painter.setOpacity(0.5)
painter.fillRect(outerRect, option.palette.highlight())
@@ -173,11 +178,11 @@ class ShowsTableDelegate(QStyledItemDelegate):
prog_options.rect = rect
prog_options.text = '%d%%' % (value*100/maximum)
prog_options.textVisible = self._show_text
- option.widget.style().drawControl(QStyle.ControlElement.CE_ProgressBar, prog_options, painter)
+ option.widget.style().drawControl(QtCompat.CE_ProgressBar(), prog_options, painter)
elif self._bar_style is self.BarStyle04:
painter.setBrush(getColor(self.colors['progress_bg']))
- painter.setPen(QtCore.Qt.GlobalColor.transparent)
+ painter.setPen(QtCompat.GlobalColor_transparent())
painter.drawRect(rect)
self.paintSubValue(painter, rect, subvalue, maximum)
if value > 0:
@@ -195,25 +200,25 @@ class ShowsTableDelegate(QStyledItemDelegate):
elif self._bar_style is self.BarStyleHybrid:
painter.setCompositionMode(
- QtGui.QPainter.CompositionMode.CompositionMode_Source)
- painter.fillRect(rect, QtCore.Qt.GlobalColor.transparent)
+ QtCompat.CompositionMode_Source())
+ painter.fillRect(rect, QtCompat.GlobalColor_transparent())
painter.setCompositionMode(
- QtGui.QPainter.CompositionMode.CompositionMode_SourceOver)
+ QtCompat.CompositionMode_SourceOver())
prog_options = QStyleOptionProgressBar()
prog_options.maximum = maximum
prog_options.progress = value
prog_options.rect = rect
prog_options.text = '%d%%' % (value*100/maximum)
- option.widget.style().drawControl(QStyle.ControlElement.CE_ProgressBar, prog_options, painter)
+ option.widget.style().drawControl(QtCompat.CE_ProgressBar(), prog_options, painter)
painter.setCompositionMode(
- QtGui.QPainter.CompositionMode.CompositionMode_SourceAtop)
- painter.setPen(QtCore.Qt.GlobalColor.transparent)
+ QtCompat.CompositionMode_SourceAtop())
+ painter.setPen(QtCompat.GlobalColor_transparent())
self.paintSubValue(painter, rect, subvalue, maximum)
self.paintEpisodes(painter, rect, episodes, maximum)
painter.setCompositionMode(
- QtGui.QPainter.CompositionMode.CompositionMode_SourceOver)
+ QtCompat.CompositionMode_SourceOver())
if self._show_text:
- option.widget.style().drawControl(QStyle.ControlElement.CE_ProgressBarLabel, prog_options, painter)
+ option.widget.style().drawControl(QtCompat.CE_ProgressBarLabel(), prog_options, painter)
painter.restore()
else:
@@ -261,7 +266,7 @@ class ShowsTableDelegate(QStyledItemDelegate):
def setEditorData(self, editor, index):
(value, maximum, decimals, step) = index.model().data(
- index, QtCore.Qt.ItemDataRole.EditRole)
+ index, QtCompat.EditRole())
editor.setMaximum(maximum or 999)
editor.setDecimals(decimals or 0)
@@ -272,8 +277,8 @@ class ShowsTableDelegate(QStyledItemDelegate):
def setModelData(self, editor, model, index):
editor.interpretText()
- old_value = index.model().data(index, QtCore.Qt.ItemDataRole.EditRole)[0]
+ old_value = index.model().data(index, QtCompat.EditRole())[0]
new_value = editor.value()
if new_value != old_value:
- model.setData(index, new_value, QtCore.Qt.ItemDataRole.EditRole)
+ model.setData(index, new_value, QtCompat.EditRole())
diff --git trackma/ui/qt/details.py trackma/ui/qt/details.py
index 9c52d28..1140304 100644
--- trackma/ui/qt/details.py
+++ trackma/ui/qt/details.py
@@ -14,7 +14,10 @@
# along with this program. If not, see .
#
-from PyQt6.QtWidgets import QDialog, QDialogButtonBox, QVBoxLayout
+from trackma.ui.qt.compat import QtWidgets
+QDialog = QtWidgets.QDialog
+QDialogButtonBox = QtWidgets.QDialogButtonBox
+QVBoxLayout = QtWidgets.QVBoxLayout
from trackma.ui.qt.widgets import DetailsWidget
@@ -29,7 +32,7 @@ class DetailsDialog(QDialog):
main_layout = QVBoxLayout()
details = DetailsWidget(self, worker)
- bottom_buttons = QDialogButtonBox(QDialogButtonBox.StandardButton.Close)
+ bottom_buttons = QDialogButtonBox(QtWidgets.QDialogButtonBox.Close)
bottom_buttons.setCenterButtons(True)
bottom_buttons.rejected.connect(self.close)
diff --git trackma/ui/qt/mainwindow.py trackma/ui/qt/mainwindow.py
index 210d243..866c685 100644
--- trackma/ui/qt/mainwindow.py
+++ trackma/ui/qt/mainwindow.py
@@ -17,12 +17,33 @@
import base64
import os
-from PyQt6 import QtCore, QtGui
-from PyQt6.QtGui import QAction, QActionGroup
-from PyQt6.QtWidgets import (QAbstractItemView, QApplication, QCheckBox, QComboBox,
- QDoubleSpinBox, QFormLayout, QHBoxLayout, QHeaderView, QInputDialog, QLabel, QLineEdit,
- QMainWindow, QMenu, QMessageBox, QProgressBar, QPushButton, QSpinBox, QStyle,
- QStyleOptionButton, QSystemTrayIcon, QTabBar, QToolButton, QVBoxLayout, QWidget)
+from trackma.ui.qt.compat import QtCore, QtGui, exec_, QT_VERSION
+from trackma.ui.qt.compat import QAction, QActionGroup
+from trackma.ui.qt.compat import QtWidgets
+QAbstractItemView = QtWidgets.QAbstractItemView
+QApplication = QtWidgets.QApplication
+QCheckBox = QtWidgets.QCheckBox
+QComboBox = QtWidgets.QComboBox
+QDoubleSpinBox = QtWidgets.QDoubleSpinBox
+QFormLayout = QtWidgets.QFormLayout
+QHBoxLayout = QtWidgets.QHBoxLayout
+QHeaderView = QtWidgets.QHeaderView
+QInputDialog = QtWidgets.QInputDialog
+QLabel = QtWidgets.QLabel
+QLineEdit = QtWidgets.QLineEdit
+QMainWindow = QtWidgets.QMainWindow
+QMenu = QtWidgets.QMenu
+QMessageBox = QtWidgets.QMessageBox
+QProgressBar = QtWidgets.QProgressBar
+QPushButton = QtWidgets.QPushButton
+QSpinBox = QtWidgets.QSpinBox
+QStyle = QtWidgets.QStyle
+QStyleOptionButton = QtWidgets.QStyleOptionButton
+QSystemTrayIcon = QtWidgets.QSystemTrayIcon
+QTabBar = QtWidgets.QTabBar
+QToolButton = QtWidgets.QToolButton
+QVBoxLayout = QtWidgets.QVBoxLayout
+QWidget = QtWidgets.QWidget
from trackma import messenger
from trackma import utils
@@ -34,6 +55,7 @@ from trackma.ui.qt.settings import SettingsDialog
from trackma.ui.qt.util import FilterBar, getIcon
from trackma.ui.qt.widgets import ShowsTableView
from trackma.ui.qt.workers import EngineWorker, ImageWorker
+from trackma.ui.qt.compat import QtCompat
class MainWindow(QMainWindow):
"""
@@ -148,7 +170,7 @@ class MainWindow(QMainWindow):
self.action_add.triggered.connect(self.s_add)
self.action_delete = QAction(getIcon('edit-delete'), '&Delete', self)
self.action_delete.setStatusTip('Remove this show from your list.')
- self.action_delete.setShortcut(QtCore.Qt.Key.Key_Delete)
+ self.action_delete.setShortcut(QtCompat.Key_Delete())
self.action_delete.triggered.connect(self.s_delete)
action_quit = QAction(getIcon('application-exit'), '&Quit', self)
action_quit.setShortcut('Ctrl+Q')
@@ -217,16 +239,16 @@ class MainWindow(QMainWindow):
# Make icons for viewed episodes
rect = QtCore.QSize(16, 16)
buffer = QtGui.QPixmap(rect)
- ep_icon_states = {'all': QStyle.StateFlag.State_On,
- 'part': QStyle.StateFlag.State_NoChange,
- 'none': QStyle.StateFlag.State_Off}
+ ep_icon_states = {'all': QtCompat.State_On(),
+ 'part': QtCompat.State_NoChange(),
+ 'none': QtCompat.State_Off()}
self.ep_icons = {}
for key, state in ep_icon_states.items():
- buffer.fill(QtCore.Qt.GlobalColor.transparent)
+ buffer.fill(QtCompat.GlobalColor_transparent())
painter = QtGui.QPainter(buffer)
opt = QStyleOptionButton()
opt.state = state
- self.style().drawPrimitive(QStyle.PrimitiveElement.PE_IndicatorMenuCheckMark, opt, painter)
+ self.style().drawPrimitive(QtCompat.PE_IndicatorMenuCheckMark(), opt, painter)
self.ep_icons[key] = QtGui.QIcon(buffer)
painter.end()
@@ -348,7 +370,7 @@ class MainWindow(QMainWindow):
self.show_image = QLabel('Trackma-qt')
self.show_image.setFixedHeight(149)
self.show_image.setMinimumWidth(100)
- self.show_image.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
+ self.show_image.setAlignment(QtCompat.AlignCenter())
show_progress_label = QLabel('Progress:')
self.show_progress = QSpinBox()
self.show_progress.setMinimumWidth(spinbox_width)
@@ -390,7 +412,7 @@ class MainWindow(QMainWindow):
small_btns_hbox.addWidget(self.show_dec_btn)
small_btns_hbox.addWidget(self.show_play_btn)
small_btns_hbox.addWidget(self.show_inc_btn)
- small_btns_hbox.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
+ small_btns_hbox.setAlignment(QtCompat.AlignCenter())
left_box.addRow(self.show_image)
left_box.addRow(self.show_progress_bar)
@@ -514,11 +536,11 @@ class MainWindow(QMainWindow):
def error(self, msg):
self.status('Error: {}'.format(msg))
- QMessageBox.critical(self, 'Error', str(msg), QMessageBox.StandardButton.Ok)
+ QMessageBox.critical(self, 'Error', str(msg), QtCompat.Ok())
def fatal(self, msg):
QMessageBox.critical(
- self, 'Fatal Error', "Fatal Error! Reason:\n\n{0}".format(msg), QMessageBox.StandardButton.Ok)
+ self, 'Fatal Error', "Fatal Error! Reason:\n\n{0}".format(msg), QtCompat.Ok())
self.accountman.set_default(None)
self._busy()
self.finish = False
@@ -632,9 +654,9 @@ class MainWindow(QMainWindow):
def _apply_view(self):
if self.config['inline_edit']:
- self.view.setEditTriggers(QAbstractItemView.EditTrigger.AllEditTriggers)
+ self.view.setEditTriggers(QtCompat.AllEditTriggers())
else:
- self.view.setEditTriggers(QAbstractItemView.EditTrigger.NoEditTriggers)
+ self.view.setEditTriggers(QtCompat.NoEditTriggers())
def _apply_tray(self):
if self.tray.isVisible() and not self.config['show_tray']:
@@ -766,8 +788,8 @@ class MainWindow(QMainWindow):
if column not in self.api_config['visible_columns']:
self.view.setColumnHidden(i, True)
- self.view.horizontalHeader().setSectionResizeMode(1, QHeaderView.ResizeMode.Stretch)
- self.view.horizontalHeader().setSectionResizeMode(3, QHeaderView.ResizeMode.Fixed)
+ self.view.horizontalHeader().setSectionResizeMode(1, QtCompat.Stretch())
+ self.view.horizontalHeader().setSectionResizeMode(3, QtCompat.Fixed())
# Recover column state
if self.config['remember_columns'] and isinstance(self.api_config['columns_state'], str):
@@ -819,7 +841,7 @@ class MainWindow(QMainWindow):
# Update information
metrics = QtGui.QFontMetrics(self.show_title.font())
title = metrics.elidedText(
- show['title'], QtCore.Qt.TextElideMode.ElideRight, self.show_title.width())
+ show['title'], QtCompat.ElideRight(), self.show_title.width())
self.show_title.setText(title)
self.show_progress.setValue(show['my_progress'])
@@ -993,7 +1015,7 @@ class MainWindow(QMainWindow):
self.show()
def s_tray_clicked(self, reason):
- if reason == QSystemTrayIcon.ActivationReason.Trigger:
+ if reason == QtCompat.Trigger():
self.s_hide()
def s_busy(self):
@@ -1012,7 +1034,11 @@ class MainWindow(QMainWindow):
def s_update_sort(self, index, order):
self.config['sort_index'] = index
- self.config['sort_order'] = order.value
+ # In PyQt6, enums have .value; in PyQt4/5 they are already integers
+ if QT_VERSION >= 6:
+ self.config['sort_order'] = order.value
+ else:
+ self.config['sort_order'] = int(order)
def s_download_image(self):
show = self.worker.engine.get_show_info(self.selected_show_id)
@@ -1060,9 +1086,9 @@ class MainWindow(QMainWindow):
self.view.model().setFilterColumns(expr_dict)
if self.show_filter_casesens.isChecked():
- self.view.model().setFilterCaseSensitivity(QtCore.Qt.CaseSensitivity.CaseSensitive)
+ self.view.model().setFilterCaseSensitivity(QtCompat.CaseSensitive())
else:
- self.view.model().setFilterCaseSensitivity(QtCore.Qt.CaseSensitivity.CaseInsensitive)
+ self.view.model().setFilterCaseSensitivity(QtCompat.CaseInsensitive())
self.view.model().setFilterFixedString(expression)
def s_filter_invert_changed(self):
@@ -1156,9 +1182,9 @@ class MainWindow(QMainWindow):
show = self.worker.engine.get_show_info(self.selected_show_id)
reply = QMessageBox.question(self, 'Confirmation',
'Are you sure you want to delete %s?' % show['title'],
- QMessageBox.StandardButton.Yes, QMessageBox.StandardButton.No)
+ QtCompat.Yes(), QtCompat.No())
- if reply == QMessageBox.StandardButton.Yes:
+ if reply == QtCompat.Yes():
self.worker_call('delete_show', self.r_generic, show)
def s_scan_library(self):
@@ -1193,11 +1219,11 @@ class MainWindow(QMainWindow):
reply = QMessageBox.question(self, 'Confirmation',
'There are %d unsynced changes. Do you want to send them first? (Choosing No will discard them!)' % len(
queue),
- QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No | QMessageBox.StandardButton.Cancel)
+ QtCompat.Yes() | QtCompat.No() | QtCompat.Cancel())
- if reply == QMessageBox.StandardButton.Yes:
+ if reply == QtCompat.Yes():
self.s_send(True)
- elif reply == QMessageBox.StandardButton.No:
+ elif reply == QtCompat.No():
self._busy(True)
self.worker_call('list_download', self.r_list_retrieved)
else:
@@ -1250,7 +1276,7 @@ class MainWindow(QMainWindow):
dialog = SettingsDialog(
None, self.worker, self.config, self.configfile)
dialog.saved.connect(self._update_config)
- dialog.exec()
+ exec_(dialog)
def s_about(self):
QMessageBox.about(self, 'About Trackma-qt %s' % utils.VERSION,
@@ -1266,7 +1292,10 @@ class MainWindow(QMainWindow):
def s_show_menu_columns(self, pos):
globalPos = self.sender().mapToGlobal(pos)
globalPos += QtCore.QPoint(3, 3)
- self.menu_columns.exec(globalPos)
+ if QT_VERSION >= 6:
+ self.menu_columns.exec(globalPos)
+ else:
+ self.menu_columns.exec_(globalPos)
def s_toggle_column(self, visible):
w = self.sender()
@@ -1341,9 +1370,9 @@ class MainWindow(QMainWindow):
box = QMessageBox(self)
box.setWindowTitle("Update prompt")
box.setText(f"Do you want to update {show['title']} to {episode}?")
- box.setIcon(QMessageBox.Icon.Question)
- box.setStandardButtons(QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No)
- box.setAttribute(QtCore.Qt.WidgetAttribute.WA_ShowWithoutActivating)
+ box.setIcon(QtCompat.Question())
+ box.setStandardButtons(QtCompat.Yes() | QtCompat.No())
+ box.setAttribute(QtCompat.WA_ShowWithoutActivating())
box.setModal(False)
box.accepted.connect(lambda:
self.worker_call('set_episode', self.r_generic,
@@ -1357,7 +1386,7 @@ class MainWindow(QMainWindow):
addwindow = AddDialog(
None, self.worker, current_status, default=show['title'])
addwindow.setModal(True)
- if addwindow.exec():
+ if exec_(addwindow):
self.worker_call('set_episode', self.r_generic,
addwindow.selected_show['id'], episode)
diff --git trackma/ui/qt/models.py trackma/ui/qt/models.py
index 180b2cf..d6f6a7e 100644
--- trackma/ui/qt/models.py
+++ trackma/ui/qt/models.py
@@ -1,10 +1,11 @@
import datetime
-from PyQt6 import QtCore, QtGui
+from trackma.ui.qt.compat import QtCore, QtGui, QT_VERSION, ItemFlags, Alignment
from trackma import utils
from trackma.ui.qt.thumbs import ThumbManager
from trackma.ui.qt.util import getColor, getIcon
+from trackma.ui.qt.compat import QtCompat
class ShowListModel(QtCore.QAbstractTableModel):
@@ -32,10 +33,11 @@ class ShowListModel(QtCore.QAbstractTableModel):
editable_columns = [COL_MY_PROGRESS, COL_MY_SCORE]
- common_flags = \
- QtCore.Qt.ItemFlag.ItemIsSelectable | \
- QtCore.Qt.ItemFlag.ItemIsEnabled | \
- QtCore.Qt.ItemFlag.ItemNeverHasChildren
+ common_flags = ItemFlags(
+ QtCompat.ItemIsSelectable() | \
+ QtCompat.ItemIsEnabled() | \
+ QtCompat.ItemNeverHasChildren()
+ )
date_format = "%Y-%m-%d"
@@ -154,9 +156,9 @@ class ShowListModel(QtCore.QAbstractTableModel):
return len(self.columns)
def headerData(self, section, orientation, role):
- if role == QtCore.Qt.ItemDataRole.DisplayRole and orientation == QtCore.Qt.Orientation.Horizontal:
+ if role == QtCompat.DisplayRole() and orientation == QtCompat.Horizontal():
return self.columns[section]
- elif role == QtCore.Qt.ItemDataRole.ToolTipRole and orientation == QtCore.Qt.Orientation.Horizontal:
+ elif role == QtCompat.ToolTipRole() and orientation == QtCompat.Horizontal():
if section == ShowListModel.COL_LAST_UPDATED:
return 'Date and time of the last synced update'
@@ -175,7 +177,7 @@ class ShowListModel(QtCore.QAbstractTableModel):
row, column = index.row(), index.column()
show = self.showlist[row]
- if role == QtCore.Qt.ItemDataRole.DisplayRole:
+ if role == QtCompat.DisplayRole():
if column == ShowListModel.COL_ID:
return show['id']
elif column == ShowListModel.COL_TITLE:
@@ -215,15 +217,15 @@ class ShowListModel(QtCore.QAbstractTableModel):
return show.get('my_tags', '-')
elif column == ShowListModel.COL_MY_STATUS:
return self.mediainfo['statuses_dict'][show['my_status']]
- elif role == QtCore.Qt.ItemDataRole.BackgroundRole:
+ elif role == QtCompat.BackgroundRole():
return self.colors.get(row)
- elif role == QtCore.Qt.ItemDataRole.DecorationRole:
+ elif role == QtCompat.DecorationRole():
if column == ShowListModel.COL_TITLE and show['id'] in self.playing:
return getIcon('media-playback-start')
- elif role == QtCore.Qt.ItemDataRole.TextAlignmentRole:
+ elif role == QtCompat.TextAlignmentRole():
if column in [ShowListModel.COL_MY_PROGRESS, ShowListModel.COL_MY_SCORE]:
- return QtCore.Qt.AlignmentFlag.AlignHCenter | QtCore.Qt.AlignmentFlag.AlignVCenter
- elif role == QtCore.Qt.ItemDataRole.ToolTipRole:
+ return Alignment(QtCompat.AlignHCenter() | QtCompat.AlignVCenter())
+ elif role == QtCompat.ToolTipRole():
if column == ShowListModel.COL_PERCENT:
tooltip = "Watched: %d
" % show['my_progress']
if self.eps.get(row):
@@ -238,7 +240,7 @@ class ShowListModel(QtCore.QAbstractTableModel):
return tooltip
elif column == ShowListModel.COL_LAST_UPDATED:
return utils.format_local_time(show.get('my_last_update'))
- elif role == QtCore.Qt.ItemDataRole.EditRole:
+ elif role == QtCompat.EditRole():
if column == ShowListModel.COL_MY_PROGRESS:
return (show['my_progress'], show['total'], 0, 1)
elif column == ShowListModel.COL_MY_SCORE:
@@ -249,14 +251,14 @@ class ShowListModel(QtCore.QAbstractTableModel):
decimals = 0
return (show['my_score'], self.mediainfo['score_max'], decimals, self.mediainfo['score_step'])
- elif role == QtCore.Qt.ItemDataRole.UserRole:
+ elif role == QtCompat.UserRole():
if column == ShowListModel.COL_LAST_UPDATED:
dt = show.get('my_last_update')
return dt.timestamp() if dt is not None else 0
def flags(self, index):
if index.column() in self.editable_columns:
- return self.common_flags | QtCore.Qt.ItemFlag.ItemIsEditable
+ return ItemFlags(self.common_flags | QtCompat.ItemIsEditable())
else:
return self.common_flags
@@ -284,13 +286,13 @@ class AddTableModel(QtCore.QAbstractTableModel):
return 3
def headerData(self, section, orientation, role):
- if role == QtCore.Qt.ItemDataRole.DisplayRole and orientation == QtCore.Qt.Orientation.Horizontal:
+ if role == QtCompat.DisplayRole() and orientation == QtCompat.Horizontal():
return self.columns[section]
def data(self, index, role):
row, column = index.row(), index.column()
- if role == QtCore.Qt.ItemDataRole.DisplayRole:
+ if role == QtCompat.DisplayRole():
item = self.results[row]
if column == 0:
@@ -322,7 +324,7 @@ class AddListModel(QtCore.QAbstractListModel):
def gotThumb(self, iid, thumb):
iid = int(iid)
self.thumbs[iid] = thumb.scaled(
- 100, 140, QtCore.Qt.AspectRatioMode.KeepAspectRatio, QtCore.Qt.TransformationMode.SmoothTransformation)
+ 100, 140, QtCompat.KeepAspectRatio(), QtCompat.SmoothTransformation())
self.dataChanged.emit(self.index(iid), self.index(iid))
@@ -345,7 +347,7 @@ class AddListModel(QtCore.QAbstractListModel):
if self.pool.exists(filename):
self.thumbs[row] = self.pool.getThumb(filename).scaled(
- 100, 140, QtCore.Qt.AspectRatioMode.KeepAspectRatio, QtCore.Qt.TransformationMode.SmoothTransformation)
+ 100, 140, QtCompat.KeepAspectRatio(), QtCompat.SmoothTransformation())
else:
self.pool.queueDownload(row, item['image'], filename)
@@ -359,11 +361,11 @@ class AddListModel(QtCore.QAbstractListModel):
def data(self, index, role):
row = index.row()
- if role == QtCore.Qt.ItemDataRole.DisplayRole:
+ if role == QtCompat.DisplayRole():
return self.results[row]
- elif role == QtCore.Qt.ItemDataRole.DecorationRole:
+ elif role == QtCompat.DecorationRole():
return self.thumbs.get(row)
- elif role == QtCore.Qt.ItemDataRole.BackgroundRole:
+ elif role == QtCompat.BackgroundRole():
t = self.results[row].get('type')
if t == utils.Type.TV:
return QtGui.QColor(202, 253, 150)
@@ -381,8 +383,8 @@ class AddListModel(QtCore.QAbstractListModel):
class AddListProxy(QtCore.QSortFilterProxyModel):
def lessThan(self, left, right):
- leftData = self.sourceModel().data(left, QtCore.Qt.ItemDataRole.DisplayRole)
- rightData = self.sourceModel().data(right, QtCore.Qt.ItemDataRole.DisplayRole)
+ leftData = self.sourceModel().data(left, QtCompat.DisplayRole())
+ rightData = self.sourceModel().data(right, QtCompat.DisplayRole())
return leftData['type'] < rightData['type']
@@ -415,7 +417,7 @@ class ShowListProxy(QtCore.QSortFilterProxyModel):
for col in range(self.sourceModel().columnCount(source_parent)):
index = self.sourceModel().index(source_row, col)
if (col in self.filter_columns and
- self.filter_columns[col] not in str(self.sourceModel().data(index, QtCore.Qt.ItemDataRole.DisplayRole))):
+ self.filter_columns[col] not in str(self.sourceModel().data(index, QtCompat.DisplayRole()))):
return self.filter_invert
return self.filter_invert != super(ShowListProxy, self).filterAcceptsRow(source_row, source_parent)
@@ -424,8 +426,8 @@ class ShowListProxy(QtCore.QSortFilterProxyModel):
col = left.column()
if col == ShowListModel.COL_LAST_UPDATED:
- lv = self.sourceModel().data(left, QtCore.Qt.ItemDataRole.UserRole)
- rv = self.sourceModel().data(right, QtCore.Qt.ItemDataRole.UserRole)
+ lv = self.sourceModel().data(left, QtCompat.UserRole())
+ rv = self.sourceModel().data(right, QtCompat.UserRole())
lnum = lv if isinstance(lv, (int, float)) else 0
rnum = rv if isinstance(rv, (int, float)) else 0
diff --git trackma/ui/qt/settings.py trackma/ui/qt/settings.py
index 16b13a0..8ef0dfb 100644
--- trackma/ui/qt/settings.py
+++ trackma/ui/qt/settings.py
@@ -14,16 +14,38 @@
# along with this program. If not, see .
#
-from PyQt6 import QtCore
-from PyQt6.QtWidgets import (QAbstractItemView, QCheckBox, QColorDialog, QComboBox, QDialog, QDialogButtonBox,
- QFileDialog, QFormLayout, QFrame, QGridLayout, QGroupBox, QLabel, QLineEdit, QListWidget,
- QListWidgetItem, QPushButton, QRadioButton, QScrollArea, QSpinBox, QSplitter,
- QStackedWidget, QTabWidget, QVBoxLayout, QWidget)
+from trackma.ui.qt.compat import QtCore
+from trackma.ui.qt.compat import QtWidgets
+QAbstractItemView = QtWidgets.QAbstractItemView
+QCheckBox = QtWidgets.QCheckBox
+QColorDialog = QtWidgets.QColorDialog
+QComboBox = QtWidgets.QComboBox
+QDialog = QtWidgets.QDialog
+QDialogButtonBox = QtWidgets.QDialogButtonBox
+QFileDialog = QtWidgets.QFileDialog
+QFormLayout = QtWidgets.QFormLayout
+QFrame = QtWidgets.QFrame
+QGridLayout = QtWidgets.QGridLayout
+QGroupBox = QtWidgets.QGroupBox
+QLabel = QtWidgets.QLabel
+QLineEdit = QtWidgets.QLineEdit
+QListWidget = QtWidgets.QListWidget
+QListWidgetItem = QtWidgets.QListWidgetItem
+QPushButton = QtWidgets.QPushButton
+QRadioButton = QtWidgets.QRadioButton
+QScrollArea = QtWidgets.QScrollArea
+QSpinBox = QtWidgets.QSpinBox
+QSplitter = QtWidgets.QSplitter
+QStackedWidget = QtWidgets.QStackedWidget
+QTabWidget = QtWidgets.QTabWidget
+QVBoxLayout = QtWidgets.QVBoxLayout
+QWidget = QtWidgets.QWidget
from trackma import utils
from trackma.ui.qt.delegates import ShowsTableDelegate
from trackma.ui.qt.themedcolorpicker import ThemedColorPicker
from trackma.ui.qt.util import FilterBar, getColor, getIcon
+from trackma.ui.qt.compat import QtCompat
class SettingsDialog(QDialog):
@@ -55,7 +77,7 @@ class SettingsDialog(QDialog):
getIcon('window-new'), 'User Interface', self.category_list)
category_theme = QListWidgetItem(
getIcon('applications-graphics'), 'Theme', self.category_list)
- self.category_list.setSelectionMode(QAbstractItemView.SelectionMode.SingleSelection)
+ self.category_list.setSelectionMode(QtCompat.SingleSelection())
self.category_list.setCurrentRow(0)
self.category_list.setMaximumWidth(
self.category_list.sizeHintForColumn(0) + 15)
@@ -65,7 +87,7 @@ class SettingsDialog(QDialog):
# Media tab
page_media = QWidget()
page_media_layout = QVBoxLayout()
- page_media_layout.setAlignment(QtCore.Qt.AlignmentFlag.AlignTop)
+ page_media_layout.setAlignment(QtCompat.AlignTop())
# Group: Media settings
g_media = QGroupBox('Media settings')
@@ -116,7 +138,7 @@ class SettingsDialog(QDialog):
self.plex_port = QLineEdit()
self.plex_user = QLineEdit()
self.plex_passw = QLineEdit()
- self.plex_passw.setEchoMode(QLineEdit.EchoMode.Password)
+ self.plex_passw.setEchoMode(QtCompat.Password())
self.plex_obey_wait = QCheckBox()
self.plex_ssl = QCheckBox()
@@ -175,7 +197,7 @@ class SettingsDialog(QDialog):
self.kodi_port = QLineEdit()
self.kodi_user = QLineEdit()
self.kodi_passw = QLineEdit()
- self.kodi_passw.setEchoMode(QLineEdit.EchoMode.Password)
+ self.kodi_passw.setEchoMode(QtCompat.Password())
self.kodi_obey_wait = QCheckBox()
g_kodi_layout = QGridLayout()
@@ -207,7 +229,7 @@ class SettingsDialog(QDialog):
# Library tab
page_library = QWidget()
page_library_layout = QVBoxLayout()
- page_library_layout.setAlignment(QtCore.Qt.AlignmentFlag.AlignTop)
+ page_library_layout.setAlignment(QtCompat.AlignTop())
# Group: Library
g_playnext = QGroupBox('Library')
@@ -219,14 +241,14 @@ class SettingsDialog(QDialog):
self.player_browse = QPushButton('Browse...')
self.player_browse.clicked.connect(self.s_player_browse)
lbl_searchdirs = QLabel('Media directories')
- lbl_searchdirs.setAlignment(QtCore.Qt.AlignmentFlag.AlignTop)
+ lbl_searchdirs.setAlignment(QtCompat.AlignTop())
self.searchdirs = QListWidget()
self.searchdirs_add = QPushButton('Add...')
self.searchdirs_add.clicked.connect(self.s_searchdirs_add)
self.searchdirs_remove = QPushButton('Remove')
self.searchdirs_remove.clicked.connect(self.s_searchdirs_remove)
self.searchdirs_buttons = QVBoxLayout()
- self.searchdirs_buttons.setAlignment(QtCore.Qt.AlignmentFlag.AlignTop)
+ self.searchdirs_buttons.setAlignment(QtCompat.AlignTop())
self.searchdirs_buttons.addWidget(self.searchdirs_add)
self.searchdirs_buttons.addWidget(self.searchdirs_remove)
self.searchdirs_buttons.addWidget(QSplitter())
@@ -272,7 +294,7 @@ class SettingsDialog(QDialog):
# Sync tab
page_sync = QWidget()
page_sync_layout = QVBoxLayout()
- page_sync_layout.setAlignment(QtCore.Qt.AlignmentFlag.AlignTop)
+ page_sync_layout.setAlignment(QtCompat.AlignTop())
# Group: Autoretrieve
g_autoretrieve = QGroupBox('Autoretrieve')
@@ -340,7 +362,7 @@ class SettingsDialog(QDialog):
# UI tab
page_ui = QWidget()
page_ui_layout = QFormLayout()
- page_ui_layout.setAlignment(QtCore.Qt.AlignmentFlag.AlignTop)
+ page_ui_layout.setAlignment(QtCompat.AlignTop())
# Group: Icon
g_icon = QGroupBox('Notification Icon')
@@ -397,7 +419,7 @@ class SettingsDialog(QDialog):
# Theming tab
page_theme = QWidget()
page_theme_layout = QFormLayout()
- page_theme_layout.setAlignment(QtCore.Qt.AlignmentFlag.AlignTop)
+ page_theme_layout.setAlignment(QtCompat.AlignTop())
# Group: Episode Bar
g_ep_bar = QGroupBox('Episode Bar')
@@ -443,7 +465,7 @@ class SettingsDialog(QDialog):
for (key2, label) in self.colors[key1]:
self.color_buttons.append(QPushButton())
# self.color_buttons[-1].setStyleSheet('background-color: ' + getColor(self.config['colors'][key]).name())
- self.color_buttons[-1].setFocusPolicy(QtCore.Qt.FocusPolicy.NoFocus)
+ self.color_buttons[-1].setFocusPolicy(QtCompat.NoFocus())
self.color_buttons[-1].clicked.connect(
self.s_color_picker(key2, False))
self.syscolor_buttons.append(QPushButton('System Colors'))
@@ -473,12 +495,12 @@ class SettingsDialog(QDialog):
# Bottom buttons
bottombox = QDialogButtonBox(
- QDialogButtonBox.StandardButton.Ok
- | QDialogButtonBox.StandardButton.Apply
- | QDialogButtonBox.StandardButton.Cancel
+ QtWidgets.QDialogButtonBox.Ok
+ | QtWidgets.QDialogButtonBox.Apply
+ | QtWidgets.QDialogButtonBox.Cancel
)
bottombox.accepted.connect(self.s_save)
- bottombox.button(QDialogButtonBox.StandardButton.Apply).clicked.connect(self._save)
+ bottombox.button(QtWidgets.QDialogButtonBox.Apply).clicked.connect(self._save)
bottombox.rejected.connect(self.reject)
# Main layout finish
diff --git trackma/ui/qt/themedcolorpicker.py trackma/ui/qt/themedcolorpicker.py
index 3252344..fc51ddb 100644
--- trackma/ui/qt/themedcolorpicker.py
+++ trackma/ui/qt/themedcolorpicker.py
@@ -14,8 +14,14 @@
# along with this program. If not, see .
#
-from PyQt6 import QtCore, QtGui
-from PyQt6.QtWidgets import QDialog, QDialogButtonBox, QGridLayout, QPushButton, QVBoxLayout
+from trackma.ui.qt.compat import QtCore, QtGui, exec_
+from trackma.ui.qt.compat import QtWidgets
+from trackma.ui.qt.compat import QtCompat
+QDialog = QtWidgets.QDialog
+QDialogButtonBox = QtWidgets.QDialogButtonBox
+QGridLayout = QtWidgets.QGridLayout
+QPushButton = QtWidgets.QPushButton
+QVBoxLayout = QtWidgets.QVBoxLayout
class ThemedColorPicker(QDialog):
@@ -37,14 +43,14 @@ class ThemedColorPicker(QDialog):
self.colors.append(QPushButton())
self.colors[-1].setStyleSheet('background-color: ' + QtGui.QColor(
QtGui.QPalette().color(QtGui.QPalette.ColorGroup(group), QtGui.QPalette.ColorRole(role))).name())
- self.colors[-1].setFocusPolicy(QtCore.Qt.FocusPolicy.NoFocus)
+ self.colors[-1].setFocusPolicy(QtCompat.NoFocus())
self.colors[-1].clicked.connect(self.s_select(group, role))
colorbox.addWidget(self.colors[-1], row, col, 1, 1)
col += 1
row += 1
bottombox = QDialogButtonBox()
- bottombox.addButton(QDialogButtonBox.StandardButton.Ok)
- bottombox.addButton(QDialogButtonBox.StandardButton.Cancel)
+ bottombox.addButton(QtWidgets.QDialogButtonBox.Ok)
+ bottombox.addButton(QtWidgets.QDialogButtonBox.Cancel)
bottombox.accepted.connect(self.accept)
bottombox.rejected.connect(self.reject)
layout.addLayout(colorbox)
@@ -60,9 +66,9 @@ class ThemedColorPicker(QDialog):
@staticmethod
def do(parent=None, default=None):
dialog = ThemedColorPicker(parent, default)
- result = dialog.exec()
+ result = exec_(dialog)
- if result == QDialog.DialogCode.Accepted:
+ if result == QtWidgets.QDialog.Accepted:
return dialog.colorString
else:
return None
diff --git trackma/ui/qt/thumbs.py trackma/ui/qt/thumbs.py
index b097f32..01b471a 100644
--- trackma/ui/qt/thumbs.py
+++ trackma/ui/qt/thumbs.py
@@ -1,7 +1,8 @@
import os
import queue
-from PyQt6 import QtCore, QtGui, QtNetwork
+from trackma.ui.qt.compat import QtCore, QtGui, QtNetwork
+from trackma.ui.qt.compat import QtCompat
ATTRIB_FILE = QtNetwork.QNetworkRequest.Attribute(1000)
ATTRIB_ID = QtNetwork.QNetworkRequest.Attribute(1001)
@@ -59,7 +60,7 @@ class ThumbManager(QtCore.QObject):
data = reply.readAll()
image = QtGui.QImage.fromData(data)
thumb = image.scaled(
- 200, 280, QtCore.Qt.AspectRatioMode.KeepAspectRatio, QtCore.Qt.TransformationMode.SmoothTransformation)
+ 200, 280, QtCompat.KeepAspectRatio(), QtCompat.SmoothTransformation())
thumb.save(fname)
self.downloads.pop(fname)
diff --git trackma/ui/qt/util.py trackma/ui/qt/util.py
index dec5575..31119f0 100644
--- trackma/ui/qt/util.py
+++ trackma/ui/qt/util.py
@@ -14,9 +14,8 @@
# along with this program. If not, see .
#
-from PyQt6 import QtGui
-
from trackma import utils
+from trackma.ui.qt.compat import QtGui, QtWidgets, QtCore, QT_VERSION, QtCompat
class FilterBar:
@@ -30,8 +29,72 @@ class FilterBar:
def getIcon(icon_name):
- fallback = QtGui.QIcon(utils.DATADIR + '/qtui/{}.png'.format(icon_name))
- return QtGui.QIcon.fromTheme(icon_name, fallback)
+ # Try theme icon first
+ icon = QtGui.QIcon.fromTheme(icon_name)
+
+ # If theme icon is available, use it
+ if not icon.isNull():
+ return icon
+
+ # Otherwise, try the fallback file
+ fallback_path = utils.DATADIR + '/qtui/{}.png'.format(icon_name)
+ import os
+ if os.path.exists(fallback_path):
+ return QtGui.QIcon(fallback_path)
+
+ # If no file exists, create a simple icon using QStyle or text
+ # This provides basic functionality especially for PyQt4/older systems
+ style = QtWidgets.QApplication.style()
+
+ # Map icon names to QStyle standard pixmaps
+ # Note: Some standard pixmaps are only available in Qt5+
+ QStyle = QtWidgets.QStyle
+ style_map = {
+ 'list-add': getattr(QStyle, 'SP_ArrowUp', None),
+ 'list-remove': getattr(QStyle, 'SP_ArrowDown', None),
+ 'edit-delete': getattr(QStyle, 'SP_TrashIcon', None),
+ 'help-about': QStyle.SP_MessageBoxInformation,
+ 'application-exit': QStyle.SP_DialogCloseButton,
+ 'folder': QStyle.SP_DirIcon,
+ 'view-refresh': getattr(QStyle, 'SP_BrowserReload', None),
+ }
+
+ # Try to get standard pixmap
+ if icon_name in style_map and style_map[icon_name] is not None:
+ try:
+ pixmap = style.standardPixmap(style_map[icon_name])
+ if not pixmap.isNull():
+ return QtGui.QIcon(pixmap)
+ except:
+ pass
+
+ # For icons without standard pixmaps, create a simple text-based icon
+ pixmap = QtGui.QPixmap(16, 16)
+ pixmap.fill(QtCompat.GlobalColor_transparent())
+ painter = QtGui.QPainter(pixmap)
+
+ # Draw simple symbols
+ font = painter.font()
+ font.setPixelSize(14)
+ font.setBold(True)
+ painter.setFont(font)
+
+ text_map = {
+ 'list-add': '+',
+ 'list-remove': '−', # minus sign
+ 'media-playback-start': '▶',
+ 'media-skip-forward': '⏭',
+ 'edit-find': '🔍',
+ 'edit-delete': '×',
+ 'view-refresh': '↻',
+ }
+
+ if icon_name in text_map:
+ painter.drawText(pixmap.rect(), QtCompat.AlignCenter(), text_map[icon_name])
+
+ painter.end()
+
+ return QtGui.QIcon(pixmap)
def getColor(colorString):
diff --git trackma/ui/qt/widgets.py trackma/ui/qt/widgets.py
index 2f1077b..c3d9026 100644
--- trackma/ui/qt/widgets.py
+++ trackma/ui/qt/widgets.py
@@ -16,14 +16,24 @@
import os
-from PyQt6 import QtCore, QtGui
-from PyQt6.QtWidgets import (QAbstractItemView, QGridLayout, QHeaderView, QLabel, QListView, QScrollArea, QSplitter,
- QTableView, QVBoxLayout, QWidget)
+from trackma.ui.qt.compat import QtCore, QtGui, exec_
+from trackma.ui.qt.compat import QtWidgets
+QAbstractItemView = QtWidgets.QAbstractItemView
+QGridLayout = QtWidgets.QGridLayout
+QHeaderView = QtWidgets.QHeaderView
+QLabel = QtWidgets.QLabel
+QListView = QtWidgets.QListView
+QScrollArea = QtWidgets.QScrollArea
+QSplitter = QtWidgets.QSplitter
+QTableView = QtWidgets.QTableView
+QVBoxLayout = QtWidgets.QVBoxLayout
+QWidget = QtWidgets.QWidget
from trackma import utils
from trackma.ui.qt.delegates import AddListDelegate, ShowsTableDelegate
from trackma.ui.qt.models import AddListModel, AddListProxy, AddTableModel, ShowListModel, ShowListProxy
from trackma.ui.qt.workers import ImageWorker
+from trackma.ui.qt.compat import QtCompat
class DetailsWidget(QWidget):
@@ -40,20 +50,20 @@ class DetailsWidget(QWidget):
show_title_font = QtGui.QFont()
show_title_font.setBold(True)
show_title_font.setPointSize(12)
- self.show_title.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
+ self.show_title.setAlignment(QtCompat.AlignCenter())
self.show_title.setFont(show_title_font)
info_area = QWidget()
info_layout = QGridLayout()
self.show_image = QLabel()
- self.show_image.setAlignment(QtCore.Qt.AlignmentFlag.AlignTop)
+ self.show_image.setAlignment(QtCompat.AlignTop())
self.show_info = QLabel()
self.show_info.setWordWrap(True)
- self.show_info.setAlignment(QtCore.Qt.AlignmentFlag.AlignTop)
+ self.show_info.setAlignment(QtCompat.AlignTop())
self.show_description = QLabel()
self.show_description.setWordWrap(True)
- self.show_description.setAlignment(QtCore.Qt.AlignmentFlag.AlignTop)
+ self.show_description.setAlignment(QtCompat.AlignTop())
info_layout.addWidget(self.show_image, 0, 0, 1, 1)
info_layout.addWidget(self.show_info, 1, 0, 1, 1)
@@ -62,7 +72,7 @@ class DetailsWidget(QWidget):
info_area.setLayout(info_layout)
scroll_area = QScrollArea()
- scroll_area.setBackgroundRole(QtGui.QPalette.ColorRole.Light)
+ scroll_area.setBackgroundRole(QtCompat.Light())
scroll_area.setWidgetResizable(True)
scroll_area.setWidget(info_area)
@@ -79,12 +89,12 @@ class DetailsWidget(QWidget):
def load(self, show):
metrics = QtGui.QFontMetrics(self.show_title.font())
title = metrics.elidedText(
- show['title'], QtCore.Qt.TextElideMode.ElideRight, self.show_title.width())
+ show['title'], QtCompat.ElideRight(), self.show_title.width())
self.show_title.setText("%s" % (show['url'], title))
- self.show_title.setTextFormat(QtCore.Qt.TextFormat.RichText)
+ self.show_title.setTextFormat(QtCompat.RichText())
self.show_title.setTextInteractionFlags(
- QtCore.Qt.TextInteractionFlag.TextBrowserInteraction)
+ QtCompat.TextBrowserInteraction())
self.show_title.setOpenExternalLinks(True)
# Load show info
@@ -161,21 +171,25 @@ class ShowsTableView(QTableView):
self.setModel(proxymodel)
self.setItemDelegate(ShowsTableDelegate(self, palette=palette))
- self.setSelectionMode(QAbstractItemView.SelectionMode.SingleSelection)
- self.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows)
+ self.setSelectionMode(QtCompat.SingleSelection())
+ self.setSelectionBehavior(QtCompat.SelectRows())
self.horizontalHeader().setHighlightSections(False)
self.horizontalHeader().setSectionsMovable(True)
- self.horizontalHeader().setContextMenuPolicy(QtCore.Qt.ContextMenuPolicy.CustomContextMenu)
+ self.horizontalHeader().setContextMenuPolicy(QtCompat.CustomContextMenu())
self.verticalHeader().hide()
- self.setGridStyle(QtCore.Qt.PenStyle.NoPen)
+ self.setGridStyle(QtCompat.NoPen())
def contextMenuEvent(self, event):
- action = self.context_menu.exec(event.globalPos())
+ from trackma.ui.qt.compat import QT_VERSION
+ if QT_VERSION >= 6:
+ action = self.context_menu.exec(event.globalPos())
+ else:
+ action = self.context_menu.exec_(event.globalPos())
def mousePressEvent(self, event):
super().mousePressEvent(event)
- if event.button() == QtCore.Qt.MouseButton.MiddleButton:
+ if event.button() == QtCompat.MiddleButton():
self.middleClicked.emit()
@@ -188,14 +202,14 @@ class AddCardView(QListView):
m = AddListModel(api_info=api_info)
proxy = AddListProxy()
proxy.setSourceModel(m)
- proxy.sort(0, QtCore.Qt.SortOrder.AscendingOrder)
+ proxy.sort(0, QtCompat.AscendingOrder())
self.setItemDelegate(AddListDelegate())
- self.setFlow(QListView.Flow.LeftToRight)
+ self.setFlow(QtCompat.LeftToRight())
self.setWrapping(True)
- self.setSelectionMode(QAbstractItemView.SelectionMode.SingleSelection)
- self.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows)
- self.setEditTriggers(QAbstractItemView.EditTrigger.NoEditTriggers)
+ self.setSelectionMode(QtCompat.SingleSelection())
+ self.setSelectionBehavior(QtCompat.SelectRows())
+ self.setEditTriggers(QtCompat.NoEditTriggers())
self.setModel(proxy)
self.selectionModel().currentRowChanged.connect(self.s_show_selected)
@@ -230,17 +244,17 @@ class AddTableDetailsView(QSplitter):
proxy = QtCore.QSortFilterProxyModel()
proxy.setSourceModel(m)
- self.table.setGridStyle(QtCore.Qt.PenStyle.NoPen)
- self.table.setSelectionMode(QAbstractItemView.SelectionMode.SingleSelection)
- self.table.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows)
- self.table.setEditTriggers(QAbstractItemView.EditTrigger.NoEditTriggers)
+ self.table.setGridStyle(QtCompat.NoPen())
+ self.table.setSelectionMode(QtCompat.SingleSelection())
+ self.table.setSelectionBehavior(QtCompat.SelectRows())
+ self.table.setEditTriggers(QtCompat.NoEditTriggers())
self.table.setModel(proxy)
# Allow sorting but don't sort by default
- self.table.horizontalHeader().setSortIndicator(-1, QtCore.Qt.SortOrder.AscendingOrder)
+ self.table.horizontalHeader().setSortIndicator(-1, QtCompat.AscendingOrder())
self.table.setSortingEnabled(True)
- self.table.horizontalHeader().setSectionResizeMode(0, QHeaderView.ResizeMode.Stretch)
+ self.table.horizontalHeader().setSectionResizeMode(0, QtCompat.Stretch())
self.table.selectionModel().currentRowChanged.connect(self.s_show_selected)
self.addWidget(self.table)
diff --git trackma/ui/qt/workers.py trackma/ui/qt/workers.py
index 7d916f8..9a6c818 100644
--- trackma/ui/qt/workers.py
+++ trackma/ui/qt/workers.py
@@ -20,7 +20,7 @@ import urllib.request
from io import BytesIO
from PIL import Image
-from PyQt6 import QtCore
+from trackma.ui.qt.compat import QtCore
from trackma import utils
from trackma.engine import Engine