diff --git a/ftl/qt/qt-accel.ftl b/ftl/qt/qt-accel.ftl index e6fc8022f..d2a57645d 100644 --- a/ftl/qt/qt-accel.ftl +++ b/ftl/qt/qt-accel.ftl @@ -33,6 +33,10 @@ qt-accel-set-due-date = Set &Due Date... qt-accel-forget = &Forget qt-accel-view = &View qt-accel-full-screen = Toggle &Full Screen +qt-accel-layout = &Layout +qt-accel-layout-auto = &Auto +qt-accel-layout-vertical = &Vertical +qt-accel-layout-horizontal = &Horizontal qt-accel-zoom-in = Zoom &In qt-accel-zoom-out = Zoom &Out qt-accel-reset-zoom = &Reset Zoom diff --git a/ftl/qt/qt-misc.ftl b/ftl/qt/qt-misc.ftl index 17afa95d2..f79629831 100644 --- a/ftl/qt/qt-misc.ftl +++ b/ftl/qt/qt-misc.ftl @@ -69,6 +69,9 @@ qt-misc-second = [one] { $count } second *[other] { $count } seconds } +qt-misc-layout-auto-enabled = Responsive layout enabled +qt-misc-layout-vertical-enabled = Vertical layout enabled +qt-misc-layout-horizontal-enabled = Horizontal layout enabled ## deprecated- these strings will be removed in the future, and do not need ## to be translated diff --git a/qt/aqt/browser/browser.py b/qt/aqt/browser/browser.py index 1e73c5e87..44a50572e 100644 --- a/qt/aqt/browser/browser.py +++ b/qt/aqt/browser/browser.py @@ -4,6 +4,7 @@ from __future__ import annotations import json +import math import re from typing import Callable, Sequence @@ -63,12 +64,14 @@ from aqt.utils import ( saveState, showWarning, skip_if_selection_is_empty, + tooltip, tr, ) from ..changenotetype import change_notetype_dialog from .card_info import BrowserCardInfo from .find_and_replace import FindAndReplaceDialog +from .layout import BrowserLayout from .previewer import BrowserPreviewer as PreviewDialog from .previewer import Previewer from .sidebar import SidebarTreeView @@ -136,6 +139,9 @@ class Browser(QMainWindow): self.setupMenus() self.setupHooks() self.setupEditor() + # responsive layout + self.aspect_ratio = self.width() / self.height() + self.set_layout(self.mw.pm.browser_layout(), True) # disable undo/redo self.on_undo_state_change(mw.undo_actions_info()) # legacy alias @@ -179,6 +185,52 @@ class Browser(QMainWindow): self.table.redraw_cells() self.sidebar.refresh_if_needed() + def set_layout(self, mode: BrowserLayout, init: bool = False) -> None: + self.mw.pm.set_browser_layout(mode) + + if mode == BrowserLayout.AUTO: + self.auto_layout = True + self.maybe_update_layout(self.aspect_ratio, True) + self.form.actionLayoutAuto.setChecked(True) + self.form.actionLayoutVertical.setChecked(False) + self.form.actionLayoutHorizontal.setChecked(False) + if not init: + tooltip(tr.qt_misc_layout_auto_enabled()) + else: + self.auto_layout = False + self.form.actionLayoutAuto.setChecked(False) + + if mode == BrowserLayout.VERTICAL: + self.form.splitter.setOrientation(Qt.Orientation.Vertical) + self.form.actionLayoutVertical.setChecked(True) + self.form.actionLayoutHorizontal.setChecked(False) + if not init: + tooltip(tr.qt_misc_layout_vertical_enabled()) + + elif mode == BrowserLayout.HORIZONTAL: + self.form.splitter.setOrientation(Qt.Orientation.Horizontal) + self.form.actionLayoutHorizontal.setChecked(True) + self.form.actionLayoutVertical.setChecked(False) + if not init: + tooltip(tr.qt_misc_layout_horizontal_enabled()) + + def maybe_update_layout(self, aspect_ratio: float, force: bool = False) -> None: + if force or math.floor(aspect_ratio) != math.floor(self.aspect_ratio): + if aspect_ratio < 1: + self.form.splitter.setOrientation(Qt.Orientation.Vertical) + else: + self.form.splitter.setOrientation(Qt.Orientation.Horizontal) + + def resizeEvent(self, event: QResizeEvent) -> None: + aspect_ratio = self.width() / self.height() + + if self.auto_layout: + self.maybe_update_layout(aspect_ratio) + + self.aspect_ratio = aspect_ratio + + QMainWindow.resizeEvent(self, event) + def setupMenus(self) -> None: # actions f = self.form @@ -207,6 +259,18 @@ class Browser(QMainWindow): f.actionResetZoom.triggered, lambda: self.editor.web.setZoomFactor(1), ) + qconnect( + self.form.actionLayoutAuto.triggered, + lambda: self.set_layout(BrowserLayout.AUTO), + ) + qconnect( + self.form.actionLayoutVertical.triggered, + lambda: self.set_layout(BrowserLayout.VERTICAL), + ) + qconnect( + self.form.actionLayoutHorizontal.triggered, + lambda: self.set_layout(BrowserLayout.HORIZONTAL), + ) # notes qconnect(f.actionAdd.triggered, self.mw.onAddCard) diff --git a/qt/aqt/browser/layout.py b/qt/aqt/browser/layout.py new file mode 100644 index 000000000..d8f9fd7d5 --- /dev/null +++ b/qt/aqt/browser/layout.py @@ -0,0 +1,10 @@ +# Copyright: Ankitects Pty Ltd and contributors +# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html + +from enum import Enum + + +class BrowserLayout(Enum): + AUTO = "auto" + VERTICAL = "vertical" + HORIZONTAL = "horizontal" diff --git a/qt/aqt/forms/browser.ui b/qt/aqt/forms/browser.ui index a43a9257d..48be791fe 100644 --- a/qt/aqt/forms/browser.ui +++ b/qt/aqt/forms/browser.ui @@ -46,7 +46,7 @@ - Qt::Vertical + Qt::Horizontal @@ -302,6 +302,15 @@ qt_accel_view + + + qt_accel_layout + + + + + + @@ -309,6 +318,8 @@ + + @@ -699,6 +710,30 @@ Ctrl+0 + + + true + + + qt_accel_layout_auto + + + + + true + + + qt_accel_layout_vertical + + + + + true + + + qt_accel_layout_horizontal + + browsing_toggle_showing_cards_notes diff --git a/qt/aqt/profiles.py b/qt/aqt/profiles.py index b3f8fe2d6..fbf6b1228 100644 --- a/qt/aqt/profiles.py +++ b/qt/aqt/profiles.py @@ -10,7 +10,7 @@ import shutil import traceback from enum import Enum from pathlib import Path -from typing import Any +from typing import TYPE_CHECKING, Any import anki.lang import aqt.forms @@ -25,6 +25,10 @@ from aqt.qt import * from aqt.theme import Theme from aqt.utils import disable_help_button, send_to_trash, showWarning, tr +if TYPE_CHECKING: + from aqt.browser.layout import BrowserLayout + + # Profile handling ########################################################################## # - Saves in pickles rather than json to easily store Qt window state. @@ -542,6 +546,14 @@ create table if not exists profiles def set_theme(self, theme: Theme) -> None: self.meta["theme"] = theme.value + def browser_layout(self) -> BrowserLayout: + from aqt.browser.layout import BrowserLayout + + return BrowserLayout(self.meta.get("browser_layout", "auto")) + + def set_browser_layout(self, layout: BrowserLayout) -> None: + self.meta["browser_layout"] = layout.value + def legacy_import_export(self) -> bool: return self.meta.get("legacy_import", False) diff --git a/qt/aqt/qt/qt5_compat.py b/qt/aqt/qt/qt5_compat.py index e549994b8..ef281b87c 100644 --- a/qt/aqt/qt/qt5_compat.py +++ b/qt/aqt/qt/qt5_compat.py @@ -270,7 +270,10 @@ _enum_map = ( ("QHeaderView", ("ResizeMode",)), ("QLayout", ("SizeConstraint",)), ("QLineEdit", ("EchoMode",)), - ("QListView", ("Flow", "LayoutMode", "ResizeMode", "Movement", "ViewMode")), + ( + "QListView", + ("Flow", "BrowserLayout", "ResizeMode", "Movement", "ViewMode"), + ), ("QListWidgetItem", ("ItemType",)), ("QMessageBox", ("StandardButton", "Icon", "ButtonRole")), ("QPlainTextEdit", ("LineWrapMode",)),