Source code for TermTk.TTkWidgets.scrollbar
# MIT License
#
# Copyright (c) 2021 Eugenio Parodi <ceccopierangiolieugenio AT googlemail DOT com>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
__all__ = ['TTkScrollBar']
from TermTk.TTkCore.constant import TTkK
from TermTk.TTkCore.signal import pyTTkSlot, pyTTkSignal
from TermTk.TTkCore.color import TTkColor
from TermTk.TTkWidgets.widget import TTkWidget
'''
ref: https://doc.qt.io/qt-5/qscrollbar.html
'''
[docs]
class TTkScrollBar(TTkWidget):
'''TTkScrollBar'''
classStyle = {
'default': {'color': TTkColor.RST},
'disabled': {'color': TTkColor.fg('#888888')},
'focus': {'color': TTkColor.fg('#cccc00')},
}
__slots__ = (
'_orientation',
'_minimum', '_maximum',
'_singleStep', '_pageStep',
'_value',
'_draggable', '_mouseDelta',
# Those Vars are required to handle the mouseclick
# |-----| Screen Pg Down
# |---| Screen Scroller
# |-----| Screen Pg Up
# <------|XXX|----->
'_screenPgDown','_screenPgUp','_screenScroller',
# Signals
'valueChanged', 'rangeChanged', 'sliderMoved'
)
def __init__(self, *,
value:int=0,
minimum:int=0,
maximum:int=99,
singleStep:int=1,
pageStep:int=10,
orientation:TTkK.Direction=TTkK.VERTICAL,
**kwargs) -> None:
# Define Signals
self.valueChanged = pyTTkSignal(int) # Value
self.rangeChanged = pyTTkSignal(int, int) # Min, Max
self.sliderMoved = pyTTkSignal(int) # Value
self._minimum = minimum
self._maximum = maximum
self._singleStep = singleStep
self._pageStep = pageStep
self._value = value
self._screenPgDown = (0,0)
self._screenPgUp = (0,0)
self._screenScroller = (0,0)
self._draggable = False
self._mouseDelta = 0
self._orientation = orientation
TTkWidget.__init__(self, **kwargs)
if self._orientation == TTkK.VERTICAL:
self.setMaximumWidth(1)
self.setMinimumSize(1,3)
else:
self.setMaximumHeight(1)
self.setMinimumSize(3,1)
self.setFocusPolicy(TTkK.ClickFocus)
'''
| min | max
<-----XXXXX--------> scrollbar
|------------------| size = widt or height
|----------------| size2 = widt or height - 2 (removed the ending arrows)
|------------| workingSize = max - min
|----------------| drawingSize = max - min + pageStep
a---b slider = [a=value-min, b=a+pageStep]
|---| pageStep, asciiStep (step size in ascii)
'''
def paintEvent(self, canvas):
style = self.currentStyle()
color = style['color']
if self._orientation == TTkK.VERTICAL:
size=self._height
else:
size=self._width
if self._maximum == self._minimum:
# Special case where no scroll is needed
aa=0
bb=size-2
else:
size2 = size-2
asciiStep = size2 * self._pageStep // (self._maximum - self._minimum + self._pageStep)
if asciiStep==0: asciiStep=1 # Force the slider to be at least one char wide
asciiDrawingSize = size2 - asciiStep
a = self._value - self._minimum
# convert i screen coordinates
aa = asciiDrawingSize * a // (self._maximum - self._minimum)
bb = aa + asciiStep
canvas.drawScroll(pos=(0,0),size=size,slider=(aa+1,bb+1),orientation=self._orientation, color=color)
# Update the screen position coordinates
self._screenPgDown = ( 1 , aa+1 )
self._screenScroller = ( aa+1 , bb+1)
self._screenPgUp = ( bb+1 , size-1 )
# TTkLog.debug(f"aa:{aa} bb:{bb}, a:{a}, size2:{size2}")
def wheelEvent(self, evt):
if evt.evt == TTkK.WHEEL_Up:
value = self._value-self._pageStep
else:
value = self._value+self._pageStep
self.setValue(max(self._minimum,min(self._maximum,value)))
self.sliderMoved.emit(self._value)
return True
def mousePressEvent(self, evt):
if self._orientation == TTkK.VERTICAL:
size=self._height
mouse = max(0,min(size,evt.y))
else:
size=self._width
mouse = max(0,min(size,evt.x))
self._draggable = False
if mouse == 0: # left/up arrow pressed
self.setValue(self._value - self._singleStep)
elif mouse == size-1: # right/down arrow pressed
self.setValue(self._value + self._singleStep)
elif self._screenPgDown[0] <= mouse < self._screenPgDown[1]:
self.setValue(self._value - self._pageStep)
elif self._screenPgUp[0] <= mouse < self._screenPgUp[1]:
self.setValue(self._value + self._pageStep)
elif self._screenScroller[0] <= mouse < self._screenScroller[1]:
self._mouseDelta = mouse-self._screenScroller[0]
self._draggable = True
else:
return False
self.sliderMoved.emit(self._value)
return True
def mouseDragEvent(self, evt):
if not self._draggable: return False
if self._orientation == TTkK.VERTICAL:
size=self._height
mouse = max(0,min(size,evt.y))
else:
size=self._width
mouse = max(0,min(size,evt.x))
aa = mouse-self._mouseDelta
size2 = size-2
asciiStep = self._screenScroller[1] - self._screenScroller[0]
asciiDrawingSize = size2 - asciiStep
a = aa * (self._maximum - self._minimum) // asciiDrawingSize
self.setValue(max(self._minimum,min(self._maximum,a+self._minimum)))
self.sliderMoved.emit(self._value)
# TTkLog.debug(f"m={mouse}, md:{self._mouseDelta}, aa:{aa}")
return True
def focusInEvent(self):
self.update()
def focusOutEvent(self):
self.update()
[docs]
@pyTTkSlot(int)
def setSingleStep(self, v):
'''setSingleStep'''
self._singleStep = v
self.update()
[docs]
@pyTTkSlot(int)
def setPageStep(self, pageStep):
'''setPageStep'''
self._pageStep = pageStep
self.update()
[docs]
@pyTTkSlot(int, int)
def setRange(self, min, max):
'''setRange'''
if self._minimum == min and \
self._maximum == max :
return
self._minimum = min
self._maximum = max
self.rangeChanged.emit(min, max)
[docs]
def setMinimum(self, v):
if v == self._minimum:
return
if v > self._maximum:
v = self._maximum
self._minimum = v
self.update()
[docs]
def setMaximum(self, v):
if v == self._maximum:
return
if v < self._minimum:
v = self._minimum
self._maximum = v
self.update()
[docs]
@pyTTkSlot(int)
def setValue(self, v):
v = max(self._minimum,min(self._maximum,v))
if self._value == v: return
self._value = v
self.valueChanged.emit(v)
self.update()
[docs]
def setFocusColor(self, color):
if self._focusColor != color:
self._focusColor = color
self.update()