# -*- coding: utf-8 -*-
"""
Composed widgets used to visualize filter parameters in a vertical form layout.
.. moduleauthor:: Florian Aldehoff <samsifter@biohazardous.de>
"""
import os
from os.path import expanduser, isfile
# Qt4 imports
from PyQt4.QtGui import (
QWidget, QSlider, QHBoxLayout, QLineEdit, QPushButton, QFileDialog, QIcon,
QRadioButton, QButtonGroup, QLabel, QDoubleSpinBox
)
from PyQt4.QtCore import Qt
[docs]class SliderSpinboxCombo(QWidget):
"""
Slider coupled with Spinbox for setting numerical values between a minimum
and maximum.
"""
def __init__(self, parent=None, minimum=0.0, maximum=100.0, default=0.0,
precision=2):
"""Initialize a new instance of a SliderSpinboxCombo widget.
Parameters
----------
parent : QWidget, optional
Parent Qt4 widget this widget belongs to, defaults to None.
minimum : float, optional
Minimum value of the permitted value range, defaults to 0.
maximum : float, optional
Maximum value of the permitted value range, defaults to 100.
default : float, optional
Default used as preset value in slider and spinbox, defaults to 0.
precision : int, optional
Number of decimals for the selected value, defaults to 2
(eg. ``0.12`` for precision 2 vs. ``0.1`` for precision 1).
"""
super(SliderSpinboxCombo, self).__init__(parent)
self.minimum = minimum
self.maximum = maximum
self.default = default
if default < minimum:
self.value = minimum
elif default > maximum:
self.value = maximum
else:
self.value = default
self.precision = precision
self.slider = QSlider(self)
self.slider.setOrientation(Qt.Horizontal)
self.slider.setTickPosition(QSlider.TicksBelow)
self.slider.setObjectName("slider")
self.slider.setMinimum(0)
self.slider.setMaximum(10000)
self.slider.setSingleStep(1)
self.slider.setTickInterval(max(1000,
10000 / (self.maximum - self.minimum)))
self.slider.setValue(self.normalize(self.value))
self.spinner = QDoubleSpinBox()
self.spinner.setDecimals(self.precision)
self.spinner.setSingleStep(10 ** (-1 * self.precision))
self.spinner.setMinimum(self.minimum)
self.spinner.setMaximum(self.maximum)
self.spinner.setValue(self.value)
self.spinner.setObjectName("spinner")
self.spinner.valueChanged.connect(self.on_spinner_value_change)
self.slider.valueChanged.connect(self.on_slider_value_change)
layout = QHBoxLayout(self)
layout.setMargin(0)
layout.addWidget(self.slider)
layout.addWidget(self.spinner)
self.setLayout(layout)
[docs] def on_spinner_value_change(self, value):
"""Handle changes to the spinbox value.
Parameters
----------
value : float
New value of spinbox widget.
"""
self.value = value
self.slider.setValue(self.normalize(value))
[docs] def on_slider_value_change(self, value):
"""Handle changes to the slider value.
Parameters
----------
value : float
New value of slider widget.
"""
self.spinner.setValue(self.denormalize(value))
[docs] def normalize(self, value):
"""Translates absolute spinbox values to normalized slider values.
Parameters
----------
value : float
Current value of spinbox widget.
"""
delta = self.maximum - self.minimum
assert delta > 0
normalized = ((value - self.minimum) * (10000)) / delta
return normalized
[docs] def denormalize(self, value):
"""Translates normalized slider values to absolute spinbox values.
Parameters
----------
value : float
Current value of slider widget.
"""
delta = self.maximum - self.minimum
assert delta > 0
denormalized = ((value * delta) / (10000)) + self.minimum
return denormalized
# Getters & Setters
[docs] def get_value(self):
return self.value
[docs] def set_value(self, value):
if self.precision == 0:
self.value = int(value)
else:
self.value = value
# no need to set slider, it is coupled to spinbox
self.spinner.setValue(value)
[docs]class FileChooser(QWidget):
"""
Combined LineEdit and Pushbutton for standard file dialog to select a file.
"""
def __init__(self, parent=None, suffix_string="CSV files (*.csv *.CSV)"):
"""Initialize a new instance of a FileChooser.
Parameters
----------
parent : QWidget, optional
Parent Qt4 widget this widget belongs to, defaults to None.
suffix_string : str, optional
String specifying the default file extensions to be displayed in
the OS-specific file selection dialog. Should contain a short
file type description and a list of space-separated file extensions
with asterisks. The default value of ``CSV files (*.csv *.CSV)``
sets the dialog to display CSV files only.
"""
super(FileChooser, self).__init__(parent)
self.suffix_string = suffix_string
self.filename = None
self.lineedit = QLineEdit(self)
self.button = QPushButton(QIcon.fromTheme('document-open'),
"Select file...", self)
self.button.pressed.connect(self.show_dialog)
layout = QHBoxLayout(self)
layout.setMargin(0)
layout.addWidget(self.lineedit)
layout.addWidget(self.button)
self.setLayout(layout)
[docs] def highlight(self, boolean=True):
"""Indicates errors or pending actions by changing background color.
Parameters
----------
boolean : bool, optional
Enable highlighting of the QlineEdit widget, defaults to True.
"""
css = "QLineEdit {background-color: #ffe08c;}"
if boolean:
self.lineedit.setStyleSheet(css)
else:
self.lineedit.setStyleSheet(None)
[docs] def show_dialog(self):
"""Opens OS-specific file selection dialog in user's home directory."""
dialog = QFileDialog()
dialog.setDefaultSuffix(self.suffix_string)
fname = dialog.getOpenFileName(
self, 'Open file', expanduser("~"),
self.suffix_string + ";;All files (*)")
self.lineedit.setText(fname)
self.set_filename(fname)
# Getters & Setters
[docs] def get_suffix_string(self):
return self.suffix_string
[docs] def set_suffix_string(self, suffix_string):
self.suffix_string = suffix_string
[docs] def get_filename(self):
return self.filename
[docs] def set_filename(self, filename):
"""Sets a new filename.
Maintains cursor position and validates the input on the fly. Tooltips
and highlights of the QLineEdit widget are set to help resolving
potential errors.
Parameters
----------
filename : str
Path to existing and readable input file.
"""
self.filename = filename
# validate file
if not (isfile(filename) and os.access(filename, os.R_OK)):
self.highlight(True)
self.lineedit.setToolTip(
"file either not existing or not readable"
)
else:
self.highlight(False)
self.lineedit.setToolTip(None)
cursor = self.lineedit.cursorPosition()
self.lineedit.setText(filename)
self.lineedit.setCursorPosition(cursor)
[docs]class OptionSwitcher(QWidget):
"""
Group of coupled radio buttons to choose between exclusive options.
Defaults to a simple True or False switch but arbitrary lists of options
are supported and their options internally referenced by list indices. The
first option in a list is considered the default value.
"""
def __init__(self, parent=None, options=(True, False)):
"""Initialize a new instance of an optionSwitcher.
Parameters
----------
parent : QWidget, optional
Parent Qt4 widget this widget belongs to, defaults to None.
options : list
Arbitrary list of options to choose between. Supports any data type
like numbers, strings, etc. However the list objects have to
provide short string representations to be properly displayed in
the GUI form. Defaults to a list with the two options True or
False.
"""
super(OptionSwitcher, self).__init__(parent)
self.options = options
self.current_index = 0
self.group = QButtonGroup(self)
layout = QHBoxLayout(self)
layout.setMargin(0)
for idx, option in enumerate(self.options):
lbl = QLabel(str(option), self)
btn = QRadioButton(self)
btn.setChecked(idx == self.current_index)
self.group.addButton(btn, idx)
layout.addWidget(btn)
layout.addWidget(lbl)
self.setLayout(layout)
# Getters & Setters
[docs] def get_options(self):
return self.options
[docs] def set_options(self, options):
self.options = options
[docs] def get_current_index(self):
return self.current_index
[docs] def set_current_index(self, index):
if 0 <= index and index < len(self.options):
self.current_index = index
self.group.button(index).setChecked(True)