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
+
@@ -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",)),