Repository URL to install this package:
Version:
5.3.0 ▾
|
# (C) Copyright 2005-2022 Enthought, Inc., Austin, TX
# All rights reserved.
#
# This software is provided without warranty under the terms of the BSD
# license included in LICENSE.txt and may be redistributed only under
# the conditions described in the aforementioned license. The license
# is also available online at http://www.enthought.com/licenses/BSD.txt
#
# Thanks for using Enthought open source!
"""
Define a standard horizontal and vertical Enable scrollbar component that wraps
the standard Qt one.
"""
from pyface.qt import QtCore, QtGui
from traits.api import Any, Bool, Enum, Float, Int, Property
from enable.component import Component
from enable.enable_traits import ScrollBarRange, ScrollPosition
class QResizableScrollBar(QtGui.QScrollBar):
resized = QtCore.Signal()
def resizeEvent(self, event):
super().resizeEvent(event)
self.resized.emit()
class NativeScrollBar(Component):
"An Enable scrollbar component that wraps/embeds the native Qt scrollbar"
# ------------------------------------------------------------------------
# Public Traits
# ------------------------------------------------------------------------
# The current position of the scroll bar. This must be within the range
# (self.low, self.high)
scroll_position = ScrollPosition()
# A tuple (low, high, page_size, line_size). Can be accessed using
# convenience properties (see below).
range = ScrollBarRange((0.0, 100.0, 10.0, 1.0))
# The orientation of the scrollbar
orientation = Enum("horizontal", "vertical")
# Is y=0 at the top or bottom?
origin = Enum("bottom", "top")
# Determines if the scroll bar should be visible and respond to events
enabled = Bool(True)
# The scroll increment associated with a single mouse wheel increment
mouse_wheel_speed = Int(3)
# Expose scroll_position, low, high, page_size as properties
low = Property
high = Property
page_size = Property
line_size = Property
# This represents the state of the mouse button on the scrollbar thumb.
# External classes can monitor this to detect when the user starts and
# finishes interacting with this scrollbar via the scrollbar thumb.
mouse_thumb = Enum("up", "down")
# ------------------------------------------------------------------------
# Private Traits
# ------------------------------------------------------------------------
_control = Any
_clean = Bool(False)
_last_widget_x = Float(0)
_last_widget_y = Float(0)
_last_widget_height = Float(0)
_last_widget_width = Float(0)
# Indicates whether or not the widget needs to be re-drawn after being
# repositioned and resized
_widget_moved = Bool(True)
# Set to True if something else has updated the scroll position and
# the widget needs to redraw. This is not set to True if the widget
# gets updated via user mouse interaction, since Qt is then responsible
# for updating the scrollbar.
_scroll_updated = Bool(True)
def destroy(self):
""" Destroy the native widget associated with this component.
"""
if self._control is not None:
self._control.hide()
self._control.deleteLater()
self._control = None
def __del__(self):
# Pray that we do not participate in a cycle.
self.destroy()
def _get_abs_coords(self, x, y):
return self.container.get_absolute_coords(x, y)
def _draw_mainlayer(self, gc, view_bounds=None, mode="default"):
x_pos, y_pos = self.position
x_size, y_size = map(int, self.bounds)
qt_xpos, qt_ypos = self._get_abs_coords(x_pos, y_pos + y_size - 1)
# We have to do this flip_y business because Qt and enable use opposite
# coordinate systems, and enable defines the component's position as
# its lower left corner, while Qt defines it as the upper left corner.
window = self.window
if window is None:
return
qt_ypos = window._flip_y(qt_ypos)
qt_xpos = int(qt_xpos)
qt_ypos = int(qt_ypos)
if not self._control:
self._create_control(window, self.range, self.scroll_position)
if self._widget_moved:
if (self._last_widget_x != qt_xpos
or self._last_widget_y != qt_ypos):
self._control.move(qt_xpos, qt_ypos)
controlsize = self._control.size()
if x_size != controlsize.width() or y_size != controlsize.height():
self._control.resize(x_size, y_size)
if self._scroll_updated:
self._update_control(self.range, self.scroll_position)
# self._control.raise_()
self._last_widget_x = qt_xpos
self._last_widget_y = qt_ypos
self._last_widget_width = x_size
self._last_widget_height = y_size
self._scroll_updated = False
self._widget_moved = False
def _create_control(self, window, enable_range, value):
qt_orientation = {
'horizontal': QtCore.Qt.Horizontal,
'vertical': QtCore.Qt.Vertical
}[self.orientation]
self._control = QResizableScrollBar(qt_orientation, window.control)
self._update_control(enable_range, value)
self._control.valueChanged.connect(self._update_enable_pos)
self._control.sliderPressed.connect(self._on_slider_pressed)
self._control.sliderReleased.connect(self._on_slider_released)
self._control.resized.connect(self._control_resized)
self._control.destroyed.connect(self._on_destroyed)
self._control.setVisible(True)
def _update_control(self, enable_range, value):
minimum, maximum, page_size, line_size = enable_range
# The maximum value of a QScrollBar is the maximum position of the
# scroll bar, not the document length. We need to subtract the length
# of the scroll bar itself.
max_value = maximum - page_size
# invert values for vertical ranges because of coordinate system issues
value = self._correct_value(value, minimum, max_value)
self._control.setMinimum(minimum)
self._control.setMaximum(max_value)
self._control.setValue(value)
self._control.setPageStep(page_size)
self._control.setSingleStep(line_size)
def _correct_value(self, value, min_value, max_value):
""" Correct vertical position values for Qt and Enable conventions
Enable expects vertical scroll_position to be measured with origin at
the bottom and positive going upwards, while Qt scrollbar values are
measured with origin at the top and positive going down.
Parameters
----------
value : float
The position value in either Enable or Qt conventions.
max_value : float
The maximum value that the Qt scrollbar can be set to (height of
the scrolled component, less the page size).
"""
if self.orientation != "vertical":
return value
return max_value - (value - min_value)
# ------------------------------------------------------------------------
# Qt Event handlers
# ------------------------------------------------------------------------
def _update_enable_pos(self, value):
# invert values for vertical ranges because of coordinate system issues
value = self._correct_value(
value, self.low, self.high - self.page_size
)
self.scroll_position = value
def _on_slider_pressed(self):
self.mouse_thumb = "down"
def _on_slider_released(self):
self.mouse_thumb = "up"
def _control_resized(self):
self._widget_moved = True
self.request_redraw()
def _on_destroyed(self):
self._control = None
# ------------------------------------------------------------------------
# Basic trait event handlers
# ------------------------------------------------------------------------
def _range_changed(self):
low, high, page_size, line_size = self.range
self.scroll_position = max(
min(self.scroll_position, high - page_size), low
)
self._scroll_updated = True
self.request_redraw()
def _range_items_changed(self):
self._range_changed()
def _mouse_wheel_changed(self, event):
# FIXME: convert to Qt.
event.handled = True
self.scroll_position += (
event.mouse_wheel * self.range[3] * self.mouse_wheel_speed
)
def _scroll_position_changed(self):
self._scroll_updated = True
self.request_redraw()
def _bounds_changed(self, old, new):
super()._bounds_changed(old, new)
self._widget_moved = True
self.request_redraw()
def _bounds_items_changed(self, event):
super()._bounds_items_changed(event)
self._widget_moved = True
self.request_redraw()
def _position_changed(self, old, new):
super()._position_changed(old, new)
self._widget_moved = True
self.request_redraw()
def _position_items_changed(self, event):
super()._position_items_changed(event)
self._widget_moved = True
self.request_redraw()
# ------------------------------------------------------------------------
# Property getters and setters
# ------------------------------------------------------------------------
def _get_low(self):
return self.range[0]
def _set_low(self, low):
ignore, high, page_size, line_size = self.range
self._clean = False
self.range = (low, high, page_size, line_size)
def _get_high(self):
return self.range[1]
def _set_high(self, high):
low, ignore, page_size, line_size = self.range
self._clean = False
self.range = (low, high, page_size, line_size)
def _get_page_size(self):
return self.range[2]
def _set_page_size(self, page_size):
low, high, ignore, line_size = self.range
self._clean = False
self.range = (low, high, page_size, line_size)
def _get_line_size(self):
return self.range[3]
def _set_line_size(self, line_size):
low, high, page_size, ignore = self.range
self._clean = False
self.range = (low, high, page_size, line_size)