From 3e793596c28335c9285bec3da996ed2fe92540a7 Mon Sep 17 00:00:00 2001 From: Andreas Huber Date: Sun, 14 Apr 2024 19:12:37 +0200 Subject: [PATCH] replace ScaledScrollBar with BigScrollBar step 4 - add repeat actions This has probably a problem. The repeat action is triggering updates asynchronously. Which means we do not wait until it is done. Which means we can DOS ourselves. --- src/ui/bigtext/BigScrollBar.py | 65 +++++++++++++++++++++++++--------- 1 file changed, 49 insertions(+), 16 deletions(-) diff --git a/src/ui/bigtext/BigScrollBar.py b/src/ui/bigtext/BigScrollBar.py index 04210b4..1c01c6f 100644 --- a/src/ui/bigtext/BigScrollBar.py +++ b/src/ui/bigtext/BigScrollBar.py @@ -2,7 +2,7 @@ import enum from PySide6.QtGui import QWheelEvent from PySide6.QtWidgets import QWidget, QStylePainter, QStyle, QStyleOptionSlider, QSlider, QAbstractSlider -from PySide6.QtCore import Qt, QSize, QEvent, QRect, QPoint, Signal +from PySide6.QtCore import Qt, QSize, QEvent, QRect, QPoint, Signal, QTimer, QElapsedTimer class BigScrollBar(QWidget): @@ -33,6 +33,11 @@ class BigScrollBar(QWidget): def __init__(self): super(BigScrollBar, self).__init__() + self.repeat_action_timer = QTimer() + self.repeat_action_control = QStyle.SubControl.SC_None + self.repeat_action_timer.setSingleShot(True) + self.repeat_action_timer.timeout.connect(self.execute_control) + self.repeat_action_timer.setInterval(50) def setMinimum(self, min: int): self.minimum = min @@ -110,8 +115,14 @@ class BigScrollBar(QWidget): return super().event(event) def mousePressEvent(self, event): + + if self.repeat_action_timer.isActive(): + self.stopRepeatAction() + + if not (event.button() == Qt.MouseButton.LeftButton or event.button() == Qt.MouseButton.MiddleButton): + return + style_options = self.style_options() - style_options.keyboardModifiers = event.modifiers() ## not sure why, copied from https://github.com/qt/qtbase/blob/dev/src/widgets/widgets/qscrollbar.cpp#L535 self.pressedControl = self.style().hitTestComplexControl(QStyle.ComplexControl.CC_ScrollBar, style_options, event.position().toPoint(), self) @@ -120,14 +131,11 @@ class BigScrollBar(QWidget): sr: QRect = self.style().subControlRect(QStyle.ComplexControl.CC_ScrollBar, style_options, QStyle.SubControl.SC_ScrollBarSlider, self) click: QPoint = event.position().toPoint() - press_y_value = click.y() - sr.center().y() + sr.topLeft().y() - press_y_value = self.pixelPosToRangeValue(press_y_value) # print(f"pressYValue {pressYValue}") if self.pressedControl == QStyle.SubControl.SC_ScrollBarSlider: self.click_offset = click.y() - sr.y() - # todo if (self.pressedControl == QStyle.SubControl.SC_ScrollBarAddPage or self.pressedControl == QStyle.SubControl.SC_ScrollBarSubPage) \ and event.button() == Qt.MouseButton.MiddleButton: @@ -135,30 +143,44 @@ class BigScrollBar(QWidget): self.set_value(self.pixelPosToRangeValue(event.position().toPoint().y())) self.pressedControl = QStyle.SubControl.SC_ScrollBarSlider self.click_offset = slider_length / 2 - pass + return - self.activateControl(self.pressedControl) + self.repeat_action_control = self.pressedControl + self.execute_control() # self.repaint(self.style().subControlRect(QStyle.ComplexControl.CC_ScrollBar, style_options, self.pressedControl)) - def activateControl(self, control): - match control: + def execute_control(self): + # print(f"execute_control: {self.repeat_action_control}") + trigger_repeat_action = True + match self.repeat_action_control: case QStyle.SubControl.SC_ScrollBarAddPage: self.scroll_event.emit(self.ScrollEvent.PageDown) case QStyle.SubControl.SC_ScrollBarSubPage: self.scroll_event.emit(self.ScrollEvent.PageUp) + if self.value <= self.minimum: + trigger_repeat_action = False case QStyle.SubControl.SC_ScrollBarAddLine: self.scroll_event.emit(self.ScrollEvent.LinesDown) case QStyle.SubControl.SC_ScrollBarSubLine: self.scroll_event.emit(self.ScrollEvent.LinesUp) + if self.value <= self.minimum: + trigger_repeat_action = False case QStyle.SubControl.SC_ScrollBarFirst: self.set_value(self.minimum) + trigger_repeat_action = False case QStyle.SubControl.SC_ScrollBarLast: self.set_value(self.maximum) + trigger_repeat_action = False case _: - pass + trigger_repeat_action = False + + if trigger_repeat_action: + # print(f"schedule repeat action: {self.repeat_action_control}") + self.repeat_action_timer.start() def mouseReleaseEvent(self, event): self.pressedControl = QStyle.SubControl.SC_None + self.stopRepeatAction() def mouseMoveEvent(self, event): if self.pressedControl == QStyle.SubControl.SC_None: @@ -177,6 +199,12 @@ class BigScrollBar(QWidget): # print(f"move to value: {new_position}") self.set_value(new_position) + # stop repeat action when pointer leaves control + pr: QRect = self.style().subControlRect(QStyle.ComplexControl.CC_ScrollBar, self.style_options(), + self.pressedControl, self) + if not pr.contains(event.position().toPoint()): + self.stopRepeatAction() + def wheelEvent(self, event: QWheelEvent): event.ignore() @@ -188,7 +216,6 @@ class BigScrollBar(QWidget): scroll_event = self.ScrollEvent.LinesDown if event.angleDelta().y() < 0 else self.ScrollEvent.LinesUp self.scroll_event.emit(scroll_event) - def pixelPosToRangeValue(self, pos: int): opt: QStyleOptionSlider = self.style_options() @@ -198,18 +225,24 @@ class BigScrollBar(QWidget): QStyle.SubControl.SC_ScrollBarSlider, self) # only for vertical scrollbars - sliderLength = sr.height(); - sliderMin = gr.y(); - sliderMax = gr.bottom() - sliderLength + 1; + slider_length = sr.height(); + slider_min = gr.y(); + slider_max = gr.bottom() - slider_length + 1; - val = QStyle.sliderValueFromPosition(opt.minimum, opt.maximum, pos - sliderMin, - sliderMax - sliderMin, opt.upsideDown) + val = QStyle.sliderValueFromPosition(opt.minimum, opt.maximum, pos - slider_min, + slider_max - slider_min, opt.upsideDown) t = self.scale / self.maximum val = int(val / t) # print(f"pixelPosToRangeValue({pos}) -> {val}") return val + def stopRepeatAction(self): + self.repeat_action_control = QStyle.SubControl.SC_None + # print(f"stop repeat action: {self.repeat_action_control}") + self.repeat_action_timer.stop() + #self.update() + def set_value(self, value: int): self.value = value self.value_changed.emit(str(self.value))