Rename ItemDelegate. Remove table checkbox
This commit is contained in:
parent
6b8b961ff7
commit
e0f1f7c356
@ -125,7 +125,7 @@ class CheckableComboBox(QtWidgets.QComboBox):
|
||||
|
||||
|
||||
# Inspiration from https://github.com/marcel-goldschen-ohm/ModelViewPyQt and https://github.com/zxt50330/qitemdelegate-example
|
||||
class ItemDelegate(QtWidgets.QStyledItemDelegate):
|
||||
class ReadStyleItemDelegate(QtWidgets.QStyledItemDelegate):
|
||||
buttonClicked = pyqtSignal(QModelIndex, ClickedButtonEnum)
|
||||
|
||||
def __init__(self, parent: QtWidgets.QWidget):
|
||||
@ -234,7 +234,7 @@ class CheckableOrderComboBox(QtWidgets.QComboBox):
|
||||
|
||||
def __init__(self, *args: Any, **kwargs: Any):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.setItemDelegate(ItemDelegate(self))
|
||||
self.setItemDelegate(ReadStyleItemDelegate(self))
|
||||
# Prevent popup from closing when clicking on an item
|
||||
self.view().viewport().installEventFilter(self)
|
||||
|
||||
@ -375,371 +375,3 @@ class CheckableOrderComboBox(QtWidgets.QComboBox):
|
||||
self.setItemChecked(index, False)
|
||||
else:
|
||||
self.setItemChecked(index, True)
|
||||
|
||||
|
||||
class CheckBoxStyle(QtWidgets.QProxyStyle):
|
||||
def subElementRect(
|
||||
self, element: QtWidgets.QStyle.SubElement, option: QtWidgets.QStyleOption, widget: QtWidgets.QWidget = None
|
||||
) -> QRect:
|
||||
r = super().subElementRect(element, option, widget)
|
||||
if element == QtWidgets.QStyle.SE_ItemViewItemCheckIndicator:
|
||||
r.moveCenter(option.rect.center())
|
||||
return r
|
||||
|
||||
|
||||
class SortLabelTableWidgetItem(QtWidgets.QTableWidgetItem):
|
||||
"""Custom QTableWidgetItem to sort with '-' below numbers"""
|
||||
|
||||
def __lt__(self, other: QtWidgets.QTableWidgetItem) -> bool:
|
||||
if self.text() == "-":
|
||||
return False
|
||||
if other.text() == "-":
|
||||
return True
|
||||
return super().__lt__(other)
|
||||
|
||||
|
||||
class HoverQLabel(QtWidgets.QLabel):
|
||||
"""A QLabel with two QButtons that appear on hover"""
|
||||
|
||||
def __init__(self, text: str, parent: TableComboBox):
|
||||
super().__init__(text, parent=parent)
|
||||
self.combobox = parent
|
||||
|
||||
self.button_up = QtWidgets.QPushButton(QtGui.QIcon(str(graphics_path / "up.png")), "", self)
|
||||
self.button_up.clicked.connect(self.button_up_clicked)
|
||||
self.button_up.setToolTip("Move style up in order")
|
||||
self.button_up.hide()
|
||||
|
||||
self.button_down = QtWidgets.QPushButton(QtGui.QIcon(str(graphics_path / "down.png")), "", self)
|
||||
self.button_down.clicked.connect(self.button_down_clicked)
|
||||
self.button_down.setToolTip("Move style down in order")
|
||||
self.button_down.hide()
|
||||
|
||||
# Place 'down' button on left side
|
||||
self.button_down.resize(self.button_down.sizeHint())
|
||||
|
||||
# Place 'up' button on right side
|
||||
self.button_up.resize(self.button_up.sizeHint())
|
||||
|
||||
def hideButtons(self) -> None:
|
||||
self.button_up.hide()
|
||||
self.button_down.hide()
|
||||
|
||||
def showHideButtons(self, index: QModelIndex) -> None:
|
||||
# TODO Better to iterate over all? Send in check state too?
|
||||
item = self.combobox.tableWidget.item(index.row(), 1)
|
||||
item_checked = item.checkState()
|
||||
if index.row() != self.combobox.tableWidget.currentRow():
|
||||
# Hide the previous row buttons
|
||||
self.button_up.hide()
|
||||
self.button_down.hide()
|
||||
elif item_checked == Qt.Checked:
|
||||
self.button_up.show()
|
||||
self.button_down.show()
|
||||
else:
|
||||
self.button_up.hide()
|
||||
self.button_down.hide()
|
||||
|
||||
def enterEvent(self, event: QEvent | None) -> None:
|
||||
# Need to manually set the rest of the row highlighted
|
||||
index: QModelIndex = self.combobox.tableWidget.indexAt(self.pos())
|
||||
self.combobox.tableWidget.selectRow(index.row())
|
||||
|
||||
super().enterEvent(event)
|
||||
|
||||
def mouseReleaseEvent(self, event: Any) -> None:
|
||||
index: QModelIndex = self.combobox.tableWidget.indexAt(self.pos())
|
||||
if self.combobox.isShown:
|
||||
self.combobox.toggleItem(index)
|
||||
|
||||
def resizeEvent(self, event: Any | None = None) -> None:
|
||||
self.button_down.move(self.width() - self.button_up.width(), (self.height() - self.button_up.height()) // 2)
|
||||
self.button_up.move(
|
||||
self.width() - self.button_up.width() - self.button_down.width(),
|
||||
(self.height() - self.button_up.height()) // 2,
|
||||
)
|
||||
|
||||
def button_up_clicked(self) -> None:
|
||||
index: QModelIndex = self.combobox.tableWidget.indexAt(self.pos())
|
||||
self.combobox.moveItem(index, True)
|
||||
|
||||
def button_down_clicked(self) -> None:
|
||||
index: QModelIndex = self.combobox.tableWidget.indexAt(self.pos())
|
||||
self.combobox.moveItem(index, False)
|
||||
|
||||
|
||||
class TableComboBox(QtWidgets.QComboBox):
|
||||
itemChanged = pyqtSignal()
|
||||
|
||||
def __init__(self, *args: Any, **kwargs: Any):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
# Longest width of read style label
|
||||
self.longest = 0
|
||||
|
||||
self.tableWidget = QtWidgets.QTableWidget()
|
||||
self.setModel(self.tableWidget.model())
|
||||
self.setView(self.tableWidget)
|
||||
|
||||
centered_checkbox_style = CheckBoxStyle()
|
||||
self.tableWidget.setStyle(centered_checkbox_style)
|
||||
|
||||
self.tableWidget.setColumnCount(3)
|
||||
self.tableWidget.setHorizontalHeaderLabels([" # ", " Enabled ", "Read Style"])
|
||||
self.tableWidget.horizontalHeaderItem(0).setToolTip("Order of overlay operations")
|
||||
self.tableWidget.horizontalHeaderItem(1).setToolTip("Whether the style is enabled or not")
|
||||
self.tableWidget.horizontalHeaderItem(2).setToolTip("Name of the read style")
|
||||
|
||||
self.tableWidget.horizontalHeader().setSectionResizeMode(2, QtWidgets.QHeaderView.Stretch)
|
||||
self.tableWidget.resizeColumnsToContents()
|
||||
|
||||
self.tableWidget.verticalHeader().setVisible(False)
|
||||
|
||||
self.tableWidget.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows)
|
||||
self.tableWidget.setSelectionMode(QtWidgets.QAbstractItemView.SingleSelection)
|
||||
self.tableWidget.setShowGrid(False)
|
||||
|
||||
# Prevent popup from closing when clicking on an item
|
||||
self.tableWidget.viewport().installEventFilter(self)
|
||||
|
||||
# Keeps track of when the combobox list is shown
|
||||
self.isShown = False
|
||||
|
||||
self.tableWidget.currentCellChanged.connect(self.current_cell_changed)
|
||||
|
||||
def current_cell_changed(self, cur_row: int, cur_col: int, prev_row: int, prev_col: int) -> None:
|
||||
# When rebuilding, cur_row -1 will occur and cause a crash
|
||||
if cur_row == -1:
|
||||
return
|
||||
if prev_row == -1:
|
||||
# First time open
|
||||
cur_index = self.tableWidget.indexFromItem(self.tableWidget.item(cur_row, 0))
|
||||
self.tableWidget.cellWidget(cur_row, 2).showHideButtons(cur_index)
|
||||
elif cur_row != prev_row:
|
||||
# Hide previous
|
||||
prev_index = self.tableWidget.indexFromItem(self.tableWidget.item(prev_row, 0))
|
||||
self.tableWidget.cellWidget(prev_row, 2).showHideButtons(prev_index)
|
||||
# Show current
|
||||
cur_index = self.tableWidget.indexFromItem(self.tableWidget.item(cur_row, 0))
|
||||
self.tableWidget.cellWidget(cur_row, 2).showHideButtons(cur_index)
|
||||
|
||||
def _longest_label(self) -> None:
|
||||
# Depending on "short" names for metadata, "Read Style" header or metadata name may be longer
|
||||
style_header_width = 0
|
||||
header_item = self.tableWidget.horizontalHeaderItem(2)
|
||||
if header_item is not None:
|
||||
font_metrics = QtGui.QFontMetrics(header_item.font())
|
||||
style_header_width = font_metrics.width(header_item.text())
|
||||
|
||||
header_width = 0
|
||||
for col in range(self.tableWidget.columnCount() - 1): # Skip "read style" as already done above
|
||||
header_item = self.tableWidget.horizontalHeaderItem(col)
|
||||
if header_item is not None:
|
||||
font_metrics = QtGui.QFontMetrics(header_item.font())
|
||||
text_width = font_metrics.width(header_item.text())
|
||||
header_width += text_width
|
||||
|
||||
# Now check items
|
||||
for i in range(self.count()):
|
||||
hlabel = self.tableWidget.cellWidget(i, 2)
|
||||
hlabel_width = (
|
||||
style_header_width if style_header_width > hlabel.sizeHint().width() else hlabel.sizeHint().width()
|
||||
)
|
||||
# Get sizeHint of one button and double it
|
||||
total_width = hlabel_width + header_width + (hlabel.button_up.sizeHint().width() * 2)
|
||||
|
||||
if total_width > self.longest:
|
||||
self.longest = total_width
|
||||
|
||||
def _resizeTable(self) -> None:
|
||||
self._longest_label()
|
||||
self.tableWidget.setMinimumWidth(self.longest)
|
||||
|
||||
def resizeEvent(self, event: Any | None = None) -> None:
|
||||
super().resizeEvent(event)
|
||||
self._updateText()
|
||||
|
||||
def eventFilter(self, obj: Any, event: Any) -> bool:
|
||||
# Allow events before the combobox list is shown
|
||||
if obj == self.tableWidget.viewport():
|
||||
if event.type() == QEvent.Hide:
|
||||
self._updateText()
|
||||
self.isShown = False
|
||||
if event.type() == QEvent.Show:
|
||||
self.isShown = True
|
||||
# QEvent.MouseButtonRelease will 'click' the selected row even if clicking outside to hide dropdown
|
||||
if self.isShown and event.type() == QEvent.MouseButtonRelease:
|
||||
# Find the current index and item
|
||||
index = self.tableWidget.indexAt(event.pos())
|
||||
self.toggleItem(index)
|
||||
return True
|
||||
return False
|
||||
|
||||
def emptyTable(self) -> None:
|
||||
self.tableWidget.setRowCount(0)
|
||||
self.longest = 0
|
||||
|
||||
def moveItem(self, index: QModelIndex, up: bool = False, row: int | None = None) -> None:
|
||||
"""'Move' an item. Really swap the data and titles around on the two rows"""
|
||||
if row is None:
|
||||
adjust = -1 if up else 1
|
||||
row = index.row() + adjust
|
||||
|
||||
# Grab values for the rows to swap
|
||||
cur_data = self.tableWidget.item(index.row(), 0).data(Qt.UserRole)
|
||||
cur_title = self.tableWidget.cellWidget(index.row(), 2).text()
|
||||
swap_data = self.tableWidget.item(row, 0).data(Qt.UserRole)
|
||||
swap_title = self.tableWidget.cellWidget(row, 2).text()
|
||||
|
||||
self.tableWidget.item(row, 0).setData(Qt.UserRole, cur_data)
|
||||
self.tableWidget.cellWidget(row, 2).setText(cur_title)
|
||||
self.tableWidget.item(index.row(), 0).setData(Qt.UserRole, swap_data)
|
||||
self.tableWidget.cellWidget(index.row(), 2).setText(swap_title)
|
||||
|
||||
# Hide buttons and clear selection to indicate to user an action has taken place
|
||||
self.tableWidget.cellWidget(index.row(), 2).hideButtons()
|
||||
self.tableWidget.clearSelection()
|
||||
|
||||
self.itemChanged.emit()
|
||||
|
||||
def addItem(self, text: str = "", data: Any | None = None, rowPosition: int | None = None) -> None:
|
||||
rowPosition = (
|
||||
self.tableWidget.rowCount()
|
||||
if rowPosition is None or rowPosition > self.tableWidget.rowCount()
|
||||
else rowPosition
|
||||
)
|
||||
self.tableWidget.insertRow(rowPosition)
|
||||
|
||||
sortTblItem = SortLabelTableWidgetItem()
|
||||
sortTblItem.setTextAlignment(Qt.AlignCenter)
|
||||
self.tableWidget.setItem(rowPosition, 0, sortTblItem)
|
||||
|
||||
chkBoxItem = QtWidgets.QTableWidgetItem()
|
||||
# Set to true to get around the "one item must be checked" check for setItemChecked
|
||||
chkBoxItem.setCheckState(Qt.Checked)
|
||||
|
||||
self.tableWidget.setItem(rowPosition, 1, chkBoxItem)
|
||||
self.tableWidget.setCellWidget(rowPosition, 2, HoverQLabel(text, parent=self))
|
||||
self.tableWidget.item(rowPosition, 0).setData(Qt.UserRole, data)
|
||||
|
||||
self._updateLabels()
|
||||
self._updateText()
|
||||
# Manual as resizeEvent doesn't trigger
|
||||
self._resizeTable()
|
||||
|
||||
def findData(self, data: str, role: int = Qt.UserRole) -> QModelIndex | None:
|
||||
for i in range(self.count()):
|
||||
item = self.itemData(i)
|
||||
if item == data:
|
||||
return self.tableWidget.indexFromItem(self.tableWidget.item(i, 0))
|
||||
return None
|
||||
|
||||
def currentData(self) -> list[str]:
|
||||
res = []
|
||||
for i in range(self.count()):
|
||||
item = self.tableWidget.item(i, 1)
|
||||
if item.checkState() == Qt.Checked:
|
||||
res.append(self.itemData(i))
|
||||
return res
|
||||
|
||||
def _setOrderNumbers(self) -> None:
|
||||
"""Recalculate the order numbers"""
|
||||
current_data = self.currentData()
|
||||
text = "-"
|
||||
|
||||
for j in range(self.count()):
|
||||
item = self.itemData(j)
|
||||
for i, cur in enumerate(current_data):
|
||||
if item == cur:
|
||||
text = str(i + 1)
|
||||
|
||||
self.tableWidget.item(j, 0).setText(text)
|
||||
|
||||
def _updateLabels(self) -> None:
|
||||
"""Update order label text and set button enablement"""
|
||||
cur_data_len = len(self.currentData())
|
||||
for i in range(self.count()):
|
||||
label = self.tableWidget.item(i, 0)
|
||||
data = self.itemData(i)
|
||||
label_num = "-"
|
||||
enable_up = True
|
||||
enable_down = True
|
||||
|
||||
# Go through currentData
|
||||
for j, cur_data in enumerate(self.currentData()):
|
||||
if cur_data == data:
|
||||
label_num = str(j + 1)
|
||||
if cur_data_len == 1:
|
||||
enable_up = False
|
||||
enable_down = False
|
||||
elif j == 0:
|
||||
enable_up = False
|
||||
elif j + 1 == cur_data_len:
|
||||
enable_down = False
|
||||
|
||||
label.setText(label_num)
|
||||
|
||||
# Enable/disable hover buttons
|
||||
self.tableWidget.cellWidget(i, 2).button_up.setEnabled(enable_up)
|
||||
self.tableWidget.cellWidget(i, 2).button_down.setEnabled(enable_down)
|
||||
|
||||
self.tableWidget.sortItems(0)
|
||||
self.tableWidget.clearSelection()
|
||||
|
||||
def _nextOrderNumber(self) -> int:
|
||||
return len(self.currentData()) - 1
|
||||
|
||||
def _updateText(self) -> None:
|
||||
texts = []
|
||||
for i in range(self.count()):
|
||||
item = self.tableWidget.item(i, 1)
|
||||
item_texts = self.tableWidget.cellWidget(i, 2)
|
||||
if item.checkState() == Qt.Checked:
|
||||
texts.append(item_texts.text())
|
||||
text = ", ".join(texts)
|
||||
|
||||
# Compute elided text (with "...")
|
||||
|
||||
# The QStyleOptionComboBox is needed for the call to subControlRect
|
||||
so = QtWidgets.QStyleOptionComboBox()
|
||||
# init with the current widget
|
||||
so.initFrom(self)
|
||||
|
||||
# Ask the style for the size of the text field
|
||||
rect = self.style().subControlRect(QtWidgets.QStyle.CC_ComboBox, so, QtWidgets.QStyle.SC_ComboBoxEditField)
|
||||
|
||||
# Compute the elided text
|
||||
elidedText = self.fontMetrics().elidedText(text, Qt.ElideRight, rect.width())
|
||||
|
||||
# This CheckableComboBox does not use the index, so we clear it and set the placeholder text
|
||||
self.setCurrentIndex(-1)
|
||||
self.setPlaceholderText(elidedText)
|
||||
|
||||
def setItemChecked(self, index: QModelIndex, state: bool) -> None:
|
||||
if index is None:
|
||||
return
|
||||
qt_state = Qt.Checked if state else Qt.Unchecked
|
||||
item = self.tableWidget.item(index.row(), 1)
|
||||
current_len = len(self.currentData())
|
||||
|
||||
# Require at least one item to be checked and provide a tooltip
|
||||
if current_len == 1 and not state and item.checkState() == Qt.Checked:
|
||||
QtWidgets.QToolTip.showText(QtGui.QCursor.pos(), self.toolTip(), self, QRect(), 3000)
|
||||
return
|
||||
|
||||
if current_len > 0:
|
||||
item.setCheckState(qt_state)
|
||||
if not state:
|
||||
# Hide hover buttons of shown row (before it changes row)
|
||||
self.tableWidget.cellWidget(index.row(), 2).showHideButtons(index)
|
||||
self._setOrderNumbers()
|
||||
self._updateLabels()
|
||||
self._updateText()
|
||||
self.itemChanged.emit()
|
||||
|
||||
def toggleItem(self, index: QModelIndex) -> None:
|
||||
cxb_index = self.model().index(index.row(), 1)
|
||||
if self.model().data(cxb_index, Qt.CheckStateRole) == Qt.Checked:
|
||||
self.setItemChecked(cxb_index, False)
|
||||
else:
|
||||
self.setItemChecked(cxb_index, True)
|
||||
|
Loading…
x
Reference in New Issue
Block a user