Aligning QGridLayouts - python-3.x

I'm trying to create a 'table' where the headers and row labels remain visible. Kind of like the Frozen Column Example, but using a QGridLayout.
My approach is to put three QGridLayouts into aligned QScrollAreas, where the main table contains RxC rows and columns and is scrollable in any direction, and the column and row headers are contained in 1xC and Rx1 cells, respectively. Then I can just get the different QScrollAreas to track each other.
I've got this working (though I haven't touched the matched-scrolling bit yet), but I'm having a lot of trouble getting the three different QGridLayouts to have the same size cells (in width or height).
Part of the problem is that the table cells can be variously sized, meaning that each row and column is not the same height/width as all of the others. But I also can't figure out how to get the heights/widths of the rows of the different QGridLayouts to match.
As you can see in the image above, there are three rows and two columns. I want the Row Headers to line up with the three rows, and the Column Headers to line up with the two columns.
Here's a minimal example. There's a lot still for me to figure out and get working, but this first step is an important one.
import PyQt5.QtGui
import PyQt5.QtWidgets
import PyQt5.QtCore
class MainBuyerWindow(PyQt5.QtWidgets.QMainWindow):
def __init__(self, *args, **kwargs):
super(PyQt5.QtWidgets.QMainWindow, self).__init__(*args, **kwargs)
(
columns_widget, rows_widget, table_widget,
columns_layout, rows_layout, table_layout,
) = self.setup_table()
self.columns_scroll_widget = PyQt5.QtWidgets.QScrollArea()
self.rows_scroll_widget = PyQt5.QtWidgets.QScrollArea()
self.table_scroll_widget = PyQt5.QtWidgets.QScrollArea()
self.columns_scroll_widget.setWidget(columns_widget)
self.rows_scroll_widget.setWidget(rows_widget)
self.table_scroll_widget.setWidget(table_widget)
central_layout = PyQt5.QtWidgets.QGridLayout()
central_layout.addWidget(self.columns_scroll_widget, 0, 1, 1, 5)
central_layout.addWidget( self.rows_scroll_widget, 1, 0, 5, 1)
central_layout.addWidget( self.table_scroll_widget, 1, 1, 5, 5)
central_widget = PyQt5.QtWidgets.QWidget()
central_widget.setLayout(central_layout)
self.setCentralWidget(central_widget)
self.show()
def setup_table(self):
columns_layout = PyQt5.QtWidgets.QGridLayout()
rows_layout = PyQt5.QtWidgets.QGridLayout()
table_layout = PyQt5.QtWidgets.QGridLayout()
columns_widget = PyQt5.QtWidgets.QWidget()
columns_widget.setLayout(columns_layout)
rows_widget = PyQt5.QtWidgets.QWidget()
rows_widget.setLayout(rows_layout)
table_widget = PyQt5.QtWidgets.QWidget()
table_widget.setLayout(table_layout)
table_layout.addWidget(PyQt5.QtWidgets.QLabel("This cell\nis a\ntall one"), 0, 0)
table_layout.addWidget(PyQt5.QtWidgets.QLabel("This cell is shorter"), 1, 0)
table_layout.addWidget(PyQt5.QtWidgets.QLabel("This cell is of\nmedium height"), 2, 0)
table_layout.addWidget(PyQt5.QtWidgets.QLabel("Also notice that widths of columns are not all the same"), 0, 1)
table_layout.addWidget(PyQt5.QtWidgets.QLabel("Though that doesn't matter"), 1, 1)
table_layout.addWidget(PyQt5.QtWidgets.QLabel("(In this example anyway)"), 2, 1)
columns_layout.addWidget(PyQt5.QtWidgets.QLabel("Column Header 1"), 0, 0)
columns_layout.addWidget(PyQt5.QtWidgets.QLabel("Column Header 2"), 0, 1)
rows_layout.addWidget(PyQt5.QtWidgets.QLabel("Row Header 1"), 0, 0)
rows_layout.addWidget(PyQt5.QtWidgets.QLabel("Row Header 2"), 1, 0)
rows_layout.addWidget(PyQt5.QtWidgets.QLabel("Row Header 3"), 2, 0)
return (
columns_widget, rows_widget, table_widget,
columns_layout, rows_layout, table_layout,
)
######################################################################
if __name__ == "__main__":
app = PyQt5.QtWidgets.QApplication([])
window = MainBuyerWindow()
app.exec_()
I think my main problem is I don't understand Qt well enough, so I'm not sure what to look at. What I tried was calling the show() (or activate()) method to get the geometries all worked out, and then going through the first row and column of the table_layout to get their dimensions using cellRect, and then calling columnMinimumWidth and rowMinimumHeight on the corresponding row/column of rows_layout and columns_layout.
This is the same idea used in this (almost identical) question, but when I implement it here it also doesn't seem to work. (Essentially just adding the following lines)
table_layout.activate()
for i in range(table_layout.columnCount()):
w = table_layout.columnMinimumWidth(i)
columns_layout.setColumnMinimumWidth(i, w)
for i in range(table_layout.rowCount()):
h = table_layout.rowMinimumHeight(i)
rows_layout.setRowMinimumHeight(i, h)
Can someone help me out here? Thank you so much!

