diff --git a/defs.bzl b/defs.bzl
index 4c75a192b..1042004ac 100644
--- a/defs.bzl
+++ b/defs.bzl
@@ -9,6 +9,7 @@ load("@build_bazel_rules_nodejs//:index.bzl", "node_repositories", "yarn_install
load("@io_bazel_rules_sass//:defs.bzl", "sass_repositories")
load("@com_github_ali5h_rules_pip//:defs.bzl", "pip_import")
load("//pip/pyqt5:defs.bzl", "install_pyqt5")
+load("//pip/pyqt6:defs.bzl", "install_pyqt6")
anki_version = "2.1.49"
@@ -43,6 +44,11 @@ def setup_deps():
python_runtime = "@python//:python",
)
+ install_pyqt6(
+ name = "pyqt6",
+ python_runtime = "@python//:python",
+ )
+
node_repositories(package_json = ["@ankidesktop//:package.json"])
yarn_install(
diff --git a/pip/pyqt6/BUILD.bazel b/pip/pyqt6/BUILD.bazel
new file mode 100644
index 000000000..e69de29bb
diff --git a/pip/pyqt6/defs.bzl b/pip/pyqt6/defs.bzl
new file mode 100644
index 000000000..ca4519cff
--- /dev/null
+++ b/pip/pyqt6/defs.bzl
@@ -0,0 +1,47 @@
+# based off https://github.com/ali5h/rules_pip/blob/master/defs.bzl
+
+pip_vendor_label = Label("@com_github_ali5h_rules_pip//:third_party/py/easy_install.py")
+
+def _execute(repository_ctx, arguments, quiet = False):
+ pip_vendor = str(repository_ctx.path(pip_vendor_label).dirname)
+ return repository_ctx.execute(arguments, environment = {
+ "PYTHONPATH": pip_vendor,
+ }, quiet = quiet)
+
+def _install_pyqt6_impl(repository_ctx):
+ python_interpreter = repository_ctx.attr.python_interpreter
+ if repository_ctx.attr.python_runtime:
+ python_interpreter = repository_ctx.path(repository_ctx.attr.python_runtime)
+
+ args = [
+ python_interpreter,
+ repository_ctx.path(repository_ctx.attr._script),
+ repository_ctx.path("."),
+ ]
+
+ result = _execute(repository_ctx, args, quiet = repository_ctx.attr.quiet)
+ if result.return_code:
+ fail("failed: %s (%s)" % (result.stdout, result.stderr))
+
+install_pyqt6 = repository_rule(
+ attrs = {
+ "python_interpreter": attr.string(default = "python", doc = """
+The command to run the Python interpreter used to invoke pip and unpack the
+wheels.
+"""),
+ "python_runtime": attr.label(doc = """
+The label to the Python run-time interpreted used to invoke pip and unpack the wheels.
+If the label is specified it will overwrite the python_interpreter attribute.
+"""),
+ "_script": attr.label(
+ executable = True,
+ default = Label("//pip/pyqt6:install_pyqt6.py"),
+ cfg = "host",
+ ),
+ "quiet": attr.bool(
+ default = True,
+ doc = "If stdout and stderr should be printed to the terminal.",
+ ),
+ },
+ implementation = _install_pyqt6_impl,
+)
diff --git a/pip/pyqt6/install_pyqt6.py b/pip/pyqt6/install_pyqt6.py
new file mode 100644
index 000000000..a04193c11
--- /dev/null
+++ b/pip/pyqt6/install_pyqt6.py
@@ -0,0 +1,196 @@
+# based on https://github.com/ali5h/rules_pip/blob/master/src/whl.py
+# MIT
+
+"""downloads and parses info of a pkg and generates a BUILD file for it"""
+import argparse
+import glob
+import logging
+import os
+import re
+import shutil
+import subprocess
+import sys
+
+import pkginfo
+
+from pip._internal.commands import create_command
+from pip._vendor import pkg_resources
+
+
+def _create_nspkg_init(dirpath):
+ """Creates an init file to enable namespacing"""
+ if not os.path.exists(dirpath):
+ # Handle missing namespace packages by ignoring them
+ return
+ nspkg_init = os.path.join(dirpath, "__init__.py")
+ with open(nspkg_init, "w") as nspkg:
+ nspkg.write("__path__ = __import__('pkgutil').extend_path(__path__, __name__)")
+
+
+def install_package(pkg, directory, pip_args):
+ """Downloads wheel for a package. Assumes python binary provided has
+ pip and wheel package installed.
+
+ Args:
+ pkg: package name
+ directory: destination directory to download the wheel file in
+ python: python binary path used to run pip command
+ pip_args: extra pip args sent to pip
+ Returns:
+ str: path to the wheel file
+ """
+ pip_args = [
+ "--isolated",
+ "--disable-pip-version-check",
+ "--target",
+ directory,
+ "--no-deps",
+ "--ignore-requires-python",
+ pkg,
+ ] + pip_args
+ cmd = create_command("install")
+ cmd.main(pip_args)
+
+ # need dist-info directory for pkg_resources to be able to find the packages
+ dist_info = glob.glob(os.path.join(directory, "*.dist-info"))[0]
+ # fix namespace packages by adding proper __init__.py files
+ namespace_packages = os.path.join(dist_info, "namespace_packages.txt")
+ if os.path.exists(namespace_packages):
+ with open(namespace_packages) as nspkg:
+ for line in nspkg.readlines():
+ namespace = line.strip().replace(".", os.sep)
+ if namespace:
+ _create_nspkg_init(os.path.join(directory, namespace))
+
+ # PEP 420 -- Implicit Namespace Packages
+ if (sys.version_info[0], sys.version_info[1]) >= (3, 3):
+ for dirpath, dirnames, filenames in os.walk(directory):
+ # we are only interested in dirs with no init file
+ if "__init__.py" in filenames:
+ dirnames[:] = []
+ continue
+ # remove bin and dist-info dirs
+ for ignored in ("bin", os.path.basename(dist_info)):
+ if ignored in dirnames:
+ dirnames.remove(ignored)
+ _create_nspkg_init(dirpath)
+
+ return pkginfo.Wheel(dist_info)
+
+
+def _cleanup(directory, pattern):
+ for p in glob.glob(os.path.join(directory, pattern)):
+ shutil.rmtree(p)
+
+
+fix_none = re.compile(r"(\s*None) =")
+
+
+def copy_and_fix_pyi(source, dest):
+ "Fix broken PyQt types."
+ with open(source) as input_file:
+ with open(dest, "w") as output_file:
+ for line in input_file.readlines():
+ # inheriting from the missing sip.sipwrapper definition
+ # causes missing attributes not to be detected, as it's treating
+ # the class as inheriting from Any
+ line = line.replace("PyQt6.sip.wrapper", "object")
+ line = line.replace("PyQt6.sip.wrapper", "object")
+ # # remove blanket getattr in QObject which also causes missing
+ # # attributes not to be detected
+ if "def __getattr__(self, name: str) -> typing.Any" in line:
+ continue
+ output_file.write(line)
+
+
+def merge_files(root, source):
+ for dirpath, _dirnames, filenames in os.walk(source):
+ target_dir = os.path.join(root, os.path.relpath(dirpath, source))
+ if not os.path.exists(target_dir):
+ os.mkdir(target_dir)
+ for fname in filenames:
+ source_path = os.path.join(dirpath, fname)
+ target_path = os.path.join(target_dir, fname)
+ if not os.path.exists(target_path):
+ if fname.endswith(".pyi"):
+ copy_and_fix_pyi(source_path, target_path)
+ else:
+ shutil.copy2(source_path, target_path)
+
+
+def main():
+ base = sys.argv[1]
+
+ local_site_packages = os.environ.get("PYTHON_SITE_PACKAGES")
+ if local_site_packages:
+ subprocess.run(
+ [
+ "rsync",
+ "-ai",
+ "--include=PyQt**",
+ "--exclude=*",
+ local_site_packages,
+ base + "/",
+ ],
+ check=True,
+ )
+ with open(os.path.join(base, "__init__.py"), "w") as file:
+ pass
+
+ else:
+ packages = [
+ ("pyqt6", "pyqt6==6.2.0"),
+ ("pyqt6-qt6", "pyqt6-qt6==6.2.0"),
+ ("pyqt6-webengine", "pyqt6-webengine==6.2.0"),
+ ("pyqt6-webengine-qt6", "pyqt6-webengine-qt6==6.2.0"),
+ ("pyqt6-sip", "pyqt6_sip==13.1.0"),
+ ]
+
+ for (name, with_version) in packages:
+ # install package in subfolder
+ folder = os.path.join(base, "temp")
+ _pkg = install_package(with_version, folder, [])
+ # merge into parent
+ merge_files(base, folder)
+ shutil.rmtree(folder)
+
+ # add missing py.typed file
+ with open(os.path.join(base, "py.typed"), "w") as file:
+ pass
+
+ result = """
+load("@rules_python//python:defs.bzl", "py_library")
+
+package(default_visibility = ["//visibility:public"])
+
+py_library(
+ name = "pkg",
+ srcs = glob(["**/*.py"]),
+ data = glob(["**/*"], exclude = [
+ "**/*.py",
+ "**/*.pyc",
+ "**/* *",
+ "BUILD",
+ "WORKSPACE",
+ "bin/*",
+ "__pycache__",
+ # these make building slower
+ "Qt/qml/**",
+ "**/*.sip",
+ "**/*.png",
+ ]),
+ # This makes this directory a top-level in the python import
+ # search path for anything that depends on this.
+ imports = ["."],
+)
+"""
+
+ # clean up
+ _cleanup(base, "__pycache__")
+
+ with open(os.path.join(base, "BUILD"), "w") as f:
+ f.write(result)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/pylib/anki/BUILD.bazel b/pylib/anki/BUILD.bazel
index f0c3c0e1f..abb7fe324 100644
--- a/pylib/anki/BUILD.bazel
+++ b/pylib/anki/BUILD.bazel
@@ -72,11 +72,11 @@ py_wheel(
},
platform = select({
"//platforms:windows_x86_64": "win_amd64",
- "//platforms:macos_x86_64": "macosx_10_7_x86_64",
- "//platforms:linux_x86_64": "manylinux2014_x86_64",
- "//platforms:linux_arm64": "manylinux2014_aarch64",
+ "//platforms:macos_x86_64": "macosx_10_13_x86_64",
+ "//platforms:linux_x86_64": "manylinux_2_28_x86_64",
+ "//platforms:linux_arm64": "manylinux_2_28_aarch64",
}),
- python_tag = "cp38",
+ python_tag = "cp39",
python_version = ">=3.9",
requires = [
"beautifulsoup4",
@@ -124,7 +124,10 @@ py_library(
":proto_py",
],
# includes the .pyi files
- data = [":proto_py", "py.typed"],
+ data = [
+ "py.typed",
+ ":proto_py",
+ ],
imports = [".."],
visibility = ["//visibility:public"],
)
diff --git a/pylib/rsbridge/BUILD.bazel b/pylib/rsbridge/BUILD.bazel
index fb81244e7..66502a1c5 100644
--- a/pylib/rsbridge/BUILD.bazel
+++ b/pylib/rsbridge/BUILD.bazel
@@ -18,7 +18,7 @@ rust_library(
): [
"-Clink-arg=-undefined",
"-Clink-arg=dynamic_lookup",
- "-Clink-arg=-mmacosx-version-min=10.7",
+ "-Clink-arg=-mmacosx-version-min=10.13",
],
"//conditions:default": [],
}),
diff --git a/qt/.pylintrc b/qt/.pylintrc
index 3cbdc97e9..6107eeed3 100644
--- a/qt/.pylintrc
+++ b/qt/.pylintrc
@@ -1,6 +1,6 @@
[MASTER]
persistent = no
-extension-pkg-whitelist=PyQt5
+extension-pkg-whitelist=PyQt6
ignore = forms,hooks_gen.py
[TYPECHECK]
diff --git a/qt/BUILD.bazel b/qt/BUILD.bazel
index a2e4bfd65..c07d8cec7 100644
--- a/qt/BUILD.bazel
+++ b/qt/BUILD.bazel
@@ -45,21 +45,21 @@ py_test(
args = [
"aqt",
"$(location mypy.ini)",
- "$(location @pyqt5//:__init__.py)",
+ "$(location @pyqt6//:__init__.py)",
"$(location //pip/stubs:extendsitepkgs)",
],
data = [
"mypy.ini",
"//pip/stubs",
"//pip/stubs:extendsitepkgs",
- "@pyqt5//:__init__.py",
+ "@pyqt6//:__init__.py",
],
env = {"EXTRA_SITE_PACKAGES": "$(location //pip/stubs)"},
main = "tests/run_mypy.py",
deps = [
"//pylib/anki",
"//qt/aqt:aqt_without_data",
- "@pyqt5//:pkg",
+ "@pyqt6//:pkg",
requirement("mypy"),
],
)
@@ -133,6 +133,20 @@ py_binary(
],
)
+py_binary(
+ name = "runanki_qt5",
+ srcs = [
+ "bazelfixes.py",
+ "runanki.py",
+ ],
+ imports = ["."],
+ main = "runanki.py",
+ deps = [
+ "//pylib/anki",
+ "//qt/aqt:aqt_with_data_qt5",
+ ],
+)
+
py_binary(
name = "dmypy",
srcs = [
diff --git a/qt/aqt/BUILD.bazel b/qt/aqt/BUILD.bazel
index e4bac550a..778db0ede 100644
--- a/qt/aqt/BUILD.bazel
+++ b/qt/aqt/BUILD.bazel
@@ -50,7 +50,6 @@ aqt_deps = [
requirement("waitress"),
requirement("send2trash"),
requirement("jsonschema"),
- "@pyqt5//:pkg",
] + select({
"@bazel_tools//src/conditions:host_windows": [
requirement("psutil"),
@@ -66,7 +65,9 @@ py_library(
srcs = _py_srcs_and_forms,
data = aqt_core_data,
visibility = ["//visibility:public"],
- deps = aqt_deps,
+ deps = aqt_deps + [
+ "@pyqt6//:pkg",
+ ],
)
py_library(
@@ -74,7 +75,19 @@ py_library(
srcs = _py_srcs_and_forms,
data = aqt_core_data + ["//qt/aqt/data"],
visibility = ["//visibility:public"],
- deps = aqt_deps,
+ deps = aqt_deps + [
+ "@pyqt6//:pkg",
+ ],
+)
+
+py_library(
+ name = "aqt_with_data_qt5",
+ srcs = _py_srcs_and_forms,
+ data = aqt_core_data + ["//qt/aqt/data"],
+ visibility = ["//visibility:public"],
+ deps = aqt_deps + [
+ "@pyqt5//:pkg",
+ ],
)
py_package(
@@ -94,10 +107,20 @@ py_wheel(
entry_points = {
"console_scripts": ["anki = aqt:run"],
},
+ extra_requires = {
+ "qt5": [
+ "pyqt5>=5.14",
+ "pyqtwebengine",
+ ],
+ "qt6": [
+ "pyqt6>=6.2",
+ "pyqt6-webengine>=6.2",
+ ],
+ },
homepage = "https://apps.ankiweb.net",
license = "License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)",
python_tag = "py3",
- python_version = ">=3.8",
+ python_version = ">=3.9",
requires = [
"beautifulsoup4",
"requests",
@@ -106,12 +129,9 @@ py_wheel(
"flask",
"flask_cors",
"waitress",
- "pyqt5>=5.12",
- "pyqtwebengine",
'psutil; sys.platform == "win32"',
'pywin32; sys.platform == "win32"',
- 'winrt==1.0.20239.1; sys.platform == "win32" and platform_release == "10" and python_version == "3.8"',
- 'winrt; sys.platform == "win32" and platform_release == "10" and python_version >= "3.9"',
+ 'winrt; sys.platform == "win32"',
"anki==" + anki_version,
],
strip_path_prefixes = [
diff --git a/qt/aqt/__init__.py b/qt/aqt/__init__.py
index 4e29129ac..ea2742d7d 100644
--- a/qt/aqt/__init__.py
+++ b/qt/aqt/__init__.py
@@ -99,8 +99,10 @@ class DialogManager:
def open(self, name: str, *args: Any, **kwargs: Any) -> Any:
(creator, instance) = self._dialogs[name]
if instance:
- if instance.windowState() & Qt.WindowMinimized:
- instance.setWindowState(instance.windowState() & ~Qt.WindowMinimized)
+ if instance.windowState() & Qt.WindowState.WindowMinimized:
+ instance.setWindowState(
+ instance.windowState() & ~Qt.WindowState.WindowMinimized
+ )
instance.activateWindow()
instance.raise_()
if hasattr(instance, "reopen"):
@@ -224,9 +226,9 @@ def setupLangAndBackend(
# switch direction for RTL languages
if anki.lang.is_rtl(lang):
- app.setLayoutDirection(Qt.RightToLeft)
+ app.setLayoutDirection(Qt.LayoutDirection.RightToLeft)
else:
- app.setLayoutDirection(Qt.LeftToRight)
+ app.setLayoutDirection(Qt.LayoutDirection.LeftToRight)
# load qt translations
_qtrans = QTranslator()
@@ -238,7 +240,10 @@ def setupLangAndBackend(
os.path.join(aqt_data_folder(), "..", "qt_translations")
)
else:
- qt_dir = QLibraryInfo.location(QLibraryInfo.TranslationsPath)
+ if qtmajor == 5:
+ qt_dir = QLibraryInfo.location(QLibraryInfo.TranslationsPath) # type: ignore
+ else:
+ qt_dir = QLibraryInfo.path(QLibraryInfo.LibraryPath.TranslationsPath)
qt_lang = lang.replace("-", "_")
if _qtrans.load(f"qtbase_{qt_lang}", qt_dir):
app.installTranslator(_qtrans)
@@ -285,7 +290,7 @@ class AnkiApp(QApplication):
def sendMsg(self, txt: str) -> bool:
sock = QLocalSocket(self)
- sock.connectToServer(self.KEY, QIODevice.WriteOnly)
+ sock.connectToServer(self.KEY, QIODevice.OpenModeFlag.WriteOnly)
if not sock.waitForConnected(self.TMOUT):
# first instance or previous instance dead
return False
@@ -315,7 +320,7 @@ class AnkiApp(QApplication):
##################################################
def event(self, evt: QEvent) -> bool:
- if evt.type() == QEvent.FileOpen:
+ if evt.type() == QEvent.Type.FileOpen:
self.appMsg.emit(evt.file() or "raise") # type: ignore
return True
return QApplication.event(self, evt)
@@ -360,20 +365,18 @@ def setupGL(pm: aqt.profiles.ProfileManager) -> None:
# catch opengl errors
def msgHandler(category: Any, ctx: Any, msg: Any) -> None:
- if category == QtDebugMsg:
+ if category == QtMsgType.QtDebugMsg:
category = "debug"
- elif category == QtInfoMsg:
+ elif category == QtMsgType.QtInfoMsg:
category = "info"
- elif category == QtWarningMsg:
+ elif category == QtMsgType.QtWarningMsg:
category = "warning"
- elif category == QtCriticalMsg:
+ elif category == QtMsgType.QtCriticalMsg:
category = "critical"
- elif category == QtDebugMsg:
+ elif category == QtMsgType.QtDebugMsg:
category = "debug"
- elif category == QtFatalMsg:
+ elif category == QtMsgType.QtFatalMsg:
category = "fatal"
- elif category == QtSystemMsg:
- category = "system"
else:
category = "unknown"
context = ""
@@ -407,7 +410,7 @@ def setupGL(pm: aqt.profiles.ProfileManager) -> None:
if isWin:
os.environ["QT_OPENGL"] = driver.value
elif isMac:
- QCoreApplication.setAttribute(Qt.AA_UseSoftwareOpenGL)
+ QCoreApplication.setAttribute(Qt.ApplicationAttribute.AA_UseSoftwareOpenGL)
elif isLin:
os.environ["QT_XCB_FORCE_SOFTWARE_OPENGL"] = "1"
@@ -501,15 +504,15 @@ def _run(argv: Optional[list[str]] = None, exec: bool = True) -> Optional[AnkiAp
os.environ["QT_SCALE_FACTOR"] = str(pm.uiScale())
# opt in to full hidpi support?
- if not os.environ.get("ANKI_NOHIGHDPI"):
- QCoreApplication.setAttribute(Qt.AA_EnableHighDpiScaling)
- QCoreApplication.setAttribute(Qt.AA_UseHighDpiPixmaps)
+ if not os.environ.get("ANKI_NOHIGHDPI") and qtmajor == 5:
+ QCoreApplication.setAttribute(Qt.ApplicationAttribute.AA_EnableHighDpiScaling) # type: ignore
+ QCoreApplication.setAttribute(Qt.ApplicationAttribute.AA_UseHighDpiPixmaps) # type: ignore
os.environ["QT_ENABLE_HIGHDPI_SCALING"] = "1"
os.environ["QT_SCALE_FACTOR_ROUNDING_POLICY"] = "PassThrough"
# Opt into software rendering. Useful for buggy systems.
if os.environ.get("ANKI_SOFTWAREOPENGL"):
- QCoreApplication.setAttribute(Qt.AA_UseSoftwareOpenGL)
+ QCoreApplication.setAttribute(Qt.ApplicationAttribute.AA_UseSoftwareOpenGL)
if (
isWin
@@ -537,11 +540,13 @@ def _run(argv: Optional[list[str]] = None, exec: bool = True) -> Optional[AnkiAp
# disable icons on mac; this must be done before window created
if isMac:
- app.setAttribute(Qt.AA_DontShowIconsInMenus)
+ app.setAttribute(Qt.ApplicationAttribute.AA_DontShowIconsInMenus)
# disable help button in title bar on qt versions that support it
- if isWin and qtminor >= 10:
- QApplication.setAttribute(Qt.AA_DisableWindowContextHelpButton)
+ if isWin and qtmajor == 5 and qtminor >= 10:
+ QApplication.setAttribute(
+ QApplication.Attribute.AA_DisableWindowContextHelpButton # type: ignore
+ )
# proxy configured?
from urllib.request import getproxies, proxy_bypass
@@ -561,7 +566,7 @@ def _run(argv: Optional[list[str]] = None, exec: bool = True) -> Optional[AnkiAp
if disable_proxies:
print("webview proxy use disabled")
proxy = QNetworkProxy()
- proxy.setType(QNetworkProxy.NoProxy)
+ proxy.setType(QNetworkProxy.ProxyType.NoProxy)
QNetworkProxy.setApplicationProxy(proxy)
# we must have a usable temp dir
diff --git a/qt/aqt/about.py b/qt/aqt/about.py
index 357d346ec..3f5d47164 100644
--- a/qt/aqt/about.py
+++ b/qt/aqt/about.py
@@ -88,8 +88,8 @@ def show(mw: aqt.AnkiQt) -> QDialog:
btn = QPushButton(tr.about_copy_debug_info())
qconnect(btn.clicked, onCopy)
- abt.buttonBox.addButton(btn, QDialogButtonBox.ActionRole)
- abt.buttonBox.button(QDialogButtonBox.Ok).setFocus()
+ abt.buttonBox.addButton(btn, QDialogButtonBox.ButtonRole.ActionRole)
+ abt.buttonBox.button(QDialogButtonBox.StandardButton.Ok).setFocus()
# WebView contents
######################################################################
diff --git a/qt/aqt/addcards.py b/qt/aqt/addcards.py
index f5280c233..7d42ba133 100644
--- a/qt/aqt/addcards.py
+++ b/qt/aqt/addcards.py
@@ -35,7 +35,7 @@ from aqt.utils import (
class AddCards(QDialog):
def __init__(self, mw: AnkiQt) -> None:
- QDialog.__init__(self, None, Qt.Window)
+ QDialog.__init__(self, None, Qt.WindowType.Window)
mw.garbage_collect_on_dialog_finish(self)
self.mw = mw
self.col = mw.col
@@ -85,7 +85,7 @@ class AddCards(QDialog):
def setupButtons(self) -> None:
bb = self.form.buttonBox
- ar = QDialogButtonBox.ActionRole
+ ar = QDialogButtonBox.ButtonRole.ActionRole
# add
self.addButton = bb.addButton(tr.actions_add(), ar)
qconnect(self.addButton.clicked, self.add_current_note)
@@ -97,11 +97,11 @@ class AddCards(QDialog):
# close
self.closeButton = QPushButton(tr.actions_close())
self.closeButton.setAutoDefault(False)
- bb.addButton(self.closeButton, QDialogButtonBox.RejectRole)
+ bb.addButton(self.closeButton, QDialogButtonBox.ButtonRole.RejectRole)
# help
self.helpButton = QPushButton(tr.actions_help(), clicked=self.helpRequested) # type: ignore
self.helpButton.setAutoDefault(False)
- bb.addButton(self.helpButton, QDialogButtonBox.HelpRole)
+ bb.addButton(self.helpButton, QDialogButtonBox.ButtonRole.HelpRole)
# history
b = bb.addButton(f"{tr.adding_history()} {downArrow()}", ar)
if isMac:
@@ -266,7 +266,10 @@ class AddCards(QDialog):
def keyPressEvent(self, evt: QKeyEvent) -> None:
"Show answer on RET or register answer."
- if evt.key() in (Qt.Key_Enter, Qt.Key_Return) and self.editor.tags.hasFocus():
+ if (
+ evt.key() in (Qt.Key.Key_Enter, Qt.Key.Key_Return)
+ and self.editor.tags.hasFocus()
+ ):
evt.accept()
return
return QDialog.keyPressEvent(self, evt)
diff --git a/qt/aqt/addons.py b/qt/aqt/addons.py
index 5a94e048a..c8c69b6de 100644
--- a/qt/aqt/addons.py
+++ b/qt/aqt/addons.py
@@ -804,7 +804,7 @@ class AddonsDialog(QDialog):
name = self.name_for_addon_list(addon)
item = QListWidgetItem(name, addonList)
if self.should_grey(addon):
- item.setForeground(Qt.gray)
+ item.setForeground(Qt.GlobalColor.gray)
if addon.dir_name in selected:
item.setSelected(True)
@@ -947,7 +947,7 @@ class GetAddons(QDialog):
self.form = aqt.forms.getaddons.Ui_Dialog()
self.form.setupUi(self)
b = self.form.buttonBox.addButton(
- tr.addons_browse_addons(), QDialogButtonBox.ActionRole
+ tr.addons_browse_addons(), QDialogButtonBox.ButtonRole.ActionRole
)
qconnect(b.clicked, self.onBrowse)
disable_help_button(self)
@@ -1183,7 +1183,7 @@ class ChooseAddonsToUpdateList(QListWidget):
)
self.ignore_check_evt = False
self.setup()
- self.setContextMenuPolicy(Qt.CustomContextMenu)
+ self.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
qconnect(self.itemClicked, self.on_click)
qconnect(self.itemChanged, self.on_check)
qconnect(self.itemDoubleClicked, self.on_double_click)
@@ -1191,7 +1191,9 @@ class ChooseAddonsToUpdateList(QListWidget):
def setup(self) -> None:
header_item = QListWidgetItem(tr.addons_choose_update_update_all(), self)
- header_item.setFlags(Qt.ItemFlag(Qt.ItemIsUserCheckable | Qt.ItemIsEnabled))
+ header_item.setFlags(
+ Qt.ItemFlag(Qt.ItemFlag.ItemIsUserCheckable | Qt.ItemFlag.ItemIsEnabled)
+ )
self.header_item = header_item
for update_info in self.updated_addons:
addon_id = update_info.id
@@ -1204,22 +1206,22 @@ class ChooseAddonsToUpdateList(QListWidget):
addon_label = f"{update_time:%Y-%m-%d} {addon_name}"
item = QListWidgetItem(addon_label, self)
# Not user checkable because it overlaps with itemClicked signal
- item.setFlags(Qt.ItemFlag(Qt.ItemIsEnabled))
+ item.setFlags(Qt.ItemFlag(Qt.ItemFlag.ItemIsEnabled))
if update_enabled:
- item.setCheckState(Qt.Checked)
+ item.setCheckState(Qt.CheckState.Checked)
else:
- item.setCheckState(Qt.Unchecked)
+ item.setCheckState(Qt.CheckState.Unchecked)
item.setData(self.ADDON_ID_ROLE, addon_id)
self.refresh_header_check_state()
def bool_to_check(self, check_bool: bool) -> Qt.CheckState:
if check_bool:
- return Qt.Checked
+ return Qt.CheckState.Checked
else:
- return Qt.Unchecked
+ return Qt.CheckState.Unchecked
def checked(self, item: QListWidgetItem) -> bool:
- return item.checkState() == Qt.Checked
+ return item.checkState() == Qt.CheckState.Checked
def on_click(self, item: QListWidgetItem) -> None:
if item == self.header_item:
@@ -1262,9 +1264,9 @@ class ChooseAddonsToUpdateList(QListWidget):
for i in range(1, self.count()):
item = self.item(i)
if not self.checked(item):
- self.check_item(self.header_item, Qt.Unchecked)
+ self.check_item(self.header_item, Qt.CheckState.Unchecked)
return
- self.check_item(self.header_item, Qt.Checked)
+ self.check_item(self.header_item, Qt.CheckState.Checked)
def get_selected_addon_ids(self) -> list[int]:
addon_ids = []
@@ -1290,7 +1292,7 @@ class ChooseAddonsToUpdateDialog(QDialog):
) -> None:
QDialog.__init__(self, parent)
self.setWindowTitle(tr.addons_choose_update_window_title())
- self.setWindowModality(Qt.WindowModal)
+ self.setWindowModality(Qt.WindowModality.WindowModal)
self.mgr = mgr
self.updated_addons = updated_addons
self.setup()
@@ -1306,9 +1308,14 @@ class ChooseAddonsToUpdateDialog(QDialog):
layout.addWidget(addons_list_widget)
self.addons_list_widget = addons_list_widget
- button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) # type: ignore
- qconnect(button_box.button(QDialogButtonBox.Ok).clicked, self.accept)
- qconnect(button_box.button(QDialogButtonBox.Cancel).clicked, self.reject)
+ button_box = QDialogButtonBox(QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel) # type: ignore
+ qconnect(
+ button_box.button(QDialogButtonBox.StandardButton.Ok).clicked, self.accept
+ )
+ qconnect(
+ button_box.button(QDialogButtonBox.StandardButton.Cancel).clicked,
+ self.reject,
+ )
layout.addWidget(button_box)
self.setLayout(layout)
@@ -1317,7 +1324,7 @@ class ChooseAddonsToUpdateDialog(QDialog):
ret = self.exec()
saveGeom(self, "addonsChooseUpdate")
self.addons_list_widget.save_check_state()
- if ret == QDialog.Accepted:
+ if ret == QDialog.DialogCode.Accepted:
return self.addons_list_widget.get_selected_addon_ids()
else:
return []
@@ -1475,7 +1482,9 @@ class ConfigEditor(QDialog):
self.mgr = dlg.mgr
self.form = aqt.forms.addonconf.Ui_Dialog()
self.form.setupUi(self)
- restore = self.form.buttonBox.button(QDialogButtonBox.RestoreDefaults)
+ restore = self.form.buttonBox.button(
+ QDialogButtonBox.StandardButton.RestoreDefaults
+ )
qconnect(restore.clicked, self.onRestoreDefaults)
self.setupFonts()
self.updateHelp()
@@ -1498,7 +1507,7 @@ class ConfigEditor(QDialog):
tooltip(tr.addons_restored_defaults(), parent=self)
def setupFonts(self) -> None:
- font_mono = QFontDatabase.systemFont(QFontDatabase.FixedFont)
+ font_mono = QFontDatabase.systemFont(QFontDatabase.SystemFont.FixedFont)
font_mono.setPointSize(font_mono.pointSize() + 1)
self.form.editor.setFont(font_mono)
@@ -1600,9 +1609,12 @@ def installAddonPackages(
parent=parent,
title=tr.addons_install_anki_addon(),
type="warning",
- customBtns=[QMessageBox.No, QMessageBox.Yes],
+ customBtns=[
+ QMessageBox.StandardButton.No,
+ QMessageBox.StandardButton.Yes,
+ ],
)
- == QMessageBox.Yes
+ == QMessageBox.StandardButton.Yes
):
return False
diff --git a/qt/aqt/browser/browser.py b/qt/aqt/browser/browser.py
index 34a6133fe..1c90beeaf 100644
--- a/qt/aqt/browser/browser.py
+++ b/qt/aqt/browser/browser.py
@@ -104,7 +104,7 @@ class Browser(QMainWindow):
search -- set and perform search; caller must ensure validity
"""
- QMainWindow.__init__(self, None, Qt.Window)
+ QMainWindow.__init__(self, None, Qt.WindowType.Window)
self.mw = mw
self.col = self.mw.col
self.lastFilter = ""
@@ -255,7 +255,7 @@ class Browser(QMainWindow):
onsuccess()
def keyPressEvent(self, evt: QKeyEvent) -> None:
- if evt.key() == Qt.Key_Escape:
+ if evt.key() == Qt.Key.Key_Escape:
self.close()
else:
super().keyPressEvent(evt)
@@ -490,9 +490,9 @@ class Browser(QMainWindow):
def setupSidebar(self) -> None:
dw = self.sidebarDockWidget = QDockWidget(tr.browsing_sidebar(), self)
- dw.setFeatures(QDockWidget.NoDockWidgetFeatures)
+ dw.setFeatures(QDockWidget.DockWidgetFeature.NoDockWidgetFeatures)
dw.setObjectName("Sidebar")
- dw.setAllowedAreas(Qt.LeftDockWidgetArea)
+ dw.setAllowedAreas(Qt.DockWidgetArea.LeftDockWidgetArea)
self.sidebar = SidebarTreeView(self)
self.sidebarTree = self.sidebar # legacy alias
@@ -513,7 +513,7 @@ class Browser(QMainWindow):
self.sidebarDockWidget.setFloating(False)
self.sidebarDockWidget.setTitleBarWidget(QWidget())
- self.addDockWidget(Qt.LeftDockWidgetArea, dw)
+ self.addDockWidget(Qt.DockWidgetArea.LeftDockWidgetArea, dw)
# schedule sidebar to refresh after browser window has loaded, so the
# UI is more responsive
diff --git a/qt/aqt/browser/card_info.py b/qt/aqt/browser/card_info.py
index 046870362..b059bfaa5 100644
--- a/qt/aqt/browser/card_info.py
+++ b/qt/aqt/browser/card_info.py
@@ -28,7 +28,7 @@ class CardInfoDialog(QDialog):
self.show()
def _setup_ui(self, card_id: CardId) -> None:
- self.setWindowModality(Qt.ApplicationModal)
+ self.setWindowModality(Qt.WindowModality.ApplicationModal)
self.mw.garbage_collect_on_dialog_finish(self)
disable_help_button(self)
restoreGeom(self, self.GEOMETRY_KEY)
@@ -40,7 +40,7 @@ class CardInfoDialog(QDialog):
layout = QVBoxLayout()
layout.setContentsMargins(0, 0, 0, 0)
layout.addWidget(self.web)
- buttons = QDialogButtonBox(QDialogButtonBox.Close)
+ buttons = QDialogButtonBox(QDialogButtonBox.StandardButton.Close)
buttons.setContentsMargins(10, 0, 10, 10)
layout.addWidget(buttons)
qconnect(buttons.rejected, self.reject)
diff --git a/qt/aqt/browser/find_and_replace.py b/qt/aqt/browser/find_and_replace.py
index 60b2f3d77..39add80e6 100644
--- a/qt/aqt/browser/find_and_replace.py
+++ b/qt/aqt/browser/find_and_replace.py
@@ -73,16 +73,18 @@ class FindAndReplaceDialog(QDialog):
disable_help_button(self)
self.form = aqt.forms.findreplace.Ui_Dialog()
self.form.setupUi(self)
- self.setWindowModality(Qt.WindowModal)
+ self.setWindowModality(Qt.WindowModality.WindowModal)
self._find_history = restore_combo_history(
self.form.find, self.COMBO_NAME + "Find"
)
- self.form.find.completer().setCaseSensitivity(Qt.CaseSensitive)
+ self.form.find.completer().setCaseSensitivity(Qt.CaseSensitivity.CaseSensitive)
self._replace_history = restore_combo_history(
self.form.replace, self.COMBO_NAME + "Replace"
)
- self.form.replace.completer().setCaseSensitivity(Qt.CaseSensitive)
+ self.form.replace.completer().setCaseSensitivity(
+ Qt.CaseSensitivity.CaseSensitive
+ )
if not self.note_ids:
# no selected notes to affect
diff --git a/qt/aqt/browser/find_duplicates.py b/qt/aqt/browser/find_duplicates.py
index bd5c3237d..4af17cfe2 100644
--- a/qt/aqt/browser/find_duplicates.py
+++ b/qt/aqt/browser/find_duplicates.py
@@ -71,7 +71,7 @@ class FindDuplicatesDialog(QDialog):
).run_in_background()
search = form.buttonBox.addButton(
- tr.actions_search(), QDialogButtonBox.ActionRole
+ tr.actions_search(), QDialogButtonBox.ButtonRole.ActionRole
)
qconnect(search.clicked, on_click)
self.show()
@@ -80,7 +80,7 @@ class FindDuplicatesDialog(QDialog):
self._dupes = dupes
if not self._dupesButton:
self._dupesButton = b = self.form.buttonBox.addButton(
- tr.browsing_tag_duplicates(), QDialogButtonBox.ActionRole
+ tr.browsing_tag_duplicates(), QDialogButtonBox.ButtonRole.ActionRole
)
qconnect(b.clicked, self._tag_duplicates)
text = ""
diff --git a/qt/aqt/browser/previewer.py b/qt/aqt/browser/previewer.py
index 774354070..190d65ecb 100644
--- a/qt/aqt/browser/previewer.py
+++ b/qt/aqt/browser/previewer.py
@@ -46,14 +46,14 @@ class Previewer(QDialog):
def __init__(
self, parent: QWidget, mw: AnkiQt, on_close: Callable[[], None]
) -> None:
- super().__init__(None, Qt.Window)
+ super().__init__(None, Qt.WindowType.Window)
mw.garbage_collect_on_dialog_finish(self)
self._open = True
self._parent = parent
self._close_callback = on_close
self.mw = mw
icon = QIcon()
- icon.addPixmap(QPixmap("icons:anki.png"), QIcon.Normal, QIcon.Off)
+ icon.addPixmap(QPixmap("icons:anki.png"), QIcon.Mode.Normal, QIcon.State.Off)
disable_help_button(self)
self.setWindowIcon(icon)
@@ -86,7 +86,7 @@ class Previewer(QDialog):
self.bbox = QDialogButtonBox()
self._replay = self.bbox.addButton(
- tr.actions_replay_audio(), QDialogButtonBox.ActionRole
+ tr.actions_replay_audio(), QDialogButtonBox.ButtonRole.ActionRole
)
self._replay.setAutoDefault(False)
self._replay.setShortcut(QKeySequence("R"))
@@ -96,7 +96,7 @@ class Previewer(QDialog):
both_sides_button = QCheckBox(tr.qt_misc_back_side_only())
both_sides_button.setShortcut(QKeySequence("B"))
both_sides_button.setToolTip(tr.actions_shortcut_key(val="B"))
- self.bbox.addButton(both_sides_button, QDialogButtonBox.ActionRole)
+ self.bbox.addButton(both_sides_button, QDialogButtonBox.ButtonRole.ActionRole)
self._show_both_sides = self.mw.col.get_config_bool(
Config.Bool.PREVIEW_BOTH_SIDES
)
@@ -266,12 +266,12 @@ class MultiCardPreviewer(Previewer):
def _create_gui(self) -> None:
super()._create_gui()
- self._prev = self.bbox.addButton("<", QDialogButtonBox.ActionRole)
+ self._prev = self.bbox.addButton("<", QDialogButtonBox.ButtonRole.ActionRole)
self._prev.setAutoDefault(False)
self._prev.setShortcut(QKeySequence("Left"))
self._prev.setToolTip(tr.qt_misc_shortcut_key_left_arrow())
- self._next = self.bbox.addButton(">", QDialogButtonBox.ActionRole)
+ self._next = self.bbox.addButton(">", QDialogButtonBox.ButtonRole.ActionRole)
self._next.setAutoDefault(True)
self._next.setShortcut(QKeySequence("Right"))
self._next.setToolTip(tr.qt_misc_shortcut_key_right_arrow_or_enter())
diff --git a/qt/aqt/browser/sidebar/model.py b/qt/aqt/browser/sidebar/model.py
index 6376f176f..e9b3062d3 100644
--- a/qt/aqt/browser/sidebar/model.py
+++ b/qt/aqt/browser/sidebar/model.py
@@ -76,35 +76,48 @@ class SidebarModel(QAbstractItemModel):
return self.createIndex(row, 0, parentItem)
- def data(self, index: QModelIndex, role: int = Qt.DisplayRole) -> QVariant:
+ def data(
+ self, index: QModelIndex, role: int = Qt.ItemDataRole.DisplayRole
+ ) -> QVariant:
if not index.isValid():
return QVariant()
- if role not in (Qt.DisplayRole, Qt.DecorationRole, Qt.ToolTipRole, Qt.EditRole):
+ if role not in (
+ Qt.ItemDataRole.DisplayRole,
+ Qt.ItemDataRole.DecorationRole,
+ Qt.ItemDataRole.ToolTipRole,
+ Qt.ItemDataRole.EditRole,
+ ):
return QVariant()
item: SidebarItem = index.internalPointer()
- if role in (Qt.DisplayRole, Qt.EditRole):
+ if role in (Qt.ItemDataRole.DisplayRole, Qt.ItemDataRole.EditRole):
return QVariant(item.name)
- if role == Qt.ToolTipRole:
+ if role == Qt.ItemDataRole.ToolTipRole:
return QVariant(item.tooltip)
return QVariant(theme_manager.icon_from_resources(item.icon))
- def setData(self, index: QModelIndex, text: str, _role: int = Qt.EditRole) -> bool:
+ def setData(
+ self, index: QModelIndex, text: str, _role: int = Qt.ItemDataRole.EditRole
+ ) -> bool:
return self.sidebar._on_rename(index.internalPointer(), text)
- def supportedDropActions(self) -> Qt.DropActions:
- return cast(Qt.DropActions, Qt.MoveAction)
+ def supportedDropActions(self) -> Qt.DropAction:
+ return cast(Qt.DropAction, Qt.DropAction.MoveAction)
- def flags(self, index: QModelIndex) -> Qt.ItemFlags:
+ def flags(self, index: QModelIndex) -> Qt.ItemFlag:
if not index.isValid():
- return cast(Qt.ItemFlags, Qt.ItemIsEnabled)
- flags = Qt.ItemIsEnabled | Qt.ItemIsSelectable | Qt.ItemIsDragEnabled
+ return cast(Qt.ItemFlag, Qt.ItemFlag.ItemIsEnabled)
+ flags = (
+ Qt.ItemFlag.ItemIsEnabled
+ | Qt.ItemFlag.ItemIsSelectable
+ | Qt.ItemFlag.ItemIsDragEnabled
+ )
item: SidebarItem = index.internalPointer()
if item.item_type in self.sidebar.valid_drop_types:
- flags |= Qt.ItemIsDropEnabled
+ flags |= Qt.ItemFlag.ItemIsDropEnabled
if item.item_type.is_editable():
- flags |= Qt.ItemIsEditable
+ flags |= Qt.ItemFlag.ItemIsEditable
- return cast(Qt.ItemFlags, flags)
+ return flags
diff --git a/qt/aqt/browser/sidebar/searchbar.py b/qt/aqt/browser/sidebar/searchbar.py
index b06ad7fcb..ae08f22fb 100644
--- a/qt/aqt/browser/sidebar/searchbar.py
+++ b/qt/aqt/browser/sidebar/searchbar.py
@@ -43,9 +43,9 @@ class SidebarSearchBar(QLineEdit):
self.sidebar.search_for(self.text())
def keyPressEvent(self, evt: QKeyEvent) -> None:
- if evt.key() in (Qt.Key_Up, Qt.Key_Down):
+ if evt.key() in (Qt.Key.Key_Up, Qt.Key.Key_Down):
self.sidebar.setFocus()
- elif evt.key() in (Qt.Key_Enter, Qt.Key_Return):
+ elif evt.key() in (Qt.Key.Key_Enter, Qt.Key.Key_Return):
self.onSearch()
else:
QLineEdit.keyPressEvent(self, evt)
diff --git a/qt/aqt/browser/sidebar/toolbar.py b/qt/aqt/browser/sidebar/toolbar.py
index 61a0b0b5e..3f544682d 100644
--- a/qt/aqt/browser/sidebar/toolbar.py
+++ b/qt/aqt/browser/sidebar/toolbar.py
@@ -29,7 +29,7 @@ class SidebarToolbar(QToolBar):
qconnect(self._action_group.triggered, self._on_action_group_triggered)
self._setup_tools()
self.setIconSize(QSize(16, 16))
- self.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
+ self.setSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Fixed)
self.setStyle(QStyleFactory.create("fusion"))
def _setup_tools(self) -> None:
diff --git a/qt/aqt/browser/sidebar/tree.py b/qt/aqt/browser/sidebar/tree.py
index c48e45491..02be98c58 100644
--- a/qt/aqt/browser/sidebar/tree.py
+++ b/qt/aqt/browser/sidebar/tree.py
@@ -79,14 +79,14 @@ class SidebarTreeView(QTreeView):
self.valid_drop_types: tuple[SidebarItemType, ...] = ()
self._refresh_needed = False
- self.setContextMenuPolicy(Qt.CustomContextMenu)
+ self.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
self.customContextMenuRequested.connect(self.onContextMenu) # type: ignore
self.setUniformRowHeights(True)
self.setHeaderHidden(True)
self.setIndentation(15)
self.setAutoExpandDelay(600)
self.setDragDropOverwriteMode(False)
- self.setEditTriggers(QAbstractItemView.EditKeyPressed)
+ self.setEditTriggers(QAbstractItemView.EditTrigger.EditKeyPressed)
qconnect(self.expanded, self._on_expansion)
qconnect(self.collapsed, self._on_collapse)
@@ -122,12 +122,12 @@ class SidebarTreeView(QTreeView):
def tool(self, tool: SidebarTool) -> None:
self._tool = tool
if tool == SidebarTool.SEARCH:
- selection_mode = QAbstractItemView.SingleSelection
- drag_drop_mode = QAbstractItemView.NoDragDrop
+ selection_mode = QAbstractItemView.SelectionMode.SingleSelection
+ drag_drop_mode = QAbstractItemView.DragDropMode.NoDragDrop
double_click_expands = False
else:
- selection_mode = QAbstractItemView.ExtendedSelection
- drag_drop_mode = QAbstractItemView.InternalMove
+ selection_mode = QAbstractItemView.SelectionMode.ExtendedSelection
+ drag_drop_mode = QAbstractItemView.DragDropMode.InternalMove
double_click_expands = True
self.setSelectionMode(selection_mode)
self.setDragDropMode(drag_drop_mode)
@@ -191,9 +191,9 @@ class SidebarTreeView(QTreeView):
if current := self.find_item(current.has_same_id):
index = self.model().index_for_item(current)
self.selectionModel().setCurrentIndex(
- index, QItemSelectionModel.SelectCurrent
+ index, QItemSelectionModel.SelectionFlag.SelectCurrent
)
- self.scrollTo(index, QAbstractItemView.PositionAtCenter)
+ self.scrollTo(index, QAbstractItemView.ScrollHint.PositionAtCenter)
def find_item(
self,
@@ -247,9 +247,12 @@ class SidebarTreeView(QTreeView):
self.setExpanded(idx, True)
if item.is_highlighted() and scroll_to_first_match:
self.selectionModel().setCurrentIndex(
- idx, QItemSelectionModel.SelectCurrent
+ idx,
+ QItemSelectionModel.SelectionFlag.SelectCurrent,
+ )
+ self.scrollTo(
+ idx, QAbstractItemView.ScrollHint.PositionAtCenter
)
- self.scrollTo(idx, QAbstractItemView.PositionAtCenter)
scroll_to_first_match = False
expand_node(parent or QModelIndex())
@@ -301,22 +304,29 @@ class SidebarTreeView(QTreeView):
def dropEvent(self, event: QDropEvent) -> None:
model = self.model()
- target_item = model.item_for_index(self.indexAt(event.pos()))
+ if qtmajor == 5:
+ pos = event.pos() # type: ignore
+ else:
+ pos = event.position().toPoint()
+ target_item = model.item_for_index(self.indexAt(pos))
if self.handle_drag_drop(self._selected_items(), target_item):
event.acceptProposedAction()
def mouseReleaseEvent(self, event: QMouseEvent) -> None:
super().mouseReleaseEvent(event)
- if self.tool == SidebarTool.SEARCH and event.button() == Qt.LeftButton:
+ if (
+ self.tool == SidebarTool.SEARCH
+ and event.button() == Qt.MouseButton.LeftButton
+ ):
if (index := self.currentIndex()) == self.indexAt(event.pos()):
self._on_search(index)
def keyPressEvent(self, event: QKeyEvent) -> None:
index = self.currentIndex()
- if event.key() in (Qt.Key_Return, Qt.Key_Enter):
+ if event.key() in (Qt.Key.Key_Return, Qt.Key.Key_Enter):
if not self.isPersistentEditorOpen(index):
self._on_search(index)
- elif event.key() == Qt.Key_Delete:
+ elif event.key() == Qt.Key.Key_Delete:
self._on_delete_key(index)
else:
super().keyPressEvent(event)
diff --git a/qt/aqt/browser/table/model.py b/qt/aqt/browser/table/model.py
index f2ab6270b..e997bf230 100644
--- a/qt/aqt/browser/table/model.py
+++ b/qt/aqt/browser/table/model.py
@@ -3,7 +3,7 @@
from __future__ import annotations
import time
-from typing import Any, Callable, Sequence, cast
+from typing import Any, Callable, Sequence
import aqt
from anki.cards import Card, CardId
@@ -307,7 +307,7 @@ class DataModel(QAbstractTableModel):
def data(self, index: QModelIndex = QModelIndex(), role: int = 0) -> Any:
if not index.isValid():
return QVariant()
- if role == Qt.FontRole:
+ if role == Qt.ItemDataRole.FontRole:
if not self.column_at(index).uses_cell_font:
return QVariant()
qfont = QFont()
@@ -315,30 +315,33 @@ class DataModel(QAbstractTableModel):
qfont.setFamily(row.font_name)
qfont.setPixelSize(row.font_size)
return qfont
- elif role == Qt.TextAlignmentRole:
- align: Qt.AlignmentFlag | int = Qt.AlignVCenter
+ elif role == Qt.ItemDataRole.TextAlignmentRole:
+ align: Qt.AlignmentFlag | int = Qt.AlignmentFlag.AlignVCenter
if self.column_at(index).alignment == Columns.ALIGNMENT_CENTER:
- align |= Qt.AlignHCenter
+ align |= Qt.AlignmentFlag.AlignHCenter
return align
- elif role == Qt.DisplayRole:
+ elif role == Qt.ItemDataRole.DisplayRole:
return self.get_cell(index).text
- elif role == Qt.ToolTipRole and self._want_tooltips:
+ elif role == Qt.ItemDataRole.ToolTipRole and self._want_tooltips:
return self.get_cell(index).text
return QVariant()
def headerData(
self, section: int, orientation: Qt.Orientation, role: int = 0
) -> str | None:
- if orientation == Qt.Horizontal and role == Qt.DisplayRole:
+ if (
+ orientation == Qt.Orientation.Horizontal
+ and role == Qt.ItemDataRole.DisplayRole
+ ):
return self._state.column_label(self.column_at_section(section))
return None
- def flags(self, index: QModelIndex) -> Qt.ItemFlags:
+ def flags(self, index: QModelIndex) -> Qt.ItemFlag:
# shortcut for large selections (Ctrl+A) to avoid fetching large numbers of rows at once
if row := self.get_cached_row(index):
if row.is_deleted:
- return Qt.ItemFlags(Qt.NoItemFlags)
- return cast(Qt.ItemFlags, Qt.ItemIsEnabled | Qt.ItemIsSelectable)
+ return Qt.ItemFlag(Qt.ItemFlag.NoItemFlags)
+ return Qt.ItemFlag.ItemIsEnabled | Qt.ItemFlag.ItemIsSelectable
def addon_column_fillin(key: str) -> Column:
diff --git a/qt/aqt/browser/table/table.py b/qt/aqt/browser/table/table.py
index f1eef337a..d08858b46 100644
--- a/qt/aqt/browser/table/table.py
+++ b/qt/aqt/browser/table/table.py
@@ -2,7 +2,7 @@
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
from __future__ import annotations
-from typing import Any, Callable, Sequence, cast
+from typing import Any, Callable, Sequence
import aqt
import aqt.forms
@@ -127,10 +127,8 @@ class Table:
self.select_all()
self._view.selectionModel().select(
selection,
- cast(
- QItemSelectionModel.SelectionFlags,
- QItemSelectionModel.Deselect | QItemSelectionModel.Rows,
- ),
+ QItemSelectionModel.SelectionFlag.Deselect
+ | QItemSelectionModel.SelectionFlag.Rows,
)
def select_single_card(self, card_id: CardId) -> None:
@@ -202,10 +200,10 @@ class Table:
# Move cursor
def to_previous_row(self) -> None:
- self._move_current(QAbstractItemView.MoveUp)
+ self._move_current(QAbstractItemView.CursorAction.MoveUp)
def to_next_row(self) -> None:
- self._move_current(QAbstractItemView.MoveDown)
+ self._move_current(QAbstractItemView.CursorAction.MoveDown)
def to_first_row(self) -> None:
self._move_current_to_row(0)
@@ -248,7 +246,8 @@ class Table:
def clear_current(self) -> None:
self._view.selectionModel().setCurrentIndex(
- QModelIndex(), QItemSelectionModel.NoUpdate
+ QModelIndex(),
+ QItemSelectionModel.SelectionFlag.NoUpdate,
)
# Private methods
@@ -268,7 +267,10 @@ class Table:
index = self._model.index(
row, self._view.horizontalHeader().logicalIndex(column)
)
- self._view.selectionModel().setCurrentIndex(index, QItemSelectionModel.NoUpdate)
+ self._view.selectionModel().setCurrentIndex(
+ index,
+ QItemSelectionModel.SelectionFlag.NoUpdate,
+ )
def _reset_selection(self) -> None:
"""Remove selection and focus without emitting signals.
@@ -286,7 +288,9 @@ class Table:
self._model.index(row, 0),
self._model.index(row, self._model.len_columns() - 1),
)
- self._view.selectionModel().select(selection, QItemSelectionModel.SelectCurrent)
+ self._view.selectionModel().select(
+ selection, QItemSelectionModel.SelectionFlag.SelectCurrent
+ )
def _set_sort_indicator(self) -> None:
hh = self._view.horizontalHeader()
@@ -295,9 +299,9 @@ class Table:
hh.setSortIndicatorShown(False)
return
if self._state.sort_backwards:
- order = Qt.DescendingOrder
+ order = Qt.SortOrder.DescendingOrder
else:
- order = Qt.AscendingOrder
+ order = Qt.SortOrder.AscendingOrder
hh.blockSignals(True)
hh.setSortIndicator(index, order)
hh.blockSignals(False)
@@ -305,9 +309,10 @@ class Table:
def _set_column_sizes(self) -> None:
hh = self._view.horizontalHeader()
- hh.setSectionResizeMode(QHeaderView.Interactive)
+ hh.setSectionResizeMode(QHeaderView.ResizeMode.Interactive)
hh.setSectionResizeMode(
- hh.logicalIndex(self._model.len_columns() - 1), QHeaderView.Stretch
+ hh.logicalIndex(self._model.len_columns() - 1),
+ QHeaderView.ResizeMode.Stretch,
)
# this must be set post-resize or it doesn't work
hh.setCascadingSectionResizes(False)
@@ -334,7 +339,7 @@ class Table:
)
qconnect(self._view.selectionModel().currentChanged, self._on_current_changed)
self._view.setWordWrap(False)
- self._view.setHorizontalScrollMode(QAbstractItemView.ScrollPerPixel)
+ self._view.setHorizontalScrollMode(QAbstractItemView.ScrollMode.ScrollPerPixel)
self._view.horizontalScrollBar().setSingleStep(10)
self._update_font()
if not theme_manager.night_mode:
@@ -346,7 +351,7 @@ class Table:
self._view.setStyleSheet(
f"QTableView {{ gridline-color: {colors.FRAME_BG} }}"
)
- self._view.setContextMenuPolicy(Qt.CustomContextMenu)
+ self._view.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
qconnect(self._view.customContextMenuRequested, self._on_context_menu)
def _update_font(self) -> None:
@@ -369,7 +374,7 @@ class Table:
hh.setHighlightSections(False)
hh.setMinimumSectionSize(50)
hh.setSectionsMovable(True)
- hh.setContextMenuPolicy(Qt.CustomContextMenu)
+ hh.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
self._restore_header()
qconnect(hh.customContextMenuRequested, self._on_header_context)
qconnect(hh.sortIndicatorChanged, self._on_sort_column_changed)
@@ -573,7 +578,9 @@ class Table:
visible = top_border >= 0 and bottom_border < self._view.viewport().height()
if not visible or scroll_even_if_visible:
horizontal = self._view.horizontalScrollBar().value()
- self._view.scrollTo(self._model.index(row, 0), self._view.PositionAtTop)
+ self._view.scrollTo(
+ self._model.index(row, 0), QAbstractItemView.ScrollHint.PositionAtTop
+ )
self._view.horizontalScrollBar().setValue(horizontal)
def _scroll_to_column(self, column: int) -> None:
@@ -583,26 +590,26 @@ class Table:
if not visible:
vertical = self._view.verticalScrollBar().value()
self._view.scrollTo(
- self._model.index(0, column), self._view.PositionAtCenter
+ self._model.index(0, column),
+ QAbstractItemView.ScrollHint.PositionAtCenter,
)
self._view.verticalScrollBar().setValue(vertical)
- def _move_current(self, direction: int, index: QModelIndex = None) -> None:
+ def _move_current(
+ self, direction: QAbstractItemView.CursorAction, index: QModelIndex = None
+ ) -> None:
if not self.has_current():
return
if index is None:
index = self._view.moveCursor(
- cast(QAbstractItemView.CursorAction, direction),
+ direction,
self.browser.mw.app.keyboardModifiers(),
)
self._view.selectionModel().setCurrentIndex(
index,
- cast(
- QItemSelectionModel.SelectionFlag,
- QItemSelectionModel.Clear
- | QItemSelectionModel.Select
- | QItemSelectionModel.Rows,
- ),
+ QItemSelectionModel.SelectionFlag.Clear
+ | QItemSelectionModel.SelectionFlag.Select
+ | QItemSelectionModel.SelectionFlag.Rows,
)
def _move_current_to_row(self, row: int) -> None:
@@ -614,10 +621,8 @@ class Table:
selection = QItemSelection(new, old)
self._view.selectionModel().select(
selection,
- cast(
- QItemSelectionModel.SelectionFlag,
- QItemSelectionModel.SelectCurrent | QItemSelectionModel.Rows,
- ),
+ QItemSelectionModel.SelectionFlag.SelectCurrent
+ | QItemSelectionModel.SelectionFlag.Rows,
)
@@ -630,7 +635,7 @@ class StatusDelegate(QItemDelegate):
self, painter: QPainter, option: QStyleOptionViewItem, index: QModelIndex
) -> None:
if self._model.get_cell(index).is_rtl:
- option.direction = Qt.RightToLeft
+ option.direction = Qt.LayoutDirection.RightToLeft
if row_color := self._model.get_row(index).color:
brush = QBrush(theme_manager.qcolor(row_color))
painter.save()
diff --git a/qt/aqt/changenotetype.py b/qt/aqt/changenotetype.py
index 89be796fb..d66209931 100644
--- a/qt/aqt/changenotetype.py
+++ b/qt/aqt/changenotetype.py
@@ -43,7 +43,7 @@ class ChangeNotetypeDialog(QDialog):
self.show()
def _setup_ui(self, notetype_id: NotetypeId) -> None:
- self.setWindowModality(Qt.ApplicationModal)
+ self.setWindowModality(Qt.WindowModality.ApplicationModal)
self.mw.garbage_collect_on_dialog_finish(self)
self.setMinimumWidth(400)
disable_help_button(self)
diff --git a/qt/aqt/clayout.py b/qt/aqt/clayout.py
index a499c5f42..ca081af87 100644
--- a/qt/aqt/clayout.py
+++ b/qt/aqt/clayout.py
@@ -45,7 +45,7 @@ class CardLayout(QDialog):
parent: Optional[QWidget] = None,
fill_empty: bool = False,
) -> None:
- QDialog.__init__(self, parent or mw, Qt.Window)
+ QDialog.__init__(self, parent or mw, Qt.WindowType.Window)
mw.garbage_collect_on_dialog_finish(self)
self.mw = aqt.mw
self.note = note
@@ -80,7 +80,7 @@ class CardLayout(QDialog):
self.redraw_everything()
restoreGeom(self, "CardLayout")
restoreSplitter(self.mainArea, "CardLayoutMainArea")
- self.setWindowModality(Qt.ApplicationModal)
+ self.setWindowModality(Qt.WindowModality.ApplicationModal)
self.show()
# take the focus away from the first input area when starting up,
# as users tend to accidentally type into the template
@@ -108,7 +108,9 @@ class CardLayout(QDialog):
def setupTopArea(self) -> None:
self.topArea = QWidget()
- self.topArea.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Minimum)
+ self.topArea.setSizePolicy(
+ QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum
+ )
self.topAreaForm = aqt.forms.clayout_top.Ui_Form()
self.topAreaForm.setupUi(self.topArea)
self.topAreaForm.templateOptions.setText(
@@ -215,8 +217,8 @@ class CardLayout(QDialog):
def setupMainArea(self) -> None:
split = self.mainArea = QSplitter()
- split.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
- split.setOrientation(Qt.Horizontal)
+ split.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
+ split.setOrientation(Qt.Orientation.Horizontal)
left = QWidget()
tform = self.tform = aqt.forms.template.Ui_Form()
tform.setupUi(left)
@@ -305,7 +307,7 @@ class CardLayout(QDialog):
if not editor.find(text):
# try again from top
cursor = editor.textCursor()
- cursor.movePosition(QTextCursor.Start)
+ cursor.movePosition(QTextCursor.MoveOperation.Start)
editor.setTextCursor(cursor)
if not editor.find(text):
tooltip("No matches found.")
@@ -752,7 +754,7 @@ class CardLayout(QDialog):
if t["did"]:
te.setText(self.col.decks.get(t["did"])["name"])
te.selectAll()
- bb = QDialogButtonBox(QDialogButtonBox.Close)
+ bb = QDialogButtonBox(QDialogButtonBox.StandardButton.Close)
qconnect(bb.rejected, d.close)
l.addWidget(bb)
d.setLayout(l)
diff --git a/qt/aqt/customstudy.py b/qt/aqt/customstudy.py
index 8b056342b..878149e48 100644
--- a/qt/aqt/customstudy.py
+++ b/qt/aqt/customstudy.py
@@ -30,7 +30,7 @@ class CustomStudy(QDialog):
self.created_custom_study = False
f.setupUi(self)
disable_help_button(self)
- self.setWindowModality(Qt.WindowModal)
+ self.setWindowModality(Qt.WindowModality.WindowModal)
self.setupSignals()
f.radioNew.click()
self.exec()
@@ -116,7 +116,7 @@ class CustomStudy(QDialog):
f.spin.setValue(sval)
f.preSpin.setText(pre)
f.postSpin.setText(post)
- f.buttonBox.button(QDialogButtonBox.Ok).setText(ok)
+ f.buttonBox.button(QDialogButtonBox.StandardButton.Ok).setText(ok)
self.radioIdx = idx
def accept(self) -> None:
diff --git a/qt/aqt/deckconf.py b/qt/aqt/deckconf.py
index 89d250fe1..40432613f 100644
--- a/qt/aqt/deckconf.py
+++ b/qt/aqt/deckconf.py
@@ -6,8 +6,6 @@ from __future__ import annotations
from operator import itemgetter
from typing import Any
-from PyQt5.QtWidgets import QLineEdit
-
import aqt
from anki.consts import NEW_CARDS_RANDOM
from anki.decks import DeckConfigDict
@@ -42,13 +40,15 @@ class DeckConf(QDialog):
self.mw.checkpoint(tr.actions_options())
self.setupCombos()
self.setupConfs()
- self.setWindowModality(Qt.WindowModal)
+ self.setWindowModality(Qt.WindowModality.WindowModal)
qconnect(
self.form.buttonBox.helpRequested, lambda: openHelp(HelpPage.DECK_OPTIONS)
)
qconnect(self.form.confOpts.clicked, self.confOpts)
qconnect(
- self.form.buttonBox.button(QDialogButtonBox.RestoreDefaults).clicked,
+ self.form.buttonBox.button(
+ QDialogButtonBox.StandardButton.RestoreDefaults
+ ).clicked,
self.onRestore,
)
self.setWindowTitle(
diff --git a/qt/aqt/deckdescription.py b/qt/aqt/deckdescription.py
index 064883613..ccd534601 100644
--- a/qt/aqt/deckdescription.py
+++ b/qt/aqt/deckdescription.py
@@ -17,7 +17,7 @@ class DeckDescriptionDialog(QDialog):
silentlyClose = True
def __init__(self, mw: aqt.main.AnkiQt) -> None:
- QDialog.__init__(self, mw, Qt.Window)
+ QDialog.__init__(self, mw, Qt.WindowType.Window)
self.mw = mw
# set on success
@@ -39,7 +39,7 @@ class DeckDescriptionDialog(QDialog):
def _setup_ui(self) -> None:
self.setWindowTitle(tr.scheduling_description())
- self.setWindowModality(Qt.ApplicationModal)
+ self.setWindowModality(Qt.WindowModality.ApplicationModal)
self.mw.garbage_collect_on_dialog_finish(self)
self.setMinimumWidth(400)
disable_help_button(self)
@@ -58,7 +58,7 @@ class DeckDescriptionDialog(QDialog):
box.addWidget(self.description)
button_box = QDialogButtonBox()
- ok = button_box.addButton(QDialogButtonBox.Ok)
+ ok = button_box.addButton(QDialogButtonBox.StandardButton.Ok)
qconnect(ok.clicked, self.save_and_accept)
box.addWidget(button_box)
diff --git a/qt/aqt/deckoptions.py b/qt/aqt/deckoptions.py
index 3a89969d0..76bf44dcb 100644
--- a/qt/aqt/deckoptions.py
+++ b/qt/aqt/deckoptions.py
@@ -28,14 +28,14 @@ class DeckOptionsDialog(QDialog):
silentlyClose = True
def __init__(self, mw: aqt.main.AnkiQt, deck: DeckDict) -> None:
- QDialog.__init__(self, mw, Qt.Window)
+ QDialog.__init__(self, mw, Qt.WindowType.Window)
self.mw = mw
self._deck = deck
self._setup_ui()
self.show()
def _setup_ui(self) -> None:
- self.setWindowModality(Qt.ApplicationModal)
+ self.setWindowModality(Qt.WindowModality.ApplicationModal)
self.mw.garbage_collect_on_dialog_finish(self)
self.setMinimumWidth(400)
disable_help_button(self)
diff --git a/qt/aqt/editcurrent.py b/qt/aqt/editcurrent.py
index fa934f305..4d841b5bc 100644
--- a/qt/aqt/editcurrent.py
+++ b/qt/aqt/editcurrent.py
@@ -12,7 +12,7 @@ from aqt.utils import disable_help_button, restoreGeom, saveGeom, tr
class EditCurrent(QDialog):
def __init__(self, mw: aqt.AnkiQt) -> None:
- QDialog.__init__(self, None, Qt.Window)
+ QDialog.__init__(self, None, Qt.WindowType.Window)
mw.garbage_collect_on_dialog_finish(self)
self.mw = mw
self.form = aqt.forms.editcurrent.Ui_Dialog()
@@ -21,7 +21,7 @@ class EditCurrent(QDialog):
disable_help_button(self)
self.setMinimumHeight(400)
self.setMinimumWidth(250)
- self.form.buttonBox.button(QDialogButtonBox.Close).setShortcut(
+ self.form.buttonBox.button(QDialogButtonBox.StandardButton.Close).setShortcut(
QKeySequence("Ctrl+Return")
)
self.editor = aqt.editor.Editor(self.mw, self.form.fieldsArea, self)
diff --git a/qt/aqt/editor.py b/qt/aqt/editor.py
index 5fce6df39..620802ed4 100644
--- a/qt/aqt/editor.py
+++ b/qt/aqt/editor.py
@@ -902,7 +902,7 @@ $editorToolbar.then(({{ toolbar }}) => toolbar.appendGroup({{
@deprecated(info=_js_legacy)
def _onHtmlEdit(self, field: int) -> None:
- d = QDialog(self.widget, Qt.Window)
+ d = QDialog(self.widget, Qt.WindowType.Window)
form = aqt.forms.edithtml.Ui_Dialog()
form.setupUi(d)
restoreGeom(d, "htmlEditor")
@@ -911,11 +911,11 @@ $editorToolbar.then(({{ toolbar }}) => toolbar.appendGroup({{
form.buttonBox.helpRequested, lambda: openHelp(HelpPage.EDITING_FEATURES)
)
font = QFont("Courier")
- font.setStyleHint(QFont.TypeWriter)
+ font.setStyleHint(QFont.StyleHint.TypeWriter)
form.textEdit.setFont(font)
form.textEdit.setPlainText(self.note.fields[field])
d.show()
- form.textEdit.moveCursor(QTextCursor.End)
+ form.textEdit.moveCursor(QTextCursor.MoveOperation.End)
d.exec()
html = form.textEdit.toPlainText()
if html.find(">") > -1:
@@ -997,7 +997,10 @@ $editorToolbar.then(({{ toolbar }}) => toolbar.appendGroup({{
def onChangeCol(self) -> None:
if isLin:
new = QColorDialog.getColor(
- QColor(self.fcolour), None, None, QColorDialog.DontUseNativeDialog
+ QColor(self.fcolour),
+ None,
+ None,
+ QColorDialog.ColorDialogOption.DontUseNativeDialog,
)
else:
new = QColorDialog.getColor(QColor(self.fcolour), None)
@@ -1118,10 +1121,10 @@ class EditorWebView(AnkiWebView):
self._flagAnkiText()
def onCut(self) -> None:
- self.triggerPageAction(QWebEnginePage.Cut)
+ self.triggerPageAction(QWebEnginePage.WebAction.Cut)
def onCopy(self) -> None:
- self.triggerPageAction(QWebEnginePage.Copy)
+ self.triggerPageAction(QWebEnginePage.WebAction.Copy)
def _wantsExtendedPaste(self) -> bool:
strip_html = self.editor.mw.col.get_config_bool(
@@ -1140,10 +1143,10 @@ class EditorWebView(AnkiWebView):
self.editor.doPaste(html, internal, extended)
def onPaste(self) -> None:
- self._onPaste(QClipboard.Clipboard)
+ self._onPaste(QClipboard.Mode.Clipboard)
def onMiddleClickPaste(self) -> None:
- self._onPaste(QClipboard.Selection)
+ self._onPaste(QClipboard.Mode.Selection)
def dragEnterEvent(self, evt: QDragEnterEvent) -> None:
evt.accept()
diff --git a/qt/aqt/emptycards.py b/qt/aqt/emptycards.py
index a84bc309f..1048caddd 100644
--- a/qt/aqt/emptycards.py
+++ b/qt/aqt/emptycards.py
@@ -63,7 +63,7 @@ class EmptyCardsDialog(QDialog):
qconnect(self.finished, on_finished)
self._delete_button = self.form.buttonBox.addButton(
- tr.empty_cards_delete_button(), QDialogButtonBox.ActionRole
+ tr.empty_cards_delete_button(), QDialogButtonBox.ButtonRole.ActionRole
)
self._delete_button.setAutoDefault(False)
qconnect(self._delete_button.clicked, self._on_delete)
diff --git a/qt/aqt/exporting.py b/qt/aqt/exporting.py
index dbcdf6260..7fbfbe001 100644
--- a/qt/aqt/exporting.py
+++ b/qt/aqt/exporting.py
@@ -31,7 +31,7 @@ class ExportDialog(QDialog):
did: DeckId | None = None,
cids: list[CardId] | None = None,
):
- QDialog.__init__(self, mw, Qt.Window)
+ QDialog.__init__(self, mw, Qt.WindowType.Window)
self.mw = mw
self.col = mw.col.weakref()
self.frm = aqt.forms.exporting.Ui_ExportDialog()
@@ -64,7 +64,7 @@ class ExportDialog(QDialog):
self.frm.deck.addItems(self.decks)
# save button
b = QPushButton(tr.exporting_export())
- self.frm.buttonBox.addButton(b, QDialogButtonBox.AcceptRole)
+ self.frm.buttonBox.addButton(b, QDialogButtonBox.ButtonRole.AcceptRole)
# set default option if accessed through deck button
if did:
name = self.mw.col.decks.get(did)["name"]
diff --git a/qt/aqt/fields.py b/qt/aqt/fields.py
index 9048e935e..00155a79b 100644
--- a/qt/aqt/fields.py
+++ b/qt/aqt/fields.py
@@ -45,13 +45,19 @@ class FieldDialog(QDialog):
without_unicode_isolation(tr.fields_fields_for(val=self.model["name"]))
)
disable_help_button(self)
- self.form.buttonBox.button(QDialogButtonBox.Help).setAutoDefault(False)
- self.form.buttonBox.button(QDialogButtonBox.Cancel).setAutoDefault(False)
- self.form.buttonBox.button(QDialogButtonBox.Save).setAutoDefault(False)
+ self.form.buttonBox.button(QDialogButtonBox.StandardButton.Help).setAutoDefault(
+ False
+ )
+ self.form.buttonBox.button(
+ QDialogButtonBox.StandardButton.Cancel
+ ).setAutoDefault(False)
+ self.form.buttonBox.button(QDialogButtonBox.StandardButton.Save).setAutoDefault(
+ False
+ )
self.currentIdx: Optional[int] = None
self.fillFields()
self.setupSignals()
- self.form.fieldList.setDragDropMode(QAbstractItemView.InternalMove)
+ self.form.fieldList.setDragDropMode(QAbstractItemView.DragDropMode.InternalMove)
self.form.fieldList.dropEvent = self.onDrop # type: ignore[assignment]
self.form.fieldList.setCurrentRow(open_at)
self.exec()
@@ -77,15 +83,21 @@ class FieldDialog(QDialog):
def onDrop(self, ev: QDropEvent) -> None:
fieldList = self.form.fieldList
indicatorPos = fieldList.dropIndicatorPosition()
- dropPos = fieldList.indexAt(ev.pos()).row()
+ if qtmajor == 5:
+ pos = ev.pos() # type: ignore
+ else:
+ pos = ev.position().toPoint()
+ dropPos = fieldList.indexAt(pos).row()
idx = self.currentIdx
if dropPos == idx:
return
- if indicatorPos == QAbstractItemView.OnViewport: # to bottom.
+ if (
+ indicatorPos == QAbstractItemView.DropIndicatorPosition.OnViewport
+ ): # to bottom.
movePos = fieldList.count() - 1
- elif indicatorPos == QAbstractItemView.AboveItem:
+ elif indicatorPos == QAbstractItemView.DropIndicatorPosition.AboveItem:
movePos = dropPos
- elif indicatorPos == QAbstractItemView.BelowItem:
+ elif indicatorPos == QAbstractItemView.DropIndicatorPosition.BelowItem:
movePos = dropPos + 1
# the item in idx is removed thus subtract 1.
if idx < dropPos:
diff --git a/qt/aqt/filtered_deck.py b/qt/aqt/filtered_deck.py
index 1876521ba..e225cb0e7 100644
--- a/qt/aqt/filtered_deck.py
+++ b/qt/aqt/filtered_deck.py
@@ -90,7 +90,7 @@ class FilteredDeckConfigDialog(QDialog):
QPushButton[label="hint"] {{ color: {grey} }}"""
)
disable_help_button(self)
- self.setWindowModality(Qt.WindowModal)
+ self.setWindowModality(Qt.WindowModality.WindowModal)
qconnect(
self.form.buttonBox.helpRequested, lambda: openHelp(HelpPage.FILTERED_DECK)
)
@@ -117,7 +117,9 @@ class FilteredDeckConfigDialog(QDialog):
build_label = tr.actions_rebuild()
else:
build_label = tr.decks_build()
- self.form.buttonBox.button(QDialogButtonBox.Ok).setText(build_label)
+ self.form.buttonBox.button(QDialogButtonBox.StandardButton.Ok).setText(
+ build_label
+ )
form.resched.setChecked(config.reschedule)
self._onReschedToggled(0)
diff --git a/qt/aqt/forms/BUILD.bazel b/qt/aqt/forms/BUILD.bazel
index 931df6037..aedbaf453 100644
--- a/qt/aqt/forms/BUILD.bazel
+++ b/qt/aqt/forms/BUILD.bazel
@@ -1,17 +1,48 @@
load("@rules_python//python:defs.bzl", "py_binary")
load("compile.bzl", "compile_all")
+py_binary(
+ name = "build_ui_qt5",
+ srcs = ["build_ui_qt5.py"],
+ legacy_create_init = False,
+ deps = ["@pyqt5//:pkg"],
+)
+
+py_binary(
+ name = "build_ui_qt6",
+ srcs = ["build_ui_qt6.py"],
+ legacy_create_init = False,
+ deps = ["@pyqt6//:pkg"],
+)
+
compile_all(
+ name = "forms_qt5",
srcs = glob(["*.ui"]),
- group = "forms",
+ builder = "build_ui_qt5",
+ suffix = "_qt5",
+)
+
+compile_all(
+ name = "forms_qt6",
+ srcs = glob(["*.ui"]),
+ builder = "build_ui_qt6",
+ suffix = "_qt6",
+)
+
+filegroup(
+ name = "forms",
+ srcs = glob(
+ ["*.py"],
+ exclude = [
+ "*_qt5.py",
+ "*_qt6.py",
+ "build_ui*.py",
+ ],
+ ) + [
+ ":forms_qt6",
+ ":forms_qt5",
+ ],
visibility = [
"//qt/aqt:__pkg__",
],
)
-
-py_binary(
- name = "build_ui",
- srcs = ["build_ui.py"],
- legacy_create_init = False,
- deps = ["@pyqt5//:pkg"],
-)
diff --git a/qt/aqt/forms/__init___qt6.py b/qt/aqt/forms/__init___qt6.py
new file mode 120000
index 000000000..d09f3c7c6
--- /dev/null
+++ b/qt/aqt/forms/__init___qt6.py
@@ -0,0 +1 @@
+../../../bazel-bin/qt/aqt/forms/__init___qt6.py
\ No newline at end of file
diff --git a/qt/aqt/forms/about.py b/qt/aqt/forms/about.py
deleted file mode 120000
index e70429a4a..000000000
--- a/qt/aqt/forms/about.py
+++ /dev/null
@@ -1 +0,0 @@
-../../../bazel-bin/qt/aqt/forms/about.py
\ No newline at end of file
diff --git a/qt/aqt/forms/about.py b/qt/aqt/forms/about.py
new file mode 100644
index 000000000..8622ff868
--- /dev/null
+++ b/qt/aqt/forms/about.py
@@ -0,0 +1,5 @@
+from aqt.qt import qtmajor
+if qtmajor > 5:
+ from .about_qt6 import *
+else:
+ from .about_qt5 import * # type: ignore
diff --git a/qt/aqt/forms/about_qt6.py b/qt/aqt/forms/about_qt6.py
new file mode 120000
index 000000000..9346a7ee6
--- /dev/null
+++ b/qt/aqt/forms/about_qt6.py
@@ -0,0 +1 @@
+../../../bazel-bin/qt/aqt/forms/about_qt6.py
\ No newline at end of file
diff --git a/qt/aqt/forms/addcards.py b/qt/aqt/forms/addcards.py
deleted file mode 120000
index f54c8fe40..000000000
--- a/qt/aqt/forms/addcards.py
+++ /dev/null
@@ -1 +0,0 @@
-../../../bazel-bin/qt/aqt/forms/addcards.py
\ No newline at end of file
diff --git a/qt/aqt/forms/addcards.py b/qt/aqt/forms/addcards.py
new file mode 100644
index 000000000..74082d63f
--- /dev/null
+++ b/qt/aqt/forms/addcards.py
@@ -0,0 +1,5 @@
+from aqt.qt import qtmajor
+if qtmajor > 5:
+ from .addcards_qt6 import *
+else:
+ from .addcards_qt5 import * # type: ignore
diff --git a/qt/aqt/forms/addcards_qt6.py b/qt/aqt/forms/addcards_qt6.py
new file mode 120000
index 000000000..2023fe24a
--- /dev/null
+++ b/qt/aqt/forms/addcards_qt6.py
@@ -0,0 +1 @@
+../../../bazel-bin/qt/aqt/forms/addcards_qt6.py
\ No newline at end of file
diff --git a/qt/aqt/forms/addfield.py b/qt/aqt/forms/addfield.py
deleted file mode 120000
index 1a54703f0..000000000
--- a/qt/aqt/forms/addfield.py
+++ /dev/null
@@ -1 +0,0 @@
-../../../bazel-bin/qt/aqt/forms/addfield.py
\ No newline at end of file
diff --git a/qt/aqt/forms/addfield.py b/qt/aqt/forms/addfield.py
new file mode 100644
index 000000000..839061794
--- /dev/null
+++ b/qt/aqt/forms/addfield.py
@@ -0,0 +1,5 @@
+from aqt.qt import qtmajor
+if qtmajor > 5:
+ from .addfield_qt6 import *
+else:
+ from .addfield_qt5 import * # type: ignore
diff --git a/qt/aqt/forms/addfield_qt6.py b/qt/aqt/forms/addfield_qt6.py
new file mode 120000
index 000000000..6074f2a7e
--- /dev/null
+++ b/qt/aqt/forms/addfield_qt6.py
@@ -0,0 +1 @@
+../../../bazel-bin/qt/aqt/forms/addfield_qt6.py
\ No newline at end of file
diff --git a/qt/aqt/forms/addmodel.py b/qt/aqt/forms/addmodel.py
deleted file mode 120000
index 319620610..000000000
--- a/qt/aqt/forms/addmodel.py
+++ /dev/null
@@ -1 +0,0 @@
-../../../bazel-bin/qt/aqt/forms/addmodel.py
\ No newline at end of file
diff --git a/qt/aqt/forms/addmodel.py b/qt/aqt/forms/addmodel.py
new file mode 100644
index 000000000..6128f1649
--- /dev/null
+++ b/qt/aqt/forms/addmodel.py
@@ -0,0 +1,5 @@
+from aqt.qt import qtmajor
+if qtmajor > 5:
+ from .addmodel_qt6 import *
+else:
+ from .addmodel_qt5 import * # type: ignore
diff --git a/qt/aqt/forms/addmodel_qt6.py b/qt/aqt/forms/addmodel_qt6.py
new file mode 120000
index 000000000..c82ec7f3a
--- /dev/null
+++ b/qt/aqt/forms/addmodel_qt6.py
@@ -0,0 +1 @@
+../../../bazel-bin/qt/aqt/forms/addmodel_qt6.py
\ No newline at end of file
diff --git a/qt/aqt/forms/addonconf.py b/qt/aqt/forms/addonconf.py
deleted file mode 120000
index de53ecee1..000000000
--- a/qt/aqt/forms/addonconf.py
+++ /dev/null
@@ -1 +0,0 @@
-../../../bazel-bin/qt/aqt/forms/addonconf.py
\ No newline at end of file
diff --git a/qt/aqt/forms/addonconf.py b/qt/aqt/forms/addonconf.py
new file mode 100644
index 000000000..759f9b11f
--- /dev/null
+++ b/qt/aqt/forms/addonconf.py
@@ -0,0 +1,5 @@
+from aqt.qt import qtmajor
+if qtmajor > 5:
+ from .addonconf_qt6 import *
+else:
+ from .addonconf_qt5 import * # type: ignore
diff --git a/qt/aqt/forms/addonconf_qt6.py b/qt/aqt/forms/addonconf_qt6.py
new file mode 120000
index 000000000..660196e9d
--- /dev/null
+++ b/qt/aqt/forms/addonconf_qt6.py
@@ -0,0 +1 @@
+../../../bazel-bin/qt/aqt/forms/addonconf_qt6.py
\ No newline at end of file
diff --git a/qt/aqt/forms/addons.py b/qt/aqt/forms/addons.py
deleted file mode 120000
index a13b69944..000000000
--- a/qt/aqt/forms/addons.py
+++ /dev/null
@@ -1 +0,0 @@
-../../../bazel-bin/qt/aqt/forms/addons.py
\ No newline at end of file
diff --git a/qt/aqt/forms/addons.py b/qt/aqt/forms/addons.py
new file mode 100644
index 000000000..fd423756e
--- /dev/null
+++ b/qt/aqt/forms/addons.py
@@ -0,0 +1,5 @@
+from aqt.qt import qtmajor
+if qtmajor > 5:
+ from .addons_qt6 import *
+else:
+ from .addons_qt5 import * # type: ignore
diff --git a/qt/aqt/forms/addons_qt6.py b/qt/aqt/forms/addons_qt6.py
new file mode 120000
index 000000000..50257706b
--- /dev/null
+++ b/qt/aqt/forms/addons_qt6.py
@@ -0,0 +1 @@
+../../../bazel-bin/qt/aqt/forms/addons_qt6.py
\ No newline at end of file
diff --git a/qt/aqt/forms/browser.py b/qt/aqt/forms/browser.py
deleted file mode 120000
index 592ca70b4..000000000
--- a/qt/aqt/forms/browser.py
+++ /dev/null
@@ -1 +0,0 @@
-../../../bazel-bin/qt/aqt/forms/browser.py
\ No newline at end of file
diff --git a/qt/aqt/forms/browser.py b/qt/aqt/forms/browser.py
new file mode 100644
index 000000000..8f443ff4a
--- /dev/null
+++ b/qt/aqt/forms/browser.py
@@ -0,0 +1,5 @@
+from aqt.qt import qtmajor
+if qtmajor > 5:
+ from .browser_qt6 import *
+else:
+ from .browser_qt5 import * # type: ignore
diff --git a/qt/aqt/forms/browser_qt6.py b/qt/aqt/forms/browser_qt6.py
new file mode 120000
index 000000000..d3e9e9e7b
--- /dev/null
+++ b/qt/aqt/forms/browser_qt6.py
@@ -0,0 +1 @@
+../../../bazel-bin/qt/aqt/forms/browser_qt6.py
\ No newline at end of file
diff --git a/qt/aqt/forms/browserdisp.py b/qt/aqt/forms/browserdisp.py
deleted file mode 120000
index d1017e9e2..000000000
--- a/qt/aqt/forms/browserdisp.py
+++ /dev/null
@@ -1 +0,0 @@
-../../../bazel-bin/qt/aqt/forms/browserdisp.py
\ No newline at end of file
diff --git a/qt/aqt/forms/browserdisp.py b/qt/aqt/forms/browserdisp.py
new file mode 100644
index 000000000..c5bc36bd3
--- /dev/null
+++ b/qt/aqt/forms/browserdisp.py
@@ -0,0 +1,5 @@
+from aqt.qt import qtmajor
+if qtmajor > 5:
+ from .browserdisp_qt6 import *
+else:
+ from .browserdisp_qt5 import * # type: ignore
diff --git a/qt/aqt/forms/browserdisp_qt6.py b/qt/aqt/forms/browserdisp_qt6.py
new file mode 120000
index 000000000..17b1ce3a0
--- /dev/null
+++ b/qt/aqt/forms/browserdisp_qt6.py
@@ -0,0 +1 @@
+../../../bazel-bin/qt/aqt/forms/browserdisp_qt6.py
\ No newline at end of file
diff --git a/qt/aqt/forms/browseropts.py b/qt/aqt/forms/browseropts.py
deleted file mode 120000
index 2f97ae88f..000000000
--- a/qt/aqt/forms/browseropts.py
+++ /dev/null
@@ -1 +0,0 @@
-../../../bazel-bin/qt/aqt/forms/browseropts.py
\ No newline at end of file
diff --git a/qt/aqt/forms/browseropts.py b/qt/aqt/forms/browseropts.py
new file mode 100644
index 000000000..d0fd9dbca
--- /dev/null
+++ b/qt/aqt/forms/browseropts.py
@@ -0,0 +1,5 @@
+from aqt.qt import qtmajor
+if qtmajor > 5:
+ from .browseropts_qt6 import *
+else:
+ from .browseropts_qt5 import * # type: ignore
diff --git a/qt/aqt/forms/browseropts_qt6.py b/qt/aqt/forms/browseropts_qt6.py
new file mode 120000
index 000000000..85414176c
--- /dev/null
+++ b/qt/aqt/forms/browseropts_qt6.py
@@ -0,0 +1 @@
+../../../bazel-bin/qt/aqt/forms/browseropts_qt6.py
\ No newline at end of file
diff --git a/qt/aqt/forms/build_ui.py b/qt/aqt/forms/build_ui_qt5.py
similarity index 100%
rename from qt/aqt/forms/build_ui.py
rename to qt/aqt/forms/build_ui_qt5.py
diff --git a/qt/aqt/forms/build_ui_qt5_qt6.py b/qt/aqt/forms/build_ui_qt5_qt6.py
new file mode 120000
index 000000000..b90c66ba6
--- /dev/null
+++ b/qt/aqt/forms/build_ui_qt5_qt6.py
@@ -0,0 +1 @@
+../../../bazel-bin/qt/aqt/forms/build_ui_qt5_qt6.py
\ No newline at end of file
diff --git a/qt/aqt/forms/build_ui_qt6.py b/qt/aqt/forms/build_ui_qt6.py
new file mode 100644
index 000000000..26d230c10
--- /dev/null
+++ b/qt/aqt/forms/build_ui_qt6.py
@@ -0,0 +1,39 @@
+import re
+import sys
+import io
+from PyQt6.uic import compileUi
+
+ui_file = sys.argv[1]
+py_file = sys.argv[2]
+buf = io.StringIO()
+compileUi(open(ui_file), buf)
+
+outdata = buf.getvalue()
+outdata = outdata.replace(
+ "from PyQt6 import QtCore, QtGui, QtWidgets",
+ "from PyQt6 import QtCore, QtGui, QtWidgets\nfrom aqt.utils import tr\n"
+)
+outdata = re.sub(
+ r'(?:QtGui\.QApplication\.)?_?translate\(".*?", "(.*?)"', "tr.\\1(", outdata
+)
+
+
+outlines = []
+qt_bad_types = [
+ ".connect(",
+]
+for line in outdata.splitlines():
+ for substr in qt_bad_types:
+ if substr in line:
+ line = line + " # type: ignore"
+ break
+ if line == "from . import icons_rc":
+ continue
+ line = line.replace(":/icons/", "icons:")
+ line = line.replace("QAction.PreferencesRole", "QAction.MenuRole.PreferencesRole")
+ line = line.replace("QAction.AboutRole", "QAction.MenuRole.AboutRole")
+ line = line.replace("QComboBox.AdjustToMinimumContentsLength", "QComboBox.SizeAdjustPolicy.AdjustToMinimumContentsLength")
+ outlines.append(line)
+
+with open(py_file, "w") as file:
+ file.write("\n".join(outlines))
diff --git a/qt/aqt/forms/build_ui_qt6_qt6.py b/qt/aqt/forms/build_ui_qt6_qt6.py
new file mode 120000
index 000000000..70bd11c7c
--- /dev/null
+++ b/qt/aqt/forms/build_ui_qt6_qt6.py
@@ -0,0 +1 @@
+../../../bazel-bin/qt/aqt/forms/build_ui_qt6_qt6.py
\ No newline at end of file
diff --git a/qt/aqt/forms/changemap.py b/qt/aqt/forms/changemap.py
deleted file mode 120000
index 96f98d078..000000000
--- a/qt/aqt/forms/changemap.py
+++ /dev/null
@@ -1 +0,0 @@
-../../../bazel-bin/qt/aqt/forms/changemap.py
\ No newline at end of file
diff --git a/qt/aqt/forms/changemap.py b/qt/aqt/forms/changemap.py
new file mode 100644
index 000000000..d6dc2d060
--- /dev/null
+++ b/qt/aqt/forms/changemap.py
@@ -0,0 +1,5 @@
+from aqt.qt import qtmajor
+if qtmajor > 5:
+ from .changemap_qt6 import *
+else:
+ from .changemap_qt5 import * # type: ignore
diff --git a/qt/aqt/forms/changemap_qt6.py b/qt/aqt/forms/changemap_qt6.py
new file mode 120000
index 000000000..7dfb40223
--- /dev/null
+++ b/qt/aqt/forms/changemap_qt6.py
@@ -0,0 +1 @@
+../../../bazel-bin/qt/aqt/forms/changemap_qt6.py
\ No newline at end of file
diff --git a/qt/aqt/forms/changemodel.py b/qt/aqt/forms/changemodel.py
deleted file mode 120000
index 8a83a2d08..000000000
--- a/qt/aqt/forms/changemodel.py
+++ /dev/null
@@ -1 +0,0 @@
-../../../bazel-bin/qt/aqt/forms/changemodel.py
\ No newline at end of file
diff --git a/qt/aqt/forms/changemodel.py b/qt/aqt/forms/changemodel.py
new file mode 100644
index 000000000..2b0a10492
--- /dev/null
+++ b/qt/aqt/forms/changemodel.py
@@ -0,0 +1,5 @@
+from aqt.qt import qtmajor
+if qtmajor > 5:
+ from .changemodel_qt6 import *
+else:
+ from .changemodel_qt5 import * # type: ignore
diff --git a/qt/aqt/forms/changemodel_qt6.py b/qt/aqt/forms/changemodel_qt6.py
new file mode 120000
index 000000000..d2b9bfda9
--- /dev/null
+++ b/qt/aqt/forms/changemodel_qt6.py
@@ -0,0 +1 @@
+../../../bazel-bin/qt/aqt/forms/changemodel_qt6.py
\ No newline at end of file
diff --git a/qt/aqt/forms/clayout_top.py b/qt/aqt/forms/clayout_top.py
deleted file mode 120000
index 1a1d15638..000000000
--- a/qt/aqt/forms/clayout_top.py
+++ /dev/null
@@ -1 +0,0 @@
-../../../bazel-bin/qt/aqt/forms/clayout_top.py
\ No newline at end of file
diff --git a/qt/aqt/forms/clayout_top.py b/qt/aqt/forms/clayout_top.py
new file mode 100644
index 000000000..9c15a8f07
--- /dev/null
+++ b/qt/aqt/forms/clayout_top.py
@@ -0,0 +1,5 @@
+from aqt.qt import qtmajor
+if qtmajor > 5:
+ from .clayout_top_qt6 import *
+else:
+ from .clayout_top_qt5 import * # type: ignore
diff --git a/qt/aqt/forms/clayout_top_qt6.py b/qt/aqt/forms/clayout_top_qt6.py
new file mode 120000
index 000000000..63bb2ba0a
--- /dev/null
+++ b/qt/aqt/forms/clayout_top_qt6.py
@@ -0,0 +1 @@
+../../../bazel-bin/qt/aqt/forms/clayout_top_qt6.py
\ No newline at end of file
diff --git a/qt/aqt/forms/compile.bzl b/qt/aqt/forms/compile.bzl
index 36a654f70..4b6751c0f 100644
--- a/qt/aqt/forms/compile.bzl
+++ b/qt/aqt/forms/compile.bzl
@@ -1,28 +1,28 @@
-def compile(name, ui_file, py_file):
+def compile(name, ui_file, py_file, builder):
native.genrule(
name = name,
srcs = [ui_file],
outs = [py_file],
- cmd = "$(location build_ui) $(location {ui_file}) $(location {py_file})".format(
+ cmd = "$(location {builder}) $(location {ui_file}) $(location {py_file})".format(
+ builder = builder,
ui_file = ui_file,
py_file = py_file,
),
tools = [
- "build_ui",
+ builder,
],
message = "Building UI",
)
-def compile_all(group, srcs, visibility):
+def compile_all(name, builder, srcs, suffix):
py_files = []
for ui_file in srcs:
- name = ui_file.replace(".ui", "")
- py_file = name + ".py"
+ fname = ui_file.replace(".ui", "") + suffix
+ py_file = fname + ".py"
py_files.append(py_file)
- compile(name, ui_file, py_file)
+ compile(fname, ui_file, py_file, builder)
native.filegroup(
- name = group,
- srcs = py_files + ["__init__.py"],
- visibility = visibility,
+ name = name,
+ srcs = py_files,
)
diff --git a/qt/aqt/forms/customstudy.py b/qt/aqt/forms/customstudy.py
deleted file mode 120000
index 7e062decc..000000000
--- a/qt/aqt/forms/customstudy.py
+++ /dev/null
@@ -1 +0,0 @@
-../../../bazel-bin/qt/aqt/forms/customstudy.py
\ No newline at end of file
diff --git a/qt/aqt/forms/customstudy.py b/qt/aqt/forms/customstudy.py
new file mode 100644
index 000000000..315e65d7d
--- /dev/null
+++ b/qt/aqt/forms/customstudy.py
@@ -0,0 +1,5 @@
+from aqt.qt import qtmajor
+if qtmajor > 5:
+ from .customstudy_qt6 import *
+else:
+ from .customstudy_qt5 import * # type: ignore
diff --git a/qt/aqt/forms/customstudy_qt6.py b/qt/aqt/forms/customstudy_qt6.py
new file mode 120000
index 000000000..fc030c146
--- /dev/null
+++ b/qt/aqt/forms/customstudy_qt6.py
@@ -0,0 +1 @@
+../../../bazel-bin/qt/aqt/forms/customstudy_qt6.py
\ No newline at end of file
diff --git a/qt/aqt/forms/dconf.py b/qt/aqt/forms/dconf.py
deleted file mode 120000
index f90767bfa..000000000
--- a/qt/aqt/forms/dconf.py
+++ /dev/null
@@ -1 +0,0 @@
-../../../bazel-bin/qt/aqt/forms/dconf.py
\ No newline at end of file
diff --git a/qt/aqt/forms/dconf.py b/qt/aqt/forms/dconf.py
new file mode 100644
index 000000000..6f540938f
--- /dev/null
+++ b/qt/aqt/forms/dconf.py
@@ -0,0 +1,5 @@
+from aqt.qt import qtmajor
+if qtmajor > 5:
+ from .dconf_qt6 import *
+else:
+ from .dconf_qt5 import * # type: ignore
diff --git a/qt/aqt/forms/dconf_qt6.py b/qt/aqt/forms/dconf_qt6.py
new file mode 120000
index 000000000..1ee73eefd
--- /dev/null
+++ b/qt/aqt/forms/dconf_qt6.py
@@ -0,0 +1 @@
+../../../bazel-bin/qt/aqt/forms/dconf_qt6.py
\ No newline at end of file
diff --git a/qt/aqt/forms/debug.py b/qt/aqt/forms/debug.py
deleted file mode 120000
index eeb139524..000000000
--- a/qt/aqt/forms/debug.py
+++ /dev/null
@@ -1 +0,0 @@
-../../../bazel-bin/qt/aqt/forms/debug.py
\ No newline at end of file
diff --git a/qt/aqt/forms/debug.py b/qt/aqt/forms/debug.py
new file mode 100644
index 000000000..e4bcd75c1
--- /dev/null
+++ b/qt/aqt/forms/debug.py
@@ -0,0 +1,5 @@
+from aqt.qt import qtmajor
+if qtmajor > 5:
+ from .debug_qt6 import *
+else:
+ from .debug_qt5 import * # type: ignore
diff --git a/qt/aqt/forms/debug_qt6.py b/qt/aqt/forms/debug_qt6.py
new file mode 120000
index 000000000..29accf4d0
--- /dev/null
+++ b/qt/aqt/forms/debug_qt6.py
@@ -0,0 +1 @@
+../../../bazel-bin/qt/aqt/forms/debug_qt6.py
\ No newline at end of file
diff --git a/qt/aqt/forms/editaddon.py b/qt/aqt/forms/editaddon.py
deleted file mode 120000
index 1f692c333..000000000
--- a/qt/aqt/forms/editaddon.py
+++ /dev/null
@@ -1 +0,0 @@
-../../../bazel-bin/qt/aqt/forms/editaddon.py
\ No newline at end of file
diff --git a/qt/aqt/forms/editaddon.py b/qt/aqt/forms/editaddon.py
new file mode 100644
index 000000000..da7a02b89
--- /dev/null
+++ b/qt/aqt/forms/editaddon.py
@@ -0,0 +1,5 @@
+from aqt.qt import qtmajor
+if qtmajor > 5:
+ from .editaddon_qt6 import *
+else:
+ from .editaddon_qt5 import * # type: ignore
diff --git a/qt/aqt/forms/editaddon_qt6.py b/qt/aqt/forms/editaddon_qt6.py
new file mode 120000
index 000000000..61a93235e
--- /dev/null
+++ b/qt/aqt/forms/editaddon_qt6.py
@@ -0,0 +1 @@
+../../../bazel-bin/qt/aqt/forms/editaddon_qt6.py
\ No newline at end of file
diff --git a/qt/aqt/forms/editcurrent.py b/qt/aqt/forms/editcurrent.py
deleted file mode 120000
index 7298dc774..000000000
--- a/qt/aqt/forms/editcurrent.py
+++ /dev/null
@@ -1 +0,0 @@
-../../../bazel-bin/qt/aqt/forms/editcurrent.py
\ No newline at end of file
diff --git a/qt/aqt/forms/editcurrent.py b/qt/aqt/forms/editcurrent.py
new file mode 100644
index 000000000..a9f03c6ea
--- /dev/null
+++ b/qt/aqt/forms/editcurrent.py
@@ -0,0 +1,5 @@
+from aqt.qt import qtmajor
+if qtmajor > 5:
+ from .editcurrent_qt6 import *
+else:
+ from .editcurrent_qt5 import * # type: ignore
diff --git a/qt/aqt/forms/editcurrent_qt6.py b/qt/aqt/forms/editcurrent_qt6.py
new file mode 120000
index 000000000..182216a50
--- /dev/null
+++ b/qt/aqt/forms/editcurrent_qt6.py
@@ -0,0 +1 @@
+../../../bazel-bin/qt/aqt/forms/editcurrent_qt6.py
\ No newline at end of file
diff --git a/qt/aqt/forms/edithtml.py b/qt/aqt/forms/edithtml.py
deleted file mode 120000
index 865dee6af..000000000
--- a/qt/aqt/forms/edithtml.py
+++ /dev/null
@@ -1 +0,0 @@
-../../../bazel-bin/qt/aqt/forms/edithtml.py
\ No newline at end of file
diff --git a/qt/aqt/forms/edithtml.py b/qt/aqt/forms/edithtml.py
new file mode 100644
index 000000000..92f792344
--- /dev/null
+++ b/qt/aqt/forms/edithtml.py
@@ -0,0 +1,5 @@
+from aqt.qt import qtmajor
+if qtmajor > 5:
+ from .edithtml_qt6 import *
+else:
+ from .edithtml_qt5 import * # type: ignore
diff --git a/qt/aqt/forms/edithtml_qt6.py b/qt/aqt/forms/edithtml_qt6.py
new file mode 120000
index 000000000..4d5732294
--- /dev/null
+++ b/qt/aqt/forms/edithtml_qt6.py
@@ -0,0 +1 @@
+../../../bazel-bin/qt/aqt/forms/edithtml_qt6.py
\ No newline at end of file
diff --git a/qt/aqt/forms/emptycards.py b/qt/aqt/forms/emptycards.py
deleted file mode 120000
index 7a520f0d4..000000000
--- a/qt/aqt/forms/emptycards.py
+++ /dev/null
@@ -1 +0,0 @@
-../../../bazel-bin/qt/aqt/forms/emptycards.py
\ No newline at end of file
diff --git a/qt/aqt/forms/emptycards.py b/qt/aqt/forms/emptycards.py
new file mode 100644
index 000000000..616ea2ed4
--- /dev/null
+++ b/qt/aqt/forms/emptycards.py
@@ -0,0 +1,5 @@
+from aqt.qt import qtmajor
+if qtmajor > 5:
+ from .emptycards_qt6 import *
+else:
+ from .emptycards_qt5 import * # type: ignore
diff --git a/qt/aqt/forms/emptycards_qt6.py b/qt/aqt/forms/emptycards_qt6.py
new file mode 120000
index 000000000..7c3b36e0f
--- /dev/null
+++ b/qt/aqt/forms/emptycards_qt6.py
@@ -0,0 +1 @@
+../../../bazel-bin/qt/aqt/forms/emptycards_qt6.py
\ No newline at end of file
diff --git a/qt/aqt/forms/exporting.py b/qt/aqt/forms/exporting.py
deleted file mode 120000
index 46f6c94a9..000000000
--- a/qt/aqt/forms/exporting.py
+++ /dev/null
@@ -1 +0,0 @@
-../../../bazel-bin/qt/aqt/forms/exporting.py
\ No newline at end of file
diff --git a/qt/aqt/forms/exporting.py b/qt/aqt/forms/exporting.py
new file mode 100644
index 000000000..2ce625bcd
--- /dev/null
+++ b/qt/aqt/forms/exporting.py
@@ -0,0 +1,5 @@
+from aqt.qt import qtmajor
+if qtmajor > 5:
+ from .exporting_qt6 import *
+else:
+ from .exporting_qt5 import * # type: ignore
diff --git a/qt/aqt/forms/exporting_qt6.py b/qt/aqt/forms/exporting_qt6.py
new file mode 120000
index 000000000..d3e88464f
--- /dev/null
+++ b/qt/aqt/forms/exporting_qt6.py
@@ -0,0 +1 @@
+../../../bazel-bin/qt/aqt/forms/exporting_qt6.py
\ No newline at end of file
diff --git a/qt/aqt/forms/fields.py b/qt/aqt/forms/fields.py
deleted file mode 120000
index ce2d542bd..000000000
--- a/qt/aqt/forms/fields.py
+++ /dev/null
@@ -1 +0,0 @@
-../../../bazel-bin/qt/aqt/forms/fields.py
\ No newline at end of file
diff --git a/qt/aqt/forms/fields.py b/qt/aqt/forms/fields.py
new file mode 100644
index 000000000..2c1512701
--- /dev/null
+++ b/qt/aqt/forms/fields.py
@@ -0,0 +1,5 @@
+from aqt.qt import qtmajor
+if qtmajor > 5:
+ from .fields_qt6 import *
+else:
+ from .fields_qt5 import * # type: ignore
diff --git a/qt/aqt/forms/fields_qt6.py b/qt/aqt/forms/fields_qt6.py
new file mode 120000
index 000000000..b404fab2c
--- /dev/null
+++ b/qt/aqt/forms/fields_qt6.py
@@ -0,0 +1 @@
+../../../bazel-bin/qt/aqt/forms/fields_qt6.py
\ No newline at end of file
diff --git a/qt/aqt/forms/filtered_deck.py b/qt/aqt/forms/filtered_deck.py
deleted file mode 120000
index 39713291a..000000000
--- a/qt/aqt/forms/filtered_deck.py
+++ /dev/null
@@ -1 +0,0 @@
-../../../bazel-bin/qt/aqt/forms/filtered_deck.py
\ No newline at end of file
diff --git a/qt/aqt/forms/filtered_deck.py b/qt/aqt/forms/filtered_deck.py
new file mode 100644
index 000000000..48a9dd52e
--- /dev/null
+++ b/qt/aqt/forms/filtered_deck.py
@@ -0,0 +1,5 @@
+from aqt.qt import qtmajor
+if qtmajor > 5:
+ from .filtered_deck_qt6 import *
+else:
+ from .filtered_deck_qt5 import * # type: ignore
diff --git a/qt/aqt/forms/filtered_deck_qt6.py b/qt/aqt/forms/filtered_deck_qt6.py
new file mode 120000
index 000000000..0c8fa97de
--- /dev/null
+++ b/qt/aqt/forms/filtered_deck_qt6.py
@@ -0,0 +1 @@
+../../../bazel-bin/qt/aqt/forms/filtered_deck_qt6.py
\ No newline at end of file
diff --git a/qt/aqt/forms/finddupes.py b/qt/aqt/forms/finddupes.py
deleted file mode 120000
index fc7c7bf1d..000000000
--- a/qt/aqt/forms/finddupes.py
+++ /dev/null
@@ -1 +0,0 @@
-../../../bazel-bin/qt/aqt/forms/finddupes.py
\ No newline at end of file
diff --git a/qt/aqt/forms/finddupes.py b/qt/aqt/forms/finddupes.py
new file mode 100644
index 000000000..4f7db20b3
--- /dev/null
+++ b/qt/aqt/forms/finddupes.py
@@ -0,0 +1,5 @@
+from aqt.qt import qtmajor
+if qtmajor > 5:
+ from .finddupes_qt6 import *
+else:
+ from .finddupes_qt5 import * # type: ignore
diff --git a/qt/aqt/forms/finddupes_qt6.py b/qt/aqt/forms/finddupes_qt6.py
new file mode 120000
index 000000000..5951a4907
--- /dev/null
+++ b/qt/aqt/forms/finddupes_qt6.py
@@ -0,0 +1 @@
+../../../bazel-bin/qt/aqt/forms/finddupes_qt6.py
\ No newline at end of file
diff --git a/qt/aqt/forms/findreplace.py b/qt/aqt/forms/findreplace.py
deleted file mode 120000
index 7d7e10e7a..000000000
--- a/qt/aqt/forms/findreplace.py
+++ /dev/null
@@ -1 +0,0 @@
-../../../bazel-bin/qt/aqt/forms/findreplace.py
\ No newline at end of file
diff --git a/qt/aqt/forms/findreplace.py b/qt/aqt/forms/findreplace.py
new file mode 100644
index 000000000..1cdd2e075
--- /dev/null
+++ b/qt/aqt/forms/findreplace.py
@@ -0,0 +1,5 @@
+from aqt.qt import qtmajor
+if qtmajor > 5:
+ from .findreplace_qt6 import *
+else:
+ from .findreplace_qt5 import * # type: ignore
diff --git a/qt/aqt/forms/findreplace.ui b/qt/aqt/forms/findreplace.ui
index 8d08770ab..ec11bbc24 100644
--- a/qt/aqt/forms/findreplace.ui
+++ b/qt/aqt/forms/findreplace.ui
@@ -6,8 +6,8 @@
0
0
- 377
- 224
+ 479
+ 247
@@ -31,7 +31,7 @@
QComboBox::NoInsert
- QComboBox::AdjustToMinimumContentsLength
+ QComboBox::AdjustToMinimumContentsLengthWithIcon
@@ -59,7 +59,7 @@
-
- QComboBox::AdjustToMinimumContentsLength
+ QComboBox::AdjustToMinimumContentsLengthWithIcon
@@ -95,7 +95,7 @@
QComboBox::NoInsert
- QComboBox::AdjustToMinimumContentsLength
+ QComboBox::AdjustToMinimumContentsLengthWithIcon
diff --git a/qt/aqt/forms/findreplace_qt6.py b/qt/aqt/forms/findreplace_qt6.py
new file mode 120000
index 000000000..863e253d2
--- /dev/null
+++ b/qt/aqt/forms/findreplace_qt6.py
@@ -0,0 +1 @@
+../../../bazel-bin/qt/aqt/forms/findreplace_qt6.py
\ No newline at end of file
diff --git a/qt/aqt/forms/getaddons.py b/qt/aqt/forms/getaddons.py
deleted file mode 120000
index 5acc58c23..000000000
--- a/qt/aqt/forms/getaddons.py
+++ /dev/null
@@ -1 +0,0 @@
-../../../bazel-bin/qt/aqt/forms/getaddons.py
\ No newline at end of file
diff --git a/qt/aqt/forms/getaddons.py b/qt/aqt/forms/getaddons.py
new file mode 100644
index 000000000..262703bd6
--- /dev/null
+++ b/qt/aqt/forms/getaddons.py
@@ -0,0 +1,5 @@
+from aqt.qt import qtmajor
+if qtmajor > 5:
+ from .getaddons_qt6 import *
+else:
+ from .getaddons_qt5 import * # type: ignore
diff --git a/qt/aqt/forms/getaddons_qt6.py b/qt/aqt/forms/getaddons_qt6.py
new file mode 120000
index 000000000..7898b6932
--- /dev/null
+++ b/qt/aqt/forms/getaddons_qt6.py
@@ -0,0 +1 @@
+../../../bazel-bin/qt/aqt/forms/getaddons_qt6.py
\ No newline at end of file
diff --git a/qt/aqt/forms/importing.py b/qt/aqt/forms/importing.py
deleted file mode 120000
index 4d04d5af0..000000000
--- a/qt/aqt/forms/importing.py
+++ /dev/null
@@ -1 +0,0 @@
-../../../bazel-bin/qt/aqt/forms/importing.py
\ No newline at end of file
diff --git a/qt/aqt/forms/importing.py b/qt/aqt/forms/importing.py
new file mode 100644
index 000000000..5dd58c0fc
--- /dev/null
+++ b/qt/aqt/forms/importing.py
@@ -0,0 +1,5 @@
+from aqt.qt import qtmajor
+if qtmajor > 5:
+ from .importing_qt6 import *
+else:
+ from .importing_qt5 import * # type: ignore
diff --git a/qt/aqt/forms/importing_qt6.py b/qt/aqt/forms/importing_qt6.py
new file mode 120000
index 000000000..f089cecd0
--- /dev/null
+++ b/qt/aqt/forms/importing_qt6.py
@@ -0,0 +1 @@
+../../../bazel-bin/qt/aqt/forms/importing_qt6.py
\ No newline at end of file
diff --git a/qt/aqt/forms/main.py b/qt/aqt/forms/main.py
deleted file mode 120000
index 7d0246bd4..000000000
--- a/qt/aqt/forms/main.py
+++ /dev/null
@@ -1 +0,0 @@
-../../../bazel-bin/qt/aqt/forms/main.py
\ No newline at end of file
diff --git a/qt/aqt/forms/main.py b/qt/aqt/forms/main.py
new file mode 100644
index 000000000..00a04ef13
--- /dev/null
+++ b/qt/aqt/forms/main.py
@@ -0,0 +1,5 @@
+from aqt.qt import qtmajor
+if qtmajor > 5:
+ from .main_qt6 import *
+else:
+ from .main_qt5 import * # type: ignore
diff --git a/qt/aqt/forms/main_qt6.py b/qt/aqt/forms/main_qt6.py
new file mode 120000
index 000000000..ceeeaef2f
--- /dev/null
+++ b/qt/aqt/forms/main_qt6.py
@@ -0,0 +1 @@
+../../../bazel-bin/qt/aqt/forms/main_qt6.py
\ No newline at end of file
diff --git a/qt/aqt/forms/modelopts.py b/qt/aqt/forms/modelopts.py
deleted file mode 120000
index cb08b0791..000000000
--- a/qt/aqt/forms/modelopts.py
+++ /dev/null
@@ -1 +0,0 @@
-../../../bazel-bin/qt/aqt/forms/modelopts.py
\ No newline at end of file
diff --git a/qt/aqt/forms/modelopts.py b/qt/aqt/forms/modelopts.py
new file mode 100644
index 000000000..f6166c8d4
--- /dev/null
+++ b/qt/aqt/forms/modelopts.py
@@ -0,0 +1,5 @@
+from aqt.qt import qtmajor
+if qtmajor > 5:
+ from .modelopts_qt6 import *
+else:
+ from .modelopts_qt5 import * # type: ignore
diff --git a/qt/aqt/forms/modelopts_qt6.py b/qt/aqt/forms/modelopts_qt6.py
new file mode 120000
index 000000000..ffdc4f397
--- /dev/null
+++ b/qt/aqt/forms/modelopts_qt6.py
@@ -0,0 +1 @@
+../../../bazel-bin/qt/aqt/forms/modelopts_qt6.py
\ No newline at end of file
diff --git a/qt/aqt/forms/models.py b/qt/aqt/forms/models.py
deleted file mode 120000
index 6f38bb2f7..000000000
--- a/qt/aqt/forms/models.py
+++ /dev/null
@@ -1 +0,0 @@
-../../../bazel-bin/qt/aqt/forms/models.py
\ No newline at end of file
diff --git a/qt/aqt/forms/models.py b/qt/aqt/forms/models.py
new file mode 100644
index 000000000..ddb565a58
--- /dev/null
+++ b/qt/aqt/forms/models.py
@@ -0,0 +1,5 @@
+from aqt.qt import qtmajor
+if qtmajor > 5:
+ from .models_qt6 import *
+else:
+ from .models_qt5 import * # type: ignore
diff --git a/qt/aqt/forms/models_qt6.py b/qt/aqt/forms/models_qt6.py
new file mode 120000
index 000000000..76fd177b2
--- /dev/null
+++ b/qt/aqt/forms/models_qt6.py
@@ -0,0 +1 @@
+../../../bazel-bin/qt/aqt/forms/models_qt6.py
\ No newline at end of file
diff --git a/qt/aqt/forms/preferences.py b/qt/aqt/forms/preferences.py
deleted file mode 120000
index 15982ca41..000000000
--- a/qt/aqt/forms/preferences.py
+++ /dev/null
@@ -1 +0,0 @@
-../../../bazel-bin/qt/aqt/forms/preferences.py
\ No newline at end of file
diff --git a/qt/aqt/forms/preferences.py b/qt/aqt/forms/preferences.py
new file mode 100644
index 000000000..0b31698eb
--- /dev/null
+++ b/qt/aqt/forms/preferences.py
@@ -0,0 +1,5 @@
+from aqt.qt import qtmajor
+if qtmajor > 5:
+ from .preferences_qt6 import *
+else:
+ from .preferences_qt5 import * # type: ignore
diff --git a/qt/aqt/forms/preferences.ui b/qt/aqt/forms/preferences.ui
index e705d0b05..085c10881 100644
--- a/qt/aqt/forms/preferences.ui
+++ b/qt/aqt/forms/preferences.ui
@@ -107,13 +107,6 @@
- -
-
-
-
-
-
-
-
@@ -616,7 +609,6 @@
paste_strips_formatting
nightMode
useCurrent
- recording_driver
default_search_text
uiScale
showEstimates
diff --git a/qt/aqt/forms/preferences_qt6.py b/qt/aqt/forms/preferences_qt6.py
new file mode 120000
index 000000000..c3cddef94
--- /dev/null
+++ b/qt/aqt/forms/preferences_qt6.py
@@ -0,0 +1 @@
+../../../bazel-bin/qt/aqt/forms/preferences_qt6.py
\ No newline at end of file
diff --git a/qt/aqt/forms/preview.py b/qt/aqt/forms/preview.py
deleted file mode 120000
index 877162b89..000000000
--- a/qt/aqt/forms/preview.py
+++ /dev/null
@@ -1 +0,0 @@
-../../../bazel-bin/qt/aqt/forms/preview.py
\ No newline at end of file
diff --git a/qt/aqt/forms/preview.py b/qt/aqt/forms/preview.py
new file mode 100644
index 000000000..f20222c81
--- /dev/null
+++ b/qt/aqt/forms/preview.py
@@ -0,0 +1,5 @@
+from aqt.qt import qtmajor
+if qtmajor > 5:
+ from .preview_qt6 import *
+else:
+ from .preview_qt5 import * # type: ignore
diff --git a/qt/aqt/forms/preview_qt6.py b/qt/aqt/forms/preview_qt6.py
new file mode 120000
index 000000000..ebd0c6300
--- /dev/null
+++ b/qt/aqt/forms/preview_qt6.py
@@ -0,0 +1 @@
+../../../bazel-bin/qt/aqt/forms/preview_qt6.py
\ No newline at end of file
diff --git a/qt/aqt/forms/profiles.py b/qt/aqt/forms/profiles.py
deleted file mode 120000
index 9643ad8ae..000000000
--- a/qt/aqt/forms/profiles.py
+++ /dev/null
@@ -1 +0,0 @@
-../../../bazel-bin/qt/aqt/forms/profiles.py
\ No newline at end of file
diff --git a/qt/aqt/forms/profiles.py b/qt/aqt/forms/profiles.py
new file mode 100644
index 000000000..55c9b6784
--- /dev/null
+++ b/qt/aqt/forms/profiles.py
@@ -0,0 +1,5 @@
+from aqt.qt import qtmajor
+if qtmajor > 5:
+ from .profiles_qt6 import *
+else:
+ from .profiles_qt5 import * # type: ignore
diff --git a/qt/aqt/forms/profiles_qt6.py b/qt/aqt/forms/profiles_qt6.py
new file mode 120000
index 000000000..e16ba8816
--- /dev/null
+++ b/qt/aqt/forms/profiles_qt6.py
@@ -0,0 +1 @@
+../../../bazel-bin/qt/aqt/forms/profiles_qt6.py
\ No newline at end of file
diff --git a/qt/aqt/forms/progress.py b/qt/aqt/forms/progress.py
deleted file mode 120000
index 22dd1eb4a..000000000
--- a/qt/aqt/forms/progress.py
+++ /dev/null
@@ -1 +0,0 @@
-../../../bazel-bin/qt/aqt/forms/progress.py
\ No newline at end of file
diff --git a/qt/aqt/forms/progress.py b/qt/aqt/forms/progress.py
new file mode 100644
index 000000000..2af563f54
--- /dev/null
+++ b/qt/aqt/forms/progress.py
@@ -0,0 +1,5 @@
+from aqt.qt import qtmajor
+if qtmajor > 5:
+ from .progress_qt6 import *
+else:
+ from .progress_qt5 import * # type: ignore
diff --git a/qt/aqt/forms/progress_qt6.py b/qt/aqt/forms/progress_qt6.py
new file mode 120000
index 000000000..596cba869
--- /dev/null
+++ b/qt/aqt/forms/progress_qt6.py
@@ -0,0 +1 @@
+../../../bazel-bin/qt/aqt/forms/progress_qt6.py
\ No newline at end of file
diff --git a/qt/aqt/forms/reposition.py b/qt/aqt/forms/reposition.py
deleted file mode 120000
index 68a9c20b5..000000000
--- a/qt/aqt/forms/reposition.py
+++ /dev/null
@@ -1 +0,0 @@
-../../../bazel-bin/qt/aqt/forms/reposition.py
\ No newline at end of file
diff --git a/qt/aqt/forms/reposition.py b/qt/aqt/forms/reposition.py
new file mode 100644
index 000000000..8f35e51cd
--- /dev/null
+++ b/qt/aqt/forms/reposition.py
@@ -0,0 +1,5 @@
+from aqt.qt import qtmajor
+if qtmajor > 5:
+ from .reposition_qt6 import *
+else:
+ from .reposition_qt5 import * # type: ignore
diff --git a/qt/aqt/forms/reposition_qt6.py b/qt/aqt/forms/reposition_qt6.py
new file mode 120000
index 000000000..a0b8c335f
--- /dev/null
+++ b/qt/aqt/forms/reposition_qt6.py
@@ -0,0 +1 @@
+../../../bazel-bin/qt/aqt/forms/reposition_qt6.py
\ No newline at end of file
diff --git a/qt/aqt/forms/setgroup.py b/qt/aqt/forms/setgroup.py
deleted file mode 120000
index e3227b7a9..000000000
--- a/qt/aqt/forms/setgroup.py
+++ /dev/null
@@ -1 +0,0 @@
-../../../bazel-bin/qt/aqt/forms/setgroup.py
\ No newline at end of file
diff --git a/qt/aqt/forms/setgroup.py b/qt/aqt/forms/setgroup.py
new file mode 100644
index 000000000..c04c3d627
--- /dev/null
+++ b/qt/aqt/forms/setgroup.py
@@ -0,0 +1,5 @@
+from aqt.qt import qtmajor
+if qtmajor > 5:
+ from .setgroup_qt6 import *
+else:
+ from .setgroup_qt5 import * # type: ignore
diff --git a/qt/aqt/forms/setgroup_qt6.py b/qt/aqt/forms/setgroup_qt6.py
new file mode 120000
index 000000000..7d7f72a1b
--- /dev/null
+++ b/qt/aqt/forms/setgroup_qt6.py
@@ -0,0 +1 @@
+../../../bazel-bin/qt/aqt/forms/setgroup_qt6.py
\ No newline at end of file
diff --git a/qt/aqt/forms/setlang.py b/qt/aqt/forms/setlang.py
deleted file mode 120000
index ae38ab548..000000000
--- a/qt/aqt/forms/setlang.py
+++ /dev/null
@@ -1 +0,0 @@
-../../../bazel-bin/qt/aqt/forms/setlang.py
\ No newline at end of file
diff --git a/qt/aqt/forms/setlang.py b/qt/aqt/forms/setlang.py
new file mode 100644
index 000000000..10107e5c0
--- /dev/null
+++ b/qt/aqt/forms/setlang.py
@@ -0,0 +1,5 @@
+from aqt.qt import qtmajor
+if qtmajor > 5:
+ from .setlang_qt6 import *
+else:
+ from .setlang_qt5 import * # type: ignore
diff --git a/qt/aqt/forms/setlang_qt6.py b/qt/aqt/forms/setlang_qt6.py
new file mode 120000
index 000000000..50ec3d564
--- /dev/null
+++ b/qt/aqt/forms/setlang_qt6.py
@@ -0,0 +1 @@
+../../../bazel-bin/qt/aqt/forms/setlang_qt6.py
\ No newline at end of file
diff --git a/qt/aqt/forms/stats.py b/qt/aqt/forms/stats.py
deleted file mode 120000
index 9ba8aa2d0..000000000
--- a/qt/aqt/forms/stats.py
+++ /dev/null
@@ -1 +0,0 @@
-../../../bazel-bin/qt/aqt/forms/stats.py
\ No newline at end of file
diff --git a/qt/aqt/forms/stats.py b/qt/aqt/forms/stats.py
new file mode 100644
index 000000000..a2e5be948
--- /dev/null
+++ b/qt/aqt/forms/stats.py
@@ -0,0 +1,5 @@
+from aqt.qt import qtmajor
+if qtmajor > 5:
+ from .stats_qt6 import *
+else:
+ from .stats_qt5 import * # type: ignore
diff --git a/qt/aqt/forms/stats_qt6.py b/qt/aqt/forms/stats_qt6.py
new file mode 120000
index 000000000..156dd54a4
--- /dev/null
+++ b/qt/aqt/forms/stats_qt6.py
@@ -0,0 +1 @@
+../../../bazel-bin/qt/aqt/forms/stats_qt6.py
\ No newline at end of file
diff --git a/qt/aqt/forms/studydeck.py b/qt/aqt/forms/studydeck.py
deleted file mode 120000
index 4dbf7513a..000000000
--- a/qt/aqt/forms/studydeck.py
+++ /dev/null
@@ -1 +0,0 @@
-../../../bazel-bin/qt/aqt/forms/studydeck.py
\ No newline at end of file
diff --git a/qt/aqt/forms/studydeck.py b/qt/aqt/forms/studydeck.py
new file mode 100644
index 000000000..00f2ee6b7
--- /dev/null
+++ b/qt/aqt/forms/studydeck.py
@@ -0,0 +1,5 @@
+from aqt.qt import qtmajor
+if qtmajor > 5:
+ from .studydeck_qt6 import *
+else:
+ from .studydeck_qt5 import * # type: ignore
diff --git a/qt/aqt/forms/studydeck_qt6.py b/qt/aqt/forms/studydeck_qt6.py
new file mode 120000
index 000000000..224c0d594
--- /dev/null
+++ b/qt/aqt/forms/studydeck_qt6.py
@@ -0,0 +1 @@
+../../../bazel-bin/qt/aqt/forms/studydeck_qt6.py
\ No newline at end of file
diff --git a/qt/aqt/forms/synclog.py b/qt/aqt/forms/synclog.py
deleted file mode 120000
index fff84ce3b..000000000
--- a/qt/aqt/forms/synclog.py
+++ /dev/null
@@ -1 +0,0 @@
-../../../bazel-bin/qt/aqt/forms/synclog.py
\ No newline at end of file
diff --git a/qt/aqt/forms/synclog.py b/qt/aqt/forms/synclog.py
new file mode 100644
index 000000000..efc0d6f71
--- /dev/null
+++ b/qt/aqt/forms/synclog.py
@@ -0,0 +1,5 @@
+from aqt.qt import qtmajor
+if qtmajor > 5:
+ from .synclog_qt6 import *
+else:
+ from .synclog_qt5 import * # type: ignore
diff --git a/qt/aqt/forms/synclog_qt6.py b/qt/aqt/forms/synclog_qt6.py
new file mode 120000
index 000000000..26c206838
--- /dev/null
+++ b/qt/aqt/forms/synclog_qt6.py
@@ -0,0 +1 @@
+../../../bazel-bin/qt/aqt/forms/synclog_qt6.py
\ No newline at end of file
diff --git a/qt/aqt/forms/taglimit.py b/qt/aqt/forms/taglimit.py
deleted file mode 120000
index b0edb113d..000000000
--- a/qt/aqt/forms/taglimit.py
+++ /dev/null
@@ -1 +0,0 @@
-../../../bazel-bin/qt/aqt/forms/taglimit.py
\ No newline at end of file
diff --git a/qt/aqt/forms/taglimit.py b/qt/aqt/forms/taglimit.py
new file mode 100644
index 000000000..2ee3398d5
--- /dev/null
+++ b/qt/aqt/forms/taglimit.py
@@ -0,0 +1,5 @@
+from aqt.qt import qtmajor
+if qtmajor > 5:
+ from .taglimit_qt6 import *
+else:
+ from .taglimit_qt5 import * # type: ignore
diff --git a/qt/aqt/forms/taglimit_qt6.py b/qt/aqt/forms/taglimit_qt6.py
new file mode 120000
index 000000000..f340c989b
--- /dev/null
+++ b/qt/aqt/forms/taglimit_qt6.py
@@ -0,0 +1 @@
+../../../bazel-bin/qt/aqt/forms/taglimit_qt6.py
\ No newline at end of file
diff --git a/qt/aqt/forms/template.py b/qt/aqt/forms/template.py
deleted file mode 120000
index 9afe0aac7..000000000
--- a/qt/aqt/forms/template.py
+++ /dev/null
@@ -1 +0,0 @@
-../../../bazel-bin/qt/aqt/forms/template.py
\ No newline at end of file
diff --git a/qt/aqt/forms/template.py b/qt/aqt/forms/template.py
new file mode 100644
index 000000000..73a332c0a
--- /dev/null
+++ b/qt/aqt/forms/template.py
@@ -0,0 +1,5 @@
+from aqt.qt import qtmajor
+if qtmajor > 5:
+ from .template_qt6 import *
+else:
+ from .template_qt5 import * # type: ignore
diff --git a/qt/aqt/forms/template_qt6.py b/qt/aqt/forms/template_qt6.py
new file mode 120000
index 000000000..284420bf2
--- /dev/null
+++ b/qt/aqt/forms/template_qt6.py
@@ -0,0 +1 @@
+../../../bazel-bin/qt/aqt/forms/template_qt6.py
\ No newline at end of file
diff --git a/qt/aqt/importing.py b/qt/aqt/importing.py
index 46f852e3d..7260ac16f 100644
--- a/qt/aqt/importing.py
+++ b/qt/aqt/importing.py
@@ -35,7 +35,7 @@ from aqt.utils import (
class ChangeMap(QDialog):
def __init__(self, mw: AnkiQt, model: dict, current: str) -> None:
- QDialog.__init__(self, mw, Qt.Window)
+ QDialog.__init__(self, mw, Qt.WindowType.Window)
self.mw = mw
self.model = model
self.frm = aqt.forms.changemap.Ui_ChangeMap()
@@ -85,13 +85,14 @@ class ImportDialog(QDialog):
_DEFAULT_FILE_DELIMITER = "\t"
def __init__(self, mw: AnkiQt, importer: Any) -> None:
- QDialog.__init__(self, mw, Qt.Window)
+ QDialog.__init__(self, mw, Qt.WindowType.Window)
self.mw = mw
self.importer = importer
self.frm = aqt.forms.importing.Ui_ImportDialog()
self.frm.setupUi(self)
qconnect(
- self.frm.buttonBox.button(QDialogButtonBox.Help).clicked, self.helpRequested
+ self.frm.buttonBox.button(QDialogButtonBox.StandardButton.Help).clicked,
+ self.helpRequested,
)
disable_help_button(self)
self.setupMappingFrame()
@@ -108,7 +109,7 @@ class ImportDialog(QDialog):
self.frm.tagModified.setCol(self.mw.col)
# import button
b = QPushButton(tr.actions_import())
- self.frm.buttonBox.addButton(b, QDialogButtonBox.AcceptRole)
+ self.frm.buttonBox.addButton(b, QDialogButtonBox.ButtonRole.AcceptRole)
self.exec()
def setupOptions(self) -> None:
diff --git a/qt/aqt/main.py b/qt/aqt/main.py
index 344186cc1..1249e764f 100644
--- a/qt/aqt/main.py
+++ b/qt/aqt/main.py
@@ -107,10 +107,7 @@ class AnkiQt(QMainWindow):
self.pm = profileManager
# init rest of app
self.safeMode = (
- bool(
- cast(Qt.KeyboardModifier, self.app.queryKeyboardModifiers())
- & Qt.ShiftModifier
- )
+ bool(self.app.queryKeyboardModifiers() & Qt.KeyboardModifier.ShiftModifier)
or self.opts.safemode
)
try:
@@ -817,15 +814,15 @@ title="{}" {}>{}""".format(
self.form.setupUi(self)
# toolbar
tweb = self.toolbarWeb = aqt.webview.AnkiWebView(title="top toolbar")
- tweb.setFocusPolicy(Qt.WheelFocus)
+ tweb.setFocusPolicy(Qt.FocusPolicy.WheelFocus)
self.toolbar = aqt.toolbar.Toolbar(self, tweb)
# main area
self.web = aqt.webview.AnkiWebView(title="main webview")
- self.web.setFocusPolicy(Qt.WheelFocus)
+ self.web.setFocusPolicy(Qt.FocusPolicy.WheelFocus)
self.web.setMinimumWidth(400)
# bottom area
sweb = self.bottomWeb = aqt.webview.AnkiWebView(title="bottom toolbar")
- sweb.setFocusPolicy(Qt.WheelFocus)
+ sweb.setFocusPolicy(Qt.FocusPolicy.WheelFocus)
# add in a layout
self.mainLayout = QVBoxLayout()
self.mainLayout.setContentsMargins(0, 0, 0, 0)
@@ -991,7 +988,7 @@ title="{}" {}>{}""".format(
def raiseMain(self) -> bool:
if not self.app.activeWindow():
# make sure window is shown
- self.setWindowState(self.windowState() & ~Qt.WindowMinimized) # type: ignore
+ self.setWindowState(self.windowState() & ~Qt.WindowState.WindowMinimized) # type: ignore
return True
def setupStyle(self) -> None:
@@ -1032,7 +1029,7 @@ title="{}" {}>{}""".format(
def clearStateShortcuts(self) -> None:
for qs in self.stateShortcuts:
- sip.delete(qs)
+ sip.delete(qs) # type: ignore
self.stateShortcuts = []
def onStudyKey(self) -> None:
@@ -1393,7 +1390,7 @@ title="{}" {}>{}""".format(
frm.setupUi(d)
restoreGeom(d, "DebugConsoleWindow")
restoreSplitter(frm.splitter, "DebugConsoleWindow")
- font = QFontDatabase.systemFont(QFontDatabase.FixedFont)
+ font = QFontDatabase.systemFont(QFontDatabase.SystemFont.FixedFont)
font.setPointSize(frm.text.font().pointSize() + 1)
frm.text.setFont(font)
frm.log.setFont(font)
@@ -1485,7 +1482,7 @@ title="{}" {}>{}""".format(
def onDebugPrint(self, frm: aqt.forms.debug.Ui_Dialog) -> None:
cursor = frm.text.textCursor()
position = cursor.position()
- cursor.select(QTextCursor.LineUnderCursor)
+ cursor.select(QTextCursor.SelectionType.LineUnderCursor)
line = cursor.selectedText()
pfx, sfx = "pp(", ")"
if not line.startswith(pfx):
@@ -1566,7 +1563,7 @@ title="{}" {}>{}""".format(
cast(QAction, action).setStatusTip("")
def onMacMinimize(self) -> None:
- self.setWindowState(self.windowState() | Qt.WindowMinimized) # type: ignore
+ self.setWindowState(self.windowState() | Qt.WindowState.WindowMinimized) # type: ignore
# Single instance support
##########################################################################
@@ -1606,7 +1603,7 @@ title="{}" {}>{}""".format(
if isWin:
# on windows we can raise the window by minimizing and restoring
self.showMinimized()
- self.setWindowState(Qt.WindowActive)
+ self.setWindowState(Qt.WindowState.WindowActive)
self.showNormal()
else:
# on osx we can raise the window. on unity the icon in the tray will just flash.
diff --git a/qt/aqt/mediacheck.py b/qt/aqt/mediacheck.py
index 299031444..fd58d4274 100644
--- a/qt/aqt/mediacheck.py
+++ b/qt/aqt/mediacheck.py
@@ -105,33 +105,33 @@ class MediaChecker:
text = QPlainTextEdit()
text.setReadOnly(True)
text.setPlainText(report)
- text.setWordWrapMode(QTextOption.NoWrap)
+ text.setWordWrapMode(QTextOption.WrapMode.NoWrap)
layout.addWidget(text)
- box = QDialogButtonBox(QDialogButtonBox.Close)
+ box = QDialogButtonBox(QDialogButtonBox.StandardButton.Close)
layout.addWidget(box)
if output.unused:
b = QPushButton(tr.media_check_delete_unused())
b.setAutoDefault(False)
- box.addButton(b, QDialogButtonBox.RejectRole)
+ box.addButton(b, QDialogButtonBox.ButtonRole.RejectRole)
qconnect(b.clicked, lambda c: self._on_trash_files(output.unused))
if output.missing:
if any(map(lambda x: x.startswith("latex-"), output.missing)):
b = QPushButton(tr.media_check_render_latex())
b.setAutoDefault(False)
- box.addButton(b, QDialogButtonBox.RejectRole)
+ box.addButton(b, QDialogButtonBox.ButtonRole.RejectRole)
qconnect(b.clicked, self._on_render_latex)
if output.have_trash:
b = QPushButton(tr.media_check_empty_trash())
b.setAutoDefault(False)
- box.addButton(b, QDialogButtonBox.RejectRole)
+ box.addButton(b, QDialogButtonBox.ButtonRole.RejectRole)
qconnect(b.clicked, lambda c: self._on_empty_trash())
b = QPushButton(tr.media_check_restore_trash())
b.setAutoDefault(False)
- box.addButton(b, QDialogButtonBox.RejectRole)
+ box.addButton(b, QDialogButtonBox.ButtonRole.RejectRole)
qconnect(b.clicked, lambda c: self._on_restore_trash())
qconnect(box.rejected, diag.reject)
diff --git a/qt/aqt/mediasync.py b/qt/aqt/mediasync.py
index 8b8e4a92d..1c5a366ff 100644
--- a/qt/aqt/mediasync.py
+++ b/qt/aqt/mediasync.py
@@ -162,7 +162,9 @@ class MediaSyncDialog(QDialog):
self.abort_button = QPushButton(tr.sync_abort_button())
qconnect(self.abort_button.clicked, self._on_abort)
self.abort_button.setAutoDefault(False)
- self.form.buttonBox.addButton(self.abort_button, QDialogButtonBox.ActionRole)
+ self.form.buttonBox.addButton(
+ self.abort_button, QDialogButtonBox.ButtonRole.ActionRole
+ )
self.abort_button.setHidden(not self._syncer.is_syncing())
gui_hooks.media_sync_did_progress.append(self._on_log_entry)
@@ -171,7 +173,7 @@ class MediaSyncDialog(QDialog):
self.form.plainTextEdit.setPlainText(
"\n".join(self._entry_to_text(x) for x in syncer.entries())
)
- self.form.plainTextEdit.moveCursor(QTextCursor.End)
+ self.form.plainTextEdit.moveCursor(QTextCursor.MoveOperation.End)
self.show()
def reject(self) -> None:
diff --git a/qt/aqt/models.py b/qt/aqt/models.py
index e395b3d3d..5904acbda 100644
--- a/qt/aqt/models.py
+++ b/qt/aqt/models.py
@@ -48,7 +48,7 @@ class Models(QDialog):
parent = parent or mw
self.fromMain = fromMain
self.selected_notetype_id = selected_notetype_id
- QDialog.__init__(self, parent, Qt.Window)
+ QDialog.__init__(self, parent, Qt.WindowType.Window)
self.col = mw.col.weakref()
assert self.col
self.mm = self.col.models
@@ -97,7 +97,7 @@ class Models(QDialog):
default_buttons.append((tr.notetypes_options(), self.onAdvanced))
for label, func in gui_hooks.models_did_init_buttons(default_buttons, self):
- button = box.addButton(label, QDialogButtonBox.ActionRole)
+ button = box.addButton(label, QDialogButtonBox.ButtonRole.ActionRole)
qconnect(button.clicked, func)
qconnect(f.modelsList.itemDoubleClicked, self.onRename)
@@ -235,7 +235,7 @@ class AddModel(QDialog):
self.parent_ = parent or mw
self.mw = mw
self.col = mw.col
- QDialog.__init__(self, self.parent_, Qt.Window)
+ QDialog.__init__(self, self.parent_, Qt.WindowType.Window)
self.model = None
self.dialog = aqt.forms.addmodel.Ui_Dialog()
self.dialog.setupUi(self)
diff --git a/qt/aqt/operations/scheduling.py b/qt/aqt/operations/scheduling.py
index 52bc95f01..7cfeea6f8 100644
--- a/qt/aqt/operations/scheduling.py
+++ b/qt/aqt/operations/scheduling.py
@@ -93,7 +93,7 @@ def reposition_new_cards_dialog(
d = QDialog(parent)
disable_help_button(d)
- d.setWindowModality(Qt.WindowModal)
+ d.setWindowModality(Qt.WindowModality.WindowModal)
frm = aqt.forms.reposition.Ui_Dialog()
frm.setupUi(d)
diff --git a/qt/aqt/pinnedmodules.py b/qt/aqt/pinnedmodules.py
index a8cbaafe6..8fe9e2c39 100644
--- a/qt/aqt/pinnedmodules.py
+++ b/qt/aqt/pinnedmodules.py
@@ -26,10 +26,13 @@ import typing
import uuid
# other modules we require that may not be automatically included
-import PyQt5.QtSvg
-import PyQt5.QtMultimedia
+try:
+ import PyQt6.QtSvg # type: ignore
+ import PyQt6.QtMultimedia # type: ignore
+except:
+ import PyQt5.QtSvg # type: ignore
+ import PyQt5.QtMultimedia # type: ignore
import socks
-import pyaudio
# legacy compat
import anki.storage
diff --git a/qt/aqt/preferences.py b/qt/aqt/preferences.py
index 6a163df97..e10029b94 100644
--- a/qt/aqt/preferences.py
+++ b/qt/aqt/preferences.py
@@ -9,21 +9,25 @@ from anki.collection import OpChanges
from anki.consts import newCardSchedulingLabels
from aqt import AnkiQt
from aqt.operations.collection import set_preferences
-from aqt.profiles import RecordingDriver, VideoDriver
+from aqt.profiles import VideoDriver
from aqt.qt import *
from aqt.utils import HelpPage, disable_help_button, openHelp, showInfo, showWarning, tr
class Preferences(QDialog):
def __init__(self, mw: AnkiQt) -> None:
- QDialog.__init__(self, mw, Qt.Window)
+ QDialog.__init__(self, mw, Qt.WindowType.Window)
self.mw = mw
self.prof = self.mw.pm.profile
self.form = aqt.forms.preferences.Ui_Preferences()
self.form.setupUi(self)
disable_help_button(self)
- self.form.buttonBox.button(QDialogButtonBox.Help).setAutoDefault(False)
- self.form.buttonBox.button(QDialogButtonBox.Close).setAutoDefault(False)
+ self.form.buttonBox.button(QDialogButtonBox.StandardButton.Help).setAutoDefault(
+ False
+ )
+ self.form.buttonBox.button(
+ QDialogButtonBox.StandardButton.Close
+ ).setAutoDefault(False)
qconnect(
self.form.buttonBox.helpRequested, lambda: openHelp(HelpPage.PREFERENCES)
)
@@ -130,48 +134,13 @@ class Preferences(QDialog):
def setup_profile(self) -> None:
"Setup options stored in the user profile."
- self.setup_recording_driver()
self.setup_network()
self.setup_backup()
def update_profile(self) -> None:
- self.update_recording_driver()
self.update_network()
self.update_backup()
- # Profile: recording driver
- ######################################################################
-
- def setup_recording_driver(self) -> None:
- self._recording_drivers = [
- RecordingDriver.QtAudioInput,
- RecordingDriver.PyAudio,
- ]
- # The plan is to phase out PyAudio soon, so will hold off on
- # making this string translatable for now.
- self.form.recording_driver.addItems(
- [
- f"Voice recording driver: {driver.value}"
- for driver in self._recording_drivers
- ]
- )
- self.form.recording_driver.setCurrentIndex(
- self._recording_drivers.index(self.mw.pm.recording_driver())
- )
-
- def update_recording_driver(self) -> None:
- new_audio_driver = self._recording_drivers[
- self.form.recording_driver.currentIndex()
- ]
- if self.mw.pm.recording_driver() != new_audio_driver:
- self.mw.pm.set_recording_driver(new_audio_driver)
- if new_audio_driver == RecordingDriver.PyAudio:
- showInfo(
- """\
-The PyAudio driver will likely be removed in a future update. If you find it works better \
-for you than the default driver, please let us know on the Anki forums."""
- )
-
# Profile: network
######################################################################
@@ -303,6 +272,7 @@ for you than the default driver, please let us know on the Anki forums."""
self.form.video_driver.setCurrentIndex(
self.video_drivers.index(self.mw.pm.video_driver())
)
+ self.form.video_driver.setVisible(qtmajor == 5)
def update_video_driver(self) -> None:
new_driver = self.video_drivers[self.form.video_driver.currentIndex()]
diff --git a/qt/aqt/profiles.py b/qt/aqt/profiles.py
index 0016db4e1..59ba16572 100644
--- a/qt/aqt/profiles.py
+++ b/qt/aqt/profiles.py
@@ -31,11 +31,6 @@ from aqt.utils import disable_help_button, showWarning, tr
# - Saves in sqlite rather than a flat file so the config can't be corrupted
-class RecordingDriver(Enum):
- PyAudio = "PyAudio"
- QtAudioInput = "Qt"
-
-
class VideoDriver(Enum):
OpenGL = "auto"
ANGLE = "angle"
@@ -163,25 +158,32 @@ class ProfileManager:
def _unpickle(self, data: bytes) -> Any:
class Unpickler(pickle.Unpickler):
- def find_class(self, module: str, name: str) -> Any:
- if module == "PyQt5.sip":
- try:
- import PyQt5.sip # pylint: disable=unused-import
- except:
- # use old sip location
- module = "sip"
- fn = super().find_class(module, name)
- if module == "sip" and name == "_unpickle_type":
+ def find_class(self, class_module: str, name: str) -> Any:
+ # handle sip lookup ourselves, mapping to current Qt version
+ if class_module == "sip" or class_module.endswith(".sip"):
- def wrapper(mod, obj, args) -> Any: # type: ignore
- if mod.startswith("PyQt4") and obj == "QByteArray":
- # can't trust str objects from python 2
- return QByteArray()
- return fn(mod, obj, args)
+ def unpickle_type(module: str, klass: str, args: Any) -> Any:
+ if qtmajor > 5:
+ module = module.replace("Qt5", "Qt6")
+ else:
+ module = module.replace("Qt6", "Qt5")
+ if klass == "QByteArray":
+ if module.startswith("PyQt4"):
+ # can't trust str objects from python 2
+ return QByteArray()
+ else:
+ # return the bytes directly
+ return args[0]
+ elif name == "_unpickle_enum":
+ if qtmajor == 5:
+ return sip._unpickle_enum(module, klass, args) # type: ignore
+ else:
+ # old style enums can't be unpickled
+ return None
+ else:
+ return sip._unpickle_type(module, klass, args) # type: ignore
- return wrapper
- else:
- return fn
+ return unpickle_type
up = Unpickler(io.BytesIO(data), errors="ignore")
return up.load()
@@ -454,9 +456,9 @@ create table if not exists profiles
code = obj[1]
name = obj[0]
r = QMessageBox.question(
- None, "Anki", tr.profiles_confirm_lang_choice(lang=name), QMessageBox.Yes | QMessageBox.No, QMessageBox.No # type: ignore
+ None, "Anki", tr.profiles_confirm_lang_choice(lang=name), QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No, QMessageBox.StandardButton.No # type: ignore
)
- if r != QMessageBox.Yes:
+ if r != QMessageBox.StandardButton.Yes:
return self.setDefaultLang(f.lang.currentRow())
self.setLang(code)
@@ -549,18 +551,6 @@ create table if not exists profiles
def set_auto_sync_media_minutes(self, val: int) -> None:
self.profile["autoSyncMediaMinutes"] = val
- def recording_driver(self) -> RecordingDriver:
- if driver := self.profile.get("recordingDriver"):
- try:
- return RecordingDriver(driver)
- except ValueError:
- # revert to default
- pass
- return RecordingDriver.QtAudioInput
-
- def set_recording_driver(self, driver: RecordingDriver) -> None:
- self.profile["recordingDriver"] = driver.value
-
def show_browser_table_tooltips(self) -> bool:
return self.profile.get("browserTableTooltips", True)
diff --git a/qt/aqt/progress.py b/qt/aqt/progress.py
index 2d8b8e290..0e89195e3 100644
--- a/qt/aqt/progress.py
+++ b/qt/aqt/progress.py
@@ -92,7 +92,7 @@ class ProgressManager:
self._win.form.progressBar.setTextVisible(False)
self._win.form.label.setText(label)
self._win.setWindowTitle("Anki")
- self._win.setWindowModality(Qt.ApplicationModal)
+ self._win.setWindowModality(Qt.WindowModality.ApplicationModal)
self._win.setMinimumWidth(300)
self._busy_cursor_timer = QTimer(self.mw)
self._busy_cursor_timer.setSingleShot(True)
@@ -177,7 +177,7 @@ class ProgressManager:
elap = time.time() - self._shown
if elap >= 0.5:
break
- self.app.processEvents(QEventLoop.ExcludeUserInputEvents) # type: ignore #possibly related to https://github.com/python/mypy/issues/6910
+ self.app.processEvents(QEventLoop.ProcessEventsFlag.ExcludeUserInputEvents) # type: ignore #possibly related to https://github.com/python/mypy/issues/6910
# if the parent window has been deleted, the progress dialog may have
# already been dropped; delete it if it hasn't been
if not sip.isdeleted(self._win):
@@ -186,7 +186,7 @@ class ProgressManager:
self._shown = 0
def _set_busy_cursor(self) -> None:
- self.mw.app.setOverrideCursor(QCursor(Qt.WaitCursor))
+ self.mw.app.setOverrideCursor(QCursor(Qt.CursorShape.WaitCursor))
def _restore_cursor(self) -> None:
self.app.restoreOverrideCursor()
@@ -235,6 +235,6 @@ class ProgressDialog(QDialog):
evt.ignore()
def keyPressEvent(self, evt: QKeyEvent) -> None:
- if evt.key() == Qt.Key_Escape:
+ if evt.key() == Qt.Key.Key_Escape:
evt.ignore()
self.wantCancel = True
diff --git a/qt/aqt/qt.py b/qt/aqt/qt.py
index 46cf9f6e7..ffc6f6512 100644
--- a/qt/aqt/qt.py
+++ b/qt/aqt/qt.py
@@ -9,14 +9,34 @@ import sys
import traceback
from typing import Callable, Union
-from PyQt5.Qt import * # type: ignore
-from PyQt5.QtCore import *
-from PyQt5.QtCore import pyqtRemoveInputHook # pylint: disable=no-name-in-module
-from PyQt5.QtGui import * # type: ignore
-from PyQt5.QtNetwork import QLocalServer, QLocalSocket, QNetworkProxy
-from PyQt5.QtWebChannel import QWebChannel
-from PyQt5.QtWebEngineWidgets import *
-from PyQt5.QtWidgets import *
+try:
+ from PyQt6 import sip
+ from PyQt6.QtCore import *
+
+ # conflicting Qt and qFuzzyCompare definitions require an ignore
+ from PyQt6.QtGui import * # type: ignore[misc]
+ from PyQt6.QtNetwork import QLocalServer, QLocalSocket, QNetworkProxy
+ from PyQt6.QtWebChannel import QWebChannel
+ from PyQt6.QtWebEngineCore import *
+ from PyQt6.QtWebEngineWidgets import *
+ from PyQt6.QtWidgets import *
+except:
+ from PyQt5.QtCore import * # type: ignore
+ from PyQt5.QtGui import * # type: ignore
+ from PyQt5.QtNetwork import ( # type: ignore
+ QLocalServer,
+ QLocalSocket,
+ QNetworkProxy,
+ )
+ from PyQt5.QtWebChannel import QWebChannel # type: ignore
+ from PyQt5.QtWebEngineCore import * # type: ignore
+ from PyQt5.QtWebEngineWidgets import * # type: ignore
+ from PyQt5.QtWidgets import * # type: ignore
+
+ try:
+ from PyQt5 import sip # type: ignore
+ except ImportError:
+ import sip # type: ignore
from anki.utils import isMac, isWin
@@ -24,12 +44,6 @@ from anki.utils import isMac, isWin
os.environ["LIBOVERLAY_SCROLLBAR"] = "0"
-try:
- from PyQt5 import sip
-except ImportError:
- import sip # type: ignore
-
-
def debug() -> None:
from pdb import set_trace
@@ -57,6 +71,8 @@ if qtmajor < 5 or (qtmajor == 5 and qtminor < 14):
raise Exception("Anki does not support your Qt version.")
-def qconnect(signal: Union[Callable, pyqtSignal], func: Callable) -> None:
- "Helper to work around type checking not working with signal.connect(func)."
+def qconnect(
+ signal: Union[Callable, pyqtSignal, pyqtBoundSignal], func: Callable
+) -> None:
+ """Helper to work around type checking not working with signal.connect(func)."""
signal.connect(func) # type: ignore
diff --git a/qt/aqt/qt5.py b/qt/aqt/qt5.py
new file mode 100644
index 000000000..d87a3fa2c
--- /dev/null
+++ b/qt/aqt/qt5.py
@@ -0,0 +1,98 @@
+# Copyright: Ankitects Pty Ltd and contributors
+# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
+
+# pylint: skip-file
+
+"""
+PyQt5-only code
+"""
+
+import wave
+from concurrent.futures import Future
+from typing import cast
+
+import aqt
+
+from .qt import *
+from .sound import Recorder
+from .utils import showWarning
+
+
+class QtAudioInputRecorder(Recorder):
+ def __init__(self, output_path: str, mw: aqt.AnkiQt, parent: QWidget) -> None:
+ super().__init__(output_path)
+
+ self.mw = mw
+ self._parent = parent
+
+ from PyQt5.QtMultimedia import ( # type: ignore
+ QAudioDeviceInfo,
+ QAudioFormat,
+ QAudioInput,
+ )
+
+ format = QAudioFormat()
+ format.setChannelCount(1)
+ format.setSampleRate(44100)
+ format.setSampleSize(16)
+ format.setCodec("audio/pcm")
+ format.setByteOrder(QAudioFormat.LittleEndian)
+ format.setSampleType(QAudioFormat.SignedInt)
+
+ device = QAudioDeviceInfo.defaultInputDevice()
+ if not device.isFormatSupported(format):
+ format = device.nearestFormat(format)
+ print("format changed")
+ print("channels", format.channelCount())
+ print("rate", format.sampleRate())
+ print("size", format.sampleSize())
+ self._format = format
+
+ self._audio_input = QAudioInput(device, format, parent)
+
+ def start(self, on_done: Callable[[], None]) -> None:
+ self._iodevice = self._audio_input.start()
+ self._buffer = bytearray()
+ qconnect(self._iodevice.readyRead, self._on_read_ready)
+ super().start(on_done)
+
+ def _on_read_ready(self) -> None:
+ self._buffer.extend(cast(bytes, self._iodevice.readAll()))
+
+ def stop(self, on_done: Callable[[str], None]) -> None:
+ def on_stop_timer() -> None:
+ # read anything remaining in buffer & stop
+ self._on_read_ready()
+ self._audio_input.stop()
+
+ if err := self._audio_input.error():
+ showWarning(f"recording failed: {err}")
+ return
+
+ def write_file() -> None:
+ # swallow the first 300ms to allow audio device to quiesce
+ wait = int(44100 * self.STARTUP_DELAY)
+ if len(self._buffer) <= wait:
+ return
+ self._buffer = self._buffer[wait:]
+
+ # write out the wave file
+ wf = wave.open(self.output_path, "wb")
+ wf.setnchannels(self._format.channelCount())
+ wf.setsampwidth(self._format.sampleSize() // 8)
+ wf.setframerate(self._format.sampleRate())
+ wf.writeframes(self._buffer)
+ wf.close()
+
+ def and_then(fut: Future) -> None:
+ fut.result()
+ Recorder.stop(self, on_done)
+
+ self.mw.taskman.run_in_background(write_file, and_then)
+
+ # schedule the stop for half a second in the future,
+ # to avoid truncating the end of the recording
+ self._stop_timer = t = QTimer(self._parent)
+ t.timeout.connect(on_stop_timer) # type: ignore
+ t.setSingleShot(True)
+ t.start(500)
diff --git a/qt/aqt/reviewer.py b/qt/aqt/reviewer.py
index 7d82b146f..afc309655 100644
--- a/qt/aqt/reviewer.py
+++ b/qt/aqt/reviewer.py
@@ -13,8 +13,6 @@ from dataclasses import dataclass
from enum import Enum, auto
from typing import Any, Callable, Literal, Match, Sequence, cast
-from PyQt5.QtCore import Qt
-
from anki import hooks
from anki.cards import Card, CardId
from anki.collection import Config, OpChanges, OpChangesWithCount
@@ -435,11 +433,11 @@ class Reviewer:
return [
("e", self.mw.onEditCurrent),
(" ", self.onEnterKey),
- (Qt.Key_Return, self.onEnterKey),
- (Qt.Key_Enter, self.onEnterKey),
+ (Qt.Key.Key_Return, self.onEnterKey),
+ (Qt.Key.Key_Enter, self.onEnterKey),
("m", self.showContextMenu),
("r", self.replayAudio),
- (Qt.Key_F5, self.replayAudio),
+ (Qt.Key.Key_F5, self.replayAudio),
*(
(f"Ctrl+{flag.index}", self.set_flag_func(flag.index))
for flag in self.mw.flags.all()
@@ -877,7 +875,7 @@ time = %(time)d;
part2 = tr.studying_minute(count=mins)
fin = tr.studying_finish()
diag = askUserDialog(f"{part1} {part2}", [tr.studying_continue(), fin])
- diag.setIcon(QMessageBox.Information)
+ diag.setIcon(QMessageBox.Icon.Information)
if diag.run() == fin:
self.mw.moveToState("deckBrowser")
return True
diff --git a/qt/aqt/sound.py b/qt/aqt/sound.py
index d0d088889..ee6c38ae2 100644
--- a/qt/aqt/sound.py
+++ b/qt/aqt/sound.py
@@ -9,13 +9,12 @@ import platform
import re
import subprocess
import sys
-import threading
import time
import wave
from abc import ABC, abstractmethod
from concurrent.futures import Future
from operator import itemgetter
-from typing import TYPE_CHECKING, Any, Callable, cast
+from typing import Any, Callable, cast
from markdown import markdown
@@ -23,11 +22,9 @@ import aqt
from anki import hooks
from anki.cards import Card
from anki.sound import AV_REF_RE, AVTag, SoundOrVideoTag
-from anki.types import assert_exhaustive
from anki.utils import isLin, isMac, isWin, namedtmp
from aqt import gui_hooks
from aqt.mpv import MPV, MPVBase, MPVCommandError
-from aqt.profiles import RecordingDriver
from aqt.qt import *
from aqt.taskman import TaskManager
from aqt.utils import (
@@ -40,9 +37,6 @@ from aqt.utils import (
tr,
)
-if TYPE_CHECKING:
- from PyQt5.QtMultimedia import QAudioRecorder
-
# AV player protocol
##########################################################################
@@ -556,43 +550,36 @@ class QtAudioInputRecorder(Recorder):
self.mw = mw
self._parent = parent
- from PyQt5.QtMultimedia import QAudioDeviceInfo, QAudioFormat, QAudioInput
+ from PyQt6.QtMultimedia import QAudioFormat, QAudioSource # type: ignore
format = QAudioFormat()
format.setChannelCount(1)
format.setSampleRate(44100)
- format.setSampleSize(16)
- format.setCodec("audio/pcm")
- format.setByteOrder(QAudioFormat.LittleEndian)
- format.setSampleType(QAudioFormat.SignedInt)
+ format.setSampleFormat(QAudioFormat.SampleFormat.Int16)
- device = QAudioDeviceInfo.defaultInputDevice()
- if not device.isFormatSupported(format):
- format = device.nearestFormat(format)
- print("format changed")
- print("channels", format.channelCount())
- print("rate", format.sampleRate())
- print("size", format.sampleSize())
- self._format = format
+ source = QAudioSource(format, parent)
- self._audio_input = QAudioInput(device, format, parent)
+ self._format = source.format()
+ self._audio_input = source
def start(self, on_done: Callable[[], None]) -> None:
self._iodevice = self._audio_input.start()
- self._buffer = b""
- self._iodevice.readyRead.connect(self._on_read_ready) # type: ignore
+ self._buffer = bytearray()
+ qconnect(self._iodevice.readyRead, self._on_read_ready)
super().start(on_done)
def _on_read_ready(self) -> None:
- self._buffer += cast(bytes, self._iodevice.readAll())
+ self._buffer.extend(cast(bytes, self._iodevice.readAll()))
def stop(self, on_done: Callable[[str], None]) -> None:
+ from PyQt6.QtMultimedia import QAudio
+
def on_stop_timer() -> None:
# read anything remaining in buffer & stop
self._on_read_ready()
self._audio_input.stop()
- if err := self._audio_input.error():
+ if (err := self._audio_input.error()) != QAudio.Error.NoError:
showWarning(f"recording failed: {err}")
return
@@ -606,7 +593,7 @@ class QtAudioInputRecorder(Recorder):
# write out the wave file
wf = wave.open(self.output_path, "wb")
wf.setnchannels(self._format.channelCount())
- wf.setsampwidth(self._format.sampleSize() // 8)
+ wf.setsampwidth(2)
wf.setframerate(self._format.sampleRate())
wf.writeframes(self._buffer)
wf.close()
@@ -625,90 +612,6 @@ class QtAudioInputRecorder(Recorder):
t.start(500)
-# PyAudio recording
-##########################################################################
-
-try:
- import pyaudio
-except:
- pyaudio = None
-
-
-PYAU_CHANNELS = 1
-PYAU_INPUT_INDEX: int | None = None
-
-
-class PyAudioThreadedRecorder(threading.Thread):
- def __init__(self, output_path: str, startup_delay: float) -> None:
- threading.Thread.__init__(self)
- self._output_path = output_path
- self._startup_delay = startup_delay
- self.finish = False
- # though we're using pyaudio here, we rely on Qt to trigger
- # the permission prompt on macOS
- if isMac and qtminor > 12:
- from PyQt5.QtMultimedia import QAudioDeviceInfo
-
- QAudioDeviceInfo.defaultInputDevice()
-
- def run(self) -> None:
- chunk = 1024
- p = pyaudio.PyAudio()
-
- rate = int(p.get_default_input_device_info()["defaultSampleRate"])
- PYAU_FORMAT = pyaudio.paInt16
-
- stream = p.open(
- format=PYAU_FORMAT,
- channels=PYAU_CHANNELS,
- rate=rate,
- input=True,
- input_device_index=PYAU_INPUT_INDEX,
- frames_per_buffer=chunk,
- )
-
- # swallow the first 300ms to allow audio device to quiesce
- wait = int(rate * self._startup_delay)
- stream.read(wait, exception_on_overflow=False)
-
- # read data in a loop until self.finish is set
- data = b""
- while not self.finish:
- data += stream.read(chunk, exception_on_overflow=False)
-
- # write out the wave file
- stream.close()
- p.terminate()
- wf = wave.open(self._output_path, "wb")
- wf.setnchannels(PYAU_CHANNELS)
- wf.setsampwidth(p.get_sample_size(PYAU_FORMAT))
- wf.setframerate(rate)
- wf.writeframes(data)
- wf.close()
-
-
-class PyAudioRecorder(Recorder):
- def __init__(self, mw: aqt.AnkiQt, output_path: str) -> None:
- super().__init__(output_path)
- self.mw = mw
-
- def start(self, on_done: Callable[[], None]) -> None:
- self.thread = PyAudioThreadedRecorder(self.output_path, self.STARTUP_DELAY)
- self.thread.start()
- super().start(on_done)
-
- def stop(self, on_done: Callable[[str], None]) -> None:
- # ensure at least a second captured
- while self.duration() < 1:
- time.sleep(0.1)
-
- def func(fut: Future) -> None:
- Recorder.stop(self, on_done)
-
- self.thread.finish = True
- self.mw.taskman.run_in_background(self.thread.join, func)
-
-
# Recording dialog
##########################################################################
@@ -741,15 +644,18 @@ class RecordDialog(QDialog):
hbox.addWidget(self.label)
v = QVBoxLayout()
v.addLayout(hbox)
- buts = QDialogButtonBox.Save | QDialogButtonBox.Cancel
+ buts = (
+ QDialogButtonBox.StandardButton.Save
+ | QDialogButtonBox.StandardButton.Cancel
+ )
b = QDialogButtonBox(buts) # type: ignore
v.addWidget(b)
self.setLayout(v)
- save_button = b.button(QDialogButtonBox.Save)
+ save_button = b.button(QDialogButtonBox.StandardButton.Save)
save_button.setDefault(True)
save_button.setAutoDefault(True)
qconnect(save_button.clicked, self.accept)
- cancel_button = b.button(QDialogButtonBox.Cancel)
+ cancel_button = b.button(QDialogButtonBox.StandardButton.Cancel)
cancel_button.setDefault(False)
cancel_button.setAutoDefault(False)
qconnect(cancel_button.clicked, self.reject)
@@ -760,15 +666,14 @@ class RecordDialog(QDialog):
saveGeom(self, "audioRecorder2")
def _start_recording(self) -> None:
- driver = self.mw.pm.recording_driver()
- if driver is RecordingDriver.PyAudio:
- self._recorder = PyAudioRecorder(self.mw, namedtmp("rec.wav"))
- elif driver is RecordingDriver.QtAudioInput:
+ if qtmajor > 5:
self._recorder = QtAudioInputRecorder(
namedtmp("rec.wav"), self.mw, self._parent
)
else:
- assert_exhaustive(driver)
+ from .qt5 import QtAudioInputRecorder as Qt5Recorder
+
+ self._recorder = Qt5Recorder(namedtmp("rec.wav"), self.mw, self._parent)
self._recorder.start(self._start_timer)
def _start_timer(self) -> None:
diff --git a/qt/aqt/stats.py b/qt/aqt/stats.py
index 755a3d355..cd2b32d5d 100644
--- a/qt/aqt/stats.py
+++ b/qt/aqt/stats.py
@@ -25,7 +25,7 @@ class NewDeckStats(QDialog):
"""New deck stats."""
def __init__(self, mw: aqt.main.AnkiQt) -> None:
- QDialog.__init__(self, mw, Qt.Window)
+ QDialog.__init__(self, mw, Qt.WindowType.Window)
mw.garbage_collect_on_dialog_finish(self)
self.mw = mw
self.name = "deckStats"
@@ -40,7 +40,9 @@ class NewDeckStats(QDialog):
f.groupBox.setVisible(False)
f.groupBox_2.setVisible(False)
restoreGeom(self, self.name)
- b = f.buttonBox.addButton(tr.statistics_save_pdf(), QDialogButtonBox.ActionRole)
+ b = f.buttonBox.addButton(
+ tr.statistics_save_pdf(), QDialogButtonBox.ButtonRole.ActionRole
+ )
qconnect(b.clicked, self.saveImage)
b.setAutoDefault(False)
maybeHideClose(self.form.buttonBox)
@@ -104,7 +106,7 @@ class DeckStats(QDialog):
"""Legacy deck stats, used by some add-ons."""
def __init__(self, mw: aqt.main.AnkiQt) -> None:
- QDialog.__init__(self, mw, Qt.Window)
+ QDialog.__init__(self, mw, Qt.WindowType.Window)
mw.garbage_collect_on_dialog_finish(self)
self.mw = mw
self.name = "deckStats"
@@ -123,7 +125,9 @@ class DeckStats(QDialog):
self.setStyleSheet("QGroupBox { border: 0; }")
f.setupUi(self)
restoreGeom(self, self.name)
- b = f.buttonBox.addButton(tr.statistics_save_pdf(), QDialogButtonBox.ActionRole)
+ b = f.buttonBox.addButton(
+ tr.statistics_save_pdf(), QDialogButtonBox.ButtonRole.ActionRole
+ )
qconnect(b.clicked, self.saveImage)
b.setAutoDefault(False)
qconnect(f.groups.clicked, lambda: self.changeScope("deck"))
diff --git a/qt/aqt/studydeck.py b/qt/aqt/studydeck.py
index 7403e6437..adc432ca1 100644
--- a/qt/aqt/studydeck.py
+++ b/qt/aqt/studydeck.py
@@ -49,18 +49,18 @@ class StudyDeck(QDialog):
disable_help_button(self)
if not cancel:
self.form.buttonBox.removeButton(
- self.form.buttonBox.button(QDialogButtonBox.Cancel)
+ self.form.buttonBox.button(QDialogButtonBox.StandardButton.Cancel)
)
if buttons is not None:
for button_or_label in buttons:
self.form.buttonBox.addButton(
- button_or_label, QDialogButtonBox.ActionRole
+ button_or_label, QDialogButtonBox.ButtonRole.ActionRole
)
else:
b = QPushButton(tr.actions_add())
b.setShortcut(QKeySequence("Ctrl+N"))
b.setToolTip(shortcut(tr.decks_add_new_deck_ctrlandn()))
- self.form.buttonBox.addButton(b, QDialogButtonBox.ActionRole)
+ self.form.buttonBox.addButton(b, QDialogButtonBox.ButtonRole.ActionRole)
qconnect(b.clicked, self.onAddDeck)
if title:
self.setWindowTitle(title)
@@ -78,9 +78,9 @@ class StudyDeck(QDialog):
self.origNames = names()
self.name: Optional[str] = None
self.ok = self.form.buttonBox.addButton(
- accept or tr.decks_study(), QDialogButtonBox.AcceptRole
+ accept or tr.decks_study(), QDialogButtonBox.ButtonRole.AcceptRole
)
- self.setWindowModality(Qt.WindowModal)
+ self.setWindowModality(Qt.WindowModality.WindowModal)
qconnect(self.form.buttonBox.helpRequested, lambda: openHelp(help))
qconnect(self.form.filter.textEdited, self.redraw)
qconnect(self.form.list.itemDoubleClicked, self.accept)
@@ -90,20 +90,20 @@ class StudyDeck(QDialog):
self.exec()
def eventFilter(self, obj: QObject, evt: QEvent) -> bool:
- if isinstance(evt, QKeyEvent) and evt.type() == QEvent.KeyPress:
+ if isinstance(evt, QKeyEvent) and evt.type() == QEvent.Type.KeyPress:
new_row = current_row = self.form.list.currentRow()
rows_count = self.form.list.count()
key = evt.key()
- if key == Qt.Key_Up:
+ if key == Qt.Key.Key_Up:
new_row = current_row - 1
- elif key == Qt.Key_Down:
+ elif key == Qt.Key.Key_Down:
new_row = current_row + 1
elif (
- int(evt.modifiers()) & Qt.ControlModifier
- and Qt.Key_1 <= key <= Qt.Key_9
+ evt.modifiers() & Qt.KeyboardModifier.ControlModifier
+ and Qt.Key.Key_1 <= key <= Qt.Key.Key_9
):
- row_index = key - Qt.Key_1
+ row_index = key - Qt.Key.Key_1
if row_index < rows_count:
new_row = row_index
@@ -126,7 +126,7 @@ class StudyDeck(QDialog):
else:
idx = 0
l.setCurrentRow(idx)
- l.scrollToItem(l.item(idx), QAbstractItemView.PositionAtCenter)
+ l.scrollToItem(l.item(idx), QAbstractItemView.ScrollHint.PositionAtCenter)
def _matches(self, name: str, filt: str) -> bool:
name = name.lower()
diff --git a/qt/aqt/switch.py b/qt/aqt/switch.py
index bc97b44fd..2d8a610e1 100644
--- a/qt/aqt/switch.py
+++ b/qt/aqt/switch.py
@@ -1,5 +1,6 @@
# Copyright: Ankitects Pty Ltd and contributors
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
+from typing import cast
from aqt import colors
from aqt.qt import *
@@ -27,7 +28,7 @@ class Switch(QAbstractButton):
super().__init__(parent=parent)
self.setCheckable(True)
super().setChecked(False)
- self.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
+ self.setSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Fixed)
self._left_label = left_label
self._right_label = right_label
self._left_color = left_color
@@ -75,8 +76,8 @@ class Switch(QAbstractButton):
def paintEvent(self, _event: QPaintEvent) -> None:
painter = QPainter(self)
- painter.setRenderHint(QPainter.Antialiasing, True)
- painter.setPen(Qt.NoPen)
+ painter.setRenderHint(QPainter.RenderHint.Antialiasing, True)
+ painter.setPen(Qt.PenStyle.NoPen)
self._paint_path(painter)
self._paint_knob(painter)
self._paint_label(painter)
@@ -112,15 +113,17 @@ class Switch(QAbstractButton):
font = painter.font()
font.setPixelSize(int(1.2 * self._knob_radius))
painter.setFont(font)
- painter.drawText(self._current_knob_rectangle(), Qt.AlignCenter, self.label)
+ painter.drawText(
+ self._current_knob_rectangle(), Qt.AlignmentFlag.AlignCenter, self.label
+ )
def mouseReleaseEvent(self, event: QMouseEvent) -> None:
super().mouseReleaseEvent(event)
- if event.button() == Qt.LeftButton:
+ if event.button() == Qt.MouseButton.LeftButton:
self._animate_toggle()
- def enterEvent(self, event: QEvent) -> None:
- self.setCursor(Qt.PointingHandCursor)
+ def enterEvent(self, event: QEnterEvent) -> None:
+ self.setCursor(Qt.CursorShape.PointingHandCursor)
super().enterEvent(event)
def toggle(self) -> None:
@@ -128,7 +131,7 @@ class Switch(QAbstractButton):
self._animate_toggle()
def _animate_toggle(self) -> None:
- animation = QPropertyAnimation(self, b"position", self)
+ animation = QPropertyAnimation(self, cast(QByteArray, b"position"), self)
animation.setDuration(100)
animation.setStartValue(self.start_position)
animation.setEndValue(self.end_position)
diff --git a/qt/aqt/sync.py b/qt/aqt/sync.py
index f84df9ce8..2da20d933 100644
--- a/qt/aqt/sync.py
+++ b/qt/aqt/sync.py
@@ -292,7 +292,7 @@ def get_id_and_pass_from_user(
diag = QDialog(mw)
diag.setWindowTitle("Anki")
disable_help_button(diag)
- diag.setWindowModality(Qt.WindowModal)
+ diag.setWindowModality(Qt.WindowModality.WindowModal)
vbox = QVBoxLayout()
info_label = QLabel(
without_unicode_isolation(
@@ -313,11 +313,11 @@ def get_id_and_pass_from_user(
g.addWidget(l2, 1, 0)
passwd = QLineEdit()
passwd.setText(password)
- passwd.setEchoMode(QLineEdit.Password)
+ passwd.setEchoMode(QLineEdit.EchoMode.Password)
g.addWidget(passwd, 1, 1)
vbox.addLayout(g)
- bb = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) # type: ignore
- bb.button(QDialogButtonBox.Ok).setAutoDefault(True)
+ bb = QDialogButtonBox(QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel) # type: ignore
+ bb.button(QDialogButtonBox.StandardButton.Ok).setAutoDefault(True)
qconnect(bb.accepted, diag.accept)
qconnect(bb.rejected, diag.reject)
vbox.addWidget(bb)
diff --git a/qt/aqt/tagedit.py b/qt/aqt/tagedit.py
index b616b1737..90cbf56ed 100644
--- a/qt/aqt/tagedit.py
+++ b/qt/aqt/tagedit.py
@@ -26,9 +26,9 @@ class TagEdit(QLineEdit):
self._completer = TagCompleter(self.model, parent, self)
else:
self._completer = QCompleter(self.model, parent)
- self._completer.setCompletionMode(QCompleter.PopupCompletion)
- self._completer.setCaseSensitivity(Qt.CaseInsensitive)
- self._completer.setFilterMode(Qt.MatchContains)
+ self._completer.setCompletionMode(QCompleter.CompletionMode.PopupCompletion)
+ self._completer.setCaseSensitivity(Qt.CaseSensitivity.CaseInsensitive)
+ self._completer.setFilterMode(Qt.MatchFlag.MatchContains)
self.setCompleter(self._completer)
def setCol(self, col: Collection) -> None:
@@ -45,12 +45,15 @@ class TagEdit(QLineEdit):
QLineEdit.focusInEvent(self, evt)
def keyPressEvent(self, evt: QKeyEvent) -> None:
- if evt.key() in (Qt.Key_Up, Qt.Key_Down):
+ if evt.key() in (Qt.Key.Key_Up, Qt.Key.Key_Down):
# show completer on arrow key up/down
if not self._completer.popup().isVisible():
self.showCompleter()
return
- if evt.key() == Qt.Key_Tab and int(evt.modifiers()) & Qt.ControlModifier:
+ if (
+ evt.key() == Qt.Key.Key_Tab
+ and evt.modifiers() & Qt.KeyboardModifier.ControlModifier
+ ):
# select next completion
if not self._completer.popup().isVisible():
self.showCompleter()
@@ -61,7 +64,7 @@ class TagEdit(QLineEdit):
self._completer.setCurrentRow(0)
return
if (
- evt.key() in (Qt.Key_Enter, Qt.Key_Return)
+ evt.key() in (Qt.Key.Key_Enter, Qt.Key.Key_Return)
and self._completer.popup().isVisible()
):
# apply first completion if no suggestion selected
@@ -78,13 +81,13 @@ class TagEdit(QLineEdit):
# if it's a modifier, don't show
return
if evt.key() not in (
- Qt.Key_Enter,
- Qt.Key_Return,
- Qt.Key_Escape,
- Qt.Key_Space,
- Qt.Key_Tab,
- Qt.Key_Backspace,
- Qt.Key_Delete,
+ Qt.Key.Key_Enter,
+ Qt.Key.Key_Return,
+ Qt.Key.Key_Escape,
+ Qt.Key.Key_Space,
+ Qt.Key.Key_Tab,
+ Qt.Key.Key_Backspace,
+ Qt.Key.Key_Delete,
):
self.showCompleter()
gui_hooks.tag_editor_did_process_key(self, evt)
@@ -99,7 +102,7 @@ class TagEdit(QLineEdit):
self._completer.popup().hide()
def hideCompleter(self) -> None:
- if sip.isdeleted(self._completer):
+ if sip.isdeleted(self._completer): # type: ignore
return
self._completer.popup().hide()
diff --git a/qt/aqt/taglimit.py b/qt/aqt/taglimit.py
index 1affd4550..0a04ef3ad 100644
--- a/qt/aqt/taglimit.py
+++ b/qt/aqt/taglimit.py
@@ -13,7 +13,7 @@ from aqt.utils import disable_help_button, restoreGeom, saveGeom, showWarning, t
class TagLimit(QDialog):
def __init__(self, mw: AnkiQt, parent: CustomStudy) -> None:
- QDialog.__init__(self, parent, Qt.Window)
+ QDialog.__init__(self, parent, Qt.WindowType.Window)
self.tags: str = ""
self.tags_list: list[str] = []
self.mw = mw
@@ -23,11 +23,15 @@ class TagLimit(QDialog):
self.dialog.setupUi(self)
disable_help_button(self)
s = QShortcut(
- QKeySequence("ctrl+d"), self.dialog.activeList, context=Qt.WidgetShortcut
+ QKeySequence("ctrl+d"),
+ self.dialog.activeList,
+ context=Qt.ShortcutContext.WidgetShortcut,
)
qconnect(s.activated, self.dialog.activeList.clearSelection)
s = QShortcut(
- QKeySequence("ctrl+d"), self.dialog.inactiveList, context=Qt.WidgetShortcut
+ QKeySequence("ctrl+d"),
+ self.dialog.inactiveList,
+ context=Qt.ShortcutContext.WidgetShortcut,
)
qconnect(s.activated, self.dialog.inactiveList.clearSelection)
self.rebuildTagList()
@@ -54,19 +58,19 @@ class TagLimit(QDialog):
item = QListWidgetItem(t.replace("_", " "))
self.dialog.activeList.addItem(item)
if t in yesHash:
- mode = QItemSelectionModel.Select
+ mode = QItemSelectionModel.SelectionFlag.Select
self.dialog.activeCheck.setChecked(True)
else:
- mode = QItemSelectionModel.Deselect
+ mode = QItemSelectionModel.SelectionFlag.Deselect
idx = self.dialog.activeList.indexFromItem(item)
self.dialog.activeList.selectionModel().select(idx, mode)
# inactive
item = QListWidgetItem(t.replace("_", " "))
self.dialog.inactiveList.addItem(item)
if t in noHash:
- mode = QItemSelectionModel.Select
+ mode = QItemSelectionModel.SelectionFlag.Select
else:
- mode = QItemSelectionModel.Deselect
+ mode = QItemSelectionModel.SelectionFlag.Deselect
idx = self.dialog.inactiveList.indexFromItem(item)
self.dialog.inactiveList.selectionModel().select(idx, mode)
diff --git a/qt/aqt/taskman.py b/qt/aqt/taskman.py
index 3c62679ab..b763f96d4 100644
--- a/qt/aqt/taskman.py
+++ b/qt/aqt/taskman.py
@@ -14,10 +14,8 @@ from concurrent.futures.thread import ThreadPoolExecutor
from threading import Lock
from typing import Any, Callable
-from PyQt5.QtCore import QObject, pyqtSignal
-
import aqt
-from aqt.qt import QWidget, qconnect
+from aqt.qt import *
Closure = Callable[[], None]
diff --git a/qt/aqt/theme.py b/qt/aqt/theme.py
index 5148b8228..7fde423aa 100644
--- a/qt/aqt/theme.py
+++ b/qt/aqt/theme.py
@@ -9,7 +9,16 @@ from dataclasses import dataclass
from anki.utils import isMac
from aqt import QApplication, colors, gui_hooks, isWin
from aqt.platform import set_dark_mode
-from aqt.qt import QColor, QIcon, QPainter, QPalette, QPixmap, QStyleFactory, Qt
+from aqt.qt import (
+ QColor,
+ QGuiApplication,
+ QIcon,
+ QPainter,
+ QPalette,
+ QPixmap,
+ QStyleFactory,
+ Qt,
+)
@dataclass
@@ -92,7 +101,9 @@ class ThemeManager:
icon = QIcon(path.path)
pixmap = icon.pixmap(16)
painter = QPainter(pixmap)
- painter.setCompositionMode(QPainter.CompositionMode_SourceIn)
+ painter.setCompositionMode(
+ QPainter.CompositionMode.CompositionMode_SourceIn
+ )
painter.fillRect(pixmap.rect(), QColor(path.current_color(self.night_mode)))
painter.end()
icon = QIcon(pixmap)
@@ -133,7 +144,7 @@ class ThemeManager:
return QColor(self.color(colors))
def apply_style(self, app: QApplication) -> None:
- self.default_palette = app.style().standardPalette()
+ self.default_palette = QGuiApplication.palette()
self._apply_palette(app)
self._apply_style(app)
@@ -207,34 +218,45 @@ QTabWidget {{ background-color: {}; }}
palette = QPalette()
text_fg = self.qcolor(colors.TEXT_FG)
- palette.setColor(QPalette.WindowText, text_fg)
- palette.setColor(QPalette.ToolTipText, text_fg)
- palette.setColor(QPalette.Text, text_fg)
- palette.setColor(QPalette.ButtonText, text_fg)
+ palette.setColor(QPalette.ColorRole.WindowText, text_fg)
+ palette.setColor(QPalette.ColorRole.ToolTipText, text_fg)
+ palette.setColor(QPalette.ColorRole.Text, text_fg)
+ palette.setColor(QPalette.ColorRole.ButtonText, text_fg)
hlbg = self.qcolor(colors.HIGHLIGHT_BG)
hlbg.setAlpha(64)
- palette.setColor(QPalette.HighlightedText, self.qcolor(colors.HIGHLIGHT_FG))
- palette.setColor(QPalette.Highlight, hlbg)
+ palette.setColor(
+ QPalette.ColorRole.HighlightedText, self.qcolor(colors.HIGHLIGHT_FG)
+ )
+ palette.setColor(QPalette.ColorRole.Highlight, hlbg)
window_bg = self.qcolor(colors.WINDOW_BG)
- palette.setColor(QPalette.Window, window_bg)
- palette.setColor(QPalette.AlternateBase, window_bg)
+ palette.setColor(QPalette.ColorRole.Window, window_bg)
+ palette.setColor(QPalette.ColorRole.AlternateBase, window_bg)
- palette.setColor(QPalette.Button, QColor("#454545"))
+ palette.setColor(QPalette.ColorRole.Button, QColor("#454545"))
frame_bg = self.qcolor(colors.FRAME_BG)
- palette.setColor(QPalette.Base, frame_bg)
- palette.setColor(QPalette.ToolTipBase, frame_bg)
+ palette.setColor(QPalette.ColorRole.Base, frame_bg)
+ palette.setColor(QPalette.ColorRole.ToolTipBase, frame_bg)
disabled_color = self.qcolor(colors.DISABLED)
- palette.setColor(QPalette.Disabled, QPalette.Text, disabled_color)
- palette.setColor(QPalette.Disabled, QPalette.ButtonText, disabled_color)
- palette.setColor(QPalette.Disabled, QPalette.HighlightedText, disabled_color)
+ palette.setColor(QPalette.ColorRole.PlaceholderText, disabled_color)
+ palette.setColor(
+ QPalette.ColorGroup.Disabled, QPalette.ColorRole.Text, disabled_color
+ )
+ palette.setColor(
+ QPalette.ColorGroup.Disabled, QPalette.ColorRole.ButtonText, disabled_color
+ )
+ palette.setColor(
+ QPalette.ColorGroup.Disabled,
+ QPalette.ColorRole.HighlightedText,
+ disabled_color,
+ )
- palette.setColor(QPalette.Link, self.qcolor(colors.LINK))
+ palette.setColor(QPalette.ColorRole.Link, self.qcolor(colors.LINK))
- palette.setColor(QPalette.BrightText, Qt.red)
+ palette.setColor(QPalette.ColorRole.BrightText, Qt.GlobalColor.red)
app.setPalette(palette)
diff --git a/qt/aqt/update.py b/qt/aqt/update.py
index 912c0beae..4b5a00fbd 100644
--- a/qt/aqt/update.py
+++ b/qt/aqt/update.py
@@ -59,17 +59,17 @@ class LatestVersionFinder(QThread):
def askAndUpdate(mw: aqt.AnkiQt, ver: str) -> None:
baseStr = tr.qt_misc_anki_updatedanki_has_been_released(val=ver)
msg = QMessageBox(mw)
- msg.setStandardButtons(QMessageBox.Yes | QMessageBox.No) # type: ignore
- msg.setIcon(QMessageBox.Information)
+ msg.setStandardButtons(QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No) # type: ignore
+ msg.setIcon(QMessageBox.Icon.Information)
msg.setText(baseStr + tr.qt_misc_would_you_like_to_download_it())
button = QPushButton(tr.qt_misc_ignore_this_update())
- msg.addButton(button, QMessageBox.RejectRole)
- msg.setDefaultButton(QMessageBox.Yes)
+ msg.addButton(button, QMessageBox.ButtonRole.RejectRole)
+ msg.setDefaultButton(QMessageBox.StandardButton.Yes)
ret = msg.exec()
if msg.clickedButton() == button:
# ignore this update
mw.pm.meta["suppressUpdate"] = ver
- elif ret == QMessageBox.Yes:
+ elif ret == QMessageBox.StandardButton.Yes:
openLink(aqt.appWebsite)
diff --git a/qt/aqt/utils.py b/qt/aqt/utils.py
index 0fef71009..6deb98459 100644
--- a/qt/aqt/utils.py
+++ b/qt/aqt/utils.py
@@ -7,19 +7,7 @@ import re
import subprocess
import sys
from functools import wraps
-from typing import TYPE_CHECKING, Any, Literal, Sequence, cast
-
-from PyQt5.QtWidgets import (
- QAction,
- QDialog,
- QDialogButtonBox,
- QFileDialog,
- QHeaderView,
- QMenu,
- QPushButton,
- QSplitter,
- QWidget,
-)
+from typing import TYPE_CHECKING, Any, Literal, Sequence
import aqt
from anki.collection import Collection, HelpPage
@@ -108,16 +96,16 @@ def showInfo(
else:
parent_widget = parent
if type == "warning":
- icon = QMessageBox.Warning
+ icon = QMessageBox.Icon.Warning
elif type == "critical":
- icon = QMessageBox.Critical
+ icon = QMessageBox.Icon.Critical
else:
- icon = QMessageBox.Information
+ icon = QMessageBox.Icon.Information
mb = QMessageBox(parent_widget) #
if textFormat == "plain":
- mb.setTextFormat(Qt.PlainText)
+ mb.setTextFormat(Qt.TextFormat.PlainText)
elif textFormat == "rich":
- mb.setTextFormat(Qt.RichText)
+ mb.setTextFormat(Qt.TextFormat.RichText)
elif textFormat is not None:
raise Exception("unexpected textFormat type")
mb.setText(text)
@@ -131,10 +119,10 @@ def showInfo(
default = b
mb.setDefaultButton(default)
else:
- b = mb.addButton(QMessageBox.Ok)
+ b = mb.addButton(QMessageBox.StandardButton.Ok)
b.setDefault(True)
if help:
- b = mb.addButton(QMessageBox.Help)
+ b = mb.addButton(QMessageBox.StandardButton.Help)
qconnect(b.clicked, lambda: openHelp(help))
b.setAutoDefault(False)
return mb.exec()
@@ -164,7 +152,7 @@ def showText(
# used by the importer
text = QPlainTextEdit()
text.setReadOnly(True)
- text.setWordWrapMode(QTextOption.NoWrap)
+ text.setWordWrapMode(QTextOption.WrapMode.NoWrap)
text.setPlainText(txt)
else:
text = QTextBrowser()
@@ -174,7 +162,7 @@ def showText(
else:
text.setHtml(txt)
layout.addWidget(text)
- box = QDialogButtonBox(QDialogButtonBox.Close)
+ box = QDialogButtonBox(QDialogButtonBox.StandardButton.Close)
layout.addWidget(box)
if copyBtn:
@@ -183,7 +171,7 @@ def showText(
btn = QPushButton(tr.qt_misc_copy_to_clipboard())
qconnect(btn.clicked, onCopy)
- box.addButton(btn, QDialogButtonBox.ActionRole)
+ box.addButton(btn, QDialogButtonBox.ButtonRole.ActionRole)
def onReject() -> None:
if geomKey:
@@ -221,20 +209,20 @@ def askUser(
parent = aqt.mw.app.activeWindow()
if not msgfunc:
msgfunc = QMessageBox.question
- sb = QMessageBox.Yes | QMessageBox.No
+ sb = QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No
if help:
- sb |= QMessageBox.Help
+ sb |= QMessageBox.StandardButton.Help
while 1:
if defaultno:
- default = QMessageBox.No
+ default = QMessageBox.StandardButton.No
else:
- default = QMessageBox.Yes
- r = msgfunc(parent, title, text, cast(QMessageBox.StandardButtons, sb), default)
- if r == QMessageBox.Help:
+ default = QMessageBox.StandardButton.Yes
+ r = msgfunc(parent, title, text, sb, default)
+ if r == QMessageBox.StandardButton.Help:
openHelp(help)
else:
break
- return r == QMessageBox.Yes
+ return r == QMessageBox.StandardButton.Yes
class ButtonedDialog(QMessageBox):
@@ -250,12 +238,12 @@ class ButtonedDialog(QMessageBox):
self._buttons: list[QPushButton] = []
self.setWindowTitle(title)
self.help = help
- self.setIcon(QMessageBox.Warning)
+ self.setIcon(QMessageBox.Icon.Warning)
self.setText(text)
for b in buttons:
- self._buttons.append(self.addButton(b, QMessageBox.AcceptRole))
+ self._buttons.append(self.addButton(b, QMessageBox.ButtonRole.AcceptRole))
if help:
- self.addButton(tr.actions_help(), QMessageBox.HelpRole)
+ self.addButton(tr.actions_help(), QMessageBox.ButtonRole.HelpRole)
buttons.append(tr.actions_help())
def run(self) -> str:
@@ -312,16 +300,21 @@ class GetTextDialog(QDialog):
self.l.setText(default)
self.l.selectAll()
v.addWidget(self.l)
- buts = QDialogButtonBox.Ok | QDialogButtonBox.Cancel
+ buts = (
+ QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel
+ )
if help:
- buts |= QDialogButtonBox.Help
+ buts |= QDialogButtonBox.StandardButton.Help
b = QDialogButtonBox(buts) # type: ignore
v.addWidget(b)
self.setLayout(v)
- qconnect(b.button(QDialogButtonBox.Ok).clicked, self.accept)
- qconnect(b.button(QDialogButtonBox.Cancel).clicked, self.reject)
+ qconnect(b.button(QDialogButtonBox.StandardButton.Ok).clicked, self.accept)
+ qconnect(b.button(QDialogButtonBox.StandardButton.Cancel).clicked, self.reject)
if help:
- qconnect(b.button(QDialogButtonBox.Help).clicked, self.helpRequested)
+ qconnect(
+ b.button(QDialogButtonBox.StandardButton.Help).clicked,
+ self.helpRequested,
+ )
def accept(self) -> None:
return QDialog.accept(self)
@@ -349,7 +342,7 @@ def getText(
d = GetTextDialog(
parent, prompt, help=help, edit=edit, default=default, title=title, **kwargs
)
- d.setWindowModality(Qt.WindowModal)
+ d.setWindowModality(Qt.WindowModality.WindowModal)
if geomKey:
restoreGeom(d, geomKey)
ret = d.exec()
@@ -375,7 +368,7 @@ def chooseList(
parent = aqt.mw.app.activeWindow()
d = QDialog(parent)
disable_help_button(d)
- d.setWindowModality(Qt.WindowModal)
+ d.setWindowModality(Qt.WindowModality.WindowModal)
l = QVBoxLayout()
d.setLayout(l)
t = QLabel(prompt)
@@ -384,7 +377,7 @@ def chooseList(
c.addItems(choices)
c.setCurrentRow(startrow)
l.addWidget(c)
- bb = QDialogButtonBox(QDialogButtonBox.Ok)
+ bb = QDialogButtonBox(QDialogButtonBox.StandardButton.Ok)
qconnect(bb.accepted, d.accept)
l.addWidget(bb)
d.exec()
@@ -405,9 +398,9 @@ def getTag(
def disable_help_button(widget: QWidget) -> None:
"Disable the help button in the window titlebar."
- flags_int = int(widget.windowFlags()) & ~Qt.WindowContextHelpButtonHint
- flags = Qt.WindowFlags(flags_int) # type: ignore
- widget.setWindowFlags(flags)
+ widget.setWindowFlags(
+ widget.windowFlags() & ~Qt.WindowType.WindowContextHelpButtonHint
+ )
# File handling
@@ -432,7 +425,11 @@ def getFile(
else:
dirkey = None
d = QFileDialog(parent)
- mode = QFileDialog.ExistingFiles if multi else QFileDialog.ExistingFile
+ mode = (
+ QFileDialog.FileMode.ExistingFiles
+ if multi
+ else QFileDialog.FileMode.ExistingFile
+ )
d.setFileMode(mode)
if os.path.exists(dir):
d.setDirectory(dir)
@@ -471,7 +468,9 @@ def getSaveFile(
variable. The file dialog will default to open with FNAME."""
config_key = f"{dir_description}Directory"
- defaultPath = QStandardPaths.writableLocation(QStandardPaths.DocumentsLocation)
+ defaultPath = QStandardPaths.writableLocation(
+ QStandardPaths.StandardLocation.DocumentsLocation
+ )
base = aqt.mw.pm.profile.get(config_key, defaultPath)
path = os.path.join(base, fname)
file = QFileDialog.getSaveFileName(
@@ -479,7 +478,7 @@ def getSaveFile(
title,
path,
f"{key} (*{ext})",
- options=QFileDialog.DontConfirmOverwrite,
+ options=QFileDialog.Option.DontConfirmOverwrite,
)[0]
if file:
# add extension
@@ -497,7 +496,7 @@ def getSaveFile(
def saveGeom(widget: QWidget, key: str) -> None:
key += "Geom"
- if isMac and int(widget.windowState()) & Qt.WindowFullScreen:
+ if isMac and (widget.windowState() & Qt.WindowState.WindowFullScreen):
geom = None
else:
geom = widget.saveGeometry()
@@ -569,15 +568,19 @@ def restoreSplitter(widget: QSplitter, key: str) -> None:
widget.restoreState(aqt.mw.pm.profile[key])
+def _header_key(key: str) -> str:
+ # not compatible across major versions
+ qt_suffix = f"Qt{qtmajor}" if qtmajor > 5 else ""
+ return f"{key}Header{qt_suffix}"
+
+
def saveHeader(widget: QHeaderView, key: str) -> None:
- key += "Header"
- aqt.mw.pm.profile[key] = widget.saveState()
+ aqt.mw.pm.profile[_header_key(key)] = widget.saveState()
def restoreHeader(widget: QHeaderView, key: str) -> None:
- key += "Header"
- if aqt.mw.pm.profile.get(key):
- widget.restoreState(aqt.mw.pm.profile[key])
+ if state := aqt.mw.pm.profile.get(_header_key(key)):
+ widget.restoreState(state)
def save_is_checked(widget: QCheckBox, key: str) -> None:
@@ -658,7 +661,7 @@ def shortcut(key: str) -> str:
def maybeHideClose(bbox: QDialogButtonBox) -> None:
if isMac:
- b = bbox.button(QDialogButtonBox.Close)
+ b = bbox.button(QDialogButtonBox.StandardButton.Close)
if b:
bbox.removeButton(b)
@@ -718,13 +721,13 @@ def tooltip(
""",
aw,
)
- lab.setFrameStyle(QFrame.Panel)
+ lab.setFrameStyle(QFrame.Shape.Panel)
lab.setLineWidth(2)
- lab.setWindowFlags(Qt.ToolTip)
+ lab.setWindowFlags(Qt.WindowType.ToolTip)
if not theme_manager.night_mode:
p = QPalette()
- p.setColor(QPalette.Window, QColor("#feffc4"))
- p.setColor(QPalette.WindowText, QColor("#000000"))
+ p.setColor(QPalette.ColorRole.Window, QColor("#feffc4"))
+ p.setColor(QPalette.ColorRole.WindowText, QColor("#000000"))
lab.setPalette(p)
lab.move(aw.mapToGlobal(QPoint(0 + x_offset, aw.height() - y_offset)))
lab.show()
@@ -878,6 +881,8 @@ Add-ons, last update check: {}
# adapted from version detection in qutebrowser
def opengl_vendor() -> str | None:
+ if qtmajor != 5:
+ return "unknown"
old_context = QOpenGLContext.currentContext()
old_surface = None if old_context is None else old_context.surface()
@@ -898,11 +903,11 @@ def opengl_vendor() -> str | None:
# Can't use versionFunctions there
return None
- vp = QOpenGLVersionProfile()
+ vp = QOpenGLVersionProfile() # type: ignore # pylint: disable=undefined-variable
vp.setVersion(2, 0)
try:
- vf = ctx.versionFunctions(vp)
+ vf = ctx.versionFunctions(vp) # type: ignore
except ImportError as e:
return None
@@ -979,16 +984,16 @@ class KeyboardModifiersPressed:
def __init__(self) -> None:
from aqt import mw
- self._modifiers = int(mw.app.keyboardModifiers())
+ self._modifiers = mw.app.keyboardModifiers()
@property
def shift(self) -> bool:
- return bool(self._modifiers & Qt.ShiftModifier)
+ return bool(self._modifiers & Qt.KeyboardModifier.ShiftModifier)
@property
def control(self) -> bool:
- return bool(self._modifiers & Qt.ControlModifier)
+ return bool(self._modifiers & Qt.KeyboardModifier.ControlModifier)
@property
def alt(self) -> bool:
- return bool(self._modifiers & Qt.AltModifier)
+ return bool(self._modifiers & Qt.KeyboardModifier.AltModifier)
diff --git a/qt/aqt/webview.py b/qt/aqt/webview.py
index a05e4d1ba..72c0cb976 100644
--- a/qt/aqt/webview.py
+++ b/qt/aqt/webview.py
@@ -48,7 +48,7 @@ class AnkiWebPage(QWebEnginePage):
qwebchannel = ":/qtwebchannel/qwebchannel.js"
jsfile = QFile(qwebchannel)
- if not jsfile.open(QIODevice.ReadOnly):
+ if not jsfile.open(QIODevice.OpenModeFlag.ReadOnly):
print(f"Error opening '{qwebchannel}': {jsfile.error()}", file=sys.stderr)
jstext = bytes(cast(bytes, jsfile.readAll())).decode("utf-8")
jsfile.close()
@@ -74,8 +74,8 @@ class AnkiWebPage(QWebEnginePage):
});
"""
)
- script.setWorldId(QWebEngineScript.MainWorld)
- script.setInjectionPoint(QWebEngineScript.DocumentReady)
+ script.setWorldId(QWebEngineScript.ScriptWorldId.MainWorld)
+ script.setInjectionPoint(QWebEngineScript.InjectionPoint.DocumentReady)
script.setRunsOnSubFrames(False)
self.profile().scripts().insert(script)
@@ -92,11 +92,11 @@ class AnkiWebPage(QWebEnginePage):
srcID = ""
else:
srcID = serverbaseurl.sub("", srcID[:80], 1)
- if level == QWebEnginePage.InfoMessageLevel:
+ if level == QWebEnginePage.JavaScriptConsoleMessageLevel.InfoMessageLevel:
level_str = "info"
- elif level == QWebEnginePage.WarningMessageLevel:
+ elif level == QWebEnginePage.JavaScriptConsoleMessageLevel.WarningMessageLevel:
level_str = "warning"
- elif level == QWebEnginePage.ErrorMessageLevel:
+ elif level == QWebEnginePage.JavaScriptConsoleMessageLevel.ErrorMessageLevel:
level_str = "error"
else:
level_str = str(level)
@@ -135,7 +135,9 @@ class AnkiWebPage(QWebEnginePage):
# catch buggy links
from aqt import mw
- if url.matches(QUrl(mw.serverURL()), cast(Any, QUrl.RemoveFragment)):
+ if url.matches(
+ QUrl(mw.serverURL()), cast(Any, QUrl.UrlFormattingOption.RemoveFragment)
+ ):
print("onclick handler needs to return false")
return False
# load all other links in browser
@@ -240,14 +242,14 @@ class AnkiWebView(QWebEngineView):
self.requiresCol = True
self.setPage(self._page)
- self._page.profile().setHttpCacheType(QWebEngineProfile.NoCache)
+ self._page.profile().setHttpCacheType(QWebEngineProfile.HttpCacheType.NoCache)
self.resetHandlers()
self.allowDrops = False
self._filterSet = False
QShortcut( # type: ignore
QKeySequence("Esc"),
self,
- context=Qt.WidgetWithChildrenShortcut,
+ context=Qt.ShortcutContext.WidgetWithChildrenShortcut,
activated=self.onEsc,
)
@@ -258,8 +260,11 @@ class AnkiWebView(QWebEngineView):
# disable pinch to zoom gesture
if isinstance(evt, QNativeGestureEvent):
return True
- elif isinstance(evt, QMouseEvent) and evt.type() == QEvent.MouseButtonRelease:
- if evt.button() == Qt.MidButton and isLin:
+ elif (
+ isinstance(evt, QMouseEvent)
+ and evt.type() == QEvent.Type.MouseButtonRelease
+ ):
+ if evt.button() == Qt.MouseButton.MiddleButton and isLin:
self.onMiddleClickPaste()
return True
return False
@@ -286,19 +291,19 @@ class AnkiWebView(QWebEngineView):
w = w.parent()
def onCopy(self) -> None:
- self.triggerPageAction(QWebEnginePage.Copy)
+ self.triggerPageAction(QWebEnginePage.WebAction.Copy)
def onCut(self) -> None:
- self.triggerPageAction(QWebEnginePage.Cut)
+ self.triggerPageAction(QWebEnginePage.WebAction.Cut)
def onPaste(self) -> None:
- self.triggerPageAction(QWebEnginePage.Paste)
+ self.triggerPageAction(QWebEnginePage.WebAction.Paste)
def onMiddleClickPaste(self) -> None:
- self.triggerPageAction(QWebEnginePage.Paste)
+ self.triggerPageAction(QWebEnginePage.WebAction.Paste)
def onSelectAll(self) -> None:
- self.triggerPageAction(QWebEnginePage.SelectAll)
+ self.triggerPageAction(QWebEnginePage.WebAction.SelectAll)
def contextMenuEvent(self, evt: QContextMenuEvent) -> None:
m = QMenu(self)
@@ -350,9 +355,9 @@ class AnkiWebView(QWebEngineView):
if webscale:
return float(webscale)
- if isMac:
+ if qtmajor > 5 or isMac:
return 1
- screen = QApplication.desktop().screen()
+ screen = QApplication.desktop().screen() # type: ignore
if screen is None:
return 1
@@ -386,11 +391,11 @@ class AnkiWebView(QWebEngineView):
# standard palette does not return correct window color on macOS
return QColor("#ececec")
else:
- return theme_manager.default_palette.color(QPalette.Window)
+ return theme_manager.default_palette.color(QPalette.ColorRole.Window)
def standard_css(self) -> str:
palette = theme_manager.default_palette
- color_hl = palette.color(QPalette.Highlight).name()
+ color_hl = palette.color(QPalette.ColorRole.Highlight).name()
if isWin:
# T: include a font for your language on Windows, eg: "Segoe UI", "MS Mincho"
@@ -406,8 +411,8 @@ button { -webkit-appearance: none; background: #fff; border: 1px solid #ccc;
border-radius:5px; font-family: Helvetica }"""
else:
family = self.font().family()
- color_hl_txt = palette.color(QPalette.HighlightedText).name()
- color_btn = palette.color(QPalette.Button).name()
+ color_hl_txt = palette.color(QPalette.ColorRole.HighlightedText).name()
+ color_btn = palette.color(QPalette.ColorRole.Button).name()
font = f'font-size:14px;font-family:"{family}";'
button_style = """
/* Buttons */
diff --git a/qt/dmypy.py b/qt/dmypy.py
index 968061146..f8c420c35 100755
--- a/qt/dmypy.py
+++ b/qt/dmypy.py
@@ -36,7 +36,7 @@ if subprocess.run(
os.path.abspath("pip/stubs/extendsitepkgs"),
],
env={
- "MYPYPATH": "bazel-bin/qt/dmypy.runfiles/pyqt5",
+ "MYPYPATH": "bazel-bin/qt/dmypy.runfiles/pyqt6",
"EXTRA_SITE_PACKAGES": os.path.abspath(os.getenv("EXTRA_SITE_PACKAGES")),
},
cwd=workspace,
diff --git a/qt/mypy.ini b/qt/mypy.ini
index 10a49657d..cff9c7490 100644
--- a/qt/mypy.ini
+++ b/qt/mypy.ini
@@ -54,6 +54,7 @@ ignore_missing_imports = True
ignore_missing_imports = True
[mypy-PyQt5.*]
ignore_errors = True
+ignore_missing_imports = True
[mypy-win32com.client]
ignore_missing_imports = True
[mypy-darkdetect]
@@ -64,6 +65,11 @@ ignore_missing_imports = True
ignore_missing_imports = True
[mypy-aqt.forms.*]
-disallow_untyped_defs=false
+disallow_untyped_defs = false
+
[mypy-anki.*]
disallow_untyped_defs=false
+
+[mypy-PyQt6.*]
+ignore_errors = True
+ignore_missing_imports = True
diff --git a/qt/tests/run_pylint.py b/qt/tests/run_pylint.py
index 964c7656e..fa9b1ad6e 100644
--- a/qt/tests/run_pylint.py
+++ b/qt/tests/run_pylint.py
@@ -6,7 +6,10 @@ import os
import subprocess
import sys
-import PyQt5
+try:
+ import PyQt6
+except:
+ import PyQt5
from pylint.lint import Run
if __name__ == "__main__":
diff --git a/run-qt5 b/run-qt5
new file mode 100755
index 000000000..9fd6ef056
--- /dev/null
+++ b/run-qt5
@@ -0,0 +1,24 @@
+#!/bin/bash
+
+set -e
+
+run_linux() {
+ bazel run $BUILDARGS //qt:runanki_qt5 -- $*
+}
+
+run_mac() {
+ # QtWebEngineProcess is unable to locate icudtl.dat from a symlinked tree,
+ # so we need to copy the files into a working folder before running on a Mac.
+ workspace=$(dirname $0)
+ bazel build $BUILDARGS //qt:runanki_qt5 && \
+ rsync -aiL --exclude=anki/external --delete -f'-p __pycache__' \
+ $workspace/bazel-bin/qt/runanki* $workspace/bazel-copy/ && \
+ $workspace/bazel-copy/runanki_qt5 $*
+}
+
+export PYTHONWARNINGS=default
+if [[ "$OSTYPE" == "darwin"* ]]; then
+ run_mac $*
+else
+ run_linux $*
+fi
diff --git a/scripts/copyright_headers.py b/scripts/copyright_headers.py
index 3aed02dba..4173c542d 100644
--- a/scripts/copyright_headers.py
+++ b/scripts/copyright_headers.py
@@ -7,6 +7,7 @@ from pathlib import Path
nonstandard_header = {
"pip/pyqt5/install_pyqt5.py",
+ "pip/pyqt6/install_pyqt6.py",
"pylib/anki/importing/pauker.py",
"pylib/anki/importing/supermemo_xml.py",
"pylib/anki/statsbg.py",