Handling multiple QComboBox in one cell widget - python

Code:
class Example(QtGui.QWidget):
def __init__(self, parent):
super(Accounts, self).__init__()
self.initUI()
def initUI(self):
self.table = QtGui.QTableWidget()
self.table.setColumnCount(5)
database = 'Setting.db'
connection = sqlite3.connect(database)
cursor = connection.cursor()
cursor.execute('''
SELECT ID, User, Text
FROM Data ''')
data = cursor.fetchall()
num = 0
for row in data:
self.table.setRowCount(num+2)
self.table.setItem(num, 0, QtGui.QTableWidgetItem(str(row[0])))
self.table.setItem(num, 1, QtGui.QTableWidgetItem(str(row[1])))
self.table.setItem(num, 2, QtGui.QTableWidgetItem(str(row[2])))
self.table.setCellWidget(num, 3, Perms(self, row[0]))
save_user = QtGui.QPushButton("Save")
save_user.clicked.connect(self.save)
self.table.setCellWidget(num, 5, save_user)
num= num+1
main_layout = QtGui.QGridLayout()
main_layout.addWidget(self.table)
self.setLayout(main_layout)
def save(self):
button = self.sender()
index = self.table.indexAt(button.pos())
row = index.row()
a_id = str(self.table.item(row, 0).text())
data = Permissions(self, a_id).update_database()
class Perms(QtGui.QWidget):
def __init__(self, parent, a_id):
super(Perms, self).__init__()
self.a_id = a_id
self.init_ui()
def init_ui(self):
database = 'Settings.db'
conn = sqlite3.connect(database)
cursor = conn.cursor()
cursor.execute('''
SELECT control, sub
FROM Perms
WHERE ID= ?''', (self.a_id,))
row = cursor.fetchone()
control_label = QtGui.QLabel("Control")
sub_label = QtGui.QLabel("Sub")
self.control_options = QtGui.QComboBox()
self.control_options.addItem("Y")
self.control_options.addItem("N")
if str(row[0]) == "Y":
self.control_options.setCurrentIndex(0)
else:
self.control_options.setCurrentIndex(1)
self.sub_options = QtGui.QComboBox()
self.sub_options.addItem("Y")
self.sub_options.addItem("N")
if str(row[1]) == "Y":
self.sub_options.setCurrentIndex(0)
else:
self.sub_options.setCurrentIndex(1)
layout = QtGui.QGridLayout()
layout.addWidget(full_control_label, 1, 1)
layout.addWidget(self.full_control_options, 1, 2)
layout.addWidget(tills_label, 2, 1)
layout.addWidget(self.tills_options, 2, 2)
self.setLayout(layout)
def update_database(self):
control = str(self.control_options.currentText())
sub = str(self.sub_options.currentText())
return (control, sub)
Ignore any errors I don't specify because I wrote this quickly just for an example. The problem I'm facing is: the save button is created within the loop in order to be used multiple times. It connects to a function that gets the current text of the combo-boxes. But the problem is, if you change the current text in the combo-boxes and then click Save, it gets the text from the combo-boxes, but not the right text - you always get the same result.
For example:
QComboBox1: Y
QComboBox2: N
If we click Save, it'll return ('Y', 'N')
If we change the combobox values:
QComboBox1: N
QComboBox2: N
and click save, it'll return ('Y', 'N')
But it never returns the new values from the combo-boxes.
How can I fix it so I get the updated values rather than the old values?

The save method doesn't currently make much sense. Surely you need to get the Perm widget from the table, not make a new one every time:
def save(self):
button = self.sender()
index = self.table.indexAt(button.pos())
row = index.row()
# get the new values from the table
data = self.table.cellWidget(row, 3).update_database()

Related

How can I get values from genereated QSpinBoxes?

I set the number of spinboxes in the previous window and then, according to the code below, they are added in the next window, but when I click the button, I need to pick up the value from each generated spinbox, but at the moment the value is transmitted only from the last one.
How do I get the value from each spinbox?
Thank you!
class CreateSpuWindow(QMainWindow):
def __init__(self, value):
super().__init__()
uic.loadUi('ui/statement_spu_1.ui', self)
self.value = value # Quantity pf spinBoxes
for i in range(value):
self.spu_label = QLabel(self)
self.spu_label.setText(f"SPU №{i+1}. PP:")
self.quantity_pp = QSpinBox(self)
self.quantity_pp.setValue(1)
self.quantity_pp.setMinimum(1)
self.gridLayout_2.addWidget(self.spu_label, 2 * i, 0)
self.gridLayout_2.addWidget(self.quantity_pp, 2 * i, 1)
self.next_tu_spu_2Button.clicked.connect(self.next_wind_spu_btn)
self.back_btn.clicked.connect(self.back_btn_cl)
def next_wind_spu_btn(self):
data = []
# Get a value from spinbox
for i in str(self.quantity_pp.value()):
data.append(i)
print(data)
quantity_spu = self.value
self.ReadyTableSpuWindow = ReadyTableSpuWindow(quantity_spu)
self.ReadyTableSpuWindow.show()
self.close()
def back_btn_cl(self):
self.close()
self.QuantitySpuWindow = QuantitySpuWindow()
self.QuantitySpuWindow.show()

Endless loop on QTreeView when filtering via QSortFilterProxyModel in a QSqlRelationalTableModel