I would set a fixed height to each row header label based on the height of that row in the table (and similarly a fixed width to each column header label based on the width of that column in the table), which fixes the layout alignment but also makes it simple to match up the scrolling.
class MainBuyerWindow(PyQt5.QtWidgets.QMainWindow):
def __init__(self, *args, **kwargs):
super(PyQt5.QtWidgets.QMainWindow, self).__init__(*args, **kwargs)
(
columns_widget, rows_widget, table_widget,
columns_layout, rows_layout, table_layout,
) = self.setup_table()
self.columns_scroll_widget = PyQt5.QtWidgets.QScrollArea()
self.rows_scroll_widget = PyQt5.QtWidgets.QScrollArea()
self.table_scroll_widget = PyQt5.QtWidgets.QScrollArea()
self.columns_scroll_widget.setWidget(columns_widget)
self.rows_scroll_widget.setWidget(rows_widget)
self.table_scroll_widget.setWidget(table_widget)
for i in range(rows_layout.count()):
label = rows_layout.itemAt(i).widget()
label.setFixedHeight(table_layout.cellRect(i, 0).height())
for i in range(columns_layout.count()):
label = columns_layout.itemAt(i).widget()
label.setFixedWidth(table_layout.cellRect(0, i).width())
...
Use QScrollArea.setWidgetResizable(True) so the scroll area can resize the widget as needed, and allow the header labels to take up the required amount of space to be aligned with the table.
...
self.columns_scroll_widget.setWidgetResizable(True)
self.rows_scroll_widget.setWidgetResizable(True)
self.table_scroll_widget.setWidgetResizable(True)
# Size policies to keep the row and column headers minimal/sufficient
# and allow table_scroll_widget to get as much space as possible when resized
self.columns_scroll_widget.setSizePolicy(PyQt5.QtWidgets.QSizePolicy.Preferred, PyQt5.QtWidgets.QSizePolicy.Minimum)
self.rows_scroll_widget.setSizePolicy(PyQt5.QtWidgets.QSizePolicy.Minimum, PyQt5.QtWidgets.QSizePolicy.Preferred)
...
QScrollBar inherits from QAbstractSlider so you can connect its valueChanged signal to the setValue slot of another scroll bar. These values will be consistent across the layouts now since their row heights and column widths are equal.
...
# User scrolling in table will cause headers to scroll
self.table_scroll_widget.horizontalScrollBar().valueChanged[int].connect(self.columns_scroll_widget.horizontalScrollBar().setValue)
self.table_scroll_widget.verticalScrollBar().valueChanged[int].connect(self.rows_scroll_widget.verticalScrollBar().setValue)
# User scrolling on headers will cause table to scroll
self.columns_scroll_widget.horizontalScrollBar().valueChanged[int].connect(self.table_scroll_widget.horizontalScrollBar().setValue)
self.rows_scroll_widget.verticalScrollBar().valueChanged[int].connect(self.table_scroll_widget.verticalScrollBar().setValue)
central_layout = PyQt5.QtWidgets.QGridLayout()
central_layout.addWidget(self.columns_scroll_widget, 0, 1, 1, 5)
central_layout.addWidget( self.rows_scroll_widget, 1, 0, 5, 1)
central_layout.addWidget( self.table_scroll_widget, 1, 1, 5, 5)
central_widget = PyQt5.QtWidgets.QWidget()
central_widget.setLayout(central_layout)
self.setCentralWidget(central_widget)
self.show()
Outcome:

