aboutsummaryrefslogtreecommitdiffstats
path: root/examples/corelib
diff options
context:
space:
mode:
Diffstat (limited to 'examples/corelib')
-rw-r--r--examples/corelib/ipc/sharedmemory/dialog.py93
-rw-r--r--examples/corelib/ipc/sharedmemory/dialog.ui47
-rw-r--r--examples/corelib/ipc/sharedmemory/image.pngbin0 -> 10199 bytes
-rw-r--r--examples/corelib/ipc/sharedmemory/main.py15
-rw-r--r--examples/corelib/ipc/sharedmemory/qt.pngbin0 -> 2991 bytes
-rw-r--r--examples/corelib/ipc/sharedmemory/sharedmemory.pyproject3
-rw-r--r--examples/corelib/ipc/sharedmemory/ui_dialog.py57
-rw-r--r--examples/corelib/mimetypesbrowser/doc/mimetypesbrowser.pngbin0 -> 22569 bytes
-rw-r--r--examples/corelib/mimetypesbrowser/doc/mimetypesbrowser.rst10
-rw-r--r--examples/corelib/mimetypesbrowser/mainwindow.py161
-rw-r--r--examples/corelib/mimetypesbrowser/mimetypemodel.py136
-rw-r--r--examples/corelib/mimetypesbrowser/mimetypesbrowser.py24
-rw-r--r--examples/corelib/mimetypesbrowser/mimetypesbrowser.pyproject3
-rw-r--r--examples/corelib/settingseditor/doc/settingseditor.pngbin0 -> 10234 bytes
-rw-r--r--examples/corelib/settingseditor/doc/settingseditor.rst10
-rw-r--r--examples/corelib/settingseditor/settingseditor.py753
-rw-r--r--examples/corelib/settingseditor/settingseditor.pyproject3
-rw-r--r--examples/corelib/threads/doc/threads.pngbin0 -> 61689 bytes
-rw-r--r--examples/corelib/threads/doc/threads.rst10
-rw-r--r--examples/corelib/threads/mandelbrot.py392
-rw-r--r--examples/corelib/threads/threads.pyproject3
-rw-r--r--examples/corelib/tools/codecs/codecs.py250
-rw-r--r--examples/corelib/tools/regexp.py194
-rw-r--r--examples/corelib/tools/settingseditor/settingseditor.py721
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
new file mode 100644
index 000000000..dd9345306
--- /dev/null
+++ b/examples/corelib/ipc/sharedmemory/image.png
Binary files differ
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
new file mode 100644
index 000000000..4f68e162d
--- /dev/null
+++ b/examples/corelib/ipc/sharedmemory/qt.png
Binary files differ
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
new file mode 100644
index 000000000..3c4a476b3
--- /dev/null
+++ b/examples/corelib/mimetypesbrowser/doc/mimetypesbrowser.png
Binary files differ
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
new file mode 100644
index 000000000..d281125d3
--- /dev/null
+++ b/examples/corelib/settingseditor/doc/settingseditor.png
Binary files differ
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
new file mode 100644
index 000000000..d022f4aff
--- /dev/null
+++ b/examples/corelib/threads/doc/threads.png
Binary files differ
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_())