Whenever I enable a filter with QSortFilterProxyModel() and insert a new record in my QSqlRelationalTableModel() which is linked to a QTreeView I get the error:
RecursionError: maximum recursion depth exceeded
Standard case is to create a new datarecord with CTRL + N - OK.
Also filtering works - OK.
But if I set the filter and create a new record, python fails with:
RecursionError: maximum recursion depth exceeded
Backend terminated (returncode: 3)
Fatal Python error: Aborted
How to reproduce:
Set a filter, e.g. lastName to Smith.
Hit CTRL + N to create a new record.
=> Result: Python falls into endless loop until mentioned error message occurrs.
=> Expected result: The row should be created and not hit by the filter. When the filter is deleted, all rows should appear, also the newly created row.
Full working code example:
import sys
import re
from PyQt5 import QtWidgets, QtGui, QtCore, QtSql
db = QtSql.QSqlDatabase.addDatabase("QSQLITE")
db.setDatabaseName(":memory:");
modelQuery = QtSql.QSqlQueryModel()
modelTable = QtSql.QSqlRelationalTableModel()
def _human_key(key):
parts = re.split(r'(\d*\.\d+|\d+)', key)
return tuple((e.swapcase() if i % 2 == 0 else float(e))
for i, e in enumerate(parts))
class FilterHeader(QtWidgets.QHeaderView):
filterActivated = QtCore.pyqtSignal()
def __init__(self, parent):
super().__init__(QtCore.Qt.Horizontal, parent)
self._editors = []
self._padding = 4
self.setStretchLastSection(True)
self.setDefaultAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter)
self.setSortIndicatorShown(False)
self.sectionResized.connect(self.adjustPositions)
parent.horizontalScrollBar().valueChanged.connect(self.adjustPositions)
def setFilterBoxes(self, count):
while self._editors:
editor = self._editors.pop()
editor.deleteLater()
for index in range(count):
editor = QtWidgets.QLineEdit(self.parent())
editor.setPlaceholderText('Filter')
editor.setClearButtonEnabled(True)
editor.textChanged.connect(self.textChanged)
self._editors.append(editor)
self.adjustPositions()
def textChanged(self):
self.filterActivated.emit()
def sizeHint(self):
size = super().sizeHint()
if self._editors:
height = self._editors[0].sizeHint().height()
size.setHeight(size.height() + height + self._padding)
return size
def updateGeometries(self):
if self._editors:
height = self._editors[0].sizeHint().height()
self.setViewportMargins(0, 0, 0, height + self._padding)
else:
self.setViewportMargins(0, 0, 0, 0)
super().updateGeometries()
self.adjustPositions()
def adjustPositions(self):
for index, editor in enumerate(self._editors):
height = editor.sizeHint().height()
editor.move(
self.sectionPosition(index) - self.offset() + 2,
height + (self._padding // 2))
editor.resize(self.sectionSize(index), height)
def filterText(self, index):
if 0 <= index < len(self._editors):
return self._editors[index].text()
return ''
def setFilterText(self, index, text):
if 0 <= index < len(self._editors):
self._editors[index].setText(text)
def clearFilters(self):
for editor in self._editors:
editor.clear()
class HumanProxyModel(QtCore.QSortFilterProxyModel):
def lessThan(self, source_left, source_right):
data_left = source_left.data()
data_right = source_right.data()
if type(data_left) == type(data_right) == str:
return _human_key(data_left) < _human_key(data_right)
return super(HumanProxyModel, self).lessThan(source_left, source_right)
#property
def filters(self):
if not hasattr(self, "_filters"):
self._filters = []
return self._filters
#filters.setter
def filters(self, filters):
self._filters = filters
self.invalidateFilter()
def filterAcceptsRow(self, sourceRow, sourceParent):
for i, text in self.filters:
if 0 <= i < self.columnCount():
ix = self.sourceModel().index(sourceRow, i, sourceParent)
data = ix.data()
if text not in data:
return False
return True
class winMain(QtWidgets.QMainWindow):
cur_row = -1
row_id = -1
def __init__(self, parent=None):
super().__init__(parent)
self.setupUi()
self.setGeometry(300,200,700,500)
self.treeView.selectionModel().selectionChanged.connect(self.item_selection_changed_slot)
self.center()
self.show()
def new_dataset(self):
print("new_dataset() called.")
# get new row
row = modelTable.rowCount()
new_row = row+1
self.cur_row = new_row
# get next free row id
model = QtSql.QSqlQueryModel()
model.setQuery("SELECT max(id)+1 FROM person")
self.row_id = model.data(model.index(0, 0))
# insert a new row with dummy data
modelTable.insertRow(row)
modelTable.setData(modelTable.index(row,0), self.row_id, QtCore.Qt.EditRole)
modelTable.setData(modelTable.index(row,1), "new" + str(self.row_id), QtCore.Qt.EditRole)
modelTable.setData(modelTable.index(row,2), "new" + str(self.row_id), QtCore.Qt.EditRole)
modelTable.setData(modelTable.index(row,3), "new" + str(self.row_id), QtCore.Qt.EditRole)
modelTable.setData(modelTable.index(row,4), 2, QtCore.Qt.EditRole)
modelTable.submitAll()
def handleFilterActivated(self):
header = self.treeView.header()
filters = []
for i in range(header.count()):
text = header.filterText(i)
if text:
filters.append((i, text))
proxy = self.treeView.model()
proxy.filters = filters
QtCore.pyqtSlot()
def item_selection_changed_slot(self):
selected = self.treeView.selectionModel()
indexes = selected.selectedIndexes()
sourceIdx = self.treeView.currentIndex()
ix = self.treeView.model().index(sourceIdx.row(), 0) # column which contains the id
self.cur_row = sourceIdx.row()
self.row_id = ix.data()
record = modelTable.record(self.cur_row)
persId = record.value("persId")
lastName = record.value("lastName")
firstName = record.value("firstName")
country = record.value("name")
print(f"{persId} - {lastName}, {firstName} from {country} selected.")
def keyReleaseEvent(self, eventQKeyEvent):
key = eventQKeyEvent.key()
modifiers = QtWidgets.QApplication.keyboardModifiers()
if modifiers == QtCore.Qt.ShiftModifier and key == QtCore.Qt.Key_Escape:
self.clear_all_filters()
def center(self):
frameGm = self.frameGeometry()
screen = QtWidgets.QApplication.desktop().screenNumber(QtWidgets.QApplication.desktop().cursor().pos())
centerPoint = QtWidgets.QApplication.desktop().screenGeometry(screen).center()
frameGm.moveCenter(centerPoint)
self.move(frameGm.topLeft())
def setupUi(self):
self.centralwidget = QtWidgets.QWidget(self)
self.horizontalLayout = QtWidgets.QHBoxLayout(self.centralwidget)
self.treeView = QtWidgets.QTreeView(self.centralwidget)
self.treeView.setRootIsDecorated(False)
self.treeView.setSortingEnabled(True)
self.treeView.setAlternatingRowColors(True)
self.treeView.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers)
self.treeView.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection)
self.treeView.header().setStretchLastSection(True)
self.horizontalLayout.addWidget(self.treeView)
self.setCentralWidget(self.centralwidget)
header = FilterHeader(self.treeView)
self.treeView.setHeader(header)
# ToolBar
newDatasetAct = QtWidgets.QAction(QtGui.QIcon('img/icons8-new-file-50.png'), 'New dataset (CTRL+N)', self)
newDatasetAct.setShortcut('Ctrl+N')
newDatasetAct.triggered.connect(self.new_dataset)
self.toolbar = self.addToolBar('Main')
self.toolbar.addAction(newDatasetAct)
modelTable.setTable("person")
modelTable.setRelation(4, QtSql.QSqlRelation("country", "id", "name"));
modelTable.setEditStrategy(QtSql.QSqlTableModel.OnManualSubmit)
self.treeView.setModel(modelTable) # display data of the SQLTableModel into the QTreeView
# enable human sorting
proxy = HumanProxyModel(self)
proxy.setSourceModel(modelTable)
self.treeView.setModel(proxy)
# enable filtering
header.setFilterBoxes(modelTable.columnCount())
header.filterActivated.connect(self.handleFilterActivated)
def create_sample_data():
modelQuery.setQuery("""CREATE TABLE IF NOT EXISTS country (
id INTEGER PRIMARY KEY UNIQUE NOT NULL,
name TEXT
)""")
# id INTEGER PRIMARY KEY UNIQUE,
modelQuery.setQuery("""CREATE TABLE IF NOT EXISTS person (
id INTEGER PRIMARY KEY UNIQUE NOT NULL,
persId TEXT,
lastName TEXT,
firstName TEXT,
country_id INTEGER NOT NULL DEFAULT 3,
FOREIGN KEY (country_id) REFERENCES country(id)
)""")
# create some sample data for our model
modelQuery.setQuery("INSERT INTO country (id, name) VALUES (0, 'None')")
modelQuery.setQuery("INSERT INTO country (id, name) VALUES (1, 'Angola')")
modelQuery.setQuery("INSERT INTO country (id, name) VALUES (2, 'Serbia')")
modelQuery.setQuery("INSERT INTO country (id, name) VALUES (3, 'Georgia')")
modelQuery.setQuery("INSERT INTO person (id, persId, lastName, firstName, country_id) VALUES (1, '1001', 'Martin', 'Robert', 1)")
modelQuery.setQuery("INSERT INTO person (id, persId, lastName, firstName, country_id) VALUES (2, '1002', 'Smith', 'Brad', 2)")
modelQuery.setQuery("INSERT INTO person (id, persId, lastName, firstName, country_id) VALUES (3, '1003', 'Smith', 'Angelina', 3)")
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
create_sample_data()
window = winMain()
sys.exit(app.exec_())
The problem comes from the call to self.columnCount() in filterAcceptsRow. columnCount of a QSortFilterProxyModel requires a mapping of the current proxy model, which in turn calls filterAcceptsRow again, making the function recursive.
Use if 0 <= i < self.sourceModel().columnCount() and the problem is solved.