Related

Make one y-axis label bold in matplotlib

Goodmorning,
Question, I've got this script that creates a horizontal bar chart (see image)
I would like to have one label in the y-axis bold "Nederland".
I've searched an tried a lot, but I really have no idea how I can do this.
I found this solution:
Matplotlib - Changing the color of a single x-axis tick label
But I could not get it to work.
Any hint to a solution would be great.
def AVG_BarChart(self, data:dict=None, graph_file:str = None, datum:str=None, countries:dict=None, watermarktext:str="energieprijzenbot.nl", prijsper:str="kWh")->bool:
plt.figure(figsize=(9, 6))
plt.xlabel(f"Prijs per {prijsper}")
plt.title(f"Gemiddelde {prijsper} inkoopprijs per land {datum}")
colors = ["#FE8000", "#EFBD76", "#FFA52B", "#FF9D3C", "#FFF858", "#FCFFCB", "#07EEB2", "#FF4179","#E05B4B", "#E09336", "#DAB552", "#DBD9A6", "#87B49C", "#4B8A7E", "#A5DD96", "#E1F3C9", "#0095AD", "#00D5E5", "#82E9F0", "#C0ED42", "#FFE301", "#FFF352", "#FF85DA", "#FF69B3","#A15AC4", "#3F7539", "#B8CBAD", "#E1E2C2", "#F84040", "#9D1E29"]
random.shuffle(colors)
values = 2 ** np.random.randint(2, 10, len(data))
max_value = values.max()
labels = list(data.keys())
values = list(data.values())
height = 0.9
plt.barh(y=labels, width=values, height=height, color=colors, align='center', alpha=0.8)
ax = plt.gca()
ax.xaxis.set_major_formatter('€ {x:n}')
plt.bar_label(ax.containers[0], labels=[f'€ {x:n}' for x in ax.containers[0].datavalues], label_type="edge", padding=-50)
ax.text(0.5, 0.5, watermarktext, transform=ax.transAxes,
fontsize=40, color='gray', alpha=0.3,
ha='center', va='center', rotation='30')
for i, (label, value) in enumerate(zip(labels, values)):
country_iso = self.get_key(val=label, my_dict=countries).lower()
self.offset_image(x=value, y=i, flag=country_iso, bar_is_too_short=value < max_value / 10, ax=ax)
plt.subplots_adjust(left=0.15)
plt.savefig(graph_file, bbox_inches='tight', width = 0.4)
return True
I tried looping thru the labels like this
i = 0
for w in ax.get_yticklabels():
country = ax.get_yticklabels()[i].get_text()
if country == "Nederland":
ax.get_yticklabels()[i].set_color('red')
ax.get_yticklabels()[i].set_fontweight('bold')
i += 1
When debugging I actually get a country name back, but when running the script normal, all country labels are empty...
So, I was close to the answer. But somehow I got back empty .get_text() string.
# ... some code
labels = list(data.keys())
# ... more code
ax.set_yticklabels(labels)
for lab in ax.get_yticklabels():
if lab.get_text() == "Nederland":
lab.set_fontweight('bold')
I just hope by setting the labels again, It does not mix up anything :-)

matplotlib set stacked bar chart labels

I am trying to create a stacked bar chart that groups data by operating system. I'm having trouble creating an individual label for each component in each bar.
What I'm trying to do is different from the example in the docs because in my data each category appears in only one bar, whereas in the example each bar contains one member of each category.
Currently I have this code
plt.cla()
plt.clf()
plt.close()
def get_cmap(n, name='hsv'):
'''Returns a function that maps each index in 0, 1, ..., n-1 to a distinct
RGB color; the keyword argument name must be a standard mpl colormap name.'''
return plt.cm.get_cmap(name, n)
fig = plt.figure(figsize=(18, 10), dpi=80)
# group by the prefixes for now
prefixes = []
indices = []
bars = []
legend = {}
cmap = get_cmap(len(os_counts.index) + 1)
k = 0
for i, prefix in enumerate(d):
indices.append(i)
if len(d[prefix]["names"]) == 1:
prefixes.append(d[prefix]["names"][0])
else:
prefixes.append(prefix)
#colors = [next(cycol) for j in range(len(d[prefix]["names"]))]
colors = [cmap(k + j) for j in range(len(d[prefix]["names"]))]
k += len(colors)
bar = plt.bar([i] * len(d[prefix]["names"]), d[prefix]["values"], color=colors, label=d[prefix]["names"])
bars.append(bar)
plt.xticks(rotation=90)
plt.ylabel("Frequency")
plt.xlabel("Operating System")
plt.xticks(indices, prefixes)
plt.legend()
plt.show()
Which produces this result. As you can see, the legend is created for the first colour within the bar and shows an array.
I think that each call to plt.bar gets one label. So, you are giving it a list as a label for each plt.bar call. If you want a label for every color, representing every operating system then I think the solution is to call plt.bar once for each color or os.

