diff options
author | Friedemann Kleint <[email protected]> | 2018-01-05 15:58:35 +0100 |
---|---|---|
committer | Friedemann Kleint <[email protected]> | 2018-01-12 12:28:10 +0000 |
commit | 9f2a9aba3aff73e31ea15eb4a7a04b0e50f4ee4e (patch) | |
tree | 92dcb0c4f64df8a8375af2e1a9bb1170068c36b2 /examples/corelib | |
parent | 26c046e521c38bbfc3a263782a3bb74a7c1bf937 (diff) |
Move examples from submodule to pyside-setup
Move PySide2 examples that are owned by the Qt Company
to a new examples directory.
Done-with: Venugopal Shivashankar <[email protected]>
Task-number: PYSIDE-363
Change-Id: I14099764d9eef2bc35e067086121427955862e3a
Reviewed-by: Alexandru Croitor <[email protected]>
Diffstat (limited to 'examples/corelib')
-rwxr-xr-x | examples/corelib/threads/mandelbrot.py | 349 | ||||
-rwxr-xr-x | examples/corelib/tools/codecs/codecs.py | 251 | ||||
-rwxr-xr-x | examples/corelib/tools/regexp.py | 195 | ||||
-rwxr-xr-x | examples/corelib/tools/settingseditor/settingseditor.py | 722 |
4 files changed, 1517 insertions, 0 deletions
diff --git a/examples/corelib/threads/mandelbrot.py b/examples/corelib/threads/mandelbrot.py new file mode 100755 index 000000000..53da3d48d --- /dev/null +++ b/examples/corelib/threads/mandelbrot.py @@ -0,0 +1,349 @@ +#!/usr/bin/env python + +############################################################################# +## +## 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 PySide 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 + + +class RenderThread(QThread): + ColormapSize = 512 + + renderedImage = Signal(QImage, float) + + def __init__(self, parent=None): + super(RenderThread, self).__init__(parent) + + self.mutex = QMutex() + self.condition = QWaitCondition() + self.centerX = 0.0 + self.centerY = 0.0 + self.scaleFactor = 0.0 + self.resultSize = 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))) + + def stop(self): + self.mutex.lock() + self.abort = True + self.condition.wakeOne() + self.mutex.unlock() + + 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 + + if not self.isRunning(): + self.start(QThread.LowPriority) + else: + self.restart = True + self.condition.wakeOne() + + def run(self): + while True: + self.mutex.lock() + resultSize = self.resultSize + scaleFactor = self.scaleFactor + centerX = self.centerX + centerY = self.centerY + self.mutex.unlock() + + halfWidth = resultSize.width() // 2 + halfHeight = 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 + + for y in range(-halfHeight, halfHeight): + if self.restart: + break + if self.abort: + return + + ay = 1j * (centerY + (y * scaleFactor)) + + for x in range(-halfWidth, halfWidth): + c0 = centerX + (x * scaleFactor) + ay + c = c0 + numIterations = 0 + + while numIterations < MaxIterations: + numIterations += 1 + c = c*c + c0 + if abs(c) >= Limit: + break + numIterations += 1 + c = c*c + c0 + if abs(c) >= Limit: + break + numIterations += 1 + c = c*c + c0 + if abs(c) >= Limit: + break + numIterations += 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 + else: + image.setPixel(x + halfWidth, y + halfHeight, qRgb(0, 0, 0)) + + if allBlack and curpass == 0: + curpass = 4 + else: + if not self.restart: + self.renderedImage.emit(image, scaleFactor) + curpass += 1 + + self.mutex.lock() + if not self.restart: + self.condition.wait(self.mutex) + self.restart = False + self.mutex.unlock() + + def rgbFromWaveLength(self, wave): + r = 0.0 + g = 0.0 + b = 0.0 + + if wave >= 380.0 and wave <= 440.0: + r = -1.0 * (wave - 440.0) / (440.0 - 380.0) + b = 1.0 + elif wave >= 440.0 and wave <= 490.0: + g = (wave - 440.0) / (490.0 - 440.0) + b = 1.0 + elif wave >= 490.0 and wave <= 510.0: + g = 1.0 + b = -1.0 * (wave - 510.0) / (510.0 - 490.0) + elif wave >= 510.0 and wave <= 580.0: + r = (wave - 510.0) / (580.0 - 510.0) + g = 1.0 + elif wave >= 580.0 and wave <= 645.0: + r = 1.0 + g = -1.0 * (wave - 645.0) / (645.0 - 580.0) + elif wave >= 645.0 and wave <= 780.0: + r = 1.0 + + s = 1.0 + if wave > 700.0: + s = 0.3 + 0.7 * (780.0 - wave) / (780.0 - 700.0) + elif wave < 420.0: + s = 0.3 + 0.7 * (wave - 380.0) / (420.0 - 380.0) + + r = pow(r * s, 0.8) + g = pow(g * s, 0.8) + b = pow(b * s, 0.8) + + return qRgb(r*255, g*255, b*255) + + +class MandelbrotWidget(QWidget): + def __init__(self, parent=None): + super(MandelbrotWidget, self).__init__(parent) + + self.thread = RenderThread() + self.pixmap = QPixmap() + self.pixmapOffset = QPoint() + self.lastDragPos = QPoint() + + self.centerX = DefaultCenterX + self.centerY = DefaultCenterY + self.pixmapScale = DefaultScale + self.curScale = DefaultScale + + self.thread.renderedImage.connect(self.updatePixmap) + + self.setWindowTitle("Mandelbrot") + self.setCursor(Qt.CrossCursor) + self.resize(550, 400) + + def paintEvent(self, event): + painter = QPainter(self) + 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.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) + + def resizeEvent(self, event): + self.thread.render(self.centerX, self.centerY, self.curScale, self.size()) + + def keyPressEvent(self, event): + if event.key() == Qt.Key_Plus: + self.zoom(ZoomInFactor) + elif event.key() == Qt.Key_Minus: + self.zoom(ZoomOutFactor) + elif event.key() == Qt.Key_Left: + self.scroll(-ScrollStep, 0) + elif event.key() == Qt.Key_Right: + self.scroll(+ScrollStep, 0) + elif event.key() == Qt.Key_Down: + self.scroll(0, -ScrollStep) + elif event.key() == Qt.Key_Up: + self.scroll(0, +ScrollStep) + else: + super(MandelbrotWidget, self).keyPressEvent(event) + + def wheelEvent(self, event): + numDegrees = event.angleDelta().y() / 8 + numSteps = numDegrees / 15.0 + self.zoom(pow(ZoomInFactor, numSteps)) + + def mousePressEvent(self, event): + if event.buttons() == Qt.LeftButton: + self.lastDragPos = QPoint(event.pos()) + + def mouseMoveEvent(self, event): + if event.buttons() & Qt.LeftButton: + self.pixmapOffset += event.pos() - self.lastDragPos + self.lastDragPos = QPoint(event.pos()) + self.update() + + def mouseReleaseEvent(self, event): + if event.button() == Qt.LeftButton: + self.pixmapOffset += event.pos() - self.lastDragPos + self.lastDragPos = QPoint() + + deltaX = (self.width() - self.pixmap.width()) / 2 - self.pixmapOffset.x() + deltaY = (self.height() - self.pixmap.height()) / 2 - self.pixmapOffset.y() + self.scroll(deltaX, deltaY) + + def updatePixmap(self, image, scaleFactor): + if not self.lastDragPos.isNull(): + return + + self.pixmap = QPixmap.fromImage(image) + self.pixmapOffset = QPoint() + self.lastDragPosition = QPoint() + self.pixmapScale = scaleFactor + self.update() + + def zoom(self, zoomFactor): + self.curScale *= zoomFactor + self.update() + self.thread.render(self.centerX, self.centerY, self.curScale, + self.size()) + + def scroll(self, deltaX, deltaY): + self.centerX += deltaX * self.curScale + self.centerY += deltaY * self.curScale + self.update() + self.thread.render(self.centerX, self.centerY, self.curScale, + self.size()) + + +if __name__ == '__main__': + + import sys + + app = QApplication(sys.argv) + widget = MandelbrotWidget() + widget.show() + r = app.exec_() + widget.thread.stop() + sys.exit(r) diff --git a/examples/corelib/tools/codecs/codecs.py b/examples/corelib/tools/codecs/codecs.py new file mode 100755 index 000000000..15f7d9563 --- /dev/null +++ b/examples/corelib/tools/codecs/codecs.py @@ -0,0 +1,251 @@ +#!/usr/bin/env python + +############################################################################# +## +## 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 PySide 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 new file mode 100755 index 000000000..7e28a5365 --- /dev/null +++ b/examples/corelib/tools/regexp.py @@ -0,0 +1,195 @@ +#!/usr/bin/env python + +############################################################################# +## +## 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 PySide 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 new file mode 100755 index 000000000..905049746 --- /dev/null +++ b/examples/corelib/tools/settingseditor/settingseditor.py @@ -0,0 +1,722 @@ +#!/usr/bin/env python + +############################################################################# +## +## 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 PySide 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_()) |