// Copyright (C) 2021 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #include #include #include #include #include static QString pyStringToQString(PyObject *s) { const char *utf8 = _PepUnicode_AsString(s); return utf8 ? QString::fromUtf8(utf8) : QString(); } // Return a string from keyword argument dict static QString kwdString(PyObject *kwds, PyObject *key) { QString result; if (PyDict_Contains(kwds, key)) { if (auto value = PyDict_GetItem(kwds, key)) result = pyStringToQString(value); } return result; } // Return a bool from keyword argument dict static bool kwdBool(PyObject *kwds, PyObject *key) { bool result = false; if (PyDict_Contains(kwds, key)) { if (auto value = PyDict_GetItem(kwds, key)) result = PyObject_IsTrue(value); } return result; } // PyDesignerCustomWidget: A custom widget registered by type // (similar to what is done by QUiLoader.registerCustomWidget()). class PyDesignerCustomWidget : public QDesignerCustomWidgetInterface { public: explicit PyDesignerCustomWidget(PyObject *pyTypeObject) : m_pyTypeObject(pyTypeObject) {} QString name() const override; QString group() const override { return m_group; } QString toolTip() const override { return m_toolTip; } QString whatsThis() const override { return toolTip(); } QString includeFile() const override { return m_includeFile; } QIcon icon() const override { return m_icon; } bool isContainer() const override { return m_container; } QWidget *createWidget(QWidget *parent) override; bool isInitialized() const override { return m_core != nullptr; } void initialize(QDesignerFormEditorInterface *core) override; QString domXml() const override { return m_domXml; } void setGroup(const QString &group) { m_group = group; } void setToolTip(const QString &toolTip) { m_toolTip = toolTip; } void setIncludeFile(const QString &includeFile) { m_includeFile = includeFile; } void setIcon(const QIcon &icon) { m_icon = icon; } void setDomXml(const QString &domXml) { m_domXml = domXml; } void setContainer(bool container) { m_container = container; } private: const char *utf8Name() const; QDesignerFormEditorInterface *m_core = nullptr; QString m_group; QString m_toolTip; QString m_includeFile; QIcon m_icon; QString m_domXml; PyObject *m_pyTypeObject = nullptr; bool m_container = false; }; const char *PyDesignerCustomWidget::utf8Name() const { return reinterpret_cast(m_pyTypeObject)->tp_name; } QString PyDesignerCustomWidget::name() const { return QString::fromUtf8(utf8Name()); } QWidget *PyDesignerCustomWidget::createWidget(QWidget *parent) { // This is a copy of the similar function used for QUiLoader // (see sources/pyside6/plugins/uitools/customwidget.cpp) // Create a python instance and return cpp object PyObject *pyParent = nullptr; bool unknownParent = false; if (parent) { pyParent = reinterpret_cast(Shiboken::BindingManager::instance().retrieveWrapper(parent)); if (pyParent) { Py_INCREF(pyParent); } else { static Shiboken::Conversions::SpecificConverter converter("QWidget*"); pyParent = converter.toPython(&parent); unknownParent = true; } } else { Py_INCREF(Py_None); pyParent = Py_None; } Shiboken::AutoDecRef pyArgs(PyTuple_New(1)); PyTuple_SET_ITEM(pyArgs, 0, pyParent); // tuple will keep pyParent reference // Call python constructor auto result = reinterpret_cast(PyObject_CallObject(m_pyTypeObject, pyArgs)); if (!result) { qWarning("Unable to create a Python custom widget of type \"%s\".", utf8Name()); PyErr_Print(); return nullptr; } if (unknownParent) // if parent does not exist in python, transfer the ownership to cpp Shiboken::Object::releaseOwnership(result); else Shiboken::Object::setParent(pyParent, reinterpret_cast(result)); return reinterpret_cast(Shiboken::Object::cppPointer(result, Py_TYPE(result))); } void PyDesignerCustomWidget::initialize(QDesignerFormEditorInterface *core) { m_core = core; } // QPyDesignerCustomWidgetCollection: A QDesignerCustomWidgetCollectionInterface // implementation that is instantiated as a singleton and stored as a dynamic // property of QCoreApplication. The PySide Designer plugin retrieves it from // there. Provides static convenience functions for registering types // or adding QDesignerCustomWidgetInterface instances. static QPyDesignerCustomWidgetCollection *collectionInstance = nullptr; static const char propertyName[] = "__qt_PySideCustomWidgetCollection"; static void cleanup() { delete collectionInstance; collectionInstance = nullptr; } QPyDesignerCustomWidgetCollection::QPyDesignerCustomWidgetCollection() = default; QPyDesignerCustomWidgetCollection::~QPyDesignerCustomWidgetCollection() { qDeleteAll(m_customWidgets); } QList QPyDesignerCustomWidgetCollection::customWidgets() const { return m_customWidgets; } QPyDesignerCustomWidgetCollection *QPyDesignerCustomWidgetCollection::instance() { if (collectionInstance == nullptr) { collectionInstance = new QPyDesignerCustomWidgetCollection(); if (auto coreApp = QCoreApplication::instance()) { QDesignerCustomWidgetCollectionInterface *c = collectionInstance; coreApp->setProperty(propertyName, QVariant::fromValue(c)); qAddPostRoutine(cleanup); } else { qWarning("%s: Cannot find QCoreApplication instance.", Q_FUNC_INFO); } } return collectionInstance; } // Register a custom widget by type and optional keyword arguments providing // the parameters of QDesignerCustomWidgetInterface. bool QPyDesignerCustomWidgetCollection::_registerCustomWidgetHelper(PyObject *typeArg, PyObject *kwds) { if (!PyType_Check(typeArg)) { PyErr_SetString(PyExc_TypeError, "registerCustomWidget() requires a type argument."); return false; } auto pyCustomWidget = new PyDesignerCustomWidget(typeArg); static PyObject *xmlKey = Shiboken::String::createStaticString("xml"); pyCustomWidget->setDomXml(kwdString(kwds, xmlKey)); static PyObject *toolTipKey = Shiboken::String::createStaticString("tool_tip"); pyCustomWidget->setToolTip(kwdString(kwds, toolTipKey)); static PyObject *groupKey = Shiboken::String::createStaticString("group"); pyCustomWidget->setGroup(kwdString(kwds, groupKey)); static PyObject *moduleKey = Shiboken::String::createStaticString("module"); pyCustomWidget->setIncludeFile(kwdString(kwds, moduleKey)); static PyObject *containerKey = Shiboken::String::createStaticString("container"); pyCustomWidget->setContainer(kwdBool(kwds, containerKey)); static PyObject *iconKey = Shiboken::String::createStaticString("icon"); const QString iconPath = kwdString(kwds, iconKey); if (!iconPath.isEmpty()) { QIcon icon(iconPath); if (icon.availableSizes().isEmpty()) qWarning("%s: Cannot load icon from '%s'.", __FUNCTION__, qPrintable(iconPath)); else pyCustomWidget->setIcon(icon); } addCustomWidget(pyCustomWidget); return true; } void QPyDesignerCustomWidgetCollection::addCustomWidget(QDesignerCustomWidgetInterface *c) { instance()->m_customWidgets.append(c); }