Table split Overlap Page Header in Reportlab

I am creating pdf using Reportlab and python 3.when a table split automatically it overlap my page header.In first page no issue.From second page onwards header gets overlapped by data. Any solution?
There are probably a few different ways to solve this problem with ReportLab. Frames can be a used to achieve the results you are looking for. The example below draws the same header on each page and restricts the table to the frame. Additionally, it uses the repeatRows table parameter to repeat the table header on each page.
The showBoundary parameter was used to show the location and size of the frame. Delete this argument or set to 0 to remove boundary lines.
The solution was developed based on this answer about text and footers.
from reportlab.platypus import BaseDocTemplate
from reportlab.platypus import Frame, PageTemplate
from reportlab.platypus import Paragraph, Table, TableStyle
from reportlab.lib.styles import getSampleStyleSheet
from reportlab.lib.pagesizes import letter
from reportlab.lib.units import inch
from reportlab.lib import colors
styles = getSampleStyleSheet()
styleN = styles['Normal']
styleH = styles['Heading1']
def header(canvas, pdf):
# Draw heading
heading = Paragraph("Heading", styleH)
heading.wrap(pdf.width, inch * 0.5)
heading.drawOn(canvas, pdf.leftMargin, pdf.height + inch)
# Draw subheading.
subheading = Paragraph("subeading", styleN)
subheading.wrap(pdf.width, inch * 0.25)
subheading.drawOn(canvas, pdf.leftMargin, pdf.height + inch * 0.75)
pdf = BaseDocTemplate('example.pdf', pagesize=letter)
frame = Frame(
pdf.leftMargin,
pdf.bottomMargin,
pdf.width,
pdf.height - inch * 0.5,
showBoundary=1) # Delete to remove line around the frame.
template = PageTemplate(id='all_pages', frames=frame, onPage=header)
pdf.addPageTemplates([template])
# Data for table
data = [["String", "String", "Number", "Number", "Number"]]
data.extend([["one", "two", i, i, i] for i in range(90)])
# Styles for table
table_style = TableStyle([
('LINEBELOW', (0, 0), (-1, 0), 2, colors.black),
('ALIGN', (2, 0), (4, -1), 'RIGHT'),
])
# Create table and repeat row 1 at every split.
table = Table(data, repeatRows=1, style=table_style)
pdf.build([table])

Creating unique comboboxes in tabs of a notebook using tkinter and python 3

