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",