diff --git a/src/ui/bigtext/BigScrollBar.py b/src/ui/bigtext/BigScrollBar.py new file mode 100644 index 0000000..79306cb --- /dev/null +++ b/src/ui/bigtext/BigScrollBar.py @@ -0,0 +1,173 @@ +from PySide6.QtWidgets import QWidget, QStylePainter, QStyle, QStyleOptionSlider, QSlider +from PySide6.QtCore import Qt, QSize, QEvent, QRect, QPoint, Signal + + +class BigScrollBar(QWidget): + value_changed = Signal(str) + """Signal emitted when the range slider value changes. + **Note**: The value is a string and must be parsed into an int. + QT's signal api only supports 32bit integers. Ints larger + than 2**32-1 will overflow. Probably because there is some C/C++ + code involved. We work around this by converting the python int + into a string.""" + + pressedControl = QStyle.SubControl.SC_None + click_offset = 0 + + scale = 10000 + + minimun = 0 + value = 0 + maximum = 100 + + def __init__(self): + super(BigScrollBar, self).__init__() + + def setMinimum(self, min: int): + self.minimun = min + if self.value < self.minimun: + self.set_value(self.minimun) + + def setMaximum(self, max: int): + self.maximum = max + if self.value > self.maximum: + self.set_value(self.maximum) + + def paintEvent(self, event): + p = QStylePainter(self) + o = self.style_options() + print(f"style_options: sliderPosition: {o.sliderPosition}") + p.drawComplexControl(QStyle.ComplexControl.CC_ScrollBar, o) + + def style_options(self) -> QStyleOptionSlider: + o = QStyleOptionSlider() + o.initFrom(self) + o.orientation = Qt.Orientation.Vertical + o.subControls = QStyle.SubControl.SC_All + + if self.pressedControl != QStyle.SubControl.SC_None: + o.activeSubControls = self.pressedControl # QStyle.SubControl.SC_None + o.state = o.state | QStyle.StateFlag.State_Sunken + + o.singleStep = 1 + + # scale values to 10000 + + t = self.scale / self.maximum + o.minimum = self.minimun * t + o.maximum = self.maximum * t + o.sliderPosition = int(self.value * t) + print(f"t={t}") + print(f"({self.minimun}, {self.value}, {self.maximum}) -> ({o.minimum},{o.sliderPosition},{o.maximum})") + + return o + + ## QSize QScrollBar::sizeHint() const + # { + # ensurePolished(); + # QStyleOptionSlider opt; + # initStyleOption(&opt); + + # int scrollBarExtent = style()->pixelMetric(QStyle::PM_ScrollBarExtent, &opt, this); + # int scrollBarSliderMin = style()->pixelMetric(QStyle::PM_ScrollBarSliderMin, &opt, this); + # QSize size; + # if (opt.orientation == Qt::Horizontal) + # size = QSize(scrollBarExtent * 2 + scrollBarSliderMin, scrollBarExtent); + # else + # size = QSize(scrollBarExtent, scrollBarExtent * 2 + scrollBarSliderMin); + # + # return style()->sizeFromContents(QStyle::CT_ScrollBar, &opt, size, this); + # } + + def sizeHint(self): + scroll_bar_extend = self.style().pixelMetric(QStyle.PixelMetric.PM_ScrollBarExtent) + scroll_bar_slider_min = self.style().pixelMetric(QStyle.PixelMetric.PM_ScrollBarSliderMin) + # print(f"scroll_bar_extend: {scroll_bar_extend}, scroll_bar_slider_min: {scroll_bar_slider_min}") + # for vertial + size = QSize(scroll_bar_extend, scroll_bar_extend * 2 + scroll_bar_slider_min) + + o = self.style_options() + return self.style().sizeFromContents(QStyle.ContentsType.CT_ScrollBar, o, size, self) + + def event(self, event: QEvent) -> bool: + # print(f"event type: {event.type()}") + + if event.type() == QEvent.Type.MouseButtonPress: + pass + # print(f"mouse button pressed") + + return super().event(event) + + def mousePressEvent(self, event): + 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) + # print(f"pressedControl {self.pressedControl}") + + sr: QRect = self.style().subControlRect(QStyle.ComplexControl.CC_ScrollBar, style_options, + QStyle.SubControl.SC_ScrollBarSlider, self) + click: QPoint = event.position().toPoint() + pressYValue = click.y() - sr.center().y() + sr.topLeft().y() + pressYValue = self.pixelPosToRangeValue(pressYValue) + # 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: + slider_length = sr.height() + self.set_value(self.pixelPosToRangeValue(event.position().toPoint().y())) + self.pressedControl = QStyle.SubControl.SC_ScrollBarSlider + self.click_offset = slider_length / 2 + pass + + def mouseReleaseEvent(self, event): + self.pressedControl = QStyle.SubControl.SC_None + + def mouseMoveEvent(self, event): + if self.pressedControl == QStyle.SubControl.SC_None: + return + + if self.pressedControl == QStyle.SubControl.SC_ScrollBarSlider: + click: QPoint = event.position().toPoint() + new_position = self.pixelPosToRangeValue(click.y() - self.click_offset) + m = self.style().pixelMetric(QStyle.PixelMetric.PM_MaximumDragDistance, self.style_options(), self) + if m >= 0: + r: QRect = self.rect() + r.adjust(-m, -m, m, m) + if not r.contains(event.position().toPoint()): + new_position = self.snap_back_position + + # print(f"move to value: {new_position}") + self.set_value(new_position) + + def pixelPosToRangeValue(self, pos: int): + opt: QStyleOptionSlider = self.style_options() + + gr: QRect = self.style().subControlRect(QStyle.ComplexControl.CC_ScrollBar, opt, + QStyle.SubControl.SC_ScrollBarGroove, self) + sr: QRect = self.style().subControlRect(QStyle.ComplexControl.CC_ScrollBar, opt, + QStyle.SubControl.SC_ScrollBarSlider, self) + + # only for vertical scrollbars + sliderLength = sr.height(); + sliderMin = gr.y(); + sliderMax = gr.bottom() - sliderLength + 1; + + val = QStyle.sliderValueFromPosition(opt.minimum, opt.maximum, pos - sliderMin, + sliderMax - sliderMin, opt.upsideDown) + + t = self.scale / self.maximum + val = int(val / t) + # print(f"pixelPosToRangeValue({pos}) -> {val}") + return val + + def set_value(self, value: int): + self.value = value + self.value_changed.emit(str(value)) + self.repaint() diff --git a/src/ui/bigtext/bigtext.py b/src/ui/bigtext/bigtext.py index 9e94af9..f0611c1 100644 --- a/src/ui/bigtext/bigtext.py +++ b/src/ui/bigtext/bigtext.py @@ -11,6 +11,7 @@ from PySide6.QtGui import QMouseEvent from PySide6.QtWidgets import * from src.ui.ScaledScrollBar import ScaledScrollBar +from src.ui.bigtext.BigScrollBar import BigScrollBar from src.ui.bigtext.highlight_regex import HighlightRegex from src.ui.bigtext.highlight_selection import HighlightSelection from src.ui.bigtext.highlighted_range import HighlightedRange @@ -86,7 +87,7 @@ class BigText(QWidget): self.grid.setVerticalSpacing(0) self.setLayout(self.grid) - self.v_scroll_bar = ScaledScrollBar() + self.v_scroll_bar = BigScrollBar() self.big_text = InnerBigText(self, model, self.v_scroll_bar) self.big_text.setSizePolicy(QSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)) @@ -97,7 +98,7 @@ class BigText(QWidget): self.h_scroll_bar.valueChanged.connect(self.big_text.h_scroll_event) # self.v_scroll_bar.setPageStep(1) - self.v_scroll_bar.scaledValueChanged.connect(self.big_text.v_scroll_event) + self.v_scroll_bar.value_changed.connect(self.big_text.v_scroll_event) if show_range_slider: self.range_limit = RangeSlider() @@ -308,12 +309,12 @@ class InnerBigText(QWidget): def scroll_by_lines(self, scroll_lines: int): self.scroll_lines = scroll_lines self.update() - self.parent.v_scroll_bar.setValue(self._byte_offset) + self.parent.v_scroll_bar.set_value(self._byte_offset) def scroll_to_byte(self, byte_offset: int): self._byte_offset = min(byte_offset, self.model.byte_count()) self.update() - self.parent.v_scroll_bar.setValue(self._byte_offset) + self.parent.v_scroll_bar.set_value(self._byte_offset) # noinspection PyTypeChecker def mousePressEvent(self, e: QtGui.QMouseEvent) -> None: