Source code for soma.qt_gui.rangeSlider

# -*- coding: utf-8 -*-
# ---------------------------------------------------------------------------------------------
# Copyright (c) 2011-2012, Ryan Galloway (ryan@rsgalloway.com)
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
#  - Redistributions of source code must retain the above copyright notice, this
#    list of conditions and the following disclaimer.
#
#  - Redistributions in binary form must reproduce the above copyright notice,
#    this list of conditions and the following disclaimer in the documentation
#    and/or other materials provided with the distribution.
#
#  - Neither the name of the software nor the names of its contributors
#    may be used to endorse or promote products derived from this software
#    without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
# ---------------------------------------------------------------------------------------------
# docs and latest version available for download at
#   http://rsgalloway.github.com/qrangeslider
# ------------------------------------------------------------------------

from __future__ import absolute_import
__author__ = "Ryan Galloway <ryan@rsgalloway.com>"
__version__ = "0.1"

# ---------------------------------------------------------------------------------------------
# SUMMARY
# ------------------------------------------------------------------------
"""
The QRangeSlider class implements a horizontal range slider widget.
"""

# ---------------------------------------------------------------------------------------------
# TODO
# ------------------------------------------------------------------------
"""
  - smoother mouse move event handler
  - support splits and joins
  - verticle sliders
  - ticks
"""

# ---------------------------------------------------------------------------------------------
# IMPORTS
# ------------------------------------------------------------------------
import os
import sys
from soma.qt_gui.qt_backend import QtCore, QtGui

try:
    _fromUtf8 = QtCore.QString.fromUtf8
except AttributeError:
    _fromUtf8 = lambda s: s

__all__ = ['QRangeSlider']

DEFAULT_CSS = """
QRangeSlider * {
    border: 0px;
    padding: 0px;
}
QRangeSlider #Head {
    background: #222;
}
QRangeSlider #Span {
    background: #393;
}
QRangeSlider #Span:active {
    background: #282;
}
QRangeSlider #Tail {
    background: #222;
}
QRangeSlider > QSplitter::handle {
    background: #393;
}
QRangeSlider > QSplitter::handle:vertical {
    height: 4px;
}
QRangeSlider > QSplitter::handle:pressed {
    background: #ca5;
}
"""

userLevel = 99


class Ui_Form(object):

    """default range slider form"""

    def setupUi(self, Form):
        Form.setObjectName(_fromUtf8("QRangeSlider"))
        Form.resize(300, 30)
        Form.setStyleSheet(_fromUtf8(DEFAULT_CSS))
        self.gridLayout = QtGui.QGridLayout(Form)
        self.gridLayout.setContentsMargins(0, 0, 0, 0)
        self.gridLayout.setSpacing(0)
        self.gridLayout.setObjectName(_fromUtf8("gridLayout"))
        self._splitter = QtGui.QSplitter(Form)
        self._splitter.setMinimumSize(QtCore.QSize(0, 0))
        self._splitter.setMaximumSize(QtCore.QSize(16777215, 16777215))
        self._splitter.setOrientation(QtCore.Qt.Horizontal)
        self._splitter.setObjectName(_fromUtf8("splitter"))
        self._head = QtGui.QGroupBox(self._splitter)
        self._head.setTitle(_fromUtf8(""))
        self._head.setObjectName(_fromUtf8("Head"))
        self._handle = QtGui.QGroupBox(self._splitter)
        self._handle.setTitle(_fromUtf8(""))
        self._handle.setObjectName(_fromUtf8("Span"))
        self._tail = QtGui.QGroupBox(self._splitter)
        self._tail.setTitle(_fromUtf8(""))
        self._tail.setObjectName(_fromUtf8("Tail"))
        self.gridLayout.addWidget(self._splitter, 0, 0, 1, 1)

        self.retranslateUi(Form)
        QtCore.QMetaObject.connectSlotsByName(Form)

    def retranslateUi(self, Form):
        Form.setWindowTitle(QtGui.QApplication.translate(
            "QRangeSlider", "QRangeSlider", None))


class Element(QtGui.QGroupBox):

    def __init__(self, parent, main):
        super(Element, self).__init__(parent)
        self.main = main

    def setStyleSheet(self, style):
        """redirect style to parent groupbox"""
        self.parent().setStyleSheet(style)

    def textColor(self):
        """text paint color"""
        return getattr(self, '__textColor', QtGui.QColor(125, 125, 125))

    def setTextColor(self, color):
        """set the text paint color"""
        if type(color) == tuple and len(color) == 3:
            color = QtGui.QColor(color[0], color[1], color[2])
        elif type(color) == int:
            color = QtGui.QColor(color, color, color)
        setattr(self, '__textColor', color)

    def paintEvent(self, event):
        """overrides paint event to handle text"""
        qp = QtGui.QPainter()
        qp.begin(self)
        if self.main.drawValues():
            self.drawText(event, qp)
        qp.end()


class Head(Element):

    """area before the handle"""

    def __init__(self, parent, main):
        super(Head, self).__init__(parent, main)

    def drawText(self, event, qp):
        qp.setPen(self.textColor())
        qp.setFont(QtGui.QFont('Arial', 10))


class Tail(Element):

    """area after the handle"""

    def __init__(self, parent, main):
        super(Tail, self).__init__(parent, main)

    def drawText(self, event, qp):
        qp.setPen(self.textColor())
        qp.setFont(QtGui.QFont('Arial', 10))


class Handle(Element):

    """handle area"""

    def __init__(self, parent, main):
        super(Handle, self).__init__(parent, main)

    def drawText(self, event, qp):
        qp.setPen(self.textColor())
        qp.setFont(QtGui.QFont('Arial', 10))

    def mouseMoveEvent(self, event):
        event.accept()
        mx = event.globalX()
        _mx = getattr(self, '__mx', None)

        if not _mx:
            setattr(self, '__mx', mx)
            dx = 0
        else:
            dx = mx - _mx

        setattr(self, '__mx', mx)

        if dx == 0:
            event.ignore()
            return
        elif dx > 0:
            dx = 1
        elif dx < 0:
            dx = -1

        s = self.main.start() + dx
        e = self.main.end() + dx
        if s >= self.main.min() and e <= self.main.max():
            self.main._movingHandle = True
            self.main.setRange(s, e)
            self.main._movingHandle = False


