aboutsummaryrefslogtreecommitdiffstats
path: root/examples/widgets
diff options
context:
space:
mode:
authorFriedemann Kleint <Friedemann.Kleint@qt.io>2021-04-06 16:03:40 +0200
committerFriedemann Kleint <Friedemann.Kleint@qt.io>2021-04-12 15:46:56 +0200
commit9a9f9fd2528c03df4b0e9dde48026a2181e8a410 (patch)
tree60b2c8b3e79d4f09421d10a551fd214d523f8814 /examples/widgets
parent59f92c2133ad1d43ba2f6a7a32c5b70fc6aba614 (diff)
Rewrite the classwizard example
The classwizard created some outdated C++ header and source which is not useful for Qt for Python. Rewrite it to generate a Python class and add a special page allowing for specifying properties and signals of QObjects. Add an overwrite check and a 'Launch' checkbox to the conclusion page. Use QFormLayout instead QGridLayout for the pages. Task-number: PYSIDE-1112 Change-Id: Ice158553571e30ea069ceda8873bf165dc704afc Reviewed-by: Christian Tismer <tismer@stackless.com>
Diffstat (limited to 'examples/widgets')
-rw-r--r--examples/widgets/dialogs/classwizard/classwizard.py563
-rw-r--r--examples/widgets/dialogs/classwizard/classwizard.pyproject2
-rw-r--r--examples/widgets/dialogs/classwizard/listchooser.py212
3 files changed, 501 insertions, 276 deletions
diff --git a/examples/widgets/dialogs/classwizard/classwizard.py b/examples/widgets/dialogs/classwizard/classwizard.py
index ce7c02d89..7267d1d8b 100644
--- a/examples/widgets/dialogs/classwizard/classwizard.py
+++ b/examples/widgets/dialogs/classwizard/classwizard.py
@@ -1,7 +1,7 @@
#############################################################################
##
## Copyright (C) 2013 Riverbank Computing Limited.
-## Copyright (C) 2016 The Qt Company Ltd.
+## Copyright (C) 2021 The Qt Company Ltd.
## Contact: http://www.qt.io/licensing/
##
## This file is part of the Qt for Python examples of the Qt Toolkit.
@@ -39,368 +39,381 @@
##
#############################################################################
-from PySide6 import QtCore, QtGui, QtWidgets
+import os
+from pathlib import Path
+import sys
+
+from PySide6.QtCore import (QByteArray, QDir, QFile, QFileInfo,
+ QRegularExpression, Qt, QUrl, Slot)
+from PySide6.QtGui import QDesktopServices, QPixmap
+from PySide6.QtWidgets import (QApplication, QComboBox, QCheckBox, QFormLayout,
+ QFileDialog, QGroupBox, QGridLayout,
+ QHBoxLayout, QLabel, QLineEdit, QMessageBox,
+ QPushButton, QRadioButton, QToolButton,
+ QVBoxLayout, QWizard, QWizardPage)
+
+from listchooser import ListChooser, PropertyChooser, SignalChooser
import classwizard_rc
-class ClassWizard(QtWidgets.QWizard):
- def __init__(self, parent=None):
- super(ClassWizard, self).__init__(parent)
+BASE_CLASSES = ['<None>', 'PySide6.QtCore.QObject',
+ 'PySide6.QtWidgets.QDialog', 'PySide6.QtWidgets.QMainWindow'
+ 'PySide6.QtWidgets.QWidget']
- self.addPage(IntroPage())
- self.addPage(ClassInfoPage())
- self.addPage(CodeStylePage())
- self.addPage(OutputFilesPage())
- self.addPage(ConclusionPage())
- self.setPixmap(QtWidgets.QWizard.BannerPixmap,
- QtGui.QPixmap(':/images/banner.png'))
- self.setPixmap(QtWidgets.QWizard.BackgroundPixmap,
- QtGui.QPixmap(':/images/background.png'))
+PYTHON_TYPES = ['int', 'list', 'str']
- self.setWindowTitle("Class Wizard")
- def accept(self):
- class_name = self.field('className')
- base_class = self.field('baseClass')
- macro_name = self.field('macroName')
- base_include = self.field('baseInclude')
+INTRODUCTION = """This wizard will generate a skeleton Python class definition,\
+ including a few functions. You simply need to specify the class name and set\
+ a few options to produce a Python file."""
- output_dir = self.field('outputDir')
- header = self.field('header')
- implementation = self.field('implementation')
- block = ''
+def property_accessors(property_type, name):
+ """Generate the property accessor functions."""
+ return (f' @Property({property_type})\n'
+ f' def {name}(self):\n'
+ f' return self._{name}\n\n'
+ f' @{name}.setter\n'
+ f' def {name}(self, value):\n'
+ f' self._{name} = value\n')
- if self.field('comment'):
- block += '/*\n'
- block += ' ' + header + '\n'
- block += '*/\n'
- block += '\n'
- if self.field('protect'):
- block += '#ifndef ' + macro_name + '\n'
- block += '#define ' + macro_name + '\n'
- block += '\n'
+def property_initialization(property_type, name):
+ """Generate the property initialization for __init__()."""
+ return f' self._{name} = {property_type}()\n'
- if self.field('includeBase'):
- block += '#include ' + base_include + '\n'
- block += '\n'
- block += 'class ' + class_name
- if base_class:
- block += ' : public ' + base_class
+def signal_initialization(signature):
+ """Generate the Signal initialization from the function signature."""
+ paren_pos = signature.find('(')
+ name = signature[:paren_pos]
+ parameters = signature[paren_pos:]
+ return f' {name} = Signal{parameters}\n'
- block += '\n'
- block += '{\n'
- if self.field('qobjectMacro'):
- block += ' Q_OBJECT\n'
- block += '\n'
+class ClassWizard(QWizard):
+ def __init__(self, parent=None):
+ super(ClassWizard, self).__init__(parent)
- block += 'public:\n'
+ self.addPage(IntroPage())
+ self._class_info_index = self.addPage(ClassInfoPage())
+ self._qobject_index = self.addPage(QObjectPage())
+ self._output_index = self.addPage(OutputFilesPage())
+ self.addPage(ConclusionPage())
- if self.field('qobjectCtor'):
- block += ' ' + class_name + '(QObject *parent = 0);\n'
- elif self.field('qwidgetCtor'):
- block += ' ' + class_name + '(QWidget *parent = 0);\n'
- elif self.field('defaultCtor'):
- block += ' ' + class_name + '();\n'
+ self.setPixmap(QWizard.BannerPixmap,
+ QPixmap(':/images/banner.png'))
+ self.setPixmap(QWizard.BackgroundPixmap,
+ QPixmap(':/images/background.png'))
- if self.field('copyCtor'):
- block += ' ' + class_name + '(const ' + class_name + ' &other);\n'
- block += '\n'
- block += ' ' + class_name + ' &operator=' + '(const ' + class_name + ' &other);\n'
+ self.setWindowTitle("Class Wizard")
- block += '};\n'
+ def nextId(self):
+ """Overrides QWizard.nextId() to insert the property/signal
+ page in case the class is a QObject."""
+ id = self.currentId()
+ if self.currentId() == self._class_info_index:
+ qobject = self.field('qobject')
+ return self._qobject_index if qobject else self._output_index
+ return super(ClassWizard, self).nextId()
+
+ def generate_code(self):
+ imports = [] # Classes to be imported
+ module_imports = {} # Module->class list
+
+ def add_import(class_str):
+ """Add a class to the import list or module hash depending on
+ whether it is 'class' or 'module.class'. Returns the
+ class name."""
+ dot = class_str.rfind('.')
+ if dot < 0:
+ imports.append(class_str)
+ return class_str
+ module = class_str[0:dot]
+ class_name = class_str[dot + 1:]
+ class_list = module_imports.get(module)
+ if class_list:
+ if class_name not in class_list:
+ class_list.append(class_name)
+ else:
+ module_imports[module] = [class_name]
+ return class_name
+
+ class_name = self.field('className')
+ qobject = self.field('qobject')
+ base_class = self.field('baseClass')
+ if base_class.startswith('<'): # <None>
+ base_class = ''
+ if qobject and not base_class:
+ base_class = 'PySide6.QtCore.QObject'
+
+ if base_class:
+ base_class = add_import(base_class)
+
+ signals = self.field('signals')
+ if signals:
+ add_import('PySide6.QtCore.Signal')
+
+ property_types = []
+ property_names = []
+ for p in self.field('properties'):
+ property_type, property_name = str(p).split(' ')
+ if property_type not in PYTHON_TYPES:
+ property_type = add_import(property_type)
+ property_types.append(property_type)
+ property_names.append(property_name)
+
+ if property_names:
+ add_import('PySide6.QtCore.Property')
+
+ signals = self.field('signals')
+ if signals:
+ add_import('PySide6.QtCore.Signal')
+
+ property_types = []
+ property_names = []
+ for p in self.field('properties'):
+ property_type, property_name = str(p).split(' ')
+ if property_type not in PYTHON_TYPES:
+ property_type = add_import(property_type)
+ property_types.append(property_type)
+ property_names.append(property_name)
+
+ if property_names:
+ add_import('PySide6.QtCore.Property')
+
+ # Generate imports
+ block = '# This Python file uses the following encoding: utf-8\n\n'
+ for module, class_list in module_imports.items():
+ class_list.sort()
+ class_list_str = ', '.join(class_list)
+ block += f'from {module} import {class_list_str}\n'
+ for klass in imports:
+ block += f'import {klass}\n'
+
+ # Generate class definition
+ block += f'\n\nclass {class_name}'
+ if base_class:
+ block += f'({base_class})'
+ block += ':\n'
+ description = self.field('description')
+ if description:
+ block += f' """{description}"""\n'
- if self.field('protect'):
+ if signals:
block += '\n'
- block += '#endif\n'
+ for s in signals:
+ block += signal_initialization(str(s))
- header_file = QtCore.QFile(output_dir + '/' + header)
+ # Generate __init__ function
+ block += '\n def __init__(self'
+ if qobject:
+ block += ', parent=None'
+ block += '):\n'
- if not header_file.open(QtCore.QFile.WriteOnly | QtCore.QFile.Text):
- name = header_file.fileName()
- reason = header_file.errorString()
- QtWidgets.QMessageBox.warning(None, "Class Wizard",
- f"Cannot write file {name}:\n{reason}")
- return
+ if base_class:
+ block += f' super({class_name}, self).__init__('
+ if qobject:
+ block += 'parent'
+ block += ')\n'
- header_file.write(QtCore.QByteArray(block.encode("utf-8")))
+ for i, name in enumerate(property_names):
+ block += property_initialization(property_types[i], name)
- block = ''
+ if not base_class and not property_names:
+ block += ' pass\n'
- if self.field('comment'):
- block += '/*\n'
- block += ' ' + implementation + '\n'
- block += '*/\n'
- block += '\n'
+ # Generate property accessors
+ for i, name in enumerate(property_names):
+ block += '\n' + property_accessors(property_types[i], name)
+
+ return block
- block += '#include "' + header + '"\n'
- block += '\n'
-
- if self.field('qobjectCtor'):
- block += class_name + '::' + class_name + '(QObject *parent)\n'
- block += ' : ' + base_class + '(parent)\n'
- block += '{\n'
- block += '}\n'
- elif self.field('qwidgetCtor'):
- block += class_name + '::' + class_name + '(QWidget *parent)\n'
- block += ' : ' + base_class + '(parent)\n'
- block += '{\n'
- block += '}\n'
- elif self.field('defaultCtor'):
- block += class_name + '::' + class_name + '()\n'
- block += '{\n'
- block += ' // missing code\n'
- block += '}\n'
-
- if self.field('copyCtor'):
- block += '\n'
- block += class_name + '::' + class_name + '(const ' + class_name + ' &other)\n'
- block += '{\n'
- block += ' *this = other;\n'
- block += '}\n'
- block += '\n'
- block += class_name + ' &' + class_name + '::operator=(const ' + class_name + ' &other)\n'
- block += '{\n'
-
- if base_class:
- block += ' ' + base_class + '::operator=(other);\n'
-
- block += ' // missing code\n'
- block += ' return *this;\n'
- block += '}\n'
-
- implementation_file = QtCore.QFile(output_dir + '/' + implementation)
-
- if not implementation_file.open(QtCore.QFile.WriteOnly | QtCore.QFile.Text):
- name = implementation_file.fileName()
- reason = implementation_file.errorString()
- QtWidgets.QMessageBox.warning(None, "Class Wizard",
- f"Cannot write file {name}:\n{reason}")
+ def accept(self):
+ file_name = self.field('file')
+ output_dir = self.field('outputDir')
+ python_file = Path(output_dir) / file_name
+ name = os.fspath(python_file)
+ try:
+ python_file.write_text(self.generate_code())
+ except (OSError, PermissionError) as e:
+ reason = str(e)
+ QMessageBox.warning(None, "Class Wizard",
+ f"Cannot write file {name}:\n{reason}")
return
- implementation_file.write(QtCore.QByteArray(block.encode("utf-8")))
+ if self.field('launch'):
+ url = QUrl.fromLocalFile(QDir.fromNativeSeparators(name))
+ QDesktopServices.openUrl(url)
super(ClassWizard, self).accept()
-class IntroPage(QtWidgets.QWizardPage):
+class IntroPage(QWizardPage):
def __init__(self, parent=None):
super(IntroPage, self).__init__(parent)
self.setTitle("Introduction")
- self.setPixmap(QtWidgets.QWizard.WatermarkPixmap,
- QtGui.QPixmap(':/images/watermark1.png'))
-
- label = QtWidgets.QLabel("This wizard will generate a skeleton C++ class "
- "definition, including a few functions. You simply need to "
- "specify the class name and set a few options to produce a "
- "header file and an implementation file for your new C++ "
- "class.")
+ self.setPixmap(QWizard.WatermarkPixmap,
+ QPixmap(':/images/watermark1.png'))
+
+ label = QLabel(INTRODUCTION)
label.setWordWrap(True)
- layout = QtWidgets.QVBoxLayout()
+ layout = QVBoxLayout(self)
layout.addWidget(label)
- self.setLayout(layout)
-class ClassInfoPage(QtWidgets.QWizardPage):
+class ClassInfoPage(QWizardPage):
def __init__(self, parent=None):
super(ClassInfoPage, self).__init__(parent)
self.setTitle("Class Information")
self.setSubTitle("Specify basic information about the class for "
- "which you want to generate skeleton source code files.")
- self.setPixmap(QtWidgets.QWizard.LogoPixmap,
- QtGui.QPixmap(':/images/logo1.png'))
+ "which you want to generate a skeleton source code file.")
+ self.setPixmap(QWizard.LogoPixmap,
+ QPixmap(':/images/logo1.png'))
- class_name_label = QtWidgets.QLabel("&Class name:")
- class_name_line_edit = QtWidgets.QLineEdit()
- class_name_label.setBuddy(class_name_line_edit)
+ class_name_line_edit = QLineEdit()
+ class_name_line_edit.setClearButtonEnabled(True)
- base_class_label = QtWidgets.QLabel("B&ase class:")
- base_class_line_edit = QtWidgets.QLineEdit()
- base_class_label.setBuddy(base_class_line_edit)
+ self._base_class_combo = QComboBox()
+ self._base_class_combo.addItems(BASE_CLASSES)
+ self._base_class_combo.setEditable(True)
- qobject_macro_check_box = QtWidgets.QCheckBox("Generate Q_OBJECT &macro")
+ base_class_line_edit = self._base_class_combo.lineEdit()
+ base_class_line_edit.setPlaceholderText('Module.Class')
+ self._base_class_combo.currentTextChanged.connect(self._base_class_changed)
- group_box = QtWidgets.QGroupBox("C&onstructor")
+ description_line_edit = QLineEdit()
+ description_line_edit.setClearButtonEnabled(True)
- qobject_ctor_radio_button = QtWidgets.QRadioButton("&QObject-style constructor")
- qwidget_ctor_radio_button = QtWidgets.QRadioButton("Q&Widget-style constructor")
- default_ctor_radio_button = QtWidgets.QRadioButton("&Default constructor")
- copy_ctor_check_box = QtWidgets.QCheckBox("&Generate copy constructor and operator=")
-
- default_ctor_radio_button.setChecked(True)
-
- default_ctor_radio_button.toggled.connect(copy_ctor_check_box.setEnabled)
+ self._qobject_check_box = QCheckBox("Inherits QObject")
self.registerField('className*', class_name_line_edit)
self.registerField('baseClass', base_class_line_edit)
- self.registerField('qobjectMacro', qobject_macro_check_box)
- self.registerField('qobjectCtor', qobject_ctor_radio_button)
- self.registerField('qwidgetCtor', qwidget_ctor_radio_button)
- self.registerField('defaultCtor', default_ctor_radio_button)
- self.registerField('copyCtor', copy_ctor_check_box)
-
- group_box_layout = QtWidgets.QVBoxLayout()
- group_box_layout.addWidget(qobject_ctor_radio_button)
- group_box_layout.addWidget(qwidget_ctor_radio_button)
- group_box_layout.addWidget(default_ctor_radio_button)
- group_box_layout.addWidget(copy_ctor_check_box)
- group_box.setLayout(group_box_layout)
-
- layout = QtWidgets.QGridLayout()
- layout.addWidget(class_name_label, 0, 0)
- layout.addWidget(class_name_line_edit, 0, 1)
- layout.addWidget(base_class_label, 1, 0)
- layout.addWidget(base_class_line_edit, 1, 1)
- layout.addWidget(qobject_macro_check_box, 2, 0, 1, 2)
- layout.addWidget(group_box, 3, 0, 1, 2)
- self.setLayout(layout)
-
-
-class CodeStylePage(QtWidgets.QWizardPage):
- def __init__(self, parent=None):
- super(CodeStylePage, self).__init__(parent)
-
- self.setTitle("Code Style Options")
- self.setSubTitle("Choose the formatting of the generated code.")
- self.setPixmap(QtWidgets.QWizard.LogoPixmap,
- QtGui.QPixmap(':/images/logo2.png'))
-
- comment_check_box = QtWidgets.QCheckBox("&Start generated files with a "
- "comment")
- comment_check_box.setChecked(True)
-
- protect_check_box = QtWidgets.QCheckBox("&Protect header file against "
- "multiple inclusions")
- protect_check_box.setChecked(True)
-
- macro_name_label = QtWidgets.QLabel("&Macro name:")
- self._macro_name_line_edit = QtWidgets.QLineEdit()
- macro_name_label.setBuddy(self._macro_name_line_edit)
-
- self._include_base_check_box = QtWidgets.QCheckBox("&Include base class "
- "definition")
- self._base_include_label = QtWidgets.QLabel("Base class include:")
- self._base_include_line_edit = QtWidgets.QLineEdit()
- self._base_include_label.setBuddy(self._base_include_line_edit)
-
- protect_check_box.toggled.connect(macro_name_label.setEnabled)
- protect_check_box.toggled.connect(self._macro_name_line_edit.setEnabled)
- self._include_base_check_box.toggled.connect(self._base_include_label.setEnabled)
- self._include_base_check_box.toggled.connect(self._base_include_line_edit.setEnabled)
-
- self.registerField('comment', comment_check_box)
- self.registerField('protect', protect_check_box)
- self.registerField('macroName', self._macro_name_line_edit)
- self.registerField('includeBase', self._include_base_check_box)
- self.registerField('baseInclude', self._base_include_line_edit)
-
- layout = QtWidgets.QGridLayout()
- layout.setColumnMinimumWidth(0, 20)
- layout.addWidget(comment_check_box, 0, 0, 1, 3)
- layout.addWidget(protect_check_box, 1, 0, 1, 3)
- layout.addWidget(macro_name_label, 2, 1)
- layout.addWidget(self._macro_name_line_edit, 2, 2)
- layout.addWidget(self._include_base_check_box, 3, 0, 1, 3)
- layout.addWidget(self._base_include_label, 4, 1)
- layout.addWidget(self._base_include_line_edit, 4, 2)
- self.setLayout(layout)
+ self.registerField('description', description_line_edit)
+ self.registerField('qobject', self._qobject_check_box)
- def initializePage(self):
- class_name = self.field('className')
- self._macro_name_line_edit.setText(class_name.upper() + "_H")
-
- base_class = self.field('baseClass')
- is_baseClass = bool(base_class)
+ layout = QFormLayout(self)
+ layout.addRow("&Class name:", class_name_line_edit)
+ layout.addRow("B&ase class:", self._base_class_combo)
+ layout.addRow("&Description:", description_line_edit)
+ layout.addRow(self._qobject_check_box)
- self._include_base_check_box.setChecked(is_baseClass)
- self._include_base_check_box.setEnabled(is_baseClass)
- self._base_include_label.setEnabled(is_baseClass)
- self._base_include_line_edit.setEnabled(is_baseClass)
+ @Slot(str)
+ def _base_class_changed(self, text):
+ is_qobject = text.startswith('PySide')
+ self._qobject_check_box.setChecked(is_qobject)
- if not is_baseClass:
- self._base_include_line_edit.clear()
- elif QtCore.QRegularExpression('^Q[A-Z].*$').match(base_class).hasMatch():
- self._base_include_line_edit.setText('<' + base_class + '>')
- else:
- self._base_include_line_edit.setText('"' + base_class.lower() + '.h"')
-
-class OutputFilesPage(QtWidgets.QWizardPage):
+class QObjectPage(QWizardPage):
+ """Allows for adding properties and signals to a QObject."""
+ def __init__(self, parent=None):
+ super(QObjectPage, self).__init__(parent)
+
+ self.setTitle("QObject parameters")
+ self.setSubTitle("Specify the signals, slots and properties.")
+ self.setPixmap(QWizard.LogoPixmap,
+ QPixmap(':/images/logo2.png'))
+ layout = QVBoxLayout(self)
+ self._properties_chooser = PropertyChooser()
+ self.registerField('properties', self._properties_chooser, 'items')
+ layout.addWidget(self._properties_chooser)
+ self._signals_chooser = SignalChooser()
+ self.registerField('signals', self._signals_chooser, 'items')
+ layout.addWidget(self._signals_chooser)
+
+
+class OutputFilesPage(QWizardPage):
def __init__(self, parent=None):
super(OutputFilesPage, self).__init__(parent)
self.setTitle("Output Files")
self.setSubTitle("Specify where you want the wizard to put the "
- "generated skeleton code.")
- self.setPixmap(QtWidgets.QWizard.LogoPixmap,
- QtGui.QPixmap(':/images/logo3.png'))
-
- output_dir_label = QtWidgets.QLabel("&Output directory:")
- self._output_dir_line_edit = QtWidgets.QLineEdit()
+ "generated skeleton code.")
+ self.setPixmap(QWizard.LogoPixmap,
+ QPixmap(':/images/logo3.png'))
+
+ output_dir_label = QLabel("&Output directory:")
+ output_dir_layout = QHBoxLayout()
+ self._output_dir_line_edit = QLineEdit()
+ output_dir_layout.addWidget(self._output_dir_line_edit)
output_dir_label.setBuddy(self._output_dir_line_edit)
+ output_dir_button = QToolButton()
+ output_dir_button.setText('...')
+ output_dir_button.clicked.connect(self._choose_output_dir)
+ output_dir_layout.addWidget(output_dir_button)
- header_label = QtWidgets.QLabel("&Header file name:")
- self._header_line_edit = QtWidgets.QLineEdit()
- header_label.setBuddy(self._header_line_edit)
-
- implementation_label = QtWidgets.QLabel("&Implementation file name:")
- self._implementation_line_edit = QtWidgets.QLineEdit()
- implementation_label.setBuddy(self._implementation_line_edit)
+ self._file_line_edit = QLineEdit()
self.registerField('outputDir*', self._output_dir_line_edit)
- self.registerField('header*', self._header_line_edit)
- self.registerField('implementation*', self._implementation_line_edit)
-
- layout = QtWidgets.QGridLayout()
- layout.addWidget(output_dir_label, 0, 0)
- layout.addWidget(self._output_dir_line_edit, 0, 1)
- layout.addWidget(header_label, 1, 0)
- layout.addWidget(self._header_line_edit, 1, 1)
- layout.addWidget(implementation_label, 2, 0)
- layout.addWidget(self._implementation_line_edit, 2, 1)
- self.setLayout(layout)
+ self.registerField('file*', self._file_line_edit)
+
+ layout = QFormLayout(self)
+ layout.addRow(output_dir_label, output_dir_layout)
+ layout.addRow("&File name:", self._file_line_edit)
def initializePage(self):
class_name = self.field('className')
- self._header_line_edit.setText(class_name.lower() + '.h')
- self._implementation_line_edit.setText(class_name.lower() + '.cpp')
- self._output_dir_line_edit.setText(QtCore.QDir.toNativeSeparators(QtCore.QDir.tempPath()))
+ self._file_line_edit.setText(class_name.lower() + '.py')
+ self.set_output_dir(QDir.tempPath())
+
+ def set_output_dir(self, dir):
+ self._output_dir_line_edit.setText(QDir.toNativeSeparators(dir))
+
+ def output_dir(self):
+ return QDir.fromNativeSeparators(self._output_dir_line_edit.text())
+ def file_name(self):
+ return self.output_dir() + '/' + self._file_line_edit.text()
-class ConclusionPage(QtWidgets.QWizardPage):
+ def _choose_output_dir(self):
+ dir = QFileDialog.getExistingDirectory(self, "Output Directory",
+ self.output_dir())
+ if dir:
+ self.set_output_dir(dir)
+
+ def validatePage(self):
+ """Ensure we do not overwrite existing files."""
+ name = self.file_name()
+ if QFileInfo.exists(name):
+ question = f'{name} already exists. Would you like to overwrite it?'
+ r = QMessageBox.question(self, 'File Exists', question)
+ if r != QMessageBox.Yes:
+ return False
+ return True
+
+
+class ConclusionPage(QWizardPage):
def __init__(self, parent=None):
super(ConclusionPage, self).__init__(parent)
self.setTitle("Conclusion")
- self.setPixmap(QtWidgets.QWizard.WatermarkPixmap,
- QtGui.QPixmap(':/images/watermark2.png'))
+ self.setPixmap(QWizard.WatermarkPixmap,
+ QPixmap(':/images/watermark2.png'))
- self.label = QtWidgets.QLabel()
+ self.label = QLabel()
self.label.setWordWrap(True)
- layout = QtWidgets.QVBoxLayout()
+ self._launch_check_box = QCheckBox("Launch")
+ self.registerField('launch', self._launch_check_box)
+
+ layout = QVBoxLayout(self)
layout.addWidget(self.label)
- self.setLayout(layout)
+ layout.addWidget(self._launch_check_box)
def initializePage(self):
- finish_text = self.wizard().buttonText(QtWidgets.QWizard.FinishButton)
- finish_text.replace('&', '')
+ finish_text = self.wizard().buttonText(QWizard.FinishButton)
+ finish_text = finish_text.replace('&', '')
self.label.setText(f"Click {finish_text} to generate the class skeleton.")
+ self._launch_check_box.setChecked(True)
if __name__ == '__main__':
-
- import sys
-
- app = QtWidgets.QApplication(sys.argv)
+ app = QApplication(sys.argv)
wizard = ClassWizard()
wizard.show()
sys.exit(app.exec_())
diff --git a/examples/widgets/dialogs/classwizard/classwizard.pyproject b/examples/widgets/dialogs/classwizard/classwizard.pyproject
index 1c1fe9998..6086099b8 100644
--- a/examples/widgets/dialogs/classwizard/classwizard.pyproject
+++ b/examples/widgets/dialogs/classwizard/classwizard.pyproject
@@ -1,4 +1,4 @@
{
"files": ["classwizard.qrc", "classwizard.py", "classwizard_rc.py",
- "classwizard_rc.pyc"]
+ "listchooser.py", "classwizard_rc.pyc"]
}
diff --git a/examples/widgets/dialogs/classwizard/listchooser.py b/examples/widgets/dialogs/classwizard/listchooser.py
new file mode 100644
index 000000000..8b6f0d020
--- /dev/null
+++ b/examples/widgets/dialogs/classwizard/listchooser.py
@@ -0,0 +1,212 @@
+#############################################################################
+##
+## Copyright (C) 2021 The Qt Company Ltd.
+## Contact: http://www.qt.io/licensing/
+##
+## This file is part of the Qt for Python examples of the Qt Toolkit.
+##
+## $QT_BEGIN_LICENSE:BSD$
+## You may use this file under the terms of the BSD license as follows:
+##
+## "Redistribution and use in source and binary forms, with or without
+## modification, are permitted provided that the following conditions are
+## met:
+## * Redistributions of source code must retain the above copyright
+## notice, this list of conditions and the following disclaimer.
+## * Redistributions in binary form must reproduce the above copyright
+## notice, this list of conditions and the following disclaimer in
+## the documentation and/or other materials provided with the
+## distribution.
+## * Neither the name of The Qt Company Ltd nor the names of its
+## contributors may be used to endorse or promote products derived
+## from this software without specific prior written permission.
+##
+##
+## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+##
+## $QT_END_LICENSE$
+##
+#############################################################################
+
+from PySide6.QtCore import (QCoreApplication, QDir, QRegularExpression, Qt,
+ Property, Slot)
+from PySide6.QtGui import QRegularExpressionValidator
+from PySide6.QtWidgets import (QComboBox, QDialog, QDialogButtonBox,
+ QFormLayout, QGroupBox, QHBoxLayout,
+ QInputDialog, QLineEdit, QListWidget,
+ QListWidgetItem, QPushButton, QVBoxLayout,
+ QWidget)
+
+
+DEFAULT_TYPES = ['int', 'str', 'PySide6.QtCore.QPoint', 'PySide6.QtCore.QRect',
+ 'PySide6.QtCore.QSize', 'PySide6.QtGui.QColor']
+
+
+FUNCTION_PATTERN = r'^\w+\([\w ,]*\)$'
+
+
+class ValidatingInputDialog(QDialog):
+ """A dialog for text input with a regular expression validation."""
+ def __init__(self, label, pattern, parent=None):
+ super(ValidatingInputDialog, self).__init__(parent)
+ layout = QVBoxLayout(self)
+
+ self._form_layout = QFormLayout()
+ self._lineedit = QLineEdit()
+ self._lineedit.setClearButtonEnabled(True)
+ re = QRegularExpression(pattern)
+ assert(re.isValid())
+ self._validator = QRegularExpressionValidator(re, self)
+ self._lineedit.setValidator(self._validator)
+ self._form_layout.addRow(label, self._lineedit)
+ layout.addLayout(self._form_layout)
+
+ bb = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
+ layout.addWidget(bb)
+ bb.rejected.connect(self.reject)
+ bb.accepted.connect(self.accept)
+
+ @Property(str)
+ def text(self):
+ return self._lineedit.text()
+
+ @text.setter
+ def text(self, t):
+ self._lineedit.setText(t)
+
+ @Property(str)
+ def placeholder_text(self):
+ return self._lineedit.placeholderText()
+
+ @placeholder_text.setter
+ def placeholder_text(self, t):
+ self._lineedit.setPlaceholderText(t)
+
+ @Property(int)
+ def cursor_position(self):
+ return self._lineedit.cursorPosition()
+
+ @cursor_position.setter
+ def cursor_position(self, p):
+ self._lineedit.setCursorPosition(p)
+
+ def is_valid(self):
+ return self.text
+
+ def accept(self):
+ if self.is_valid():
+ super(ValidatingInputDialog, self).accept()
+
+
+class FunctionSignatureDialog(ValidatingInputDialog):
+ """A dialog for input of function signatures."""
+ def __init__(self, name, parent=None):
+ super(FunctionSignatureDialog, self).__init__(name, FUNCTION_PATTERN,
+ parent)
+ self.text = '()'
+ self.cursor_position = 0
+
+
+class PropertyDialog(ValidatingInputDialog):
+ """A dialog for input of a property name and type."""
+ def __init__(self, parent=None):
+ super(PropertyDialog, self).__init__('&Name:', r'^\w+$', parent)
+ self.setWindowTitle('Add a Property')
+ self._type_combo = QComboBox()
+ self._type_combo.addItems(DEFAULT_TYPES)
+ self._form_layout.insertRow(0, '&Type:', self._type_combo)
+
+ def property_type(self):
+ return self._type_combo.currentText()
+
+
+class ListChooser(QGroupBox):
+ """A widget for editing a list of strings with a customization point
+ for creating the strings."""
+ def __init__(self, title, parent=None):
+ super(ListChooser, self).__init__(title, parent)
+ main_layout = QHBoxLayout(self)
+ self._list = QListWidget(self)
+ self._list.currentItemChanged.connect(self._current_item_changed)
+ main_layout.addWidget(self._list)
+
+ vbox_layout = QVBoxLayout()
+ main_layout.addLayout(vbox_layout)
+ self._addButton = QPushButton("Add...")
+ vbox_layout.addWidget(self._addButton)
+ self._addButton.clicked.connect(self._add)
+ self._removeButton = QPushButton("Remove")
+ self._removeButton.setEnabled(False)
+ self._removeButton.clicked.connect(self._remove_current)
+ vbox_layout.addWidget(self._removeButton)
+ vbox_layout.addStretch()
+
+ @Property(list)
+ def items(self):
+ result = []
+ for i in range(self._list.count()):
+ result.append(self._list.item(i).text())
+ return result
+
+ @items.setter
+ def items(self, item_list):
+ self._list.clear()
+ for i in item_list:
+ self._list.append(i)
+
+ @Slot(QListWidgetItem, QListWidgetItem)
+ def _current_item_changed(self, current, previous):
+ self._removeButton.setEnabled(current is not None)
+
+ @Slot()
+ def _add(self):
+ new_item = self._create_new_item()
+ if new_item:
+ self._list.addItem(new_item)
+
+ def _create_new_item(self):
+ """Overwrite to return a new item."""
+ return 'new_item'
+
+ @Slot()
+ def _remove_current(self):
+ row = self._list.row(self._list.currentItem())
+ if row >= 0:
+ self._list.takeItem(row)
+
+
+class SignalChooser(ListChooser):
+ """A widget for editing a list of signal function signatures."""
+ def __init__(self, parent=None):
+ super(SignalChooser, self).__init__('Signals', parent)
+
+ def _create_new_item(self):
+ dialog = FunctionSignatureDialog('&Signal signature:', self)
+ dialog.setWindowTitle('Enter Signal')
+ if dialog.exec_() != QDialog.Accepted:
+ return ''
+ return dialog.text
+
+
+class PropertyChooser(ListChooser):
+ """A widget for editing a list of properties as a string of 'type name'."""
+ def __init__(self, parent=None):
+ super(PropertyChooser, self).__init__('Properties', parent)
+
+ def _create_new_item(self):
+ dialog = PropertyDialog(self)
+ if dialog.exec_() != QDialog.Accepted:
+ return ''
+ name = dialog.text
+ property_type = dialog.property_type()
+ return f'{property_type} {name}'