Fix toasts and modal dialogs

Toasts calculated the duration bar in python this is now a QPropertyAnimation

Series/Issue Selection windows now use signals/slots to communicate
This commit is contained in:
Timmy Welch 2025-01-14 21:23:17 -08:00
parent 13ee9e9ad8
commit ca8f36d105
7 changed files with 63 additions and 71 deletions

View File

@ -143,6 +143,7 @@ class IssueSelectionWindow(SelectionWindow):
self.accept()
def update_row(self, row: int, issue: GenericMetadata) -> None: # type: ignore[override]
self.twList.setStyleSheet(self.twList.styleSheet())
item_text = issue.issue or ""
item = self.twList.item(row, 0)
item.setText(item_text)

View File

@ -23,7 +23,7 @@ from collections import deque
import natsort
from PyQt5 import QtCore, QtGui, QtWidgets, uic
from PyQt5.QtCore import QUrl, pyqtSignal
from PyQt5.QtCore import Qt, QUrl, pyqtSignal
from comicapi import utils
from comicapi.comicarchive import ComicArchive
@ -149,6 +149,7 @@ class SelectionWindow(QtWidgets.QDialog):
literal: bool = False,
) -> None:
super().__init__(parent)
self.setWindowModality(Qt.WindowModality.WindowModal)
with self.ui_file.open(encoding="utf-8") as uifile:
uic.loadUi(uifile, self)
@ -497,7 +498,6 @@ class SeriesSelectionWindow(SelectionWindow):
selector = MatchSelectionWindow(
self, issues, self.comic_archive, talker=self.talker, config=self.config
)
selector.setModal(True)
selector.exec()
if selector.result():
# we should now have a list index
@ -523,9 +523,8 @@ class SeriesSelectionWindow(SelectionWindow):
break
self.selector.setWindowTitle(title + "Select Issue")
self.selector.setModal(True)
self.selector.finished.connect(self.issue_selected)
self.selector.open()
self.selector.show()
def issue_selected(self, result) -> None:
if result and self.selector:

View File

@ -669,7 +669,6 @@ class SettingsWindow(QtWidgets.QDialog):
def show_template_help(self) -> None:
template_help_win = TemplateHelpWindow(self)
template_help_win.setModal(False)
template_help_win.show()

View File

@ -1090,8 +1090,7 @@ class TaggerWindow(QtWidgets.QMainWindow):
self.selector.setWindowTitle(f"Search: '{series_name}' - Select Series")
self.selector.finished.connect(self.finish_query)
self.selector.setModal(True)
self.selector.open()
self.selector.show()
def finish_query(self, result) -> None:
if result and self.selector:
@ -1154,20 +1153,22 @@ class TaggerWindow(QtWidgets.QMainWindow):
self.metadata_to_form()
def on_ratelimit(self, full_time: float, sleep_time: float) -> None:
toast = Toast(self)
# Convert to milliseconds, make it end half a second before the ratelimit triggers again, make sure we have a positive time
toast.setDuration(abs(int(sleep_time * 1000) - 500))
toast.setResetDurationOnHover(False)
toast.setTitle("Rate Limit Hit!")
toast.setText(
self.toast = Toast(self)
if qtutils.is_dark_mode():
self.toast.applyPreset(ToastPreset.WARNING_DARK)
else:
self.toast.applyPreset(ToastPreset.WARNING)
# Convert to milliseconds, add 200ms because python is slow
self.toast.setDuration(abs(int(sleep_time * 1000) + 200))
self.toast.setResetDurationOnHover(False)
self.toast.setFadeOutDuration(50)
self.toast.setTitle("Rate Limit Hit!")
self.toast.setText(
f"Rate limit reached: {full_time:.0f}s until next request. Waiting {sleep_time:.0f}s for ratelimit"
)
if qtutils.is_dark_mode():
toast.applyPreset(ToastPreset.WARNING_DARK)
else:
toast.applyPreset(ToastPreset.WARNING)
toast.setPositionRelativeToWidget(self)
toast.show()
self.toast.setPositionRelativeToWidget(self)
self.toast.show()
def write_tags(self) -> None:
if self.metadata is not None and self.comic_archive is not None:
@ -1395,7 +1396,6 @@ class TaggerWindow(QtWidgets.QMainWindow):
def show_settings(self) -> None:
settingswin = SettingsWindow(self, self.config, self.talkers)
settingswin.setModal(True)
settingswin.exec()
settingswin.result()
self.adjust_source_combo()
@ -1779,8 +1779,7 @@ class TaggerWindow(QtWidgets.QMainWindow):
return
self.atprogdialog = AutoTagProgressWindow(self, self.current_talker())
self.atprogdialog.setModal(True)
self.atprogdialog.show()
self.atprogdialog.open()
self.atprogdialog.progressBar.setMaximum(len(ca_list))
self.atprogdialog.setWindowTitle("Auto-Tagging")
@ -1862,7 +1861,6 @@ class TaggerWindow(QtWidgets.QMainWindow):
self.config[0],
self.current_talker(),
)
matchdlg.setModal(True)
matchdlg.exec()
self.fileSelectionList.update_selected_rows()
new_ca = self.fileSelectionList.get_current_archive()
@ -1980,7 +1978,6 @@ class TaggerWindow(QtWidgets.QMainWindow):
"File Rename", "If you rename files now, unsaved data in the form will be lost. Are you sure?"
):
dlg = RenameWindow(self, ca_list, self.selected_read_tags, self.config, self.talkers)
dlg.setModal(True)
if dlg.exec() and self.comic_archive is not None:
self.fileSelectionList.update_selected_rows()
self.load_archive(self.comic_archive)

