# -*- coding: utf-8 -*-
#
# Copyright Université Rennes 1 / INSERM
# Contributor: Raphael Weber
#
# Under CeCILL license
# http://www.cecill.info
"""
Module with sub-classes and functions for GUI creation with PyQt5
See https://pypi.org/project/PyQt5 and
https://doc.qt.io/qt-5/reference-overview.html
"""
from PyQt5 import QtWidgets, QtGui, QtCore
from sys import flags, argv
# *************************************************************************** #
# *************************************************************************** #
# ************************* QtWidgets subclasses **************************** #
# *************************************************************************** #
# *************************************************************************** #
[docs]class ComboBox(QtWidgets.QComboBox):
"""
Subclass of QtWidgets.QComboBox so that key press events are ignored
"""
[docs] def keyPressEvent(self, ev):
"""
Re-implemented so that key press events are ignored
"""
pass
# *************************************************************************** #
# *************************************************************************** #
# ******************************* Functions ********************************* #
# *************************************************************************** #
# *************************************************************************** #
[docs]def initializeDisplay():
"""
Creates a GUI application for display
:returns: instance of QtCore.QCoreApplication or QtWidgets.QApplication
"""
app = QtCore.QCoreApplication.instance()
if app is None:
app = QtWidgets.QApplication(argv)
return app
[docs]def infiniteLoopDisplay(app):
"""
Creates an infinite loop for a given GUI application, so that the window
does not disappear right after being created
:param app: instance of QtCore.QCoreApplication or QtWidgets.QApplication
"""
if (flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'):
app.instance().exec_()
[docs]def addSpinBoxTable(grid, pos, nb_rows, nb_cols, name="", params={}):
"""
Adds a table of spin boxes to an instance of QtWidgets.QGridLayout
The spin boxes are contained in an instance of QtWidgets.QGroupBox filled
by an instance of QtWidgets.QGridLayout, see func:`.ToolsPyQt.addGroupBox`.
:param grid: parent layout where the spin boxes are added
:type grid: QtWidgets.QGridLayout
:param pos: position of the group box containing the spin boxes in the
parent layout, length 2 ``(row, col)`` or 4
``(row, col, rowspan, colspan)``
:type pos: tuple of integers
:param nb_rows: number of rows in the table of spin boxes
:type nb_rows: int
:param nb_cols: number of columns in the table of spin boxes
:type nb_cols: int
:param name: name of the group box containing the spin boxes
:type name: str
:param params: keyword arguments of the constructor of QtWidgets.QSpinBox
(e.g. {"minimum": 0, "maximum": 255}), it might be a nested list of
length ``nb_rows`` x ``nb_cols`` if the parameters differ from on spin
box to another
:type params: dict or list
:returns:
- **grid_sub** -- instance of QtWidgets.QGridLayout containing the
table of spin boxes
- **goup_box** -- instance of QtWidgets.QGroupBox containing the table
of spin boxes
"""
# create group box
grid_sub, group_box = addGroupBox(grid, pos, name)
# add spin boxes in the group box
for i in range(nb_rows):
for j in range(nb_cols):
if isinstance(params, list):
spin_box = QtWidgets.QSpinBox(**params[i][j])
else:
spin_box = QtWidgets.QSpinBox(**params)
grid_sub.addWidget(spin_box, i, j)
return grid_sub, group_box
[docs]def setSpinBoxTable(grid, value_list_list, flag_display):
"""
Sets the values of a table of spin boxes from an input list of lists
or returns the values of a table of spin boxes
:param grid: layout containing only the table of spin boxes, see
:func:`.ToolsPyQt.addSpinBoxTable` for creating one
:type grid: QtWidgets.QGridLayout
:param value_list_list: in case of ``flag_display`` set to ``True``, each
element is a list of values for setting the table of spin boxes --
otherwise, let it be an empty list
:type value_list_list: list
:param flag_display: specify if displaying values in the table of spin
boxes
:type flag_display: bool
:returns: each element is a list containing the values in the table of spin
boxes (same as input argument ``value_list_list`` in case of
``flag_display`` set to ``True``)
:rtype: list
"""
# if setting value_list_list, reset its value
if not flag_display:
value_list_list = []
# get number of columns in the grid
nb_cols = grid.columnCount()
# get number of rows in the grid
nb_rows = grid.rowCount()
# loop on the number of rows
for i in range(nb_rows):
# initialize temporary list for setting value_list_list
list_tmp = []
# loop on the number of columns
for j in range(nb_cols):
# get spin box
spin_box = grid.itemAtPosition(i, j).widget()
# if setting display, check length of value_list_list
if flag_display:
if i < len(value_list_list):
# set spin box value
spin_box.setValue(value_list_list[i][j])
else:
# set spin box value
spin_box.setValue(0)
# if setting value_list_list
else:
# append temporary list
list_tmp.append(spin_box.value())
if not flag_display:
value_list_list.append(list_tmp)
return value_list_list
[docs]def addLineEditList(grid, pos, nb_rows, name=""):
"""
Adds a list of line edits to a layout
The line edits are contained in an instance of QtWidgets.QGroupBox filled
by an instance of QtWidgets.QGridLayout, see
:func:`.ToolsPyQt.addGroupBox`.
:param grid: parent layout where the line edits are added
:type grid: QtWidgets.QGridLayout
:param pos: position of the group box containing the spin boxes in the
parent layout, length 2 ``(row, col)`` or 4
``(row, col, rowspan, colspan)``
:type pos: tuple of integers
:param nb_rows: number of rows in the list of line edits
:type nb_rows: int
:param name: name of the group box containing the spin boxes
:type name: str
:returns:
- **grid_sub** -- instance of QtWidgets.QGridLayout containing the
instances of QLineEdit
- **goup_box** -- instance of QtWidgets.QGroupBox containing the
instances of QLineEdit
"""
# create group box
grid_sub, group_box = addGroupBox(grid, pos, name)
# add spin boxes in the group box
for i in range(nb_rows):
edit = QtWidgets.QLineEdit()
grid_sub.addWidget(edit, i, 0)
return grid_sub, group_box
[docs]def setLineEditList(grid, value_list, mode):
"""
Sets the values of a list of QLineEdit from a list of lists (mode 0) or
sets the values of a list of lists from the values of a list of QLineEdit
(mode 1)
:param grid: layout containing only the edit lines
:type grid: QtWidgets.QGridLayout
:param value_list: in case of mode 0, each element is a value for setting
the corresponding line edit -- in case of mode 1, let it be an empty
list
:type value_list: list
:param mode: either 0 or 1
:type mode: int
:returns: list of values contained in the list of QLineEdit
(same as input argument ``value_list`` in case of mode 0)
:rtype: list
"""
# check mode
if mode == 0 or mode == 1:
# if setting self.general_dict, reset its value
if mode == 1:
value_list = []
# get number of rows in the grid <=> number of items in the list
nb_rows = grid.count()
# loop on the number of rows
for i in range(nb_rows):
# get edit text
edit = grid.itemAtPosition(i, 0).widget()
if mode == 0:
if i < len(value_list):
# set edit text
edit.setText(value_list[i])
else:
# set edit text
edit.setText("")
elif mode == 1:
# append value list
value_list.append(edit.text())
return value_list
[docs]def addGroupBox(layout, position, title=""):
"""
Adds a group box to a grid layout
Another grid layout is created to fill the group box.
The function raises an error if the length of ``position`` is not 2 or 4.
:param layout: parent layout where the group box is added
:type layout: QtWidgets.QGridLayout
:param position: position of the group box in the parent layout, length 2
``(row, col)`` or 4 ``(row, col, rowspan, colspan)``
:type position: tuple of integers
:param title: group box title
:type title: str
:returns:
- **grid** (*QtWidgets.QGridLayout*) -- layout filling the group box
- **group_box** (*QtWidgets.QGroupBox*)
"""
# create the group box
group_box = QtWidgets.QGroupBox(title)
# create grid layout and set it to the group box
grid = QtWidgets.QGridLayout(group_box)
# add the group box to the layout
addWidgetToLayout(layout, group_box, position)
return grid, group_box
[docs]def addCheckBox(layout, position, text, flag_checked=False, color=None):
"""
Adds a check box to a grid layout
The method raises an error if the length of position is not 2 or 4.
:param layout: parent layout where the check box is added
:type layout: QtWidgets.QGridLayout
:param position: position of the check box in the parent layout, length 2
``(row, col)`` or 4 ``(row, col, rowspan, colspan)``
:type position: tuple of integers
:param text: text displayed next to the check box
:type text: str
:param flag_checked: specify if check box is initially checked
:type flag_checked: bool
:param color: color in RGB format
:type color: tuple
:returns: check box item
:rtype: QtWidgets.QCheckBox
"""
# create radio button
check_box = QtWidgets.QCheckBox(text)
# set color
if color is not None:
check_box.setStyleSheet("QCheckBox { color: rgb(%d,%d,%d) }" % color)
# set checked
if flag_checked:
check_box.setChecked(True)
# add radio button to the layout
addWidgetToLayout(layout, check_box, position)
return check_box
[docs]def createWindow(
size=(0, 0), title=None, bg_color=(240, 240, 240), flag_show=True
):
"""
Creates a window
The geometry of the window can be updated afterward, see
http://doc.qt.io/qt-4.8/application-windows.html#window-geometry for
details.
If pyqtgraph items are to be added in the window, the background color
won't apply to them. To get the same background color for pyqtgraph items,
the following line must be added:
``pyqtgraph.setConfigOption('background',color)``.
:param size: size of the window in pixels, length 2 ``(width, height)``,
set one value to 0 in order to have the window maximized in the
corresponding direction
:type size: tuple
:param title: window title
:type title: str
:param bg_color: background color of the window, either (RGB) or (RGBA)
:type bg_color: tuple or list
:param flag_show: specify if the window must be displayed
:type flag_show: bool
:returns:
- **win** (*QtWidgets.QWidget*) -- window container
- **layout** (*QtWidgets.QGridLayout*) -- layout filling the window
"""
# create the window and the layout to fill it
win = QtWidgets.QWidget()
layout = QtWidgets.QGridLayout(win)
# set the background color
if len(bg_color) == 3:
color = QtGui.QColor(bg_color[0], bg_color[1], bg_color[2])
elif len(bg_color) == 4:
color = QtGui.QColor(bg_color[0], bg_color[1], bg_color[2], bg_color[3])
palette = QtGui.QPalette()
palette.setColor(QtGui.QPalette.Window, color)
win.setPalette(palette)
win.setAutoFillBackground(True)
# set the window title
if isinstance(title, str):
win.setWindowTitle(title)
if 0 in size:
# set the window size when minimized
screen_resolution = QtWidgets.QApplication.desktop().screenGeometry()
win.resize(
screen_resolution.width() - 80, screen_resolution.height() - 200
)
# maximize window by default
win.showMaximized()
else:
win.resize(size[0], size[1])
# show the window
if flag_show:
win.show()
return win, layout
[docs]def addComboBox(
lay, widget_position, item_list, box_title=None,
flag_enable_key_interaction=True
):
"""
Creates a group box with a combo box and adds it to a grid layout
:param lay: parent layout where the group box is added
:type lay: QtWidgets.QGridLayout
:param widget_position: position of the group box in the parent layout,
length 2 ``(row, col)`` or 4 ``(row, col, rowspan, colspan)``
:type widget_position: tuple of integers
:param item_list: items of the combo box, each element is a string
:type item_list: list
:param box_title: title of the group box containing the combo box
:type box_title: str
:returns:
- **grid** (*QtWidgets.QGridLayout*) -- layout filling the group box
- **group_box** (*QtWidgets.QGroupBox*) -- widget added to the parent
layout
- **combo_box** (:class:`.ToolsPyQt.ComboBox` or *QtWidgets.QComboBox*)
- combo box item
"""
# create combo box
if flag_enable_key_interaction:
combo_box = QtWidgets.QComboBox()
else:
combo_box = ComboBox()
# add items
combo_box.addItems(item_list)
# check if group box must be created
if box_title is not None:
# create group box
grid, group_box = addGroupBox(lay, widget_position, box_title)
# add combo box to group box
grid.addWidget(combo_box, 0, 0)
else:
grid, group_box = None, None
# add combo box to layout
addWidgetToLayout(lay, combo_box, widget_position)
return grid, group_box, combo_box
[docs]def setStyleSheet(app, font_name, font_size, font_color, qobj_list):
"""
Sets the style sheet for a list of Qt classes in a QApplication
:param app: GUI application in which the style sheet must be set
:type app: QtCore.QCoreApplication or QtWidgets.QApplication
:param font_name: font name
:type font_name: str
:param font_size: size of the font in pt
:type font_size: int or float
:param font_color: color (RGB) of the font
:type font_color: tuple or list
:param qobj_list: each element is a string of the Qt class on which the
style sheet is applied
:type qobj_list: list
"""
# get style sheet string
style_string = \
"""color: rgb(%d,%d,%d); font: %s; font-size: %dpt""" % (font_color[0],
font_color[1],
font_color[2],
font_name,
font_size)
style_sheet_string = ""
for qobj in qobj_list:
style_sheet_string += "%s {%s} " % (qobj, style_string)
# set style sheet
app.setStyleSheet(style_sheet_string)