Source code for pyleecan.GUI.Tools.CheckableComboBox
# https://gis.stackexchange.com/questions/350148/qcombobox-multiple-selection-pyqt5
from PySide2.QtWidgets import QComboBox, QStyledItemDelegate, QApplication
from PySide2.QtGui import QPalette, QStandardItem, QFontMetrics
from PySide2.QtCore import QEvent, Qt, Signal
[docs]class CheckableComboBox(QComboBox):
selectionChanged = Signal()
# Subclass Delegate to increase item height
[docs] class Delegate(QStyledItemDelegate):
[docs] def sizeHint(self, option, index):
size = super().sizeHint(option, index)
size.setHeight(20)
return size
def __init__(
self, *args, label=None, singleSelection=False, allowNoSelection=True, **kwargs
):
super().__init__(*args, **kwargs)
self._label = label
self._singleSelection = singleSelection
self._allowNoSelection = allowNoSelection
self._selectionChanged = False
# Make the combo editable to set a custom text, but readonly
self.setEditable(True)
self.lineEdit().setReadOnly(True)
# Make the lineedit the same color as QPushButton
palette = QApplication.palette()
palette.setBrush(QPalette.Base, palette.button())
self.lineEdit().setPalette(palette)
# Use custom delegate
self.setItemDelegate(CheckableComboBox.Delegate())
# Update the text when an item is toggled
self.model().dataChanged.connect(self.onDataChanged)
# Hide and show popup when clicking the line edit
self.lineEdit().installEventFilter(self)
self.closeOnLineEditClick = False
# Prevent popup from closing when clicking on an item
self.view().viewport().installEventFilter(self)
[docs] def resizeEvent(self, event):
# Recompute text to elide as needed
self.updateText()
super().resizeEvent(event)
[docs] def eventFilter(self, object, event):
if object == self.lineEdit():
if event.type() == QEvent.MouseButtonRelease:
if self.closeOnLineEditClick:
self.hidePopup()
else:
self.showPopup()
return True
return False
if object == self.view().viewport():
if event.type() == QEvent.MouseButtonRelease:
index = self.view().indexAt(event.pos())
item = self.model().item(index.row())
if item.checkState() == Qt.Checked:
item.setCheckState(Qt.Unchecked)
else:
item.setCheckState(Qt.Checked)
return True
return False
[docs] def timerEvent(self, event):
# After timeout, kill timer, and reenable click on line edit
self.killTimer(event.timerId())
self.closeOnLineEditClick = False
[docs] def onDataChanged(self, topLeft=None, bottomRight=None):
current = self.currentData()
item = None
if topLeft is not None and topLeft.isValid():
item = self.model().itemFromIndex(topLeft)
if not self._allowNoSelection:
if not current and self.model().rowCount() > 0:
self.model().item(0).setCheckState(Qt.Checked)
if self._singleSelection:
if item is not None and item.data(Qt.CheckStateRole) == Qt.Checked:
text = item.text()
for i in range(self.model().rowCount()):
others = self.model().item(i)
if others.checkState() == Qt.Checked and others.text() != text:
self.model().item(i).setCheckState(Qt.Unchecked)
self._selectionChanged = True
self.updateText()
[docs] def updateText(self):
texts = []
for i in range(self.model().rowCount()):
if self.model().item(i).checkState() == Qt.Checked:
texts.append(self.model().item(i).text())
text = self._label + ": " if self._label else ""
text += ", ".join(texts)
# Compute elided text (with "...")
# metrics = QFontMetrics(self.lineEdit().font())
# elidedText = metrics.elidedText(text, Qt.ElideRight, self.lineEdit().width())
# self.lineEdit().setText(elidedText)
self.lineEdit().setText(text) # no elided text for now
[docs] def addItem(self, text, data=None):
item = QStandardItem()
item.setText(text)
if data is None:
item.setData(text)
else:
item.setData(data)
item.setFlags(Qt.ItemIsEnabled | Qt.ItemIsUserCheckable)
item.setData(Qt.Unchecked, Qt.CheckStateRole)
self.model().appendRow(item)
self.onDataChanged()
[docs] def addItems(self, texts, datalist=None):
for i, text in enumerate(texts):
try:
data = datalist[i]
except (TypeError, IndexError):
data = None
self.addItem(text, data)
self.onDataChanged()
[docs] def currentData(self):
# Return the list of selected items data
res = []
for i in range(self.model().rowCount()):
if self.model().item(i).checkState() == Qt.Checked:
res.append(self.model().item(i).data())
return res