I am trying to create a program which will hold the sweep values of an RF directional coupler.
I have 5 couplers and each coupler has 4 forward ports.
I have managed to get the 5 couplers on seperate tabs of a tk notebook and loading the stored data from excel for a single port. the problem comes when i want to make the UI reload data for a different port using a combobox.
The code i have at the moment is:
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
Notebook_Tabbed = ttk.Notebook(self)
VHF_1, VHF_2 = ttk.Frame(Notebook_Tabbed), ttk.Frame(Notebook_Tabbed)
Notebook_Tabbed.grid(column = 2, row = 2, columnspan = 5, sticky = "nwes")
Notebook_Tabbed.add(VHF_1, text = ' VHF 1 '), Notebook_Tabbed.add(VHF_2, text = ' VHF 2 ')
# Dictionaries to hold each couplers variables
vhf_1_dict = {1 : [2, 7, 4, 5], 3 : [2, 7, 9, 10]}
vhf_2_dict = {1 : [9, 14, 4, 5], 3 : [9, 14, 9, 10]}
# Dictionary to hold above dictionaries
var_dict = {VHF_1 : vhf_1_dict, VHF_2 : vhf_2_dict}
def populate_table_tab(self, tab, Row_Start, Row_End, Column_Start,
Column_End, Coupler_Type, port):
print(tab, Row_Start, Row_End, Column_Start,
Column_End, Coupler_Type, port)
# Load data for chosen port
def change_port(self, tab, port):
populate_table_tab(self, tab, var_dict[tab][port][0], var_dict[tab][port][1],
var_dict[tab][port][2], var_dict[tab][port][3], 'VHF', port)
# Generate comboboxes for each tab of the notebook
ports = StringVar()
ports.set(1)
def generate_port_selections(self, tab, row_num):
port_selection = ttk.Combobox(tab, textvariable = ports, state = 'readonly',
justify = "center")
port_selection['values'] = (1, 3)
port_selection.grid(column = 2, row = row_num, sticky = "nwes")
port_selection.bind('<<ComboboxSelected>>',
lambda _: change_port(self, tab, int(ports.get())))
# _ denotes a throwaway argument required to make lambda work with a combobox event
# Generate port selection comboboxes on each tab
for tab, row_num in {VHF_1 : 4, VHF_2 : 11}.items():
generate_port_selections(self, tab, row_num)
This code does work but it changes the combobox on all of the other tabs at the same time, without changing the data loaded from excel.
I have tried: ('<<ComboboxSelected>>', lambda i = i: change_port(self, tab, port_selection_list[i].get())) but TypeError: list indices must be integers or slices, not Event occurs.
Any help would be greatly appreciated, also i posted this in a rush as i'm working and busy so if i have missed out any necessary code / information please ask.
Edit: Formatted code to be readable by making single lines into multiple.
Edit 2: Simplified code.
Edit 3: You should now be able to run the code within a blank container and frame from this code.
You have problem because you create only one StringVar (ports = StringVar()) and you use it with all Combobox( textvariable=ports ) so they share the same information.
You have to use ports = StringVar() inside generate_port_selections() to create new unique ports for new Combobox.

How to list only the visible items in QTableView in pyqt