View File

@ -49,6 +49,15 @@
<verstretch>7</verstretch>
</sizepolicy>
</property>
<property name="styleSheet">
<string notr="true">QTableWidget[rowCount=&quot;0&quot;] {
background-image: url(&quot;:/graphics/about.png&quot;);
background-attachment: fixed;
background-position: top center;
background-repeat: no-repeat;
background-color: white;
}</string>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::SingleSelection</enum>
</property>

View File

@ -3,7 +3,6 @@ from __future__ import annotations
from PyQt5.QtGui import QColor
UPDATE_POSITION_DURATION = 200
DURATION_BAR_UPDATE_INTERVAL = 1
DROP_SHADOW_SIZE = 5
SUCCESS_ACCENT_COLOR = QColor("#3E9141")
WARNING_ACCENT_COLOR = QColor("#E8B849")
@ -23,7 +22,6 @@ DEFAULT_CLOSE_BUTTON_ICON_COLOR_DARK = QColor("#C9C9C9")
__all__ = [
"UPDATE_POSITION_DURATION",
"DURATION_BAR_UPDATE_INTERVAL",
"DROP_SHADOW_SIZE",
"SUCCESS_ACCENT_COLOR",
"WARNING_ACCENT_COLOR",

View File

@ -2,7 +2,18 @@ from __future__ import annotations
import math
from PyQt5.QtCore import QEvent, QMargins, QPoint, QPropertyAnimation, QRect, QSize, Qt, QTimer, pyqtSignal
from PyQt5.QtCore import (
QAbstractAnimation,
QEvent,
QMargins,
QPoint,
QPropertyAnimation,
QRect,
QSize,
Qt,
QTimer,
pyqtSignal,
)
from PyQt5.QtGui import QColor, QFont, QFontMetrics, QGuiApplication, QIcon, QPixmap, QScreen
from PyQt5.QtWidgets import QDialog, QGraphicsOpacityEffect, QLabel, QPushButton, QWidget
@ -19,7 +30,6 @@ from .constants import (
DEFAULT_TITLE_COLOR,
DEFAULT_TITLE_COLOR_DARK,
DROP_SHADOW_SIZE,
DURATION_BAR_UPDATE_INTERVAL,
ERROR_ACCENT_COLOR,
INFORMATION_ACCENT_COLOR,
SUCCESS_ACCENT_COLOR,
@ -95,7 +105,6 @@ class Toast(QDialog):
self.__close_button_margins = QMargins(0, -8, 0, -8)
self.__text_section_spacing = 8
self.__elapsed_time = 0
self.__fading_out = False
self.__used = False
@ -145,7 +154,6 @@ class Toast(QDialog):
# Duration bar chunk
self.__duration_bar_chunk = QWidget(self.__duration_bar_container)
self.__duration_bar_chunk.setFixedHeight(20)
self.__duration_bar_chunk.move(0, -16)
# Set defaults
@ -173,10 +181,6 @@ class Toast(QDialog):
self.__duration_timer.setSingleShot(True)
self.__duration_timer.timeout.connect(self.hide)
# Timer for updating the duration bar
self.__duration_bar_timer = QTimer(self)
self.__duration_bar_timer.timeout.connect(self.__update_duration_bar)
# Apply stylesheet
self.setStyleSheet((css_path / "toast.css").read_text(encoding="utf-8"))
@ -205,14 +209,13 @@ class Toast(QDialog):
"""
# Reset timer if hovered and resetting is enabled
if self.__duration != 0 and self.__duration_timer.isActive() and self.__reset_duration_on_hover:
if self.__reset_duration_on_hover and self.__duration != 0 and self.__duration_timer.isActive():
self.__duration_timer.stop()
# Reset duration bar if enabled
if self.__show_duration_bar:
self.__duration_bar_timer.stop()
self.__duration_bar_chunk.setFixedWidth(self.width())
self.__elapsed_time = 0
self.__bar_animation.stop()
self.__duration_bar_chunk.setGeometry(QRect(0, 0, self.__duration_bar_container.width(), 4))
def leaveEvent(self, event: QEvent) -> None:
"""Event that happens every time the mouse leaves this widget.
@ -222,12 +225,21 @@ class Toast(QDialog):
"""
# Start timer again when leaving notification and reset is enabled
if self.__duration != 0 and not self.__duration_timer.isActive() and self.__reset_duration_on_hover:
if self.__reset_duration_on_hover and self.__duration != 0 and not self.__duration_timer.isActive():
self.__duration_timer.start(self.__duration)
# Restart duration bar animation if enabled
if self.__show_duration_bar:
self.__duration_bar_timer.start(DURATION_BAR_UPDATE_INTERVAL)
self.__start_duration_bar_animation()
def __start_duration_bar_animation(self) -> None:
self.__bar_animation = QPropertyAnimation(self.__duration_bar_chunk, b"geometry")
self.__bar_animation.setDuration(self.__duration)
self.__bar_animation.setStartValue(QRect(0, 0, self.__duration_bar.width(), 4))
self.__bar_animation.setEndValue(QRect(0, 0, 0, 4))
self.__bar_animation.setDirection(QAbstractAnimation.Direction.Forward)
self.__bar_animation.start()
def show(self) -> None:
"""Show the toast notification"""
@ -244,16 +256,12 @@ class Toast(QDialog):
# Setup UI
self.__setup_ui()
# Calculate position and show (animate position too if not first notification)
x, y = self.__calculate_position()
# Start duration timer
if self.__duration != 0:
self.__duration_timer.start(self.__duration)
# Start duration bar update timer
if self.__duration != 0 and self.__show_duration_bar:
self.__duration_bar_timer.start(DURATION_BAR_UPDATE_INTERVAL)
# Calculate position and show (animate position too if not first notification)
x, y = self.__calculate_position()
self.__start_duration_bar_animation()
# If not first toast on screen, also do a fade down/up animation
if len(Toast.__currently_shown) > 1:
@ -327,7 +335,6 @@ class Toast(QDialog):
if self in Toast.__currently_shown:
Toast.__currently_shown.remove(self)
self.__elapsed_time = 0
self.__fading_out = False
# Emit signal
@ -343,21 +350,6 @@ class Toast(QDialog):
timer.timeout.connect(Toast.__show_next_in_queue)
timer.start(self.__fade_in_duration)
def __update_duration_bar(self) -> None:
"""Update the duration bar chunk with the elapsed time"""
self.__elapsed_time += DURATION_BAR_UPDATE_INTERVAL
if self.__elapsed_time >= self.__duration:
self.__duration_bar_timer.stop()
return
new_chunk_width = math.floor(
self.__duration_bar_container.width()
- self.__elapsed_time / self.__duration * self.__duration_bar_container.width()
)
self.__duration_bar_chunk.setFixedWidth(new_chunk_width)
def __update_position_xy(self, animate: bool = True) -> None:
"""Update the x and y position of the toast with an optional animation
@ -893,14 +885,11 @@ class Toast(QDialog):
self.__close_button.setVisible(False)
# Resize, move, and show duration bar if enabled
if self.__show_duration_bar:
self.__duration_bar_container.setFixedWidth(width)
self.__duration_bar_container.move(0, height - duration_bar_height)
self.__duration_bar.setFixedWidth(width)
self.__duration_bar_chunk.setFixedWidth(width)
self.__duration_bar_container.setVisible(True)
else:
self.__duration_bar_container.setVisible(False)
self.__duration_bar_container.setFixedWidth(width)
self.__duration_bar_container.move(0, height - duration_bar_height)
self.__duration_bar.setFixedWidth(width)
self.__duration_bar_container.setVisible(self.__show_duration_bar)
def __install_widget_event_filter(self) -> None:
"""Install an event filter on parent"""