diff options
Diffstat (limited to 'silx/gui/widgets/RangeSlider.py')
-rw-r--r-- | silx/gui/widgets/RangeSlider.py | 627 |
1 files changed, 0 insertions, 627 deletions
diff --git a/silx/gui/widgets/RangeSlider.py b/silx/gui/widgets/RangeSlider.py deleted file mode 100644 index 0b72e71..0000000 --- a/silx/gui/widgets/RangeSlider.py +++ /dev/null @@ -1,627 +0,0 @@ -# coding: utf-8 -# /*########################################################################## -# -# Copyright (c) 2015-2018 European Synchrotron Radiation Facility -# -# 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. -# -# ###########################################################################*/ -"""This module provides a :class:`RangeSlider` widget. - -.. image:: img/RangeSlider.png - :align: center -""" -from __future__ import absolute_import, division - -__authors__ = ["D. Naudet", "T. Vincent"] -__license__ = "MIT" -__date__ = "02/08/2018" - - -import numpy as numpy - -from silx.gui import qt, icons, colors -from silx.gui.utils.image import convertArrayToQImage - - -class RangeSlider(qt.QWidget): - """Range slider with 2 thumbs and an optional colored groove. - - The position of the slider thumbs can be retrieved either as values - in the slider range or as a number of steps or pixels. - - :param QWidget parent: See QWidget - """ - - _SLIDER_WIDTH = 10 - """Width of the slider rectangle""" - - _PIXMAP_VOFFSET = 7 - """Vertical groove pixmap offset""" - - sigRangeChanged = qt.Signal(float, float) - """Signal emitted when the value range has changed. - - It provides the new range (min, max). - """ - - sigValueChanged = qt.Signal(float, float) - """Signal emitted when the value of the sliders has changed. - - It provides the slider values (first, second). - """ - - sigPositionCountChanged = qt.Signal(object) - """This signal is emitted when the number of steps has changed. - - It provides the new position count. - """ - - sigPositionChanged = qt.Signal(int, int) - """Signal emitted when the position of the sliders has changed. - - It provides the slider positions in steps or pixels (first, second). - """ - - def __init__(self, parent=None): - self.__pixmap = None - self.__positionCount = None - self.__firstValue = 0. - self.__secondValue = 1. - self.__minValue = 0. - self.__maxValue = 1. - - self.__focus = None - self.__moving = None - - self.__icons = { - 'first': icons.getQIcon('previous'), - 'second': icons.getQIcon('next') - } - - # call the super constructor AFTER defining all members that - # are used in the "paint" method - super(RangeSlider, self).__init__(parent) - - self.setFocusPolicy(qt.Qt.ClickFocus) - - self.setMinimumSize(qt.QSize(50, 20)) - self.setMaximumHeight(20) - - # Broadcast value changed signal - self.sigValueChanged.connect(self.__emitPositionChanged) - - # Position <-> Value conversion - - def __positionToValue(self, position): - """Returns value corresponding to position - - :param int position: - :rtype: float - """ - min_, max_ = self.getMinimum(), self.getMaximum() - maxPos = self.__getCurrentPositionCount() - 1 - return min_ + (max_ - min_) * int(position) / maxPos - - def __valueToPosition(self, value): - """Returns closest position corresponding to value - - :param float value: - :rtype: int - """ - min_, max_ = self.getMinimum(), self.getMaximum() - maxPos = self.__getCurrentPositionCount() - 1 - return int(0.5 + maxPos * (float(value) - min_) / (max_ - min_)) - - # Position (int) API - - def __getCurrentPositionCount(self): - """Return current count (either position count or widget width - - :rtype: int - """ - count = self.getPositionCount() - if count is not None: - return count - else: - return max(2, self.width() - self._SLIDER_WIDTH) - - def getPositionCount(self): - """Returns the number of positions. - - :rtype: Union[int,None]""" - return self.__positionCount - - def setPositionCount(self, count): - """Set the number of positions. - - Slider values are eventually adjusted. - - :param Union[int,None] count: - Either the number of possible positions or - None to allow any values. - :raise ValueError: If count <= 1 - """ - count = None if count is None else int(count) - if count != self.getPositionCount(): - if count is not None and count <= 1: - raise ValueError("Position count must be higher than 1") - self.__positionCount = count - emit = self.__setValues(*self.getValues()) - self.sigPositionCountChanged.emit(count) - if emit: - self.sigValueChanged.emit(*self.getValues()) - - def getFirstPosition(self): - """Returns first slider position - - :rtype: int - """ - return self.__valueToPosition(self.getFirstValue()) - - def setFirstPosition(self, position): - """Set the position of the first slider - - The position is adjusted to valid values - - :param int position: - """ - self.setFirstValue(self.__positionToValue(position)) - - def getSecondPosition(self): - """Returns second slider position - - :rtype: int - """ - return self.__valueToPosition(self.getSecondValue()) - - def setSecondPosition(self, position): - """Set the position of the second slider - - The position is adjusted to valid values - - :param int position: - """ - self.setSecondValue(self.__positionToValue(position)) - - def getPositions(self): - """Returns slider positions (first, second) - - :rtype: List[int] - """ - return self.getFirstPosition(), self.getSecondPosition() - - def setPositions(self, first, second): - """Set the position of both sliders at once - - First is clipped to the slider range: [0, max]. - Second is clipped to valid values: [first, max] - - :param int first: - :param int second: - """ - self.setValues(self.__positionToValue(first), - self.__positionToValue(second)) - - # Value (float) API - - def __emitPositionChanged(self, *args, **kwargs): - self.sigPositionChanged.emit(*self.getPositions()) - - def __rangeChanged(self): - """Handle change of value range""" - emit = self.__setValues(*self.getValues()) - self.sigRangeChanged.emit(*self.getRange()) - if emit: - self.sigValueChanged.emit(*self.getValues()) - - def getMinimum(self): - """Returns the minimum value of the slider range - - :rtype: float - """ - return self.__minValue - - def setMinimum(self, minimum): - """Set the minimum value of the slider range. - - It eventually adjusts maximum. - Slider positions remains unchanged and slider values are modified. - - :param float minimum: - """ - minimum = float(minimum) - if minimum != self.getMinimum(): - if minimum > self.getMaximum(): - self.__maxValue = minimum - self.__minValue = minimum - self.__rangeChanged() - - def getMaximum(self): - """Returns the maximum value of the slider range - - :rtype: float - """ - return self.__maxValue - - def setMaximum(self, maximum): - """Set the maximum value of the slider range - - It eventually adjusts minimum. - Slider positions remains unchanged and slider values are modified. - - :param float maximum: - """ - maximum = float(maximum) - if maximum != self.getMaximum(): - if maximum < self.getMinimum(): - self.__minValue = maximum - self.__maxValue = maximum - self.__rangeChanged() - - def getRange(self): - """Returns the range of values (min, max) - - :rtype: List[float] - """ - return self.getMinimum(), self.getMaximum() - - def setRange(self, minimum, maximum): - """Set the range of values. - - If maximum is lower than minimum, minimum is the only valid value. - Slider positions remains unchanged and slider values are modified. - - :param float minimum: - :param float maximum: - """ - minimum, maximum = float(minimum), float(maximum) - if minimum != self.getMinimum() or maximum != self.getMaximum(): - self.__minValue = minimum - self.__maxValue = max(maximum, minimum) - self.__rangeChanged() - - def getFirstValue(self): - """Returns the value of the first slider - - :rtype: float - """ - return self.__firstValue - - def __clipFirstValue(self, value, max_=None): - """Clip first value to range and steps - - :param float value: - :param float max_: Alternative maximum to use - """ - if max_ is None: - max_ = self.getSecondValue() - value = min(max(self.getMinimum(), float(value)), max_) - if self.getPositionCount() is not None: # Clip to steps - value = self.__positionToValue(self.__valueToPosition(value)) - return value - - def setFirstValue(self, value): - """Set the value of the first slider - - Value is clipped to valid values. - - :param float value: - """ - value = self.__clipFirstValue(value) - if value != self.getFirstValue(): - self.__firstValue = value - self.update() - self.sigValueChanged.emit(*self.getValues()) - - def getSecondValue(self): - """Returns the value of the second slider - - :rtype: float - """ - return self.__secondValue - - def __clipSecondValue(self, value): - """Clip second value to range and steps - - :param float value: - """ - value = min(max(self.getFirstValue(), float(value)), self.getMaximum()) - if self.getPositionCount() is not None: # Clip to steps - value = self.__positionToValue(self.__valueToPosition(value)) - return value - - def setSecondValue(self, value): - """Set the value of the second slider - - Value is clipped to valid values. - - :param float value: - """ - value = self.__clipSecondValue(value) - if value != self.getSecondValue(): - self.__secondValue = value - self.update() - self.sigValueChanged.emit(*self.getValues()) - - def getValues(self): - """Returns value of both sliders at once - - :return: (first value, second value) - :rtype: List[float] - """ - return self.getFirstValue(), self.getSecondValue() - - def setValues(self, first, second): - """Set values for both sliders at once - - First is clipped to the slider range: [minimum, maximum]. - Second is clipped to valid values: [first, maximum] - - :param float first: - :param float second: - """ - if self.__setValues(first, second): - self.sigValueChanged.emit(*self.getValues()) - - def __setValues(self, first, second): - """Set values for both sliders at once - - First is clipped to the slider range: [minimum, maximum]. - Second is clipped to valid values: [first, maximum] - - :param float first: - :param float second: - :return: True if values has changed, False otherwise - :rtype: bool - """ - first = self.__clipFirstValue(first, self.getMaximum()) - second = self.__clipSecondValue(second) - values = first, second - - if self.getValues() != values: - self.__firstValue, self.__secondValue = values - self.update() - return True - return False - - # Groove API - - def getGroovePixmap(self): - """Returns the pixmap displayed in the slider groove if any. - - :rtype: Union[QPixmap,None] - """ - return self.__pixmap - - def setGroovePixmap(self, pixmap): - """Set the pixmap displayed in the slider groove. - - :param Union[QPixmap,None] pixmap: The QPixmap to use or None to unset. - """ - assert pixmap is None or isinstance(pixmap, qt.QPixmap) - self.__pixmap = pixmap - self.update() - - def setGroovePixmapFromProfile(self, profile, colormap=None): - """Set the pixmap displayed in the slider groove from histogram values. - - :param Union[numpy.ndarray,None] profile: - 1D array of values to display - :param Union[Colormap,str] colormap: - The colormap name or object to convert profile values to colors - """ - if profile is None: - self.setSliderPixmap(None) - return - - profile = numpy.array(profile, copy=False) - - if profile.size == 0: - self.setSliderPixmap(None) - return - - if colormap is None: - colormap = colors.Colormap() - elif isinstance(colormap, str): - colormap = colors.Colormap(name=colormap) - assert isinstance(colormap, colors.Colormap) - - rgbImage = colormap.applyToData(profile.reshape(1, -1))[:, :, :3] - qimage = convertArrayToQImage(rgbImage) - qpixmap = qt.QPixmap.fromImage(qimage) - self.setGroovePixmap(qpixmap) - - # Handle interaction - - def mousePressEvent(self, event): - super(RangeSlider, self).mousePressEvent(event) - - if event.buttons() == qt.Qt.LeftButton: - picked = None - for name in ('first', 'second'): - area = self.__sliderRect(name) - if area.contains(event.pos()): - picked = name - break - - self.__moving = picked - self.__focus = picked - self.update() - - def mouseMoveEvent(self, event): - super(RangeSlider, self).mouseMoveEvent(event) - - if self.__moving is not None: - position = self.__xPixelToPosition(event.pos().x()) - if self.__moving == 'first': - self.setFirstPosition(position) - else: - self.setSecondPosition(position) - - def mouseReleaseEvent(self, event): - super(RangeSlider, self).mouseReleaseEvent(event) - - if event.button() == qt.Qt.LeftButton and self.__moving is not None: - self.__moving = None - self.update() - - def focusOutEvent(self, event): - if self.__focus is not None: - self.__focus = None - self.update() - super(RangeSlider, self).focusOutEvent(event) - - def keyPressEvent(self, event): - key = event.key() - if event.modifiers() == qt.Qt.NoModifier and self.__focus is not None: - if key in (qt.Qt.Key_Left, qt.Qt.Key_Down): - if self.__focus == 'first': - self.setFirstPosition(self.getFirstPosition() - 1) - else: - self.setSecondPosition(self.getSecondPosition() - 1) - return # accept event - elif key in (qt.Qt.Key_Right, qt.Qt.Key_Up): - if self.__focus == 'first': - self.setFirstPosition(self.getFirstPosition() + 1) - else: - self.setSecondPosition(self.getSecondPosition() + 1) - return # accept event - - super(RangeSlider, self).keyPressEvent(event) - - # Handle resize - - def resizeEvent(self, event): - super(RangeSlider, self).resizeEvent(event) - - # If no step, signal position update when width change - if (self.getPositionCount() is None and - event.size().width() != event.oldSize().width()): - self.sigPositionChanged.emit(*self.getPositions()) - - # Handle repaint - - def __xPixelToPosition(self, x): - """Convert position in pixel to slider position - - :param int x: X in pixel coordinates - :rtype: int - """ - sliderArea = self.__sliderAreaRect() - maxPos = self.__getCurrentPositionCount() - 1 - position = maxPos * (x - sliderArea.left()) / (sliderArea.width() - 1) - return int(position + 0.5) - - def __sliderRect(self, name): - """Returns rectangle corresponding to slider in pixels - - :param str name: 'first' or 'second' - :rtype: QRect - :raise ValueError: If wrong name - """ - assert name in ('first', 'second') - if name == 'first': - offset = - self._SLIDER_WIDTH - position = self.getFirstPosition() - elif name == 'second': - offset = 0 - position = self.getSecondPosition() - else: - raise ValueError('Unknown name') - - sliderArea = self.__sliderAreaRect() - - maxPos = self.__getCurrentPositionCount() - 1 - xOffset = int((sliderArea.width() - 1) * position / maxPos) - xPos = sliderArea.left() + xOffset + offset - - return qt.QRect(xPos, - sliderArea.top(), - self._SLIDER_WIDTH, - sliderArea.height()) - - def __drawArea(self): - return self.rect().adjusted(self._SLIDER_WIDTH, 0, - -self._SLIDER_WIDTH, 0) - - def __sliderAreaRect(self): - return self.__drawArea().adjusted(self._SLIDER_WIDTH / 2., - 0, - -self._SLIDER_WIDTH / 2., - 0) - - def __pixMapRect(self): - return self.__sliderAreaRect().adjusted(0, - self._PIXMAP_VOFFSET, - 0, - -self._PIXMAP_VOFFSET) - - def paintEvent(self, event): - painter = qt.QPainter(self) - - style = qt.QApplication.style() - - area = self.__drawArea() - pixmapRect = self.__pixMapRect() - - option = qt.QStyleOptionProgressBar() - option.initFrom(self) - option.rect = area - option.state = ((self.isEnabled() and qt.QStyle.State_Enabled) - or qt.QStyle.State_None) - style.drawControl(qt.QStyle.CE_ProgressBarGroove, - option, - painter, - self) - - painter.save() - pen = painter.pen() - pen.setWidth(1) - pen.setColor(qt.Qt.black if self.isEnabled() else qt.Qt.gray) - painter.setPen(pen) - painter.drawRect(pixmapRect.adjusted(-1, -1, 1, 1)) - painter.restore() - - if self.isEnabled() and self.__pixmap is not None: - painter.drawPixmap(area.adjusted(self._SLIDER_WIDTH / 2, - self._PIXMAP_VOFFSET, - -self._SLIDER_WIDTH / 2 + 1, - -self._PIXMAP_VOFFSET + 1), - self.__pixmap, - self.__pixmap.rect()) - - for name in ('first', 'second'): - rect = self.__sliderRect(name) - option = qt.QStyleOptionButton() - option.initFrom(self) - option.icon = self.__icons[name] - option.iconSize = rect.size() * 0.7 - if option.state & qt.QStyle.State_MouseOver: - option.state ^= qt.QStyle.State_MouseOver - if self.__focus == name: - option.state |= qt.QStyle.State_HasFocus - elif option.state & qt.QStyle.State_HasFocus: - option.state ^= qt.QStyle.State_HasFocus - option.rect = rect - style.drawControl( - qt.QStyle.CE_PushButton, option, painter, self) - - def sizeHint(self): - return qt.QSize(200, self.minimumHeight()) |