From 7535f92ac876a1773005be06ebe4053240770cf9 Mon Sep 17 00:00:00 2001 From: i1sm3ky <114871165+i1sm3ky@users.noreply.github.com> Date: Mon, 17 Apr 2023 15:47:01 +0530 Subject: [PATCH] PyQt6 Upgrade (#1685) This puts Vorta on PyQt6 and starts a new main branch 0.9. --------- Co-authored-by: real-yfprojects Co-authored-by: Manu <3916435+m3nu@users.noreply.github.com> Co-authored-by: yfprojects <62463991+real-yfprojects@users.noreply.github.com> --- .github/workflows/test.yml | 3 +- .../fix_app_qt_folder_names_for_codesign.py | 6 +-- setup.cfg | 10 ++-- src/vorta/__main__.py | 2 +- src/vorta/_version.py | 2 +- src/vorta/application.py | 22 ++++----- src/vorta/borg/borg_job.py | 4 +- src/vorta/borg/extract.py | 2 +- src/vorta/borg/jobs_manager.py | 2 +- src/vorta/i18n/__init__.py | 2 +- src/vorta/keyring/kwallet.py | 10 ++-- src/vorta/log.py | 2 +- src/vorta/network_status/network_manager.py | 4 +- src/vorta/notifications.py | 13 ++--- src/vorta/qt_single_application.py | 8 ++-- src/vorta/scheduler.py | 6 +-- src/vorta/tray_menu.py | 8 ++-- src/vorta/utils.py | 15 +++--- src/vorta/views/archive_tab.py | 43 ++++++++--------- src/vorta/views/diff_result.py | 20 ++++---- src/vorta/views/export_window.py | 7 +-- src/vorta/views/extract_dialog.py | 48 ++++++++++++------- src/vorta/views/import_window.py | 4 +- src/vorta/views/main_window.py | 32 ++++++------- src/vorta/views/misc_tab.py | 8 ++-- src/vorta/views/partials/loading_button.py | 2 +- src/vorta/views/partials/tooltip_button.py | 14 +++--- src/vorta/views/partials/treemodel.py | 4 +- src/vorta/views/profile_add_edit_dialog.py | 14 +++--- src/vorta/views/repo_add_dialog.py | 13 ++--- src/vorta/views/repo_tab.py | 20 ++++---- src/vorta/views/schedule_tab.py | 34 ++++++------- src/vorta/views/source_tab.py | 21 ++++---- src/vorta/views/ssh_dialog.py | 8 ++-- src/vorta/views/utils.py | 2 +- tests/conftest.py | 6 +++ tests/test_archives.py | 8 ++-- tests/test_diff.py | 2 +- tests/test_extract.py | 38 +++++++-------- tests/test_import_export.py | 16 +++++-- tests/test_lock.py | 6 +-- tests/test_misc.py | 8 ++-- tests/test_notifications.py | 2 +- tests/test_profile.py | 20 ++++---- tests/test_repo.py | 22 +++++---- tests/test_schedule.py | 8 ++-- tests/test_treemodel.py | 2 +- 47 files changed, 294 insertions(+), 259 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 9846a1e0..53c5bd9a 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -65,7 +65,8 @@ jobs: sudo apt update && sudo apt install -y \ xvfb libssl-dev openssl libacl1-dev libacl1 build-essential borgbackup \ libxkbcommon-x11-0 dbus-x11 libxcb-icccm4 libxcb-image0 libxcb-keysyms1 \ - libxcb-randr0 libxcb-render-util0 libxcb-xinerama0 libxcb-xfixes0 libxcb-shape0 + libxcb-randr0 libxcb-render-util0 libxcb-xinerama0 libxcb-xfixes0 libxcb-shape0 \ + libegl1 libxcb-cursor0 - name: Install system dependencies (macOS) if: runner.os == 'macOS' run: | diff --git a/package/fix_app_qt_folder_names_for_codesign.py b/package/fix_app_qt_folder_names_for_codesign.py index 65b405f5..0adfb03f 100644 --- a/package/fix_app_qt_folder_names_for_codesign.py +++ b/package/fix_app_qt_folder_names_for_codesign.py @@ -20,11 +20,11 @@ def create_symlink(folder: Path) -> None: """ sibbling = Path(str(folder).replace("MacOS", "")) - # PyQt5/Qt/qml/QtQml/Models.2 + # PyQt6/Qt/qml/QtQml/Models.2 root = str(sibbling).partition("Contents")[2].lstrip("/") # ../../../../ backward = "../" * (root.count("/") + 1) - # ../../../../Resources/PyQt5/Qt/qml/QtQml/Models.2 + # ../../../../Resources/PyQt6/Qt/qml/QtQml/Models.2 good_path = f"{backward}Resources/{root}" folder.symlink_to(good_path) @@ -51,7 +51,7 @@ def fix_dll(dll: Path) -> None: return None return f"@loader_path{good_path}/{basename}" - # Resources/PyQt5/Qt/qml/QtQuick/Controls.2/Fusion + # Resources/PyQt6/Qt/qml/QtQuick/Controls.2/Fusion root = str(dll.parent).partition("Contents")[2][1:] # /../../../../../../.. backward = "/.." * (root.count("/") + 1) diff --git a/setup.cfg b/setup.cfg index 285f68ff..c2c92037 100644 --- a/setup.cfg +++ b/setup.cfg @@ -39,14 +39,14 @@ python_requires = >=3.7 install_requires = platformdirs >=3.0.0, <4.0.0; sys_platform == 'darwin' # for macOS: breaking changes in 3.0.0, platformdirs >=2.6.0, <4.0.0; sys_platform != 'darwin' # for others: 2.6+ works consistently. - pyqt5 + pyqt6 peewee psutil setuptools secretstorage; sys_platform != 'darwin' - pyobjc-core; sys_platform == 'darwin' - pyobjc-framework-Cocoa; sys_platform == 'darwin' - pyobjc-framework-LaunchServices; sys_platform == 'darwin' + pyobjc-core < 9.1; sys_platform == 'darwin' + pyobjc-framework-Cocoa < 9.1; sys_platform == 'darwin' + pyobjc-framework-LaunchServices < 9.1; sys_platform == 'darwin' tests_require = pytest pytest-qt @@ -100,7 +100,7 @@ commands=flake8 src tests max_line_length = 120 [pylint.master] -extension-pkg-whitelist=PyQt5 +extension-pkg-whitelist=PyQt6 load-plugins= [pylint.messages control] diff --git a/src/vorta/__main__.py b/src/vorta/__main__.py index 34a5bd8c..40f146bc 100644 --- a/src/vorta/__main__.py +++ b/src/vorta/__main__.py @@ -14,7 +14,7 @@ from vorta.utils import parse_args def main(): def exception_handler(type, value, tb): from traceback import format_exception - from PyQt5.QtWidgets import QMessageBox + from PyQt6.QtWidgets import QMessageBox logger.critical( "Uncaught exception, file a report at https://github.com/borgbase/vorta/issues/new/choose", diff --git a/src/vorta/_version.py b/src/vorta/_version.py index 2863f686..e4e49b3b 100644 --- a/src/vorta/_version.py +++ b/src/vorta/_version.py @@ -1 +1 @@ -__version__ = '0.8.11' +__version__ = '0.9.0' diff --git a/src/vorta/application.py b/src/vorta/application.py index 66f4dcd6..3542131e 100644 --- a/src/vorta/application.py +++ b/src/vorta/application.py @@ -3,8 +3,8 @@ import os import sys from pathlib import Path from typing import Any, Dict, List, Tuple -from PyQt5 import QtCore -from PyQt5.QtWidgets import QMessageBox +from PyQt6 import QtCore +from PyQt6.QtWidgets import QMessageBox from vorta.borg.break_lock import BorgBreakJob from vorta.borg.create import BorgCreateJob from vorta.borg.jobs_manager import JobsManager @@ -179,10 +179,10 @@ class VortaApp(QtSingleApplication): def _alert_missing_borg(self): msg = QMessageBox() - msg.setIcon(QMessageBox.Critical) + msg.setIcon(QMessageBox.Icon.Critical) msg.setText(self.tr("No Borg Binary Found")) msg.setInformativeText(self.tr("Vorta was unable to locate a usable Borg Backup binary.")) - msg.setStandardButtons(QMessageBox.Ok) + msg.setStandardButtons(QMessageBox.StandardButton.Ok) msg.exec() def check_darwin_permissions(self): @@ -201,8 +201,8 @@ class VortaApp(QtSingleApplication): test_path = Path('~/Library/Cookies').expanduser() if test_path.exists() and not os.access(test_path, os.R_OK): msg = QMessageBox() - msg.setIcon(QMessageBox.Warning) - msg.setTextInteractionFlags(QtCore.Qt.LinksAccessibleByMouse) + msg.setIcon(QMessageBox.Icon.Warning) + msg.setTextInteractionFlags(QtCore.Qt.TextInteractionFlag.LinksAccessibleByMouse) msg.setText(self.tr("Vorta needs Full Disk Access for complete Backups")) msg.setInformativeText( self.tr( @@ -212,7 +212,7 @@ class VortaApp(QtSingleApplication): "System Preferences > Security & Privacy." ) ) - msg.setStandardButtons(QMessageBox.Ok) + msg.setStandardButtons(QMessageBox.StandardButton.Ok) msg.exec() def react_to_log(self, mgs, context): @@ -225,9 +225,9 @@ class VortaApp(QtSingleApplication): repo_url = context.get('repo_url') msg = QMessageBox() msg.setWindowTitle(self.tr("Repository In Use")) - msg.setIcon(QMessageBox.Critical) - abortButton = msg.addButton(self.tr("Abort"), QMessageBox.RejectRole) - msg.addButton(self.tr("Continue"), QMessageBox.AcceptRole) + msg.setIcon(QMessageBox.Icon.Critical) + abortButton = msg.addButton(self.tr("Abort"), QMessageBox.ButtonRole.RejectRole) + msg.addButton(self.tr("Continue"), QMessageBox.ButtonRole.AcceptRole) msg.setDefaultButton(abortButton) msg.setText(self.tr(f"The repository at {repo_url} might be in use elsewhere.")) msg.setInformativeText( @@ -324,7 +324,7 @@ class VortaApp(QtSingleApplication): # Create QMessageBox msg = QMessageBox() msg.setIcon(QMessageBox.Icon.Critical) # changed for warning - msg.setStandardButtons(QMessageBox.Ok) + msg.setStandardButtons(QMessageBox.StandardButton.Ok) msg.setWindowTitle(self.tr('Repo Check Failed')) if returncode == 1: diff --git a/src/vorta/borg/borg_job.py b/src/vorta/borg/borg_job.py index 25fc92af..d1e7f1b4 100644 --- a/src/vorta/borg/borg_job.py +++ b/src/vorta/borg/borg_job.py @@ -11,8 +11,8 @@ from collections import namedtuple from datetime import datetime as dt from subprocess import PIPE, Popen, TimeoutExpired from threading import Lock -from PyQt5 import QtCore -from PyQt5.QtWidgets import QApplication +from PyQt6 import QtCore +from PyQt6.QtWidgets import QApplication from vorta import application from vorta.borg.jobs_manager import JobInterface from vorta.i18n import trans_late, translate diff --git a/src/vorta/borg/extract.py b/src/vorta/borg/extract.py index 14814183..a2a37725 100644 --- a/src/vorta/borg/extract.py +++ b/src/vorta/borg/extract.py @@ -1,5 +1,5 @@ import tempfile -from PyQt5.QtCore import QModelIndex, Qt +from PyQt6.QtCore import QModelIndex, Qt from vorta.utils import borg_compat from vorta.views.extract_dialog import ExtractTree, FileData from vorta.views.partials.treemodel import FileSystemItem, path_to_str diff --git a/src/vorta/borg/jobs_manager.py b/src/vorta/borg/jobs_manager.py index 0e14cd54..5f2d6cd6 100644 --- a/src/vorta/borg/jobs_manager.py +++ b/src/vorta/borg/jobs_manager.py @@ -2,7 +2,7 @@ import logging import queue import threading from abc import abstractmethod -from PyQt5.QtCore import QObject +from PyQt6.QtCore import QObject logger = logging.getLogger(__name__) diff --git a/src/vorta/i18n/__init__.py b/src/vorta/i18n/__init__.py index aaf7c1d3..54bc95d8 100644 --- a/src/vorta/i18n/__init__.py +++ b/src/vorta/i18n/__init__.py @@ -3,7 +3,7 @@ internationalisation (i18n) support code """ import logging import os -from PyQt5.QtCore import QLocale, QTranslator +from PyQt6.QtCore import QLocale, QTranslator logger = logging.getLogger(__name__) diff --git a/src/vorta/keyring/kwallet.py b/src/vorta/keyring/kwallet.py index 32402160..08841d6d 100644 --- a/src/vorta/keyring/kwallet.py +++ b/src/vorta/keyring/kwallet.py @@ -1,7 +1,7 @@ import logging import os -from PyQt5 import QtDBus -from PyQt5.QtCore import QVariant +from PyQt6 import QtDBus +from PyQt6.QtCore import QMetaType, QVariant from vorta.keyring.abc import VortaKeyring logger = logging.getLogger(__name__) @@ -47,9 +47,9 @@ class VortaKWallet5Keyring(VortaKeyring): def get_result(self, method, args=[]): if args: - result = self.iface.callWithArgumentList(QtDBus.QDBus.AutoDetect, method, args) + result = self.iface.callWithArgumentList(QtDBus.QDBus.CallMode.AutoDetect, method, args) else: - result = self.iface.call(QtDBus.QDBus.AutoDetect, method) + result = self.iface.call(QtDBus.QDBus.CallMode.AutoDetect, method) return result.arguments()[0] @property @@ -60,7 +60,7 @@ class VortaKWallet5Keyring(VortaKeyring): def try_unlock(self): wallet_name = self.get_result("networkWallet") wId = QVariant(0) - wId.convert(4) + wId.convert(QMetaType(QMetaType.Type.LongLong.value)) output = self.get_result("open", args=[wallet_name, wId, 'vorta-repo']) try: self.handle = int(output) diff --git a/src/vorta/log.py b/src/vorta/log.py index e0f8e751..c4ee22df 100644 --- a/src/vorta/log.py +++ b/src/vorta/log.py @@ -16,7 +16,7 @@ logger = logging.getLogger() def init_logger(background=False): logger.setLevel(logging.DEBUG) logging.getLogger('peewee').setLevel(logging.INFO) - logging.getLogger('PyQt5').setLevel(logging.INFO) + logging.getLogger('PyQt6').setLevel(logging.INFO) # create logging format formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') diff --git a/src/vorta/network_status/network_manager.py b/src/vorta/network_status/network_manager.py index d1f336e8..55b9f0ba 100644 --- a/src/vorta/network_status/network_manager.py +++ b/src/vorta/network_status/network_manager.py @@ -2,8 +2,8 @@ import logging from datetime import datetime from enum import Enum from typing import Any, List, Mapping, NamedTuple, Optional -from PyQt5 import QtDBus -from PyQt5.QtCore import QObject, QVersionNumber +from PyQt6 import QtDBus +from PyQt6.QtCore import QObject, QVersionNumber from vorta.network_status.abc import NetworkStatusMonitor, SystemWifiInfo logger = logging.getLogger(__name__) diff --git a/src/vorta/notifications.py b/src/vorta/notifications.py index 2f71f769..320f1e3e 100644 --- a/src/vorta/notifications.py +++ b/src/vorta/notifications.py @@ -1,6 +1,6 @@ import logging import sys -from PyQt5 import QtCore, QtDBus +from PyQt6 import QtCore, QtDBus from vorta.store.models import SettingsModel logger = logging.getLogger(__name__) @@ -77,13 +77,12 @@ class DBusNotifications(VortaNotifications): path = "/org/freedesktop/Notifications" interface = "org.freedesktop.Notifications" app_name = "vorta" - v = QtCore.QVariant(12321) # random int to identify all notifications - if v.convert(QtCore.QVariant.UInt): - id_replace = v + id_replace = QtCore.QVariant(12321) + id_replace.convert(QtCore.QMetaType(QtCore.QMetaType.Type.UInt.value)) icon = "com.borgbase.Vorta-symbolic" title = header text = msg - actions_list = QtDBus.QDBusArgument([], QtCore.QMetaType.QStringList) + actions_list = QtDBus.QDBusArgument([], QtCore.QMetaType.Type.QStringList.value) hint = {'urgency': self.URGENCY[level]} time = 5000 # milliseconds for display timeout @@ -91,7 +90,9 @@ class DBusNotifications(VortaNotifications): notify = QtDBus.QDBusInterface(item, path, interface, bus) if notify.isValid(): x = notify.call( - QtDBus.QDBus.AutoDetect, + # Call arguments for Notify interface need to match exactly: + # https://specifications.freedesktop.org/notification-spec/notification-spec-latest.html#command-notify + QtDBus.QDBus.CallMode.AutoDetect, "Notify", app_name, id_replace, diff --git a/src/vorta/qt_single_application.py b/src/vorta/qt_single_application.py index 8e5be661..cb9f65a8 100644 --- a/src/vorta/qt_single_application.py +++ b/src/vorta/qt_single_application.py @@ -1,6 +1,6 @@ -from PyQt5.QtCore import QTextStream, pyqtSignal -from PyQt5.QtNetwork import QLocalServer, QLocalSocket -from PyQt5.QtWidgets import QApplication +from PyQt6.QtCore import QTextStream, pyqtSignal +from PyQt6.QtNetwork import QLocalServer, QLocalSocket +from PyQt6.QtWidgets import QApplication class QtSingleApplication(QApplication): @@ -52,7 +52,6 @@ class QtSingleApplication(QApplication): if self._isRunning: # Yes, there is. self._outStream = QTextStream(self._outSocket) - self._outStream.setCodec('UTF-8') else: # No, there isn't. self._outSocket = None @@ -84,7 +83,6 @@ class QtSingleApplication(QApplication): if not self._inSocket: return self._inStream = QTextStream(self._inSocket) - self._inStream.setCodec('UTF-8') self._inSocket.readyRead.connect(self._onReadyRead) def _onReadyRead(self): diff --git a/src/vorta/scheduler.py b/src/vorta/scheduler.py index 4cace732..700294e9 100644 --- a/src/vorta/scheduler.py +++ b/src/vorta/scheduler.py @@ -4,9 +4,9 @@ import threading from datetime import datetime as dt from datetime import timedelta from typing import Dict, NamedTuple, Optional, Tuple, Union -from PyQt5 import QtCore, QtDBus -from PyQt5.QtCore import QTimer -from PyQt5.QtWidgets import QApplication +from PyQt6 import QtCore, QtDBus +from PyQt6.QtCore import QTimer +from PyQt6.QtWidgets import QApplication from vorta import application from vorta.borg.check import BorgCheckJob from vorta.borg.create import BorgCreateJob diff --git a/src/vorta/tray_menu.py b/src/vorta/tray_menu.py index cfab9a26..14d80284 100644 --- a/src/vorta/tray_menu.py +++ b/src/vorta/tray_menu.py @@ -1,6 +1,6 @@ import os -from PyQt5.QtGui import QIcon -from PyQt5.QtWidgets import QApplication, QMenu, QSystemTrayIcon +from PyQt6.QtGui import QIcon +from PyQt6.QtWidgets import QApplication, QMenu, QSystemTrayIcon from vorta.store.models import BackupProfileModel from vorta.utils import get_asset @@ -31,8 +31,8 @@ class TrayMenu(QSystemTrayIcon): If XDG_CURRENT_DESKTOP isn't set, always open the tray menu (macOS) """ if reason in [ - QSystemTrayIcon.Trigger, - QSystemTrayIcon.DoubleClick, + QSystemTrayIcon.ActivationReason.Trigger, + QSystemTrayIcon.ActivationReason.DoubleClick, ] and os.environ.get('XDG_CURRENT_DESKTOP'): self.app.toggle_main_window_visibility() diff --git a/src/vorta/utils.py b/src/vorta/utils.py index 774949d6..5e099491 100644 --- a/src/vorta/utils.py +++ b/src/vorta/utils.py @@ -12,17 +12,14 @@ from datetime import datetime as dt from functools import reduce from typing import Any, Callable, Iterable, List, Optional, Tuple, TypeVar import psutil -from PyQt5 import QtCore -from PyQt5.QtCore import QFileInfo, QThread, pyqtSignal -from PyQt5.QtWidgets import QApplication, QFileDialog, QSystemTrayIcon +from PyQt6 import QtCore +from PyQt6.QtCore import QFileInfo, QThread, pyqtSignal +from PyQt6.QtWidgets import QApplication, QFileDialog, QSystemTrayIcon from vorta.borg._compatibility import BorgCompatibility from vorta.i18n import trans_late from vorta.log import logger from vorta.network_status.abc import NetworkStatusMonitor -QApplication.setAttribute(QtCore.Qt.AA_EnableHighDpiScaling, True) # enable highdpi scaling -QApplication.setAttribute(QtCore.Qt.AA_UseHighDpiPixmaps, True) # use highdpi icons - borg_compat = BorgCompatibility() _network_status_monitor = None @@ -168,10 +165,10 @@ def get_dict_from_list(dataDict, mapList): def choose_file_dialog(parent, title, want_folder=True): dialog = QFileDialog(parent, title, os.path.expanduser('~')) - dialog.setFileMode(QFileDialog.Directory if want_folder else QFileDialog.ExistingFiles) - dialog.setParent(parent, QtCore.Qt.Sheet) + dialog.setFileMode(QFileDialog.FileMode.Directory if want_folder else QFileDialog.FileMode.ExistingFiles) + dialog.setParent(parent, QtCore.Qt.WindowType.Sheet) if want_folder: - dialog.setOption(QFileDialog.ShowDirsOnly) + dialog.setOption(QFileDialog.Option.ShowDirsOnly) return dialog diff --git a/src/vorta/views/archive_tab.py b/src/vorta/views/archive_tab.py index 31e3aef2..20e31035 100644 --- a/src/vorta/views/archive_tab.py +++ b/src/vorta/views/archive_tab.py @@ -2,18 +2,17 @@ import logging import sys from datetime import timedelta from typing import Dict, Optional -from PyQt5 import QtCore, uic -from PyQt5.QtCore import QItemSelectionModel, QMimeData, QPoint, Qt, pyqtSlot -from PyQt5.QtGui import QDesktopServices, QKeySequence -from PyQt5.QtWidgets import ( - QAction, +from PyQt6 import QtCore, uic +from PyQt6.QtCore import QItemSelectionModel, QMimeData, QPoint, Qt, pyqtSlot +from PyQt6.QtGui import QAction, QDesktopServices, QKeySequence, QShortcut +from PyQt6.QtWidgets import ( + QAbstractItemView, QApplication, QHeaderView, QInputDialog, QLayout, QMenu, QMessageBox, - QShortcut, QTableView, QTableWidgetItem, QWidget, @@ -77,20 +76,20 @@ class ArchiveTab(ArchiveTabBase, ArchiveTabUI, BackupProfileMixin): header = self.archiveTable.horizontalHeader() header.setVisible(True) - header.setSectionResizeMode(0, QHeaderView.ResizeToContents) - header.setSectionResizeMode(1, QHeaderView.ResizeToContents) - header.setSectionResizeMode(2, QHeaderView.ResizeToContents) - header.setSectionResizeMode(3, QHeaderView.Interactive) - header.setSectionResizeMode(4, QHeaderView.Stretch) + header.setSectionResizeMode(0, QHeaderView.ResizeMode.ResizeToContents) + header.setSectionResizeMode(1, QHeaderView.ResizeMode.ResizeToContents) + header.setSectionResizeMode(2, QHeaderView.ResizeMode.ResizeToContents) + header.setSectionResizeMode(3, QHeaderView.ResizeMode.Interactive) + header.setSectionResizeMode(4, QHeaderView.ResizeMode.Stretch) header.setStretchLastSection(True) if sys.platform != 'darwin': self._set_status('') # Set platform-specific hints. - self.archiveTable.setSelectionBehavior(QTableView.SelectRows) - self.archiveTable.setEditTriggers(QTableView.NoEditTriggers) + self.archiveTable.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows) + self.archiveTable.setEditTriggers(QAbstractItemView.EditTrigger.NoEditTriggers) self.archiveTable.setWordWrap(False) - self.archiveTable.setTextElideMode(QtCore.Qt.ElideLeft) + self.archiveTable.setTextElideMode(QtCore.Qt.TextElideMode.ElideLeft) self.archiveTable.setAlternatingRowColors(True) self.archiveTable.cellDoubleClicked.connect(self.cell_double_clicked) self.archiveTable.setSortingEnabled(True) @@ -757,7 +756,7 @@ class ArchiveTab(ArchiveTabBase, ArchiveTabUI, BackupProfileMixin): window = ExtractDialog(archive, model) self._toggle_all_buttons(True) - window.setParent(self, QtCore.Qt.Sheet) + window.setParent(self, QtCore.Qt.WindowType.Sheet) self._window = window # for testing window.show() window.accepted.connect(process_result) @@ -778,19 +777,19 @@ class ArchiveTab(ArchiveTabBase, ArchiveTabUI, BackupProfileMixin): QDesktopServices.openUrl(QtCore.QUrl(f'file:///{mount_point}')) def row_of_archive(self, archive_name): - items = self.archiveTable.findItems(archive_name, QtCore.Qt.MatchExactly) + items = self.archiveTable.findItems(archive_name, QtCore.Qt.MatchFlag.MatchExactly) rows = [item.row() for item in items if item.column() == 4] return rows[0] if rows else None def confirm_dialog(self, title, text): msg = QMessageBox() - msg.setIcon(QMessageBox.Information) + msg.setIcon(QMessageBox.Icon.Information) msg.setText(text) msg.setWindowTitle(title) - msg.setStandardButtons(QMessageBox.Yes | QMessageBox.Cancel) - msg.button(msg.Yes).setText(self.tr("Yes")) - msg.button(msg.Cancel).setText(self.tr("Cancel")) - return msg.exec_() == QMessageBox.Yes + msg.setStandardButtons(QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.Cancel) + msg.button(QMessageBox.StandardButton.Yes).setText(self.tr("Yes")) + msg.button(QMessageBox.StandardButton.Cancel).setText(self.tr("Cancel")) + return msg.exec() == QMessageBox.StandardButton.Yes def delete_action(self): # Since this function modify the UI, we can't put the whole function in a JobQUeue. @@ -835,7 +834,7 @@ class ArchiveTab(ArchiveTabBase, ArchiveTabUI, BackupProfileMixin): # remove rows from list and database for archive in archives: - for entry in self.archiveTable.findItems(archive, QtCore.Qt.MatchExactly): + for entry in self.archiveTable.findItems(archive, QtCore.Qt.MatchFlag.MatchExactly): self.archiveTable.removeRow(entry.row()) ArchiveModel.get(name=archive).delete_instance() diff --git a/src/vorta/views/diff_result.py b/src/vorta/views/diff_result.py index c9aae4fd..2fc12639 100644 --- a/src/vorta/views/diff_result.py +++ b/src/vorta/views/diff_result.py @@ -5,10 +5,10 @@ import re from dataclasses import dataclass from pathlib import PurePath from typing import List, Optional, Tuple -from PyQt5 import uic -from PyQt5.QtCore import QDateTime, QLocale, QMimeData, QModelIndex, QPoint, Qt, QThread, QUrl -from PyQt5.QtGui import QColor, QKeySequence -from PyQt5.QtWidgets import QApplication, QHeaderView, QMenu, QShortcut, QTreeView +from PyQt6 import uic +from PyQt6.QtCore import QDateTime, QLocale, QMimeData, QModelIndex, QPoint, Qt, QThread, QUrl +from PyQt6.QtGui import QColor, QKeySequence, QShortcut +from PyQt6.QtWidgets import QApplication, QHeaderView, QMenu, QTreeView from vorta.store.models import SettingsModel from vorta.utils import get_asset, pretty_bytes, uses_dark_mode from vorta.views.partials.treemodel import ( @@ -89,9 +89,9 @@ class DiffResultDialog(DiffResultBase, DiffResultUI): # header header = self.treeView.header() header.setStretchLastSection(False) # stretch only first section - header.setSectionResizeMode(0, QHeaderView.Stretch) - header.setSectionResizeMode(1, QHeaderView.ResizeToContents) - header.setSectionResizeMode(2, QHeaderView.ResizeToContents) + header.setSectionResizeMode(0, QHeaderView.ResizeMode.Stretch) + header.setSectionResizeMode(1, QHeaderView.ResizeMode.ResizeToContents) + header.setSectionResizeMode(2, QHeaderView.ResizeMode.ResizeToContents) # signals @@ -824,11 +824,11 @@ class DiffTree(FileTreeModel[DiffData]): if role == Qt.ItemDataRole.ForegroundRole: # colour if item.data.change_type == ChangeType.ADDED: - return QColor(Qt.green) if uses_dark_mode() else QColor(Qt.darkGreen) + return QColor(Qt.GlobalColor.green) if uses_dark_mode() else QColor(Qt.GlobalColor.darkGreen) if item.data.change_type == ChangeType.MODIFIED: - return QColor(Qt.yellow) if uses_dark_mode() else QColor(Qt.darkYellow) + return QColor(Qt.GlobalColor.yellow) if uses_dark_mode() else QColor(Qt.GlobalColor.darkYellow) if item.data.change_type == ChangeType.REMOVED: - return QColor(Qt.red) if uses_dark_mode() else QColor(Qt.darkRed) + return QColor(Qt.GlobalColor.red) if uses_dark_mode() else QColor(Qt.GlobalColor.darkRed) return None # no change if role == Qt.ItemDataRole.ToolTipRole: diff --git a/src/vorta/views/export_window.py b/src/vorta/views/export_window.py index 70688006..2af17e13 100644 --- a/src/vorta/views/export_window.py +++ b/src/vorta/views/export_window.py @@ -1,8 +1,9 @@ import logging import os from pathlib import Path -from PyQt5 import uic -from PyQt5.QtWidgets import QFileDialog, QMessageBox +from PyQt6 import uic +from PyQt6.QtCore import Qt +from PyQt6.QtWidgets import QFileDialog, QMessageBox from vorta.keyring.abc import VortaKeyring from vorta.store.models import BackupProfileModel # noqa: F401 from vorta.utils import get_asset @@ -31,7 +32,7 @@ class ExportWindow(ExportWindowBase, ExportWindowUI): self.keyring = VortaKeyring.get_keyring() profile = self.profile if profile.repo is None or self.keyring.get_password('vorta-repo', profile.repo.url) is None: - self.storePassword.setCheckState(False) + self.storePassword.setCheckState(Qt.CheckState(False)) self.storePassword.setDisabled(True) self.storePassword.setToolTip(self.tr('Disclose your borg passphrase (No passphrase set)')) diff --git a/src/vorta/views/extract_dialog.py b/src/vorta/views/extract_dialog.py index 9b2fed4c..acbec219 100644 --- a/src/vorta/views/extract_dialog.py +++ b/src/vorta/views/extract_dialog.py @@ -4,11 +4,11 @@ import logging from dataclasses import dataclass from datetime import datetime from pathlib import PurePath -from typing import Optional -from PyQt5 import uic -from PyQt5.QtCore import QDateTime, QLocale, QMimeData, QModelIndex, QPoint, Qt, QThread, QUrl -from PyQt5.QtGui import QColor, QKeySequence -from PyQt5.QtWidgets import QApplication, QDialogButtonBox, QHeaderView, QMenu, QPushButton, QShortcut +from typing import Optional, Union +from PyQt6 import uic +from PyQt6.QtCore import QDateTime, QLocale, QMimeData, QModelIndex, QPoint, Qt, QThread, QUrl +from PyQt6.QtGui import QColor, QKeySequence, QShortcut +from PyQt6.QtWidgets import QApplication, QDialogButtonBox, QHeaderView, QMenu, QPushButton from vorta.store.models import SettingsModel from vorta.utils import borg_compat, get_asset, pretty_bytes, uses_dark_mode from vorta.views.utils import get_colored_icon @@ -72,10 +72,10 @@ class ExtractDialog(ExtractDialogBase, ExtractDialogUI): # header header = view.header() header.setStretchLastSection(False) - header.setSectionResizeMode(1, QHeaderView.ResizeToContents) - header.setSectionResizeMode(2, QHeaderView.ResizeToContents) - header.setSectionResizeMode(3, QHeaderView.ResizeToContents) - header.setSectionResizeMode(0, QHeaderView.Stretch) + header.setSectionResizeMode(1, QHeaderView.ResizeMode.ResizeToContents) + header.setSectionResizeMode(2, QHeaderView.ResizeMode.ResizeToContents) + header.setSectionResizeMode(3, QHeaderView.ResizeMode.ResizeToContents) + header.setSectionResizeMode(0, QHeaderView.ResizeMode.Stretch) # shortcuts shortcut_copy = QShortcut(QKeySequence.StandardKey.Copy, self.treeView) @@ -281,7 +281,7 @@ class FileData: last_modified: QDateTime source_path: Optional[str] = None # only relevant for links - checkstate: int = 0 # whether to extract the file (0, 1 or 2) + checkstate: Qt.CheckState = Qt.CheckState.Unchecked # whether to extract the file (0, 1 or 2) checked_children: int = 0 # number of children checked @@ -378,7 +378,7 @@ class ExtractTree(FileTreeModel[FileData]): self, section: int, orientation: Qt.Orientation, - role: int = Qt.ItemDataRole.DisplayRole, + role: Union[int, Qt.ItemDataRole] = Qt.ItemDataRole.DisplayRole, ): """ Get the data for the given role and section in the given header. @@ -414,7 +414,7 @@ class ExtractTree(FileTreeModel[FileData]): return None - def data(self, index: QModelIndex, role: int = Qt.ItemDataRole.DisplayRole): + def data(self, index: QModelIndex, role: Union[int, Qt.ItemDataRole] = Qt.ItemDataRole.DisplayRole): """ Get the data for the given role and index. @@ -467,9 +467,9 @@ class ExtractTree(FileTreeModel[FileData]): if role == Qt.ItemDataRole.BackgroundRole and column == 3: # health indicator if item.data.health: - return QColor(Qt.green) if uses_dark_mode() else QColor(Qt.darkGreen) + return QColor(Qt.GlobalColor.green) if uses_dark_mode() else QColor(Qt.GlobalColor.darkGreen) else: - return QColor(Qt.green) if uses_dark_mode() else QColor(Qt.darkGreen) + return QColor(Qt.GlobalColor.green) if uses_dark_mode() else QColor(Qt.GlobalColor.darkGreen) if role == Qt.ItemDataRole.ToolTipRole: if column == 0: @@ -532,7 +532,12 @@ class ExtractTree(FileTreeModel[FileData]): if role == Qt.ItemDataRole.CheckStateRole and column == 0: return item.data.checkstate - def setData(self, index: QModelIndex, value, role: int = Qt.ItemDataRole.DisplayRole) -> bool: + def setData( + self, + index: QModelIndex, + value: Union[int, Qt.CheckState], + role: Union[int, Qt.ItemDataRole] = Qt.ItemDataRole.CheckStateRole, + ) -> bool: """ Sets the role data for the item at index to value. @@ -543,6 +548,13 @@ class ExtractTree(FileTreeModel[FileData]): if role != Qt.ItemDataRole.CheckStateRole: return False + # convert int to enum member + # PyQt6 will pass Ints where there were IntEnums in PyQt5 + if isinstance(value, int): + value = Qt.CheckState(value) + if isinstance(role, int): + role = Qt.ItemDataRole(role) + item: ExtractFileItem = index.internalPointer() if value == item.data.checkstate: @@ -616,7 +628,7 @@ class ExtractTree(FileTreeModel[FileData]): item = index.internalPointer() for i in range(number_children): - child = index.child(i, 0) + child = self.index(i, 0, index) child_item: ExtractFileItem = child.internalPointer() child_item.data.checkstate = value @@ -633,8 +645,8 @@ class ExtractTree(FileTreeModel[FileData]): self.set_checkstate_recursively(child, value) self.dataChanged.emit( - index.child(0, 0), - index.child(0, number_children - 1), + self.index(0, 0, index), + self.index(0, number_children - 1, index), (Qt.ItemDataRole.CheckStateRole,), ) diff --git a/src/vorta/views/import_window.py b/src/vorta/views/import_window.py index 55395912..b60a113f 100644 --- a/src/vorta/views/import_window.py +++ b/src/vorta/views/import_window.py @@ -1,5 +1,5 @@ -from PyQt5 import QtCore -from PyQt5.QtWidgets import QMessageBox +from PyQt6 import QtCore +from PyQt6.QtWidgets import QMessageBox from vorta.keyring.abc import VortaKeyring from vorta.profile_export import VersionException from vorta.store.connection import SCHEMA_VERSION diff --git a/src/vorta/views/main_window.py b/src/vorta/views/main_window.py index 95439842..e2f05d33 100644 --- a/src/vorta/views/main_window.py +++ b/src/vorta/views/main_window.py @@ -1,9 +1,9 @@ import logging from pathlib import Path -from PyQt5 import QtCore, uic -from PyQt5.QtCore import QPoint -from PyQt5.QtGui import QFontMetrics, QKeySequence -from PyQt5.QtWidgets import QApplication, QCheckBox, QFileDialog, QMenu, QMessageBox, QShortcut, QToolTip +from PyQt6 import QtCore, uic +from PyQt6.QtCore import QPoint +from PyQt6.QtGui import QFontMetrics, QKeySequence, QShortcut +from PyQt6.QtWidgets import QApplication, QCheckBox, QFileDialog, QMenu, QMessageBox, QToolTip from vorta.profile_export import ImportFailedException, ProfileExport from vorta.store.models import BackupProfileModel, SettingsModel from vorta.utils import borg_compat, get_asset, get_network_status_monitor, is_system_tray_available @@ -31,7 +31,7 @@ class MainWindow(MainWindowBase, MainWindowUI): self.setWindowTitle('Vorta for Borg Backup') self.app = parent self.setWindowIcon(get_colored_icon("icon")) - self.setWindowFlags(QtCore.Qt.WindowCloseButtonHint | QtCore.Qt.WindowMinimizeButtonHint) + self.setWindowFlags(QtCore.Qt.WindowType.WindowCloseButtonHint | QtCore.Qt.WindowType.WindowMinimizeButtonHint) self.createStartBtn = LoadingButton(self.tr("Start Backup")) self.gridLayout.addWidget(self.createStartBtn, 0, 0, 1, 1) self.createStartBtn.setGif(get_asset("icons/loading")) @@ -156,7 +156,7 @@ class MainWindow(MainWindowBase, MainWindowUI): def profile_rename_action(self): window = EditProfileWindow(rename_existing_id=self.profileSelector.currentData()) self.window = window # For tests - window.setParent(self, QtCore.Qt.Sheet) + window.setParent(self, QtCore.Qt.WindowType.Sheet) window.open() window.profile_changed.connect(self.profile_add_edit_result) window.rejected.connect(lambda: self.profileSelector.setCurrentIndex(self.profileSelector.currentIndex())) @@ -171,11 +171,11 @@ class MainWindow(MainWindowBase, MainWindowUI): self, self.tr("Confirm deletion"), msg, - QMessageBox.Yes | QMessageBox.No, - QMessageBox.No, + QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No, + QMessageBox.StandardButton.No, ) - if reply == QMessageBox.Yes: + if reply == QMessageBox.StandardButton.Yes: to_delete.delete_instance(recursive=True) self.app.scheduler.remove_job(to_delete_id) # Remove pending jobs self.profileSelector.removeItem(self.profileSelector.currentIndex()) @@ -189,7 +189,7 @@ class MainWindow(MainWindowBase, MainWindowUI): def profile_add_action(self): window = AddProfileWindow() self.window = window # For tests - window.setParent(self, QtCore.Qt.Sheet) + window.setParent(self, QtCore.Qt.WindowType.Sheet) window.open() window.profile_changed.connect(self.profile_add_edit_result) window.rejected.connect(lambda: self.profileSelector.setCurrentIndex(self.profileSelector.currentIndex())) @@ -201,7 +201,7 @@ class MainWindow(MainWindowBase, MainWindowUI): """ window = ExportWindow(profile=self.current_profile.refresh()) self.window = window - window.setParent(self, QtCore.Qt.Sheet) + window.setParent(self, QtCore.Qt.WindowType.Sheet) window.show() def profile_import_action(self): @@ -236,7 +236,7 @@ class MainWindow(MainWindowBase, MainWindowUI): return window = ImportWindow(profile_export=profile_export) self.window = window - window.setParent(self, QtCore.Qt.Sheet) + window.setParent(self, QtCore.Qt.WindowType.Sheet) window.profile_imported.connect(profile_imported_event) window.show() @@ -280,13 +280,13 @@ class MainWindow(MainWindowBase, MainWindowUI): if not is_system_tray_available(): if SettingsModel.get(key="enable_background_question").value: msg = QMessageBox() - msg.setStandardButtons(QMessageBox.Yes | QMessageBox.No) - msg.setParent(self, QtCore.Qt.Sheet) + msg.setStandardButtons(QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No) + msg.setParent(self, QtCore.Qt.WindowType.Sheet) msg.setText(self.tr("Should Vorta continue to run in the background?")) - msg.button(QMessageBox.Yes).clicked.connect( + msg.button(QMessageBox.StandardButton.Yes).clicked.connect( lambda: self.miscTab.save_setting("disable_background_state", True) ) - msg.button(QMessageBox.No).clicked.connect( + msg.button(QMessageBox.StandardButton.No).clicked.connect( lambda: ( self.miscTab.save_setting("disable_background_state", False), self.app.quit(), diff --git a/src/vorta/views/misc_tab.py b/src/vorta/views/misc_tab.py index a94b4d28..ccc0c65f 100644 --- a/src/vorta/views/misc_tab.py +++ b/src/vorta/views/misc_tab.py @@ -1,7 +1,7 @@ import logging -from PyQt5 import uic -from PyQt5.QtCore import Qt -from PyQt5.QtWidgets import QApplication, QCheckBox, QFormLayout, QHBoxLayout, QLabel, QSizePolicy, QSpacerItem +from PyQt6 import uic +from PyQt6.QtCore import Qt +from PyQt6.QtWidgets import QApplication, QCheckBox, QFormLayout, QHBoxLayout, QLabel, QSizePolicy, QSpacerItem from vorta._version import __version__ from vorta.config import LOG_DIR from vorta.i18n import translate @@ -87,7 +87,7 @@ class MiscTab(MiscTabBase, MiscTabUI, BackupProfileMixin): # create widget cb = QCheckBox(translate('settings', setting.label)) cb.setToolTip(setting.tooltip) - cb.setCheckState(setting.value) + cb.setCheckState(Qt.CheckState(setting.value)) cb.setTristate(False) cb.stateChanged.connect(lambda v, key=setting.key: self.save_setting(key, v)) diff --git a/src/vorta/views/partials/loading_button.py b/src/vorta/views/partials/loading_button.py index 7a241b21..1b85a1aa 100644 --- a/src/vorta/views/partials/loading_button.py +++ b/src/vorta/views/partials/loading_button.py @@ -2,7 +2,7 @@ Adapted from https://stackoverflow.com/questions/53618971/how-to-make-a-qpushbutton-a-loading-button """ -from PyQt5 import QtCore, QtGui, QtWidgets +from PyQt6 import QtCore, QtGui, QtWidgets class LoadingButton(QtWidgets.QPushButton): diff --git a/src/vorta/views/partials/tooltip_button.py b/src/vorta/views/partials/tooltip_button.py index bde53a81..6edfb61c 100644 --- a/src/vorta/views/partials/tooltip_button.py +++ b/src/vorta/views/partials/tooltip_button.py @@ -1,7 +1,7 @@ from typing import Optional -from PyQt5.QtCore import QCoreApplication, QEvent, QSize, Qt -from PyQt5.QtGui import QHelpEvent, QIcon, QMouseEvent, QPaintEvent -from PyQt5.QtWidgets import QSizePolicy, QStyle, QStylePainter, QToolTip, QWidget +from PyQt6.QtCore import QCoreApplication, QEvent, QSize, Qt +from PyQt6.QtGui import QHelpEvent, QIcon, QMouseEvent, QPaintEvent +from PyQt6.QtWidgets import QSizePolicy, QStyle, QStylePainter, QToolTip, QWidget class ToolTipButton(QWidget): @@ -24,7 +24,7 @@ class ToolTipButton(QWidget): """ super().__init__(parent) self.setCursor(Qt.CursorShape.WhatsThisCursor) - self.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) + self.setSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Fixed) self.setMouseTracking(True) self._icon = icon or QIcon() @@ -98,8 +98,10 @@ class ToolTipButton(QWidget): https://doc.qt.io/qt-5/qwidget.html#mouseMoveEvent """ super().mouseMoveEvent(event) - QToolTip.showText(event.globalPos(), self.toolTip(), self) - QCoreApplication.postEvent(self, QHelpEvent(QEvent.Type.ToolTip, event.pos(), event.globalPos())) + QToolTip.showText(event.globalPosition().toPoint(), self.toolTip(), self) + QCoreApplication.postEvent( + self, QHelpEvent(QEvent.Type.ToolTip, event.position().toPoint(), event.globalPosition().toPoint()) + ) def setIcon(self, icon: QIcon): """ diff --git a/src/vorta/views/partials/treemodel.py b/src/vorta/views/partials/treemodel.py index 71b79899..dfb433a2 100644 --- a/src/vorta/views/partials/treemodel.py +++ b/src/vorta/views/partials/treemodel.py @@ -9,7 +9,7 @@ import os.path as osp from functools import reduce from pathlib import PurePath from typing import Generic, List, Optional, Sequence, Tuple, TypeVar, Union, overload -from PyQt5.QtCore import QAbstractItemModel, QModelIndex, QObject, QSortFilterProxyModel, Qt, pyqtSignal +from PyQt6.QtCore import QAbstractItemModel, QModelIndex, QObject, QSortFilterProxyModel, Qt, pyqtSignal #: A representation of a path Path = Tuple[str, ...] @@ -837,7 +837,7 @@ class FileTreeModel(QAbstractItemModel, Generic[T]): row, item = parent_item._parent.get(parent_item.subpath) return self.createIndex(row, 0, parent_item) - def flags(self, index: QModelIndex) -> Qt.ItemFlags: + def flags(self, index: QModelIndex) -> Qt.ItemFlag: """ Returns the item flags for the given index. diff --git a/src/vorta/views/profile_add_edit_dialog.py b/src/vorta/views/profile_add_edit_dialog.py index 298d114f..9ccfa46f 100644 --- a/src/vorta/views/profile_add_edit_dialog.py +++ b/src/vorta/views/profile_add_edit_dialog.py @@ -1,5 +1,5 @@ -from PyQt5 import QtCore, uic -from PyQt5.QtWidgets import QDialogButtonBox +from PyQt6 import QtCore, uic +from PyQt6.QtWidgets import QDialogButtonBox from vorta.i18n import trans_late, translate from vorta.store.models import BackupProfileModel from vorta.utils import get_asset @@ -14,20 +14,20 @@ class AddProfileWindow(AddProfileBase, AddProfileUI): def __init__(self, parent=None): super().__init__(parent) self.setupUi(self) - self.setAttribute(QtCore.Qt.WA_DeleteOnClose) + self.setAttribute(QtCore.Qt.WidgetAttribute.WA_DeleteOnClose) self.edited_profile = None self.buttonBox.rejected.connect(self.close) self.buttonBox.accepted.connect(self.save) self.profileNameField.textChanged.connect(self.button_validation) - self.buttonBox.button(QDialogButtonBox.Save).setText(self.tr("Save")) - self.buttonBox.button(QDialogButtonBox.Cancel).setText(self.tr("Cancel")) + self.buttonBox.button(QDialogButtonBox.StandardButton.Save).setText(self.tr("Save")) + self.buttonBox.button(QDialogButtonBox.StandardButton.Cancel).setText(self.tr("Cancel")) self.name_blank = trans_late('AddProfileWindow', 'Please enter a profile name.') self.name_exists = trans_late('AddProfileWindow', 'A profile with this name already exists.') # Call validate to set inital messages - self.buttonBox.button(QDialogButtonBox.Save).setEnabled(self.validate()) + self.buttonBox.button(QDialogButtonBox.StandardButton.Save).setEnabled(self.validate()) def _set_status(self, text): self.errorText.setText(text) @@ -40,7 +40,7 @@ class AddProfileWindow(AddProfileBase, AddProfileUI): self.accept() def button_validation(self): - self.buttonBox.button(QDialogButtonBox.Save).setEnabled(self.validate()) + self.buttonBox.button(QDialogButtonBox.StandardButton.Save).setEnabled(self.validate()) def validate(self): name = self.profileNameField.text() diff --git a/src/vorta/views/repo_add_dialog.py b/src/vorta/views/repo_add_dialog.py index 4c8c988e..70e60a00 100644 --- a/src/vorta/views/repo_add_dialog.py +++ b/src/vorta/views/repo_add_dialog.py @@ -1,6 +1,7 @@ import re -from PyQt5 import QtCore, uic -from PyQt5.QtWidgets import QAction, QApplication, QDialogButtonBox, QLineEdit +from PyQt6 import QtCore, uic +from PyQt6.QtGui import QAction +from PyQt6.QtWidgets import QApplication, QDialogButtonBox, QLineEdit from vorta.borg.info_repo import BorgInfoRepoJob from vorta.borg.init import BorgInitJob from vorta.i18n import translate @@ -19,7 +20,7 @@ class AddRepoWindow(AddRepoBase, AddRepoUI): def __init__(self, parent=None): super().__init__(parent) self.setupUi(self) - self.setAttribute(QtCore.Qt.WA_DeleteOnClose) + self.setAttribute(QtCore.Qt.WidgetAttribute.WA_DeleteOnClose) self.result = None self.is_remote_repo = True @@ -41,7 +42,7 @@ class AddRepoWindow(AddRepoBase, AddRepoUI): self.showHideAction.setCheckable(True) self.showHideAction.toggled.connect(self.set_visibility) - self.passwordLineEdit.addAction(self.showHideAction, QLineEdit.TrailingPosition) + self.passwordLineEdit.addAction(self.showHideAction, QLineEdit.ActionPosition.TrailingPosition) self.tabWidget.setCurrentIndex(0) @@ -103,7 +104,7 @@ class AddRepoWindow(AddRepoBase, AddRepoUI): self.confirmLineEdit.setText(password) def set_visibility(self, visible): - visibility = QLineEdit.Normal if visible else QLineEdit.Password + visibility = QLineEdit.EchoMode.Normal if visible else QLineEdit.EchoMode.Password self.passwordLineEdit.setEchoMode(visibility) self.confirmLineEdit.setEchoMode(visibility) @@ -224,7 +225,7 @@ class ExistingRepoWindow(AddRepoWindow): del self.confirmLabel def set_visibility(self, visible): - visibility = QLineEdit.Normal if visible else QLineEdit.Password + visibility = QLineEdit.EchoMode.Normal if visible else QLineEdit.EchoMode.Password self.passwordLineEdit.setEchoMode(visibility) if visible: diff --git a/src/vorta/views/repo_tab.py b/src/vorta/views/repo_tab.py index 99d3613b..0b7a398b 100644 --- a/src/vorta/views/repo_tab.py +++ b/src/vorta/views/repo_tab.py @@ -1,8 +1,8 @@ import os from pathlib import PurePath -from PyQt5 import QtCore, uic -from PyQt5.QtCore import QMimeData, QUrl -from PyQt5.QtWidgets import QApplication, QLayout, QMenu, QMessageBox +from PyQt6 import QtCore, uic +from PyQt6.QtCore import QMimeData, QUrl +from PyQt6.QtWidgets import QApplication, QLayout, QMenu, QMessageBox from vorta.store.models import ArchiveModel, BackupProfileMixin, RepoModel from vorta.utils import borg_compat, get_asset, get_private_keys, pretty_bytes from .repo_add_dialog import AddRepoWindow, ExistingRepoWindow @@ -192,15 +192,15 @@ class RepoTab(RepoBase, RepoUI, BackupProfileMixin): """Open a dialog to create an ssh key.""" ssh_add_window = SSHAddWindow() self._window = ssh_add_window # For tests - ssh_add_window.setParent(self, QtCore.Qt.Sheet) + ssh_add_window.setParent(self, QtCore.Qt.WindowType.Sheet) ssh_add_window.accepted.connect(self.init_ssh) # ssh_add_window.rejected.connect(lambda: self.sshComboBox.setCurrentIndex(0)) ssh_add_window.open() def ssh_copy_to_clipboard_action(self): msg = QMessageBox() - msg.setStandardButtons(QMessageBox.Ok) - msg.setParent(self, QtCore.Qt.Sheet) + msg.setStandardButtons(QMessageBox.StandardButton.Ok) + msg.setParent(self, QtCore.Qt.WindowType.Sheet) index = self.sshComboBox.currentIndex() if index > 0: @@ -234,7 +234,7 @@ class RepoTab(RepoBase, RepoUI, BackupProfileMixin): """Open a dialog to create a new repo and add it to vorta.""" window = AddRepoWindow() self._window = window # For tests - window.setParent(self, QtCore.Qt.Sheet) + window.setParent(self, QtCore.Qt.WindowType.Sheet) window.added_repo.connect(self.process_new_repo) # window.rejected.connect(lambda: self.repoSelector.setCurrentIndex(0)) window.open() @@ -243,7 +243,7 @@ class RepoTab(RepoBase, RepoUI, BackupProfileMixin): """Open a dialog to add a existing repo to vorta.""" window = ExistingRepoWindow() self._window = window # For tests - window.setParent(self, QtCore.Qt.Sheet) + window.setParent(self, QtCore.Qt.WindowType.Sheet) window.added_repo.connect(self.process_new_repo) # window.rejected.connect(lambda: self.repoSelector.setCurrentIndex(0)) window.open() @@ -271,8 +271,8 @@ class RepoTab(RepoBase, RepoUI, BackupProfileMixin): self.init_repo_stats() msg = QMessageBox() - msg.setStandardButtons(QMessageBox.Ok) - msg.setParent(self, QtCore.Qt.Sheet) + msg.setStandardButtons(QMessageBox.StandardButton.Ok) + msg.setParent(self, QtCore.Qt.WindowType.Sheet) selected_repo_id = self.repoSelector.currentData() selected_repo_index = self.repoSelector.currentIndex() diff --git a/src/vorta/views/schedule_tab.py b/src/vorta/views/schedule_tab.py index 55837c3f..a72c782b 100644 --- a/src/vorta/views/schedule_tab.py +++ b/src/vorta/views/schedule_tab.py @@ -1,6 +1,6 @@ -from PyQt5 import QtCore, uic -from PyQt5.QtCore import QDateTime, QLocale -from PyQt5.QtWidgets import QApplication, QHeaderView, QListWidgetItem, QTableView, QTableWidgetItem +from PyQt6 import QtCore, uic +from PyQt6.QtCore import QDateTime, QLocale +from PyQt6.QtWidgets import QAbstractItemView, QApplication, QHeaderView, QListWidgetItem, QTableWidgetItem from vorta import application from vorta.i18n import get_locale from vorta.scheduler import ScheduleStatusType @@ -37,10 +37,10 @@ class ScheduleTab(ScheduleBase, ScheduleUI, BackupProfileMixin): self.logTableWidget.setAlternatingRowColors(True) header = self.logTableWidget.horizontalHeader() header.setVisible(True) - [header.setSectionResizeMode(i, QHeaderView.ResizeToContents) for i in range(5)] - header.setSectionResizeMode(3, QHeaderView.Stretch) - self.logTableWidget.setSelectionBehavior(QTableView.SelectRows) - self.logTableWidget.setEditTriggers(QTableView.NoEditTriggers) + [header.setSectionResizeMode(i, QHeaderView.ResizeMode.ResizeToContents) for i in range(5)] + header.setSectionResizeMode(3, QHeaderView.ResizeMode.Stretch) + self.logTableWidget.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows) + self.logTableWidget.setEditTriggers(QAbstractItemView.EditTrigger.NoEditTriggers) # Scheduler intervals we know self.scheduleIntervalUnit.addItem(self.tr('Minutes'), 'minutes') @@ -138,17 +138,19 @@ class ScheduleTab(ScheduleBase, ScheduleUI, BackupProfileMixin): self.scheduleFixedTime.setTime(QtCore.QTime(profile.schedule_fixed_hour, profile.schedule_fixed_minute)) # Set borg-check options - self.validationCheckBox.setCheckState(QtCore.Qt.Checked if profile.validation_on else QtCore.Qt.Unchecked) + self.validationCheckBox.setCheckState( + QtCore.Qt.CheckState.Checked if profile.validation_on else QtCore.Qt.CheckState.Unchecked + ) self.validationWeeksCount.setValue(profile.validation_weeks) # Other checkbox options - self.pruneCheckBox.setCheckState(QtCore.Qt.Checked if profile.prune_on else QtCore.Qt.Unchecked) + self.pruneCheckBox.setCheckState( + QtCore.Qt.CheckState.Checked if profile.prune_on else QtCore.Qt.CheckState.Unchecked + ) self.missedBackupsCheckBox.setCheckState( - QtCore.Qt.Checked if profile.schedule_make_up_missed else QtCore.Qt.Unchecked - ) - self.meteredNetworksCheckBox.setChecked( - QtCore.Qt.Unchecked if profile.dont_run_on_metered_networks else QtCore.Qt.Checked + QtCore.Qt.CheckState.Checked if profile.schedule_make_up_missed else QtCore.Qt.CheckState.Unchecked ) + self.meteredNetworksCheckBox.setChecked(False if profile.dont_run_on_metered_networks else True) self.preBackupCmdLineEdit.setText(profile.pre_backup_cmd) self.postBackupCmdLineEdit.setText(profile.post_backup_cmd) @@ -183,11 +185,11 @@ class ScheduleTab(ScheduleBase, ScheduleUI, BackupProfileMixin): for wifi in get_sorted_wifis(self.profile()): item = QListWidgetItem() item.setText(wifi.ssid) - item.setFlags(item.flags() | QtCore.Qt.ItemIsUserCheckable) + item.setFlags(item.flags() | QtCore.Qt.ItemFlag.ItemIsUserCheckable) if wifi.allowed: - item.setCheckState(QtCore.Qt.Checked) + item.setCheckState(QtCore.Qt.CheckState.Checked) else: - item.setCheckState(QtCore.Qt.Unchecked) + item.setCheckState(QtCore.Qt.CheckState.Unchecked) self.wifiListWidget.addItem(item) self.wifiListWidget.itemChanged.connect(self.save_wifi_item) diff --git a/src/vorta/views/source_tab.py b/src/vorta/views/source_tab.py index 85b02e20..5aba63a8 100644 --- a/src/vorta/views/source_tab.py +++ b/src/vorta/views/source_tab.py @@ -1,9 +1,10 @@ import logging import os from pathlib import PurePath -from PyQt5 import QtCore, QtGui, uic -from PyQt5.QtCore import QFileInfo, QMimeData, QPoint, Qt, QUrl, pyqtSlot -from PyQt5.QtWidgets import QApplication, QHeaderView, QMenu, QMessageBox, QShortcut, QTableWidgetItem +from PyQt6 import QtCore, QtGui, uic +from PyQt6.QtCore import QFileInfo, QMimeData, QPoint, Qt, QUrl, pyqtSlot +from PyQt6.QtGui import QShortcut +from PyQt6.QtWidgets import QApplication, QHeaderView, QMenu, QMessageBox, QTableWidgetItem from vorta.store.models import BackupProfileMixin, SettingsModel, SourceFileModel from vorta.utils import FilePathInfoAsync, choose_file_dialog, get_asset, pretty_bytes, sort_sizes from vorta.views.utils import get_colored_icon @@ -23,7 +24,7 @@ class SourceColumn: class SizeItem(QTableWidgetItem): def __init__(self, s): super().__init__(s) - self.setTextAlignment(Qt.AlignVCenter + Qt.AlignRight) + self.setTextAlignment(Qt.AlignmentFlag.AlignVCenter + Qt.AlignmentFlag.AlignRight) def __lt__(self, other): if other.text() == '': @@ -63,9 +64,9 @@ class SourceTab(SourceBase, SourceUI, BackupProfileMixin): header.setVisible(True) header.setSortIndicatorShown(1) - header.setSectionResizeMode(SourceColumn.Path, QHeaderView.Stretch) - header.setSectionResizeMode(SourceColumn.Size, QHeaderView.ResizeToContents) - header.setSectionResizeMode(SourceColumn.FilesCount, QHeaderView.ResizeToContents) + header.setSectionResizeMode(SourceColumn.Path, QHeaderView.ResizeMode.Stretch) + header.setSectionResizeMode(SourceColumn.Size, QHeaderView.ResizeMode.ResizeToContents) + header.setSectionResizeMode(SourceColumn.FilesCount, QHeaderView.ResizeMode.ResizeToContents) self.sourceFilesWidget.setSortingEnabled(True) self.sourceFilesWidget.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu) @@ -139,7 +140,7 @@ class SourceTab(SourceBase, SourceUI, BackupProfileMixin): sorting = self.sourceFilesWidget.isSortingEnabled() self.sourceFilesWidget.setSortingEnabled(False) - items = self.sourceFilesWidget.findItems(path, QtCore.Qt.MatchExactly) + items = self.sourceFilesWidget.findItems(path, QtCore.Qt.MatchFlag.MatchExactly) # Conversion int->str->int needed because QT limits int to 32-bit data_size = int(data_size) files_count = int(files_count) @@ -250,7 +251,7 @@ class SourceTab(SourceBase, SourceUI, BackupProfileMixin): sourcetab_sort_order = int(SettingsModel.get(key='sourcetab_sort_order').str_value) # Sort items as per settings - self.sourceFilesWidget.sortItems(sourcetab_sort_column, sourcetab_sort_order) + self.sourceFilesWidget.sortItems(sourcetab_sort_column, Qt.SortOrder(sourcetab_sort_order)) self.excludePatternsField.appendPlainText(profile.exclude_patterns) self.excludeIfPresentField.appendPlainText(profile.exclude_if_present) @@ -262,7 +263,7 @@ class SourceTab(SourceBase, SourceUI, BackupProfileMixin): SettingsModel.update({SettingsModel.str_value: str(column)}).where( SettingsModel.key == 'sourcetab_sort_column' ).execute() - SettingsModel.update({SettingsModel.str_value: str(order)}).where( + SettingsModel.update({SettingsModel.str_value: str(order.value)}).where( SettingsModel.key == 'sourcetab_sort_order' ).execute() diff --git a/src/vorta/views/ssh_dialog.py b/src/vorta/views/ssh_dialog.py index df740281..74cb6e11 100644 --- a/src/vorta/views/ssh_dialog.py +++ b/src/vorta/views/ssh_dialog.py @@ -1,7 +1,7 @@ import os -from PyQt5 import uic -from PyQt5.QtCore import QProcess, Qt -from PyQt5.QtWidgets import QApplication, QDialogButtonBox +from PyQt6 import uic +from PyQt6.QtCore import QProcess, Qt +from PyQt6.QtWidgets import QApplication, QDialogButtonBox from ..utils import get_asset uifile = get_asset('UI/sshadd.ui') @@ -12,7 +12,7 @@ class SSHAddWindow(SSHAddBase, SSHAddUI): def __init__(self): super().__init__() self.setupUi(self) - self.setAttribute(Qt.WA_DeleteOnClose) + self.setAttribute(Qt.WidgetAttribute.WA_DeleteOnClose) # dialogButtonBox self.generateButton = self.buttonBox.button(QDialogButtonBox.StandardButton.Ok) diff --git a/src/vorta/views/utils.py b/src/vorta/views/utils.py index c0bc7ba3..023b3384 100644 --- a/src/vorta/views/utils.py +++ b/src/vorta/views/utils.py @@ -1,4 +1,4 @@ -from PyQt5.QtGui import QIcon, QImage, QPixmap +from PyQt6.QtGui import QIcon, QImage, QPixmap from vorta.utils import get_asset, uses_dark_mode diff --git a/tests/conftest.py b/tests/conftest.py index 9350489c..0e788297 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -46,6 +46,12 @@ def qapp(tmpdir_factory): mock_db = SqliteDatabase(str(tmp_db)) vorta.store.connection.init_db(mock_db) + # Needs to be disabled before calling VortaApp() + if sys.platform == 'darwin': + cfg = vorta.store.models.SettingsModel.get(key='check_full_disk_access') + cfg.value = False + cfg.save() + from vorta.application import VortaApp VortaApp.set_borg_details_action = MagicMock() # Can't use pytest-mock in session scope diff --git a/tests/test_archives.py b/tests/test_archives.py index d5c47327..16b0e243 100644 --- a/tests/test_archives.py +++ b/tests/test_archives.py @@ -1,7 +1,7 @@ from collections import namedtuple import psutil import pytest -from PyQt5 import QtCore +from PyQt6 import QtCore import vorta.borg import vorta.utils import vorta.views.archive_tab @@ -58,7 +58,7 @@ def test_repo_prune(qapp, qtbot, mocker, borg_json_output): popen_result = mocker.MagicMock(stdout=stdout, stderr=stderr, returncode=0) mocker.patch.object(vorta.borg.borg_job, 'Popen', return_value=popen_result) - qtbot.mouseClick(tab.bPrune, QtCore.Qt.LeftButton) + qtbot.mouseClick(tab.bPrune, QtCore.Qt.MouseButton.LeftButton) qtbot.waitUntil(lambda: 'Refreshing archives done.' in main.progressText.text(), **pytest._wait_defaults) @@ -73,7 +73,7 @@ def test_repo_compact(qapp, qtbot, mocker, borg_json_output): popen_result = mocker.MagicMock(stdout=stdout, stderr=stderr, returncode=0) mocker.patch.object(vorta.borg.borg_job, 'Popen', return_value=popen_result) - qtbot.mouseClick(tab.compactButton, QtCore.Qt.LeftButton) + qtbot.mouseClick(tab.compactButton, QtCore.Qt.MouseButton.LeftButton) qtbot.waitUntil( lambda: 'compaction freed about 56.00 kB repository space' in main.logText.text(), **pytest._wait_defaults @@ -91,7 +91,7 @@ def test_check(qapp, mocker, borg_json_output, qtbot): popen_result = mocker.MagicMock(stdout=stdout, stderr=stderr, returncode=0) mocker.patch.object(vorta.borg.borg_job, 'Popen', return_value=popen_result) - qtbot.mouseClick(tab.bCheck, QtCore.Qt.LeftButton) + qtbot.mouseClick(tab.bCheck, QtCore.Qt.MouseButton.LeftButton) success_text = 'INFO: Archive consistency check complete' qtbot.waitUntil(lambda: success_text in main.logText.text(), **pytest._wait_defaults) diff --git a/tests/test_diff.py b/tests/test_diff.py index 73929771..31b28b7a 100644 --- a/tests/test_diff.py +++ b/tests/test_diff.py @@ -1,6 +1,6 @@ from pathlib import PurePath import pytest -from PyQt5.QtCore import QDateTime, QItemSelectionModel, Qt +from PyQt6.QtCore import QDateTime, QItemSelectionModel, Qt import vorta.borg import vorta.utils import vorta.views.archive_tab diff --git a/tests/test_extract.py b/tests/test_extract.py index 9f59e7e0..571fa313 100644 --- a/tests/test_extract.py +++ b/tests/test_extract.py @@ -1,4 +1,4 @@ -from PyQt5.QtCore import QModelIndex, Qt +from PyQt6.QtCore import QModelIndex, Qt import vorta.borg from vorta.views.extract_dialog import ExtractTree, FileData, FileType, parse_json_lines from vorta.views.partials.treemodel import FileSystemItem @@ -92,12 +92,12 @@ def test_selection(): c: FileSystemItem[FileData] = ic.internalPointer() select(model, ic) - assert c.data.checkstate == 2 + assert c.data.checkstate == Qt.CheckState(2) assert c.data.checked_children == 0 # Test deselect deselect(model, ic) - assert c.data.checkstate == 0 + assert c.data.checkstate == Qt.CheckState(0) assert c.data.checked_children == 0 # Test select parent as well as children @@ -121,19 +121,19 @@ def test_selection(): iab = model.indexPath(("a", "b")) deselect(model, iab) - assert a.data.checkstate == 1 - assert aa.data.checkstate == 2 - assert ab.data.checkstate == 0 - assert abc.data.checkstate == 0 + assert a.data.checkstate == Qt.CheckState(1) + assert aa.data.checkstate == Qt.CheckState(2) + assert ab.data.checkstate == Qt.CheckState(0) + assert abc.data.checkstate == Qt.CheckState(0) assert a.data.checked_children == 1 assert ab.data.checked_children == 0 # Test deselect item and children deselect(model, ia) - assert a.data.checkstate == 0 - assert aa.data.checkstate == 0 - assert ab.data.checkstate == 0 + assert a.data.checkstate == Qt.CheckState(0) + assert aa.data.checkstate == Qt.CheckState(0) + assert ab.data.checkstate == Qt.CheckState(0) assert a.data.checked_children == 0 assert aa.data.checked_children == 0 @@ -146,9 +146,9 @@ def test_selection(): select(model, iab) select(model, iaac) - assert a.data.checkstate == 1 - assert aa.data.checkstate == 1 - assert ab.data.checkstate == 2 + assert a.data.checkstate == Qt.CheckState(1) + assert aa.data.checkstate == Qt.CheckState(1) + assert ab.data.checkstate == Qt.CheckState(2) assert a.data.checked_children == 2 assert ab.data.checked_children == 2 @@ -159,21 +159,21 @@ def test_selection(): deselect(model, iaa) deselect(model, iab) - assert a.data.checkstate == 0 + assert a.data.checkstate == Qt.CheckState(0) assert a.data.checked_children == 0 # Test select child with deselected parent select(model, iaac) - assert a.data.checkstate == 1 - assert ab.data.checkstate == 0 - assert aa.data.checkstate == 1 + assert a.data.checkstate == Qt.CheckState(1) + assert ab.data.checkstate == Qt.CheckState(0) + assert aa.data.checkstate == Qt.CheckState(1) assert a.data.checked_children == 1 assert ab.data.checked_children == 0 assert aa.data.checked_children == 1 select(model, iaa) - assert a.data.checkstate == 1 + assert a.data.checkstate == Qt.CheckState(1) select(model, iab) - assert a.data.checkstate == 1 + assert a.data.checkstate == Qt.CheckState(1) diff --git a/tests/test_import_export.py b/tests/test_import_export.py index 5e80d0eb..81f970e6 100644 --- a/tests/test_import_export.py +++ b/tests/test_import_export.py @@ -1,8 +1,8 @@ import os from pathlib import Path import pytest -from PyQt5 import QtCore -from PyQt5.QtWidgets import QDialogButtonBox, QFileDialog, QMessageBox +from PyQt6 import QtCore +from PyQt6.QtWidgets import QDialogButtonBox, QFileDialog, QMessageBox from vorta.store.models import BackupProfileModel, SourceFileModel from vorta.views.import_window import ImportWindow @@ -18,7 +18,9 @@ def test_import_success(qapp, qtbot, rootdir, monkeypatch): import_dialog: ImportWindow = main.window import_dialog.overwriteExistingSettings.setChecked(True) - qtbot.mouseClick(import_dialog.buttonBox.button(QDialogButtonBox.Ok), QtCore.Qt.LeftButton) + qtbot.mouseClick( + import_dialog.buttonBox.button(QDialogButtonBox.StandardButton.Ok), QtCore.Qt.MouseButton.LeftButton + ) qtbot.waitSignal(import_dialog.profile_imported, **pytest._wait_defaults) restored_profile = BackupProfileModel.get_or_none(name="Test Profile Restoration") @@ -81,7 +83,9 @@ def test_export_success(qapp, qtbot, tmpdir, monkeypatch): main.profile_export_action() export_dialog = main.window - qtbot.mouseClick(export_dialog.buttonBox.button(QDialogButtonBox.Save), QtCore.Qt.LeftButton) + qtbot.mouseClick( + export_dialog.buttonBox.button(QDialogButtonBox.StandardButton.Save), QtCore.Qt.MouseButton.LeftButton + ) qtbot.waitUntil(lambda: os.path.isfile(FILE_PATH)) assert os.path.isfile(FILE_PATH) @@ -107,7 +111,9 @@ def test_export_fail_unwritable(qapp, qtbot, tmpdir, monkeypatch): main.profile_export_action() export_dialog = main.window - qtbot.mouseClick(export_dialog.buttonBox.button(QDialogButtonBox.Save), QtCore.Qt.LeftButton) + qtbot.mouseClick( + export_dialog.buttonBox.button(QDialogButtonBox.StandardButton.Save), QtCore.Qt.MouseButton.LeftButton + ) assert 'could not be created' in alert_message assert not os.path.isfile(FILE_PATH) diff --git a/tests/test_lock.py b/tests/test_lock.py index 3c0f710f..d472ee24 100644 --- a/tests/test_lock.py +++ b/tests/test_lock.py @@ -1,5 +1,5 @@ import pytest -from PyQt5 import QtCore +from PyQt6 import QtCore import vorta.application import vorta.borg.borg_job @@ -12,7 +12,7 @@ def test_create_perm_error(qapp, borg_json_output, mocker, qtbot): popen_result = mocker.MagicMock(stdout=stdout, stderr=stderr, returncode=0) mocker.patch.object(vorta.borg.borg_job, 'Popen', return_value=popen_result) - qtbot.mouseClick(main.createStartBtn, QtCore.Qt.LeftButton) + qtbot.mouseClick(main.createStartBtn, QtCore.Qt.MouseButton.LeftButton) qtbot.waitUntil(lambda: hasattr(qapp, '_msg'), **pytest._wait_defaults) assert qapp._msg.text().startswith("You do not have permission") @@ -28,7 +28,7 @@ def test_create_lock(qapp, borg_json_output, mocker, qtbot): popen_result = mocker.MagicMock(stdout=stdout, stderr=stderr, returncode=0) mocker.patch.object(vorta.borg.borg_job, 'Popen', return_value=popen_result) - qtbot.mouseClick(main.createStartBtn, QtCore.Qt.LeftButton) + qtbot.mouseClick(main.createStartBtn, QtCore.Qt.MouseButton.LeftButton) qtbot.waitUntil(lambda: hasattr(qapp, '_msg'), **pytest._wait_defaults) assert "The repository at" in qapp._msg.text() diff --git a/tests/test_misc.py b/tests/test_misc.py index a774a2f4..fc0bc9c9 100644 --- a/tests/test_misc.py +++ b/tests/test_misc.py @@ -3,8 +3,8 @@ import sys from pathlib import Path from unittest.mock import Mock import pytest -from PyQt5 import QtCore -from PyQt5.QtWidgets import QCheckBox, QFormLayout +from PyQt6 import QtCore +from PyQt6.QtWidgets import QCheckBox, QFormLayout import vorta.store.models @@ -79,5 +79,7 @@ def _click_toggle_setting(setting, qapp, qtbot): if checkbox.text() == setting: # Have to use pos to click checkbox correctly # https://stackoverflow.com/questions/19418125/pysides-qtest-not-checking-box/24070484#24070484 - qtbot.mouseClick(checkbox, QtCore.Qt.LeftButton, pos=QtCore.QPoint(2, int(checkbox.height() / 2))) + qtbot.mouseClick( + checkbox, QtCore.Qt.MouseButton.LeftButton, pos=QtCore.QPoint(2, int(checkbox.height() / 2)) + ) break diff --git a/tests/test_notifications.py b/tests/test_notifications.py index e355eb6d..3bf869af 100644 --- a/tests/test_notifications.py +++ b/tests/test_notifications.py @@ -1,6 +1,6 @@ import sys import pytest -from PyQt5 import QtDBus +from PyQt6 import QtDBus import vorta.borg import vorta.notifications diff --git a/tests/test_profile.py b/tests/test_profile.py index d0ed4869..f7c58bb7 100644 --- a/tests/test_profile.py +++ b/tests/test_profile.py @@ -1,17 +1,19 @@ -from PyQt5 import QtCore -from PyQt5.QtWidgets import QDialogButtonBox +from PyQt6 import QtCore +from PyQt6.QtWidgets import QDialogButtonBox from vorta.store.models import BackupProfileModel def test_profile_add(qapp, qtbot): main = qapp.main_window - qtbot.mouseClick(main.profileAddButton, QtCore.Qt.LeftButton) + qtbot.mouseClick(main.profileAddButton, QtCore.Qt.MouseButton.LeftButton) add_profile_window = main.window - qtbot.addWidget(add_profile_window) + # qtbot.addWidget(add_profile_window) qtbot.keyClicks(add_profile_window.profileNameField, 'Test Profile') - qtbot.mouseClick(add_profile_window.buttonBox.button(QDialogButtonBox.Save), QtCore.Qt.LeftButton) + qtbot.mouseClick( + add_profile_window.buttonBox.button(QDialogButtonBox.StandardButton.Save), QtCore.Qt.MouseButton.LeftButton + ) assert BackupProfileModel.get_or_none(name='Test Profile') is not None assert main.profileSelector.currentText() == 'Test Profile' @@ -19,14 +21,16 @@ def test_profile_add(qapp, qtbot): def test_profile_edit(qapp, qtbot): main = qapp.main_window - qtbot.mouseClick(main.profileRenameButton, QtCore.Qt.LeftButton) + qtbot.mouseClick(main.profileRenameButton, QtCore.Qt.MouseButton.LeftButton) edit_profile_window = main.window - qtbot.addWidget(edit_profile_window) + # qtbot.addWidget(edit_profile_window) edit_profile_window.profileNameField.setText("") qtbot.keyClicks(edit_profile_window.profileNameField, 'Test Profile') - qtbot.mouseClick(edit_profile_window.buttonBox.button(QDialogButtonBox.Save), QtCore.Qt.LeftButton) + qtbot.mouseClick( + edit_profile_window.buttonBox.button(QDialogButtonBox.StandardButton.Save), QtCore.Qt.MouseButton.LeftButton + ) assert BackupProfileModel.get_or_none(name='Default') is None assert BackupProfileModel.get_or_none(name='Test Profile') is not None diff --git a/tests/test_repo.py b/tests/test_repo.py index f9079871..11f96204 100644 --- a/tests/test_repo.py +++ b/tests/test_repo.py @@ -1,7 +1,8 @@ import os import uuid import pytest -from PyQt5 import QtCore +from PyQt6 import QtCore +from PyQt6.QtWidgets import QMessageBox import vorta.borg.borg_job from vorta.keyring.abc import VortaKeyring from vorta.store.models import ArchiveModel, EventLogModel, RepoModel @@ -20,7 +21,7 @@ def test_repo_add_failures(qapp, qtbot, mocker, borg_json_output): qtbot.keyClicks(add_repo_window.passwordLineEdit, LONG_PASSWORD) qtbot.keyClicks(add_repo_window.confirmLineEdit, LONG_PASSWORD) qtbot.keyClicks(add_repo_window.repoURL, 'aaa') - qtbot.mouseClick(add_repo_window.saveButton, QtCore.Qt.LeftButton) + qtbot.mouseClick(add_repo_window.saveButton, QtCore.Qt.MouseButton.LeftButton) assert add_repo_window.errorText.text().startswith('Please enter a valid') add_repo_window.passwordLineEdit.clear() @@ -28,34 +29,35 @@ def test_repo_add_failures(qapp, qtbot, mocker, borg_json_output): qtbot.keyClicks(add_repo_window.passwordLineEdit, SHORT_PASSWORD) qtbot.keyClicks(add_repo_window.confirmLineEdit, SHORT_PASSWORD) qtbot.keyClicks(add_repo_window.repoURL, 'bbb.com:repo') - qtbot.mouseClick(add_repo_window.saveButton, QtCore.Qt.LeftButton) + qtbot.mouseClick(add_repo_window.saveButton, QtCore.Qt.MouseButton.LeftButton) assert add_repo_window.passwordLabel.text() == 'Passwords must be greater than 8 characters long.' add_repo_window.passwordLineEdit.clear() add_repo_window.confirmLineEdit.clear() qtbot.keyClicks(add_repo_window.passwordLineEdit, SHORT_PASSWORD + "1") qtbot.keyClicks(add_repo_window.confirmLineEdit, SHORT_PASSWORD) - qtbot.mouseClick(add_repo_window.saveButton, QtCore.Qt.LeftButton) + qtbot.mouseClick(add_repo_window.saveButton, QtCore.Qt.MouseButton.LeftButton) assert add_repo_window.passwordLabel.text() == 'Passwords must be identical and greater than 8 characters long.' add_repo_window.passwordLineEdit.clear() add_repo_window.confirmLineEdit.clear() qtbot.keyClicks(add_repo_window.passwordLineEdit, LONG_PASSWORD) qtbot.keyClicks(add_repo_window.confirmLineEdit, SHORT_PASSWORD) - qtbot.mouseClick(add_repo_window.saveButton, QtCore.Qt.LeftButton) + qtbot.mouseClick(add_repo_window.saveButton, QtCore.Qt.MouseButton.LeftButton) assert add_repo_window.passwordLabel.text() == 'Passwords must be identical.' -def test_repo_unlink(qapp, qtbot): +def test_repo_unlink(qapp, qtbot, monkeypatch): main = qapp.main_window tab = main.repoTab + monkeypatch.setattr(QMessageBox, "show", lambda *args: True) main.tabWidget.setCurrentIndex(0) - qtbot.mouseClick(tab.repoRemoveToolbutton, QtCore.Qt.LeftButton) + qtbot.mouseClick(tab.repoRemoveToolbutton, QtCore.Qt.MouseButton.LeftButton) qtbot.waitUntil(lambda: tab.repoSelector.count() == 1, **pytest._wait_defaults) assert RepoModel.select().count() == 0 - qtbot.mouseClick(main.createStartBtn, QtCore.Qt.LeftButton) + qtbot.mouseClick(main.createStartBtn, QtCore.Qt.MouseButton.LeftButton) # -1 is the repo id in this test qtbot.waitUntil(lambda: 'Select a backup repository first.' in main.progressText.text(), **pytest._wait_defaults) assert 'Select a backup repository first.' in main.progressText.text() @@ -105,7 +107,7 @@ def test_repo_add_success(qapp, qtbot, mocker, borg_json_output): def test_ssh_dialog(qapp, qtbot, tmpdir): main = qapp.main_window - qtbot.mouseClick(main.repoTab.bAddSSHKey, QtCore.Qt.LeftButton) + qtbot.mouseClick(main.repoTab.bAddSSHKey, QtCore.Qt.MouseButton.LeftButton) ssh_dialog = main.repoTab._window ssh_dir = tmpdir @@ -135,7 +137,7 @@ def test_create(qapp, borg_json_output, mocker, qtbot): popen_result = mocker.MagicMock(stdout=stdout, stderr=stderr, returncode=0) mocker.patch.object(vorta.borg.borg_job, 'Popen', return_value=popen_result) - qtbot.mouseClick(main.createStartBtn, QtCore.Qt.LeftButton) + qtbot.mouseClick(main.createStartBtn, QtCore.Qt.MouseButton.LeftButton) qtbot.waitUntil(lambda: 'Backup finished.' in main.progressText.text(), **pytest._wait_defaults) qtbot.waitUntil(lambda: main.createStartBtn.isEnabled(), **pytest._wait_defaults) assert EventLogModel.select().count() == 1 diff --git a/tests/test_schedule.py b/tests/test_schedule.py index 7b896929..06654d3e 100644 --- a/tests/test_schedule.py +++ b/tests/test_schedule.py @@ -2,7 +2,7 @@ from datetime import datetime as dt from datetime import timedelta from unittest.mock import MagicMock import pytest -from PyQt5 import QtCore +from PyQt6 import QtCore import vorta.scheduler from vorta.application import VortaApp from vorta.store.models import BackupProfileModel, EventLogModel @@ -31,17 +31,17 @@ def test_schedule_tab(qapp: VortaApp, qtbot, clockmock): qapp.scheduler.schedule_changed.connect(lambda *args: tab.draw_next_scheduled_backup()) # Test - qtbot.mouseClick(tab.scheduleOffRadio, QtCore.Qt.LeftButton) + qtbot.mouseClick(tab.scheduleOffRadio, QtCore.Qt.MouseButton.LeftButton) assert tab.nextBackupDateTimeLabel.text() == 'None scheduled' tab.scheduleIntervalCount.setValue(5) - qtbot.mouseClick(tab.scheduleIntervalRadio, QtCore.Qt.LeftButton) + qtbot.mouseClick(tab.scheduleIntervalRadio, QtCore.Qt.MouseButton.LeftButton) assert "None" not in tab.nextBackupDateTimeLabel.text() tab.scheduleFixedTime.setTime(QtCore.QTime(23, 59)) # Clicking currently broken for this button on github.com only - # qtbot.mouseClick(tab.scheduleFixedRadio, QtCore.Qt.LeftButton) + # qtbot.mouseClick(tab.scheduleFixedRadio, QtCore.Qt.MouseButton.LeftButton) # Workaround for github tab.scheduleFixedRadio.setChecked(True) diff --git a/tests/test_treemodel.py b/tests/test_treemodel.py index 31d249f2..d60c359b 100644 --- a/tests/test_treemodel.py +++ b/tests/test_treemodel.py @@ -1,6 +1,6 @@ from pathlib import PurePath import pytest -from PyQt5.QtCore import QModelIndex +from PyQt6.QtCore import QModelIndex from vorta.views.partials.treemodel import FileSystemItem, FileTreeModel