feat: wheel only adjusts values while the right button is held
WheelValueGuard (app-level event filter, installed by MainWindow) makes a bare wheel over spin boxes, sliders, dials, and combos scroll the enclosing scroll area instead of nudging the value - motor protection. NoWheelScrollArea stops swallowing wheel events since values are now guarded at the widget. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -2,9 +2,10 @@ from PySide6.QtWidgets import QScrollArea
|
||||
|
||||
|
||||
class NoWheelScrollArea(QScrollArea):
|
||||
"""Historic name: it used to swallow the wheel entirely so scrolling the
|
||||
column could not nudge a value widget. WheelValueGuard now protects the
|
||||
value widgets themselves (right button + wheel to adjust), so the wheel
|
||||
scrolls the column content normally again — and only ever scrolls."""
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
|
||||
def wheelEvent(self, event):
|
||||
# Override the wheelEvent and do nothing
|
||||
pass
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
from PySide6.QtCore import QEvent, QObject, Qt
|
||||
from PySide6.QtGui import QWheelEvent
|
||||
from PySide6.QtWidgets import (
|
||||
QAbstractScrollArea,
|
||||
QAbstractSpinBox,
|
||||
QApplication,
|
||||
QComboBox,
|
||||
QDial,
|
||||
QSlider,
|
||||
QTabBar,
|
||||
)
|
||||
|
||||
|
||||
class WheelValueGuard(QObject):
|
||||
"""App-level wheel safety for value widgets.
|
||||
|
||||
The wheel only ADJUSTS a slider / spin box / dial / combo while the
|
||||
RIGHT mouse button is held down — a deliberate two-hand gesture. A bare
|
||||
wheel over any of them is re-aimed at the enclosing scroll area, so
|
||||
scrolling a page can never nudge a value and therefore never moves a
|
||||
motor. Install once with QApplication.installEventFilter.
|
||||
"""
|
||||
|
||||
# QTabBar: wheel switches tabs on Linux by default — same accidental-input
|
||||
# hazard as a value nudge, so guard it too.
|
||||
GUARDED = (QAbstractSpinBox, QSlider, QDial, QComboBox, QTabBar)
|
||||
|
||||
def eventFilter(self, obj, event):
|
||||
if event.type() == QEvent.Type.Wheel and isinstance(obj, self.GUARDED):
|
||||
if event.buttons() & Qt.MouseButton.RightButton:
|
||||
return False # right button held: deliberate value adjustment
|
||||
area = obj.parentWidget()
|
||||
while area is not None and not isinstance(area, QAbstractScrollArea):
|
||||
area = area.parentWidget()
|
||||
if area is not None:
|
||||
relayed = QWheelEvent(
|
||||
area.viewport().mapFromGlobal(event.globalPosition()),
|
||||
event.globalPosition(),
|
||||
event.pixelDelta(),
|
||||
event.angleDelta(),
|
||||
event.buttons(),
|
||||
event.modifiers(),
|
||||
event.phase(),
|
||||
event.inverted(),
|
||||
)
|
||||
QApplication.sendEvent(area.viewport(), relayed)
|
||||
return True
|
||||
return super().eventFilter(obj, event)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# ponytail: smallest check that fails if the guard logic breaks
|
||||
import os
|
||||
|
||||
os.environ.setdefault("QT_QPA_PLATFORM", "offscreen")
|
||||
from PySide6.QtCore import QPoint, QPointF
|
||||
|
||||
app = QApplication([])
|
||||
guard = WheelValueGuard()
|
||||
app.installEventFilter(guard)
|
||||
slider = QSlider(Qt.Orientation.Horizontal)
|
||||
slider.setRange(0, 100)
|
||||
slider.setValue(50)
|
||||
slider.show()
|
||||
|
||||
def wheel(buttons):
|
||||
return QWheelEvent(
|
||||
QPointF(5, 5),
|
||||
QPointF(5, 5),
|
||||
QPoint(0, 0),
|
||||
QPoint(0, 120),
|
||||
buttons,
|
||||
Qt.KeyboardModifier.NoModifier,
|
||||
Qt.ScrollPhase.NoScrollPhase,
|
||||
False,
|
||||
)
|
||||
|
||||
QApplication.sendEvent(slider, wheel(Qt.MouseButton.NoButton))
|
||||
assert slider.value() == 50, "bare wheel must not adjust the slider"
|
||||
QApplication.sendEvent(slider, wheel(Qt.MouseButton.RightButton))
|
||||
assert slider.value() != 50, "right-button + wheel must adjust the slider"
|
||||
print("gude")
|
||||
Reference in New Issue
Block a user