diff options
Diffstat (limited to 'examples/corelib')
24 files changed, 1526 insertions, 1359 deletions
diff --git a/examples/corelib/ipc/sharedmemory/dialog.py b/examples/corelib/ipc/sharedmemory/dialog.py new file mode 100644 index 000000000..134900047 --- /dev/null +++ b/examples/corelib/ipc/sharedmemory/dialog.py @@ -0,0 +1,93 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +import os +from pathlib import Path + +from PySide6.QtWidgets import QFileDialog, QDialog +from PySide6.QtCore import QBuffer, QIODeviceBase, Slot, QSharedMemory, QDataStream, qVersion +from PySide6.QtGui import QImage, QPixmap +from ui_dialog import Ui_Dialog + + +class Dialog(QDialog): + def __init__(self, parent=None): + super().__init__(parent) + + v = qVersion() + name = f"QSharedMemoryExample_v{v}" + self._shared_memory = QSharedMemory(name) + + self.ui = Ui_Dialog() + self.ui.setupUi(self) + self.ui.loadFromFileButton.clicked.connect(self.load_from_file) + self.ui.loadFromSharedMemoryButton.clicked.connect(self.load_from_memory) + self.setWindowTitle("SharedMemory Example") + + def ensure_detached(self): + if self._shared_memory.isAttached(): + self.detach() + + def closeEvent(self, e): + self.ensure_detached() + e.accept() + + @Slot() + def load_from_file(self): + self.ensure_detached() + + self.ui.label.setText("Select an image file") + dir = Path(__file__).resolve().parent + fileName, _ = QFileDialog.getOpenFileName(self, "Choose Image", + os.fspath(dir), + "Images (*.png *.jpg)") + if not fileName: + return + image = QImage() + if not image.load(fileName): + self.ui.label.setText("Selected file is not an image, please select another.") + return + self.ui.label.setPixmap(QPixmap.fromImage(image)) + + # load into shared memory + buffer = QBuffer() + buffer.open(QIODeviceBase.WriteOnly) + out = QDataStream(buffer) + out << image + buffer.close() + size = buffer.size() + + if not self._shared_memory.create(size): + self.ui.label.setText("Unable to create shared memory segment.") + return + + self._shared_memory.lock() + _to = memoryview(self._shared_memory.data()) + _from = buffer.data().data() + _to[0:size] = _from[0:size] + self._shared_memory.unlock() + + @Slot() + def load_from_memory(self): + if not self._shared_memory.isAttached() and not self._shared_memory.attach(): + self.ui.label.setText("Unable to attach to shared memory segment.\n" + "Load an image first.") + return + + self._shared_memory.lock() + mv = memoryview(self._shared_memory.constData()) + buffer = QBuffer() + buffer.setData(mv.tobytes()) + buffer.open(QBuffer.ReadOnly) + _in = QDataStream(buffer) + image = QImage() + _in >> image + buffer.close() + self._shared_memory.unlock() + self._shared_memory.detach() + + self.ui.label.setPixmap(QPixmap.fromImage(image)) + + def detach(self): + if not self._shared_memory.detach(): + self.ui.label.setText(self.tr("Unable to detach from shared memory.")) # noqa: F821 diff --git a/examples/corelib/ipc/sharedmemory/dialog.ui b/examples/corelib/ipc/sharedmemory/dialog.ui new file mode 100644 index 000000000..e99d6fb3c --- /dev/null +++ b/examples/corelib/ipc/sharedmemory/dialog.ui @@ -0,0 +1,47 @@ +<ui version="4.0" > + <class>Dialog</class> + <widget class="QDialog" name="Dialog" > + <property name="geometry" > + <rect> + <x>0</x> + <y>0</y> + <width>451</width> + <height>322</height> + </rect> + </property> + <property name="windowTitle" > + <string>Dialog</string> + </property> + <layout class="QGridLayout" > + <item row="0" column="0" > + <widget class="QPushButton" name="loadFromFileButton" > + <property name="text" > + <string>Load Image From File...</string> + </property> + </widget> + </item> + <item row="1" column="0" > + <widget class="QLabel" name="label" > + <property name="text" > + <string>Launch two of these dialogs. In the first, press the top button and load an image from a file. In the second, press the bottom button and display the loaded image from shared memory.</string> + </property> + <property name="alignment" > + <set>Qt::AlignCenter</set> + </property> + <property name="wordWrap" > + <bool>true</bool> + </property> + </widget> + </item> + <item row="2" column="0" > + <widget class="QPushButton" name="loadFromSharedMemoryButton" > + <property name="text" > + <string>Display Image From Shared Memory</string> + </property> + </widget> + </item> + </layout> + </widget> + <resources/> + <connections/> +</ui> diff --git a/examples/corelib/ipc/sharedmemory/image.png b/examples/corelib/ipc/sharedmemory/image.png Binary files differnew file mode 100644 index 000000000..dd9345306 --- /dev/null +++ b/examples/corelib/ipc/sharedmemory/image.png diff --git a/examples/corelib/ipc/sharedmemory/main.py b/examples/corelib/ipc/sharedmemory/main.py new file mode 100644 index 000000000..e497c8de6 --- /dev/null +++ b/examples/corelib/ipc/sharedmemory/main.py @@ -0,0 +1,15 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +"""PySide6 port of the ipc/sharedmemory example from Qt v6.x""" + +import sys +from PySide6.QtWidgets import QApplication +from dialog import Dialog + + +if __name__ == "__main__": + application = QApplication() + dialog = Dialog() + dialog.show() + sys.exit(application.exec()) diff --git a/examples/corelib/ipc/sharedmemory/qt.png b/examples/corelib/ipc/sharedmemory/qt.png Binary files differnew file mode 100644 index 000000000..4f68e162d --- /dev/null +++ b/examples/corelib/ipc/sharedmemory/qt.png diff --git a/examples/corelib/ipc/sharedmemory/sharedmemory.pyproject b/examples/corelib/ipc/sharedmemory/sharedmemory.pyproject new file mode 100644 index 000000000..14fe88f13 --- /dev/null +++ b/examples/corelib/ipc/sharedmemory/sharedmemory.pyproject @@ -0,0 +1,3 @@ +{ + "files": ["dialog.py", "dialog.ui", "image.png", "main.py", "qt.png"] +} diff --git a/examples/corelib/ipc/sharedmemory/ui_dialog.py b/examples/corelib/ipc/sharedmemory/ui_dialog.py new file mode 100644 index 000000000..2cd544f40 --- /dev/null +++ b/examples/corelib/ipc/sharedmemory/ui_dialog.py @@ -0,0 +1,57 @@ +# -*- coding: utf-8 -*- + +################################################################################ +## Form generated from reading UI file 'dialog.ui' +## +## Created by: Qt User Interface Compiler version 6.7.0 +## +## WARNING! All changes made in this file will be lost when recompiling UI file! +################################################################################ + +from PySide6.QtCore import (QCoreApplication, QDate, QDateTime, QLocale, + QMetaObject, QObject, QPoint, QRect, + QSize, QTime, QUrl, Qt) +from PySide6.QtGui import (QBrush, QColor, QConicalGradient, QCursor, + QFont, QFontDatabase, QGradient, QIcon, + QImage, QKeySequence, QLinearGradient, QPainter, + QPalette, QPixmap, QRadialGradient, QTransform) +from PySide6.QtWidgets import (QApplication, QDialog, QGridLayout, QLabel, + QPushButton, QSizePolicy, QWidget) + +class Ui_Dialog(object): + def setupUi(self, Dialog): + if not Dialog.objectName(): + Dialog.setObjectName(u"Dialog") + Dialog.resize(451, 322) + self.gridLayout = QGridLayout(Dialog) + self.gridLayout.setObjectName(u"gridLayout") + self.loadFromFileButton = QPushButton(Dialog) + self.loadFromFileButton.setObjectName(u"loadFromFileButton") + + self.gridLayout.addWidget(self.loadFromFileButton, 0, 0, 1, 1) + + self.label = QLabel(Dialog) + self.label.setObjectName(u"label") + self.label.setAlignment(Qt.AlignCenter) + self.label.setWordWrap(True) + + self.gridLayout.addWidget(self.label, 1, 0, 1, 1) + + self.loadFromSharedMemoryButton = QPushButton(Dialog) + self.loadFromSharedMemoryButton.setObjectName(u"loadFromSharedMemoryButton") + + self.gridLayout.addWidget(self.loadFromSharedMemoryButton, 2, 0, 1, 1) + + + self.retranslateUi(Dialog) + + QMetaObject.connectSlotsByName(Dialog) + # setupUi + + def retranslateUi(self, Dialog): + Dialog.setWindowTitle(QCoreApplication.translate("Dialog", u"Dialog", None)) + self.loadFromFileButton.setText(QCoreApplication.translate("Dialog", u"Load Image From File...", None)) + self.label.setText(QCoreApplication.translate("Dialog", u"Launch two of these dialogs. In the first, press the top button and load an image from a file. In the second, press the bottom button and display the loaded image from shared memory.", None)) + self.loadFromSharedMemoryButton.setText(QCoreApplication.translate("Dialog", u"Display Image From Shared Memory", None)) + # retranslateUi + diff --git a/examples/corelib/mimetypesbrowser/doc/mimetypesbrowser.png b/examples/corelib/mimetypesbrowser/doc/mimetypesbrowser.png Binary files differnew file mode 100644 index 000000000..3c4a476b3 --- /dev/null +++ b/examples/corelib/mimetypesbrowser/doc/mimetypesbrowser.png diff --git a/examples/corelib/mimetypesbrowser/doc/mimetypesbrowser.rst b/examples/corelib/mimetypesbrowser/doc/mimetypesbrowser.rst new file mode 100644 index 000000000..e8e4d1ecd --- /dev/null +++ b/examples/corelib/mimetypesbrowser/doc/mimetypesbrowser.rst @@ -0,0 +1,10 @@ +MIME Type Browser Example +========================= + +A Python application that demonstrates the analogous example in C++ +`MIME Type Browser Example <https://doc.qt.io/qt-6/qtcore-mimetypes-mimetypebrowser-example.html>`_ + +.. image:: mimetypesbrowser.png + :width: 400 + :alt: mimetypebrowser screenshot + diff --git a/examples/corelib/mimetypesbrowser/mainwindow.py b/examples/corelib/mimetypesbrowser/mainwindow.py new file mode 100644 index 000000000..76404eeb7 --- /dev/null +++ b/examples/corelib/mimetypesbrowser/mainwindow.py @@ -0,0 +1,161 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +from mimetypemodel import MimeTypeModel +from PySide6.QtCore import (QDir, QFileInfo, QMimeDatabase, QModelIndex, Qt, + Slot) +from PySide6.QtGui import QAction, QKeySequence +from PySide6.QtWidgets import (QAbstractItemView, QApplication, QDialog, + QFileDialog, QInputDialog, QMainWindow, + QMessageBox, QSplitter, QTextEdit, QTreeView, + QWidget) + + +class MainWindow(QMainWindow): + def __init__(self, parent: QWidget = None) -> None: + super().__init__(parent=parent) + self.m_find_index: int = 0 + self.m_model = MimeTypeModel(self) + self.m_tree_view = QTreeView(self) + self.m_details_text = QTextEdit(self) + self.m_find_matches = [] + + self.setWindowTitle("Qt Mime Database Browser") + + # create actions + self.detect_file_action = QAction( + "&Detect File Type...", self, shortcut="Ctrl+O", triggered=self.detect_file + ) + self.exit_action = QAction("E&xit", self, shortcut="Ctrl+Q", triggered=self.close) + self.m_find_action = QAction("&Find...", self, shortcut="Ctrl+F", triggered=self.find) + self.m_find_next_action = QAction( + "Find &Next", self, shortcut="Ctrl+G", triggered=self.find_next + ) + self.m_find_previous_action = QAction( + "Find &Previous", + self, + shortcut="Ctrl+Shift+G", + triggered=self.find_previous, + ) + self.about_action = QAction( + "About Qt", + self, + shortcut=QKeySequence(QKeySequence.HelpContents), + triggered=QApplication.aboutQt, + ) + + # add action to menu + self.file_menu = self.menuBar().addMenu("&File") + self.file_menu.addAction(self.detect_file_action) + self.file_menu.addAction(self.exit_action) + self.find_menu = self.menuBar().addMenu("&Edit") + self.find_menu.addAction(self.m_find_action) + self.find_menu.addAction(self.m_find_next_action) + self.find_menu.addAction(self.m_find_previous_action) + self.about_menu = self.menuBar().addMenu("&About") + self.about_menu.addAction(self.about_action) + + self.central_splitter = QSplitter(self) + self.setCentralWidget(self.central_splitter) + + self.m_tree_view.setUniformRowHeights(True) + self.m_tree_view.setModel(self.m_model) + + self.items = self.m_model.findItems( + "application/octet-stream", + Qt.MatchContains | Qt.MatchFixedString | Qt.MatchRecursive, + ) + + if self.items: + self.m_tree_view.expand(self.m_model.indexFromItem(self.items[0])) + + self.m_tree_view.selectionModel().currentChanged.connect(self.current_changed) + self.central_splitter.addWidget(self.m_tree_view) + self.m_details_text.setReadOnly(True) + self.central_splitter.addWidget(self.m_details_text) + + self.update_find_actions() + + @Slot() + def detect_file(self): + file_name = QFileDialog.getOpenFileName(self, "Choose File") + if not file_name: + return + + mime_database = QMimeDatabase() + fi = QFileInfo(file_name[0]) + mime_type = mime_database.mimeTypeForFile(fi) + index = ( + self.m_model.indexForMimeType(mime_type.name()) + if mime_type.isValid() + else QModelIndex() + ) + + if index.isValid(): + self.statusBar().showMessage(f'{fi.fileName()}" is of type "{mime_type.name()}"') + self._select_and_goto(index) + else: + QMessageBox.information( + self, + "Unknown File Type", + f"The type of {QDir.toNativeSeparators(file_name)} could not be determined.", + ) + + @Slot() + def find(self): + input_dialog = QInputDialog(self) + input_dialog.setWindowTitle("Find") + input_dialog.setLabelText("Text") + if input_dialog.exec() != QDialog.Accepted: + return + + value = input_dialog.textValue().strip() + if not value: + return + + self.m_find_matches.clear() + self.m_find_index = 0 + items = self.m_model.findItems( + value, Qt.MatchContains | Qt.MatchFixedString | Qt.MatchRecursive + ) + + for item in items: + self.m_find_matches.append(self.m_model.indexFromItem(item)) + + self.statusBar().showMessage(f'{len(self.m_find_matches)} mime types match "{value}".') + self.update_find_actions() + + if self.m_find_matches: + self._select_and_goto(self.m_find_matches[0]) + + @Slot() + def find_next(self): + self.m_find_index = self.m_find_index + 1 + if self.m_find_index >= len(self.m_find_matches): + self.m_find_index = 0 + if self.m_find_index < len(self.m_find_matches): + self._select_and_goto(self.m_find_matches[self.m_find_index]) + + @Slot() + def find_previous(self): + self.m_find_index = self.m_find_index - 1 + if self.m_find_index < 0: + self.m_find_index = len(self.m_find_matches) - 1 + if self.m_find_index >= 0: + self._select_and_goto(self.m_find_matches[self.m_find_index]) + + @Slot(QModelIndex) + def current_changed(self, index: QModelIndex): + if index.isValid(): + self.m_details_text.setText( + MimeTypeModel.formatMimeTypeInfo(self.m_model.mimeType(index)) + ) + + def update_find_actions(self): + self.find_next_previous_enabled = len(self.m_find_matches) > 1 + self.m_find_next_action.setEnabled(self.find_next_previous_enabled) + self.m_find_previous_action.setEnabled(self.find_next_previous_enabled) + + def _select_and_goto(self, index: QModelIndex): + self.m_tree_view.scrollTo(index, QAbstractItemView.PositionAtCenter) + self.m_tree_view.setCurrentIndex(index) diff --git a/examples/corelib/mimetypesbrowser/mimetypemodel.py b/examples/corelib/mimetypesbrowser/mimetypemodel.py new file mode 100644 index 000000000..5243f110e --- /dev/null +++ b/examples/corelib/mimetypesbrowser/mimetypemodel.py @@ -0,0 +1,136 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +from typing import List + +from PySide6.QtCore import QMimeDatabase, QMimeType, QModelIndex, QObject, Qt, qWarning +from PySide6.QtGui import QStandardItem, QStandardItemModel + +mimeTypeRole = Qt.UserRole + 1 +iconQueriedRole = Qt.UserRole + 2 + + +def createRow(t: QMimeType): + name_item = QStandardItem(t.name()) + flags = Qt.ItemIsSelectable | Qt.ItemIsEnabled + name_item.setData(t, mimeTypeRole) + name_item.setData(False, iconQueriedRole) + name_item.setFlags(flags) + name_item.setToolTip(t.comment()) + return [name_item] + + +class MimeTypeModel(QStandardItemModel): + def __init__(self, parent: QObject = None): + super().__init__(0, 1, parent) + self.setHorizontalHeaderLabels(["Name"]) + self.m_name_index_hash = {} + self.populate() + + def populate(self): + mime_database = QMimeDatabase() + all_types: List[QMimeType] = mime_database.allMimeTypes() + + # Move top level types to rear end of list, sort this partition, + # create top level items and truncate the list. + with_parent_mimetypes, without_parent_mimetypes = [], [] + + for mime_type in all_types: + if mime_type.parentMimeTypes(): + with_parent_mimetypes.append(mime_type) + else: + without_parent_mimetypes.append(mime_type) + + without_parent_mimetypes.sort(key=lambda x: x.name()) + + for top_level_type in without_parent_mimetypes: + row = createRow(top_level_type) + self.appendRow(row) + self.m_name_index_hash[top_level_type.name()] = self.indexFromItem(row[0]) + + all_types = with_parent_mimetypes + + while all_types: + # Find a type inheriting one that is already in the model. + name_index_value: QModelIndex = None + name_index_key = "" + for mime_type in all_types: + name_index_value = self.m_name_index_hash.get( + mime_type.parentMimeTypes()[0] + ) + if name_index_value: + name_index_key = mime_type.parentMimeTypes()[0] + break + + if not name_index_value: + orphaned_mime_types = ", ".join( + [mime_type.name() for mime_type in all_types] + ) + qWarning(f"Orphaned mime types: {orphaned_mime_types}") + break + + # Move types inheriting the parent type to rear end of list, sort this partition, + # append the items to parent and truncate the list. + parent_name = name_index_key + with_parent_name, without_parent_name = [], [] + + for mime_type in all_types: + if parent_name in mime_type.parentMimeTypes(): + with_parent_name.append(mime_type) + else: + without_parent_name.append(mime_type) + + without_parent_name.sort(key=lambda x: x.name()) + parent_item = self.itemFromIndex(name_index_value) + + for mime_type in with_parent_name: + row = createRow(mime_type) + parent_item.appendRow(row) + self.m_name_index_hash[mime_type.name()] = self.indexFromItem(row[0]) + + all_types = without_parent_name + + def mimeType(self, index: QModelIndex): + return index.data(mimeTypeRole) + + def indexForMimeType(self, name): + return self.m_name_index_hash[name] + + @staticmethod + def formatMimeTypeInfo(t: QMimeType): + out = f"<html><head/><body><h3><center>{t.name()}</center></h3><br><table>" + aliases_str = ", ".join(t.aliases()) + if aliases_str: + out += f"<tr><td>Aliases:</td><td> ({aliases_str})" + + out += ( + f"</td></tr><tr><td>Comment:</td><td>{t.comment()}" + f"</td></tr><tr><td>Icon name:</td><td>{t.iconName()}</td></tr>" + f"<tr><td>Generic icon name</td><td>{t.genericIconName()}</td></tr>" + ) + + filter_str = t.filterString() + if filter_str: + out += f"<tr><td>Filter:</td><td>{filter_str}</td></tr>" + + patterns_str = ", ".join(t.globPatterns()) + if patterns_str: + out += f"<tr><td>Glob patterns:</td><td>{patterns_str}</td></tr>" + + parentMimeTypes_str = ", ".join(t.parentMimeTypes()) + if parentMimeTypes_str: + out += f"<tr><td>Parent types:</td><td>{parentMimeTypes_str}</td></tr>" + + suffixes = t.suffixes() + if suffixes: + out += "<tr><td>Suffixes:</td><td>" + preferredSuffix = t.preferredSuffix() + if preferredSuffix: + suffixes.remove(preferredSuffix) + out += f"<b>{preferredSuffix}</b> " + suffixes_str = ", ".join(suffixes) + out += f"{suffixes_str}</td></tr>" + + out += "</table></body></html>" + + return out diff --git a/examples/corelib/mimetypesbrowser/mimetypesbrowser.py b/examples/corelib/mimetypesbrowser/mimetypesbrowser.py new file mode 100644 index 000000000..4742a31b8 --- /dev/null +++ b/examples/corelib/mimetypesbrowser/mimetypesbrowser.py @@ -0,0 +1,24 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +"""PySide6 port of the corelib/mimetypes/mimetypebrowser example from from Qt""" + +import argparse +import sys + +from mainwindow import MainWindow +from PySide6.QtWidgets import QApplication + +if __name__ == "__main__": + app = QApplication(sys.argv) + + parser = argparse.ArgumentParser(description="MimeTypesBrowser Example") + parser.add_argument("-v", "--version", action="version", version="%(prog)s 1.0") + args = parser.parse_args() + + mainWindow = MainWindow() + availableGeometry = mainWindow.screen().availableGeometry() + mainWindow.resize(availableGeometry.width() / 3, availableGeometry.height() / 2) + mainWindow.show() + + sys.exit(app.exec()) diff --git a/examples/corelib/mimetypesbrowser/mimetypesbrowser.pyproject b/examples/corelib/mimetypesbrowser/mimetypesbrowser.pyproject new file mode 100644 index 000000000..ada4252da --- /dev/null +++ b/examples/corelib/mimetypesbrowser/mimetypesbrowser.pyproject @@ -0,0 +1,3 @@ +{ + "files": ["mimetypesbrowser.py"] +} diff --git a/examples/corelib/settingseditor/doc/settingseditor.png b/examples/corelib/settingseditor/doc/settingseditor.png Binary files differnew file mode 100644 index 000000000..d281125d3 --- /dev/null +++ b/examples/corelib/settingseditor/doc/settingseditor.png diff --git a/examples/corelib/settingseditor/doc/settingseditor.rst b/examples/corelib/settingseditor/doc/settingseditor.rst new file mode 100644 index 000000000..4c60dbe8e --- /dev/null +++ b/examples/corelib/settingseditor/doc/settingseditor.rst @@ -0,0 +1,10 @@ +Settings Editor Example +======================= + +The Settings Editor example shows how Qt's standard settings support is used in +an application by providing an editor that enables the user to view the +settings for installed applications, and modify those that can be edited. + +.. image:: settingseditor.png + :width: 400 + :alt: Settings Editor Screenshot diff --git a/examples/corelib/settingseditor/settingseditor.py b/examples/corelib/settingseditor/settingseditor.py new file mode 100644 index 000000000..f87a2f4b5 --- /dev/null +++ b/examples/corelib/settingseditor/settingseditor.py @@ -0,0 +1,753 @@ +# 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 widgets/tools/settingseditor example from Qt v5.x""" + +import sys + +from PySide6.QtCore import (QByteArray, QDate, QDateTime, QDir, QEvent, QPoint, + QRect, QRegularExpression, QSettings, QSize, QTime, + QTimer, Qt, Slot) +from PySide6.QtGui import (QAction, QColor, QIcon, QIntValidator, + QDoubleValidator, QRegularExpressionValidator, + QValidator) +from PySide6.QtWidgets import (QAbstractItemView, QApplication, + QCheckBox, QComboBox, QFileDialog, QDialog, + QDialogButtonBox, QGridLayout, + QGroupBox, QHeaderView, QInputDialog, + QItemDelegate, QLabel, QLineEdit, + QMainWindow, QMessageBox, QStyle, QSpinBox, + QStyleOptionViewItem, QTableWidget, + QTableWidgetItem, QTreeWidget, QTreeWidgetItem, + QVBoxLayout) + + +class TypeChecker: + def __init__(self, parent=None): + self.bool_exp = QRegularExpression('^(true)|(false)$') + assert self.bool_exp.isValid() + self.bool_exp.setPatternOptions(QRegularExpression.CaseInsensitiveOption) + + self.byteArray_exp = QRegularExpression(r'^[\x00-\xff]*$') + assert self.byteArray_exp.isValid() + + self.char_exp = QRegularExpression('^.$') + assert self.char_exp.isValid() + + pattern = r'^[+-]?\d+$' + self.int_exp = QRegularExpression(pattern) + assert self.int_exp.isValid() + + pattern = r'^\(([0-9]*),([0-9]*),([0-9]*),([0-9]*)\)$' + self.color_exp = QRegularExpression(pattern) + assert self.color_exp.isValid() + + pattern = r'^\((-?[0-9]*),(-?[0-9]*)\)$' + self.point_exp = QRegularExpression(pattern) + assert self.point_exp.isValid() + + pattern = r'^\((-?[0-9]*),(-?[0-9]*),(-?[0-9]*),(-?[0-9]*)\)$' + self.rect_exp = QRegularExpression(pattern) + assert self.rect_exp.isValid() + + self.size_exp = QRegularExpression(self.point_exp) + + date_pattern = '([0-9]{,4})-([0-9]{,2})-([0-9]{,2})' + self.date_exp = QRegularExpression(f'^{date_pattern}$') + assert self.date_exp.isValid() + + time_pattern = '([0-9]{,2}):([0-9]{,2}):([0-9]{,2})' + self.time_exp = QRegularExpression(f'^{time_pattern}$') + assert self.time_exp.isValid() + + pattern = f'^{date_pattern}T{time_pattern}$' + self.dateTime_exp = QRegularExpression(pattern) + assert self.dateTime_exp.isValid() + + def type_from_text(self, text): + if self.bool_exp.match(text).hasMatch(): + return bool + if self.int_exp.match(text).hasMatch(): + return int + return None + + def create_validator(self, value, parent): + if isinstance(value, bool): + return QRegularExpressionValidator(self.bool_exp, parent) + if isinstance(value, float): + return QDoubleValidator(parent) + if isinstance(value, int): + return QIntValidator(parent) + if isinstance(value, QByteArray): + return QRegularExpressionValidator(self.byteArray_exp, parent) + if isinstance(value, QColor): + return QRegularExpressionValidator(self.color_exp, parent) + if isinstance(value, QDate): + return QRegularExpressionValidator(self.date_exp, parent) + if isinstance(value, QDateTime): + return QRegularExpressionValidator(self.dateTime_exp, parent) + if isinstance(value, QTime): + return QRegularExpressionValidator(self.time_exp, parent) + if isinstance(value, QPoint): + return QRegularExpressionValidator(self.point_exp, parent) + if isinstance(value, QRect): + return QRegularExpressionValidator(self.rect_exp, parent) + if isinstance(value, QSize): + return QRegularExpressionValidator(self.size_exp, parent) + return None + + def from_string(self, text, original_value): + if isinstance(original_value, QColor): + match = self.color_exp.match(text) + return QColor(min(int(match.captured(1)), 255), + min(int(match.captured(2)), 255), + min(int(match.captured(3)), 255), + min(int(match.captured(4)), 255)) + if isinstance(original_value, QDate): + value = QDate.fromString(text, Qt.ISODate) + return value if value.isValid() else None + if isinstance(original_value, QDateTime): + value = QDateTime.fromString(text, Qt.ISODate) + return value if value.isValid() else None + if isinstance(original_value, QTime): + value = QTime.fromString(text, Qt.ISODate) + return value if value.isValid() else None + if isinstance(original_value, QPoint): + match = self.point_exp.match(text) + return QPoint(int(match.captured(1)), + int(match.captured(2))) + if isinstance(original_value, QRect): + match = self.rect_exp.match(text) + return QRect(int(match.captured(1)), + int(match.captured(2)), + int(match.captured(3)), + int(match.captured(4))) + if isinstance(original_value, QSize): + match = self.size_exp.match(text) + return QSize(int(match.captured(1)), + int(match.captured(2))) + if isinstance(original_value, list): + return text.split(',') + return type(original_value)(text) + + +class MainWindow(QMainWindow): + def __init__(self, parent=None): + super().__init__(parent) + + self.settings_tree = SettingsTree() + self.setCentralWidget(self.settings_tree) + + self.location_dialog = None + + self.create_actions() + self.create_menus() + + self.auto_refresh_action.setChecked(True) + self.fallbacks_action.setChecked(True) + + self.setWindowTitle("Settings Editor") + self.resize(500, 600) + + @Slot() + def open_settings(self): + if self.location_dialog is None: + self.location_dialog = LocationDialog(self) + + if self.location_dialog.exec(): + settings = QSettings(self.location_dialog.format(), + self.location_dialog.scope(), + self.location_dialog.organization(), + self.location_dialog.application()) + self.set_settings_object(settings) + self.fallbacks_action.setEnabled(True) + + @Slot() + def open_inifile(self): + file_name, _ = QFileDialog.getOpenFileName(self, "Open INI File", + '', "INI Files (*.ini *.conf)") + + if file_name: + self.load_ini_file(file_name) + + def load_ini_file(self, file_name): + settings = QSettings(file_name, QSettings.IniFormat) + if settings.status() != QSettings.NoError: + return + self.set_settings_object(settings) + self.fallbacks_action.setEnabled(False) + + @Slot() + def open_property_list(self): + file_name, _ = QFileDialog.getOpenFileName(self, + "Open Property List", '', + "Property List Files (*.plist)") + + if file_name: + settings = QSettings(file_name, QSettings.NativeFormat) + self.set_settings_object(settings) + self.fallbacks_action.setEnabled(False) + + @Slot() + def open_registry_path(self): + path, ok = QInputDialog.getText(self, "Open Registry Path", + "Enter the path in the Windows registry:", + QLineEdit.Normal, 'HKEY_CURRENT_USER\\') + + if ok and path != '': + settings = QSettings(path, QSettings.NativeFormat) + self.set_settings_object(settings) + self.fallbacks_action.setEnabled(False) + + @Slot() + def about(self): + QMessageBox.about(self, "About Settings Editor", + "The <b>Settings Editor</b> example shows how to access " + "application settings using Qt.") + + def create_actions(self): + self.open_settings_action = QAction("&Open Application Settings...", + self, shortcut="Ctrl+O", triggered=self.open_settings) + + self.open_ini_file_action = QAction("Open I&NI File...", self, + shortcut="Ctrl+N", triggered=self.open_inifile) + + self.open_property_list_action = QAction("Open macOS &Property List...", self, + shortcut="Ctrl+P", + triggered=self.open_property_list) + if sys.platform != 'darwin': + self.open_property_list_action.setEnabled(False) + + self.open_registry_path_action = QAction( + "Open Windows &Registry Path...", self, shortcut="Ctrl+G", + triggered=self.open_registry_path) + if sys.platform != 'win32': + self.open_registry_path_action.setEnabled(False) + + self.refresh_action = QAction("&Refresh", self, shortcut="Ctrl+R", + enabled=False, triggered=self.settings_tree.refresh) + + self.exit_action = QAction("E&xit", self, shortcut="Ctrl+Q", triggered=self.close) + + self.auto_refresh_action = QAction("&Auto-Refresh", self, + shortcut="Ctrl+A", checkable=True, enabled=False) + self.auto_refresh_action.triggered[bool].connect(self.settings_tree.set_auto_refresh) + self.auto_refresh_action.triggered[bool].connect(self.refresh_action.setDisabled) + + self.fallbacks_action = QAction("&Fallbacks", self, + shortcut="Ctrl+F", checkable=True, enabled=False) + self.fallbacks_action.triggered[bool].connect(self.settings_tree.set_fallbacks_enabled) + + self.about_action = QAction("&About", self, triggered=self.about) + + self.about_Qt_action = QAction("About &Qt", self, + triggered=qApp.aboutQt) # noqa: F821 + + def create_menus(self): + self.file_menu = self.menuBar().addMenu("&File") + self.file_menu.addAction(self.open_settings_action) + self.file_menu.addAction(self.open_ini_file_action) + self.file_menu.addAction(self.open_property_list_action) + self.file_menu.addAction(self.open_registry_path_action) + self.file_menu.addSeparator() + self.file_menu.addAction(self.refresh_action) + self.file_menu.addSeparator() + self.file_menu.addAction(self.exit_action) + + self.options_menu = self.menuBar().addMenu("&Options") + self.options_menu.addAction(self.auto_refresh_action) + self.options_menu.addAction(self.fallbacks_action) + + self.menuBar().addSeparator() + + self.help_menu = self.menuBar().addMenu("&Help") + self.help_menu.addAction(self.about_action) + self.help_menu.addAction(self.about_Qt_action) + + def set_settings_object(self, settings): + settings.setFallbacksEnabled(self.fallbacks_action.isChecked()) + self.settings_tree.set_settings_object(settings) + + self.refresh_action.setEnabled(True) + self.auto_refresh_action.setEnabled(True) + + nice_name = QDir.fromNativeSeparators(settings.fileName()) + nice_name = nice_name.split('/')[-1] + + if not settings.isWritable(): + nice_name += " (read only)" + + self.setWindowTitle(f"{nice_name} - Settings Editor") + + +class LocationDialog(QDialog): + def __init__(self, parent=None): + super().__init__(parent) + + self.format_combo = QComboBox() + self.format_combo.addItem("Native") + self.format_combo.addItem("INI") + + self.scope_cCombo = QComboBox() + self.scope_cCombo.addItem("User") + self.scope_cCombo.addItem("System") + + self.organization_combo = QComboBox() + self.organization_combo.addItem("Trolltech") + self.organization_combo.setEditable(True) + + self.application_combo = QComboBox() + self.application_combo.addItem("Any") + self.application_combo.addItem("Application Example") + self.application_combo.addItem("Assistant") + self.application_combo.addItem("Designer") + self.application_combo.addItem("Linguist") + self.application_combo.setEditable(True) + self.application_combo.setCurrentIndex(3) + + format_label = QLabel("&Format:") + format_label.setBuddy(self.format_combo) + + scope_label = QLabel("&Scope:") + scope_label.setBuddy(self.scope_cCombo) + + organization_label = QLabel("&Organization:") + organization_label.setBuddy(self.organization_combo) + + application_label = QLabel("&Application:") + application_label.setBuddy(self.application_combo) + + self.locations_groupbox = QGroupBox("Setting Locations") + + self.locations_table = QTableWidget() + self.locations_table.setSelectionMode(QAbstractItemView.SingleSelection) + self.locations_table.setSelectionBehavior(QAbstractItemView.SelectRows) + self.locations_table.setEditTriggers(QAbstractItemView.NoEditTriggers) + self.locations_table.setColumnCount(2) + self.locations_table.setHorizontalHeaderLabels(("Location", "Access")) + self.locations_table.horizontalHeader().setSectionResizeMode(0, QHeaderView.Stretch) + self.locations_table.horizontalHeader().resizeSection(1, 180) + + self.button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) + + self.format_combo.activated.connect(self.update_locations) + self.scope_cCombo.activated.connect(self.update_locations) + self.organization_combo.lineEdit().editingFinished.connect(self.update_locations) + self.application_combo.lineEdit().editingFinished.connect(self.update_locations) + self.button_box.accepted.connect(self.accept) + self.button_box.rejected.connect(self.reject) + + locations_layout = QVBoxLayout(self.locations_groupbox) + locations_layout.addWidget(self.locations_table) + + main_layout = QGridLayout(self) + main_layout.addWidget(format_label, 0, 0) + main_layout.addWidget(self.format_combo, 0, 1) + main_layout.addWidget(scope_label, 1, 0) + main_layout.addWidget(self.scope_cCombo, 1, 1) + main_layout.addWidget(organization_label, 2, 0) + main_layout.addWidget(self.organization_combo, 2, 1) + main_layout.addWidget(application_label, 3, 0) + main_layout.addWidget(self.application_combo, 3, 1) + main_layout.addWidget(self.locations_groupbox, 4, 0, 1, 2) + main_layout.addWidget(self.button_box, 5, 0, 1, 2) + + self.update_locations() + + self.setWindowTitle("Open Application Settings") + self.resize(650, 400) + + def format(self): + if self.format_combo.currentIndex() == 0: + return QSettings.NativeFormat + else: + return QSettings.IniFormat + + def scope(self): + if self.scope_cCombo.currentIndex() == 0: + return QSettings.UserScope + else: + return QSettings.SystemScope + + def organization(self): + return self.organization_combo.currentText() + + def application(self): + if self.application_combo.currentText() == "Any": + return '' + + return self.application_combo.currentText() + + def update_locations(self): + self.locations_table.setUpdatesEnabled(False) + self.locations_table.setRowCount(0) + + for i in range(2): + if i == 0: + if self.scope() == QSettings.SystemScope: + continue + + actual_scope = QSettings.UserScope + else: + actual_scope = QSettings.SystemScope + + for j in range(2): + if j == 0: + if not self.application(): + continue + + actual_application = self.application() + else: + actual_application = '' + + settings = QSettings(self.format(), actual_scope, + self.organization(), actual_application) + + row = self.locations_table.rowCount() + self.locations_table.setRowCount(row + 1) + + item0 = QTableWidgetItem() + item0.setText(settings.fileName()) + + item1 = QTableWidgetItem() + disable = not (settings.childKeys() or settings.childGroups()) + + if row == 0: + if settings.isWritable(): + item1.setText("Read-write") + disable = False + else: + item1.setText("Read-only") + self.button_box.button(QDialogButtonBox.Ok).setDisabled(disable) + else: + item1.setText("Read-only fallback") + + if disable: + item0.setFlags(item0.flags() & ~Qt.ItemIsEnabled) + item1.setFlags(item1.flags() & ~Qt.ItemIsEnabled) + + self.locations_table.setItem(row, 0, item0) + self.locations_table.setItem(row, 1, item1) + + self.locations_table.setUpdatesEnabled(True) + + +class SettingsTree(QTreeWidget): + def __init__(self, parent=None): + super().__init__(parent) + + self._type_checker = TypeChecker() + self.setItemDelegate(VariantDelegate(self._type_checker, self)) + + self.setHeaderLabels(("Setting", "Type", "Value")) + self.header().setSectionResizeMode(0, QHeaderView.Stretch) + self.header().setSectionResizeMode(2, QHeaderView.Stretch) + + self.settings = None + self.refresh_timer = QTimer() + self.refresh_timer.setInterval(2000) + self.auto_refresh = False + + self.group_icon = QIcon() + style = self.style() + self.group_icon.addPixmap(style.standardPixmap(QStyle.SP_DirClosedIcon), + QIcon.Normal, QIcon.Off) + self.group_icon.addPixmap(style.standardPixmap(QStyle.SP_DirOpenIcon), + QIcon.Normal, QIcon.On) + self.key_icon = QIcon() + self.key_icon.addPixmap(style.standardPixmap(QStyle.SP_FileIcon)) + + self.refresh_timer.timeout.connect(self.maybe_refresh) + + def set_settings_object(self, settings): + self.settings = settings + self.clear() + + if self.settings is not None: + self.settings.setParent(self) + self.refresh() + if self.auto_refresh: + self.refresh_timer.start() + else: + self.refresh_timer.stop() + + def sizeHint(self): + return QSize(800, 600) + + @Slot(bool) + def set_auto_refresh(self, autoRefresh): + self.auto_refresh = autoRefresh + + if self.settings is not None: + if self.auto_refresh: + self.maybe_refresh() + self.refresh_timer.start() + else: + self.refresh_timer.stop() + + @Slot(bool) + def set_fallbacks_enabled(self, enabled): + if self.settings is not None: + self.settings.setFallbacksEnabled(enabled) + self.refresh() + + @Slot() + def maybe_refresh(self): + if self.state() != QAbstractItemView.EditingState: + self.refresh() + + @Slot() + def refresh(self): + if self.settings is None: + return + + # The signal might not be connected. + try: + self.itemChanged.disconnect(self.update_setting) + except Exception: + pass + + self.settings.sync() + self.update_child_items(None) + + self.itemChanged.connect(self.update_setting) + + def event(self, event): + if event.type() == QEvent.WindowActivate: + if self.isActiveWindow() and self.auto_refresh: + self.maybe_refresh() + + return super(SettingsTree, self).event(event) + + def update_setting(self, item): + key = item.text(0) + ancestor = item.parent() + + while ancestor: + key = ancestor.text(0) + '/' + key + ancestor = ancestor.parent() + + self.settings.setValue(key, item.data(2, Qt.UserRole)) + + if self.auto_refresh: + self.refresh() + + def update_child_items(self, parent): + divider_index = 0 + + for group in self.settings.childGroups(): + child_index = self.find_child(parent, group, divider_index) + if child_index != -1: + child = self.child_at(parent, child_index) + child.setText(1, '') + child.setText(2, '') + child.setData(2, Qt.UserRole, None) + self.move_item_forward(parent, child_index, divider_index) + else: + child = self.create_item(group, parent, divider_index) + + child.setIcon(0, self.group_icon) + divider_index += 1 + + self.settings.beginGroup(group) + self.update_child_items(child) + self.settings.endGroup() + + for key in self.settings.childKeys(): + child_index = self.find_child(parent, key, 0) + if child_index == -1 or child_index >= divider_index: + if child_index != -1: + child = self.child_at(parent, child_index) + for i in range(child.childCount()): + self.delete_item(child, i) + self.move_item_forward(parent, child_index, divider_index) + else: + child = self.create_item(key, parent, divider_index) + child.setIcon(0, self.key_icon) + divider_index += 1 + else: + child = self.child_at(parent, child_index) + + value = self.settings.value(key) + if value is None: + child.setText(1, 'Invalid') + else: + # Try to convert to type unless a QByteArray is received + if isinstance(value, str): + value_type = self._type_checker.type_from_text(value) + if value_type: + value = self.settings.value(key, type=value_type) + child.setText(1, value.__class__.__name__) + child.setText(2, VariantDelegate.display_text(value)) + child.setData(2, Qt.UserRole, value) + + while divider_index < self.child_count(parent): + self.delete_item(parent, divider_index) + + def create_item(self, text, parent, index): + after = None + + if index != 0: + after = self.child_at(parent, index - 1) + + if parent is not None: + item = QTreeWidgetItem(parent, after) + else: + item = QTreeWidgetItem(self, after) + + item.setText(0, text) + item.setFlags(item.flags() | Qt.ItemIsEditable) + return item + + def delete_item(self, parent, index): + if parent is not None: + item = parent.takeChild(index) + else: + item = self.takeTopLevelItem(index) + del item + + def child_at(self, parent, index): + if parent is not None: + return parent.child(index) + else: + return self.topLevelItem(index) + + def child_count(self, parent): + if parent is not None: + return parent.childCount() + else: + return self.topLevelItemCount() + + def find_child(self, parent, text, startIndex): + for i in range(self.child_count(parent)): + if self.child_at(parent, i).text(0) == text: + return i + return -1 + + def move_item_forward(self, parent, oldIndex, newIndex): + for int in range(oldIndex - newIndex): + self.delete_item(parent, newIndex) + + +class VariantDelegate(QItemDelegate): + def __init__(self, type_checker, parent=None): + super().__init__(parent) + self._type_checker = type_checker + + def paint(self, painter, option, index): + if index.column() == 2: + value = index.model().data(index, Qt.UserRole) + if not self.is_supported_type(value): + my_option = QStyleOptionViewItem(option) + my_option.state &= ~QStyle.State_Enabled + super(VariantDelegate, self).paint(painter, my_option, index) + return + + super(VariantDelegate, self).paint(painter, option, index) + + def createEditor(self, parent, option, index): + if index.column() != 2: + return None + + original_value = index.model().data(index, Qt.UserRole) + if not self.is_supported_type(original_value): + return None + + editor = None + if isinstance(original_value, bool): + editor = QCheckBox(parent) + if isinstance(original_value, int): + editor = QSpinBox(parent) + editor.setRange(-32767, 32767) + else: + editor = QLineEdit(parent) + editor.setFrame(False) + validator = self._type_checker.create_validator(original_value, editor) + if validator: + editor.setValidator(validator) + return editor + + def setEditorData(self, editor, index): + if not editor: + return + value = index.model().data(index, Qt.UserRole) + if isinstance(editor, QCheckBox): + editor.setCheckState(Qt.Checked if value else Qt.Unchecked) + elif isinstance(editor, QSpinBox): + editor.setValue(value) + else: + editor.setText(self.display_text(value)) + + def value_from_lineedit(self, lineedit, model, index): + if not lineedit.isModified(): + return None + text = lineedit.text() + validator = lineedit.validator() + if validator is not None: + state, text, _ = validator.validate(text, 0) + if state != QValidator.Acceptable: + return None + original_value = index.model().data(index, Qt.UserRole) + return self._type_checker.from_string(text, original_value) + + def setModelData(self, editor, model, index): + value = None + if isinstance(editor, QCheckBox): + value = editor.checkState() == Qt.Checked + elif isinstance(editor, QSpinBox): + value = editor.value() + else: + value = self.value_from_lineedit(editor, model, index) + if value is not None: + model.setData(index, value, Qt.UserRole) + model.setData(index, self.display_text(value), Qt.DisplayRole) + + @staticmethod + def is_supported_type(value): + return isinstance(value, (bool, float, int, QByteArray, str, QColor, + QDate, QDateTime, QTime, QPoint, QRect, + QSize, list)) + + @staticmethod + def display_text(value): + if isinstance(value, str): + return value + if isinstance(value, bool): + return '✓' if value else '☐' + if isinstance(value, (int, float, QByteArray)): + return str(value) + if isinstance(value, QColor): + (r, g, b, a) = (value.red(), value.green(), value.blue(), value.alpha()) + return f'({r},{g},{b},{a})' + if isinstance(value, (QDate, QDateTime, QTime)): + return value.toString(Qt.ISODate) + if isinstance(value, QPoint): + x = value.x() + y = value.y() + return f'({x},{y})' + if isinstance(value, QRect): + x = value.x() + y = value.y() + w = value.width() + h = value.height() + return f'({x},{y},{w},{h})' + if isinstance(value, QSize): + w = value.width() + h = value.height() + return f'({w},{h})' + if isinstance(value, list): + return ','.join(map(repr, value)) + if value is None: + return '<Invalid>' + + return f'<{value}>' + + +if __name__ == '__main__': + app = QApplication(sys.argv) + main_win = MainWindow() + if len(sys.argv) > 1: + main_win.load_ini_file(sys.argv[1]) + main_win.show() + sys.exit(app.exec()) diff --git a/examples/corelib/settingseditor/settingseditor.pyproject b/examples/corelib/settingseditor/settingseditor.pyproject new file mode 100644 index 000000000..9eb637af2 --- /dev/null +++ b/examples/corelib/settingseditor/settingseditor.pyproject @@ -0,0 +1,3 @@ +{ + "files": ["settingseditor.py"] +} diff --git a/examples/corelib/threads/doc/threads.png b/examples/corelib/threads/doc/threads.png Binary files differnew file mode 100644 index 000000000..d022f4aff --- /dev/null +++ b/examples/corelib/threads/doc/threads.png diff --git a/examples/corelib/threads/doc/threads.rst b/examples/corelib/threads/doc/threads.rst new file mode 100644 index 000000000..d1bcf6fd4 --- /dev/null +++ b/examples/corelib/threads/doc/threads.rst @@ -0,0 +1,10 @@ +Mandelbrot Threads Example +========================== + +The Mandelbrot example demonstrates multi-thread programming using Qt. It shows +how to use a worker thread to perform heavy computations without blocking the +main thread's event loop. + +.. image:: threads.png + :width: 400 + :alt: Mandelbrot Threads Screenshot diff --git a/examples/corelib/threads/mandelbrot.py b/examples/corelib/threads/mandelbrot.py index f0d13dbf5..4689813d4 100644 --- a/examples/corelib/threads/mandelbrot.py +++ b/examples/corelib/threads/mandelbrot.py @@ -1,83 +1,60 @@ +# Copyright (C) 2013 Riverbank Computing Limited. +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause -############################################################################# -## -## 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$ -## -############################################################################# - -"""PySide2 port of the corelib/threads/mandelbrot example from Qt v5.x, originating from PyQt""" - -from PySide2.QtCore import (Signal, QMutex, QMutexLocker, QPoint, QSize, Qt, - QThread, QWaitCondition) -from PySide2.QtGui import QColor, QImage, QPainter, QPixmap, qRgb -from PySide2.QtWidgets import QApplication, QWidget - - -DefaultCenterX = -0.647011 -DefaultCenterY = -0.0395159 -DefaultScale = 0.00403897 - -ZoomInFactor = 0.8 -ZoomOutFactor = 1 / ZoomInFactor -ScrollStep = 20 +"""PySide6 port of the corelib/threads/mandelbrot example from Qt v5.x, originating from PyQt""" + +from argparse import ArgumentParser, RawTextHelpFormatter +import sys + +from PySide6.QtCore import (Signal, QMutex, QElapsedTimer, QMutexLocker, + QPoint, QPointF, QSize, Qt, QThread, + QWaitCondition, Slot) +from PySide6.QtGui import QColor, QImage, QPainter, QPixmap, qRgb +from PySide6.QtWidgets import QApplication, QWidget + + +DEFAULT_CENTER_X = -0.647011 +DEFAULT_CENTER_Y = -0.0395159 +DEFAULT_SCALE = 0.00403897 + +ZOOM_IN_FACTOR = 0.8 +ZOOM_OUT_FACTOR = 1 / ZOOM_IN_FACTOR +SCROLL_STEP = 20 + + +NUM_PASSES = 8 + + +INFO_KEY = 'info' + + +HELP = ("Use mouse wheel or the '+' and '-' keys to zoom. Press and " + "hold left mouse button to scroll.") class RenderThread(QThread): - ColormapSize = 512 + colormap_size = 512 - renderedImage = Signal(QImage, float) + rendered_image = Signal(QImage, float) def __init__(self, parent=None): - super(RenderThread, self).__init__(parent) + super().__init__(parent) self.mutex = QMutex() self.condition = QWaitCondition() - self.centerX = 0.0 - self.centerY = 0.0 - self.scaleFactor = 0.0 - self.resultSize = QSize() + self._center_x = 0.0 + self._center_y = 0.0 + self._scale_factor = 0.0 + self._result_size = QSize() self.colormap = [] self.restart = False self.abort = False - for i in range(RenderThread.ColormapSize): - self.colormap.append(self.rgbFromWaveLength(380.0 + (i * 400.0 / RenderThread.ColormapSize))) + for i in range(RenderThread.colormap_size): + self.colormap.append( + self.rgb_from_wave_length(380.0 + (i * 400.0 / RenderThread.colormap_size))) def stop(self): self.mutex.lock() @@ -87,84 +64,94 @@ class RenderThread(QThread): self.wait(2000) - def render(self, centerX, centerY, scaleFactor, resultSize): - locker = QMutexLocker(self.mutex) - - self.centerX = centerX - self.centerY = centerY - self.scaleFactor = scaleFactor - self.resultSize = resultSize + def render(self, centerX, centerY, scale_factor, resultSize): + with QMutexLocker(self.mutex): + self._center_x = centerX + self._center_y = centerY + self._scale_factor = scale_factor + self._result_size = resultSize - if not self.isRunning(): - self.start(QThread.LowPriority) - else: - self.restart = True - self.condition.wakeOne() + if not self.isRunning(): + self.start(QThread.LowPriority) + else: + self.restart = True + self.condition.wakeOne() def run(self): + timer = QElapsedTimer() + while True: self.mutex.lock() - resultSize = self.resultSize - scaleFactor = self.scaleFactor - centerX = self.centerX - centerY = self.centerY + resultSize = self._result_size + scale_factor = self._scale_factor + centerX = self._center_x + centerY = self._center_y self.mutex.unlock() - halfWidth = resultSize.width() // 2 - halfHeight = resultSize.height() // 2 + half_width = resultSize.width() // 2 + half_height = resultSize.height() // 2 image = QImage(resultSize, QImage.Format_RGB32) - NumPasses = 8 curpass = 0 - while curpass < NumPasses: - MaxIterations = (1 << (2 * curpass + 6)) + 32 - Limit = 4 - allBlack = True + while curpass < NUM_PASSES: + timer.restart() + max_iterations = (1 << (2 * curpass + 6)) + 32 + LIMIT = 4 + all_black = True - for y in range(-halfHeight, halfHeight): + for y in range(-half_height, half_height): if self.restart: break if self.abort: return - ay = 1j * (centerY + (y * scaleFactor)) + ay = 1j * (centerY + (y * scale_factor)) - for x in range(-halfWidth, halfWidth): - c0 = centerX + (x * scaleFactor) + ay + for x in range(-half_width, half_width): + c0 = centerX + (x * scale_factor) + ay c = c0 - numIterations = 0 + num_iterations = 0 - while numIterations < MaxIterations: - numIterations += 1 - c = c*c + c0 - if abs(c) >= Limit: + while num_iterations < max_iterations: + num_iterations += 1 + c = c * c + c0 + if abs(c) >= LIMIT: break - numIterations += 1 - c = c*c + c0 - if abs(c) >= Limit: + num_iterations += 1 + c = c * c + c0 + if abs(c) >= LIMIT: break - numIterations += 1 - c = c*c + c0 - if abs(c) >= Limit: + num_iterations += 1 + c = c * c + c0 + if abs(c) >= LIMIT: break - numIterations += 1 - c = c*c + c0 - if abs(c) >= Limit: + num_iterations += 1 + c = c * c + c0 + if abs(c) >= LIMIT: break - if numIterations < MaxIterations: - image.setPixel(x + halfWidth, y + halfHeight, - self.colormap[numIterations % RenderThread.ColormapSize]) - allBlack = False + if num_iterations < max_iterations: + image.setPixel(x + half_width, y + half_height, + self.colormap[ + num_iterations % RenderThread.colormap_size]) + all_black = False else: - image.setPixel(x + halfWidth, y + halfHeight, qRgb(0, 0, 0)) + image.setPixel(x + half_width, y + half_height, qRgb(0, 0, 0)) - if allBlack and curpass == 0: + if all_black and curpass == 0: curpass = 4 else: if not self.restart: - self.renderedImage.emit(image, scaleFactor) + elapsed = timer.elapsed() + unit = 'ms' + if elapsed > 2000: + elapsed /= 1000 + unit = 's' + text = (f"Pass {curpass + 1}/{NUM_PASSES}, " + f"max iterations: {max_iterations}, time: {elapsed}{unit}") + image.setText(INFO_KEY, text) + self.rendered_image.emit(image, scale_factor) curpass += 1 self.mutex.lock() @@ -173,7 +160,7 @@ class RenderThread(QThread): self.restart = False self.mutex.unlock() - def rgbFromWaveLength(self, wave): + def rgb_from_wave_length(self, wave): r = 0.0 g = 0.0 b = 0.0 @@ -206,143 +193,160 @@ class RenderThread(QThread): g = pow(g * s, 0.8) b = pow(b * s, 0.8) - return qRgb(r*255, g*255, b*255) + return qRgb(r * 255, g * 255, b * 255) class MandelbrotWidget(QWidget): def __init__(self, parent=None): - super(MandelbrotWidget, self).__init__(parent) + super().__init__(parent) self.thread = RenderThread() self.pixmap = QPixmap() - self.pixmapOffset = QPoint() - self.lastDragPos = QPoint() + self._pixmap_offset = QPointF() + self._last_drag_pos = QPointF() - self.centerX = DefaultCenterX - self.centerY = DefaultCenterY - self.pixmapScale = DefaultScale - self.curScale = DefaultScale + self._center_x = DEFAULT_CENTER_X + self._center_y = DEFAULT_CENTER_Y + self._pixmap_scale = DEFAULT_SCALE + self._cur_scale = DEFAULT_SCALE - self.thread.renderedImage.connect(self.updatePixmap) + self.thread.rendered_image.connect(self.update_pixmap) self.setWindowTitle("Mandelbrot") self.setCursor(Qt.CrossCursor) - self.resize(550, 400) + self._info = '' def paintEvent(self, event): - painter = QPainter(self) - painter.fillRect(self.rect(), Qt.black) - - if self.pixmap.isNull(): + with QPainter(self) as painter: + painter.fillRect(self.rect(), Qt.black) + + if self.pixmap.isNull(): + painter.setPen(Qt.white) + painter.drawText(self.rect(), Qt.AlignCenter, + "Rendering initial image, please wait...") + return + + if self._cur_scale == self._pixmap_scale: + painter.drawPixmap(self._pixmap_offset, self.pixmap) + else: + scale_factor = self._pixmap_scale / self._cur_scale + new_width = int(self.pixmap.width() * scale_factor) + new_height = int(self.pixmap.height() * scale_factor) + new_x = self._pixmap_offset.x() + (self.pixmap.width() - new_width) / 2 + new_y = self._pixmap_offset.y() + (self.pixmap.height() - new_height) / 2 + + painter.save() + painter.translate(new_x, new_y) + painter.scale(scale_factor, scale_factor) + exposed, _ = painter.transform().inverted() + exposed = exposed.mapRect(self.rect()).adjusted(-1, -1, 1, 1) + painter.drawPixmap(exposed, self.pixmap, exposed) + painter.restore() + + text = HELP + if self._info: + text += ' ' + self._info + metrics = painter.fontMetrics() + text_width = metrics.horizontalAdvance(text) + + painter.setPen(Qt.NoPen) + painter.setBrush(QColor(0, 0, 0, 127)) + painter.drawRect((self.width() - text_width) / 2 - 5, 0, text_width + 10, + metrics.lineSpacing() + 5) painter.setPen(Qt.white) - painter.drawText(self.rect(), Qt.AlignCenter, - "Rendering initial image, please wait...") - return - - if self.curScale == self.pixmapScale: - painter.drawPixmap(self.pixmapOffset, self.pixmap) - else: - scaleFactor = self.pixmapScale / self.curScale - newWidth = int(self.pixmap.width() * scaleFactor) - newHeight = int(self.pixmap.height() * scaleFactor) - newX = self.pixmapOffset.x() + (self.pixmap.width() - newWidth) / 2 - newY = self.pixmapOffset.y() + (self.pixmap.height() - newHeight) / 2 - - painter.save() - painter.translate(newX, newY) - painter.scale(scaleFactor, scaleFactor) - exposed, _ = painter.matrix().inverted() - exposed = exposed.mapRect(self.rect()).adjusted(-1, -1, 1, 1) - painter.drawPixmap(exposed, self.pixmap, exposed) - painter.restore() - - text = "Use mouse wheel or the '+' and '-' keys to zoom. Press and " \ - "hold left mouse button to scroll." - metrics = painter.fontMetrics() - textWidth = metrics.width(text) - - painter.setPen(Qt.NoPen) - painter.setBrush(QColor(0, 0, 0, 127)) - painter.drawRect((self.width() - textWidth) / 2 - 5, 0, textWidth + 10, - metrics.lineSpacing() + 5) - painter.setPen(Qt.white) - painter.drawText((self.width() - textWidth) / 2, - metrics.leading() + metrics.ascent(), text) + painter.drawText((self.width() - text_width) / 2, + metrics.leading() + metrics.ascent(), text) def resizeEvent(self, event): - self.thread.render(self.centerX, self.centerY, self.curScale, self.size()) + self.thread.render(self._center_x, self._center_y, self._cur_scale, self.size()) def keyPressEvent(self, event): if event.key() == Qt.Key_Plus: - self.zoom(ZoomInFactor) + self.zoom(ZOOM_IN_FACTOR) elif event.key() == Qt.Key_Minus: - self.zoom(ZoomOutFactor) + self.zoom(ZOOM_OUT_FACTOR) elif event.key() == Qt.Key_Left: - self.scroll(-ScrollStep, 0) + self.scroll(-SCROLL_STEP, 0) elif event.key() == Qt.Key_Right: - self.scroll(+ScrollStep, 0) + self.scroll(+SCROLL_STEP, 0) elif event.key() == Qt.Key_Down: - self.scroll(0, -ScrollStep) + self.scroll(0, -SCROLL_STEP) elif event.key() == Qt.Key_Up: - self.scroll(0, +ScrollStep) + self.scroll(0, +SCROLL_STEP) + elif event.key() == Qt.Key_Q: + self.close() else: super(MandelbrotWidget, self).keyPressEvent(event) def wheelEvent(self, event): - numDegrees = event.angleDelta().y() / 8 - numSteps = numDegrees / 15.0 - self.zoom(pow(ZoomInFactor, numSteps)) + num_degrees = event.angleDelta().y() / 8 + num_steps = num_degrees / 15.0 + self.zoom(pow(ZOOM_IN_FACTOR, num_steps)) def mousePressEvent(self, event): if event.buttons() == Qt.LeftButton: - self.lastDragPos = QPoint(event.pos()) + self._last_drag_pos = event.position() def mouseMoveEvent(self, event): if event.buttons() & Qt.LeftButton: - self.pixmapOffset += event.pos() - self.lastDragPos - self.lastDragPos = QPoint(event.pos()) + pos = event.position() + self._pixmap_offset += pos - self._last_drag_pos + self._last_drag_pos = pos self.update() def mouseReleaseEvent(self, event): if event.button() == Qt.LeftButton: - self.pixmapOffset += event.pos() - self.lastDragPos - self.lastDragPos = QPoint() + pos = event.position() + self._pixmap_offset += pos - self._last_drag_pos + self._last_drag_pos = QPointF() - deltaX = (self.width() - self.pixmap.width()) / 2 - self.pixmapOffset.x() - deltaY = (self.height() - self.pixmap.height()) / 2 - self.pixmapOffset.y() - self.scroll(deltaX, deltaY) + delta_x = (self.width() - self.pixmap.width()) / 2 - self._pixmap_offset.x() + delta_y = (self.height() - self.pixmap.height()) / 2 - self._pixmap_offset.y() + self.scroll(delta_x, delta_y) - def updatePixmap(self, image, scaleFactor): - if not self.lastDragPos.isNull(): + @Slot(QImage, float) + def update_pixmap(self, image, scale_factor): + if not self._last_drag_pos.isNull(): return + self._info = image.text(INFO_KEY) self.pixmap = QPixmap.fromImage(image) - self.pixmapOffset = QPoint() - self.lastDragPosition = QPoint() - self.pixmapScale = scaleFactor + self._pixmap_offset = QPointF() + self._last_drag_position = QPointF() + self._pixmap_scale = scale_factor self.update() def zoom(self, zoomFactor): - self.curScale *= zoomFactor + self._cur_scale *= zoomFactor self.update() - self.thread.render(self.centerX, self.centerY, self.curScale, - self.size()) + self.thread.render(self._center_x, self._center_y, self._cur_scale, self.size()) def scroll(self, deltaX, deltaY): - self.centerX += deltaX * self.curScale - self.centerY += deltaY * self.curScale + self._center_x += deltaX * self._cur_scale + self._center_y += deltaY * self._cur_scale self.update() - self.thread.render(self.centerX, self.centerY, self.curScale, - self.size()) + self.thread.render(self._center_x, self._center_y, self._cur_scale, self.size()) if __name__ == '__main__': - - import sys + parser = ArgumentParser(description='Qt Mandelbrot Example', + formatter_class=RawTextHelpFormatter) + parser.add_argument('--passes', '-p', type=int, help='Number of passes (1-8)') + options = parser.parse_args() + if options.passes: + NUM_PASSES = int(options.passes) + if NUM_PASSES < 1 or NUM_PASSES > 8: + print(f'Invalid value: {options.passes}') + sys.exit(-1) app = QApplication(sys.argv) widget = MandelbrotWidget() + geometry = widget.screen().availableGeometry() + widget.resize((2 * geometry.size()) / 3) + pos = (geometry.size() - widget.size()) / 2 + widget.move(geometry.topLeft() + QPoint(pos.width(), pos.height())) + widget.show() - r = app.exec_() + r = app.exec() widget.thread.stop() sys.exit(r) diff --git a/examples/corelib/threads/threads.pyproject b/examples/corelib/threads/threads.pyproject new file mode 100644 index 000000000..254aabec0 --- /dev/null +++ b/examples/corelib/threads/threads.pyproject @@ -0,0 +1,3 @@ +{ + "files": ["mandelbrot.py"] +} diff --git a/examples/corelib/tools/codecs/codecs.py b/examples/corelib/tools/codecs/codecs.py deleted file mode 100644 index 5139bb6db..000000000 --- a/examples/corelib/tools/codecs/codecs.py +++ /dev/null @@ -1,250 +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$ -## -############################################################################# - -"""PySide2 port of the widgets/tools/codecs example from Qt v5.x""" - -from PySide2 import QtCore, QtGui, QtWidgets - - -def codec_name(codec): - try: - # Python v3. - name = str(codec.name(), encoding='ascii') - except TypeError: - # Python v2. - name = str(codec.name()) - - return name - - -class MainWindow(QtWidgets.QMainWindow): - def __init__(self): - super(MainWindow, self).__init__() - - self.textEdit = QtWidgets.QTextEdit() - self.textEdit.setLineWrapMode(QtWidgets.QTextEdit.NoWrap) - self.setCentralWidget(self.textEdit) - - self.codecs = [] - self.findCodecs() - - self.previewForm = PreviewForm(self) - self.previewForm.setCodecList(self.codecs) - - self.saveAsActs = [] - self.createActions() - self.createMenus() - - self.setWindowTitle("Codecs") - self.resize(500, 400) - - def open(self): - fileName, _ = QtWidgets.QFileDialog.getOpenFileName(self) - if fileName: - inFile = QtCore.QFile(fileName) - if not inFile.open(QtCore.QFile.ReadOnly): - QtWidgets.QMessageBox.warning(self, "Codecs", - "Cannot read file %s:\n%s" % (fileName, inFile.errorString())) - return - - data = inFile.readAll() - - self.previewForm.setEncodedData(data) - if self.previewForm.exec_(): - self.textEdit.setPlainText(self.previewForm.decodedString()) - - def save(self): - fileName = QtWidgets.QFileDialog.getSaveFileName(self) - if fileName: - outFile = QtCore.QFile(fileName) - if not outFile.open(QtCore.QFile.WriteOnly|QtCore.QFile.Text): - QtWidgets.QMessageBox.warning(self, "Codecs", - "Cannot write file %s:\n%s" % (fileName, outFile.errorString())) - return - - action = self.sender() - codecName = action.data() - - out = QtCore.QTextStream(outFile) - out.setCodec(codecName) - out << self.textEdit.toPlainText() - - def about(self): - QtWidgets.QMessageBox.about(self, "About Codecs", - "The <b>Codecs</b> example demonstrates how to read and " - "write files using various encodings.") - - def aboutToShowSaveAsMenu(self): - currentText = self.textEdit.toPlainText() - - for action in self.saveAsActs: - codecName = str(action.data()) - codec = QtCore.QTextCodec.codecForName(codecName) - action.setVisible(codec and codec.canEncode(currentText)) - - def findCodecs(self): - codecMap = [] - iso8859RegExp = QtCore.QRegExp('ISO[- ]8859-([0-9]+).*') - - for mib in QtCore.QTextCodec.availableMibs(): - codec = QtCore.QTextCodec.codecForMib(mib) - sortKey = codec_name(codec).upper() - rank = 0 - - if sortKey.startswith('UTF-8'): - rank = 1 - elif sortKey.startswith('UTF-16'): - rank = 2 - elif iso8859RegExp.exactMatch(sortKey): - if len(iso8859RegExp.cap(1)) == 1: - rank = 3 - else: - rank = 4 - else: - rank = 5 - - codecMap.append((str(rank) + sortKey, codec)) - - codecMap.sort() - self.codecs = [item[-1] for item in codecMap] - - def createActions(self): - self.openAct = QtWidgets.QAction("&Open...", self, shortcut="Ctrl+O", - triggered=self.open) - - for codec in self.codecs: - name = codec_name(codec) - - action = QtWidgets.QAction(name + '...', self, triggered=self.save) - action.setData(name) - self.saveAsActs.append(action) - - self.exitAct = QtWidgets.QAction("E&xit", self, shortcut="Ctrl+Q", - triggered=self.close) - - self.aboutAct = QtWidgets.QAction("&About", self, triggered=self.about) - - self.aboutQtAct = QtWidgets.QAction("About &Qt", self, - triggered=QtWidgets.qApp.aboutQt) - - def createMenus(self): - self.saveAsMenu = QtWidgets.QMenu("&Save As", self) - for action in self.saveAsActs: - self.saveAsMenu.addAction(action) - - self.saveAsMenu.aboutToShow.connect(self.aboutToShowSaveAsMenu) - - self.fileMenu = QtWidgets.QMenu("&File", self) - self.fileMenu.addAction(self.openAct) - self.fileMenu.addMenu(self.saveAsMenu) - self.fileMenu.addSeparator() - self.fileMenu.addAction(self.exitAct) - - self.helpMenu = QtWidgets.QMenu("&Help", self) - self.helpMenu.addAction(self.aboutAct) - self.helpMenu.addAction(self.aboutQtAct) - - self.menuBar().addMenu(self.fileMenu) - self.menuBar().addSeparator() - self.menuBar().addMenu(self.helpMenu) - - -class PreviewForm(QtWidgets.QDialog): - def __init__(self, parent): - super(PreviewForm, self).__init__(parent) - - self.encodingComboBox = QtWidgets.QComboBox() - encodingLabel = QtWidgets.QLabel("&Encoding:") - encodingLabel.setBuddy(self.encodingComboBox) - - self.textEdit = QtWidgets.QTextEdit() - self.textEdit.setLineWrapMode(QtWidgets.QTextEdit.NoWrap) - self.textEdit.setReadOnly(True) - - buttonBox = QtWidgets.QDialogButtonBox(QtWidgets.QDialogButtonBox.Ok | QtWidgets.QDialogButtonBox.Cancel) - - self.encodingComboBox.activated.connect(self.updateTextEdit) - buttonBox.accepted.connect(self.accept) - buttonBox.rejected.connect(self.reject) - - mainLayout = QtWidgets.QGridLayout() - mainLayout.addWidget(encodingLabel, 0, 0) - mainLayout.addWidget(self.encodingComboBox, 0, 1) - mainLayout.addWidget(self.textEdit, 1, 0, 1, 2) - mainLayout.addWidget(buttonBox, 2, 0, 1, 2) - self.setLayout(mainLayout) - - self.setWindowTitle("Choose Encoding") - self.resize(400, 300) - - def setCodecList(self, codecs): - self.encodingComboBox.clear() - for codec in codecs: - self.encodingComboBox.addItem(codec_name(codec), codec.mibEnum()) - - def setEncodedData(self, data): - self.encodedData = data - self.updateTextEdit() - - def decodedString(self): - return self.decodedStr - - def updateTextEdit(self): - mib = self.encodingComboBox.itemData(self.encodingComboBox.currentIndex()) - codec = QtCore.QTextCodec.codecForMib(mib) - - data = QtCore.QTextStream(self.encodedData) - data.setAutoDetectUnicode(False) - data.setCodec(codec) - - self.decodedStr = data.readAll() - self.textEdit.setPlainText(self.decodedStr) - - -if __name__ == '__main__': - - import sys - - app = QtWidgets.QApplication(sys.argv) - mainWin = MainWindow() - mainWin.show() - sys.exit(app.exec_()) diff --git a/examples/corelib/tools/regexp.py b/examples/corelib/tools/regexp.py deleted file mode 100644 index 3a953329a..000000000 --- a/examples/corelib/tools/regexp.py +++ /dev/null @@ -1,194 +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$ -## -############################################################################# - -"""PySide2 port of the widgets/tools/regexp example from Qt v5.x""" - -from PySide2 import QtCore, QtGui, QtWidgets - - -class RegExpDialog(QtWidgets.QDialog): - MaxCaptures = 6 - - def __init__(self, parent=None): - super(RegExpDialog, self).__init__(parent) - - self.patternComboBox = QtWidgets.QComboBox() - self.patternComboBox.setEditable(True) - self.patternComboBox.setSizePolicy(QtWidgets.QSizePolicy.Expanding, - QtWidgets.QSizePolicy.Preferred) - - patternLabel = QtWidgets.QLabel("&Pattern:") - patternLabel.setBuddy(self.patternComboBox) - - self.escapedPatternLineEdit = QtWidgets.QLineEdit() - self.escapedPatternLineEdit.setReadOnly(True) - palette = self.escapedPatternLineEdit.palette() - palette.setBrush(QtGui.QPalette.Base, - palette.brush(QtGui.QPalette.Disabled, QtGui.QPalette.Base)) - self.escapedPatternLineEdit.setPalette(palette) - - escapedPatternLabel = QtWidgets.QLabel("&Escaped Pattern:") - escapedPatternLabel.setBuddy(self.escapedPatternLineEdit) - - self.syntaxComboBox = QtWidgets.QComboBox() - self.syntaxComboBox.addItem("Regular expression v1", - QtCore.QRegExp.RegExp) - self.syntaxComboBox.addItem("Regular expression v2", - QtCore.QRegExp.RegExp2) - self.syntaxComboBox.addItem("Wildcard", QtCore.QRegExp.Wildcard) - self.syntaxComboBox.addItem("Fixed string", - QtCore.QRegExp.FixedString) - - syntaxLabel = QtWidgets.QLabel("&Pattern Syntax:") - syntaxLabel.setBuddy(self.syntaxComboBox) - - self.textComboBox = QtWidgets.QComboBox() - self.textComboBox.setEditable(True) - self.textComboBox.setSizePolicy(QtWidgets.QSizePolicy.Expanding, - QtWidgets.QSizePolicy.Preferred) - - textLabel = QtWidgets.QLabel("&Text:") - textLabel.setBuddy(self.textComboBox) - - self.caseSensitiveCheckBox = QtWidgets.QCheckBox("Case &Sensitive") - self.caseSensitiveCheckBox.setChecked(True) - self.minimalCheckBox = QtWidgets.QCheckBox("&Minimal") - - indexLabel = QtWidgets.QLabel("Index of Match:") - self.indexEdit = QtWidgets.QLineEdit() - self.indexEdit.setReadOnly(True) - - matchedLengthLabel = QtWidgets.QLabel("Matched Length:") - self.matchedLengthEdit = QtWidgets.QLineEdit() - self.matchedLengthEdit.setReadOnly(True) - - self.captureLabels = [] - self.captureEdits = [] - for i in range(self.MaxCaptures): - self.captureLabels.append(QtWidgets.QLabel("Capture %d:" % i)) - self.captureEdits.append(QtWidgets.QLineEdit()) - self.captureEdits[i].setReadOnly(True) - self.captureLabels[0].setText("Match:") - - checkBoxLayout = QtWidgets.QHBoxLayout() - checkBoxLayout.addWidget(self.caseSensitiveCheckBox) - checkBoxLayout.addWidget(self.minimalCheckBox) - checkBoxLayout.addStretch(1) - - mainLayout = QtWidgets.QGridLayout() - mainLayout.addWidget(patternLabel, 0, 0) - mainLayout.addWidget(self.patternComboBox, 0, 1) - mainLayout.addWidget(escapedPatternLabel, 1, 0) - mainLayout.addWidget(self.escapedPatternLineEdit, 1, 1) - mainLayout.addWidget(syntaxLabel, 2, 0) - mainLayout.addWidget(self.syntaxComboBox, 2, 1) - mainLayout.addLayout(checkBoxLayout, 3, 0, 1, 2) - mainLayout.addWidget(textLabel, 4, 0) - mainLayout.addWidget(self.textComboBox, 4, 1) - mainLayout.addWidget(indexLabel, 5, 0) - mainLayout.addWidget(self.indexEdit, 5, 1) - mainLayout.addWidget(matchedLengthLabel, 6, 0) - mainLayout.addWidget(self.matchedLengthEdit, 6, 1) - - for i in range(self.MaxCaptures): - mainLayout.addWidget(self.captureLabels[i], 7 + i, 0) - mainLayout.addWidget(self.captureEdits[i], 7 + i, 1) - self.setLayout(mainLayout) - - self.patternComboBox.editTextChanged.connect(self.refresh) - self.textComboBox.editTextChanged.connect(self.refresh) - self.caseSensitiveCheckBox.toggled.connect(self.refresh) - self.minimalCheckBox.toggled.connect(self.refresh) - self.syntaxComboBox.currentIndexChanged.connect(self.refresh) - - self.patternComboBox.addItem("[A-Za-z_]+([A-Za-z_0-9]*)") - self.textComboBox.addItem("(10 + delta4)* 32") - - self.setWindowTitle("RegExp") - self.setFixedHeight(self.sizeHint().height()) - self.refresh() - - def refresh(self): - self.setUpdatesEnabled(False) - - pattern = self.patternComboBox.currentText() - text = self.textComboBox.currentText() - - escaped = str(pattern) - escaped.replace('\\', '\\\\') - escaped.replace('"', '\\"') - self.escapedPatternLineEdit.setText('"' + escaped + '"') - - rx = QtCore.QRegExp(pattern) - cs = QtCore.Qt.CaseInsensitive - if self.caseSensitiveCheckBox.isChecked(): - cs = QtCore.Qt.CaseSensitive - rx.setCaseSensitivity(cs) - rx.setMinimal(self.minimalCheckBox.isChecked()) - syntax = self.syntaxComboBox.itemData(self.syntaxComboBox.currentIndex()) - rx.setPatternSyntax(QtCore.QRegExp.PatternSyntax(syntax)) - - palette = self.patternComboBox.palette() - if rx.isValid(): - palette.setColor(QtGui.QPalette.Text, - self.textComboBox.palette().color(QtGui.QPalette.Text)) - else: - palette.setColor(QtGui.QPalette.Text, QtCore.Qt.red) - self.patternComboBox.setPalette(palette) - - self.indexEdit.setText(str(rx.indexIn(text))) - self.matchedLengthEdit.setText(str(rx.matchedLength())) - - for i in range(self.MaxCaptures): - self.captureLabels[i].setEnabled(i <= rx.captureCount()) - self.captureEdits[i].setEnabled(i <= rx.captureCount()) - self.captureEdits[i].setText(rx.cap(i)) - - self.setUpdatesEnabled(True) - -if __name__ == '__main__': - - import sys - - app = QtWidgets.QApplication(sys.argv) - dialog = RegExpDialog() - sys.exit(dialog.exec_()) diff --git a/examples/corelib/tools/settingseditor/settingseditor.py b/examples/corelib/tools/settingseditor/settingseditor.py deleted file mode 100644 index 9d691046f..000000000 --- a/examples/corelib/tools/settingseditor/settingseditor.py +++ /dev/null @@ -1,721 +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$ -## -############################################################################# - -"""PySide2 port of the widgets/tools/settingseditor example from Qt v5.x""" - -import sys - -from PySide2 import QtCore, QtGui, QtWidgets - - -class MainWindow(QtWidgets.QMainWindow): - def __init__(self, parent=None): - super(MainWindow, self).__init__(parent) - - self.settingsTree = SettingsTree() - self.setCentralWidget(self.settingsTree) - - self.locationDialog = None - - self.createActions() - self.createMenus() - - self.autoRefreshAct.setChecked(True) - self.fallbacksAct.setChecked(True) - - self.setWindowTitle("Settings Editor") - self.resize(500, 600) - - def openSettings(self): - if self.locationDialog is None: - self.locationDialog = LocationDialog(self) - - if self.locationDialog.exec_(): - settings = QtCore.QSettings(self.locationDialog.format(), - self.locationDialog.scope(), - self.locationDialog.organization(), - self.locationDialog.application()) - self.setSettingsObject(settings) - self.fallbacksAct.setEnabled(True) - - def openIniFile(self): - fileName, _ = QtWidgets.QFileDialog.getOpenFileName(self, "Open INI File", - '', "INI Files (*.ini *.conf)") - - if fileName: - settings = QtCore.QSettings(fileName, QtCore.QSettings.IniFormat) - self.setSettingsObject(settings) - self.fallbacksAct.setEnabled(False) - - def openPropertyList(self): - fileName, _ = QtWidgets.QFileDialog.getOpenFileName(self, - "Open Property List", '', "Property List Files (*.plist)") - - if fileName: - settings = QtCore.QSettings(fileName, QtCore.QSettings.NativeFormat) - self.setSettingsObject(settings) - self.fallbacksAct.setEnabled(False) - - def openRegistryPath(self): - path, ok = QtWidgets.QInputDialog.getText(self, "Open Registry Path", - "Enter the path in the Windows registry:", - QtWidgets.QLineEdit.Normal, 'HKEY_CURRENT_USER\\') - - if ok and path != '': - settings = QtCore.QSettings(path, QtCore.QSettings.NativeFormat) - self.setSettingsObject(settings) - self.fallbacksAct.setEnabled(False) - - def about(self): - QtWidgets.QMessageBox.about(self, "About Settings Editor", - "The <b>Settings Editor</b> example shows how to access " - "application settings using Qt.") - - def createActions(self): - self.openSettingsAct = QtWidgets.QAction("&Open Application Settings...", - self, shortcut="Ctrl+O", triggered=self.openSettings) - - self.openIniFileAct = QtWidgets.QAction("Open I&NI File...", self, - shortcut="Ctrl+N", triggered=self.openIniFile) - - self.openPropertyListAct = QtWidgets.QAction("Open macOS &Property List...", - self, shortcut="Ctrl+P", triggered=self.openPropertyList) - if sys.platform != 'darwin': - self.openPropertyListAct.setEnabled(False) - - self.openRegistryPathAct = QtWidgets.QAction( - "Open Windows &Registry Path...", self, shortcut="Ctrl+G", - triggered=self.openRegistryPath) - if sys.platform != 'win32': - self.openRegistryPathAct.setEnabled(False) - - self.refreshAct = QtWidgets.QAction("&Refresh", self, shortcut="Ctrl+R", - enabled=False, triggered=self.settingsTree.refresh) - - self.exitAct = QtWidgets.QAction("E&xit", self, shortcut="Ctrl+Q", - triggered=self.close) - - self.autoRefreshAct = QtWidgets.QAction("&Auto-Refresh", self, - shortcut="Ctrl+A", checkable=True, enabled=False) - self.autoRefreshAct.triggered[bool].connect(self.settingsTree.setAutoRefresh) - self.autoRefreshAct.triggered[bool].connect(self.refreshAct.setDisabled) - - self.fallbacksAct = QtWidgets.QAction("&Fallbacks", self, - shortcut="Ctrl+F", checkable=True, enabled=False) - self.fallbacksAct.triggered[bool].connect(self.settingsTree.setFallbacksEnabled) - - self.aboutAct = QtWidgets.QAction("&About", self, triggered=self.about) - - self.aboutQtAct = QtWidgets.QAction("About &Qt", self, - triggered=QtWidgets.qApp.aboutQt) - - def createMenus(self): - self.fileMenu = self.menuBar().addMenu("&File") - self.fileMenu.addAction(self.openSettingsAct) - self.fileMenu.addAction(self.openIniFileAct) - self.fileMenu.addAction(self.openPropertyListAct) - self.fileMenu.addAction(self.openRegistryPathAct) - self.fileMenu.addSeparator() - self.fileMenu.addAction(self.refreshAct) - self.fileMenu.addSeparator() - self.fileMenu.addAction(self.exitAct) - - self.optionsMenu = self.menuBar().addMenu("&Options") - self.optionsMenu.addAction(self.autoRefreshAct) - self.optionsMenu.addAction(self.fallbacksAct) - - self.menuBar().addSeparator() - - self.helpMenu = self.menuBar().addMenu("&Help") - self.helpMenu.addAction(self.aboutAct) - self.helpMenu.addAction(self.aboutQtAct) - - def setSettingsObject(self, settings): - settings.setFallbacksEnabled(self.fallbacksAct.isChecked()) - self.settingsTree.setSettingsObject(settings) - - self.refreshAct.setEnabled(True) - self.autoRefreshAct.setEnabled(True) - - niceName = settings.fileName() - niceName.replace('\\', '/') - niceName = niceName.split('/')[-1] - - if not settings.isWritable(): - niceName += " (read only)" - - self.setWindowTitle("%s - Settings Editor" % niceName) - - -class LocationDialog(QtWidgets.QDialog): - def __init__(self, parent=None): - super(LocationDialog, self).__init__(parent) - - self.formatComboBox = QtWidgets.QComboBox() - self.formatComboBox.addItem("Native") - self.formatComboBox.addItem("INI") - - self.scopeComboBox = QtWidgets.QComboBox() - self.scopeComboBox.addItem("User") - self.scopeComboBox.addItem("System") - - self.organizationComboBox = QtWidgets.QComboBox() - self.organizationComboBox.addItem("Trolltech") - self.organizationComboBox.setEditable(True) - - self.applicationComboBox = QtWidgets.QComboBox() - self.applicationComboBox.addItem("Any") - self.applicationComboBox.addItem("Application Example") - self.applicationComboBox.addItem("Assistant") - self.applicationComboBox.addItem("Designer") - self.applicationComboBox.addItem("Linguist") - self.applicationComboBox.setEditable(True) - self.applicationComboBox.setCurrentIndex(3) - - formatLabel = QtWidgets.QLabel("&Format:") - formatLabel.setBuddy(self.formatComboBox) - - scopeLabel = QtWidgets.QLabel("&Scope:") - scopeLabel.setBuddy(self.scopeComboBox) - - organizationLabel = QtWidgets.QLabel("&Organization:") - organizationLabel.setBuddy(self.organizationComboBox) - - applicationLabel = QtWidgets.QLabel("&Application:") - applicationLabel.setBuddy(self.applicationComboBox) - - self.locationsGroupBox = QtWidgets.QGroupBox("Setting Locations") - - self.locationsTable = QtWidgets.QTableWidget() - self.locationsTable.setSelectionMode(QtWidgets.QAbstractItemView.SingleSelection) - self.locationsTable.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows) - self.locationsTable.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers) - self.locationsTable.setColumnCount(2) - self.locationsTable.setHorizontalHeaderLabels(("Location", "Access")) - self.locationsTable.horizontalHeader().setSectionResizeMode(0, QtWidgets.QHeaderView.Stretch) - self.locationsTable.horizontalHeader().resizeSection(1, 180) - - self.buttonBox = QtWidgets.QDialogButtonBox(QtWidgets.QDialogButtonBox.Ok | QtWidgets.QDialogButtonBox.Cancel) - - self.formatComboBox.activated.connect(self.updateLocationsTable) - self.scopeComboBox.activated.connect(self.updateLocationsTable) - self.organizationComboBox.lineEdit().editingFinished.connect(self.updateLocationsTable) - self.applicationComboBox.lineEdit().editingFinished.connect(self.updateLocationsTable) - self.buttonBox.accepted.connect(self.accept) - self.buttonBox.rejected.connect(self.reject) - - locationsLayout = QtWidgets.QVBoxLayout() - locationsLayout.addWidget(self.locationsTable) - self.locationsGroupBox.setLayout(locationsLayout) - - mainLayout = QtWidgets.QGridLayout() - mainLayout.addWidget(formatLabel, 0, 0) - mainLayout.addWidget(self.formatComboBox, 0, 1) - mainLayout.addWidget(scopeLabel, 1, 0) - mainLayout.addWidget(self.scopeComboBox, 1, 1) - mainLayout.addWidget(organizationLabel, 2, 0) - mainLayout.addWidget(self.organizationComboBox, 2, 1) - mainLayout.addWidget(applicationLabel, 3, 0) - mainLayout.addWidget(self.applicationComboBox, 3, 1) - mainLayout.addWidget(self.locationsGroupBox, 4, 0, 1, 2) - mainLayout.addWidget(self.buttonBox, 5, 0, 1, 2) - self.setLayout(mainLayout) - - self.updateLocationsTable() - - self.setWindowTitle("Open Application Settings") - self.resize(650, 400) - - def format(self): - if self.formatComboBox.currentIndex() == 0: - return QtCore.QSettings.NativeFormat - else: - return QtCore.QSettings.IniFormat - - def scope(self): - if self.scopeComboBox.currentIndex() == 0: - return QtCore.QSettings.UserScope - else: - return QtCore.QSettings.SystemScope - - def organization(self): - return self.organizationComboBox.currentText() - - def application(self): - if self.applicationComboBox.currentText() == "Any": - return '' - - return self.applicationComboBox.currentText() - - def updateLocationsTable(self): - self.locationsTable.setUpdatesEnabled(False) - self.locationsTable.setRowCount(0) - - for i in range(2): - if i == 0: - if self.scope() == QtCore.QSettings.SystemScope: - continue - - actualScope = QtCore.QSettings.UserScope - else: - actualScope = QtCore.QSettings.SystemScope - - for j in range(2): - if j == 0: - if not self.application(): - continue - - actualApplication = self.application() - else: - actualApplication = '' - - settings = QtCore.QSettings(self.format(), actualScope, - self.organization(), actualApplication) - - row = self.locationsTable.rowCount() - self.locationsTable.setRowCount(row + 1) - - item0 = QtWidgets.QTableWidgetItem() - item0.setText(settings.fileName()) - - item1 = QtWidgets.QTableWidgetItem() - disable = not (settings.childKeys() or settings.childGroups()) - - if row == 0: - if settings.isWritable(): - item1.setText("Read-write") - disable = False - else: - item1.setText("Read-only") - self.buttonBox.button(QtWidgets.QDialogButtonBox.Ok).setDisabled(disable) - else: - item1.setText("Read-only fallback") - - if disable: - item0.setFlags(item0.flags() & ~QtCore.Qt.ItemIsEnabled) - item1.setFlags(item1.flags() & ~QtCore.Qt.ItemIsEnabled) - - self.locationsTable.setItem(row, 0, item0) - self.locationsTable.setItem(row, 1, item1) - - self.locationsTable.setUpdatesEnabled(True) - - -class SettingsTree(QtWidgets.QTreeWidget): - def __init__(self, parent=None): - super(SettingsTree, self).__init__(parent) - - self.setItemDelegate(VariantDelegate(self)) - - self.setHeaderLabels(("Setting", "Type", "Value")) - self.header().setSectionResizeMode(0, QtWidgets.QHeaderView.Stretch) - self.header().setSectionResizeMode(2, QtWidgets.QHeaderView.Stretch) - - self.settings = None - self.refreshTimer = QtCore.QTimer() - self.refreshTimer.setInterval(2000) - self.autoRefresh = False - - self.groupIcon = QtGui.QIcon() - self.groupIcon.addPixmap(self.style().standardPixmap(QtWidgets.QStyle.SP_DirClosedIcon), - QtGui.QIcon.Normal, QtGui.QIcon.Off) - self.groupIcon.addPixmap(self.style().standardPixmap(QtWidgets.QStyle.SP_DirOpenIcon), - QtGui.QIcon.Normal, QtGui.QIcon.On) - self.keyIcon = QtGui.QIcon() - self.keyIcon.addPixmap(self.style().standardPixmap(QtWidgets.QStyle.SP_FileIcon)) - - self.refreshTimer.timeout.connect(self.maybeRefresh) - - def setSettingsObject(self, settings): - self.settings = settings - self.clear() - - if self.settings is not None: - self.settings.setParent(self) - self.refresh() - if self.autoRefresh: - self.refreshTimer.start() - else: - self.refreshTimer.stop() - - def sizeHint(self): - return QtCore.QSize(800, 600) - - def setAutoRefresh(self, autoRefresh): - self.autoRefresh = autoRefresh - - if self.settings is not None: - if self.autoRefresh: - self.maybeRefresh() - self.refreshTimer.start() - else: - self.refreshTimer.stop() - - def setFallbacksEnabled(self, enabled): - if self.settings is not None: - self.settings.setFallbacksEnabled(enabled) - self.refresh() - - def maybeRefresh(self): - if self.state() != QtWidgets.QAbstractItemView.EditingState: - self.refresh() - - def refresh(self): - if self.settings is None: - return - - # The signal might not be connected. - try: - self.itemChanged.disconnect(self.updateSetting) - except: - pass - - self.settings.sync() - self.updateChildItems(None) - - self.itemChanged.connect(self.updateSetting) - - def event(self, event): - if event.type() == QtCore.QEvent.WindowActivate: - if self.isActiveWindow() and self.autoRefresh: - self.maybeRefresh() - - return super(SettingsTree, self).event(event) - - def updateSetting(self, item): - key = item.text(0) - ancestor = item.parent() - - while ancestor: - key = ancestor.text(0) + '/' + key - ancestor = ancestor.parent() - - d = item.data(2, QtCore.Qt.UserRole) - self.settings.setValue(key, item.data(2, QtCore.Qt.UserRole)) - - if self.autoRefresh: - self.refresh() - - def updateChildItems(self, parent): - dividerIndex = 0 - - for group in self.settings.childGroups(): - childIndex = self.findChild(parent, group, dividerIndex) - if childIndex != -1: - child = self.childAt(parent, childIndex) - child.setText(1, '') - child.setText(2, '') - child.setData(2, QtCore.Qt.UserRole, None) - self.moveItemForward(parent, childIndex, dividerIndex) - else: - child = self.createItem(group, parent, dividerIndex) - - child.setIcon(0, self.groupIcon) - dividerIndex += 1 - - self.settings.beginGroup(group) - self.updateChildItems(child) - self.settings.endGroup() - - for key in self.settings.childKeys(): - childIndex = self.findChild(parent, key, 0) - if childIndex == -1 or childIndex >= dividerIndex: - if childIndex != -1: - child = self.childAt(parent, childIndex) - for i in range(child.childCount()): - self.deleteItem(child, i) - self.moveItemForward(parent, childIndex, dividerIndex) - else: - child = self.createItem(key, parent, dividerIndex) - child.setIcon(0, self.keyIcon) - dividerIndex += 1 - else: - child = self.childAt(parent, childIndex) - - value = self.settings.value(key) - if value is None: - child.setText(1, 'Invalid') - else: - child.setText(1, value.__class__.__name__) - child.setText(2, VariantDelegate.displayText(value)) - child.setData(2, QtCore.Qt.UserRole, value) - - while dividerIndex < self.childCount(parent): - self.deleteItem(parent, dividerIndex) - - def createItem(self, text, parent, index): - after = None - - if index != 0: - after = self.childAt(parent, index - 1) - - if parent is not None: - item = QtWidgets.QTreeWidgetItem(parent, after) - else: - item = QtWidgets.QTreeWidgetItem(self, after) - - item.setText(0, text) - item.setFlags(item.flags() | QtCore.Qt.ItemIsEditable) - return item - - def deleteItem(self, parent, index): - if parent is not None: - item = parent.takeChild(index) - else: - item = self.takeTopLevelItem(index) - del item - - def childAt(self, parent, index): - if parent is not None: - return parent.child(index) - else: - return self.topLevelItem(index) - - def childCount(self, parent): - if parent is not None: - return parent.childCount() - else: - return self.topLevelItemCount() - - def findChild(self, parent, text, startIndex): - for i in range(self.childCount(parent)): - if self.childAt(parent, i).text(0) == text: - return i - return -1 - - def moveItemForward(self, parent, oldIndex, newIndex): - for int in range(oldIndex - newIndex): - self.deleteItem(parent, newIndex) - - -class VariantDelegate(QtWidgets.QItemDelegate): - def __init__(self, parent=None): - super(VariantDelegate, self).__init__(parent) - - self.boolExp = QtCore.QRegExp() - self.boolExp.setPattern('true|false') - self.boolExp.setCaseSensitivity(QtCore.Qt.CaseInsensitive) - - self.byteArrayExp = QtCore.QRegExp() - self.byteArrayExp.setPattern('[\\x00-\\xff]*') - - self.charExp = QtCore.QRegExp() - self.charExp.setPattern('.') - - self.colorExp = QtCore.QRegExp() - self.colorExp.setPattern('\\(([0-9]*),([0-9]*),([0-9]*),([0-9]*)\\)') - - self.doubleExp = QtCore.QRegExp() - self.doubleExp.setPattern('') - - self.pointExp = QtCore.QRegExp() - self.pointExp.setPattern('\\((-?[0-9]*),(-?[0-9]*)\\)') - - self.rectExp = QtCore.QRegExp() - self.rectExp.setPattern('\\((-?[0-9]*),(-?[0-9]*),(-?[0-9]*),(-?[0-9]*)\\)') - - self.signedIntegerExp = QtCore.QRegExp() - self.signedIntegerExp.setPattern('-?[0-9]*') - - self.sizeExp = QtCore.QRegExp(self.pointExp) - - self.unsignedIntegerExp = QtCore.QRegExp() - self.unsignedIntegerExp.setPattern('[0-9]*') - - self.dateExp = QtCore.QRegExp() - self.dateExp.setPattern('([0-9]{,4})-([0-9]{,2})-([0-9]{,2})') - - self.timeExp = QtCore.QRegExp() - self.timeExp.setPattern('([0-9]{,2}):([0-9]{,2}):([0-9]{,2})') - - self.dateTimeExp = QtCore.QRegExp() - self.dateTimeExp.setPattern(self.dateExp.pattern() + 'T' + self.timeExp.pattern()) - - def paint(self, painter, option, index): - if index.column() == 2: - value = index.model().data(index, QtCore.Qt.UserRole) - if not self.isSupportedType(value): - myOption = QtWidgets.QStyleOptionViewItem(option) - myOption.state &= ~QtWidgets.QStyle.State_Enabled - super(VariantDelegate, self).paint(painter, myOption, index) - return - - super(VariantDelegate, self).paint(painter, option, index) - - def createEditor(self, parent, option, index): - if index.column() != 2: - return None - - originalValue = index.model().data(index, QtCore.Qt.UserRole) - if not self.isSupportedType(originalValue): - return None - - lineEdit = QtWidgets.QLineEdit(parent) - lineEdit.setFrame(False) - - if isinstance(originalValue, bool): - regExp = self.boolExp - elif isinstance(originalValue, float): - regExp = self.doubleExp - elif isinstance(originalValue, int): - regExp = self.signedIntegerExp - elif isinstance(originalValue, QtCore.QByteArray): - regExp = self.byteArrayExp - elif isinstance(originalValue, QtGui.QColor): - regExp = self.colorExp - elif isinstance(originalValue, QtCore.QDate): - regExp = self.dateExp - elif isinstance(originalValue, QtCore.QDateTime): - regExp = self.dateTimeExp - elif isinstance(originalValue, QtCore.QTime): - regExp = self.timeExp - elif isinstance(originalValue, QtCore.QPoint): - regExp = self.pointExp - elif isinstance(originalValue, QtCore.QRect): - regExp = self.rectExp - elif isinstance(originalValue, QtCore.QSize): - regExp = self.sizeExp - else: - regExp = QtCore.QRegExp() - - if not regExp.isEmpty(): - validator = QtGui.QRegExpValidator(regExp, lineEdit) - lineEdit.setValidator(validator) - - return lineEdit - - def setEditorData(self, editor, index): - value = index.model().data(index, QtCore.Qt.UserRole) - if editor is not None: - editor.setText(self.displayText(value)) - - def setModelData(self, editor, model, index): - if not editor.isModified(): - return - - text = editor.text() - validator = editor.validator() - if validator is not None: - state, text, _ = validator.validate(text, 0) - if state != QtGui.QValidator.Acceptable: - return - - originalValue = index.model().data(index, QtCore.Qt.UserRole) - - if isinstance(originalValue, QtGui.QColor): - self.colorExp.exactMatch(text) - value = QtGui.QColor(min(int(self.colorExp.cap(1)), 255), - min(int(self.colorExp.cap(2)), 255), - min(int(self.colorExp.cap(3)), 255), - min(int(self.colorExp.cap(4)), 255)) - elif isinstance(originalValue, QtCore.QDate): - value = QtCore.QDate.fromString(text, QtCore.Qt.ISODate) - if not value.isValid(): - return - elif isinstance(originalValue, QtCore.QDateTime): - value = QtCore.QDateTime.fromString(text, QtCore.Qt.ISODate) - if not value.isValid(): - return - elif isinstance(originalValue, QtCore.QTime): - value = QtCore.QTime.fromString(text, QtCore.Qt.ISODate) - if not value.isValid(): - return - elif isinstance(originalValue, QtCore.QPoint): - self.pointExp.exactMatch(text) - value = QtCore.QPoint(int(self.pointExp.cap(1)), - int(self.pointExp.cap(2))) - elif isinstance(originalValue, QtCore.QRect): - self.rectExp.exactMatch(text) - value = QtCore.QRect(int(self.rectExp.cap(1)), - int(self.rectExp.cap(2)), - int(self.rectExp.cap(3)), - int(self.rectExp.cap(4))) - elif isinstance(originalValue, QtCore.QSize): - self.sizeExp.exactMatch(text) - value = QtCore.QSize(int(self.sizeExp.cap(1)), - int(self.sizeExp.cap(2))) - elif isinstance(originalValue, list): - value = text.split(',') - else: - value = type(originalValue)(text) - - model.setData(index, self.displayText(value), QtCore.Qt.DisplayRole) - model.setData(index, value, QtCore.Qt.UserRole) - - @staticmethod - def isSupportedType(value): - return isinstance(value, (bool, float, int, QtCore.QByteArray, - str, QtGui.QColor, QtCore.QDate, QtCore.QDateTime, - QtCore.QTime, QtCore.QPoint, QtCore.QRect, QtCore.QSize, - list)) - - @staticmethod - def displayText(value): - if isinstance(value, (bool, int, QtCore.QByteArray)): - return str(value) - if isinstance(value, str): - return value - elif isinstance(value, float): - return '%g' % value - elif isinstance(value, QtGui.QColor): - return '(%u,%u,%u,%u)' % (value.red(), value.green(), value.blue(), value.alpha()) - elif isinstance(value, (QtCore.QDate, QtCore.QDateTime, QtCore.QTime)): - return value.toString(QtCore.Qt.ISODate) - elif isinstance(value, QtCore.QPoint): - return '(%d,%d)' % (value.x(), value.y()) - elif isinstance(value, QtCore.QRect): - return '(%d,%d,%d,%d)' % (value.x(), value.y(), value.width(), value.height()) - elif isinstance(value, QtCore.QSize): - return '(%d,%d)' % (value.width(), value.height()) - elif isinstance(value, list): - return ','.join(value) - elif value is None: - return '<Invalid>' - - return '<%s>' % value - - -if __name__ == '__main__': - app = QtWidgets.QApplication(sys.argv) - mainWin = MainWindow() - mainWin.show() - sys.exit(app.exec_()) |