aboutsummaryrefslogtreecommitdiffstats
path: root/examples/widgets/itemviews
diff options
context:
space:
mode:
Diffstat (limited to 'examples/widgets/itemviews')
-rw-r--r--examples/widgets/itemviews/address_book/adddialogwidget.py65
-rw-r--r--examples/widgets/itemviews/address_book/address_book.py99
-rw-r--r--examples/widgets/itemviews/address_book/address_book.pyproject4
-rw-r--r--examples/widgets/itemviews/address_book/addresswidget.py215
-rw-r--r--examples/widgets/itemviews/address_book/doc/address_book.pngbin0 -> 4922 bytes
-rw-r--r--examples/widgets/itemviews/address_book/doc/address_book.rst9
-rw-r--r--examples/widgets/itemviews/address_book/newaddresstab.py56
-rw-r--r--examples/widgets/itemviews/address_book/tablemodel.py (renamed from examples/widgets/itemviews/addressbook/tablemodel.py)57
-rw-r--r--examples/widgets/itemviews/addressbook/adddialogwidget.py102
-rw-r--r--examples/widgets/itemviews/addressbook/addressbook.py130
-rw-r--r--examples/widgets/itemviews/addressbook/addressbook.pyproject4
-rw-r--r--examples/widgets/itemviews/addressbook/addresswidget.py247
-rw-r--r--examples/widgets/itemviews/addressbook/newaddresstab.py93
-rw-r--r--examples/widgets/itemviews/basicfiltermodel/basicsortfiltermodel.py178
-rw-r--r--examples/widgets/itemviews/basicfiltermodel/basicsortfiltermodel.pyproject3
-rw-r--r--examples/widgets/itemviews/basicsortfiltermodel.py202
-rw-r--r--examples/widgets/itemviews/dirview/dirview.py59
-rw-r--r--examples/widgets/itemviews/dirview/dirview.pyproject3
-rw-r--r--examples/widgets/itemviews/dirview/doc/dirview.rst5
-rw-r--r--examples/widgets/itemviews/editabletreemodel/default.txt40
-rw-r--r--examples/widgets/itemviews/editabletreemodel/doc/editabletreemodel.pngbin0 -> 111556 bytes
-rw-r--r--examples/widgets/itemviews/editabletreemodel/doc/editabletreemodel.rst10
-rw-r--r--examples/widgets/itemviews/editabletreemodel/editabletreemodel.pyproject7
-rw-r--r--examples/widgets/itemviews/editabletreemodel/main.py14
-rw-r--r--examples/widgets/itemviews/editabletreemodel/mainwindow.py163
-rw-r--r--examples/widgets/itemviews/editabletreemodel/treeitem.py94
-rw-r--r--examples/widgets/itemviews/editabletreemodel/treemodel.py199
-rw-r--r--examples/widgets/itemviews/fetchmore.py147
-rw-r--r--examples/widgets/itemviews/fetchmore/fetchmore.py137
-rw-r--r--examples/widgets/itemviews/fetchmore/fetchmore.pyproject3
-rw-r--r--examples/widgets/itemviews/itemviews.pyproject3
-rw-r--r--examples/widgets/itemviews/jsonmodel/doc/jsonmodel.pngbin0 -> 14837 bytes
-rw-r--r--examples/widgets/itemviews/jsonmodel/doc/jsonmodel.rst8
-rw-r--r--examples/widgets/itemviews/jsonmodel/example.json26
-rw-r--r--examples/widgets/itemviews/jsonmodel/jsonmodel.py320
-rw-r--r--examples/widgets/itemviews/jsonmodel/jsonmodel.pyproject3
-rw-r--r--examples/widgets/itemviews/spinboxdelegate/doc/spinboxdelegate.rst5
-rw-r--r--examples/widgets/itemviews/spinboxdelegate/spinboxdelegate.py79
-rw-r--r--examples/widgets/itemviews/spinboxdelegate/spinboxdelegate.pyproject3
-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
-rw-r--r--examples/widgets/itemviews/stardelegate/doc/stardelegate.pngbin0 -> 22482 bytes
-rw-r--r--examples/widgets/itemviews/stardelegate/doc/stardelegate.rst10
-rw-r--r--examples/widgets/itemviews/stardelegate/stardelegate.py109
-rw-r--r--examples/widgets/itemviews/stardelegate/stareditor.py80
-rw-r--r--examples/widgets/itemviews/stardelegate/starrating.py86
50 files changed, 2670 insertions, 1169 deletions
diff --git a/examples/widgets/itemviews/address_book/adddialogwidget.py b/examples/widgets/itemviews/address_book/adddialogwidget.py
new file mode 100644
index 000000000..ecb853e80
--- /dev/null
+++ b/examples/widgets/itemviews/address_book/adddialogwidget.py
@@ -0,0 +1,65 @@
+# Copyright (C) 2011 Arun Srinivasan <rulfzid@gmail.com>
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+from PySide6.QtCore import Qt
+from PySide6.QtWidgets import (QDialog, QLabel, QTextEdit, QLineEdit,
+ QDialogButtonBox, QGridLayout, QVBoxLayout)
+
+
+class AddDialogWidget(QDialog):
+ """ A dialog to add a new address to the addressbook. """
+
+ def __init__(self, parent=None):
+ super().__init__(parent)
+
+ name_label = QLabel("Name")
+ address_label = QLabel("Address")
+ button_box = QDialogButtonBox(QDialogButtonBox.Ok
+ | QDialogButtonBox.Cancel)
+
+ self._name_text = QLineEdit()
+ self._address_text = QTextEdit()
+
+ grid = QGridLayout()
+ grid.setColumnStretch(1, 2)
+ grid.addWidget(name_label, 0, 0)
+ grid.addWidget(self._name_text, 0, 1)
+ grid.addWidget(address_label, 1, 0, Qt.AlignLeft | Qt.AlignTop)
+ grid.addWidget(self._address_text, 1, 1, Qt.AlignLeft)
+
+ layout = QVBoxLayout()
+ layout.addLayout(grid)
+ layout.addWidget(button_box)
+
+ self.setLayout(layout)
+
+ self.setWindowTitle("Add a Contact")
+
+ button_box.accepted.connect(self.accept)
+ button_box.rejected.connect(self.reject)
+
+ # These properties make using this dialog a little cleaner. It's much
+ # nicer to type "addDialog.address" to retrieve the address as compared
+ # to "addDialog.addressText.toPlainText()"
+ @property
+ def name(self):
+ return self._name_text.text()
+
+ @property
+ def address(self):
+ return self._address_text.toPlainText()
+
+
+if __name__ == "__main__":
+ import sys
+ from PySide6.QtWidgets import QApplication
+
+ app = QApplication(sys.argv)
+
+ dialog = AddDialogWidget()
+ if (dialog.exec()):
+ name = dialog.name
+ address = dialog.address
+ print(f"Name: {name}")
+ print(f"Address: {address}")
diff --git a/examples/widgets/itemviews/address_book/address_book.py b/examples/widgets/itemviews/address_book/address_book.py
new file mode 100644
index 000000000..af0cf3dee
--- /dev/null
+++ b/examples/widgets/itemviews/address_book/address_book.py
@@ -0,0 +1,99 @@
+# Copyright (C) 2011 Arun Srinivasan <rulfzid@gmail.com>
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+from PySide6.QtCore import Slot
+from PySide6.QtGui import QAction
+from PySide6.QtWidgets import (QMainWindow, QFileDialog, QApplication)
+
+from addresswidget import AddressWidget
+
+
+class MainWindow(QMainWindow):
+
+ def __init__(self, parent=None):
+ super().__init__(parent)
+
+ self._address_widget = AddressWidget()
+ self.setCentralWidget(self._address_widget)
+ self.create_menus()
+ self.setWindowTitle("Address Book")
+
+ def create_menus(self):
+ # Create the main menuBar menu items
+ file_menu = self.menuBar().addMenu("&File")
+ tool_menu = self.menuBar().addMenu("&Tools")
+
+ # Populate the File menu
+ self.open_action = self.create_action("&Open...", file_menu, self.open_file)
+ self.save_action = self.create_action("&Save As...", file_menu, self.save_file)
+ file_menu.addSeparator()
+ self.exit_action = self.create_action("E&xit", file_menu, self.close)
+
+ # Populate the Tools menu
+ self.add_action = self.create_action(
+ "&Add Entry...", tool_menu, self._address_widget.add_entry)
+ self._edit_action = self.create_action(
+ "&Edit Entry...", tool_menu, self._address_widget.edit_entry)
+ tool_menu.addSeparator()
+ self._remove_action = self.create_action(
+ "&Remove Entry", tool_menu, self._address_widget.remove_entry)
+
+ # Disable the edit and remove menu items initially, as there are
+ # no items yet.
+ self._edit_action.setEnabled(False)
+ self._remove_action.setEnabled(False)
+
+ # Wire up the updateActions slot
+ self._address_widget.selection_changed.connect(self.update_actions)
+
+ def create_action(self, text, menu, slot):
+ """ Helper function to save typing when populating menus
+ with action.
+ """
+ action = QAction(text, self)
+ menu.addAction(action)
+ action.triggered.connect(slot)
+ return action
+
+ # Quick gotcha:
+ #
+ # QFiledialog.getOpenFilename and QFileDialog.get.SaveFileName don't
+ # behave in PySide6 as they do in Qt, where they return a QString
+ # containing the filename.
+ #
+ # In PySide6, these functions return a tuple: (filename, filter)
+
+ @Slot()
+ def open_file(self):
+ filename, _ = QFileDialog.getOpenFileName(self)
+ if filename:
+ self._address_widget.read_from_file(filename)
+
+ @Slot()
+ def save_file(self):
+ filename, _ = QFileDialog.getSaveFileName(self)
+ if filename:
+ self._address_widget.write_to_file(filename)
+
+ def update_actions(self, selection):
+ """ Only allow the user to remove or edit an item if an item
+ is actually selected.
+ """
+ indexes = selection.indexes()
+
+ if len(indexes) > 0:
+ self._remove_action.setEnabled(True)
+ self._edit_action.setEnabled(True)
+ else:
+ self._remove_action.setEnabled(False)
+ self._edit_action.setEnabled(False)
+
+
+if __name__ == "__main__":
+ """ Run the application. """
+ import sys
+ app = QApplication(sys.argv)
+ mw = MainWindow()
+ mw.show()
+ sys.exit(app.exec())
diff --git a/examples/widgets/itemviews/address_book/address_book.pyproject b/examples/widgets/itemviews/address_book/address_book.pyproject
new file mode 100644
index 000000000..1b5dd597c
--- /dev/null
+++ b/examples/widgets/itemviews/address_book/address_book.pyproject
@@ -0,0 +1,4 @@
+{
+ "files": ["tablemodel.py", "address_book.py", "adddialogwidget.py",
+ "addresswidget.py", "newaddresstab.py"]
+}
diff --git a/examples/widgets/itemviews/address_book/addresswidget.py b/examples/widgets/itemviews/address_book/addresswidget.py
new file mode 100644
index 000000000..cb2f46ea1
--- /dev/null
+++ b/examples/widgets/itemviews/address_book/addresswidget.py
@@ -0,0 +1,215 @@
+# Copyright (C) 2011 Arun Srinivasan <rulfzid@gmail.com>
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+try:
+ import cpickle as pickle
+except ImportError:
+ import pickle
+
+from PySide6.QtCore import (Qt, Signal, Slot, QRegularExpression, QModelIndex,
+ QItemSelection, QSortFilterProxyModel)
+from PySide6.QtWidgets import QTabWidget, QMessageBox, QTableView, QAbstractItemView
+
+from tablemodel import TableModel
+from newaddresstab import NewAddressTab
+from adddialogwidget import AddDialogWidget
+
+
+class AddressWidget(QTabWidget):
+ """ The central widget of the application. Most of the addressbook's
+ functionality is contained in this class.
+ """
+
+ selection_changed = Signal(QItemSelection)
+
+ def __init__(self, parent=None):
+ """ Initialize the AddressWidget. """
+ super().__init__(parent)
+
+ self._table_model = TableModel()
+ self._new_address_tab = NewAddressTab()
+ self._new_address_tab.send_details.connect(self.add_entry)
+
+ self.addTab(self._new_address_tab, "Address Book")
+
+ self.setup_tabs()
+
+ @Slot()
+ def add_entry(self, name=None, address=None):
+ """ Add an entry to the addressbook. """
+ if name is None and address is None:
+ add_dialog = AddDialogWidget()
+
+ if add_dialog.exec():
+ name = add_dialog.name
+ address = add_dialog.address
+
+ address = {"name": name, "address": address}
+ addresses = self._table_model.addresses[:]
+
+ # The QT docs for this example state that what we're doing here
+ # is checking if the entered name already exists. What they
+ # (and we here) are actually doing is checking if the whole
+ # name/address pair exists already - ok for the purposes of this
+ # example, but obviously not how a real addressbook application
+ # should behave.
+ try:
+ addresses.remove(address)
+ QMessageBox.information(self, "Duplicate Name",
+ f'The name "{name}" already exists.')
+ except ValueError:
+ # The address didn't already exist, so let's add it to the model.
+
+ # Step 1: create the row
+ self._table_model.insertRows(0)
+
+ # Step 2: get the index of the newly created row and use it.
+ # to set the name
+ ix = self._table_model.index(0, 0, QModelIndex())
+ self._table_model.setData(ix, address["name"], Qt.EditRole)
+
+ # Step 3: lather, rinse, repeat for the address.
+ ix = self._table_model.index(0, 1, QModelIndex())
+ self._table_model.setData(ix, address["address"], Qt.EditRole)
+
+ # Remove the newAddressTab, as we now have at least one
+ # address in the model.
+ self.removeTab(self.indexOf(self._new_address_tab))
+
+ # The screenshot for the QT example shows nicely formatted
+ # multiline cells, but the actual application doesn't behave
+ # quite so nicely, at least on Ubuntu. Here we resize the newly
+ # created row so that multiline addresses look reasonable.
+ table_view = self.currentWidget()
+ table_view.resizeRowToContents(ix.row())
+
+ @Slot()
+ def edit_entry(self):
+ """ Edit an entry in the addressbook. """
+ table_view = self.currentWidget()
+ proxy_model = table_view.model()
+ selection_model = table_view.selectionModel()
+
+ # Get the name and address of the currently selected row.
+ indexes = selection_model.selectedRows()
+ if len(indexes) != 1:
+ return
+
+ row = proxy_model.mapToSource(indexes[0]).row()
+ ix = self._table_model.index(row, 0, QModelIndex())
+ name = self._table_model.data(ix, Qt.DisplayRole)
+ ix = self._table_model.index(row, 1, QModelIndex())
+ address = self._table_model.data(ix, Qt.DisplayRole)
+
+ # Open an addDialogWidget, and only allow the user to edit the address.
+ add_dialog = AddDialogWidget()
+ add_dialog.setWindowTitle("Edit a Contact")
+
+ add_dialog._name_text.setReadOnly(True)
+ add_dialog._name_text.setText(name)
+ add_dialog._address_text.setText(address)
+
+ # If the address is different, add it to the model.
+ if add_dialog.exec():
+ new_address = add_dialog.address
+ if new_address != address:
+ ix = self._table_model.index(row, 1, QModelIndex())
+ self._table_model.setData(ix, new_address, Qt.EditRole)
+
+ @Slot()
+ def remove_entry(self):
+ """ Remove an entry from the addressbook. """
+ table_view = self.currentWidget()
+ proxy_model = table_view.model()
+ selection_model = table_view.selectionModel()
+
+ # Just like editEntry, but this time remove the selected row.
+ indexes = selection_model.selectedRows()
+
+ for index in indexes:
+ row = proxy_model.mapToSource(index).row()
+ self._table_model.removeRows(row)
+
+ # If we've removed the last address in the model, display the
+ # newAddressTab
+ if self._table_model.rowCount() == 0:
+ self.insertTab(0, self._new_address_tab, "Address Book")
+
+ def setup_tabs(self):
+ """ Setup the various tabs in the AddressWidget. """
+ groups = ["ABC", "DEF", "GHI", "JKL", "MNO", "PQR", "STU", "VW", "XYZ"]
+
+ for group in groups:
+ proxy_model = QSortFilterProxyModel(self)
+ proxy_model.setSourceModel(self._table_model)
+ proxy_model.setDynamicSortFilter(True)
+
+ table_view = QTableView()
+ table_view.setModel(proxy_model)
+ table_view.setSortingEnabled(True)
+ table_view.setSelectionBehavior(QAbstractItemView.SelectRows)
+ table_view.horizontalHeader().setStretchLastSection(True)
+ table_view.verticalHeader().hide()
+ table_view.setEditTriggers(QAbstractItemView.NoEditTriggers)
+ table_view.setSelectionMode(QAbstractItemView.SingleSelection)
+
+ # This here be the magic: we use the group name (e.g. "ABC") to
+ # build the regex for the QSortFilterProxyModel for the group's
+ # tab. The regex will end up looking like "^[ABC].*", only
+ # allowing this tab to display items where the name starts with
+ # "A", "B", or "C". Notice that we set it to be case-insensitive.
+ re = QRegularExpression(f"^[{group}].*")
+ assert re.isValid()
+ re.setPatternOptions(QRegularExpression.CaseInsensitiveOption)
+ proxy_model.setFilterRegularExpression(re)
+ proxy_model.setFilterKeyColumn(0) # Filter on the "name" column
+ proxy_model.sort(0, Qt.AscendingOrder)
+
+ # This prevents an application crash (see:
+ # https://www.qtcentre.org/threads/58874-QListView-SelectionModel-selectionChanged-Crash) # noqa: E501
+ self.viewselectionmodel = table_view.selectionModel()
+ table_view.selectionModel().selectionChanged.connect(self.selection_changed)
+
+ self.addTab(table_view, group)
+
+ # Note: the QT example uses a QDataStream for the saving and loading.
+ # Here we're using a python dictionary to store the addresses, which
+ # can't be streamed using QDataStream, so we just use cpickle for this
+ # example.
+ def read_from_file(self, filename):
+ """ Read contacts in from a file. """
+ try:
+ f = open(filename, "rb")
+ addresses = pickle.load(f)
+ except IOError:
+ QMessageBox.information(self, f"Unable to open file: {filename}")
+ finally:
+ f.close()
+
+ if len(addresses) == 0:
+ QMessageBox.information(self, f"No contacts in file: {filename}")
+ else:
+ for address in addresses:
+ self.add_entry(address["name"], address["address"])
+
+ def write_to_file(self, filename):
+ """ Save all contacts in the model to a file. """
+ try:
+ f = open(filename, "wb")
+ pickle.dump(self._table_model.addresses, f)
+
+ except IOError:
+ QMessageBox.information(self, f"Unable to open file: {filename}")
+ finally:
+ f.close()
+
+
+if __name__ == "__main__":
+ import sys
+ from PySide6.QtWidgets import QApplication
+
+ app = QApplication(sys.argv)
+ address_widget = AddressWidget()
+ address_widget.show()
+ sys.exit(app.exec())
diff --git a/examples/widgets/itemviews/address_book/doc/address_book.png b/examples/widgets/itemviews/address_book/doc/address_book.png
new file mode 100644
index 000000000..40f2d9e63
--- /dev/null
+++ b/examples/widgets/itemviews/address_book/doc/address_book.png
Binary files differ
diff --git a/examples/widgets/itemviews/address_book/doc/address_book.rst b/examples/widgets/itemviews/address_book/doc/address_book.rst
new file mode 100644
index 000000000..04b91054e
--- /dev/null
+++ b/examples/widgets/itemviews/address_book/doc/address_book.rst
@@ -0,0 +1,9 @@
+Address Book Example
+====================
+
+The address book example shows how to use proxy models to display different
+views onto data from a single model.
+
+.. image:: address_book.png
+ :width: 400
+ :alt: Address Book Screenshot
diff --git a/examples/widgets/itemviews/address_book/newaddresstab.py b/examples/widgets/itemviews/address_book/newaddresstab.py
new file mode 100644
index 000000000..d3d037ad4
--- /dev/null
+++ b/examples/widgets/itemviews/address_book/newaddresstab.py
@@ -0,0 +1,56 @@
+# Copyright (C) 2011 Arun Srinivasan <rulfzid@gmail.com>
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+from PySide6.QtCore import (Qt, Signal)
+from PySide6.QtWidgets import (QWidget, QLabel, QPushButton, QVBoxLayout)
+
+from adddialogwidget import AddDialogWidget
+
+
+class NewAddressTab(QWidget):
+ """ An extra tab that prompts the user to add new contacts.
+ To be displayed only when there are no contacts in the model.
+ """
+
+ send_details = Signal(str, str)
+
+ def __init__(self, parent=None):
+ super().__init__(parent)
+
+ description_label = QLabel("There are no contacts in your address book."
+ "\nClick Add to add new contacts.")
+
+ add_button = QPushButton("Add")
+
+ layout = QVBoxLayout()
+ layout.addWidget(description_label)
+ layout.addWidget(add_button, 0, Qt.AlignCenter)
+
+ self.setLayout(layout)
+
+ add_button.clicked.connect(self.add_entry)
+
+ def add_entry(self):
+ add_dialog = AddDialogWidget()
+
+ if add_dialog.exec():
+ name = add_dialog.name
+ address = add_dialog.address
+ self.send_details.emit(name, address)
+
+
+if __name__ == "__main__":
+
+ def print_address(name, address):
+ print(f"Name: {name}")
+ print(f"Address: {address}")
+
+ import sys
+ from PySide6.QtWidgets import QApplication
+
+ app = QApplication(sys.argv)
+ new_address_tab = NewAddressTab()
+ new_address_tab.send_details.connect(print_address)
+ new_address_tab.show()
+ sys.exit(app.exec())
diff --git a/examples/widgets/itemviews/addressbook/tablemodel.py b/examples/widgets/itemviews/address_book/tablemodel.py
index 155f09131..3c1dbd4cc 100644
--- a/examples/widgets/itemviews/addressbook/tablemodel.py
+++ b/examples/widgets/itemviews/address_book/tablemodel.py
@@ -1,51 +1,14 @@
+# Copyright (C) 2011 Arun Srinivasan <rulfzid@gmail.com>
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+from PySide6.QtCore import (Qt, QAbstractTableModel, QModelIndex)
-#############################################################################
-##
-## Copyright (C) 2011 Arun Srinivasan <rulfzid@gmail.com>
-## Copyright (C) 2016 The Qt Company Ltd.
-## Contact: http://www.qt.io/licensing/
-##
-## This file is part of the Qt for Python examples of the Qt Toolkit.
-##
-## $QT_BEGIN_LICENSE:BSD$
-## You may use this file under the terms of the BSD license as follows:
-##
-## "Redistribution and use in source and binary forms, with or without
-## modification, are permitted provided that the following conditions are
-## met:
-## * Redistributions of source code must retain the above copyright
-## notice, this list of conditions and the following disclaimer.
-## * Redistributions in binary form must reproduce the above copyright
-## notice, this list of conditions and the following disclaimer in
-## the documentation and/or other materials provided with the
-## distribution.
-## * Neither the name of The Qt Company Ltd nor the names of its
-## contributors may be used to endorse or promote products derived
-## from this software without specific prior written permission.
-##
-##
-## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
-##
-## $QT_END_LICENSE$
-##
-#############################################################################
-
-from PySide2.QtCore import (Qt, QAbstractTableModel, QModelIndex)
class TableModel(QAbstractTableModel):
def __init__(self, addresses=None, parent=None):
- super(TableModel, self).__init__(parent)
+ super().__init__(parent)
if addresses is None:
self.addresses = []
@@ -100,7 +63,7 @@ class TableModel(QAbstractTableModel):
self.beginInsertRows(QModelIndex(), position, position + rows - 1)
for row in range(rows):
- self.addresses.insert(position + row, {"name":"", "address":""})
+ self.addresses.insert(position + row, {"name": "", "address": ""})
self.endInsertRows()
return True
@@ -109,7 +72,7 @@ class TableModel(QAbstractTableModel):
""" Remove a row from the model. """
self.beginRemoveRows(QModelIndex(), position, position + rows - 1)
- del self.addresses[position:position+rows]
+ del self.addresses[position:position + rows]
self.endRemoveRows()
return True
@@ -142,5 +105,5 @@ class TableModel(QAbstractTableModel):
"""
if not index.isValid():
return Qt.ItemIsEnabled
- return Qt.ItemFlags(QAbstractTableModel.flags(self, index) |
- Qt.ItemIsEditable)
+ return Qt.ItemFlags(QAbstractTableModel.flags(self, index)
+ | Qt.ItemIsEditable)
diff --git a/examples/widgets/itemviews/addressbook/adddialogwidget.py b/examples/widgets/itemviews/addressbook/adddialogwidget.py
deleted file mode 100644
index 7991039ef..000000000
--- a/examples/widgets/itemviews/addressbook/adddialogwidget.py
+++ /dev/null
@@ -1,102 +0,0 @@
-
-#############################################################################
-##
-## Copyright (C) 2011 Arun Srinivasan <rulfzid@gmail.com>
-## Copyright (C) 2016 The Qt Company Ltd.
-## Contact: http://www.qt.io/licensing/
-##
-## This file is part of the Qt for Python examples of the Qt Toolkit.
-##
-## $QT_BEGIN_LICENSE:BSD$
-## You may use this file under the terms of the BSD license as follows:
-##
-## "Redistribution and use in source and binary forms, with or without
-## modification, are permitted provided that the following conditions are
-## met:
-## * Redistributions of source code must retain the above copyright
-## notice, this list of conditions and the following disclaimer.
-## * Redistributions in binary form must reproduce the above copyright
-## notice, this list of conditions and the following disclaimer in
-## the documentation and/or other materials provided with the
-## distribution.
-## * Neither the name of The Qt Company Ltd nor the names of its
-## contributors may be used to endorse or promote products derived
-## from this software without specific prior written permission.
-##
-##
-## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
-##
-## $QT_END_LICENSE$
-##
-#############################################################################
-
-from PySide2.QtCore import Qt
-from PySide2.QtWidgets import (QDialog, QLabel, QTextEdit, QLineEdit,
- QDialogButtonBox, QGridLayout, QVBoxLayout)
-
-class AddDialogWidget(QDialog):
- """ A dialog to add a new address to the addressbook. """
-
- def __init__(self, parent=None):
- super(AddDialogWidget, self).__init__(parent)
-
- nameLabel = QLabel("Name")
- addressLabel = QLabel("Address")
- buttonBox = QDialogButtonBox(QDialogButtonBox.Ok |
- QDialogButtonBox.Cancel)
-
- self.nameText = QLineEdit()
- self.addressText = QTextEdit()
-
- grid = QGridLayout()
- grid.setColumnStretch(1, 2)
- grid.addWidget(nameLabel, 0, 0)
- grid.addWidget(self.nameText, 0, 1)
- grid.addWidget(addressLabel, 1, 0, Qt.AlignLeft | Qt.AlignTop)
- grid.addWidget(self.addressText, 1, 1, Qt.AlignLeft)
-
- layout = QVBoxLayout()
- layout.addLayout(grid)
- layout.addWidget(buttonBox)
-
- self.setLayout(layout)
-
- self.setWindowTitle("Add a Contact")
-
- buttonBox.accepted.connect(self.accept)
- buttonBox.rejected.connect(self.reject)
-
- # These properties make using this dialog a little cleaner. It's much
- # nicer to type "addDialog.address" to retrieve the address as compared
- # to "addDialog.addressText.toPlainText()"
- @property
- def name(self):
- return self.nameText.text()
-
- @property
- def address(self):
- return self.addressText.toPlainText()
-
-
-if __name__ == "__main__":
- import sys
- from PySide2.QtWidgets import QApplication
-
- app = QApplication(sys.argv)
-
- dialog = AddDialogWidget()
- if (dialog.exec_()):
- name = dialog.name
- address = dialog.address
- print("Name:" + name)
- print("Address:" + address)
diff --git a/examples/widgets/itemviews/addressbook/addressbook.py b/examples/widgets/itemviews/addressbook/addressbook.py
deleted file mode 100644
index 262027a64..000000000
--- a/examples/widgets/itemviews/addressbook/addressbook.py
+++ /dev/null
@@ -1,130 +0,0 @@
-
-#############################################################################
-##
-## Copyright (C) 2011 Arun Srinivasan <rulfzid@gmail.com>
-## Copyright (C) 2016 The Qt Company Ltd.
-## Contact: http://www.qt.io/licensing/
-##
-## This file is part of the Qt for Python examples of the Qt Toolkit.
-##
-## $QT_BEGIN_LICENSE:BSD$
-## You may use this file under the terms of the BSD license as follows:
-##
-## "Redistribution and use in source and binary forms, with or without
-## modification, are permitted provided that the following conditions are
-## met:
-## * Redistributions of source code must retain the above copyright
-## notice, this list of conditions and the following disclaimer.
-## * Redistributions in binary form must reproduce the above copyright
-## notice, this list of conditions and the following disclaimer in
-## the documentation and/or other materials provided with the
-## distribution.
-## * Neither the name of The Qt Company Ltd nor the names of its
-## contributors may be used to endorse or promote products derived
-## from this software without specific prior written permission.
-##
-##
-## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
-##
-## $QT_END_LICENSE$
-##
-#############################################################################
-
-from PySide2.QtWidgets import (QMainWindow, QAction, QFileDialog, QApplication)
-
-from addresswidget import AddressWidget
-
-
-class MainWindow(QMainWindow):
-
- def __init__(self, parent=None):
- super(MainWindow, self).__init__(parent)
-
- self.addressWidget = AddressWidget()
- self.setCentralWidget(self.addressWidget)
- self.createMenus()
- self.setWindowTitle("Address Book")
-
- def createMenus(self):
- # Create the main menuBar menu items
- fileMenu = self.menuBar().addMenu("&File")
- toolMenu = self.menuBar().addMenu("&Tools")
-
- # Populate the File menu
- openAction = self.createAction("&Open...", fileMenu, self.openFile)
- saveAction = self.createAction("&Save As...", fileMenu, self.saveFile)
- fileMenu.addSeparator()
- exitAction = self.createAction("E&xit", fileMenu, self.close)
-
- # Populate the Tools menu
- addAction = self.createAction("&Add Entry...", toolMenu, self.addressWidget.addEntry)
- self.editAction = self.createAction("&Edit Entry...", toolMenu, self.addressWidget.editEntry)
- toolMenu.addSeparator()
- self.removeAction = self.createAction("&Remove Entry", toolMenu, self.addressWidget.removeEntry)
-
- # Disable the edit and remove menu items initially, as there are
- # no items yet.
- self.editAction.setEnabled(False)
- self.removeAction.setEnabled(False)
-
- # Wire up the updateActions slot
- self.addressWidget.selectionChanged.connect(self.updateActions)
-
- def createAction(self, text, menu, slot):
- """ Helper function to save typing when populating menus
- with action.
- """
- action = QAction(text, self)
- menu.addAction(action)
- action.triggered.connect(slot)
- return action
-
- # Quick gotcha:
- #
- # QFiledialog.getOpenFilename and QFileDialog.get.SaveFileName don't
- # behave in PySide2 as they do in Qt, where they return a QString
- # containing the filename.
- #
- # In PySide2, these functions return a tuple: (filename, filter)
-
- def openFile(self):
- filename, _ = QFileDialog.getOpenFileName(self)
- if filename:
- self.addressWidget.readFromFile(filename)
-
- def saveFile(self):
- filename, _ = QFileDialog.getSaveFileName(self)
- if filename:
- self.addressWidget.writeToFile(filename)
-
- def updateActions(self, selection):
- """ Only allow the user to remove or edit an item if an item
- is actually selected.
- """
- indexes = selection.indexes()
-
- if len(indexes) > 0:
- self.removeAction.setEnabled(True)
- self.editAction.setEnabled(True)
- else:
- self.removeAction.setEnabled(False)
- self.editAction.setEnabled(False)
-
-
-if __name__ == "__main__":
- """ Run the application. """
- import sys
- app = QApplication(sys.argv)
- mw = MainWindow()
- mw.show()
- sys.exit(app.exec_())
diff --git a/examples/widgets/itemviews/addressbook/addressbook.pyproject b/examples/widgets/itemviews/addressbook/addressbook.pyproject
deleted file mode 100644
index 2aa763753..000000000
--- a/examples/widgets/itemviews/addressbook/addressbook.pyproject
+++ /dev/null
@@ -1,4 +0,0 @@
-{
- "files": ["tablemodel.py", "addressbook.py", "adddialogwidget.py",
- "addresswidget.py", "newaddresstab.py"]
-}
diff --git a/examples/widgets/itemviews/addressbook/addresswidget.py b/examples/widgets/itemviews/addressbook/addresswidget.py
deleted file mode 100644
index b70b44b0a..000000000
--- a/examples/widgets/itemviews/addressbook/addresswidget.py
+++ /dev/null
@@ -1,247 +0,0 @@
-
-#############################################################################
-##
-## Copyright (C) 2011 Arun Srinivasan <rulfzid@gmail.com>
-## Copyright (C) 2016 The Qt Company Ltd.
-## Contact: http://www.qt.io/licensing/
-##
-## This file is part of the Qt for Python examples of the Qt Toolkit.
-##
-## $QT_BEGIN_LICENSE:BSD$
-## You may use this file under the terms of the BSD license as follows:
-##
-## "Redistribution and use in source and binary forms, with or without
-## modification, are permitted provided that the following conditions are
-## met:
-## * Redistributions of source code must retain the above copyright
-## notice, this list of conditions and the following disclaimer.
-## * Redistributions in binary form must reproduce the above copyright
-## notice, this list of conditions and the following disclaimer in
-## the documentation and/or other materials provided with the
-## distribution.
-## * Neither the name of The Qt Company Ltd nor the names of its
-## contributors may be used to endorse or promote products derived
-## from this software without specific prior written permission.
-##
-##
-## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
-##
-## $QT_END_LICENSE$
-##
-#############################################################################
-
-try:
- import cpickle as pickle
-except ImportError:
- import pickle
-
-from PySide2.QtCore import (Qt, Signal, QRegExp, QModelIndex,
- QItemSelection, QSortFilterProxyModel)
-from PySide2.QtWidgets import QTabWidget, QMessageBox, QTableView, QAbstractItemView
-
-from tablemodel import TableModel
-from newaddresstab import NewAddressTab
-from adddialogwidget import AddDialogWidget
-
-
-class AddressWidget(QTabWidget):
- """ The central widget of the application. Most of the addressbook's
- functionality is contained in this class.
- """
-
- selectionChanged = Signal(QItemSelection)
-
- def __init__(self, parent=None):
- """ Initialize the AddressWidget. """
- super(AddressWidget, self).__init__(parent)
-
- self.tableModel = TableModel()
- self.newAddressTab = NewAddressTab()
- self.newAddressTab.sendDetails.connect(self.addEntry)
-
- self.addTab(self.newAddressTab, "Address Book")
-
- self.setupTabs()
-
- def addEntry(self, name=None, address=None):
- """ Add an entry to the addressbook. """
- if name is None and address is None:
- addDialog = AddDialogWidget()
-
- if addDialog.exec_():
- name = addDialog.name
- address = addDialog.address
-
- address = {"name": name, "address": address}
- addresses = self.tableModel.addresses[:]
-
- # The QT docs for this example state that what we're doing here
- # is checking if the entered name already exists. What they
- # (and we here) are actually doing is checking if the whole
- # name/address pair exists already - ok for the purposes of this
- # example, but obviously not how a real addressbook application
- # should behave.
- try:
- addresses.remove(address)
- QMessageBox.information(self, "Duplicate Name",
- "The name \"%s\" already exists." % name)
- except ValueError:
- # The address didn't already exist, so let's add it to the model.
-
- # Step 1: create the row
- self.tableModel.insertRows(0)
-
- # Step 2: get the index of the newly created row and use it.
- # to set the name
- ix = self.tableModel.index(0, 0, QModelIndex())
- self.tableModel.setData(ix, address["name"], Qt.EditRole)
-
- # Step 3: lather, rinse, repeat for the address.
- ix = self.tableModel.index(0, 1, QModelIndex())
- self.tableModel.setData(ix, address["address"], Qt.EditRole)
-
- # Remove the newAddressTab, as we now have at least one
- # address in the model.
- self.removeTab(self.indexOf(self.newAddressTab))
-
- # The screenshot for the QT example shows nicely formatted
- # multiline cells, but the actual application doesn't behave
- # quite so nicely, at least on Ubuntu. Here we resize the newly
- # created row so that multiline addresses look reasonable.
- tableView = self.currentWidget()
- tableView.resizeRowToContents(ix.row())
-
- def editEntry(self):
- """ Edit an entry in the addressbook. """
- tableView = self.currentWidget()
- proxyModel = tableView.model()
- selectionModel = tableView.selectionModel()
-
- # Get the name and address of the currently selected row.
- indexes = selectionModel.selectedRows()
-
- for index in indexes:
- row = proxyModel.mapToSource(index).row()
- ix = self.tableModel.index(row, 0, QModelIndex())
- name = self.tableModel.data(ix, Qt.DisplayRole)
- ix = self.tableModel.index(row, 1, QModelIndex())
- address = self.tableModel.data(ix, Qt.DisplayRole)
-
- # Open an addDialogWidget, and only allow the user to edit the address.
- addDialog = AddDialogWidget()
- addDialog.setWindowTitle("Edit a Contact")
-
- addDialog.nameText.setReadOnly(True)
- addDialog.nameText.setText(name)
- addDialog.addressText.setText(address)
-
- # If the address is different, add it to the model.
- if addDialog.exec_():
- newAddress = addDialog.address
- if newAddress != address:
- ix = self.tableModel.index(row, 1, QModelIndex())
- self.tableModel.setData(ix, newAddress, Qt.EditRole)
-
- def removeEntry(self):
- """ Remove an entry from the addressbook. """
- tableView = self.currentWidget()
- proxyModel = tableView.model()
- selectionModel = tableView.selectionModel()
-
- # Just like editEntry, but this time remove the selected row.
- indexes = selectionModel.selectedRows()
-
- for index in indexes:
- row = proxyModel.mapToSource(index).row()
- self.tableModel.removeRows(row)
-
- # If we've removed the last address in the model, display the
- # newAddressTab
- if self.tableModel.rowCount() == 0:
- self.insertTab(0, self.newAddressTab, "Address Book")
-
- def setupTabs(self):
- """ Setup the various tabs in the AddressWidget. """
- groups = ["ABC", "DEF", "GHI", "JKL", "MNO", "PQR", "STU", "VW", "XYZ"]
-
- for group in groups:
- proxyModel = QSortFilterProxyModel(self)
- proxyModel.setSourceModel(self.tableModel)
- proxyModel.setDynamicSortFilter(True)
-
- tableView = QTableView()
- tableView.setModel(proxyModel)
- tableView.setSortingEnabled(True)
- tableView.setSelectionBehavior(QAbstractItemView.SelectRows)
- tableView.horizontalHeader().setStretchLastSection(True)
- tableView.verticalHeader().hide()
- tableView.setEditTriggers(QAbstractItemView.NoEditTriggers)
- tableView.setSelectionMode(QAbstractItemView.SingleSelection)
-
- # This here be the magic: we use the group name (e.g. "ABC") to
- # build the regex for the QSortFilterProxyModel for the group's
- # tab. The regex will end up looking like "^[ABC].*", only
- # allowing this tab to display items where the name starts with
- # "A", "B", or "C". Notice that we set it to be case-insensitive.
- reFilter = "^[%s].*" % group
-
- proxyModel.setFilterRegExp(QRegExp(reFilter, Qt.CaseInsensitive))
- proxyModel.setFilterKeyColumn(0) # Filter on the "name" column
- proxyModel.sort(0, Qt.AscendingOrder)
-
- # This prevents an application crash (see: http://www.qtcentre.org/threads/58874-QListView-SelectionModel-selectionChanged-Crash)
- viewselectionmodel = tableView.selectionModel()
- tableView.selectionModel().selectionChanged.connect(self.selectionChanged)
-
- self.addTab(tableView, group)
-
- # Note: the QT example uses a QDataStream for the saving and loading.
- # Here we're using a python dictionary to store the addresses, which
- # can't be streamed using QDataStream, so we just use cpickle for this
- # example.
- def readFromFile(self, filename):
- """ Read contacts in from a file. """
- try:
- f = open(filename, "rb")
- addresses = pickle.load(f)
- except IOError:
- QMessageBox.information(self, "Unable to open file: %s" % filename)
- finally:
- f.close()
-
- if len(addresses) == 0:
- QMessageBox.information(self, "No contacts in file: %s" % filename)
- else:
- for address in addresses:
- self.addEntry(address["name"], address["address"])
-
- def writeToFile(self, filename):
- """ Save all contacts in the model to a file. """
- try:
- f = open(filename, "wb")
- pickle.dump(self.tableModel.addresses, f)
-
- except IOError:
- QMessageBox.information(self, "Unable to open file: %s" % filename)
- finally:
- f.close()
-
-
-if __name__ == "__main__":
- import sys
- from PySide2.QtWidgets import QApplication
-
- app = QApplication(sys.argv)
- addressWidget = AddressWidget()
- addressWidget.show()
- sys.exit(app.exec_())
diff --git a/examples/widgets/itemviews/addressbook/newaddresstab.py b/examples/widgets/itemviews/addressbook/newaddresstab.py
deleted file mode 100644
index ab54fb8a7..000000000
--- a/examples/widgets/itemviews/addressbook/newaddresstab.py
+++ /dev/null
@@ -1,93 +0,0 @@
-
-#############################################################################
-##
-## Copyright (C) 2011 Arun Srinivasan <rulfzid@gmail.com>
-## Copyright (C) 2016 The Qt Company Ltd.
-## Contact: http://www.qt.io/licensing/
-##
-## This file is part of the Qt for Python examples of the Qt Toolkit.
-##
-## $QT_BEGIN_LICENSE:BSD$
-## You may use this file under the terms of the BSD license as follows:
-##
-## "Redistribution and use in source and binary forms, with or without
-## modification, are permitted provided that the following conditions are
-## met:
-## * Redistributions of source code must retain the above copyright
-## notice, this list of conditions and the following disclaimer.
-## * Redistributions in binary form must reproduce the above copyright
-## notice, this list of conditions and the following disclaimer in
-## the documentation and/or other materials provided with the
-## distribution.
-## * Neither the name of The Qt Company Ltd nor the names of its
-## contributors may be used to endorse or promote products derived
-## from this software without specific prior written permission.
-##
-##
-## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
-##
-## $QT_END_LICENSE$
-##
-#############################################################################
-
-from PySide2.QtCore import (Qt, Signal)
-from PySide2.QtWidgets import (QWidget, QLabel, QPushButton, QVBoxLayout)
-
-from adddialogwidget import AddDialogWidget
-
-class NewAddressTab(QWidget):
- """ An extra tab that prompts the user to add new contacts.
- To be displayed only when there are no contacts in the model.
- """
-
- sendDetails = Signal(str, str)
-
- def __init__(self, parent=None):
- super(NewAddressTab, self).__init__(parent)
-
- descriptionLabel = QLabel("There are no contacts in your address book."
- "\nClick Add to add new contacts.")
-
- addButton = QPushButton("Add")
-
- layout = QVBoxLayout()
- layout.addWidget(descriptionLabel)
- layout.addWidget(addButton, 0, Qt.AlignCenter)
-
- self.setLayout(layout)
-
- addButton.clicked.connect(self.addEntry)
-
- def addEntry(self):
- addDialog = AddDialogWidget()
-
- if addDialog.exec_():
- name = addDialog.name
- address = addDialog.address
- self.sendDetails.emit(name, address)
-
-
-if __name__ == "__main__":
-
- def printAddress(name, address):
- print("Name:" + name)
- print("Address:" + address)
-
- import sys
- from PySide2.QtWidgets import QApplication
-
- app = QApplication(sys.argv)
- newAddressTab = NewAddressTab()
- newAddressTab.sendDetails.connect(printAddress)
- newAddressTab.show()
- sys.exit(app.exec_())
diff --git a/examples/widgets/itemviews/basicfiltermodel/basicsortfiltermodel.py b/examples/widgets/itemviews/basicfiltermodel/basicsortfiltermodel.py
new file mode 100644
index 000000000..a30b0abdf
--- /dev/null
+++ b/examples/widgets/itemviews/basicfiltermodel/basicsortfiltermodel.py
@@ -0,0 +1,178 @@
+# Copyright (C) 2013 Riverbank Computing Limited.
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import sys
+from PySide6.QtCore import (QDate, QDateTime, QRegularExpression,
+ QSortFilterProxyModel, QTime, Qt, Slot)
+from PySide6.QtGui import QStandardItemModel
+from PySide6.QtWidgets import (QApplication, QCheckBox, QComboBox, QGridLayout,
+ QGroupBox, QHBoxLayout, QLabel, QLineEdit,
+ QTreeView, QVBoxLayout, QWidget)
+
+
+REGULAR_EXPRESSION = 0
+WILDCARD = 1
+FIXED_STRING = 2
+
+
+class Window(QWidget):
+ def __init__(self):
+ super().__init__()
+
+ self._proxy_model = QSortFilterProxyModel()
+ self._proxy_model.setDynamicSortFilter(True)
+
+ self._source_group_box = QGroupBox("Original Model")
+ self._proxy_group_box = QGroupBox("Sorted/Filtered Model")
+
+ self._source_view = QTreeView()
+ self._source_view.setRootIsDecorated(False)
+ self._source_view.setAlternatingRowColors(True)
+
+ self._proxy_view = QTreeView()
+ self._proxy_view.setRootIsDecorated(False)
+ self._proxy_view.setAlternatingRowColors(True)
+ self._proxy_view.setModel(self._proxy_model)
+ self._proxy_view.setSortingEnabled(True)
+
+ self._sort_case_sensitivity_check_box = QCheckBox("Case sensitive sorting")
+ self._filter_case_sensitivity_check_box = QCheckBox("Case sensitive filter")
+
+ self._filter_pattern_line_edit = QLineEdit()
+ self._filter_pattern_line_edit.setClearButtonEnabled(True)
+ self._filter_pattern_label = QLabel("&Filter pattern:")
+ self._filter_pattern_label.setBuddy(self._filter_pattern_line_edit)
+
+ self._filter_syntax_combo_box = QComboBox()
+ self._filter_syntax_combo_box.addItem("Regular expression",
+ REGULAR_EXPRESSION)
+ self._filter_syntax_combo_box.addItem("Wildcard",
+ WILDCARD)
+ self._filter_syntax_combo_box.addItem("Fixed string",
+ FIXED_STRING)
+ self._filter_syntax_label = QLabel("Filter &syntax:")
+ self._filter_syntax_label.setBuddy(self._filter_syntax_combo_box)
+
+ self._filter_column_combo_box = QComboBox()
+ self._filter_column_combo_box.addItem("Subject")
+ self._filter_column_combo_box.addItem("Sender")
+ self._filter_column_combo_box.addItem("Date")
+ self._filter_column_label = QLabel("Filter &column:")
+ self._filter_column_label.setBuddy(self._filter_column_combo_box)
+
+ self._filter_pattern_line_edit.textChanged.connect(self.filter_reg_exp_changed)
+ self._filter_syntax_combo_box.currentIndexChanged.connect(self.filter_reg_exp_changed)
+ self._filter_column_combo_box.currentIndexChanged.connect(self.filter_column_changed)
+ self._filter_case_sensitivity_check_box.toggled.connect(self.filter_reg_exp_changed)
+ self._sort_case_sensitivity_check_box.toggled.connect(self.sort_changed)
+
+ source_layout = QHBoxLayout()
+ source_layout.addWidget(self._source_view)
+ self._source_group_box.setLayout(source_layout)
+
+ proxy_layout = QGridLayout()
+ proxy_layout.addWidget(self._proxy_view, 0, 0, 1, 3)
+ proxy_layout.addWidget(self._filter_pattern_label, 1, 0)
+ proxy_layout.addWidget(self._filter_pattern_line_edit, 1, 1, 1, 2)
+ proxy_layout.addWidget(self._filter_syntax_label, 2, 0)
+ proxy_layout.addWidget(self._filter_syntax_combo_box, 2, 1, 1, 2)
+ proxy_layout.addWidget(self._filter_column_label, 3, 0)
+ proxy_layout.addWidget(self._filter_column_combo_box, 3, 1, 1, 2)
+ proxy_layout.addWidget(self._filter_case_sensitivity_check_box, 4, 0, 1, 2)
+ proxy_layout.addWidget(self._sort_case_sensitivity_check_box, 4, 2)
+ self._proxy_group_box.setLayout(proxy_layout)
+
+ main_layout = QVBoxLayout()
+ main_layout.addWidget(self._source_group_box)
+ main_layout.addWidget(self._proxy_group_box)
+ self.setLayout(main_layout)
+
+ self.setWindowTitle("Basic Sort/Filter Model")
+ self.resize(500, 450)
+
+ self._proxy_view.sortByColumn(1, Qt.AscendingOrder)
+ self._filter_column_combo_box.setCurrentIndex(1)
+
+ self._filter_pattern_line_edit.setText("Andy|Grace")
+ self._filter_case_sensitivity_check_box.setChecked(True)
+ self._sort_case_sensitivity_check_box.setChecked(True)
+
+ def set_source_model(self, model):
+ self._proxy_model.setSourceModel(model)
+ self._source_view.setModel(model)
+
+ @Slot()
+ def filter_reg_exp_changed(self):
+ syntax_nr = self._filter_syntax_combo_box.currentData()
+ pattern = self._filter_pattern_line_edit.text()
+ if syntax_nr == WILDCARD:
+ pattern = QRegularExpression.wildcardToRegularExpression(pattern)
+ elif syntax_nr == FIXED_STRING:
+ pattern = QRegularExpression.escape(pattern)
+
+ reg_exp = QRegularExpression(pattern)
+ if not self._filter_case_sensitivity_check_box.isChecked():
+ options = reg_exp.patternOptions()
+ options |= QRegularExpression.CaseInsensitiveOption
+ reg_exp.setPatternOptions(options)
+ self._proxy_model.setFilterRegularExpression(reg_exp)
+
+ @Slot()
+ def filter_column_changed(self):
+ self._proxy_model.setFilterKeyColumn(self._filter_column_combo_box.currentIndex())
+
+ @Slot()
+ def sort_changed(self):
+ if self._sort_case_sensitivity_check_box.isChecked():
+ case_sensitivity = Qt.CaseSensitive
+ else:
+ case_sensitivity = Qt.CaseInsensitive
+
+ self._proxy_model.setSortCaseSensitivity(case_sensitivity)
+
+
+def add_mail(model, subject, sender, date):
+ model.insertRow(0)
+ model.setData(model.index(0, 0), subject)
+ model.setData(model.index(0, 1), sender)
+ model.setData(model.index(0, 2), date)
+
+
+def create_mail_model(parent):
+ model = QStandardItemModel(0, 3, parent)
+
+ model.setHeaderData(0, Qt.Horizontal, "Subject")
+ model.setHeaderData(1, Qt.Horizontal, "Sender")
+ model.setHeaderData(2, Qt.Horizontal, "Date")
+
+ add_mail(model, "Happy New Year!", "Grace K. <grace@software-inc.com>",
+ QDateTime(QDate(2006, 12, 31), QTime(17, 3)))
+ add_mail(model, "Radically new concept", "Grace K. <grace@software-inc.com>",
+ QDateTime(QDate(2006, 12, 22), QTime(9, 44)))
+ add_mail(model, "Accounts", "pascale@nospam.com",
+ QDateTime(QDate(2006, 12, 31), QTime(12, 50)))
+ add_mail(model, "Expenses", "Joe Bloggs <joe@bloggs.com>",
+ QDateTime(QDate(2006, 12, 25), QTime(11, 39)))
+ add_mail(model, "Re: Expenses", "Andy <andy@nospam.com>",
+ QDateTime(QDate(2007, 1, 2), QTime(16, 5)))
+ add_mail(model, "Re: Accounts", "Joe Bloggs <joe@bloggs.com>",
+ QDateTime(QDate(2007, 1, 3), QTime(14, 18)))
+ add_mail(model, "Re: Accounts", "Andy <andy@nospam.com>",
+ QDateTime(QDate(2007, 1, 3), QTime(14, 26)))
+ add_mail(model, "Sports", "Linda Smith <linda.smith@nospam.com>",
+ QDateTime(QDate(2007, 1, 5), QTime(11, 33)))
+ add_mail(model, "AW: Sports", "Rolf Newschweinstein <rolfn@nospam.com>",
+ QDateTime(QDate(2007, 1, 5), QTime(12, 0)))
+ add_mail(model, "RE: Sports", "Petra Schmidt <petras@nospam.com>",
+ QDateTime(QDate(2007, 1, 5), QTime(12, 1)))
+
+ return model
+
+
+if __name__ == '__main__':
+ app = QApplication(sys.argv)
+ window = Window()
+ window.set_source_model(create_mail_model(window))
+ window.show()
+ sys.exit(app.exec())
diff --git a/examples/widgets/itemviews/basicfiltermodel/basicsortfiltermodel.pyproject b/examples/widgets/itemviews/basicfiltermodel/basicsortfiltermodel.pyproject
new file mode 100644
index 000000000..3351bba88
--- /dev/null
+++ b/examples/widgets/itemviews/basicfiltermodel/basicsortfiltermodel.pyproject
@@ -0,0 +1,3 @@
+{
+ "files": ["basicsortfiltermodel.py"]
+}
diff --git a/examples/widgets/itemviews/basicsortfiltermodel.py b/examples/widgets/itemviews/basicsortfiltermodel.py
deleted file mode 100644
index 4aea218d2..000000000
--- a/examples/widgets/itemviews/basicsortfiltermodel.py
+++ /dev/null
@@ -1,202 +0,0 @@
-
-#############################################################################
-##
-## Copyright (C) 2013 Riverbank Computing Limited.
-## Copyright (C) 2016 The Qt Company Ltd.
-## Contact: http://www.qt.io/licensing/
-##
-## This file is part of the Qt for Python examples of the Qt Toolkit.
-##
-## $QT_BEGIN_LICENSE:BSD$
-## You may use this file under the terms of the BSD license as follows:
-##
-## "Redistribution and use in source and binary forms, with or without
-## modification, are permitted provided that the following conditions are
-## met:
-## * Redistributions of source code must retain the above copyright
-## notice, this list of conditions and the following disclaimer.
-## * Redistributions in binary form must reproduce the above copyright
-## notice, this list of conditions and the following disclaimer in
-## the documentation and/or other materials provided with the
-## distribution.
-## * Neither the name of The Qt Company Ltd nor the names of its
-## contributors may be used to endorse or promote products derived
-## from this software without specific prior written permission.
-##
-##
-## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
-##
-## $QT_END_LICENSE$
-##
-#############################################################################
-
-from PySide2 import QtCore, QtGui, QtWidgets
-
-
-class Window(QtWidgets.QWidget):
- def __init__(self):
- super(Window, self).__init__()
-
- self.proxyModel = QtCore.QSortFilterProxyModel()
- self.proxyModel.setDynamicSortFilter(True)
-
- self.sourceGroupBox = QtWidgets.QGroupBox("Original Model")
- self.proxyGroupBox = QtWidgets.QGroupBox("Sorted/Filtered Model")
-
- self.sourceView = QtWidgets.QTreeView()
- self.sourceView.setRootIsDecorated(False)
- self.sourceView.setAlternatingRowColors(True)
-
- self.proxyView = QtWidgets.QTreeView()
- self.proxyView.setRootIsDecorated(False)
- self.proxyView.setAlternatingRowColors(True)
- self.proxyView.setModel(self.proxyModel)
- self.proxyView.setSortingEnabled(True)
-
- self.sortCaseSensitivityCheckBox = QtWidgets.QCheckBox("Case sensitive sorting")
- self.filterCaseSensitivityCheckBox = QtWidgets.QCheckBox("Case sensitive filter")
-
- self.filterPatternLineEdit = QtWidgets.QLineEdit()
- self.filterPatternLabel = QtWidgets.QLabel("&Filter pattern:")
- self.filterPatternLabel.setBuddy(self.filterPatternLineEdit)
-
- self.filterSyntaxComboBox = QtWidgets.QComboBox()
- self.filterSyntaxComboBox.addItem("Regular expression",
- QtCore.QRegExp.RegExp)
- self.filterSyntaxComboBox.addItem("Wildcard",
- QtCore.QRegExp.Wildcard)
- self.filterSyntaxComboBox.addItem("Fixed string",
- QtCore.QRegExp.FixedString)
- self.filterSyntaxLabel = QtWidgets.QLabel("Filter &syntax:")
- self.filterSyntaxLabel.setBuddy(self.filterSyntaxComboBox)
-
- self.filterColumnComboBox = QtWidgets.QComboBox()
- self.filterColumnComboBox.addItem("Subject")
- self.filterColumnComboBox.addItem("Sender")
- self.filterColumnComboBox.addItem("Date")
- self.filterColumnLabel = QtWidgets.QLabel("Filter &column:")
- self.filterColumnLabel.setBuddy(self.filterColumnComboBox)
-
- self.filterPatternLineEdit.textChanged.connect(self.filterRegExpChanged)
- self.filterSyntaxComboBox.currentIndexChanged.connect(self.filterRegExpChanged)
- self.filterColumnComboBox.currentIndexChanged.connect(self.filterColumnChanged)
- self.filterCaseSensitivityCheckBox.toggled.connect(self.filterRegExpChanged)
- self.sortCaseSensitivityCheckBox.toggled.connect(self.sortChanged)
-
- sourceLayout = QtWidgets.QHBoxLayout()
- sourceLayout.addWidget(self.sourceView)
- self.sourceGroupBox.setLayout(sourceLayout)
-
- proxyLayout = QtWidgets.QGridLayout()
- proxyLayout.addWidget(self.proxyView, 0, 0, 1, 3)
- proxyLayout.addWidget(self.filterPatternLabel, 1, 0)
- proxyLayout.addWidget(self.filterPatternLineEdit, 1, 1, 1, 2)
- proxyLayout.addWidget(self.filterSyntaxLabel, 2, 0)
- proxyLayout.addWidget(self.filterSyntaxComboBox, 2, 1, 1, 2)
- proxyLayout.addWidget(self.filterColumnLabel, 3, 0)
- proxyLayout.addWidget(self.filterColumnComboBox, 3, 1, 1, 2)
- proxyLayout.addWidget(self.filterCaseSensitivityCheckBox, 4, 0, 1, 2)
- proxyLayout.addWidget(self.sortCaseSensitivityCheckBox, 4, 2)
- self.proxyGroupBox.setLayout(proxyLayout)
-
- mainLayout = QtWidgets.QVBoxLayout()
- mainLayout.addWidget(self.sourceGroupBox)
- mainLayout.addWidget(self.proxyGroupBox)
- self.setLayout(mainLayout)
-
- self.setWindowTitle("Basic Sort/Filter Model")
- self.resize(500, 450)
-
- self.proxyView.sortByColumn(1, QtCore.Qt.AscendingOrder)
- self.filterColumnComboBox.setCurrentIndex(1)
-
- self.filterPatternLineEdit.setText("Andy|Grace")
- self.filterCaseSensitivityCheckBox.setChecked(True)
- self.sortCaseSensitivityCheckBox.setChecked(True)
-
- def setSourceModel(self, model):
- self.proxyModel.setSourceModel(model)
- self.sourceView.setModel(model)
-
- def filterRegExpChanged(self):
- syntax_nr = self.filterSyntaxComboBox.itemData(self.filterSyntaxComboBox.currentIndex())
- syntax = QtCore.QRegExp.PatternSyntax(syntax_nr)
-
- if self.filterCaseSensitivityCheckBox.isChecked():
- caseSensitivity = QtCore.Qt.CaseSensitive
- else:
- caseSensitivity = QtCore.Qt.CaseInsensitive
-
- regExp = QtCore.QRegExp(self.filterPatternLineEdit.text(),
- caseSensitivity, syntax)
- self.proxyModel.setFilterRegExp(regExp)
-
- def filterColumnChanged(self):
- self.proxyModel.setFilterKeyColumn(self.filterColumnComboBox.currentIndex())
-
- def sortChanged(self):
- if self.sortCaseSensitivityCheckBox.isChecked():
- caseSensitivity = QtCore.Qt.CaseSensitive
- else:
- caseSensitivity = QtCore.Qt.CaseInsensitive
-
- self.proxyModel.setSortCaseSensitivity(caseSensitivity)
-
-
-def addMail(model, subject, sender, date):
- model.insertRow(0)
- model.setData(model.index(0, 0), subject)
- model.setData(model.index(0, 1), sender)
- model.setData(model.index(0, 2), date)
-
-
-def createMailModel(parent):
- model = QtGui.QStandardItemModel(0, 3, parent)
-
- model.setHeaderData(0, QtCore.Qt.Horizontal, "Subject")
- model.setHeaderData(1, QtCore.Qt.Horizontal, "Sender")
- model.setHeaderData(2, QtCore.Qt.Horizontal, "Date")
-
- addMail(model, "Happy New Year!", "Grace K. <grace@software-inc.com>",
- QtCore.QDateTime(QtCore.QDate(2006, 12, 31), QtCore.QTime(17, 3)))
- addMail(model, "Radically new concept", "Grace K. <grace@software-inc.com>",
- QtCore.QDateTime(QtCore.QDate(2006, 12, 22), QtCore.QTime(9, 44)))
- addMail(model, "Accounts", "pascale@nospam.com",
- QtCore.QDateTime(QtCore.QDate(2006, 12, 31), QtCore.QTime(12, 50)))
- addMail(model, "Expenses", "Joe Bloggs <joe@bloggs.com>",
- QtCore.QDateTime(QtCore.QDate(2006, 12, 25), QtCore.QTime(11, 39)))
- addMail(model, "Re: Expenses", "Andy <andy@nospam.com>",
- QtCore.QDateTime(QtCore.QDate(2007, 1, 2), QtCore.QTime(16, 5)))
- addMail(model, "Re: Accounts", "Joe Bloggs <joe@bloggs.com>",
- QtCore.QDateTime(QtCore.QDate(2007, 1, 3), QtCore.QTime(14, 18)))
- addMail(model, "Re: Accounts", "Andy <andy@nospam.com>",
- QtCore.QDateTime(QtCore.QDate(2007, 1, 3), QtCore.QTime(14, 26)))
- addMail(model, "Sports", "Linda Smith <linda.smith@nospam.com>",
- QtCore.QDateTime(QtCore.QDate(2007, 1, 5), QtCore.QTime(11, 33)))
- addMail(model, "AW: Sports", "Rolf Newschweinstein <rolfn@nospam.com>",
- QtCore.QDateTime(QtCore.QDate(2007, 1, 5), QtCore.QTime(12, 0)))
- addMail(model, "RE: Sports", "Petra Schmidt <petras@nospam.com>",
- QtCore.QDateTime(QtCore.QDate(2007, 1, 5), QtCore.QTime(12, 1)))
-
- return model
-
-
-if __name__ == '__main__':
-
- import sys
-
- app = QtWidgets.QApplication(sys.argv)
- window = Window()
- window.setSourceModel(createMailModel(window))
- window.show()
- sys.exit(app.exec_())
diff --git a/examples/widgets/itemviews/dirview/dirview.py b/examples/widgets/itemviews/dirview/dirview.py
new file mode 100644
index 000000000..d1be6958e
--- /dev/null
+++ b/examples/widgets/itemviews/dirview/dirview.py
@@ -0,0 +1,59 @@
+# Copyright (C) 2020 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import sys
+from argparse import ArgumentParser, RawTextHelpFormatter
+
+from PySide6.QtWidgets import (QApplication, QFileSystemModel,
+ QFileIconProvider, QScroller, QTreeView)
+from PySide6.QtCore import QDir
+
+"""PySide6 port of the widgets/itemviews/dirview example from Qt v6.x"""
+
+
+if __name__ == "__main__":
+ app = QApplication(sys.argv)
+
+ name = "Dir View"
+ argument_parser = ArgumentParser(description=name,
+ formatter_class=RawTextHelpFormatter)
+ argument_parser.add_argument("--no-custom", "-c", action="store_true",
+ help="Set QFileSystemModel.DontUseCustomDirectoryIcons")
+ argument_parser.add_argument("--no-watch", "-w", action="store_true",
+ help="Set QFileSystemModel.DontWatch")
+ argument_parser.add_argument("directory",
+ help="The directory to start in.",
+ nargs='?', type=str)
+ options = argument_parser.parse_args()
+ root_path = options.directory
+
+ model = QFileSystemModel()
+ icon_provider = QFileIconProvider()
+ model.setIconProvider(icon_provider)
+ model.setRootPath("")
+ if options.no_custom:
+ model.setOption(QFileSystemModel.DontUseCustomDirectoryIcons)
+ if options.no_watch:
+ model.setOption(QFileSystemModel.DontWatchForChanges)
+ tree = QTreeView()
+ tree.setModel(model)
+ if root_path:
+ root_index = model.index(QDir.cleanPath(root_path))
+ if root_index.isValid():
+ tree.setRootIndex(root_index)
+
+ # Demonstrating look and feel features
+ tree.setAnimated(False)
+ tree.setIndentation(20)
+ tree.setSortingEnabled(True)
+ availableSize = tree.screen().availableGeometry().size()
+ tree.resize(availableSize / 2)
+ tree.setColumnWidth(0, tree.width() / 3)
+
+ # Make it flickable on touchscreens
+ QScroller.grabGesture(tree, QScroller.ScrollerGestureType.TouchGesture)
+
+ tree.setWindowTitle(name)
+ tree.show()
+
+ sys.exit(app.exec())
diff --git a/examples/widgets/itemviews/dirview/dirview.pyproject b/examples/widgets/itemviews/dirview/dirview.pyproject
new file mode 100644
index 000000000..9470083c9
--- /dev/null
+++ b/examples/widgets/itemviews/dirview/dirview.pyproject
@@ -0,0 +1,3 @@
+{
+ "files": ["dirview.py"]
+}
diff --git a/examples/widgets/itemviews/dirview/doc/dirview.rst b/examples/widgets/itemviews/dirview/doc/dirview.rst
new file mode 100644
index 000000000..7044fdf58
--- /dev/null
+++ b/examples/widgets/itemviews/dirview/doc/dirview.rst
@@ -0,0 +1,5 @@
+Dir View Example
+================
+
+The Dir View example shows a tree view of the local file system. It uses the
+QFileSystemModel class to provide file and directory information.
diff --git a/examples/widgets/itemviews/editabletreemodel/default.txt b/examples/widgets/itemviews/editabletreemodel/default.txt
new file mode 100644
index 000000000..98746548b
--- /dev/null
+++ b/examples/widgets/itemviews/editabletreemodel/default.txt
@@ -0,0 +1,40 @@
+Getting Started How to familiarize yourself with Qt Designer
+ Launching Designer Running the Qt Designer application
+ The User Interface How to interact with Qt Designer
+
+Designing a Component Creating a GUI for your application
+ Creating a Dialog How to create a dialog
+ Composing the Dialog Putting widgets into the dialog example
+ Creating a Layout Arranging widgets on a form
+ Signal and Slot Connections Making widget communicate with each other
+
+Using a Component in Your Application Generating code from forms
+ The Direct Approach Using a form without any adjustments
+ The Single Inheritance Approach Subclassing a form's base class
+ The Multiple Inheritance Approach Subclassing the form itself
+ Automatic Connections Connecting widgets using a naming scheme
+ A Dialog Without Auto-Connect How to connect widgets without a naming scheme
+ A Dialog With Auto-Connect Using automatic connections
+
+Form Editing Mode How to edit a form in Qt Designer
+ Managing Forms Loading and saving forms
+ Editing a Form Basic editing techniques
+ The Property Editor Changing widget properties
+ The Object Inspector Examining the hierarchy of objects on a form
+ Layouts Objects that arrange widgets on a form
+ Applying and Breaking Layouts Managing widgets in layouts
+ Horizontal and Vertical Layouts Standard row and column layouts
+ The Grid Layout Arranging widgets in a matrix
+ Previewing Forms Checking that the design works
+
+Using Containers How to group widgets together
+ General Features Common container features
+ Frames QFrame
+ Group Boxes QGroupBox
+ Stacked Widgets QStackedWidget
+ Tab Widgets QTabWidget
+ Toolbox Widgets QToolBox
+
+Connection Editing Mode Connecting widgets together with signals and slots
+ Connecting Objects Making connections in Qt Designer
+ Editing Connections Changing existing connections
diff --git a/examples/widgets/itemviews/editabletreemodel/doc/editabletreemodel.png b/examples/widgets/itemviews/editabletreemodel/doc/editabletreemodel.png
new file mode 100644
index 000000000..b50c792aa
--- /dev/null
+++ b/examples/widgets/itemviews/editabletreemodel/doc/editabletreemodel.png
Binary files differ
diff --git a/examples/widgets/itemviews/editabletreemodel/doc/editabletreemodel.rst b/examples/widgets/itemviews/editabletreemodel/doc/editabletreemodel.rst
new file mode 100644
index 000000000..c936972a0
--- /dev/null
+++ b/examples/widgets/itemviews/editabletreemodel/doc/editabletreemodel.rst
@@ -0,0 +1,10 @@
+Editable Tree Model Example
+===========================
+
+A Python application that demonstrates the analogous example in C++
+`Editable Tree Model Example <https://doc.qt.io/qt-6/qtwidgets-itemviews-editabletreemodel-example.html>`_
+
+.. image:: editabletreemodel.png
+ :width: 611
+ :alt: editabletreemodel screenshot
+
diff --git a/examples/widgets/itemviews/editabletreemodel/editabletreemodel.pyproject b/examples/widgets/itemviews/editabletreemodel/editabletreemodel.pyproject
new file mode 100644
index 000000000..1e67c727b
--- /dev/null
+++ b/examples/widgets/itemviews/editabletreemodel/editabletreemodel.pyproject
@@ -0,0 +1,7 @@
+{
+ "files": ["main.py",
+ "mainwindow.py",
+ "treeitem.py",
+ "treemodel.py",
+ "default.txt"]
+}
diff --git a/examples/widgets/itemviews/editabletreemodel/main.py b/examples/widgets/itemviews/editabletreemodel/main.py
new file mode 100644
index 000000000..491baa68d
--- /dev/null
+++ b/examples/widgets/itemviews/editabletreemodel/main.py
@@ -0,0 +1,14 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+
+import sys
+from PySide6.QtWidgets import QApplication
+from mainwindow import MainWindow
+
+
+if __name__ == '__main__':
+ app = QApplication(sys.argv)
+ window = MainWindow()
+ window.show()
+ sys.exit(app.exec())
diff --git a/examples/widgets/itemviews/editabletreemodel/mainwindow.py b/examples/widgets/itemviews/editabletreemodel/mainwindow.py
new file mode 100644
index 000000000..1489bf28b
--- /dev/null
+++ b/examples/widgets/itemviews/editabletreemodel/mainwindow.py
@@ -0,0 +1,163 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import sys
+from pathlib import Path
+
+from PySide6.QtCore import (QAbstractItemModel, QItemSelectionModel,
+ QModelIndex, Qt, Slot)
+from PySide6.QtWidgets import (QAbstractItemView, QMainWindow, QTreeView,
+ QWidget)
+from PySide6.QtTest import QAbstractItemModelTester
+
+from treemodel import TreeModel
+
+
+class MainWindow(QMainWindow):
+ def __init__(self, parent: QWidget = None):
+ super().__init__(parent)
+ self.resize(573, 468)
+
+ self.view = QTreeView()
+ self.view.setAlternatingRowColors(True)
+ self.view.setSelectionBehavior(QAbstractItemView.SelectItems)
+ self.view.setHorizontalScrollMode(QAbstractItemView.ScrollPerPixel)
+ self.view.setAnimated(False)
+ self.view.setAllColumnsShowFocus(True)
+ self.setCentralWidget(self.view)
+
+ menubar = self.menuBar()
+ file_menu = menubar.addMenu("&File")
+ self.exit_action = file_menu.addAction("E&xit")
+ self.exit_action.setShortcut("Ctrl+Q")
+ self.exit_action.triggered.connect(self.close)
+
+ actions_menu = menubar.addMenu("&Actions")
+ actions_menu.triggered.connect(self.update_actions)
+ self.insert_row_action = actions_menu.addAction("Insert Row")
+ self.insert_row_action.setShortcut("Ctrl+I, R")
+ self.insert_row_action.triggered.connect(self.insert_row)
+ self.insert_column_action = actions_menu.addAction("Insert Column")
+ self.insert_column_action.setShortcut("Ctrl+I, C")
+ self.insert_column_action.triggered.connect(self.insert_column)
+ actions_menu.addSeparator()
+ self.remove_row_action = actions_menu.addAction("Remove Row")
+ self.remove_row_action.setShortcut("Ctrl+R, R")
+ self.remove_row_action.triggered.connect(self.remove_row)
+ self.remove_column_action = actions_menu.addAction("Remove Column")
+ self.remove_column_action.setShortcut("Ctrl+R, C")
+ self.remove_column_action.triggered.connect(self.remove_column)
+ actions_menu.addSeparator()
+ self.insert_child_action = actions_menu.addAction("Insert Child")
+ self.insert_child_action.setShortcut("Ctrl+N")
+ self.insert_child_action.triggered.connect(self.insert_child)
+ help_menu = menubar.addMenu("&Help")
+ about_qt_action = help_menu.addAction("About Qt", qApp.aboutQt) # noqa: F821
+ about_qt_action.setShortcut("F1")
+
+ self.setWindowTitle("Editable Tree Model")
+
+ headers = ["Title", "Description"]
+
+ file = Path(__file__).parent / "default.txt"
+ self.model = TreeModel(headers, file.read_text(), self)
+
+ if "-t" in sys.argv:
+ QAbstractItemModelTester(self.model, self)
+ self.view.setModel(self.model)
+ self.view.expandAll()
+
+ for column in range(self.model.columnCount()):
+ self.view.resizeColumnToContents(column)
+
+ selection_model = self.view.selectionModel()
+ selection_model.selectionChanged.connect(self.update_actions)
+
+ self.update_actions()
+
+ @Slot()
+ def insert_child(self) -> None:
+ selection_model = self.view.selectionModel()
+ index: QModelIndex = selection_model.currentIndex()
+ model: QAbstractItemModel = self.view.model()
+
+ if model.columnCount(index) == 0:
+ if not model.insertColumn(0, index):
+ return
+
+ if not model.insertRow(0, index):
+ return
+
+ for column in range(model.columnCount(index)):
+ child: QModelIndex = model.index(0, column, index)
+ model.setData(child, "[No data]", Qt.EditRole)
+ if not model.headerData(column, Qt.Horizontal):
+ model.setHeaderData(column, Qt.Horizontal, "[No header]",
+ Qt.EditRole)
+
+ selection_model.setCurrentIndex(
+ model.index(0, 0, index), QItemSelectionModel.ClearAndSelect
+ )
+ self.update_actions()
+
+ @Slot()
+ def insert_column(self) -> None:
+ model: QAbstractItemModel = self.view.model()
+ column: int = self.view.selectionModel().currentIndex().column()
+
+ changed: bool = model.insertColumn(column + 1)
+ if changed:
+ model.setHeaderData(column + 1, Qt.Horizontal, "[No header]",
+ Qt.EditRole)
+
+ self.update_actions()
+
+ @Slot()
+ def insert_row(self) -> None:
+ index: QModelIndex = self.view.selectionModel().currentIndex()
+ model: QAbstractItemModel = self.view.model()
+ parent: QModelIndex = index.parent()
+
+ if not model.insertRow(index.row() + 1, parent):
+ return
+
+ self.update_actions()
+
+ for column in range(model.columnCount(parent)):
+ child: QModelIndex = model.index(index.row() + 1, column, parent)
+ model.setData(child, "[No data]", Qt.EditRole)
+
+ @Slot()
+ def remove_column(self) -> None:
+ model: QAbstractItemModel = self.view.model()
+ column: int = self.view.selectionModel().currentIndex().column()
+
+ if model.removeColumn(column):
+ self.update_actions()
+
+ @Slot()
+ def remove_row(self) -> None:
+ index: QModelIndex = self.view.selectionModel().currentIndex()
+ model: QAbstractItemModel = self.view.model()
+
+ if model.removeRow(index.row(), index.parent()):
+ self.update_actions()
+
+ @Slot()
+ def update_actions(self) -> None:
+ selection_model = self.view.selectionModel()
+ has_selection: bool = not selection_model.selection().isEmpty()
+ self.remove_row_action.setEnabled(has_selection)
+ self.remove_column_action.setEnabled(has_selection)
+
+ current_index = selection_model.currentIndex()
+ has_current: bool = current_index.isValid()
+ self.insert_row_action.setEnabled(has_current)
+ self.insert_column_action.setEnabled(has_current)
+
+ if has_current:
+ self.view.closePersistentEditor(current_index)
+ msg = f"Position: ({current_index.row()},{current_index.column()})"
+ if not current_index.parent().isValid():
+ msg += " in top level"
+ self.statusBar().showMessage(msg)
diff --git a/examples/widgets/itemviews/editabletreemodel/treeitem.py b/examples/widgets/itemviews/editabletreemodel/treeitem.py
new file mode 100644
index 000000000..1a25b0774
--- /dev/null
+++ b/examples/widgets/itemviews/editabletreemodel/treeitem.py
@@ -0,0 +1,94 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+
+class TreeItem:
+ def __init__(self, data: list, parent: 'TreeItem' = None):
+ self.item_data = data
+ self.parent_item = parent
+ self.child_items = []
+
+ def child(self, number: int) -> 'TreeItem':
+ if number < 0 or number >= len(self.child_items):
+ return None
+ return self.child_items[number]
+
+ def last_child(self):
+ return self.child_items[-1] if self.child_items else None
+
+ def child_count(self) -> int:
+ return len(self.child_items)
+
+ def child_number(self) -> int:
+ if self.parent_item:
+ return self.parent_item.child_items.index(self)
+ return 0
+
+ def column_count(self) -> int:
+ return len(self.item_data)
+
+ def data(self, column: int):
+ if column < 0 or column >= len(self.item_data):
+ return None
+ return self.item_data[column]
+
+ def insert_children(self, position: int, count: int, columns: int) -> bool:
+ if position < 0 or position > len(self.child_items):
+ return False
+
+ for row in range(count):
+ data = [None] * columns
+ item = TreeItem(data.copy(), self)
+ self.child_items.insert(position, item)
+
+ return True
+
+ def insert_columns(self, position: int, columns: int) -> bool:
+ if position < 0 or position > len(self.item_data):
+ return False
+
+ for column in range(columns):
+ self.item_data.insert(position, None)
+
+ for child in self.child_items:
+ child.insert_columns(position, columns)
+
+ return True
+
+ def parent(self):
+ return self.parent_item
+
+ def remove_children(self, position: int, count: int) -> bool:
+ if position < 0 or position + count > len(self.child_items):
+ return False
+
+ for row in range(count):
+ self.child_items.pop(position)
+
+ return True
+
+ def remove_columns(self, position: int, columns: int) -> bool:
+ if position < 0 or position + columns > len(self.item_data):
+ return False
+
+ for column in range(columns):
+ self.item_data.pop(position)
+
+ for child in self.child_items:
+ child.remove_columns(position, columns)
+
+ return True
+
+ def set_data(self, column: int, value):
+ if column < 0 or column >= len(self.item_data):
+ return False
+
+ self.item_data[column] = value
+ return True
+
+ def __repr__(self) -> str:
+ result = f"<treeitem.TreeItem at 0x{id(self):x}"
+ for d in self.item_data:
+ result += f' "{d}"' if d else " <None>"
+ result += f", {len(self.child_items)} children>"
+ return result
diff --git a/examples/widgets/itemviews/editabletreemodel/treemodel.py b/examples/widgets/itemviews/editabletreemodel/treemodel.py
new file mode 100644
index 000000000..a58572fca
--- /dev/null
+++ b/examples/widgets/itemviews/editabletreemodel/treemodel.py
@@ -0,0 +1,199 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+
+from PySide6.QtCore import QModelIndex, Qt, QAbstractItemModel
+from treeitem import TreeItem
+
+
+class TreeModel(QAbstractItemModel):
+
+ def __init__(self, headers: list, data: str, parent=None):
+ super().__init__(parent)
+
+ self.root_data = headers
+ self.root_item = TreeItem(self.root_data.copy())
+ self.setup_model_data(data.split("\n"), self.root_item)
+
+ def columnCount(self, parent: QModelIndex = None) -> int:
+ return self.root_item.column_count()
+
+ def data(self, index: QModelIndex, role: int = None):
+ if not index.isValid():
+ return None
+
+ if role != Qt.DisplayRole and role != Qt.EditRole:
+ return None
+
+ item: TreeItem = self.get_item(index)
+
+ return item.data(index.column())
+
+ def flags(self, index: QModelIndex) -> Qt.ItemFlags:
+ if not index.isValid():
+ return Qt.NoItemFlags
+
+ return Qt.ItemIsEditable | QAbstractItemModel.flags(self, index)
+
+ def get_item(self, index: QModelIndex = QModelIndex()) -> TreeItem:
+ if index.isValid():
+ item: TreeItem = index.internalPointer()
+ if item:
+ return item
+
+ return self.root_item
+
+ def headerData(self, section: int, orientation: Qt.Orientation,
+ role: int = Qt.DisplayRole):
+ if orientation == Qt.Horizontal and role == Qt.DisplayRole:
+ return self.root_item.data(section)
+
+ return None
+
+ def index(self, row: int, column: int, parent: QModelIndex = QModelIndex()) -> QModelIndex:
+ if parent.isValid() and parent.column() != 0:
+ return QModelIndex()
+
+ parent_item: TreeItem = self.get_item(parent)
+ if not parent_item:
+ return QModelIndex()
+
+ child_item: TreeItem = parent_item.child(row)
+ if child_item:
+ return self.createIndex(row, column, child_item)
+ return QModelIndex()
+
+ def insertColumns(self, position: int, columns: int,
+ parent: QModelIndex = QModelIndex()) -> bool:
+ self.beginInsertColumns(parent, position, position + columns - 1)
+ success: bool = self.root_item.insert_columns(position, columns)
+ self.endInsertColumns()
+
+ return success
+
+ def insertRows(self, position: int, rows: int,
+ parent: QModelIndex = QModelIndex()) -> bool:
+ parent_item: TreeItem = self.get_item(parent)
+ if not parent_item:
+ return False
+
+ self.beginInsertRows(parent, position, position + rows - 1)
+ column_count = self.root_item.column_count()
+ success: bool = parent_item.insert_children(position, rows, column_count)
+ self.endInsertRows()
+
+ return success
+
+ def parent(self, index: QModelIndex = QModelIndex()) -> QModelIndex:
+ if not index.isValid():
+ return QModelIndex()
+
+ child_item: TreeItem = self.get_item(index)
+ if child_item:
+ parent_item: TreeItem = child_item.parent()
+ else:
+ parent_item = None
+
+ if parent_item == self.root_item or not parent_item:
+ return QModelIndex()
+
+ return self.createIndex(parent_item.child_number(), 0, parent_item)
+
+ def removeColumns(self, position: int, columns: int,
+ parent: QModelIndex = QModelIndex()) -> bool:
+ self.beginRemoveColumns(parent, position, position + columns - 1)
+ success: bool = self.root_item.remove_columns(position, columns)
+ self.endRemoveColumns()
+
+ if self.root_item.column_count() == 0:
+ self.removeRows(0, self.rowCount())
+
+ return success
+
+ def removeRows(self, position: int, rows: int,
+ parent: QModelIndex = QModelIndex()) -> bool:
+ parent_item: TreeItem = self.get_item(parent)
+ if not parent_item:
+ return False
+
+ self.beginRemoveRows(parent, position, position + rows - 1)
+ success: bool = parent_item.remove_children(position, rows)
+ self.endRemoveRows()
+
+ return success
+
+ def rowCount(self, parent: QModelIndex = QModelIndex()) -> int:
+ if parent.isValid() and parent.column() > 0:
+ return 0
+
+ parent_item: TreeItem = self.get_item(parent)
+ if not parent_item:
+ return 0
+ return parent_item.child_count()
+
+ def setData(self, index: QModelIndex, value, role: int) -> bool:
+ if role != Qt.EditRole:
+ return False
+
+ item: TreeItem = self.get_item(index)
+ result: bool = item.set_data(index.column(), value)
+
+ if result:
+ self.dataChanged.emit(index, index, [Qt.DisplayRole, Qt.EditRole])
+
+ return result
+
+ def setHeaderData(self, section: int, orientation: Qt.Orientation, value,
+ role: int = None) -> bool:
+ if role != Qt.EditRole or orientation != Qt.Horizontal:
+ return False
+
+ result: bool = self.root_item.set_data(section, value)
+
+ if result:
+ self.headerDataChanged.emit(orientation, section, section)
+
+ return result
+
+ def setup_model_data(self, lines: list, parent: TreeItem):
+ parents = [parent]
+ indentations = [0]
+
+ for line in lines:
+ line = line.rstrip()
+ if line and "\t" in line:
+
+ position = 0
+ while position < len(line):
+ if line[position] != " ":
+ break
+ position += 1
+
+ column_data = line[position:].split("\t")
+ column_data = [string for string in column_data if string]
+
+ if position > indentations[-1]:
+ if parents[-1].child_count() > 0:
+ parents.append(parents[-1].last_child())
+ indentations.append(position)
+ else:
+ while position < indentations[-1] and parents:
+ parents.pop()
+ indentations.pop()
+
+ parent: TreeItem = parents[-1]
+ col_count = self.root_item.column_count()
+ parent.insert_children(parent.child_count(), 1, col_count)
+
+ for column in range(len(column_data)):
+ child = parent.last_child()
+ child.set_data(column, column_data[column])
+
+ def _repr_recursion(self, item: TreeItem, indent: int = 0) -> str:
+ result = " " * indent + repr(item) + "\n"
+ for child in item.child_items:
+ result += self._repr_recursion(child, indent + 2)
+ return result
+
+ def __repr__(self) -> str:
+ return self._repr_recursion(self.root_item)
diff --git a/examples/widgets/itemviews/fetchmore.py b/examples/widgets/itemviews/fetchmore.py
deleted file mode 100644
index 2b0d8c104..000000000
--- a/examples/widgets/itemviews/fetchmore.py
+++ /dev/null
@@ -1,147 +0,0 @@
-
-#############################################################################
-##
-## Copyright (C) 2009 Darryl Wallace, 2009 <wallacdj@gmail.com>
-## Copyright (C) 2013 Riverbank Computing Limited.
-## Copyright (C) 2016 The Qt Company Ltd.
-## Contact: http://www.qt.io/licensing/
-##
-## This file is part of the Qt for Python examples of the Qt Toolkit.
-##
-## $QT_BEGIN_LICENSE:BSD$
-## You may use this file under the terms of the BSD license as follows:
-##
-## "Redistribution and use in source and binary forms, with or without
-## modification, are permitted provided that the following conditions are
-## met:
-## * Redistributions of source code must retain the above copyright
-## notice, this list of conditions and the following disclaimer.
-## * Redistributions in binary form must reproduce the above copyright
-## notice, this list of conditions and the following disclaimer in
-## the documentation and/or other materials provided with the
-## distribution.
-## * Neither the name of The Qt Company Ltd nor the names of its
-## contributors may be used to endorse or promote products derived
-## from this software without specific prior written permission.
-##
-##
-## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
-##
-## $QT_END_LICENSE$
-##
-#############################################################################
-
-from PySide2 import QtCore, QtWidgets
-
-
-class FileListModel(QtCore.QAbstractListModel):
- numberPopulated = QtCore.Signal(int)
-
- def __init__(self, parent=None):
- super(FileListModel, self).__init__(parent)
-
- self.fileCount = 0
- self.fileList = []
-
- def rowCount(self, parent=QtCore.QModelIndex()):
- return self.fileCount
-
- def data(self, index, role=QtCore.Qt.DisplayRole):
- if not index.isValid():
- return None
-
- if index.row() >= len(self.fileList) or index.row() < 0:
- return None
-
- if role == QtCore.Qt.DisplayRole:
- return self.fileList[index.row()]
-
- if role == QtCore.Qt.BackgroundRole:
- batch = (index.row() // 100) % 2
-# FIXME: QGuiApplication::palette() required
- if batch == 0:
- return qApp.palette().base()
-
- return qApp.palette().alternateBase()
-
- return None
-
- def canFetchMore(self, index):
- return self.fileCount < len(self.fileList)
-
- def fetchMore(self, index):
- remainder = len(self.fileList) - self.fileCount
- itemsToFetch = min(100, remainder)
-
- self.beginInsertRows(QtCore.QModelIndex(), self.fileCount,
- self.fileCount + itemsToFetch)
-
- self.fileCount += itemsToFetch
-
- self.endInsertRows()
-
- self.numberPopulated.emit(itemsToFetch)
-
- def setDirPath(self, path):
- dir = QtCore.QDir(path)
-
- self.beginResetModel()
- self.fileList = list(dir.entryList())
- self.fileCount = 0
- self.endResetModel()
-
-
-class Window(QtWidgets.QWidget):
- def __init__(self, parent=None):
- super(Window, self).__init__(parent)
-
- model = FileListModel(self)
- model.setDirPath(QtCore.QLibraryInfo.location(QtCore.QLibraryInfo.PrefixPath))
-
- label = QtWidgets.QLabel("Directory")
- lineEdit = QtWidgets.QLineEdit()
- label.setBuddy(lineEdit)
-
- view = QtWidgets.QListView()
- view.setModel(model)
-
- self.logViewer = QtWidgets.QTextBrowser()
- self.logViewer.setSizePolicy(QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred))
-
- lineEdit.textChanged.connect(model.setDirPath)
- lineEdit.textChanged.connect(self.logViewer.clear)
- model.numberPopulated.connect(self.updateLog)
-
- layout = QtWidgets.QGridLayout()
- layout.addWidget(label, 0, 0)
- layout.addWidget(lineEdit, 0, 1)
- layout.addWidget(view, 1, 0, 1, 2)
- layout.addWidget(self.logViewer, 2, 0, 1, 2)
-
- self.setLayout(layout)
- self.setWindowTitle("Fetch More Example")
-
- def updateLog(self, number):
- self.logViewer.append("%d items added." % number)
-
-
-if __name__ == '__main__':
-
- import sys
-
- app = QtWidgets.QApplication(sys.argv)
-
- window = Window()
- window.show()
-
- sys.exit(app.exec_())
diff --git a/examples/widgets/itemviews/fetchmore/fetchmore.py b/examples/widgets/itemviews/fetchmore/fetchmore.py
new file mode 100644
index 000000000..5150250e0
--- /dev/null
+++ b/examples/widgets/itemviews/fetchmore/fetchmore.py
@@ -0,0 +1,137 @@
+# Copyright (C) 2009 Darryl Wallace, 2009 <wallacdj@gmail.com>
+# Copyright (C) 2013 Riverbank Computing Limited.
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+"""PySide6 port of the itemviews/fetchmore/fetchmore example from Qt v6.x
+
+Navigate to a directory with many entries by doubleclicking and scroll
+down the list to see the model being populated on demand.
+"""
+
+import sys
+
+from PySide6.QtCore import (QAbstractListModel, QDir,
+ QModelIndex, Qt, Signal, Slot)
+from PySide6.QtWidgets import (QApplication, QFileIconProvider, QListView,
+ QPlainTextEdit, QSizePolicy, QVBoxLayout,
+ QWidget)
+
+
+BATCH_SIZE = 100
+
+
+class FileListModel(QAbstractListModel):
+
+ number_populated = Signal(str, int, int, int)
+
+ def __init__(self, parent=None):
+ super().__init__(parent)
+
+ self._path = ''
+ self._file_count = 0
+ self._file_list = []
+ self._icon_provider = QFileIconProvider()
+
+ def rowCount(self, parent=QModelIndex()):
+ return self._file_count
+
+ def data(self, index, role=Qt.DisplayRole):
+ if not index.isValid():
+ return None
+
+ row = index.row()
+ if row >= len(self._file_list) or row < 0:
+ return None
+
+ if role == Qt.DisplayRole:
+ return self._file_list[row].fileName()
+
+ if role == Qt.BackgroundRole:
+ batch = row // BATCH_SIZE
+ palette = qApp.palette() # noqa: F821
+ return palette.base() if batch % 2 == 0 else palette.alternateBase()
+
+ if role == Qt.DecorationRole:
+ return self._icon_provider.icon(self._file_list[row])
+
+ return None
+
+ def canFetchMore(self, index):
+ return self._file_count < len(self._file_list)
+
+ def fetchMore(self, index):
+ start = self._file_count
+ total = len(self._file_list)
+ remainder = total - start
+ items_to_fetch = min(BATCH_SIZE, remainder)
+
+ self.beginInsertRows(QModelIndex(), start, start + items_to_fetch)
+
+ self._file_count += items_to_fetch
+
+ self.endInsertRows()
+
+ self.number_populated.emit(self._path, start, items_to_fetch, total)
+
+ @Slot(str)
+ def set_dir_path(self, path):
+ self._path = path
+ directory = QDir(path)
+
+ self.beginResetModel()
+ directory_filter = QDir.AllEntries | QDir.NoDot
+ self._file_list = directory.entryInfoList(directory_filter, QDir.Name)
+ self._file_count = 0
+ self.endResetModel()
+
+ def fileinfo_at(self, index):
+ return self._file_list[index.row()]
+
+
+class Window(QWidget):
+ def __init__(self, parent=None):
+ super().__init__(parent)
+
+ self._model = FileListModel(self)
+ self._model.set_dir_path(QDir.rootPath())
+
+ self._view = QListView()
+ self._view.setModel(self._model)
+
+ self._log_viewer = QPlainTextEdit()
+ self._log_viewer.setSizePolicy(QSizePolicy(QSizePolicy.Preferred,
+ QSizePolicy.Preferred))
+
+ self._model.number_populated.connect(self.update_log)
+ self._view.activated.connect(self.activated)
+
+ layout = QVBoxLayout(self)
+ layout.addWidget(self._view)
+ layout.addWidget(self._log_viewer)
+
+ self.setWindowTitle("Fetch More Example")
+
+ @Slot(str, int, int, int)
+ def update_log(self, path, start, number, total):
+ native_path = QDir.toNativeSeparators(path)
+ last = start + number - 1
+ entry = f'{start}..{last}/{total} items from "{native_path}" added.'
+ self._log_viewer.appendPlainText(entry)
+
+ @Slot(QModelIndex)
+ def activated(self, index):
+ fileinfo = self._model.fileinfo_at(index)
+ if fileinfo.isDir():
+ self._log_viewer.clear()
+ self._model.set_dir_path(fileinfo.absoluteFilePath())
+
+
+if __name__ == '__main__':
+ app = QApplication(sys.argv)
+
+ window = Window()
+ window.resize(400, 500)
+ window.show()
+
+ sys.exit(app.exec())
diff --git a/examples/widgets/itemviews/fetchmore/fetchmore.pyproject b/examples/widgets/itemviews/fetchmore/fetchmore.pyproject
new file mode 100644
index 000000000..e5c369374
--- /dev/null
+++ b/examples/widgets/itemviews/fetchmore/fetchmore.pyproject
@@ -0,0 +1,3 @@
+{
+ "files": ["fetchmore.py"]
+}
diff --git a/examples/widgets/itemviews/itemviews.pyproject b/examples/widgets/itemviews/itemviews.pyproject
deleted file mode 100644
index a582259cc..000000000
--- a/examples/widgets/itemviews/itemviews.pyproject
+++ /dev/null
@@ -1,3 +0,0 @@
-{
- "files": ["basicsortfiltermodel.py", "fetchmore.py"]
-}
diff --git a/examples/widgets/itemviews/jsonmodel/doc/jsonmodel.png b/examples/widgets/itemviews/jsonmodel/doc/jsonmodel.png
new file mode 100644
index 000000000..8b5c8d0c3
--- /dev/null
+++ b/examples/widgets/itemviews/jsonmodel/doc/jsonmodel.png
Binary files differ
diff --git a/examples/widgets/itemviews/jsonmodel/doc/jsonmodel.rst b/examples/widgets/itemviews/jsonmodel/doc/jsonmodel.rst
new file mode 100644
index 000000000..d5e2831bf
--- /dev/null
+++ b/examples/widgets/itemviews/jsonmodel/doc/jsonmodel.rst
@@ -0,0 +1,8 @@
+JSON Model Example
+==================
+
+Simple example to visualize the values of a JSON file.
+
+.. image:: jsonmodel.png
+ :width: 400
+ :alt: JSON Model Screenshot
diff --git a/examples/widgets/itemviews/jsonmodel/example.json b/examples/widgets/itemviews/jsonmodel/example.json
new file mode 100644
index 000000000..3c3ecfbfd
--- /dev/null
+++ b/examples/widgets/itemviews/jsonmodel/example.json
@@ -0,0 +1,26 @@
+{
+ "id": "0001",
+ "type": "donut",
+ "name": "Cake",
+ "ppu": 0.55,
+ "batters":
+ {
+ "batter":
+ [
+ { "id": "1001", "type": "Regular" },
+ { "id": "1002", "type": "Chocolate" },
+ { "id": "1003", "type": "Blueberry" },
+ { "id": "1004", "type": "Devil's Food" }
+ ]
+ },
+ "topping":
+ [
+ { "id": "5001", "type": "None" },
+ { "id": "5002", "type": "Glazed" },
+ { "id": "5005", "type": "Sugar" },
+ { "id": "5007", "type": "Powdered Sugar" },
+ { "id": "5006", "type": "Chocolate with Sprinkles" },
+ { "id": "5003", "type": "Chocolate" },
+ { "id": "5004", "type": "Maple" }
+ ]
+}
diff --git a/examples/widgets/itemviews/jsonmodel/jsonmodel.py b/examples/widgets/itemviews/jsonmodel/jsonmodel.py
new file mode 100644
index 000000000..6e614c77f
--- /dev/null
+++ b/examples/widgets/itemviews/jsonmodel/jsonmodel.py
@@ -0,0 +1,320 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import json
+import sys
+from typing import Any, List, Dict, Union
+
+from PySide6.QtWidgets import QTreeView, QApplication, QHeaderView
+from PySide6.QtCore import QAbstractItemModel, QModelIndex, QObject, Qt, QFileInfo
+
+
+class TreeItem:
+ """A Json item corresponding to a line in QTreeView"""
+
+ def __init__(self, parent: "TreeItem" = None):
+ self._parent = parent
+ self._key = ""
+ self._value = ""
+ self._value_type = None
+ self._children = []
+
+ def appendChild(self, item: "TreeItem"):
+ """Add item as a child"""
+ self._children.append(item)
+
+ def child(self, row: int) -> "TreeItem":
+ """Return the child of the current item from the given row"""
+ return self._children[row]
+
+ def parent(self) -> "TreeItem":
+ """Return the parent of the current item"""
+ return self._parent
+
+ def childCount(self) -> int:
+ """Return the number of children of the current item"""
+ return len(self._children)
+
+ def row(self) -> int:
+ """Return the row where the current item occupies in the parent"""
+ return self._parent._children.index(self) if self._parent else 0
+
+ @property
+ def key(self) -> str:
+ """Return the key name"""
+ return self._key
+
+ @key.setter
+ def key(self, key: str):
+ """Set key name of the current item"""
+ self._key = key
+
+ @property
+ def value(self) -> str:
+ """Return the value name of the current item"""
+ return self._value
+
+ @value.setter
+ def value(self, value: str):
+ """Set value name of the current item"""
+ self._value = value
+
+ @property
+ def value_type(self):
+ """Return the python type of the item's value."""
+ return self._value_type
+
+ @value_type.setter
+ def value_type(self, value):
+ """Set the python type of the item's value."""
+ self._value_type = value
+
+ @classmethod
+ def load(
+ cls, value: Union[List, Dict], parent: "TreeItem" = None, sort=True
+ ) -> "TreeItem":
+ """Create a 'root' TreeItem from a nested list or a nested dictonary
+
+ Examples:
+ with open("file.json") as file:
+ data = json.dump(file)
+ root = TreeItem.load(data)
+
+ This method is a recursive function that calls itself.
+
+ Returns:
+ TreeItem: TreeItem
+ """
+ rootItem = TreeItem(parent)
+ rootItem.key = "root"
+
+ if isinstance(value, dict):
+ items = sorted(value.items()) if sort else value.items()
+
+ for key, value in items:
+ child = cls.load(value, rootItem)
+ child.key = key
+ child.value_type = type(value)
+ rootItem.appendChild(child)
+
+ elif isinstance(value, list):
+ for index, value in enumerate(value):
+ child = cls.load(value, rootItem)
+ child.key = index
+ child.value_type = type(value)
+ rootItem.appendChild(child)
+
+ else:
+ rootItem.value = value
+ rootItem.value_type = type(value)
+
+ return rootItem
+
+
+class JsonModel(QAbstractItemModel):
+ """ An editable model of Json data """
+
+ def __init__(self, parent: QObject = None):
+ super().__init__(parent)
+
+ self._rootItem = TreeItem()
+ self._headers = ("key", "value")
+
+ def clear(self):
+ """ Clear data from the model """
+ self.load({})
+
+ def load(self, document: dict):
+ """Load model from a nested dictionary returned by json.loads()
+
+ Arguments:
+ document (dict): JSON-compatible dictionary
+ """
+
+ assert isinstance(
+ document, (dict, list, tuple)
+ ), "`document` must be of dict, list or tuple, " f"not {type(document)}"
+
+ self.beginResetModel()
+
+ self._rootItem = TreeItem.load(document)
+ self._rootItem.value_type = type(document)
+
+ self.endResetModel()
+
+ return True
+
+ def data(self, index: QModelIndex, role: Qt.ItemDataRole) -> Any:
+ """Override from QAbstractItemModel
+
+ Return data from a json item according index and role
+
+ """
+ if not index.isValid():
+ return None
+
+ item = index.internalPointer()
+
+ if role == Qt.DisplayRole:
+ if index.column() == 0:
+ return item.key
+
+ if index.column() == 1:
+ return item.value
+
+ elif role == Qt.EditRole:
+ if index.column() == 1:
+ return item.value
+
+ def setData(self, index: QModelIndex, value: Any, role: Qt.ItemDataRole):
+ """Override from QAbstractItemModel
+
+ Set json item according index and role
+
+ Args:
+ index (QModelIndex)
+ value (Any)
+ role (Qt.ItemDataRole)
+
+ """
+ if role == Qt.EditRole:
+ if index.column() == 1:
+ item = index.internalPointer()
+ item.value = str(value)
+
+ self.dataChanged.emit(index, index, [Qt.EditRole])
+
+ return True
+
+ return False
+
+ def headerData(
+ self, section: int, orientation: Qt.Orientation, role: Qt.ItemDataRole
+ ):
+ """Override from QAbstractItemModel
+
+ For the JsonModel, it returns only data for columns (orientation = Horizontal)
+
+ """
+ if role != Qt.DisplayRole:
+ return None
+
+ if orientation == Qt.Horizontal:
+ return self._headers[section]
+
+ def index(self, row: int, column: int, parent=QModelIndex()) -> QModelIndex:
+ """Override from QAbstractItemModel
+
+ Return index according row, column and parent
+
+ """
+ if not self.hasIndex(row, column, parent):
+ return QModelIndex()
+
+ if not parent.isValid():
+ parentItem = self._rootItem
+ else:
+ parentItem = parent.internalPointer()
+
+ childItem = parentItem.child(row)
+ if childItem:
+ return self.createIndex(row, column, childItem)
+ else:
+ return QModelIndex()
+
+ def parent(self, index: QModelIndex) -> QModelIndex:
+ """Override from QAbstractItemModel
+
+ Return parent index of index
+
+ """
+
+ if not index.isValid():
+ return QModelIndex()
+
+ childItem = index.internalPointer()
+ parentItem = childItem.parent()
+
+ if parentItem == self._rootItem:
+ return QModelIndex()
+
+ return self.createIndex(parentItem.row(), 0, parentItem)
+
+ def rowCount(self, parent=QModelIndex()):
+ """Override from QAbstractItemModel
+
+ Return row count from parent index
+ """
+ if parent.column() > 0:
+ return 0
+
+ if not parent.isValid():
+ parentItem = self._rootItem
+ else:
+ parentItem = parent.internalPointer()
+
+ return parentItem.childCount()
+
+ def columnCount(self, parent=QModelIndex()):
+ """Override from QAbstractItemModel
+
+ Return column number. For the model, it always return 2 columns
+ """
+ return 2
+
+ def flags(self, index: QModelIndex) -> Qt.ItemFlags:
+ """Override from QAbstractItemModel
+
+ Return flags of index
+ """
+ flags = super(JsonModel, self).flags(index)
+
+ if index.column() == 1:
+ return Qt.ItemIsEditable | flags
+ else:
+ return flags
+
+ def to_json(self, item=None):
+
+ if item is None:
+ item = self._rootItem
+
+ nchild = item.childCount()
+
+ if item.value_type is dict:
+ document = {}
+ for i in range(nchild):
+ ch = item.child(i)
+ document[ch.key] = self.to_json(ch)
+ return document
+
+ elif item.value_type == list:
+ document = []
+ for i in range(nchild):
+ ch = item.child(i)
+ document.append(self.to_json(ch))
+ return document
+
+ else:
+ return item.value
+
+
+if __name__ == "__main__":
+
+ app = QApplication(sys.argv)
+ view = QTreeView()
+ model = JsonModel()
+
+ view.setModel(model)
+
+ json_path = QFileInfo(__file__).absoluteDir().filePath("example.json")
+
+ with open(json_path) as file:
+ document = json.load(file)
+ model.load(document)
+
+ view.show()
+ view.header().setSectionResizeMode(0, QHeaderView.Stretch)
+ view.setAlternatingRowColors(True)
+ view.resize(500, 300)
+ app.exec()
diff --git a/examples/widgets/itemviews/jsonmodel/jsonmodel.pyproject b/examples/widgets/itemviews/jsonmodel/jsonmodel.pyproject
new file mode 100644
index 000000000..7d551b31c
--- /dev/null
+++ b/examples/widgets/itemviews/jsonmodel/jsonmodel.pyproject
@@ -0,0 +1,3 @@
+{
+ "files": ["jsonmodel.py", "example.json"]
+}
diff --git a/examples/widgets/itemviews/spinboxdelegate/doc/spinboxdelegate.rst b/examples/widgets/itemviews/spinboxdelegate/doc/spinboxdelegate.rst
new file mode 100644
index 000000000..12e505207
--- /dev/null
+++ b/examples/widgets/itemviews/spinboxdelegate/doc/spinboxdelegate.rst
@@ -0,0 +1,5 @@
+SpinBox Delegate Example
+=========================
+
+A simple example that shows how a view can use a custom delegate to edit
+data obtained from a model.
diff --git a/examples/widgets/itemviews/spinboxdelegate/spinboxdelegate.py b/examples/widgets/itemviews/spinboxdelegate/spinboxdelegate.py
new file mode 100644
index 000000000..577f0faa5
--- /dev/null
+++ b/examples/widgets/itemviews/spinboxdelegate/spinboxdelegate.py
@@ -0,0 +1,79 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import sys
+
+from PySide6.QtWidgets import (QApplication, QStyledItemDelegate, QSpinBox,
+ QTableView)
+from PySide6.QtGui import QStandardItemModel, Qt
+from PySide6.QtCore import QModelIndex
+
+"""PySide6 port of the widgets/itemviews/spinboxdelegate from Qt v6.x"""
+
+
+#! [0]
+class SpinBoxDelegate(QStyledItemDelegate):
+ """A delegate that allows the user to change integer values from the model
+ using a spin box widget. """
+
+#! [0]
+ def __init__(self, parent=None):
+ super().__init__(parent)
+#! [0]
+
+#! [1]
+ def createEditor(self, parent, option, index):
+ editor = QSpinBox(parent)
+ editor.setFrame(False)
+ editor.setMinimum(0)
+ editor.setMaximum(100)
+ return editor
+#! [1]
+
+#! [2]
+ def setEditorData(self, editor, index):
+ value = index.model().data(index, Qt.EditRole)
+ editor.setValue(value)
+#! [2]
+
+#! [3]
+ def setModelData(self, editor, model, index):
+ editor.interpretText()
+ value = editor.value()
+ model.setData(index, value, Qt.EditRole)
+#! [3]
+
+#! [4]
+ def updateEditorGeometry(self, editor, option, index):
+ editor.setGeometry(option.rect)
+#! [4]
+
+
+#! [main0]
+if __name__ == '__main__':
+ app = QApplication(sys.argv)
+
+ model = QStandardItemModel(4, 2)
+ tableView = QTableView()
+ tableView.setModel(model)
+
+ delegate = SpinBoxDelegate()
+ tableView.setItemDelegate(delegate)
+#! [main0]
+
+ tableView.horizontalHeader().setStretchLastSection(True)
+
+#! [main1]
+ for row in range(4):
+ for column in range(2):
+ index = model.index(row, column, QModelIndex())
+ value = (row + 1) * (column + 1)
+ model.setData(index, value)
+#! [main1] //# [main2]
+#! [main2]
+
+#! [main3]
+ tableView.setWindowTitle("Spin Box Delegate")
+ tableView.show()
+ sys.exit(app.exec())
+#! [main3]
diff --git a/examples/widgets/itemviews/spinboxdelegate/spinboxdelegate.pyproject b/examples/widgets/itemviews/spinboxdelegate/spinboxdelegate.pyproject
new file mode 100644
index 000000000..70616905c
--- /dev/null
+++ b/examples/widgets/itemviews/spinboxdelegate/spinboxdelegate.pyproject
@@ -0,0 +1,3 @@
+{
+ "files": ["spinboxdelegate.py"]
+}
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..82ebe5ebb
--- /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(QTableWidgetItem)
+ 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(QTableWidgetItem)
+ 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(QTableWidgetItem)
+ 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)
diff --git a/examples/widgets/itemviews/stardelegate/doc/stardelegate.png b/examples/widgets/itemviews/stardelegate/doc/stardelegate.png
new file mode 100644
index 000000000..343416397
--- /dev/null
+++ b/examples/widgets/itemviews/stardelegate/doc/stardelegate.png
Binary files differ
diff --git a/examples/widgets/itemviews/stardelegate/doc/stardelegate.rst b/examples/widgets/itemviews/stardelegate/doc/stardelegate.rst
new file mode 100644
index 000000000..fe8a2732a
--- /dev/null
+++ b/examples/widgets/itemviews/stardelegate/doc/stardelegate.rst
@@ -0,0 +1,10 @@
+Star Delegate Example
+=====================
+
+Demonstrates Qt's itemview architecture
+
+This example demonstrates the Qt model view architecture.
+
+.. image:: stardelegate.png
+ :width: 400
+ :alt: Star Delegate Screenshot
diff --git a/examples/widgets/itemviews/stardelegate/stardelegate.py b/examples/widgets/itemviews/stardelegate/stardelegate.py
index 86fd99ce6..973eb14f6 100644
--- a/examples/widgets/itemviews/stardelegate/stardelegate.py
+++ b/examples/widgets/itemviews/stardelegate/stardelegate.py
@@ -1,58 +1,21 @@
+# Copyright (C) 2010 Hans-Peter Jansen <hpj@urpla.net>
+# Copyright (C) 2011 Arun Srinivasan <rulfzid@gmail.com>
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-#############################################################################
-##
-## Copyright (C) 2010 Hans-Peter Jansen <hpj@urpla.net>
-## Copyright (C) 2011 Arun Srinivasan <rulfzid@gmail.com>
-## Copyright (C) 2016 The Qt Company Ltd.
-## Contact: http://www.qt.io/licensing/
-##
-## This file is part of the Qt for Python examples of the Qt Toolkit.
-##
-## $QT_BEGIN_LICENSE:BSD$
-## You may use this file under the terms of the BSD license as follows:
-##
-## "Redistribution and use in source and binary forms, with or without
-## modification, are permitted provided that the following conditions are
-## met:
-## * Redistributions of source code must retain the above copyright
-## notice, this list of conditions and the following disclaimer.
-## * Redistributions in binary form must reproduce the above copyright
-## notice, this list of conditions and the following disclaimer in
-## the documentation and/or other materials provided with the
-## distribution.
-## * Neither the name of The Qt Company Ltd nor the names of its
-## contributors may be used to endorse or promote products derived
-## from this software without specific prior written permission.
-##
-##
-## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
-##
-## $QT_END_LICENSE$
-##
-#############################################################################
-
-from PySide2.QtWidgets import QStyledItemDelegate, QStyle
+from PySide6.QtWidgets import QStyledItemDelegate, QStyle
from starrating import StarRating
from stareditor import StarEditor
+
class StarDelegate(QStyledItemDelegate):
""" A subclass of QStyledItemDelegate that allows us to render our
pretty star ratings.
"""
def __init__(self, parent=None):
- super(StarDelegate, self).__init__(parent)
+ super().__init__(parent)
def paint(self, painter, option, index):
""" Paint the items in the table.
@@ -66,7 +29,7 @@ class StarDelegate(QStyledItemDelegate):
it works for the purposes of this example.
"""
if index.column() == 3:
- starRating = StarRating(index.data())
+ star_rating = StarRating(index.data())
# If the row is currently selected, we need to make sure we
# paint the background accordingly.
@@ -85,15 +48,15 @@ class StarDelegate(QStyledItemDelegate):
# Now that we've painted the background, call starRating.paint()
# to paint the stars.
- starRating.paint(painter, option.rect, option.palette)
+ star_rating.paint(painter, option.rect, option.palette)
else:
QStyledItemDelegate.paint(self, painter, option, index)
def sizeHint(self, option, index):
""" Returns the size needed to display the item in a QSize object. """
if index.column() == 3:
- starRating = StarRating(index.data())
- return starRating.sizeHint()
+ star_rating = StarRating(index.data())
+ return star_rating.sizeHint()
else:
return QStyledItemDelegate.sizeHint(self, option, index)
@@ -107,7 +70,7 @@ class StarDelegate(QStyledItemDelegate):
"""
if index.column() == 3:
editor = StarEditor(parent)
- editor.editingFinished.connect(self.commitAndCloseEditor)
+ editor.editing_finished.connect(self.commit_and_close_editor)
return editor
else:
return QStyledItemDelegate.createEditor(self, parent, option, index)
@@ -115,7 +78,7 @@ class StarDelegate(QStyledItemDelegate):
def setEditorData(self, editor, index):
""" Sets the data to be displayed and edited by our custom editor. """
if index.column() == 3:
- editor.starRating = StarRating(index.data())
+ editor.star_rating = StarRating(index.data())
else:
QStyledItemDelegate.setEditorData(self, editor, index)
@@ -123,11 +86,11 @@ class StarDelegate(QStyledItemDelegate):
""" Get the data from our custom editor and stuffs it into the model.
"""
if index.column() == 3:
- model.setData(index, editor.starRating.starCount)
+ model.setData(index, editor.star_rating.star_count)
else:
QStyledItemDelegate.setModelData(self, editor, model, index)
- def commitAndCloseEditor(self):
+ def commit_and_close_editor(self):
""" Erm... commits the data and closes the editor. :) """
editor = self.sender()
@@ -139,35 +102,35 @@ class StarDelegate(QStyledItemDelegate):
if __name__ == "__main__":
""" Run the application. """
- from PySide2.QtWidgets import (QApplication, QTableWidget, QTableWidgetItem,
+ from PySide6.QtWidgets import (QApplication, QTableWidget, QTableWidgetItem,
QAbstractItemView)
import sys
app = QApplication(sys.argv)
# Create and populate the tableWidget
- tableWidget = QTableWidget(4, 4)
- tableWidget.setItemDelegate(StarDelegate())
- tableWidget.setEditTriggers(QAbstractItemView.DoubleClicked |
- QAbstractItemView.SelectedClicked)
- tableWidget.setSelectionBehavior(QAbstractItemView.SelectRows)
- tableWidget.setHorizontalHeaderLabels(["Title", "Genre", "Artist", "Rating"])
-
- data = [ ["Mass in B-Minor", "Baroque", "J.S. Bach", 5],
- ["Three More Foxes", "Jazz", "Maynard Ferguson", 4],
- ["Sex Bomb", "Pop", "Tom Jones", 3],
- ["Barbie Girl", "Pop", "Aqua", 5] ]
+ table_widget = QTableWidget(4, 4)
+ table_widget.setItemDelegate(StarDelegate())
+ table_widget.setEditTriggers(QAbstractItemView.DoubleClicked
+ | QAbstractItemView.SelectedClicked)
+ table_widget.setSelectionBehavior(QAbstractItemView.SelectRows)
+ table_widget.setHorizontalHeaderLabels(["Title", "Genre", "Artist", "Rating"])
+
+ data = [["Mass in B-Minor", "Baroque", "J.S. Bach", 5],
+ ["Three More Foxes", "Jazz", "Maynard Ferguson", 4],
+ ["Sex Bomb", "Pop", "Tom Jones", 3],
+ ["Barbie Girl", "Pop", "Aqua", 5]]
for r in range(len(data)):
- tableWidget.setItem(r, 0, QTableWidgetItem(data[r][0]))
- tableWidget.setItem(r, 1, QTableWidgetItem(data[r][1]))
- tableWidget.setItem(r, 2, QTableWidgetItem(data[r][2]))
+ table_widget.setItem(r, 0, QTableWidgetItem(data[r][0]))
+ table_widget.setItem(r, 1, QTableWidgetItem(data[r][1]))
+ table_widget.setItem(r, 2, QTableWidgetItem(data[r][2]))
item = QTableWidgetItem()
- item.setData(0, StarRating(data[r][3]).starCount)
- tableWidget.setItem(r, 3, item)
+ item.setData(0, StarRating(data[r][3]).star_count)
+ table_widget.setItem(r, 3, item)
- tableWidget.resizeColumnsToContents()
- tableWidget.resize(500, 300)
- tableWidget.show()
+ table_widget.resizeColumnsToContents()
+ table_widget.resize(500, 300)
+ table_widget.show()
- sys.exit(app.exec_())
+ sys.exit(app.exec())
diff --git a/examples/widgets/itemviews/stardelegate/stareditor.py b/examples/widgets/itemviews/stardelegate/stareditor.py
index 820aba8bf..296afa950 100644
--- a/examples/widgets/itemviews/stardelegate/stareditor.py
+++ b/examples/widgets/itemviews/stardelegate/stareditor.py
@@ -1,100 +1,62 @@
+# Copyright (C) 2010 Hans-Peter Jansen <hpj@urpla.net>
+# Copyright (C) 2011 Arun Srinivasan <rulfzid@gmail.com>
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-#############################################################################
-##
-## Copyright (C) 2010 Hans-Peter Jansen <hpj@urpla.net>
-## Copyright (C) 2011 Arun Srinivasan <rulfzid@gmail.com>
-## Copyright (C) 2016 The Qt Company Ltd.
-## Contact: http://www.qt.io/licensing/
-##
-## This file is part of the Qt for Python examples of the Qt Toolkit.
-##
-## $QT_BEGIN_LICENSE:BSD$
-## You may use this file under the terms of the BSD license as follows:
-##
-## "Redistribution and use in source and binary forms, with or without
-## modification, are permitted provided that the following conditions are
-## met:
-## * Redistributions of source code must retain the above copyright
-## notice, this list of conditions and the following disclaimer.
-## * Redistributions in binary form must reproduce the above copyright
-## notice, this list of conditions and the following disclaimer in
-## the documentation and/or other materials provided with the
-## distribution.
-## * Neither the name of The Qt Company Ltd nor the names of its
-## contributors may be used to endorse or promote products derived
-## from this software without specific prior written permission.
-##
-##
-## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
-##
-## $QT_END_LICENSE$
-##
-#############################################################################
-
-from PySide2.QtWidgets import (QWidget)
-from PySide2.QtGui import (QPainter)
-from PySide2.QtCore import Signal
+from PySide6.QtWidgets import (QWidget)
+from PySide6.QtGui import (QPainter)
+from PySide6.QtCore import Signal
from starrating import StarRating
+
class StarEditor(QWidget):
""" The custom editor for editing StarRatings. """
# A signal to tell the delegate when we've finished editing.
- editingFinished = Signal()
+ editing_finished = Signal()
def __init__(self, parent=None):
""" Initialize the editor object, making sure we can watch mouse
events.
"""
- super(StarEditor, self).__init__(parent)
+ super().__init__(parent)
self.setMouseTracking(True)
self.setAutoFillBackground(True)
- self.starRating = StarRating()
+ self.star_rating = StarRating()
def sizeHint(self):
""" Tell the caller how big we are. """
- return self.starRating.sizeHint()
+ return self.star_rating.sizeHint()
def paintEvent(self, event):
""" Paint the editor, offloading the work to the StarRating class. """
- painter = QPainter(self)
- self.starRating.paint(painter, self.rect(), self.palette(), isEditable=True)
+ with QPainter(self) as painter:
+ self.star_rating.paint(painter, self.rect(), self.palette(), isEditable=True)
def mouseMoveEvent(self, event):
""" As the mouse moves inside the editor, track the position and
update the editor to display as many stars as necessary.
"""
- star = self.starAtPosition(event.x())
+ star = self.star_at_position(event.x())
- if (star != self.starRating.starCount) and (star != -1):
- self.starRating.starCount = star
+ if (star != self.star_rating.star_count) and (star != -1):
+ self.star_rating.star_count = star
self.update()
def mouseReleaseEvent(self, event):
""" Once the user has clicked his/her chosen star rating, tell the
delegate we're done editing.
"""
- self.editingFinished.emit()
+ self.editing_finished.emit()
- def starAtPosition(self, x):
+ def star_at_position(self, x):
""" Calculate which star the user's mouse cursor is currently
hovering over.
"""
- star = (x / (self.starRating.sizeHint().width() /
- self.starRating.maxStarCount)) + 1
- if (star <= 0) or (star > self.starRating.maxStarCount):
+ star = (x / (self.star_rating.sizeHint().width() / self.star_rating.MAX_STAR_COUNT)) + 1
+ if (star <= 0) or (star > self.star_rating.MAX_STAR_COUNT):
return -1
return star
diff --git a/examples/widgets/itemviews/stardelegate/starrating.py b/examples/widgets/itemviews/stardelegate/starrating.py
index d40b382f4..28dbacd6f 100644
--- a/examples/widgets/itemviews/stardelegate/starrating.py
+++ b/examples/widgets/itemviews/stardelegate/starrating.py
@@ -1,50 +1,12 @@
-
-#############################################################################
-##
-## Copyright (C) 2010 Hans-Peter Jansen <hpj@urpla.net>
-## Copyright (C) 2011 Arun Srinivasan <rulfzid@gmail.com>
-## Copyright (C) 2016 The Qt Company Ltd.
-## Contact: http://www.qt.io/licensing/
-##
-## This file is part of the Qt for Python examples of the Qt Toolkit.
-##
-## $QT_BEGIN_LICENSE:BSD$
-## You may use this file under the terms of the BSD license as follows:
-##
-## "Redistribution and use in source and binary forms, with or without
-## modification, are permitted provided that the following conditions are
-## met:
-## * Redistributions of source code must retain the above copyright
-## notice, this list of conditions and the following disclaimer.
-## * Redistributions in binary form must reproduce the above copyright
-## notice, this list of conditions and the following disclaimer in
-## the documentation and/or other materials provided with the
-## distribution.
-## * Neither the name of The Qt Company Ltd nor the names of its
-## contributors may be used to endorse or promote products derived
-## from this software without specific prior written permission.
-##
-##
-## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
-##
-## $QT_END_LICENSE$
-##
-#############################################################################
+# Copyright (C) 2010 Hans-Peter Jansen <hpj@urpla.net>
+# Copyright (C) 2011 Arun Srinivasan <rulfzid@gmail.com>
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
from math import (cos, sin, pi)
-from PySide2.QtGui import (QPainter, QPolygonF)
-from PySide2.QtCore import (QPointF, QSize, Qt)
+from PySide6.QtGui import (QPainter, QPolygonF)
+from PySide6.QtCore import (QPointF, QSize, Qt)
PAINTING_SCALE_FACTOR = 20
@@ -53,26 +15,26 @@ class StarRating(object):
""" Handle the actual painting of the stars themselves. """
def __init__(self, starCount=1, maxStarCount=5):
- self.starCount = starCount
- self.maxStarCount = maxStarCount
+ self.star_count = starCount
+ self.MAX_STAR_COUNT = maxStarCount
# Create the star shape we'll be drawing.
- self.starPolygon = QPolygonF()
- self.starPolygon.append(QPointF(1.0, 0.5))
+ self._star_polygon = QPolygonF()
+ self._star_polygon.append(QPointF(1.0, 0.5))
for i in range(1, 5):
- self.starPolygon.append(QPointF(0.5 + 0.5 * cos(0.8 * i * pi),
- 0.5 + 0.5 * sin(0.8 * i * pi)))
+ self._star_polygon.append(QPointF(0.5 + 0.5 * cos(0.8 * i * pi),
+ 0.5 + 0.5 * sin(0.8 * i * pi)))
# Create the diamond shape we'll show in the editor
- self.diamondPolygon = QPolygonF()
- diamondPoints = [QPointF(0.4, 0.5), QPointF(0.5, 0.4),
- QPointF(0.6, 0.5), QPointF(0.5, 0.6),
- QPointF(0.4, 0.5)]
- self.diamondPolygon.append(diamondPoints)
+ self._diamond_polygon = QPolygonF()
+ diamond_points = [QPointF(0.4, 0.5), QPointF(0.5, 0.4),
+ QPointF(0.6, 0.5), QPointF(0.5, 0.6),
+ QPointF(0.4, 0.5)]
+ self._diamond_polygon.append(diamond_points)
def sizeHint(self):
""" Tell the caller how big we are. """
- return PAINTING_SCALE_FACTOR * QSize(self.maxStarCount, 1)
+ return PAINTING_SCALE_FACTOR * QSize(self.MAX_STAR_COUNT, 1)
def paint(self, painter, rect, palette, isEditable=False):
""" Paint the stars (and/or diamonds if we're in editing mode). """
@@ -86,15 +48,15 @@ class StarRating(object):
else:
painter.setBrush(palette.windowText())
- yOffset = (rect.height() - PAINTING_SCALE_FACTOR) / 2
- painter.translate(rect.x(), rect.y() + yOffset)
+ y_offset = (rect.height() - PAINTING_SCALE_FACTOR) / 2
+ painter.translate(rect.x(), rect.y() + y_offset)
painter.scale(PAINTING_SCALE_FACTOR, PAINTING_SCALE_FACTOR)
- for i in range(self.maxStarCount):
- if i < self.starCount:
- painter.drawPolygon(self.starPolygon, Qt.WindingFill)
+ for i in range(self.MAX_STAR_COUNT):
+ if i < self.star_count:
+ painter.drawPolygon(self._star_polygon, Qt.WindingFill)
elif isEditable:
- painter.drawPolygon(self.diamondPolygon, Qt.WindingFill)
+ painter.drawPolygon(self._diamond_polygon, Qt.WindingFill)
painter.translate(1.0, 0.0)
painter.restore()