I have below code to get filters for QTableView. But i am not able to filter multiple column at a time.
i.e., if filter column 2 with row 0 col 0 and try to filter column 2, it should show only the visible unique values of column 2 (probably it should show row 0 col 1 only) but now its showing all the elements of column 2 (row 0 col 1, row 1 col 1, row 2 col 1)
#-*- coding:utf-8 -*-
from PyQt4 import QtCore, QtGui
class myWindow(QtGui.QMainWindow):
def __init__(self, parent=None):
super(myWindow, self).__init__(parent)
self.centralwidget = QtGui.QWidget(self)
self.lineEdit = QtGui.QLineEdit(self.centralwidget)
self.view = QtGui.QTableView(self.centralwidget)
self.comboBox = QtGui.QComboBox(self.centralwidget)
self.label = QtGui.QLabel(self.centralwidget)
self.gridLayout = QtGui.QGridLayout(self.centralwidget)
self.gridLayout.addWidget(self.lineEdit, 0, 1, 1, 1)
self.gridLayout.addWidget(self.view, 1, 0, 1, 3)
self.gridLayout.addWidget(self.comboBox, 0, 2, 1, 1)
self.gridLayout.addWidget(self.label, 0, 0, 1, 1)
self.setCentralWidget(self.centralwidget)
self.label.setText("Regex Filter")
self.model = QtGui.QStandardItemModel(self)
for rowName in range(3) * 5:
self.model.invisibleRootItem().appendRow(
[ QtGui.QStandardItem("row {0} col {1}".format(rowName, column))
for column in range(3)
]
)
self.proxy = QtGui.QSortFilterProxyModel(self)
self.proxy.setSourceModel(self.model)
self.view.setModel(self.proxy)
self.comboBox.addItems(["Column {0}".format(x) for x in range(self.model.columnCount())])
self.lineEdit.textChanged.connect(self.on_lineEdit_textChanged)
self.comboBox.currentIndexChanged.connect(self.on_comboBox_currentIndexChanged)
self.horizontalHeader = self.view.horizontalHeader()
self.horizontalHeader.sectionClicked.connect(self.on_view_horizontalHeader_sectionClicked)
#QtCore.pyqtSlot(int)
def on_view_horizontalHeader_sectionClicked(self, logicalIndex):
self.logicalIndex = logicalIndex
self.menuValues = QtGui.QMenu(self)
self.signalMapper = QtCore.QSignalMapper(self)
self.comboBox.blockSignals(True)
self.comboBox.setCurrentIndex(self.logicalIndex)
self.comboBox.blockSignals(True)
valuesUnique = [ self.model.item(row, self.logicalIndex).text()
for row in range(self.model.rowCount())
]
actionAll = QtGui.QAction("All", self)
actionAll.triggered.connect(self.on_actionAll_triggered)
self.menuValues.addAction(actionAll)
self.menuValues.addSeparator()
for actionNumber, actionName in enumerate(sorted(list(set(valuesUnique)))):
action = QtGui.QAction(actionName, self)
self.signalMapper.setMapping(action, actionNumber)
action.triggered.connect(self.signalMapper.map)
self.menuValues.addAction(action)
self.signalMapper.mapped.connect(self.on_signalMapper_mapped)
headerPos = self.view.mapToGlobal(self.horizontalHeader.pos())
posY = headerPos.y() + self.horizontalHeader.height()
posX = headerPos.x() + self.horizontalHeader.sectionPosition(self.logicalIndex)
self.menuValues.exec_(QtCore.QPoint(posX, posY))
#QtCore.pyqtSlot()
def on_actionAll_triggered(self):
filterColumn = self.logicalIndex
filterString = QtCore.QRegExp( "",
QtCore.Qt.CaseInsensitive,
QtCore.QRegExp.RegExp
)
self.proxy.setFilterRegExp(filterString)
self.proxy.setFilterKeyColumn(filterColumn)
#QtCore.pyqtSlot(int)
def on_signalMapper_mapped(self, i):
stringAction = self.signalMapper.mapping(i).text()
filterColumn = self.logicalIndex
filterString = QtCore.QRegExp( stringAction,
QtCore.Qt.CaseSensitive,
QtCore.QRegExp.FixedString
)
self.proxy.setFilterRegExp(filterString)
self.proxy.setFilterKeyColumn(filterColumn)
#QtCore.pyqtSlot(str)
def on_lineEdit_textChanged(self, text):
search = QtCore.QRegExp( text,
QtCore.Qt.CaseInsensitive,
QtCore.QRegExp.RegExp
)
self.proxy.setFilterRegExp(search)
#QtCore.pyqtSlot(int)
def on_comboBox_currentIndexChanged(self, index):
self.proxy.setFilterKeyColumn(index)
if __name__ == "__main__":
import sys
app = QtGui.QApplication(sys.argv)
main = myWindow()
main.show()
main.resize(400, 600)
sys.exit(app.exec_())
When i run the above code i get the below output
When i click on Column 2 Header the filter list displayed is as below and its showing correctly (Unique values in that column)...
When i select row 0 col 1 in the displayed filter, i get the following filtered list
But again when i click on the Column 2 header for filter it shows the same list as my 1st image. All the unique items of column 2 (from model view) and not from proxyfilter. Actually it should show only row 0 col 1 as unique items in Column 2 are only row 0 col 1
It is because you are still using the source model to look up the rows. Data in the original model does not change. Only the filter proxy model reflects the filtered changes.
So you just need to modify your lookup in your (really long named) on_view_horizontalHeader_sectionClicked slot:
valuesUnique = [
self.proxy.index(row, self.logicalIndex).data().toString()
for row in xrange(self.proxy.rowCount())
]
You could also remove a few of the conversion for the unique set:
valuesUnique = set(
self.proxy.index(row, self.logicalIndex).data().toString()
for row in xrange(self.proxy.rowCount())
)
...
for actionNumber, actionName in enumerate(sorted(valuesUnique)):
...
And just a small other thing I wanted to point out. You are keeping around a couple of temp objects that don't ever get deleted. In that same slot, you create a new QMenu and QSignalMapper every time but you never clean up the old ones. Over time you are just making more and more.
For the QMenu, just make it a local variable and don't parent it to self. That way it will get cleaned up after it disappears. And for the QSignalMapper, you can just use a deleteLater call before creating a new one:
# local variable, and no parent
menuValues = QtGui.QMenu()
# delete the previous one
try:
self.signalMapper.deleteLater()
except:
pass
self.signalMapper = QtCore.QSignalMapper(self)

Resources