summaryrefslogtreecommitdiff
path: root/silx/gui/widgets/RangeSlider.py
diff options
context:
space:
mode:
Diffstat (limited to 'silx/gui/widgets/RangeSlider.py')
-rw-r--r--silx/gui/widgets/RangeSlider.py765
1 files changed, 0 insertions, 765 deletions
diff --git a/silx/gui/widgets/RangeSlider.py b/silx/gui/widgets/RangeSlider.py
deleted file mode 100644
index 31dbd4e..0000000
--- a/silx/gui/widgets/RangeSlider.py
+++ /dev/null
@@ -1,765 +0,0 @@
-# coding: utf-8
-# /*##########################################################################
-#
-# Copyright (c) 2015-2020 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__ = "26/11/2018"
-
-
-import numpy as numpy
-
-from silx.gui import qt, icons, colors
-from silx.gui.utils.image import convertArrayToQImage
-
-
-class StyleOptionRangeSlider(qt.QStyleOption):
- def __init__(self):
- super(StyleOptionRangeSlider, self).__init__()
- self.minimum = None
- self.maximum = None
- self.sliderPosition1 = None
- self.sliderPosition2 = None
- self.handlerRect1 = None
- self.handlerRect2 = None
-
-
-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.__hoverRect = qt.QRect()
- self.__hoverControl = None
-
- 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.setAttribute(qt.Qt.WA_Hover)
-
- self.setMinimumSize(qt.QSize(50, 20))
- self.setMaximumHeight(20)
-
- # Broadcast value changed signal
- self.sigValueChanged.connect(self.__emitPositionChanged)
-
- def event(self, event):
- t = event.type()
- if t == qt.QEvent.HoverEnter or t == qt.QEvent.HoverLeave or t == qt.QEvent.HoverMove:
- return self.__updateHoverControl(event.pos())
- else:
- return super(RangeSlider, self).event(event)
-
- def __updateHoverControl(self, pos):
- hoverControl, hoverRect = self.__findHoverControl(pos)
- if hoverControl != self.__hoverControl:
- self.update(self.__hoverRect)
- self.update(hoverRect)
- self.__hoverControl = hoverControl
- self.__hoverRect = hoverRect
- return True
- return hoverControl is not None
-
- def __findHoverControl(self, pos):
- """Returns the control at the position and it's rect location"""
- for name in ["first", "second"]:
- rect = self.__sliderRect(name)
- if rect.contains(pos):
- return name, rect
- rect = self.__drawArea()
- if rect.contains(pos):
- return "groove", rect
- return None, qt.QRect()
-
- # 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[~silx.gui.colors.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:
- delta = self._SLIDER_WIDTH // 2
- if self.__moving == 'first':
- position = self.__xPixelToPosition(event.pos().x() + delta)
- self.setFirstPosition(position)
- else:
- position = self.__xPixelToPosition(event.pos().x() - delta)
- 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 + 1,
- 0)
-
- def __pixMapRect(self):
- return self.__sliderAreaRect().adjusted(0,
- self._PIXMAP_VOFFSET,
- -1,
- -self._PIXMAP_VOFFSET)
-
- def paintEvent(self, event):
- painter = qt.QPainter(self)
-
- style = qt.QApplication.style()
-
- area = self.__drawArea()
- if self.__pixmap is not None:
- pixmapRect = self.__pixMapRect()
-
- option = qt.QStyleOptionProgressBar()
- option.initFrom(self)
- option.rect = area
- option.state = (qt.QStyle.State_Enabled if self.isEnabled()
- else 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, 0, 1))
- painter.restore()
-
- if self.isEnabled():
- rect = area.adjusted(self._SLIDER_WIDTH // 2,
- self._PIXMAP_VOFFSET,
- -self._SLIDER_WIDTH // 2,
- -self._PIXMAP_VOFFSET + 1)
- painter.drawPixmap(rect,
- self.__pixmap,
- self.__pixmap.rect())
- else:
- option = StyleOptionRangeSlider()
- option.initFrom(self)
- option.rect = area
- option.sliderPosition1 = self.__firstValue
- option.sliderPosition2 = self.__secondValue
- option.handlerRect1 = self.__sliderRect("first")
- option.handlerRect2 = self.__sliderRect("second")
- option.minimum = self.__minValue
- option.maximum = self.__maxValue
- option.state = (qt.QStyle.State_Enabled if self.isEnabled()
- else qt.QStyle.State_None)
- if self.__hoverControl == "groove":
- option.state |= qt.QStyle.State_MouseOver
- elif option.state & qt.QStyle.State_MouseOver:
- option.state ^= qt.QStyle.State_MouseOver
- self.drawRangeSliderBackground(painter, option, self)
-
- # Avoid glitch when moving handles
- hoverControl = self.__moving or self.__hoverControl
-
- 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 hoverControl == name:
- option.state |= qt.QStyle.State_MouseOver
- elif 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())
-
- @classmethod
- def drawRangeSliderBackground(cls, painter, option, widget):
- """Draw the background of the RangeSlider widget into the painter.
-
- :param qt.QPainter painter: A painter
- :param StyleOptionRangeSlider option: Options to draw the widget
- :param qt.QWidget: The widget which have to be drawn
- """
- painter.save()
- painter.translate(0.5, 0.5)
-
- backgroundRect = qt.QRect(option.rect)
- if backgroundRect.height() > 8:
- center = backgroundRect.center()
- backgroundRect.setHeight(8)
- backgroundRect.moveCenter(center)
-
- selectedRangeRect = qt.QRect(backgroundRect)
- selectedRangeRect.setLeft(option.handlerRect1.center().x())
- selectedRangeRect.setRight(option.handlerRect2.center().x())
-
- highlight = option.palette.color(qt.QPalette.Highlight)
- activeHighlight = highlight
- selectedOutline = option.palette.color(qt.QPalette.Highlight)
-
- buttonColor = option.palette.button().color()
- val = qt.qGray(buttonColor.rgb())
- buttonColor = buttonColor.lighter(100 + max(1, (180 - val) // 6))
- buttonColor.setHsv(buttonColor.hue(), (buttonColor.saturation() * 3) // 4, buttonColor.value())
-
- grooveColor = qt.QColor()
- grooveColor.setHsv(buttonColor.hue(),
- min(255, (int)(buttonColor.saturation())),
- min(255, (int)(buttonColor.value() * 0.9)))
-
- selectedInnerContrastLine = qt.QColor(255, 255, 255, 30)
-
- outline = option.palette.color(qt.QPalette.Background).darker(140)
- if (option.state & qt.QStyle.State_HasFocus and option.state & qt.QStyle.State_KeyboardFocusChange):
- outline = highlight.darker(125)
- if outline.value() > 160:
- outline.setHsl(highlight.hue(), highlight.saturation(), 160)
-
- # Draw background groove
- painter.setRenderHint(qt.QPainter.Antialiasing, True)
- gradient = qt.QLinearGradient()
- gradient.setStart(backgroundRect.center().x(), backgroundRect.top())
- gradient.setFinalStop(backgroundRect.center().x(), backgroundRect.bottom())
- painter.setPen(qt.QPen(outline))
- gradient.setColorAt(0, grooveColor.darker(110))
- gradient.setColorAt(1, grooveColor.lighter(110))
- painter.setBrush(gradient)
- painter.drawRoundedRect(backgroundRect.adjusted(1, 1, -2, -2), 1, 1)
-
- # Draw slider background for the value
- gradient = qt.QLinearGradient()
- gradient.setStart(selectedRangeRect.center().x(), selectedRangeRect.top())
- gradient.setFinalStop(selectedRangeRect.center().x(), selectedRangeRect.bottom())
- painter.setRenderHint(qt.QPainter.Antialiasing, True)
- painter.setPen(qt.QPen(selectedOutline))
- gradient.setColorAt(0, activeHighlight)
- gradient.setColorAt(1, activeHighlight.lighter(130))
- painter.setBrush(gradient)
- painter.drawRoundedRect(selectedRangeRect.adjusted(1, 1, -2, -2), 1, 1)
- painter.setPen(selectedInnerContrastLine)
- painter.setBrush(qt.Qt.NoBrush)
- painter.drawRoundedRect(selectedRangeRect.adjusted(2, 2, -3, -3), 1, 1)
-
- painter.restore()