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 <http://www.gnu.org/licenses/>.
 #
 
-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 <http://www.gnu.org/licenses/>.
+#
+
+"""
+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 <http://www.gnu.org/licenses/>.
 #
 
-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<br>" % 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 <http://www.gnu.org/licenses/>.
 #
 
-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 <http://www.gnu.org/licenses/>.
 #
 
-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 <http://www.gnu.org/licenses/>.
 #
 
-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("<a href=\"%s\">%s</a>" % (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
