From ba0d49acf04580838676757c010b348ccc9ca3a5 Mon Sep 17 00:00:00 2001 From: nolan778 <17110894+nolan778@users.noreply.github.com> Date: Thu, 4 Sep 2025 16:31:38 -0400 Subject: [PATCH] Prevent job history misclick on new image row --- ai_diffusion/ui/generation.py | 55 +++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/ai_diffusion/ui/generation.py b/ai_diffusion/ui/generation.py index f57ce27e5..5ba169287 100644 --- a/ai_diffusion/ui/generation.py +++ b/ai_diffusion/ui/generation.py @@ -33,6 +33,8 @@ class HistoryWidget(QListWidget): item_activated = pyqtSignal(QListWidgetItem) _thumb_size = 96 + _scroll_recent_ms = 10 + _misclick_guard_ms = 300 _applied_icon = Image.load(theme.icon_path / "star.png") _list_css = f""" QListWidget {{ background-color: transparent; }} @@ -53,6 +55,19 @@ def __init__(self, parent: QWidget | None): super().__init__(parent) self._model = root.active_model self._connections = [] + self._prev_scroll_max = 0 + self._misclick_guard_active = False + self._misclick_guard_timer = QTimer(self) + self._misclick_guard_timer.setSingleShot(True) + self._misclick_guard_timer.timeout.connect(self._end_misclick_guard) + self._scroll_range_changed_recently = False + self._scrolled_to_bottom_recently = False + self._scroll_range_change_recent_timer = QTimer(self) + self._scroll_range_change_recent_timer.setSingleShot(True) + self._scroll_range_change_recent_timer.timeout.connect(self._end_scroll_range_change_recent) + self._scroll_recent_timer = QTimer(self) + self._scroll_recent_timer.setSingleShot(True) + self._scroll_recent_timer.timeout.connect(self._end_scroll_recent) self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self.setResizeMode(QListView.Adjust) @@ -83,6 +98,8 @@ def __init__(self, parent: QWidget | None): self._context_button.setFixedWidth(f.height() + 8) if scrollbar := self.verticalScrollBar(): scrollbar.valueChanged.connect(self.update_apply_button) + scrollbar.rangeChanged.connect(self._on_scroll_range_changed) + self._prev_scroll_max = scrollbar.maximum() self.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu) self.customContextMenuRequested.connect(self._show_context_menu) @@ -145,6 +162,9 @@ def add(self, job: Job): self.addItem(item) if scroll_to_bottom: + self._mark_scrolled_to_bottom_recent() + if self._scroll_range_changed_recently: + self._start_misclick_guard() self.scrollToBottom() _job_info_translations = { @@ -305,6 +325,9 @@ def rebuild(self): self.clear() for job in filter(self.is_finished, self._model.jobs): self.add(job) + self._mark_scrolled_to_bottom_recent() + if self._scroll_range_changed_recently: + self._start_misclick_guard() self.scrollToBottom() def item_info(self, item: QListWidgetItem) -> tuple[str, int]: # job id, image index @@ -325,6 +348,9 @@ def handle_preview_click(self, item: QListWidgetItem): clipboard.setText(prompt) def mousePressEvent(self, e: QMouseEvent | None): + if e is not None and self._misclick_guard_active: + e.accept() + return if ( # make single click deselect current item (usually requires Ctrl+click) e is not None and e.button() == Qt.MouseButton.LeftButton @@ -375,6 +401,8 @@ def _image_thumbnail(self, job: Job, index: int): return thumb.to_icon() def _show_context_menu(self, pos: QPoint): + if self._misclick_guard_active: + return item = self.itemAt(pos) if item is not None: job = self._model.jobs.find(self._item_data(item).job) @@ -402,10 +430,37 @@ def _show_context_menu(self, pos: QPoint): menu.exec(self.mapToGlobal(pos)) def _show_context_menu_dropdown(self): + if self._misclick_guard_active: + return pos = self._context_button.pos() pos.setY(pos.y() + self._context_button.height()) self._show_context_menu(pos) + def _start_misclick_guard(self): + self._misclick_guard_active = True + self._misclick_guard_timer.start(self._misclick_guard_ms) + + def _end_misclick_guard(self): + self._misclick_guard_active = False + + def _mark_scrolled_to_bottom_recent(self): + self._scrolled_to_bottom_recently = True + self._scroll_recent_timer.start(self._scroll_recent_ms) + + def _end_scroll_recent(self): + self._scrolled_to_bottom_recently = False + + def _end_scroll_range_change_recent(self): + self._scroll_range_changed_recently = False + + def _on_scroll_range_changed(self, _min: int, _max: int): + if _max > self._prev_scroll_max: + self._scroll_range_changed_recently = True + self._scroll_range_change_recent_timer.start(self._scroll_recent_ms) + if self._scrolled_to_bottom_recently: + self._start_misclick_guard() + self._prev_scroll_max = _max + def _copy_prompt(self): if job := self.selected_job: active = self._model.regions.active_or_root