.deleter method only deletes once

I am having issues with .deleter where the values I am trying to delete only works for the first try.
To replicate:
Checked 'GrpB-Alice' and hit 'Click Me' (Do not close the window yet)
i.myDict returns {u'GrpB': set([u'Alice'])}) which is correct
Checked 'GrpB-Phan' and hit 'Click Me'
i.myDict returns {u'GrpB': set([u'Alice', 'Phan])}) which is correct
Checked 'GrpB-Alice' and hit 'Click Me'
i.myDict returns {u'GrpB': set([u'Alice', 'Phan])}) which is incorrect where I am expecting {u'GrpB': set([u'Alice'])})
If I close the tool, and redo the last step, only will i.myDict returns correctly, but should I checked on new options, at the third time, i.myDict will returns incorrect result again.
The deleter is definitely being called but it only seems to execute the real deletion once though. Am I missing something here?
class SubMenuWindow(QtGui.QWidget):
def __init__(self, menu_items, parent=None, callback=None):
super(SubMenuWindow, self).__init__(parent)
self.callback = callback
self.my_lyt = QtGui.QVBoxLayout()
self.checked_options = []
self.sel = defaultdict(set)
for menu_name, submenu_name in menu_items.items():
# Set the main menu item name
self.groupbox = QtGui.QGroupBox(self)
self.groupbox.setTitle(menu_name)
self.groupbox.setLayout(QtGui.QVBoxLayout())
self.my_lyt.addWidget(self.groupbox)
if submenu_name:
sub_txt = [action for action in submenu_name]
for s in sub_txt:
sub_chk = QtGui.QCheckBox(s)
self.checked_options.append(sub_chk)
self.groupbox.layout().addWidget(sub_chk)
apply_tag_btn = QtGui.QPushButton('Apply to selected item')
apply_tag_btn.clicked.connect(self.get_checked_options)
self.my_lyt.addWidget(apply_tag_btn)
self.my_lyt.addStretch()
self.setLayout(self.my_lyt)
self.show()
def get_checked_options(self):
for chk in self.checked_options:
if chk.isChecked():
print 'Checked - {0}, Parent - {1}'.format(chk.text(), chk.parent().title())
self.sel[chk.parent().title()].add(chk.text())
if self.callback:
self.callback()
class MainWin(QtGui.QWidget):
def __init__(self, parent=None):
super(MainWin, self).__init__(parent)
self.my_dict = {}
btnA = QtGui.QPushButton('Click Me')
btnA.clicked.connect(self.get_options)
btnB = QtGui.QPushButton('Get results')
btnB.clicked.connect(self.get_results)
layout = QtGui.QVBoxLayout()
layout.addWidget(btnA)
layout.addWidget(btnB)
self.setLayout(layout)
def get_options(self):
sample_dict = {'GrpA' : ['John', 'Zack'], 'GrpB' : ['Alice', 'Phan']}
self.subWin = SubMenuWindow(sample_dict, callback=self.get_results)
def get_results(self):
i = MyDict()
# delete any existing values
del (i.myDict)
# append the new values
i.myDict = self.subWin.sel
print 'current myDict values : ', i.myDict
# If I select 'GrpB - Alice'
# i.myDict is `{u'GrpB': set([u'Alice'])})` # correct
# If I select 'GrpB - Alice' and 'GrpB - Phan'
# i.myDict is `{u'GrpB': set([u'Alice', 'Phan'])})` # correct
# If I re-select 'GrpB - Alice'
# i.myDict is `{u'GrpB': set([u'Alice', 'Phan'])})` # in-correct
class MyDict(QtCore.QObject):
def __init__(self):
self._myDict = None
#property
def myDict(self):
return self._myDict
#myDict.setter
def myDict(self, value):
self._myDict = value
#myDict.deleter
def myDict(self):
del(self._myDict)

Add additional line to QTableView object at a given position

In my Gui I creats a QTableView with a QStandardItemModel and I would like to add either an additional row or column at a given position.
class Output(object):
def __init__(self):
''' '''
self.tabs = QtGui.QTabWidget()
self.group_box = QtGui.QGroupBox('Example')
def run(self):
form_layout = QtGui.QFormLayout(self.group_box)
self.tabs.addTab(self.__genTable(),"Tab 1")
self.tabs.addTab(self.__genTable(),"Tab 2")
form_layout.addWidget(self.tabs)
return self.group_box
def __genTable(self):
table_view = QtGui.QTableView()
table_view.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
table_view.customContextMenuRequested.connect( self.__openMenu )
table_view.setSelectionBehavior( QtGui.QTableView.SelectItems )
table_view.setModel(QtGui.QStandardItemModel(4, 2))
return table_view
def __openMenu(self, position):
menu = QtGui.QMenu()
sub_menu_row = QtGui.QMenu("Row")
menu.addMenu(sub_menu_row)
addRowBelowAction = sub_menu_row.addAction("add Row below")
action = menu.exec_(QtGui.QCursor.pos())
if action == addRowBelowAction:
idx = self.tabs.currentWidget().selectionModel().currentIndex()
for i in range(self.tabs.count()):
model = self.tabs.widget(i).selectionModel()
model.insertRow(idx.row(), QtCore.QModelIndex())
Unfortunately i get the following error:
model.insertRow(idx.row(), QtCore.QModelIndex())
AttributeError: 'PySide.QtGui.QItemSelectionModel' object has no attribute 'insertRow'
Untested, but try holding a reference to your model
then call the appropriate methods of the model (insertRow, insertColumn).
The effect of these methods will be apparent in the view.
E.g.:
table_view = QtGui.QTableView()
table_view.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
table_view.setSelectionBehavior( QtGui.QTableView.SelectItems )
model = QtGui.QStandardItemModel(4, 2)
table_view.setModel(model)
model.insertRow(2, QtCore.QModelIndex())

Drag and Dropping Rows between two separate QTableWidgets

Background:
I have two separate QTableWidgets on my Main Window. Both of which are dynamically added in python. I have implemented pyqt code, which can be found under three_pineapples solution here.
My Problem
I want to be able to drag and drop table rows from one table, to another. For example:
If I attempt to drag Item B from the Left Table into the Right Table
This would be the outcome
Any suggestions on how to get this desired behavior?
I took a look at the #three_pineapples' code, didn't understand a half and deleted that half. Works with multiple row selection.
import sys
from PyQt4.QtGui import *
class TableWidgetDragRows(QTableWidget):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.setDragEnabled(True)
self.setAcceptDrops(True)
self.setSelectionBehavior(QAbstractItemView.SelectRows)
self.setDragDropOverwriteMode(False)
# self.setSelectionMode(QAbstractItemView.SingleSelection)
self.last_drop_row = None
# Override this method to get the correct row index for insertion
def dropMimeData(self, row, col, mimeData, action):
self.last_drop_row = row
return True
def dropEvent(self, event):
# The QTableWidget from which selected rows will be moved
sender = event.source()
# Default dropEvent method fires dropMimeData with appropriate parameters (we're interested in the row index).
super().dropEvent(event)
# Now we know where to insert selected row(s)
dropRow = self.last_drop_row
selectedRows = sender.getselectedRowsFast()
# Allocate space for transfer
for _ in selectedRows:
self.insertRow(dropRow)
# if sender == receiver (self), after creating new empty rows selected rows might change their locations
sel_rows_offsets = [0 if self != sender or srow < dropRow else len(selectedRows) for srow in selectedRows]
selectedRows = [row + offset for row, offset in zip(selectedRows, sel_rows_offsets)]
# copy content of selected rows into empty ones
for i, srow in enumerate(selectedRows):
for j in range(self.columnCount()):
item = sender.item(srow, j)
if item:
source = QTableWidgetItem(item)
self.setItem(dropRow + i, j, source)
# delete selected rows
for srow in reversed(selectedRows):
sender.removeRow(srow)
event.accept()
def getselectedRowsFast(self):
selectedRows = []
for item in self.selectedItems():
if item.row() not in selectedRows:
selectedRows.append(item.row())
selectedRows.sort()
return selectedRows
class Window(QWidget):
def __init__(self):
super().__init__()
layout = QHBoxLayout()
self.setLayout(layout)
self.table_widgets = []
for _ in range(3):
tw = TableWidgetDragRows()
tw.setColumnCount(2)
tw.setHorizontalHeaderLabels(['Colour', 'Model'])
self.table_widgets.append(tw)
layout.addWidget(tw)
filled_widget = self.table_widgets[0]
items = [('Red', 'Toyota'), ('Blue', 'RV'), ('Green', 'Beetle')]
for i, (colour, model) in enumerate(items):
c = QTableWidgetItem(colour)
m = QTableWidgetItem(model)
filled_widget.insertRow(filled_widget.rowCount())
filled_widget.setItem(i, 0, c)
filled_widget.setItem(i, 1, m)
if __name__ == '__main__':
app = QApplication(sys.argv)
window = Window()
window.show()
sys.exit(app.exec_())

Categories

Resources