[docs]class QRangeSlider(QtGui.QWidget, Ui_Form): """ The QRangeSlider class implements a horizontal range slider widget. Inherits QWidget. Methods * __init__ (self, QWidget parent = None) * bool drawValues (self) * int end (self) * (int, int) getRange (self) * int max (self) * int min (self) * int start (self) * setBackgroundStyle (self, QString styleSheet) * setDrawValues (self, bool draw) * setEnd (self, int end) * setStart (self, int start) * setRange (self, int start, int end) * setSpanStyle (self, QString styleSheet) Signals * endValueChanged (int) * maxValueChanged (int) * minValueChanged (int) * startValueChanged (int) * rangeChanged (int, int) Customizing QRangeSlider You can style the range slider as below: .. code-block:: css QRangeSlider * { border: 0px; padding: 0px; } QRangeSlider #Head { background: #222; } QRangeSlider #Span { background: #393; } QRangeSlider #Span:active { background: #282; } QRangeSlider #Tail { background: #222; } Styling the range slider handles follows QSplitter options: .. code-block:: css QRangeSlider > QSplitter::handle { background: #393; } QRangeSlider > QSplitter::handle:vertical { height: 4px; } QRangeSlider > QSplitter::handle:pressed { background: #ca5; } """ # define splitter indices _SPLIT_START = 1 _SPLIT_END = 2 startValueChanged = QtCore.Signal(int) endValueChanged = QtCore.Signal(int) maxValueChanged = QtCore.Signal(int) minValueChanged = QtCore.Signal(int) startValueChanged = QtCore.Signal(int) rangeChanged = QtCore.Signal(int, int) def __init__(self, parent=None): """ Create a new QRangeSlider instance. :param parent: QWidget parent :return: New QRangeSlider instance. """ QtGui.QWidget.__init__(self, parent) self.setupUi(self) self.setMouseTracking(False) self._splitter.splitterMoved.connect(self._handleMoveSplitter) # head layout self._head_layout = QtGui.QHBoxLayout() self._head_layout.setSpacing(0) self._head_layout.setContentsMargins(0, 0, 0, 0) self._head.setLayout(self._head_layout) self.head = Head(self._head, main=self) self._head_layout.addWidget(self.head) # handle layout self._handle_layout = QtGui.QHBoxLayout() self._handle_layout.setSpacing(0) self._handle_layout.setContentsMargins(0, 0, 0, 0) self._handle.setLayout(self._handle_layout) self.handle = Handle(self._handle, main=self) self.handle.setTextColor((150, 255, 150)) self._handle_layout.addWidget(self.handle) # tail layout self._tail_layout = QtGui.QHBoxLayout() self._tail_layout.setSpacing(0) self._tail_layout.setContentsMargins(0, 0, 0, 0) self._tail.setLayout(self._tail_layout) self.tail = Tail(self._tail, main=self) self._tail_layout.addWidget(self.tail) # defaults self.setMin(0) self.setMax(99) self.setStart(0) self.setEnd(99) self.setDrawValues(True) self._movingHandle = False
[docs] def min(self): """:return: minimum value""" return getattr(self, '__min', None)
[docs] def max(self): """:return: maximum value""" return getattr(self, '__max', None)
[docs] def setMin(self, value): """sets minimum value""" assert type(value) is int setattr(self, '__min', value) self.minValueChanged.emit(value)
[docs] def setMax(self, value): """sets maximum value""" assert type(value) is int setattr(self, '__max', value) self.maxValueChanged.emit(value)
[docs] def start(self): """:return: range slider start value""" return getattr(self, '__start', None)
[docs] def end(self): """:return: range slider end value""" return getattr(self, '__end', None)
def _setStart(self, value): """stores the start value only""" setattr(self, '__start', value) self.startValueChanged.emit(value)
[docs] def setStart(self, value): """sets the range slider start value""" assert type(value) is int v = self._valueToPos(value) self._splitter.moveSplitter(v, self._SPLIT_START) self._setStart(value)
def _setEnd(self, value): """stores the end value only""" setattr(self, '__end', value) self.endValueChanged.emit(value)
[docs] def setEnd(self, value): """set the range slider end value""" assert type(value) is int v = self._valueToPos(value) self._splitter.moveSplitter(v, self._SPLIT_END) self._setEnd(value)
[docs] def drawValues(self): """:return: True if slider values will be drawn""" return getattr(self, '__drawValues', None)
[docs] def setDrawValues(self, draw): """sets draw values boolean to draw slider values""" assert type(draw) is bool setattr(self, '__drawValues', draw)
[docs] def getRange(self): """:return: the start and end values as a tuple""" return (self.start(), self.end())
[docs] def setRange(self, start, end): """set the start and end values""" self.setStart(start) self.setEnd(end) self.rangeChanged.emit(start, end)
[docs] def keyPressEvent(self, event): """overrides key press event to move range left and right""" key = event.key() if key == QtCore.Qt.Key_Left: s = self.start() - 1 e = self.end() - 1 elif key == QtCore.Qt.Key_Right: s = self.start() + 1 e = self.end() + 1 else: event.ignore() return event.accept() if s >= self.min() and e <= self.max(): self.setRange(s, e)
[docs] def setBackgroundStyle(self, style): """sets background style""" self._tail.setStyleSheet(style) self._head.setStyleSheet(style)
[docs] def setSpanStyle(self, style): """sets range span handle style""" self._handle.setStyleSheet(style)
def _valueToPos(self, value): """converts slider value to local pixel x coord""" return int(self.width() * (float(value) / self.max())) def _posToValue(self, xpos): """converts local pixel x coord to slider value""" return int(((xpos + self._splitter.handleWidth()) / float(self.width())) * self.max()) def _handleMoveSplitter(self, xpos, index): """private method for handling moving splitter handles""" hw = self._splitter.handleWidth() def _lockWidth(widget): width = widget.size().width() widget.setMinimumWidth(width) widget.setMaximumWidth(width) def _unlockWidth(widget): widget.setMinimumWidth(0) widget.setMaximumWidth(16777215) v = self._posToValue(xpos) if index == self._SPLIT_START: _lockWidth(self._tail) if self.end() and v >= self.end(): return offset = -20 w = xpos + offset self._setStart(v) elif index == self._SPLIT_END: _lockWidth(self._head) if v <= self.start(): return offset = -40 w = self.width() - xpos + offset self._setEnd(v) _unlockWidth(self._tail) _unlockWidth(self._head) _unlockWidth(self._handle)
if __name__ == '__main__': app = QtGui.QApplication(sys.argv) rs = QRangeSlider() rs.show() rs.setRange(15, 35) rs.setBackgroundStyle( 'background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #222, stop:1 #333);') rs.handle.setStyleSheet( 'background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #282, stop:1 #393);') app.exec_()