mirror of
https://github.com/TagStudioDev/TagStudio.git
synced 2026-09-15 06:59:34 +02:00
refactor(ui): MVC split pagination widget
This commit is contained in:
@@ -41,9 +41,9 @@ from tagstudio.i18n.platform_strings import trash_term
|
||||
from tagstudio.i18n.translations import Translations
|
||||
from tagstudio.qt.controllers.banner import Banner
|
||||
from tagstudio.qt.controllers.inspector import Inspector
|
||||
from tagstudio.qt.controllers.pagination import Pagination
|
||||
from tagstudio.qt.helpers.mnemonics import assign_mnemonics
|
||||
from tagstudio.qt.mixed.landing import LandingWidget
|
||||
from tagstudio.qt.mixed.pagination import Pagination
|
||||
from tagstudio.qt.resource_manager import ResourceManager
|
||||
from tagstudio.qt.views.layouts.thumb_grid_layout import ThumbGridLayout
|
||||
from tagstudio.qt.views.styles.color_overlay import auto_theme_overlay
|
||||
|
||||
@@ -0,0 +1,210 @@
|
||||
# SPDX-FileCopyrightText: (c) TagStudio Contributors
|
||||
# SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
|
||||
from typing import override
|
||||
from warnings import catch_warnings
|
||||
|
||||
from PySide6.QtCore import QEvent, QObject, Qt, Signal
|
||||
from PySide6.QtWidgets import QHBoxLayout, QPushButton, QWidget
|
||||
|
||||
from tagstudio.qt.views.pagination_view import PaginationView
|
||||
from tagstudio.qt.views.styles.stylesheets import pagination_style
|
||||
|
||||
|
||||
class Pagination(QWidget):
|
||||
HEIGHT = 36
|
||||
_RIGHT_MARGIN = 6
|
||||
_SCROLLBAR_WIDTH = 14
|
||||
|
||||
index = Signal(int)
|
||||
|
||||
def __init__(self, parent: QWidget | None = None) -> None:
|
||||
super().__init__(parent)
|
||||
self.page_count: int = 0
|
||||
self.current_page_index: int = 0
|
||||
|
||||
self.setHidden(True)
|
||||
self.setObjectName("pagination")
|
||||
self.setAttribute(Qt.WidgetAttribute.WA_StyledBackground)
|
||||
self.setFixedHeight(self.HEIGHT)
|
||||
self.setStyleSheet(pagination_style())
|
||||
|
||||
self.view = PaginationView()
|
||||
self.setLayout(self.view)
|
||||
self._connect_callbacks()
|
||||
|
||||
if parent is not None:
|
||||
parent.installEventFilter(self)
|
||||
self._sync_geometry()
|
||||
|
||||
def _connect_callbacks(self) -> None:
|
||||
self.view.current_page_field.returnPressed.connect(
|
||||
lambda: self._goto_page(int(self.view.current_page_field.text()) - 1)
|
||||
)
|
||||
|
||||
def update_buttons(self, page_count: int, index: int, emit: bool = True):
|
||||
if index < 0:
|
||||
raise ValueError("Negative index detected")
|
||||
|
||||
view = self.view
|
||||
for i in range(0, 10):
|
||||
if button := self._get_button_at(view.start_buffer_layout, i):
|
||||
button.setHidden(True)
|
||||
if button := self._get_button_at(view.end_buffer_layout, i):
|
||||
button.setHidden(True)
|
||||
|
||||
end_page = page_count - 1
|
||||
# Hide everything if there are only one or less pages.
|
||||
if page_count <= 1:
|
||||
self.setHidden(True)
|
||||
|
||||
# Enable/Disable Next + Prev Buttons
|
||||
elif page_count > 1:
|
||||
if index == 0:
|
||||
view.prev_button.setDisabled(True)
|
||||
else:
|
||||
self._assign_click(view.prev_button, index - 1)
|
||||
view.prev_button.setDisabled(False)
|
||||
|
||||
if index == end_page:
|
||||
view.next_button.setDisabled(True)
|
||||
else:
|
||||
self._assign_click(view.next_button, index + 1)
|
||||
view.next_button.setDisabled(False)
|
||||
|
||||
# Set Ellipses Sizes
|
||||
if 8 <= page_count <= 11:
|
||||
end_scale = max(1, page_count - index - 6)
|
||||
start_scale = max(1, index - 5)
|
||||
elif page_count > 11:
|
||||
end_scale = max(1, 7 - index)
|
||||
start_scale = max(1, (7 - (end_page - index)))
|
||||
else:
|
||||
end_scale, start_scale = 1, 1
|
||||
|
||||
if page_count >= 8:
|
||||
end_size = view.BUTTON_SIZE.width() * end_scale + (3 * (end_scale - 1))
|
||||
start_size = view.BUTTON_SIZE.width() * start_scale + (3 * (start_scale - 1))
|
||||
view.end_ellipses.setMinimumWidth(end_size)
|
||||
view.end_ellipses.setMaximumWidth(end_size)
|
||||
view.start_ellipses.setMinimumWidth(start_size)
|
||||
view.start_ellipses.setMaximumWidth(start_size)
|
||||
|
||||
# Enable/Disable Ellipses
|
||||
if index <= view.BUFFER_PAGE_COUNT + 1:
|
||||
view.start_ellipses.setHidden(True)
|
||||
else:
|
||||
view.start_ellipses.setHidden(False)
|
||||
self._assign_click(view.start_button, 0)
|
||||
if index >= (page_count - view.BUFFER_PAGE_COUNT - 2):
|
||||
view.end_ellipses.setHidden(True)
|
||||
else:
|
||||
view.end_ellipses.setHidden(False)
|
||||
|
||||
# Hide/Unhide Start + End Buttons
|
||||
if index != 0:
|
||||
view.start_button.setText("1")
|
||||
self._assign_click(view.start_button, 0)
|
||||
view.start_button.setHidden(False)
|
||||
else:
|
||||
view.start_button.setHidden(True)
|
||||
if index != page_count - 1:
|
||||
view.end_button.setText(str(page_count))
|
||||
self._assign_click(view.end_button, page_count - 1)
|
||||
view.end_button.setHidden(False)
|
||||
else:
|
||||
view.end_button.setHidden(True)
|
||||
|
||||
if index == 0 or index == 1:
|
||||
view.start_buffer_container.setHidden(True)
|
||||
else:
|
||||
view.start_buffer_container.setHidden(False)
|
||||
|
||||
if index == page_count - 1 or index == page_count - 2:
|
||||
view.end_buffer_container.setHidden(True)
|
||||
else:
|
||||
view.end_buffer_container.setHidden(False)
|
||||
|
||||
# Current Field and Buffer Pages
|
||||
for i in range(0, page_count):
|
||||
# Set Field
|
||||
if i == index:
|
||||
if button := self._get_button_at(view.start_buffer_layout, i):
|
||||
button.setHidden(True)
|
||||
if button := self._get_button_at(view.end_buffer_layout, i):
|
||||
button.setHidden(True)
|
||||
view.current_page_field.setText(str(i + 1))
|
||||
|
||||
start_offset = max(0, (index - 4) - 4)
|
||||
end_offset = min(page_count - 1, (index + 4) - 4)
|
||||
if i < index:
|
||||
if (i != 0) and i >= index - 4:
|
||||
if button := self._get_button_at(
|
||||
view.start_buffer_layout, i - start_offset
|
||||
):
|
||||
button.setHidden(False)
|
||||
button.setText(str(i + 1))
|
||||
self._assign_click(button, i)
|
||||
else:
|
||||
if button := self._get_button_at(view.start_buffer_layout, i):
|
||||
button.setHidden(True)
|
||||
if button := self._get_button_at(view.end_buffer_layout, i):
|
||||
button.setHidden(True)
|
||||
elif i > index:
|
||||
if i != page_count - 1 and i <= index + 4:
|
||||
if button := self._get_button_at(view.end_buffer_layout, i - end_offset):
|
||||
button.setHidden(False)
|
||||
button.setText(str(i + 1))
|
||||
self._assign_click(button, i)
|
||||
else:
|
||||
if button := self._get_button_at(view.end_buffer_layout, i):
|
||||
button.setHidden(True)
|
||||
for j in range(0, view.BUFFER_PAGE_COUNT):
|
||||
if button := self._get_button_at(
|
||||
view.end_buffer_layout, i - end_offset + j
|
||||
):
|
||||
button.setHidden(True)
|
||||
|
||||
if button := self._get_button_at(view.start_buffer_layout, i - 1):
|
||||
button.setHidden(True)
|
||||
|
||||
self.setHidden(False)
|
||||
self._sync_geometry()
|
||||
self.raise_()
|
||||
|
||||
view.validator.setTop(page_count)
|
||||
if emit:
|
||||
self.index.emit(index)
|
||||
self.current_page_index = index
|
||||
self.page_count = page_count
|
||||
|
||||
def _goto_page(self, index: int):
|
||||
self.update_buttons(self.page_count, index)
|
||||
|
||||
def _assign_click(self, button: QPushButton, index: int):
|
||||
with catch_warnings(record=True):
|
||||
button.clicked.disconnect()
|
||||
button.clicked.connect(lambda checked=False, i=index: self._goto_page(i))
|
||||
|
||||
@staticmethod
|
||||
def _get_button_at(layout: QHBoxLayout, index: int) -> QPushButton | None:
|
||||
"""Safely return a button in a layout at a given index, if it exists."""
|
||||
item = layout.itemAt(index)
|
||||
widget = item.widget() if item else None
|
||||
return widget if isinstance(widget, QPushButton) else None
|
||||
|
||||
@override
|
||||
def eventFilter(self, watched: QObject, event: QEvent) -> bool:
|
||||
if watched is self.parentWidget() and event.type() == QEvent.Type.Resize:
|
||||
self._sync_geometry()
|
||||
return super().eventFilter(watched, event)
|
||||
|
||||
def _sync_geometry(self):
|
||||
parent = self.parentWidget()
|
||||
if parent is None:
|
||||
return
|
||||
# Stays out of the way of the entry view scrollbar
|
||||
right_inset = max(self._RIGHT_MARGIN, self._SCROLLBAR_WIDTH)
|
||||
width = max(0, parent.width() - right_inset)
|
||||
self.setGeometry(0, parent.height() - self.HEIGHT, width, self.HEIGHT)
|
||||
@@ -1,329 +0,0 @@
|
||||
# SPDX-FileCopyrightText: (c) TagStudio Contributors
|
||||
# SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
# pyright: reportOptionalMemberAccess=false
|
||||
|
||||
from typing import cast, override
|
||||
from warnings import catch_warnings
|
||||
|
||||
from PIL import Image, ImageQt
|
||||
from PySide6.QtCore import QEvent, QObject, QSize, Qt, Signal
|
||||
from PySide6.QtGui import QIntValidator, QPixmap
|
||||
from PySide6.QtWidgets import QHBoxLayout, QLabel, QLineEdit, QPushButton, QWidget
|
||||
|
||||
from tagstudio.qt.resource_manager import ResourceManager
|
||||
from tagstudio.qt.views.styles.color_overlay import auto_theme_overlay
|
||||
from tagstudio.qt.views.styles.stylesheets import pagination_style
|
||||
|
||||
|
||||
# TODO: Split to use MVC guidelines.
|
||||
class Pagination(QWidget):
|
||||
"""Widget containing controls for navigating between pages of items."""
|
||||
|
||||
HEIGHT = 36 # Button row height (24) plus 6px above and below.
|
||||
RIGHT_MARGIN = 6
|
||||
SCROLLBAR_WIDTH = 14
|
||||
|
||||
index = Signal(int)
|
||||
|
||||
def __init__(self, parent: QWidget | None = None) -> None:
|
||||
super().__init__(parent=parent)
|
||||
self.rm = ResourceManager()
|
||||
self.page_count: int = 0
|
||||
self.current_page_index: int = 0
|
||||
self.buffer_page_count: int = 4
|
||||
self.button_size = QSize(32, 24)
|
||||
|
||||
# ------------ UI EXAMPLE --------------
|
||||
# [<] [1]...[3][4] [5] [6][7]...[42] [>]
|
||||
# ^^^^ <-- 2 Buffer Pages
|
||||
# Center Page Number is Editable Text
|
||||
# --------------------------------------
|
||||
|
||||
# [----------- ROOT LAYOUT ------------]
|
||||
self.setHidden(True)
|
||||
self.setObjectName("pagination")
|
||||
self.setAttribute(Qt.WidgetAttribute.WA_StyledBackground)
|
||||
self.setFixedHeight(self.HEIGHT)
|
||||
self.setStyleSheet(pagination_style())
|
||||
self.root_layout = QHBoxLayout(self)
|
||||
self.root_layout.setContentsMargins(0, 6, 0, 6)
|
||||
self.root_layout.setSpacing(3)
|
||||
|
||||
if parent is not None:
|
||||
parent.installEventFilter(self)
|
||||
self._sync_geometry()
|
||||
|
||||
# [<] ----------------------------------
|
||||
self.prev_button = QPushButton()
|
||||
prev_icon: Image.Image = self.rm.bxs_left_arrow
|
||||
prev_icon = auto_theme_overlay(prev_icon, use_alpha=False)
|
||||
self.prev_button.setIcon(QPixmap.fromImage(ImageQt.ImageQt(prev_icon)))
|
||||
self.prev_button.setIconSize(QSize(12, 12))
|
||||
self.prev_button.setMinimumSize(self.button_size)
|
||||
self.prev_button.setMaximumSize(self.button_size)
|
||||
|
||||
# --- [1] ------------------------------
|
||||
self.start_button = QPushButton()
|
||||
self.start_button.setMinimumSize(self.button_size)
|
||||
self.start_button.setMaximumSize(self.button_size)
|
||||
|
||||
# ------ ... ---------------------------
|
||||
self.start_ellipses = QLabel()
|
||||
self.start_ellipses.setMinimumSize(self.button_size)
|
||||
self.start_ellipses.setMaximumSize(self.button_size)
|
||||
# self.start_ellipses.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
self.start_ellipses.setText(". . .")
|
||||
|
||||
# --------- [3][4] ---------------------
|
||||
self.start_buffer_container = QWidget()
|
||||
self.start_buffer_layout = QHBoxLayout(self.start_buffer_container)
|
||||
self.start_buffer_layout.setContentsMargins(0, 0, 0, 0)
|
||||
self.start_buffer_layout.setSpacing(3)
|
||||
|
||||
# ---------------- [5] -----------------
|
||||
self.current_page_field = QLineEdit()
|
||||
self.current_page_field.setMinimumSize(self.button_size)
|
||||
self.current_page_field.setMaximumSize(self.button_size)
|
||||
self.validator = Validator(1, self.page_count)
|
||||
self.current_page_field.setValidator(self.validator)
|
||||
self.current_page_field.returnPressed.connect(
|
||||
lambda: self._goto_page(int(self.current_page_field.text()) - 1)
|
||||
)
|
||||
|
||||
# -------------------- [6][7] ----------
|
||||
self.end_buffer_container = QWidget()
|
||||
self.end_buffer_layout = QHBoxLayout(self.end_buffer_container)
|
||||
self.end_buffer_layout.setContentsMargins(0, 0, 0, 0)
|
||||
self.end_buffer_layout.setSpacing(3)
|
||||
|
||||
# -------------------------- ... -------
|
||||
self.end_ellipses = QLabel()
|
||||
self.end_ellipses.setMinimumSize(self.button_size)
|
||||
self.end_ellipses.setMaximumSize(self.button_size)
|
||||
# self.end_ellipses.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
self.end_ellipses.setText(". . .")
|
||||
|
||||
# ----------------------------- [42] ---
|
||||
self.end_button = QPushButton()
|
||||
self.end_button.setMinimumSize(self.button_size)
|
||||
self.end_button.setMaximumSize(self.button_size)
|
||||
|
||||
# ---------------------------------- [>]
|
||||
self.next_button = QPushButton()
|
||||
next_icon: Image.Image = self.rm.bxs_right_arrow
|
||||
next_icon = auto_theme_overlay(next_icon, use_alpha=False)
|
||||
self.next_button.setIcon(QPixmap.fromImage(ImageQt.ImageQt(next_icon)))
|
||||
self.next_button.setIconSize(QSize(12, 12))
|
||||
self.next_button.setMinimumSize(self.button_size)
|
||||
self.next_button.setMaximumSize(self.button_size)
|
||||
|
||||
# Add Widgets to Root Layout
|
||||
self.root_layout.addStretch(1)
|
||||
self.root_layout.addWidget(self.prev_button)
|
||||
self.root_layout.addWidget(self.start_button)
|
||||
self.root_layout.addWidget(self.start_ellipses)
|
||||
self.root_layout.addWidget(self.start_buffer_container)
|
||||
self.root_layout.addWidget(self.current_page_field)
|
||||
self.root_layout.addWidget(self.end_buffer_container)
|
||||
self.root_layout.addWidget(self.end_ellipses)
|
||||
self.root_layout.addWidget(self.end_button)
|
||||
self.root_layout.addWidget(self.next_button)
|
||||
self.root_layout.addStretch(1)
|
||||
|
||||
self._populate_buffer_buttons()
|
||||
|
||||
def update_buttons(self, page_count: int, index: int, emit: bool = True):
|
||||
# Guard
|
||||
if index < 0:
|
||||
raise ValueError("Negative index detected")
|
||||
|
||||
for i in range(0, 10):
|
||||
if self.start_buffer_layout.itemAt(i):
|
||||
self.start_buffer_layout.itemAt(i).widget().setHidden(True)
|
||||
if self.end_buffer_layout.itemAt(i):
|
||||
self.end_buffer_layout.itemAt(i).widget().setHidden(True)
|
||||
|
||||
end_page = page_count - 1
|
||||
if page_count <= 1:
|
||||
# Hide everything if there are only one or less pages.
|
||||
# [-------------- HIDDEN --------------]
|
||||
self.setHidden(True)
|
||||
|
||||
elif page_count > 1:
|
||||
# Enable/Disable Next+Prev Buttons
|
||||
if index == 0:
|
||||
self.prev_button.setDisabled(True)
|
||||
else:
|
||||
self._assign_click(self.prev_button, index - 1)
|
||||
self.prev_button.setDisabled(False)
|
||||
|
||||
if index == end_page:
|
||||
self.next_button.setDisabled(True)
|
||||
else:
|
||||
self._assign_click(self.next_button, index + 1)
|
||||
self.next_button.setDisabled(False)
|
||||
|
||||
# Set Ellipses Sizes
|
||||
if 8 <= page_count <= 11:
|
||||
end_scale = max(1, page_count - index - 6)
|
||||
srt_scale = max(1, index - 5)
|
||||
elif page_count > 11:
|
||||
end_scale = max(1, 7 - index)
|
||||
srt_scale = max(1, (7 - (end_page - index)))
|
||||
|
||||
if page_count >= 8:
|
||||
end_size = self.button_size.width() * end_scale + (3 * (end_scale - 1)) # pyright: ignore[reportPossiblyUnboundVariable]
|
||||
srt_size = self.button_size.width() * srt_scale + (3 * (srt_scale - 1)) # pyright: ignore[reportPossiblyUnboundVariable]
|
||||
self.end_ellipses.setMinimumWidth(end_size)
|
||||
self.end_ellipses.setMaximumWidth(end_size)
|
||||
self.start_ellipses.setMinimumWidth(srt_size)
|
||||
self.start_ellipses.setMaximumWidth(srt_size)
|
||||
|
||||
# Enable/Disable Ellipses
|
||||
if index <= self.buffer_page_count + 1:
|
||||
self.start_ellipses.setHidden(True)
|
||||
else:
|
||||
self.start_ellipses.setHidden(False)
|
||||
self._assign_click(self.start_button, 0)
|
||||
if index >= (page_count - self.buffer_page_count - 2):
|
||||
self.end_ellipses.setHidden(True)
|
||||
else:
|
||||
self.end_ellipses.setHidden(False)
|
||||
|
||||
# Hide/Unhide Start+End Buttons
|
||||
if index != 0:
|
||||
self.start_button.setText("1")
|
||||
self._assign_click(self.start_button, 0)
|
||||
self.start_button.setHidden(False)
|
||||
else:
|
||||
self.start_button.setHidden(True)
|
||||
if index != page_count - 1:
|
||||
self.end_button.setText(str(page_count))
|
||||
self._assign_click(self.end_button, page_count - 1)
|
||||
self.end_button.setHidden(False)
|
||||
else:
|
||||
self.end_button.setHidden(True)
|
||||
|
||||
if index == 0 or index == 1:
|
||||
self.start_buffer_container.setHidden(True)
|
||||
else:
|
||||
self.start_buffer_container.setHidden(False)
|
||||
|
||||
if index == page_count - 1 or index == page_count - 2:
|
||||
self.end_buffer_container.setHidden(True)
|
||||
else:
|
||||
self.end_buffer_container.setHidden(False)
|
||||
|
||||
# Current Field and Buffer Pages
|
||||
sbc = 0
|
||||
for i in range(0, page_count):
|
||||
# Set Field
|
||||
if i == index:
|
||||
if self.start_buffer_layout.itemAt(i):
|
||||
self.start_buffer_layout.itemAt(i).widget().setHidden(True)
|
||||
if self.end_buffer_layout.itemAt(i):
|
||||
self.end_buffer_layout.itemAt(i).widget().setHidden(True)
|
||||
sbc += 1
|
||||
self.current_page_field.setText(str(i + 1))
|
||||
|
||||
start_offset = max(0, (index - 4) - 4)
|
||||
end_offset = min(page_count - 1, (index + 4) - 4)
|
||||
if i < index:
|
||||
if (i != 0) and i >= index - 4:
|
||||
self.start_buffer_layout.itemAt(i - start_offset).widget().setHidden(False)
|
||||
self.start_buffer_layout.itemAt(i - start_offset).widget().setText( # pyright: ignore[reportAttributeAccessIssue]
|
||||
str(i + 1)
|
||||
)
|
||||
self._assign_click(
|
||||
cast(
|
||||
QPushButton,
|
||||
self.start_buffer_layout.itemAt(i - start_offset).widget(),
|
||||
),
|
||||
i,
|
||||
)
|
||||
sbc += 1
|
||||
else:
|
||||
if self.start_buffer_layout.itemAt(i):
|
||||
self.start_buffer_layout.itemAt(i).widget().setHidden(True)
|
||||
if self.end_buffer_layout.itemAt(i):
|
||||
self.end_buffer_layout.itemAt(i).widget().setHidden(True)
|
||||
elif i > index:
|
||||
if i != page_count - 1 and i <= index + 4:
|
||||
self.end_buffer_layout.itemAt(i - end_offset).widget().setHidden(False)
|
||||
self.end_buffer_layout.itemAt(i - end_offset).widget().setText(str(i + 1)) # pyright: ignore[reportAttributeAccessIssue]
|
||||
self._assign_click(
|
||||
cast(
|
||||
QPushButton,
|
||||
self.end_buffer_layout.itemAt(i - end_offset).widget(),
|
||||
),
|
||||
i,
|
||||
)
|
||||
else:
|
||||
if self.end_buffer_layout.itemAt(i):
|
||||
self.end_buffer_layout.itemAt(i).widget().setHidden(True)
|
||||
for j in range(0, self.buffer_page_count):
|
||||
if self.end_buffer_layout.itemAt(i - end_offset + j):
|
||||
self.end_buffer_layout.itemAt(
|
||||
i - end_offset + j
|
||||
).widget().setHidden(True)
|
||||
|
||||
if self.start_buffer_layout.itemAt(i - 1):
|
||||
self.start_buffer_layout.itemAt(i - 1).widget().setHidden(True)
|
||||
|
||||
self.setHidden(False)
|
||||
self._sync_geometry()
|
||||
self.raise_()
|
||||
|
||||
self.validator.setTop(page_count)
|
||||
if emit:
|
||||
self.index.emit(index)
|
||||
self.current_page_index = index
|
||||
self.page_count = page_count
|
||||
|
||||
def _goto_page(self, index: int):
|
||||
self.update_buttons(self.page_count, index)
|
||||
|
||||
def _assign_click(self, button: QPushButton, index: int):
|
||||
with catch_warnings(record=True):
|
||||
button.clicked.disconnect()
|
||||
button.clicked.connect(lambda checked=False, i=index: self._goto_page(i))
|
||||
|
||||
def _populate_buffer_buttons(self):
|
||||
for _ in range(max(self.buffer_page_count * 2, 5)):
|
||||
button = QPushButton()
|
||||
button.setMinimumSize(self.button_size)
|
||||
button.setMaximumSize(self.button_size)
|
||||
button.setHidden(True)
|
||||
self.start_buffer_layout.addWidget(button)
|
||||
|
||||
end_button = QPushButton()
|
||||
end_button.setMinimumSize(self.button_size)
|
||||
end_button.setMaximumSize(self.button_size)
|
||||
end_button.setHidden(True)
|
||||
self.end_buffer_layout.addWidget(end_button)
|
||||
|
||||
@override
|
||||
def eventFilter(self, watched: QObject, event: QEvent) -> bool:
|
||||
if watched is self.parentWidget() and event.type() == QEvent.Type.Resize:
|
||||
self._sync_geometry()
|
||||
return super().eventFilter(watched, event)
|
||||
|
||||
def _sync_geometry(self):
|
||||
parent = self.parentWidget()
|
||||
if parent is None:
|
||||
return
|
||||
# Stays out of the way of the entry view scrollbar
|
||||
right_inset = max(self.RIGHT_MARGIN, self.SCROLLBAR_WIDTH)
|
||||
width = max(0, parent.width() - right_inset)
|
||||
self.setGeometry(0, parent.height() - self.HEIGHT, width, self.HEIGHT)
|
||||
|
||||
|
||||
class Validator(QIntValidator):
|
||||
def __init__(self, bottom: int, top: int) -> None:
|
||||
super().__init__(bottom, top)
|
||||
|
||||
@override
|
||||
def fixup(self, input: str) -> str:
|
||||
input = input.strip("0")
|
||||
return super().fixup(str(self.top()) if input else "1")
|
||||
@@ -0,0 +1,120 @@
|
||||
# SPDX-FileCopyrightText: (c) TagStudio Contributors
|
||||
# SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
|
||||
from typing import override
|
||||
|
||||
from PIL import Image, ImageQt
|
||||
from PySide6.QtCore import QSize
|
||||
from PySide6.QtGui import QIntValidator, QPixmap, Qt
|
||||
from PySide6.QtWidgets import QHBoxLayout, QLabel, QLineEdit, QPushButton, QWidget
|
||||
|
||||
from tagstudio.qt.resource_manager import ResourceManager
|
||||
from tagstudio.qt.views.styles.color_overlay import auto_theme_overlay
|
||||
|
||||
|
||||
class PageValidator(QIntValidator):
|
||||
@override
|
||||
def fixup(self, input: str) -> str:
|
||||
input = input.strip("0")
|
||||
return super().fixup(str(self.top()) if input else "1")
|
||||
|
||||
|
||||
class PaginationView(QHBoxLayout):
|
||||
# NOTE: UI Example:
|
||||
# [<] [1]...[3][4] [5] [6][7]...[42] [>]
|
||||
# ^^^^ <-- 2 Buffer Pages
|
||||
# Center Page Number is Editable Text
|
||||
|
||||
BUTTON_SIZE = QSize(32, 24)
|
||||
BUFFER_PAGE_COUNT = 4
|
||||
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
self.setContentsMargins(0, 6, 0, 6)
|
||||
self.setSpacing(3)
|
||||
_rm = ResourceManager()
|
||||
|
||||
# [<] ----------------------------------
|
||||
self.prev_button = QPushButton()
|
||||
prev_icon: Image.Image = auto_theme_overlay(_rm.bxs_left_arrow, use_alpha=False)
|
||||
self.prev_button.setIcon(QPixmap.fromImage(ImageQt.ImageQt(prev_icon)))
|
||||
self.prev_button.setIconSize(QSize(12, 12))
|
||||
self.prev_button.setMinimumSize(self.BUTTON_SIZE)
|
||||
self.prev_button.setMaximumSize(self.BUTTON_SIZE)
|
||||
|
||||
# --- [1] ------------------------------
|
||||
self.start_button = QPushButton()
|
||||
self.start_button.setMinimumSize(self.BUTTON_SIZE)
|
||||
self.start_button.setMaximumSize(self.BUTTON_SIZE)
|
||||
|
||||
# ------ ... ---------------------------
|
||||
self.start_ellipses = QLabel(". . .")
|
||||
self.start_ellipses.setMinimumSize(self.BUTTON_SIZE)
|
||||
self.start_ellipses.setMaximumSize(self.BUTTON_SIZE)
|
||||
|
||||
# --------- [3][4] ---------------------
|
||||
self.start_buffer_container = QWidget()
|
||||
self.start_buffer_layout = QHBoxLayout(self.start_buffer_container)
|
||||
self.start_buffer_layout.setContentsMargins(0, 0, 0, 0)
|
||||
self.start_buffer_layout.setSpacing(3)
|
||||
|
||||
# ---------------- [5] -----------------
|
||||
self.current_page_field = QLineEdit()
|
||||
self.current_page_field.setMinimumSize(self.BUTTON_SIZE)
|
||||
self.current_page_field.setMaximumSize(self.BUTTON_SIZE)
|
||||
self.current_page_field.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
self.validator = PageValidator(1, 0)
|
||||
self.current_page_field.setValidator(self.validator)
|
||||
|
||||
# -------------------- [6][7] ----------
|
||||
self.end_buffer_container = QWidget()
|
||||
self.end_buffer_layout = QHBoxLayout(self.end_buffer_container)
|
||||
self.end_buffer_layout.setContentsMargins(0, 0, 0, 0)
|
||||
self.end_buffer_layout.setSpacing(3)
|
||||
|
||||
# -------------------------- ... -------
|
||||
self.end_ellipses = QLabel(". . .")
|
||||
self.end_ellipses.setMinimumSize(self.BUTTON_SIZE)
|
||||
self.end_ellipses.setMaximumSize(self.BUTTON_SIZE)
|
||||
|
||||
# ----------------------------- [42] ---
|
||||
self.end_button = QPushButton()
|
||||
self.end_button.setMinimumSize(self.BUTTON_SIZE)
|
||||
self.end_button.setMaximumSize(self.BUTTON_SIZE)
|
||||
|
||||
# ---------------------------------- [>]
|
||||
self.next_button = QPushButton()
|
||||
next_icon: Image.Image = auto_theme_overlay(_rm.bxs_right_arrow, use_alpha=False)
|
||||
self.next_button.setIcon(QPixmap.fromImage(ImageQt.ImageQt(next_icon)))
|
||||
self.next_button.setIconSize(QSize(12, 12))
|
||||
self.next_button.setMinimumSize(self.BUTTON_SIZE)
|
||||
self.next_button.setMaximumSize(self.BUTTON_SIZE)
|
||||
|
||||
# Finalize Layout
|
||||
self.addStretch(1)
|
||||
self.addWidget(self.prev_button)
|
||||
self.addWidget(self.start_button)
|
||||
self.addWidget(self.start_ellipses)
|
||||
self.addWidget(self.start_buffer_container)
|
||||
self.addWidget(self.current_page_field)
|
||||
self.addWidget(self.end_buffer_container)
|
||||
self.addWidget(self.end_ellipses)
|
||||
self.addWidget(self.end_button)
|
||||
self.addWidget(self.next_button)
|
||||
self.addStretch(1)
|
||||
self._populate_buffer_buttons()
|
||||
|
||||
def _populate_buffer_buttons(self) -> None:
|
||||
for _ in range(max(self.BUFFER_PAGE_COUNT * 2, 5)):
|
||||
button = QPushButton()
|
||||
button.setMinimumSize(self.BUTTON_SIZE)
|
||||
button.setMaximumSize(self.BUTTON_SIZE)
|
||||
button.setHidden(True)
|
||||
self.start_buffer_layout.addWidget(button)
|
||||
|
||||
end_button = QPushButton()
|
||||
end_button.setMinimumSize(self.BUTTON_SIZE)
|
||||
end_button.setMaximumSize(self.BUTTON_SIZE)
|
||||
end_button.setHidden(True)
|
||||
self.end_buffer_layout.addWidget(end_button)
|
||||
Reference in New Issue
Block a user