Keep the wheel off the boxes and a floor under the window

Two things the scrolling tabs brought with them.

A combo box and a spin box read the wheel as a change of value, and Qt
hands them the focus before delivering it. Now that every tab scrolls,
rolling down the API tab with the pointer over the model box picks a
different model on the way past, and Save writes it down. The boxes take
the focus by click or by tab only, and a wheel that arrives at one
without the focus is refused rather than swallowed, so it carries on up
to the scroll area and the page moves instead.

A tab that scrolls also asks for no height of its own, which left
nothing to stop the window being dragged down to a tab bar and half a
button. It has a floor now, and the floor never asks for more room than
the screen was just found to have.
This commit is contained in:
2026-08-16 12:09:21 +03:00
parent f7fa37b4b7
commit 5547f85848
2 changed files with 89 additions and 6 deletions
+40 -6
View File
@@ -4,12 +4,12 @@ import os
import shutil
import threading
from PyQt6.QtCore import QRect, Qt, QUrl, pyqtSignal
from PyQt6.QtCore import QEvent, QObject, QRect, Qt, QUrl, pyqtSignal
from PyQt6.QtGui import QDesktopServices, QGuiApplication, QKeySequence, QShortcut
from PyQt6.QtWidgets import (
QAbstractItemView, QCheckBox, QComboBox, QDialog, QDialogButtonBox,
QFileDialog, QFormLayout, QGroupBox, QHBoxLayout, QLabel, QLineEdit,
QListWidget, QListWidgetItem, QMenu, QMessageBox, QPlainTextEdit,
QAbstractItemView, QAbstractSpinBox, QCheckBox, QComboBox, QDialog,
QDialogButtonBox, QFileDialog, QFormLayout, QGroupBox, QHBoxLayout, QLabel,
QLineEdit, QListWidget, QListWidgetItem, QMenu, QMessageBox, QPlainTextEdit,
QPushButton, QScrollArea, QSpinBox, QTabWidget, QVBoxLayout, QWidget,
)
@@ -159,6 +159,26 @@ class WrappedLabel(QLabel):
self.fontMetrics().boundingRect(box, wrap, self.text()).height())
class WheelGuard(QObject):
"""Keeps a rolled wheel off the box the pointer only passed over.
A combo box and a spin box both read the wheel as a change of value, and
every tab scrolls now: rolling down the API tab with the pointer over the
model box would pick a different model on the way past, and the setting is
saved without anybody having chosen it. The wheel counts once the box has
been clicked into; before that it is handed back to the page underneath,
which is what the roll was for.
"""
def eventFilter(self, box, event):
if event.type() == QEvent.Type.Wheel and not box.hasFocus():
# Refused rather than swallowed. An unaccepted wheel event carries
# on up the parents to the scroll area, so the page still moves.
event.ignore()
return True
return super().eventFilter(box, event)
class LocalModelBox(QGroupBox):
"""The program, the model, and the two downloads that put them there.
@@ -516,6 +536,10 @@ class SettingsWindow(QDialog):
self.transcriber = FileTranscriber(conf, self)
self.setWindowTitle(t("Dikte Settings"))
# One for the whole window, parented to it so it outlives the boxes it
# watches and goes when they do.
self._wheel_guard = WheelGuard(self)
tabs = self.tabs = QTabWidget(self)
tabs.addTab(self._scrolled(self._general_tab()), t("General"))
self.api_tab_index = tabs.addTab(
@@ -560,8 +584,7 @@ class SettingsWindow(QDialog):
if not conf.transcribe_ready():
self.tabs.setCurrentIndex(self.api_tab_index)
@staticmethod
def _scrolled(page):
def _scrolled(self, page):
"""A tab that scrolls instead of growing the window to fit."""
# Every tab goes through here. A page kept at its full height passes
# that height on as the window's minimum, and a tall one (the API tab
@@ -571,6 +594,12 @@ class SettingsWindow(QDialog):
area.setWidgetResizable(True)
area.setFrameShape(QScrollArea.Shape.NoFrame)
area.setWidget(page)
for box in page.findChildren((QComboBox, QAbstractSpinBox)):
# Focus by click or by tab, not by wheel. Qt hands the focus over
# before it delivers the wheel, so a box left on the default policy
# would have it by the time the guard below asked.
box.setFocusPolicy(Qt.FocusPolicy.StrongFocus)
box.installEventFilter(self._wheel_guard)
return area
def _size_to_screen(self, width, height):
@@ -582,6 +611,11 @@ class SettingsWindow(QDialog):
# and the buttons along the bottom stay on screen.
width = min(width, room.width() - 40)
height = min(height, room.height() - 80)
# Scrolling tabs ask for no height of their own, which leaves nothing to
# stop the window being dragged down to a tab bar and half a button. The
# floor is a floor and not a demand: it never asks for more room than
# the screen has just been found to have.
self.setMinimumSize(min(520, width), min(380, height))
self.resize(width, height)
# ---- tabs ----------------------------------------------------------