1#!/usr/bin/env python
2
3
4#############################################################################
5##
6## Copyright (C) 2013 Riverbank Computing Limited.
7## Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
8## All rights reserved.
9##
10## This file is part of the examples of PyQt.
11##
12## $QT_BEGIN_LICENSE:BSD$
13## You may use this file under the terms of the BSD license as follows:
14##
15## "Redistribution and use in source and binary forms, with or without
16## modification, are permitted provided that the following conditions are
17## met:
18##   * Redistributions of source code must retain the above copyright
19##     notice, this list of conditions and the following disclaimer.
20##   * Redistributions in binary form must reproduce the above copyright
21##     notice, this list of conditions and the following disclaimer in
22##     the documentation and/or other materials provided with the
23##     distribution.
24##   * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor
25##     the names of its contributors may be used to endorse or promote
26##     products derived from this software without specific prior written
27##     permission.
28##
29## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
30## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
31## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
32## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
33## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
34## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
35## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
36## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
37## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
38## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
39## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
40## $QT_END_LICENSE$
41##
42#############################################################################
43
44
45import sys
46
47from PyQt5.QtCore import (QByteArray, QDate, QDateTime, QEvent, QPoint, QRect,
48        QRegExp, QSettings, QSize, Qt, QTime, QTimer)
49from PyQt5.QtGui import QColor, QIcon, QRegExpValidator, QValidator
50from PyQt5.QtWidgets import (QAbstractItemView, QAction, QApplication,
51        QComboBox, QDialog, QDialogButtonBox, QFileDialog, QGridLayout,
52        QGroupBox, QHeaderView, QInputDialog, QItemDelegate, QLabel, QLineEdit,
53        QMainWindow, QMessageBox, QStyle, QStyleOptionViewItem, QTableWidget,
54        QTableWidgetItem, QTreeWidget, QTreeWidgetItem, QVBoxLayout)
55
56
57class MainWindow(QMainWindow):
58    def __init__(self, parent=None):
59        super(MainWindow, self).__init__(parent)
60
61        self.settingsTree = SettingsTree()
62        self.setCentralWidget(self.settingsTree)
63
64        self.locationDialog = None
65
66        self.createActions()
67        self.createMenus()
68
69        self.autoRefreshAct.setChecked(True)
70        self.fallbacksAct.setChecked(True)
71
72        self.setWindowTitle("Settings Editor")
73        self.resize(500, 600)
74
75    def openSettings(self):
76        if self.locationDialog is None:
77            self.locationDialog = LocationDialog(self)
78
79        if self.locationDialog.exec_():
80            settings = QSettings(self.locationDialog.format(),
81                                        self.locationDialog.scope(),
82                                        self.locationDialog.organization(),
83                                        self.locationDialog.application())
84            self.setSettingsObject(settings)
85            self.fallbacksAct.setEnabled(True)
86
87    def openIniFile(self):
88        fileName, _ = QFileDialog.getOpenFileName(self, "Open INI File", '',
89                "INI Files (*.ini *.conf)")
90
91        if fileName:
92            settings = QSettings(fileName, QSettings.IniFormat)
93            self.setSettingsObject(settings)
94            self.fallbacksAct.setEnabled(False)
95
96    def openPropertyList(self):
97        fileName, _ = QFileDialog.getOpenFileName(self, "Open Property List",
98                '', "Property List Files (*.plist)")
99
100        if fileName:
101            settings = QSettings(fileName, QSettings.NativeFormat)
102            self.setSettingsObject(settings)
103            self.fallbacksAct.setEnabled(False)
104
105    def openRegistryPath(self):
106        path, ok = QInputDialog.getText(self, "Open Registry Path",
107                "Enter the path in the Windows registry:", QLineEdit.Normal,
108                'HKEY_CURRENT_USER\\')
109
110        if ok and path != '':
111            settings = QSettings(path, QSettings.NativeFormat)
112            self.setSettingsObject(settings)
113            self.fallbacksAct.setEnabled(False)
114
115    def about(self):
116        QMessageBox.about(self, "About Settings Editor",
117                "The <b>Settings Editor</b> example shows how to access "
118                "application settings using Qt.")
119
120    def createActions(self):
121        self.openSettingsAct = QAction("&Open Application Settings...", self,
122                shortcut="Ctrl+O", triggered=self.openSettings)
123
124        self.openIniFileAct = QAction("Open I&NI File...", self,
125                shortcut="Ctrl+N", triggered=self.openIniFile)
126
127        self.openPropertyListAct = QAction("Open Mac &Property List...", self,
128                shortcut="Ctrl+P", triggered=self.openPropertyList)
129        if sys.platform != 'darwin':
130            self.openPropertyListAct.setEnabled(False)
131
132        self.openRegistryPathAct = QAction("Open Windows &Registry Path...",
133                self, shortcut="Ctrl+G", triggered=self.openRegistryPath)
134        if sys.platform != 'win32':
135            self.openRegistryPathAct.setEnabled(False)
136
137        self.refreshAct = QAction("&Refresh", self, shortcut="Ctrl+R",
138                enabled=False, triggered=self.settingsTree.refresh)
139
140        self.exitAct = QAction("E&xit", self, shortcut="Ctrl+Q",
141                triggered=self.close)
142
143        self.autoRefreshAct = QAction("&Auto-Refresh", self, shortcut="Ctrl+A",
144                checkable=True, enabled=False)
145        self.autoRefreshAct.triggered.connect(self.settingsTree.setAutoRefresh)
146        self.autoRefreshAct.triggered.connect(self.refreshAct.setDisabled)
147
148        self.fallbacksAct = QAction("&Fallbacks", self, shortcut="Ctrl+F",
149                checkable=True, enabled=False,
150                triggered=self.settingsTree.setFallbacksEnabled)
151
152        self.aboutAct = QAction("&About", self, triggered=self.about)
153
154        self.aboutQtAct = QAction("About &Qt", self,
155                triggered=QApplication.instance().aboutQt)
156
157    def createMenus(self):
158        self.fileMenu = self.menuBar().addMenu("&File")
159        self.fileMenu.addAction(self.openSettingsAct)
160        self.fileMenu.addAction(self.openIniFileAct)
161        self.fileMenu.addAction(self.openPropertyListAct)
162        self.fileMenu.addAction(self.openRegistryPathAct)
163        self.fileMenu.addSeparator()
164        self.fileMenu.addAction(self.refreshAct)
165        self.fileMenu.addSeparator()
166        self.fileMenu.addAction(self.exitAct)
167
168        self.optionsMenu = self.menuBar().addMenu("&Options")
169        self.optionsMenu.addAction(self.autoRefreshAct)
170        self.optionsMenu.addAction(self.fallbacksAct)
171
172        self.menuBar().addSeparator()
173
174        self.helpMenu = self.menuBar().addMenu("&Help")
175        self.helpMenu.addAction(self.aboutAct)
176        self.helpMenu.addAction(self.aboutQtAct)
177
178    def setSettingsObject(self, settings):
179        settings.setFallbacksEnabled(self.fallbacksAct.isChecked())
180        self.settingsTree.setSettingsObject(settings)
181
182        self.refreshAct.setEnabled(True)
183        self.autoRefreshAct.setEnabled(True)
184
185        niceName = settings.fileName()
186        niceName.replace('\\', '/')
187        niceName = niceName.split('/')[-1]
188
189        if not settings.isWritable():
190            niceName += " (read only)"
191
192        self.setWindowTitle("%s - Settings Editor" % niceName)
193
194
195class LocationDialog(QDialog):
196    def __init__(self, parent=None):
197        super(LocationDialog, self).__init__(parent)
198
199        self.formatComboBox = QComboBox()
200        self.formatComboBox.addItem("Native")
201        self.formatComboBox.addItem("INI")
202
203        self.scopeComboBox = QComboBox()
204        self.scopeComboBox.addItem("User")
205        self.scopeComboBox.addItem("System")
206
207        self.organizationComboBox = QComboBox()
208        self.organizationComboBox.addItem("Trolltech")
209        self.organizationComboBox.setEditable(True)
210
211        self.applicationComboBox = QComboBox()
212        self.applicationComboBox.addItem("Any")
213        self.applicationComboBox.addItem("Application Example")
214        self.applicationComboBox.addItem("Assistant")
215        self.applicationComboBox.addItem("Designer")
216        self.applicationComboBox.addItem("Linguist")
217        self.applicationComboBox.setEditable(True)
218        self.applicationComboBox.setCurrentIndex(3)
219
220        formatLabel = QLabel("&Format:")
221        formatLabel.setBuddy(self.formatComboBox)
222
223        scopeLabel = QLabel("&Scope:")
224        scopeLabel.setBuddy(self.scopeComboBox)
225
226        organizationLabel = QLabel("&Organization:")
227        organizationLabel.setBuddy(self.organizationComboBox)
228
229        applicationLabel = QLabel("&Application:")
230        applicationLabel.setBuddy(self.applicationComboBox)
231
232        self.locationsGroupBox = QGroupBox("Setting Locations")
233
234        self.locationsTable = QTableWidget()
235        self.locationsTable.setSelectionMode(QAbstractItemView.SingleSelection)
236        self.locationsTable.setSelectionBehavior(QAbstractItemView.SelectRows)
237        self.locationsTable.setEditTriggers(QAbstractItemView.NoEditTriggers)
238        self.locationsTable.setColumnCount(2)
239        self.locationsTable.setHorizontalHeaderLabels(("Location", "Access"))
240        self.locationsTable.horizontalHeader().setSectionResizeMode(0, QHeaderView.Stretch)
241        self.locationsTable.horizontalHeader().resizeSection(1, 180)
242
243        self.buttonBox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
244
245        self.formatComboBox.activated.connect(self.updateLocationsTable)
246        self.scopeComboBox.activated.connect(self.updateLocationsTable)
247        self.organizationComboBox.lineEdit().editingFinished.connect(self.updateLocationsTable)
248        self.applicationComboBox.lineEdit().editingFinished.connect(self.updateLocationsTable)
249        self.buttonBox.accepted.connect(self.accept)
250        self.buttonBox.rejected.connect(self.reject)
251
252        locationsLayout = QVBoxLayout()
253        locationsLayout.addWidget(self.locationsTable)
254        self.locationsGroupBox.setLayout(locationsLayout)
255
256        mainLayout = QGridLayout()
257        mainLayout.addWidget(formatLabel, 0, 0)
258        mainLayout.addWidget(self.formatComboBox, 0, 1)
259        mainLayout.addWidget(scopeLabel, 1, 0)
260        mainLayout.addWidget(self.scopeComboBox, 1, 1)
261        mainLayout.addWidget(organizationLabel, 2, 0)
262        mainLayout.addWidget(self.organizationComboBox, 2, 1)
263        mainLayout.addWidget(applicationLabel, 3, 0)
264        mainLayout.addWidget(self.applicationComboBox, 3, 1)
265        mainLayout.addWidget(self.locationsGroupBox, 4, 0, 1, 2)
266        mainLayout.addWidget(self.buttonBox, 5, 0, 1, 2)
267        self.setLayout(mainLayout)
268
269        self.updateLocationsTable()
270
271        self.setWindowTitle("Open Application Settings")
272        self.resize(650, 400)
273
274    def format(self):
275        if self.formatComboBox.currentIndex() == 0:
276            return QSettings.NativeFormat
277        else:
278            return QSettings.IniFormat
279
280    def scope(self):
281        if self.scopeComboBox.currentIndex() == 0:
282            return QSettings.UserScope
283        else:
284            return QSettings.SystemScope
285
286    def organization(self):
287        return self.organizationComboBox.currentText()
288
289    def application(self):
290        if self.applicationComboBox.currentText() == "Any":
291            return ''
292
293        return self.applicationComboBox.currentText()
294
295    def updateLocationsTable(self):
296        self.locationsTable.setUpdatesEnabled(False)
297        self.locationsTable.setRowCount(0)
298
299        for i in range(2):
300            if i == 0:
301                if self.scope() == QSettings.SystemScope:
302                    continue
303
304                actualScope = QSettings.UserScope
305            else:
306                actualScope = QSettings.SystemScope
307
308            for j in range(2):
309                if j == 0:
310                    if not self.application():
311                        continue
312
313                    actualApplication = self.application()
314                else:
315                    actualApplication = ''
316
317                settings = QSettings(self.format(), actualScope,
318                        self.organization(), actualApplication)
319
320                row = self.locationsTable.rowCount()
321                self.locationsTable.setRowCount(row + 1)
322
323                item0 = QTableWidgetItem()
324                item0.setText(settings.fileName())
325
326                item1 = QTableWidgetItem()
327                disable = not (settings.childKeys() or settings.childGroups())
328
329                if row == 0:
330                    if settings.isWritable():
331                        item1.setText("Read-write")
332                        disable = False
333                    else:
334                        item1.setText("Read-only")
335                    self.buttonBox.button(QDialogButtonBox.Ok).setDisabled(disable)
336                else:
337                    item1.setText("Read-only fallback")
338
339                if disable:
340                    item0.setFlags(item0.flags() & ~Qt.ItemIsEnabled)
341                    item1.setFlags(item1.flags() & ~Qt.ItemIsEnabled)
342
343                self.locationsTable.setItem(row, 0, item0)
344                self.locationsTable.setItem(row, 1, item1)
345
346        self.locationsTable.setUpdatesEnabled(True)
347
348
349class SettingsTree(QTreeWidget):
350    def __init__(self, parent=None):
351        super(SettingsTree, self).__init__(parent)
352
353        self.setItemDelegate(VariantDelegate(self))
354
355        self.setHeaderLabels(("Setting", "Type", "Value"))
356        self.header().setSectionResizeMode(0, QHeaderView.Stretch)
357        self.header().setSectionResizeMode(2, QHeaderView.Stretch)
358
359        self.settings = None
360        self.refreshTimer = QTimer()
361        self.refreshTimer.setInterval(2000)
362        self.autoRefresh = False
363
364        self.groupIcon = QIcon()
365        self.groupIcon.addPixmap(self.style().standardPixmap(QStyle.SP_DirClosedIcon),
366                QIcon.Normal, QIcon.Off)
367        self.groupIcon.addPixmap(self.style().standardPixmap(QStyle.SP_DirOpenIcon),
368                QIcon.Normal, QIcon.On)
369        self.keyIcon = QIcon()
370        self.keyIcon.addPixmap(self.style().standardPixmap(QStyle.SP_FileIcon))
371
372        self.refreshTimer.timeout.connect(self.maybeRefresh)
373
374    def setSettingsObject(self, settings):
375        self.settings = settings
376        self.clear()
377
378        if self.settings is not None:
379            self.settings.setParent(self)
380            self.refresh()
381            if self.autoRefresh:
382                self.refreshTimer.start()
383        else:
384            self.refreshTimer.stop()
385
386    def sizeHint(self):
387        return QSize(800, 600)
388
389    def setAutoRefresh(self, autoRefresh):
390        self.autoRefresh = autoRefresh
391
392        if self.settings is not None:
393            if self.autoRefresh:
394                self.maybeRefresh()
395                self.refreshTimer.start()
396            else:
397                self.refreshTimer.stop()
398
399    def setFallbacksEnabled(self, enabled):
400        if self.settings is not None:
401            self.settings.setFallbacksEnabled(enabled)
402            self.refresh()
403
404    def maybeRefresh(self):
405        if self.state() != QAbstractItemView.EditingState:
406            self.refresh()
407
408    def refresh(self):
409        if self.settings is None:
410            return
411
412        # The signal might not be connected.
413        try:
414            self.itemChanged.disconnect(self.updateSetting)
415        except:
416            pass
417
418        self.settings.sync()
419        self.updateChildItems(None)
420
421        self.itemChanged.connect(self.updateSetting)
422
423    def event(self, event):
424        if event.type() == QEvent.WindowActivate:
425            if self.isActiveWindow() and self.autoRefresh:
426                self.maybeRefresh()
427
428        return super(SettingsTree, self).event(event)
429
430    def updateSetting(self, item):
431        key = item.text(0)
432        ancestor = item.parent()
433
434        while ancestor:
435            key = ancestor.text(0) + '/' + key
436            ancestor = ancestor.parent()
437
438        d = item.data(2, Qt.UserRole)
439        self.settings.setValue(key, item.data(2, Qt.UserRole))
440
441        if self.autoRefresh:
442            self.refresh()
443
444    def updateChildItems(self, parent):
445        dividerIndex = 0
446
447        for group in self.settings.childGroups():
448            childIndex = self.findChild(parent, group, dividerIndex)
449            if childIndex != -1:
450                child = self.childAt(parent, childIndex)
451                child.setText(1, '')
452                child.setText(2, '')
453                child.setData(2, Qt.UserRole, None)
454                self.moveItemForward(parent, childIndex, dividerIndex)
455            else:
456                child = self.createItem(group, parent, dividerIndex)
457
458            child.setIcon(0, self.groupIcon)
459            dividerIndex += 1
460
461            self.settings.beginGroup(group)
462            self.updateChildItems(child)
463            self.settings.endGroup()
464
465        for key in self.settings.childKeys():
466            childIndex = self.findChild(parent, key, 0)
467            if childIndex == -1 or childIndex >= dividerIndex:
468                if childIndex != -1:
469                    child = self.childAt(parent, childIndex)
470                    for i in range(child.childCount()):
471                        self.deleteItem(child, i)
472                    self.moveItemForward(parent, childIndex, dividerIndex)
473                else:
474                    child = self.createItem(key, parent, dividerIndex)
475                child.setIcon(0, self.keyIcon)
476                dividerIndex += 1
477            else:
478                child = self.childAt(parent, childIndex)
479
480            value = self.settings.value(key)
481            if value is None:
482                child.setText(1, 'Invalid')
483            else:
484                child.setText(1, value.__class__.__name__)
485            child.setText(2, VariantDelegate.displayText(value))
486            child.setData(2, Qt.UserRole, value)
487
488        while dividerIndex < self.childCount(parent):
489            self.deleteItem(parent, dividerIndex)
490
491    def createItem(self, text, parent, index):
492        after = None
493
494        if index != 0:
495            after = self.childAt(parent, index - 1)
496
497        if parent is not None:
498            item = QTreeWidgetItem(parent, after)
499        else:
500            item = QTreeWidgetItem(self, after)
501
502        item.setText(0, text)
503        item.setFlags(item.flags() | Qt.ItemIsEditable)
504        return item
505
506    def deleteItem(self, parent, index):
507        if parent is not None:
508            item = parent.takeChild(index)
509        else:
510            item = self.takeTopLevelItem(index)
511        del item
512
513    def childAt(self, parent, index):
514        if parent is not None:
515            return parent.child(index)
516        else:
517            return self.topLevelItem(index)
518
519    def childCount(self, parent):
520        if parent is not None:
521            return parent.childCount()
522        else:
523            return self.topLevelItemCount()
524
525    def findChild(self, parent, text, startIndex):
526        for i in range(self.childCount(parent)):
527            if self.childAt(parent, i).text(0) == text:
528                return i
529        return -1
530
531    def moveItemForward(self, parent, oldIndex, newIndex):
532        for int in range(oldIndex - newIndex):
533            self.deleteItem(parent, newIndex)
534
535
536class VariantDelegate(QItemDelegate):
537    def __init__(self, parent=None):
538        super(VariantDelegate, self).__init__(parent)
539
540        self.boolExp = QRegExp()
541        self.boolExp.setPattern('true|false')
542        self.boolExp.setCaseSensitivity(Qt.CaseInsensitive)
543
544        self.byteArrayExp = QRegExp()
545        self.byteArrayExp.setPattern('[\\x00-\\xff]*')
546
547        self.charExp = QRegExp()
548        self.charExp.setPattern('.')
549
550        self.colorExp = QRegExp()
551        self.colorExp.setPattern('\\(([0-9]*),([0-9]*),([0-9]*),([0-9]*)\\)')
552
553        self.doubleExp = QRegExp()
554        self.doubleExp.setPattern('')
555
556        self.pointExp = QRegExp()
557        self.pointExp.setPattern('\\((-?[0-9]*),(-?[0-9]*)\\)')
558
559        self.rectExp = QRegExp()
560        self.rectExp.setPattern('\\((-?[0-9]*),(-?[0-9]*),(-?[0-9]*),(-?[0-9]*)\\)')
561
562        self.signedIntegerExp = QRegExp()
563        self.signedIntegerExp.setPattern('-?[0-9]*')
564
565        self.sizeExp = QRegExp(self.pointExp)
566
567        self.unsignedIntegerExp = QRegExp()
568        self.unsignedIntegerExp.setPattern('[0-9]*')
569
570        self.dateExp = QRegExp()
571        self.dateExp.setPattern('([0-9]{,4})-([0-9]{,2})-([0-9]{,2})')
572
573        self.timeExp = QRegExp()
574        self.timeExp.setPattern('([0-9]{,2}):([0-9]{,2}):([0-9]{,2})')
575
576        self.dateTimeExp = QRegExp()
577        self.dateTimeExp.setPattern(self.dateExp.pattern() + 'T' + self.timeExp.pattern())
578
579    def paint(self, painter, option, index):
580        if index.column() == 2:
581            value = index.model().data(index, Qt.UserRole)
582            if not self.isSupportedType(value):
583                myOption = QStyleOptionViewItem(option)
584                myOption.state &= ~QStyle.State_Enabled
585                super(VariantDelegate, self).paint(painter, myOption, index)
586                return
587
588        super(VariantDelegate, self).paint(painter, option, index)
589
590    def createEditor(self, parent, option, index):
591        if index.column() != 2:
592            return None
593
594        originalValue = index.model().data(index, Qt.UserRole)
595        if not self.isSupportedType(originalValue):
596            return None
597
598        lineEdit = QLineEdit(parent)
599        lineEdit.setFrame(False)
600
601        if isinstance(originalValue, bool):
602            regExp = self.boolExp
603        elif isinstance(originalValue, float):
604            regExp = self.doubleExp
605        elif isinstance(originalValue, int):
606            regExp = self.signedIntegerExp
607        elif isinstance(originalValue, QByteArray):
608            regExp = self.byteArrayExp
609        elif isinstance(originalValue, QColor):
610            regExp = self.colorExp
611        elif isinstance(originalValue, QDate):
612            regExp = self.dateExp
613        elif isinstance(originalValue, QDateTime):
614            regExp = self.dateTimeExp
615        elif isinstance(originalValue, QTime):
616            regExp = self.timeExp
617        elif isinstance(originalValue, QPoint):
618            regExp = self.pointExp
619        elif isinstance(originalValue, QRect):
620            regExp = self.rectExp
621        elif isinstance(originalValue, QSize):
622            regExp = self.sizeExp
623        else:
624            regExp = QRegExp()
625
626        if not regExp.isEmpty():
627            validator = QRegExpValidator(regExp, lineEdit)
628            lineEdit.setValidator(validator)
629
630        return lineEdit
631
632    def setEditorData(self, editor, index):
633        value = index.model().data(index, Qt.UserRole)
634        if editor is not None:
635            editor.setText(self.displayText(value))
636
637    def setModelData(self, editor, model, index):
638        if not editor.isModified():
639            return
640
641        text = editor.text()
642        validator = editor.validator()
643        if validator is not None:
644            state, text, _ = validator.validate(text, 0)
645            if state != QValidator.Acceptable:
646                return
647
648        originalValue = index.model().data(index, Qt.UserRole)
649
650        if isinstance(originalValue, QColor):
651            self.colorExp.exactMatch(text)
652            value = QColor(min(int(self.colorExp.cap(1)), 255),
653                           min(int(self.colorExp.cap(2)), 255),
654                           min(int(self.colorExp.cap(3)), 255),
655                           min(int(self.colorExp.cap(4)), 255))
656        elif isinstance(originalValue, QDate):
657            value = QDate.fromString(text, Qt.ISODate)
658            if not value.isValid():
659                return
660        elif isinstance(originalValue, QDateTime):
661            value = QDateTime.fromString(text, Qt.ISODate)
662            if not value.isValid():
663                return
664        elif isinstance(originalValue, QTime):
665            value = QTime.fromString(text, Qt.ISODate)
666            if not value.isValid():
667                return
668        elif isinstance(originalValue, QPoint):
669            self.pointExp.exactMatch(text)
670            value = QPoint(int(self.pointExp.cap(1)),
671                           int(self.pointExp.cap(2)))
672        elif isinstance(originalValue, QRect):
673            self.rectExp.exactMatch(text)
674            value = QRect(int(self.rectExp.cap(1)),
675                          int(self.rectExp.cap(2)),
676                          int(self.rectExp.cap(3)),
677                          int(self.rectExp.cap(4)))
678        elif isinstance(originalValue, QSize):
679            self.sizeExp.exactMatch(text)
680            value = QSize(int(self.sizeExp.cap(1)),
681                          int(self.sizeExp.cap(2)))
682        elif isinstance(originalValue, list):
683            value = text.split(',')
684        else:
685            value = type(originalValue)(text)
686
687        model.setData(index, self.displayText(value), Qt.DisplayRole)
688        model.setData(index, value, Qt.UserRole)
689
690    @staticmethod
691    def isSupportedType(value):
692        return isinstance(value, (bool, float, int, QByteArray, str, QColor,
693                QDate, QDateTime, QTime, QPoint, QRect, QSize, list))
694
695    @staticmethod
696    def displayText(value):
697        if isinstance(value, (bool, int, QByteArray)):
698            return str(value)
699        if isinstance(value, str):
700            return value
701        elif isinstance(value, float):
702            return '%g' % value
703        elif isinstance(value, QColor):
704            return '(%u,%u,%u,%u)' % (value.red(), value.green(), value.blue(), value.alpha())
705        elif isinstance(value, (QDate, QDateTime, QTime)):
706            return value.toString(Qt.ISODate)
707        elif isinstance(value, QPoint):
708            return '(%d,%d)' % (value.x(), value.y())
709        elif isinstance(value, QRect):
710            return '(%d,%d,%d,%d)' % (value.x(), value.y(), value.width(), value.height())
711        elif isinstance(value, QSize):
712            return '(%d,%d)' % (value.width(), value.height())
713        elif isinstance(value, list):
714            return ','.join(value)
715        elif value is None:
716            return '<Invalid>'
717
718        return '<%s>' % value
719
720
721if __name__ == '__main__':
722    app = QApplication(sys.argv)
723    mainWin = MainWindow()
724    mainWin.show()
725    sys.exit(app.exec_())
726