aboutsummaryrefslogtreecommitdiffstats
path: root/examples
diff options
context:
space:
mode:
authorAdrian Herrmann <adrian.herrmann@qt.io>2022-07-11 11:29:35 +0200
committerAdrian Herrmann <adrian.herrmann@qt.io>2022-07-20 15:21:02 +0200
commiteae788072fff001028e0c8f04babd61325125281 (patch)
tree76336aa495ebdb4f58a68056cfe8c2633306513f /examples
parenta3c1a50eb1577caf3715c47b00ca26312d235a80 (diff)
PySide6-examples: SpreadSheet example
Port pre-existing SpreadSheet example to PySide. Pick-to: 6.2 6.3 Change-Id: I32d014fbd5e3e406672b6bec2465c6e41c3a6580 Reviewed-by: Cristian Maureira-Fredes <cristian.maureira-fredes@qt.io>
Diffstat (limited to 'examples')
-rw-r--r--examples/widgets/itemviews/spreadsheet/doc/spreadsheet.pngbin0 -> 40187 bytes
-rw-r--r--examples/widgets/itemviews/spreadsheet/doc/spreadsheet.rst10
-rw-r--r--examples/widgets/itemviews/spreadsheet/main.py19
-rw-r--r--examples/widgets/itemviews/spreadsheet/spreadsheet.py544
-rw-r--r--examples/widgets/itemviews/spreadsheet/spreadsheetdelegate.py67
-rw-r--r--examples/widgets/itemviews/spreadsheet/spreadsheetitem.py122
6 files changed, 762 insertions, 0 deletions
diff --git a/examples/widgets/itemviews/spreadsheet/doc/spreadsheet.png b/examples/widgets/itemviews/spreadsheet/doc/spreadsheet.png
new file mode 100644
index 000000000..ae7dde24b
--- /dev/null
+++ b/examples/widgets/itemviews/spreadsheet/doc/spreadsheet.png
Binary files differ
diff --git a/examples/widgets/itemviews/spreadsheet/doc/spreadsheet.rst b/examples/widgets/itemviews/spreadsheet/doc/spreadsheet.rst
new file mode 100644
index 000000000..c0839b232
--- /dev/null
+++ b/examples/widgets/itemviews/spreadsheet/doc/spreadsheet.rst
@@ -0,0 +1,10 @@
+Spreadsheet example
+===================
+
+The Spreadsheet example shows how a table view can be used to create a simple
+spreadsheet application. Custom delegates are used to render different types of
+data in distinctive colors.
+
+.. image:: spreadsheet.png
+ :width: 400
+ :alt: Spreadsheet screenshot
diff --git a/examples/widgets/itemviews/spreadsheet/main.py b/examples/widgets/itemviews/spreadsheet/main.py
new file mode 100644
index 000000000..0ecc5ec23
--- /dev/null
+++ b/examples/widgets/itemviews/spreadsheet/main.py
@@ -0,0 +1,19 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import sys
+
+from PySide6.QtGui import QPixmap
+from PySide6.QtWidgets import QApplication, QLayout
+
+from spreadsheet import SpreadSheet
+
+if __name__ == "__main__":
+ app = QApplication()
+
+ sheet = SpreadSheet(10, 6)
+ sheet.setWindowIcon(QPixmap(":/images/interview.png"))
+ sheet.show()
+ sheet.layout().setSizeConstraint(QLayout.SetFixedSize)
+
+ sys.exit(app.exec())
diff --git a/examples/widgets/itemviews/spreadsheet/spreadsheet.py b/examples/widgets/itemviews/spreadsheet/spreadsheet.py
new file mode 100644
index 000000000..2f5fbb64f
--- /dev/null
+++ b/examples/widgets/itemviews/spreadsheet/spreadsheet.py
@@ -0,0 +1,544 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+from PySide6.QtCore import QPoint, Qt, QCoreApplication, Slot
+from PySide6.QtGui import QAction, QBrush, QPixmap, QColor, QPainter
+from PySide6.QtWidgets import (QColorDialog, QComboBox, QDialog, QFontDialog,
+ QGroupBox, QHBoxLayout, QMainWindow, QLabel,
+ QLineEdit, QMessageBox, QPushButton, QToolBar,
+ QTableWidgetItem, QTableWidget, QVBoxLayout, QWidget)
+
+from spreadsheetdelegate import SpreadSheetDelegate
+from spreadsheetitem import SpreadSheetItem
+
+from typing import Optional
+from numbers import Number
+
+
+class SpreadSheet(QMainWindow):
+ def __init__(self, rows: Number, cols: Number, parent: Optional[QWidget] = None) -> None:
+ super().__init__(parent)
+
+ self._tool_bar = QToolBar(self)
+ self._color_action = QAction()
+ self._font_action = QAction()
+ self._first_separator = QAction()
+ self._cell_sum_action = QAction()
+ self._cell_add_action = QAction()
+ self._cell_sub_action = QAction()
+ self._cell_mul_action = QAction()
+ self._cell_div_action = QAction()
+ self._second_separator = QAction()
+ self._clear_action = QAction()
+ self._about_spreadsheet = QAction()
+ self._exit_action = QAction()
+
+ # self._print_action = QAction()
+
+ self._cell_label = QLabel(self._tool_bar)
+ self._table = QTableWidget(rows, cols, self)
+ self._formula_input = QLineEdit(self)
+
+ self.addToolBar(self._tool_bar)
+
+ self._cell_label.setMinimumSize(80, 0)
+
+ self._tool_bar.addWidget(self._cell_label)
+ self._tool_bar.addWidget(self._formula_input)
+
+ self._table.setSizeAdjustPolicy(QTableWidget.SizeAdjustPolicy.AdjustToContents)
+ for c in range(cols):
+ character = chr(ord('A') + c)
+ self._table.setHorizontalHeaderItem(c, QTableWidgetItem(character))
+
+ self._table.setItemPrototype(self._table.item(rows - 1, cols - 1))
+ self._table.setItemDelegate(SpreadSheetDelegate())
+
+ self.create_actions()
+ self.update_color(None)
+ self.setup_menu_bar()
+ self.setup_contents()
+ self.setup_context_menu()
+ self.setCentralWidget(self._table)
+
+ self.statusBar()
+ self._table.currentItemChanged.connect(self.update_status)
+ self._table.currentItemChanged.connect(self.update_color)
+ self._table.currentItemChanged.connect(self.update_line_edit)
+ self._table.itemChanged.connect(self.update_status)
+ self._formula_input.returnPressed.connect(self.return_pressed)
+ self._table.itemChanged.connect(self.update_line_edit)
+
+ self.setWindowTitle("Spreadsheet")
+
+ def create_actions(self) -> None:
+ self._cell_sum_action = QAction("Sum", self)
+ self._cell_sum_action.triggered.connect(self.action_sum)
+
+ self._cell_add_action = QAction("&Add", self)
+ self._cell_add_action.setShortcut(Qt.CTRL | Qt.Key_Plus)
+ self._cell_add_action.triggered.connect(self.action_add)
+
+ self._cell_sub_action = QAction("&Subtract", self)
+ self._cell_sub_action.setShortcut(Qt.CTRL | Qt.Key_Minus)
+ self._cell_sub_action.triggered.connect(self.action_subtract)
+
+ self._cell_mul_action = QAction("&Multiply", self)
+ self._cell_mul_action.setShortcut(Qt.CTRL | Qt.Key_multiply)
+ self._cell_mul_action.triggered.connect(self.action_multiply)
+
+ self._cell_div_action = QAction("&Divide", self)
+ self._cell_div_action.setShortcut(Qt.CTRL | Qt.Key_division)
+ self._cell_div_action.triggered.connect(self.action_divide)
+
+ self._font_action = QAction("Font...", self)
+ self._font_action.setShortcut(Qt.CTRL | Qt.Key_F)
+ self._font_action.triggered.connect(self.select_font)
+
+ self._color_action = QAction(QPixmap(16, 16), "Background &Color...", self)
+ self._color_action.triggered.connect(self.select_color)
+
+ self._clear_action = QAction("Clear", self)
+ self._clear_action.setShortcut(Qt.Key_Delete)
+ self._clear_action.triggered.connect(self.clear)
+
+ self._about_spreadsheet = QAction("About Spreadsheet", self)
+ self._about_spreadsheet.triggered.connect(self.show_about)
+
+ self._exit_action = QAction("E&xit", self)
+ self._exit_action.triggered.connect(QCoreApplication.quit)
+
+ self._first_separator = QAction(self)
+ self._first_separator.setSeparator(True)
+
+ self._second_separator = QAction(self)
+ self._second_separator.setSeparator(True)
+
+ def setup_menu_bar(self) -> None:
+ file_menu = self.menuBar().addMenu("&File")
+ # file_menu.addAction(self._print_action)
+ file_menu.addAction(self._exit_action)
+
+ cell_menu = self.menuBar().addMenu("&Cell")
+ cell_menu.addAction(self._cell_add_action)
+ cell_menu.addAction(self._cell_sub_action)
+ cell_menu.addAction(self._cell_mul_action)
+ cell_menu.addAction(self._cell_div_action)
+ cell_menu.addAction(self._cell_sum_action)
+ cell_menu.addSeparator()
+ cell_menu.addAction(self._color_action)
+ cell_menu.addAction(self._font_action)
+
+ self.menuBar().addSeparator()
+
+ about_menu = self.menuBar().addMenu("&Help")
+ about_menu.addAction(self._about_spreadsheet)
+
+ @Slot()
+ def update_status(self, item: QTableWidgetItem) -> None:
+ if item and item == self._table.currentItem():
+ self.statusBar().showMessage(str(item.data(Qt.StatusTipRole)), 1000)
+ self._cell_label.setText(
+ "Cell: ({})".format(
+ SpreadSheetItem.encode_pos(self._table.row(item), self._table.column(item))
+ )
+ )
+
+ @Slot()
+ def update_color(self, item: QTableWidgetItem) -> None:
+ pix = QPixmap(16, 16)
+ col = QColor()
+ if item:
+ col = item.background().color()
+ if not col.isValid():
+ col = self.palette().base().color()
+
+ pt = QPainter(pix)
+ pt.fillRect(0, 0, 16, 16, col)
+
+ lighter = col.lighter()
+ pt.setPen(lighter)
+ light_frame = [QPoint(0, 15), QPoint(0, 0), QPoint(15, 0)]
+ pt.drawPolyline(light_frame)
+
+ pt.setPen(col.darker())
+ darkFrame = [QPoint(1, 15), QPoint(15, 15), QPoint(15, 1)]
+ pt.drawPolyline(darkFrame)
+
+ pt.end()
+
+ self._color_action.setIcon(pix)
+
+ @Slot()
+ def update_line_edit(self, item: QTableWidgetItem) -> None:
+ if item != self._table.currentItem():
+ return
+ if item:
+ self._formula_input.setText(str(item.data(Qt.EditRole)))
+ else:
+ self._formula_input.clear()
+
+ @Slot()
+ def return_pressed(self) -> None:
+ text = self._formula_input.text()
+ row = self._table.currentRow()
+ col = self._table.currentColumn()
+ item = self._table.item(row, col)
+ if not item:
+ self._table.setItem(row, col, SpreadSheetItem(text))
+ else:
+ item.setData(Qt.EditRole, text)
+ self._table.viewport().update()
+
+ @Slot()
+ def select_color(self) -> None:
+ item = self._table.currentItem()
+ col = item.background().color() if item else self._table.palette().base().color()
+ col = QColorDialog.getColor(col, self)
+ if not col.isValid():
+ return
+
+ selected = self._table.selectedItems()
+ if not selected:
+ return
+
+ for i in selected:
+ if i:
+ i.setBackground(col)
+
+ self.update_color(self._table.currentItem())
+
+ @Slot()
+ def select_font(self) -> None:
+ selected = self._table.selectedItems()
+ if not selected:
+ return
+
+ ok = False
+ fnt = QFontDialog.getFont(ok, self.font(), self)
+
+ if not ok:
+ return
+ for i in selected:
+ if i:
+ i.setFont(fnt)
+
+ def run_input_dialog(self, title: str, c1Text: str, c2Text: str, opText: str,
+ outText: str, cell1: str, cell2: str, outCell: str) -> bool:
+ rows, cols = [], []
+ for c in range(self._table.columnCount()):
+ cols.append(chr(ord('A') + c))
+ for r in range(self._table.rowCount()):
+ rows.append(str(1 + r))
+
+ add_dialog = QDialog(self)
+ add_dialog.setWindowTitle(title)
+
+ group = QGroupBox(title, add_dialog)
+ group.setMinimumSize(250, 100)
+
+ cell1_label = QLabel(c1Text, group)
+ cell1_row_input = QComboBox(group)
+ c1_row, c1_col = SpreadSheetItem.decode_pos(cell1)
+ cell1_row_input.addItems(rows)
+ cell1_row_input.setCurrentIndex(c1_row)
+
+ cell1_col_input = QComboBox(group)
+ cell1_col_input.addItems(cols)
+ cell1_col_input.setCurrentIndex(c1_col)
+
+ operator_label = QLabel(opText, group)
+ operator_label.setAlignment(Qt.AlignHCenter)
+
+ cell2_label = QLabel(c2Text, group)
+ cell2_row_input = QComboBox(group)
+ c2_row, c2_col = SpreadSheetItem.decode_pos(cell2)
+ cell2_row_input.addItems(rows)
+ cell2_row_input.setCurrentIndex(c2_row)
+ cell2_col_input = QComboBox(group)
+ cell2_col_input.addItems(cols)
+ cell2_col_input.setCurrentIndex(c2_col)
+
+ equals_label = QLabel("=", group)
+ equals_label.setAlignment(Qt.AlignHCenter)
+
+ out_label = QLabel(outText, group)
+ out_row_input = QComboBox(group)
+ out_row, out_col = SpreadSheetItem.decode_pos(outCell)
+ out_row_input.addItems(rows)
+ out_row_input.setCurrentIndex(out_row)
+ out_col_input = QComboBox(group)
+ out_col_input.addItems(cols)
+ out_col_input.setCurrentIndex(out_col)
+
+ cancel_button = QPushButton("Cancel", add_dialog)
+ cancel_button.clicked.connect(add_dialog.reject)
+
+ ok_button = QPushButton("OK", add_dialog)
+ ok_button.setDefault(True)
+ ok_button.clicked.connect(add_dialog.accept)
+
+ buttons_layout = QHBoxLayout()
+ buttons_layout.addStretch(1)
+ buttons_layout.addWidget(ok_button)
+ buttons_layout.addSpacing(10)
+ buttons_layout.addWidget(cancel_button)
+
+ dialog_layout = QVBoxLayout(add_dialog)
+ dialog_layout.addWidget(group)
+ dialog_layout.addStretch(1)
+ dialog_layout.addItem(buttons_layout)
+
+ cell1_layout = QHBoxLayout()
+ cell1_layout.addWidget(cell1_label)
+ cell1_layout.addSpacing(10)
+ cell1_layout.addWidget(cell1_col_input)
+ cell1_layout.addSpacing(10)
+ cell1_layout.addWidget(cell1_row_input)
+
+ cell2_layout = QHBoxLayout()
+ cell2_layout.addWidget(cell2_label)
+ cell2_layout.addSpacing(10)
+ cell2_layout.addWidget(cell2_col_input)
+ cell2_layout.addSpacing(10)
+ cell2_layout.addWidget(cell2_row_input)
+
+ out_layout = QHBoxLayout()
+ out_layout.addWidget(out_label)
+ out_layout.addSpacing(10)
+ out_layout.addWidget(out_col_input)
+ out_layout.addSpacing(10)
+ out_layout.addWidget(out_row_input)
+
+ v_layout = QVBoxLayout(group)
+ v_layout.addItem(cell1_layout)
+ v_layout.addWidget(operator_label)
+ v_layout.addItem(cell2_layout)
+ v_layout.addWidget(equals_label)
+ v_layout.addStretch(1)
+ v_layout.addItem(out_layout)
+
+ if add_dialog.exec():
+ cell1 = cell1_col_input.currentText() + cell1_row_input.currentText()
+ cell2 = cell2_col_input.currentText() + cell2_row_input.currentText()
+ outCell = out_col_input.currentText() + out_row_input.currentText()
+ return True
+
+ return False
+
+ @Slot()
+ def action_sum(self) -> None:
+ row_first = row_last = row_cur = 0
+ col_first = col_last = col_cur = 0
+
+ selected = self._table.selectedItems()
+
+ if selected is not None:
+ first = selected[0]
+ last = selected[-1]
+ row_first = self._table.row(first)
+ row_last = self._table.row(last)
+ col_first = self._table.column(first)
+ col_last = self._table.column(last)
+
+ current = self._table.currentItem()
+
+ if current:
+ row_cur = self._table.row(current)
+ col_cur = self._table.column(current)
+
+ cell1 = SpreadSheetItem.encode_pos(row_first, col_first)
+ cell2 = SpreadSheetItem.encode_pos(row_last, col_last)
+ out = SpreadSheetItem.encode_pos(row_cur, col_cur)
+
+ if self.run_input_dialog(
+ "Sum cells", "First cell:", "Last cell:",
+ f"{(chr(0x03a3))}", "Output to:",
+ cell1, cell2, out
+ ):
+ row, col = SpreadSheetItem.decode_pos(out)
+ self._table.item(row, col).setText(f"sum {cell1} {cell2}")
+
+ def action_math_helper(self, title: str, op: str) -> None:
+ cell1 = "C1"
+ cell2 = "C2"
+ out = "C3"
+
+ current = self._table.currentItem()
+ if current:
+ out = SpreadSheetItem.encode_pos(self._table.currentRow(), self._table.currentColumn())
+
+ if self.run_input_dialog(title, "Cell 1", "Cell 2", op, "Output to:", cell1, cell2, out):
+ row, col = SpreadSheetItem.decode_pos(out)
+ self._table.item(row, col).setText(f"{op} {cell1} {cell2}")
+
+ @Slot()
+ def action_add(self) -> None:
+ self.action_math_helper("Addition", "+")
+
+ @Slot()
+ def action_subtract(self) -> None:
+ self.action_math_helper("Subtraction", "-")
+
+ @Slot()
+ def action_multiply(self) -> None:
+ self.action_math_helper("Multiplication", "*")
+
+ @Slot()
+ def action_divide(self) -> None:
+ self.action_math_helper("Division", "/")
+
+ @Slot()
+ def clear(self) -> None:
+ selected_items = self._table.selectedItems()
+ for item in selected_items:
+ item.setText("")
+
+ def setup_context_menu(self) -> None:
+ self.addAction(self._cell_add_action)
+ self.addAction(self._cell_sub_action)
+ self.addAction(self._cell_mul_action)
+ self.addAction(self._cell_div_action)
+ self.addAction(self._cell_sum_action)
+ self.addAction(self._first_separator)
+ self.addAction(self._color_action)
+ self.addAction(self._font_action)
+ self.addAction(self._second_separator)
+ self.addAction(self._clear_action)
+ self.setContextMenuPolicy(Qt.ActionsContextMenu)
+
+ def setup_contents(self) -> None:
+ title_background = QBrush(Qt.lightGray)
+ title_font = self._table.font()
+ title_font.setBold(True)
+
+ # column 0
+ self._table.setItem(0, 0, SpreadSheetItem("Item"))
+ self._table.item(0, 0).setBackground(title_background)
+ self._table.item(0, 0).setToolTip(
+ "This column shows the purchased item/service"
+ )
+ self._table.item(0, 0).setFont(title_font)
+
+ self._table.setItem(1, 0, SpreadSheetItem("AirportBus"))
+ self._table.setItem(2, 0, SpreadSheetItem("Flight (Munich)"))
+ self._table.setItem(3, 0, SpreadSheetItem("Lunch"))
+ self._table.setItem(4, 0, SpreadSheetItem("Flight (LA)"))
+ self._table.setItem(5, 0, SpreadSheetItem("Taxi"))
+ self._table.setItem(6, 0, SpreadSheetItem("Dinner"))
+ self._table.setItem(7, 0, SpreadSheetItem("Hotel"))
+ self._table.setItem(8, 0, SpreadSheetItem("Flight (Oslo)"))
+ self._table.setItem(9, 0, SpreadSheetItem("Total:"))
+
+ self._table.item(9, 0).setFont(title_font)
+ self._table.item(9, 0).setBackground(title_background)
+
+ # column 1
+ self._table.setItem(0, 1, SpreadSheetItem("Date"))
+ self._table.item(0, 1).setBackground(title_background)
+ self._table.item(0, 1).setToolTip(
+ "This column shows the purchase date, double click to change"
+ )
+ self._table.item(0, 1).setFont(title_font)
+
+ self._table.setItem(1, 1, SpreadSheetItem("15/6/2006"))
+ self._table.setItem(2, 1, SpreadSheetItem("15/6/2006"))
+ self._table.setItem(3, 1, SpreadSheetItem("15/6/2006"))
+ self._table.setItem(4, 1, SpreadSheetItem("21/5/2006"))
+ self._table.setItem(5, 1, SpreadSheetItem("16/6/2006"))
+ self._table.setItem(6, 1, SpreadSheetItem("16/6/2006"))
+ self._table.setItem(7, 1, SpreadSheetItem("16/6/2006"))
+ self._table.setItem(8, 1, SpreadSheetItem("18/6/2006"))
+
+ self._table.setItem(9, 1, SpreadSheetItem())
+ self._table.item(9, 1).setBackground(title_background)
+
+ # column 2
+ self._table.setItem(0, 2, SpreadSheetItem("Price"))
+ self._table.item(0, 2).setBackground(title_background)
+ self._table.item(0, 2).setToolTip("This column shows the price of the purchase")
+ self._table.item(0, 2).setFont(title_font)
+
+ self._table.setItem(1, 2, SpreadSheetItem("150"))
+ self._table.setItem(2, 2, SpreadSheetItem("2350"))
+ self._table.setItem(3, 2, SpreadSheetItem("-14"))
+ self._table.setItem(4, 2, SpreadSheetItem("980"))
+ self._table.setItem(5, 2, SpreadSheetItem("5"))
+ self._table.setItem(6, 2, SpreadSheetItem("120"))
+ self._table.setItem(7, 2, SpreadSheetItem("300"))
+ self._table.setItem(8, 2, SpreadSheetItem("1240"))
+
+ self._table.setItem(9, 2, SpreadSheetItem())
+ self._table.item(9, 2).setBackground(Qt.lightGray)
+
+ # column 3
+ self._table.setItem(0, 3, SpreadSheetItem("Currency"))
+ self._table.item(0, 3).setBackground(title_background)
+ self._table.item(0, 3).setToolTip("This column shows the currency")
+ self._table.item(0, 3).setFont(title_font)
+
+ self._table.setItem(1, 3, SpreadSheetItem("NOK"))
+ self._table.setItem(2, 3, SpreadSheetItem("NOK"))
+ self._table.setItem(3, 3, SpreadSheetItem("EUR"))
+ self._table.setItem(4, 3, SpreadSheetItem("EUR"))
+ self._table.setItem(5, 3, SpreadSheetItem("USD"))
+ self._table.setItem(6, 3, SpreadSheetItem("USD"))
+ self._table.setItem(7, 3, SpreadSheetItem("USD"))
+ self._table.setItem(8, 3, SpreadSheetItem("USD"))
+
+ self._table.setItem(9, 3, SpreadSheetItem())
+ self._table.item(9, 3).setBackground(Qt.lightGray)
+
+ # column 4
+ self._table.setItem(0, 4, SpreadSheetItem("Ex. Rate"))
+ self._table.item(0, 4).setBackground(title_background)
+ self._table.item(0, 4).setToolTip("This column shows the exchange rate to NOK")
+ self._table.item(0, 4).setFont(title_font)
+
+ self._table.setItem(1, 4, SpreadSheetItem("1"))
+ self._table.setItem(2, 4, SpreadSheetItem("1"))
+ self._table.setItem(3, 4, SpreadSheetItem("8"))
+ self._table.setItem(4, 4, SpreadSheetItem("8"))
+ self._table.setItem(5, 4, SpreadSheetItem("7"))
+ self._table.setItem(6, 4, SpreadSheetItem("7"))
+ self._table.setItem(7, 4, SpreadSheetItem("7"))
+ self._table.setItem(8, 4, SpreadSheetItem("7"))
+
+ self._table.setItem(9, 4, SpreadSheetItem())
+ self._table.item(9, 4).setBackground(title_background)
+
+ # column 5
+ self._table.setItem(0, 5, SpreadSheetItem("NOK"))
+ self._table.item(0, 5).setBackground(title_background)
+ self._table.item(0, 5).setToolTip("This column shows the expenses in NOK")
+ self._table.item(0, 5).setFont(title_font)
+
+ self._table.setItem(1, 5, SpreadSheetItem("* C2 E2"))
+ self._table.setItem(2, 5, SpreadSheetItem("* C3 E3"))
+ self._table.setItem(3, 5, SpreadSheetItem("* C4 E4"))
+ self._table.setItem(4, 5, SpreadSheetItem("* C5 E5"))
+ self._table.setItem(5, 5, SpreadSheetItem("* C6 E6"))
+ self._table.setItem(6, 5, SpreadSheetItem("* C7 E7"))
+ self._table.setItem(7, 5, SpreadSheetItem("* C8 E8"))
+ self._table.setItem(8, 5, SpreadSheetItem("* C9 E9"))
+
+ self._table.setItem(9, 5, SpreadSheetItem("sum F2 F9"))
+ self._table.item(9, 5).setBackground(title_background)
+
+ @Slot()
+ def show_about(self) -> None:
+ html_text = (
+ "<HTML>"
+ "<p><b>This demo shows use of <c>QTableWidget</c> with custom handling for"
+ " individual cells.</b></p>"
+ "<p>Using a customized table item we make it possible to have dynamic"
+ " output in different cells. The content that is implemented for this"
+ " particular demo is:"
+ "<ul>"
+ "<li>Adding two cells.</li>"
+ "<li>Subtracting one cell from another.</li>"
+ "<li>Multiplying two cells.</li>"
+ "<li>Dividing one cell with another.</li>"
+ "<li>Summing the contents of an arbitrary number of cells.</li>"
+ "</HTML>")
+ QMessageBox.about(self, "About Spreadsheet", html_text)
diff --git a/examples/widgets/itemviews/spreadsheet/spreadsheetdelegate.py b/examples/widgets/itemviews/spreadsheet/spreadsheetdelegate.py
new file mode 100644
index 000000000..57aba6f47
--- /dev/null
+++ b/examples/widgets/itemviews/spreadsheet/spreadsheetdelegate.py
@@ -0,0 +1,67 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+from PySide6.QtCore import (QAbstractItemModel, QDate, QModelIndex, QObject,
+ QStringListModel, Qt, Slot)
+from PySide6.QtWidgets import (QCompleter, QDateTimeEdit, QLineEdit,
+ QStyleOptionViewItem, QStyledItemDelegate, QWidget)
+
+from typing import Optional
+
+
+class SpreadSheetDelegate(QStyledItemDelegate):
+ def __init__(self, parent: Optional[QObject] = None) -> None:
+ super().__init__(parent)
+
+ def create_editor(self, parent: QWidget,
+ option: QStyleOptionViewItem,
+ index: QModelIndex) -> QWidget:
+ if index.column() == 1:
+ editor = QDateTimeEdit(parent)
+ editor.setDisplayFormat("dd/M/yyyy")
+ editor.setCalendarPopup(True)
+ return editor
+
+ editor = QLineEdit(parent)
+
+ # create a completer with the strings in the column as model
+ allStrings = QStringListModel()
+ for i in range(1, index.model().rowCount()):
+ strItem = str(index.model().data(index.sibling(i, index.column()), Qt.EditRole))
+
+ if not allStrings.contains(strItem):
+ allStrings.append(strItem)
+
+ autoComplete = QCompleter(allStrings)
+ editor.setCompleter(autoComplete)
+ editor.editingFinished.connect(SpreadSheetDelegate.commit_and_close_editor)
+ return editor
+
+ @Slot()
+ def commit_and_close_editor(self) -> None:
+ editor = self.sender()
+ self.commitData.emit(editor)
+ self.closeEditor.emit(editor)
+
+ def set_editor_data(self, editor: QWidget, index: QModelIndex) -> None:
+ edit = QLineEdit(editor)
+ if edit:
+ edit.setText(str(index.model().data(index, Qt.EditRole)))
+ return
+
+ dateEditor = QDateTimeEdit(editor)
+ if dateEditor:
+ dateEditor.setDate(
+ QDate.fromString(
+ str(index.model().data(index, Qt.EditRole)), "d/M/yyyy"))
+
+ def set_model_data(self, editor: QWidget,
+ model: QAbstractItemModel, index: QModelIndex) -> None:
+ edit = QLineEdit(editor)
+ if edit:
+ model.setData(index, edit.text())
+ return
+
+ dateEditor = QDateTimeEdit(editor)
+ if dateEditor:
+ model.setData(index, dateEditor.date().toString("dd/M/yyyy"))
diff --git a/examples/widgets/itemviews/spreadsheet/spreadsheetitem.py b/examples/widgets/itemviews/spreadsheet/spreadsheetitem.py
new file mode 100644
index 000000000..dc70da883
--- /dev/null
+++ b/examples/widgets/itemviews/spreadsheet/spreadsheetitem.py
@@ -0,0 +1,122 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+from typing import Any, Tuple
+from PySide6.QtCore import QMetaType, Qt
+from PySide6.QtWidgets import QTableWidget, QTableWidgetItem
+
+
+class SpreadSheetItem(QTableWidgetItem):
+ is_resolving = False
+
+ def __init_subclass__(cls) -> None:
+ return super().__init_subclass__()
+
+ def data(self, role: int) -> Any:
+ if role == Qt.EditRole or role == Qt.StatusTipRole:
+ return self.formula()
+
+ if role == Qt.DisplayRole:
+ return self.display()
+
+ t = str(self.display())
+
+ if role == Qt.ForegroundRole:
+ try:
+ number = int(t)
+ color = Qt.red if number < 0 else Qt.blue
+ except ValueError:
+ color = Qt.black
+ return color
+
+ if role == Qt.TextAlignmentRole:
+ if t and (t[0].isdigit() or t[0] == '-'):
+ return int(Qt.AlignRight | Qt.AlignVCenter)
+
+ return super().data(role)
+
+ def setData(self, role: int, value: Any) -> None:
+ super().setData(role, value)
+ if self.tableWidget():
+ self.tableWidget().viewport().update()
+
+ def display(self) -> QMetaType.Type.QVariant:
+ # avoid circular dependencies
+ if self.is_resolving:
+ return QMetaType.Type.QVariant
+
+ self.is_resolving = True
+ result = self.compute_formula(self.formula(), self.tableWidget(), self)
+ self.is_resolving = False
+ return result
+
+ def formula(self) -> None:
+ return str(super().data(Qt.DisplayRole))
+
+ def compute_formula(self, formula: str, widget: QTableWidget, this) -> QMetaType.Type.QVariant:
+ # check if the string is actually a formula or not
+ list_ = formula.split(' ')
+ if not list_ or not widget:
+ return formula # it is a normal string
+
+ op = list_[0].lower() if list_[0] else ""
+
+ first_row = -1
+ first_col = -1
+ second_row = -1
+ second_col = -1
+
+ if len(list_) > 1:
+ SpreadSheetItem.decode_pos(list_[1])
+
+ if len(list_) > 2:
+ SpreadSheetItem.decode_pos(list_[2])
+
+ start = widget.item(first_row, first_col)
+ end = widget.item(second_row, second_col)
+
+ first_val = int(start.text()) if start else 0
+ second_val = int(end.text()) if start else 0
+
+ if op == "sum":
+ sum = 0
+ for r in range(first_row, second_row + 1):
+ for c in range(first_col, second_col + 1):
+ table_item = widget.item(r, c)
+ if table_item and table_item != this:
+ sum += int(table_item.text())
+
+ result = sum
+ elif op == "+":
+ result = first_val + second_val
+ elif op == "-":
+ result = first_val - second_val
+ elif op == "*":
+ result = first_val * second_val
+ elif op == "/":
+ if second_val == 0:
+ result = "nan"
+ else:
+ result = first_val / second_val
+ elif op == "=":
+ if start:
+ result = start.text()
+ else:
+ result = formula
+
+ return result
+
+ def decode_pos(pos: str) -> Tuple[int, int]:
+ if (not pos):
+ col = -1
+ row = -1
+ else:
+ col = ord(pos[0].encode("latin1")) - ord('A')
+ try:
+ row = int(pos[1:]) - 1
+ except ValueError:
+ row = -1
+ return row, col
+
+ def encode_pos(row: int, col: int) -> str:
+ return str(chr(col + ord('A'))) + str(row + 1)