diff options
author | Friedemann Kleint <Friedemann.Kleint@qt.io> | 2020-09-17 07:56:30 +0200 |
---|---|---|
committer | Friedemann Kleint <Friedemann.Kleint@qt.io> | 2020-09-17 08:22:55 +0200 |
commit | 4c5e405e691f707f2765b5f920c28213a36d22e5 (patch) | |
tree | 48000f220fc2c4365b276146e3d08a05c3a90312 /sources | |
parent | 53181fb95d3884cc067d005f37accd92d128bccc (diff) | |
parent | 38814354ff6a30258b79947304fd3a6be4dc7089 (diff) |
Merge remote-tracking branch 'origin/5.15' into dev
Change-Id: I8aa48d07067c45c888c73af87314f6a88c2a6e14
Diffstat (limited to 'sources')
31 files changed, 525 insertions, 120 deletions
diff --git a/sources/pyside2/PySide2/QtQml/pysideqmlregistertype.h b/sources/pyside2/PySide2/QtQml/pysideqmlregistertype.h index 8ea332f04..b147d9888 100644 --- a/sources/pyside2/PySide2/QtQml/pysideqmlregistertype.h +++ b/sources/pyside2/PySide2/QtQml/pysideqmlregistertype.h @@ -42,8 +42,6 @@ #include <sbkpython.h> -#include <atomic> - struct SbkObjectType; namespace PySide @@ -88,15 +86,6 @@ int qmlRegisterSingletonType(PyObject *pyObj,const char *uri, int versionMajor, } -// Volatile Bool Ptr type definition for QQmlIncubationController::incubateWhile(std::atomic<bool> *, int) - -using AtomicBool = std::atomic<bool>; - -typedef struct { - PyObject_HEAD - AtomicBool *flag; -} QtQml_VolatileBoolObject; - PyAPI_FUNC(PyTypeObject *) QtQml_VolatileBoolTypeF(void); #define VolatileBool_Check(op) (Py_TYPE(op) == QtQml_VolatileBoolTypeF()) diff --git a/sources/pyside2/PySide2/QtQml/typesystem_qml.xml b/sources/pyside2/PySide2/QtQml/typesystem_qml.xml index 0d463b991..b2def633f 100644 --- a/sources/pyside2/PySide2/QtQml/typesystem_qml.xml +++ b/sources/pyside2/PySide2/QtQml/typesystem_qml.xml @@ -44,10 +44,25 @@ <load-typesystem name="QtNetwork/typesystem_network.xml" generate="no"/> <load-typesystem name="QtGui/typesystem_gui.xml" generate="no"/> + <inject-code class="target" position="declaration"> + // Volatile Bool Ptr type definition for QQmlIncubationController::incubateWhile(std::atomic<bool> *, int) + #include <atomic> + + using AtomicBool = std::atomic<bool>; + + typedef struct { + PyObject_HEAD + AtomicBool *flag; + } QtQml_VolatileBoolObject; + </inject-code> + + <inject-code class="native" position="beginning"> + #include "pysideqmlregistertype.h" + </inject-code> + <!-- This is to inform the generator that the VolatileBool python type exists --> <custom-type name="VolatileBool"/> <primitive-type name="bool volatile" target-lang-api-name="VolatileBool"> - <include file-name="pysideqmlregistertype.h" location="local"/> <!-- No conversion rules are specified here, because the generator does not handle pointer to primitive types without function adjustment. See commit ff0b861b59b41387e771d9cd565e13de8b2750d1 or search for changePStr @@ -88,7 +103,6 @@ <enum-type identified-by-value="QML_HAS_ATTACHED_PROPERTIES"> <extra-includes> <include file-name="QtQml" location="global"/> - <include file-name="pysideqmlregistertype.h" location="local"/> </extra-includes> </enum-type> @@ -162,6 +176,9 @@ <enum-type name="Status"/> </object-type> <object-type name="QQmlIncubationController"> + <extra-includes> + <include file-name="pysideqmlregistertype.h" location="local"/> + </extra-includes> <modify-function signature="incubateWhile(std::atomic<bool>*,int)" allow-thread="yes"> <modify-argument index="1"> The replace type is needed to use the VolatileBool_Check macro instead of diff --git a/sources/pyside2/PySide2/glue/qtcore.cpp b/sources/pyside2/PySide2/glue/qtcore.cpp index d35a73289..45e0e8c25 100644 --- a/sources/pyside2/PySide2/glue/qtcore.cpp +++ b/sources/pyside2/PySide2/glue/qtcore.cpp @@ -357,9 +357,15 @@ static bool getReceiver(QObject *source, const char *signal, PyObject *callback, usingGlobalReceiver = true; } + const auto receiverThread = *receiver ? (*receiver)->thread() : nullptr; + if (usingGlobalReceiver) { PySide::SignalManager &signalManager = PySide::SignalManager::instance(); *receiver = signalManager.globalReceiver(source, callback); + // PYSIDE-1354: Move the global receiver to the original receivers's thread + // so that autoconnections work correctly. + if (receiverThread && receiverThread != (*receiver)->thread()) + (*receiver)->moveToThread(receiverThread); *callbackSig = PySide::Signal::getCallbackSignature(signal, *receiver, callback, usingGlobalReceiver).toLatin1(); } diff --git a/sources/pyside2/doc/tutorials/expenses/main.py b/sources/pyside2/doc/tutorials/expenses/main.py index d927f126c..5dd632301 100644 --- a/sources/pyside2/doc/tutorials/expenses/main.py +++ b/sources/pyside2/doc/tutorials/expenses/main.py @@ -1,6 +1,6 @@ ############################################################################# ## -## Copyright (C) 2019 The Qt Company Ltd. +## Copyright (C) 2020 The Qt Company Ltd. ## Contact: http://www.qt.io/licensing/ ## ## This file is part of the Qt for Python examples of the Qt Toolkit. diff --git a/sources/pyside2/doc/tutorials/expenses/main_snake_case.py b/sources/pyside2/doc/tutorials/expenses/main_snake_prop.py index 154396b41..4421980c5 100644 --- a/sources/pyside2/doc/tutorials/expenses/main_snake_case.py +++ b/sources/pyside2/doc/tutorials/expenses/main_snake_prop.py @@ -39,14 +39,14 @@ ############################################################################# import sys -from PySide2.QtCore import Qt, Slot +from PySide2.QtCore import Qt, Slot, QSize from PySide2.QtGui import QPainter from PySide2.QtWidgets import (QAction, QApplication, QHeaderView, QHBoxLayout, QLabel, QLineEdit, QMainWindow, QPushButton, QTableWidget, QTableWidgetItem, QVBoxLayout, QWidget) from PySide2.QtCharts import QtCharts -from __feature__ import snake_case +from __feature__ import snake_case, true_property class Widget(QWidget): @@ -61,13 +61,13 @@ class Widget(QWidget): # Left self.table = QTableWidget() - self.table.set_column_count(2) - self.table.set_horizontal_header_labels(["Description", "Price"]) - self.table.horizontal_header().set_section_resize_mode(QHeaderView.Stretch) + self.table.column_count = 2 + self.table.horizontal_header_labels = ["Description", "Price"] + self.table.horizontal_header().section_resize_mode = QHeaderView.Stretch # Chart self.chart_view = QtCharts.QChartView() - self.chart_view.set_render_hint(QPainter.Antialiasing) + self.chart_view.render_hint = QPainter.Antialiasing # Right self.description = QLineEdit() @@ -78,10 +78,10 @@ class Widget(QWidget): self.plot = QPushButton("Plot") # Disabling 'Add' button - self.add.setEnabled(False) + self.add.enabled = False self.right = QVBoxLayout() - self.right.set_margin(10) + self.right.margin = 10 self.right.add_widget(QLabel("Description")) self.right.add_widget(self.description) self.right.add_widget(QLabel("Price")) @@ -115,41 +115,41 @@ class Widget(QWidget): @Slot() def add_element(self): - des = self.description.text() - price = self.price.text() + des = self.description.text + price = self.price.text self.table.insert_row(self.items) description_item = QTableWidgetItem(des) price_item = QTableWidgetItem("{:.2f}".format(float(price))) - price_item.set_text_alignment(Qt.AlignRight) + price_item.text_alignment = Qt.AlignRight self.table.set_item(self.items, 0, description_item) self.table.set_item(self.items, 1, price_item) - self.description.set_text("") - self.price.set_text("") + self.description.text = "" + self.price.text = "" self.items += 1 @Slot() def check_disable(self, s): - if not self.description.text() or not self.price.text(): - self.add.set_enabled(False) + if not self.description.text or not self.price.text: + self.add.enabled = False else: - self.add.set_enabled(True) + self.add.enabled = True @Slot() def plot_data(self): # Get table information series = QtCharts.QPieSeries() - for i in range(self.table.row_count()): + for i in range(self.table.row_count): text = self.table.item(i, 0).text() number = float(self.table.item(i, 1).text()) series.append(text, number) chart = QtCharts.QChart() chart.add_series(series) - chart.legend().set_alignment(Qt.AlignLeft) + chart.legend().alignment = Qt.AlignLeft self.chart_view.set_chart(chart) @Slot() @@ -161,7 +161,7 @@ class Widget(QWidget): for desc, price in data.items(): description_item = QTableWidgetItem(desc) price_item = QTableWidgetItem("{:.2f}".format(price)) - price_item.set_text_alignment(Qt.AlignRight) + price_item.text_alignment = Qt.AlignRight self.table.insert_row(self.items) self.table.set_item(self.items, 0, description_item) self.table.set_item(self.items, 1, price_item) @@ -169,14 +169,14 @@ class Widget(QWidget): @Slot() def clear_table(self): - self.table.set_row_count(0) + self.table.row_count = 0 self.items = 0 class MainWindow(QMainWindow): def __init__(self, widget): QMainWindow.__init__(self) - self.setWindowTitle("Tutorial") + self.window_title = "Tutorial" # Menu self.menu = self.menu_bar() @@ -184,7 +184,7 @@ class MainWindow(QMainWindow): # Exit QAction exit_action = QAction("Exit", self) - exit_action.set_shortcut("Ctrl+Q") + exit_action.shortcut = "Ctrl+Q" exit_action.triggered.connect(self.exit_app) self.file_menu.add_action(exit_action) @@ -202,7 +202,7 @@ if __name__ == "__main__": widget = Widget() # QMainWindow using QWidget as central widget window = MainWindow(widget) - window.resize(800, 600) + window.size = QSize(800, 600) window.show() # Execute application diff --git a/sources/pyside2/libpyside/feature_select.cpp b/sources/pyside2/libpyside/feature_select.cpp index d3beeef7a..a1ba76251 100644 --- a/sources/pyside2/libpyside/feature_select.cpp +++ b/sources/pyside2/libpyside/feature_select.cpp @@ -39,6 +39,7 @@ #include "feature_select.h" #include "pyside.h" +#include "pysidestaticstrings.h" #include <shiboken.h> #include <sbkstaticstrings.h> @@ -125,7 +126,7 @@ namespace PySide { namespace Feature { using namespace Shiboken; -typedef bool(*FeatureProc)(PyTypeObject *type, PyObject *prev_dict); +typedef bool(*FeatureProc)(PyTypeObject *type, PyObject *prev_dict, int id); static FeatureProc *featurePointer = nullptr; @@ -226,7 +227,11 @@ static inline void setCurrentSelectId(PyTypeObject *type, int id) static inline PyObject *getCurrentSelectId(PyTypeObject *type) { - return fast_id_array[SbkObjectType_GetReserved(type)]; + int id = SbkObjectType_GetReserved(type); + // This can be too early. + if (id < 0) + id = 0; + return fast_id_array[id]; } static bool replaceClassDict(PyTypeObject *type) @@ -331,7 +336,7 @@ static bool createNewFeatureSet(PyTypeObject *type, PyObject *select_id) // clear the tp_dict that will get new content PyDict_Clear(type->tp_dict); // let the proc re-fill the tp_dict - if (!(*proc)(type, prev_dict)) + if (!(*proc)(type, prev_dict, id)) return false; // if there is still a step, prepare `prev_dict` if (idx >> 1) { @@ -413,18 +418,18 @@ void Select(PyObject *obj) type->tp_dict = SelectFeatureSet(type); } -static bool feature_01_addLowerNames(PyTypeObject *type, PyObject *prev_dict); -static bool feature_02_addDummyNames(PyTypeObject *type, PyObject *prev_dict); -static bool feature_04_addDummyNames(PyTypeObject *type, PyObject *prev_dict); -static bool feature_08_addDummyNames(PyTypeObject *type, PyObject *prev_dict); -static bool feature_10_addDummyNames(PyTypeObject *type, PyObject *prev_dict); -static bool feature_20_addDummyNames(PyTypeObject *type, PyObject *prev_dict); -static bool feature_40_addDummyNames(PyTypeObject *type, PyObject *prev_dict); -static bool feature_80_addDummyNames(PyTypeObject *type, PyObject *prev_dict); +static bool feature_01_addLowerNames(PyTypeObject *type, PyObject *prev_dict, int id); +static bool feature_02_true_property(PyTypeObject *type, PyObject *prev_dict, int id); +static bool feature_04_addDummyNames(PyTypeObject *type, PyObject *prev_dict, int id); +static bool feature_08_addDummyNames(PyTypeObject *type, PyObject *prev_dict, int id); +static bool feature_10_addDummyNames(PyTypeObject *type, PyObject *prev_dict, int id); +static bool feature_20_addDummyNames(PyTypeObject *type, PyObject *prev_dict, int id); +static bool feature_40_addDummyNames(PyTypeObject *type, PyObject *prev_dict, int id); +static bool feature_80_addDummyNames(PyTypeObject *type, PyObject *prev_dict, int id); static FeatureProc featureProcArray[] = { feature_01_addLowerNames, - feature_02_addDummyNames, + feature_02_true_property, feature_04_addDummyNames, feature_08_addDummyNames, feature_10_addDummyNames, @@ -471,8 +476,8 @@ void init() // static PyObject *methodWithNewName(PyTypeObject *type, - PyMethodDef *meth, - const char *new_name) + PyMethodDef *meth, + const char *new_name) { /* * Create a method with a lower case name. @@ -499,7 +504,7 @@ static PyObject *methodWithNewName(PyTypeObject *type, return descr; } -static bool feature_01_addLowerNames(PyTypeObject *type, PyObject *prev_dict) +static bool feature_01_addLowerNames(PyTypeObject *type, PyObject *prev_dict, int id) { /* * Add objects with lower names to `type->tp_dict` from 'prev_dict`. @@ -520,6 +525,9 @@ static bool feature_01_addLowerNames(PyTypeObject *type, PyObject *prev_dict) // Then we walk over the tp_methods to get all methods and insert // them with changed names. PyMethodDef *meth = type->tp_methods; + if (!meth) + return true; + for (; meth != nullptr && meth->ml_name != nullptr; ++meth) { const char *name = String::toCString(String::getSnakeCaseName(meth->ml_name, true)); AutoDecRef new_method(methodWithNewName(type, meth, name)); @@ -535,22 +543,140 @@ static bool feature_01_addLowerNames(PyTypeObject *type, PyObject *prev_dict) // // PYSIDE-1019: Support switchable extensions // -// Feature 0x02..0x80: A fake switchable option for testing +// Feature 0x02: Use true properties instead of getters and setters +// + +static PyObject *createProperty(PyObject *getter, PyObject *setter, PyObject *doc) +{ + assert(getter != nullptr); + if (setter == nullptr) + setter = Py_None; + PyObject *deleter = Py_None; + PyObject *prop = PyObject_CallObject(reinterpret_cast<PyObject *>(&PyProperty_Type), nullptr); + AutoDecRef args(Py_BuildValue("OOOO", getter, setter, deleter, doc)); + PyProperty_Type.tp_init(prop, args, nullptr); + return prop; +} + +static PyObject *calcPropDocString(PyTypeObject *type, PyObject *getterName, PyObject *setterName) +{ + // To calculate the docstring, we need the __doc__ attribute of the original + // getter and setter. We temporatily switch back to no features. This + // might change when we have full signature support for features. + auto hold = type->tp_dict; + moveToFeatureSet(type, fast_id_array[0]); + auto dict = type->tp_dict; + auto getter = PyDict_GetItem(dict, getterName); + auto setter = setterName ? PyDict_GetItem(dict, setterName) : nullptr; + PyObject *buf = PyObject_GetAttr(getter, PyMagicName::doc()); + type->tp_dict = hold; + + if (setter == nullptr) + return buf; + AutoDecRef nl(Py_BuildValue("s", "\n")); + AutoDecRef wdoc(PyObject_GetAttr(setter, PyMagicName::doc())); + String::concat(&buf, nl); + String::concat(&buf, wdoc); + return buf; +} + +static QStringList parseFields(const char *propstr) +{ + /* + * Break the string into subfields at ':' and add defaults. + */ + QString s = QString(QLatin1String(propstr)); + auto list = s.split(QLatin1Char(':')); + assert(list.size() == 2 || list.size() == 3); + auto name = list[0]; + auto read = list[1]; + if (read.size() == 0) + list[1] = name; + if (list.size() == 2) + return list; + auto write = list[2]; + if (write.size() == 0) { + list[2] = QLatin1String("set") + name; + list[2][3] = list[2][3].toUpper(); + } + return list; +} + +static PyObject *make_snake_case(QString s, bool lower) +{ + if (s.isNull()) + return nullptr; + return String::getSnakeCaseName(s.toLatin1().data(), lower); +} + +static bool feature_02_true_property(PyTypeObject *type, PyObject *prev_dict, int id) +{ + /* + * Use the property info to create true Python property objects. + */ + + // The empty `tp_dict` gets populated by the previous dict. + PyObject *prop_dict = type->tp_dict; + if (PyDict_Update(prop_dict, prev_dict) < 0) + return false; + + // We then replace methods by properties. + bool lower = (id & 0x01) != 0; + auto props = SbkObjectType_GetPropertyStrings(type); + if (props == nullptr || *props == nullptr) + return true; + for (; *props != nullptr; ++props) { + auto propstr = *props; + auto fields = parseFields(propstr); + bool haveWrite = fields.size() == 3; + PyObject *name = make_snake_case(fields[0], lower); + PyObject *read = make_snake_case(fields[1], lower); + PyObject *write = haveWrite ? make_snake_case(fields[2], lower) : nullptr; + PyObject *getter = PyDict_GetItem(prev_dict, read); + if (getter == nullptr || Py_TYPE(getter) != PepMethodDescr_TypePtr) + continue; + PyObject *setter = haveWrite ? PyDict_GetItem(prev_dict, write) : nullptr; + if (setter != nullptr && Py_TYPE(setter) != PepMethodDescr_TypePtr) + continue; + + PyObject *doc_read = make_snake_case(fields[1], false); + PyObject *doc_write(haveWrite ? make_snake_case(fields[2], false) : nullptr); + AutoDecRef doc(calcPropDocString(type, doc_read, doc_write)); + AutoDecRef PyProperty(createProperty(getter, setter, doc)); + if (PyProperty.isNull()) + return false; + if (PyDict_SetItem(prop_dict, name, PyProperty) < 0) + return false; + if (fields[0] != fields[1] && PyDict_GetItem(prop_dict, read)) + if (PyDict_DelItem(prop_dict, read) < 0) + return false; + // Theoretically, we need to check for multiple signatures to be exact. + // But we don't do so intentionally because it would be confusing. + if (haveWrite && PyDict_GetItem(prop_dict, write)) + if (PyDict_DelItem(prop_dict, write) < 0) + return false; + } + return true; +} + +////////////////////////////////////////////////////////////////////////////// +// +// PYSIDE-1019: Support switchable extensions +// +// Feature 0x04..0x40: A fake switchable option for testing // #define SIMILAR_FEATURE(xx) \ -static bool feature_##xx##_addDummyNames(PyTypeObject *type, PyObject *prev_dict) \ +static bool feature_##xx##_addDummyNames(PyTypeObject *type, PyObject *prev_dict, int id) \ { \ PyObject *dict = type->tp_dict; \ if (PyDict_Update(dict, prev_dict) < 0) \ return false; \ - Py_INCREF(Py_None); \ if (PyDict_SetItemString(dict, "fake_feature_" #xx, Py_None) < 0) \ return false; \ return true; \ } -SIMILAR_FEATURE(02) SIMILAR_FEATURE(04) SIMILAR_FEATURE(08) SIMILAR_FEATURE(10) diff --git a/sources/pyside2/libpyside/pyside.cpp b/sources/pyside2/libpyside/pyside.cpp index 7c8f99b93..2404788d3 100644 --- a/sources/pyside2/libpyside/pyside.cpp +++ b/sources/pyside2/libpyside/pyside.cpp @@ -584,16 +584,22 @@ bool registerInternalQtConf() #ifdef PYSIDE_QT_CONF_PREFIX setupPrefix = QStringLiteral(PYSIDE_QT_CONF_PREFIX); #endif - QString prefixPath = pysideDir.absoluteFilePath(setupPrefix); + const QString prefixPathStr = pysideDir.absoluteFilePath(setupPrefix); +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + const QByteArray prefixPath = prefixPathStr.toLocal8Bit(); +#else + // PYSIDE-972, QSettings used by QtCore uses Latin1 + const QByteArray prefixPath = prefixPathStr.toLatin1(); +#endif // rccData needs to be static, otherwise when it goes out of scope, the Qt resource system // will point to invalid memory. - static QByteArray rccData = QByteArray("[Paths]\nPrefix = ") + prefixPath.toLocal8Bit() + static QByteArray rccData = QByteArrayLiteral("[Paths]\nPrefix = ") + prefixPath #ifdef Q_OS_WIN // LibraryExecutables needs to point to Prefix instead of ./bin because we don't // currently conform to the Qt default directory layout on Windows. This is necessary // for QtWebEngineCore to find the location of QtWebEngineProcess.exe. - + QByteArray("\nLibraryExecutables = ") + prefixPath.toLocal8Bit() + + QByteArray("\nLibraryExecutables = ") + prefixPath #endif ; rccData.append('\n'); diff --git a/sources/pyside2/libpyside/pysidestaticstrings.cpp b/sources/pyside2/libpyside/pysidestaticstrings.cpp index 82e233621..760d77632 100644 --- a/sources/pyside2/libpyside/pysidestaticstrings.cpp +++ b/sources/pyside2/libpyside/pysidestaticstrings.cpp @@ -55,5 +55,9 @@ STATIC_STRING_IMPL(qtStaticMetaObject, "staticMetaObject") STATIC_STRING_IMPL(qtConnect, "connect") STATIC_STRING_IMPL(qtDisconnect, "disconnect") STATIC_STRING_IMPL(qtEmit, "emit") +STATIC_STRING_IMPL(dict_ring, "dict_ring") +STATIC_STRING_IMPL(name, "name") +STATIC_STRING_IMPL(property, "property") +STATIC_STRING_IMPL(select_id, "select_id") } // namespace PyName } // namespace PySide diff --git a/sources/pyside2/libpyside/pysidestaticstrings.h b/sources/pyside2/libpyside/pysidestaticstrings.h index 1d5700c51..1222d8f47 100644 --- a/sources/pyside2/libpyside/pysidestaticstrings.h +++ b/sources/pyside2/libpyside/pysidestaticstrings.h @@ -50,6 +50,10 @@ PyObject *qtStaticMetaObject(); PyObject *qtConnect(); PyObject *qtDisconnect(); PyObject *qtEmit(); +PyObject *dict_ring(); +PyObject *name(); +PyObject *property(); +PyObject *select_id(); } // namespace PyName } // namespace PySide diff --git a/sources/pyside2/tests/QtCore/CMakeLists.txt b/sources/pyside2/tests/QtCore/CMakeLists.txt index 98bfb9334..ee87345db 100644 --- a/sources/pyside2/tests/QtCore/CMakeLists.txt +++ b/sources/pyside2/tests/QtCore/CMakeLists.txt @@ -121,7 +121,7 @@ PYSIDE_TEST(quuid_test.py) PYSIDE_TEST(qversionnumber_test.py) PYSIDE_TEST(repr_test.py) PYSIDE_TEST(setprop_on_ctor_test.py) -PYSIDE_TEST(snake_case_feature_test.py) +PYSIDE_TEST(snake_prop_feature_test.py) PYSIDE_TEST(staticMetaObject_test.py) PYSIDE_TEST(static_method_test.py) PYSIDE_TEST(thread_signals_test.py) diff --git a/sources/pyside2/tests/QtCore/multiple_feature_test.py b/sources/pyside2/tests/QtCore/multiple_feature_test.py index 351090382..329e513fb 100644 --- a/sources/pyside2/tests/QtCore/multiple_feature_test.py +++ b/sources/pyside2/tests/QtCore/multiple_feature_test.py @@ -48,8 +48,7 @@ from init_paths import init_test_paths init_test_paths(False) from PySide2 import QtCore -from PySide2.support.__feature__ import ( - _really_all_feature_names, pyside_feature_dict) +from PySide2.support import __feature__ from textwrap import dedent """ @@ -62,6 +61,8 @@ The first feature is `snake_case` instead of `camelCase`. There is much more to come. """ +MethodDescriptorType = type(str.split) + class FeaturesTest(unittest.TestCase): def testAllFeatureCombinations(self): @@ -69,7 +70,7 @@ class FeaturesTest(unittest.TestCase): Test for all 256 possible combinations of `__feature__` imports. """ - def tst_bit0(flag, self): + def tst_bit0(flag, self, bits): if flag == 0: QtCore.QCborArray.isEmpty QtCore.QCborArray.__dict__["isEmpty"] @@ -85,13 +86,25 @@ class FeaturesTest(unittest.TestCase): with self.assertRaises(KeyError): QtCore.QCborArray.__dict__["isEmpty"] + def tst_bit1(flag, self, bits): + getter_name = "object_name" if bits & 1 else "objectName" + setter_name = "set_object_name" if bits & 1 else "setObjectName" + thing = getattr(QtCore.QObject, getter_name) + if flag: + self.assertEqual(type(thing), property) + with self.assertRaises(AttributeError): + getattr(QtCore.QObject, setter_name) + else: + self.assertEqual(type(thing), MethodDescriptorType) + getattr(QtCore.QObject, setter_name) + edict = {} - for bit in range(1, 8): + for bit in range(2, 8): # We are cheating here, since the functions are in the globals. eval(compile(dedent(""" - def tst_bit{0}(flag, self): + def tst_bit{0}(flag, self, bits): if flag == 0: with self.assertRaises(AttributeError): QtCore.QCborArray.fake_feature_{1:02x} @@ -103,12 +116,12 @@ class FeaturesTest(unittest.TestCase): """).format(bit, 1 << bit), "<string>", "exec"), globals(), edict) globals().update(edict) - feature_list = _really_all_feature_names + feature_list = __feature__._really_all_feature_names func_list = [tst_bit0, tst_bit1, tst_bit2, tst_bit3, tst_bit4, tst_bit5, tst_bit6, tst_bit7] for idx in range(0x100): - pyside_feature_dict.clear() + __feature__.set_selection(0) config = "feature_{:02x}".format(idx) print() print("--- Feature Test Config `{}` ---".format(config)) @@ -121,7 +134,7 @@ class FeaturesTest(unittest.TestCase): eval(compile(text, "<string>", "exec"), globals(), edict) for bit in range(8): value = idx & 1 << bit - func_list[bit](value, self=self) + func_list[bit](value, self=self, bits=idx) if __name__ == '__main__': diff --git a/sources/pyside2/tests/QtCore/snake_case_feature_test.py b/sources/pyside2/tests/QtCore/snake_prop_feature_test.py index b7f23396e..779b8a408 100644 --- a/sources/pyside2/tests/QtCore/snake_case_feature_test.py +++ b/sources/pyside2/tests/QtCore/snake_prop_feature_test.py @@ -46,41 +46,61 @@ from init_paths import init_test_paths init_test_paths(False) from PySide2 import QtWidgets +from PySide2.support import __feature__ """ -snake_case_feature_test.py +snake_prop_feature_test.py -------------------------- -Test the snake_case feature. +Test the snake_case and true_property feature. This works now. More tests needed! """ -class RenamingTest(unittest.TestCase): +class Window(QtWidgets.QWidget): + def __init__(self): + super(Window, self).__init__() + + +class FeatureTest(unittest.TestCase): def setUp(self): qApp or QtWidgets.QApplication() + __feature__.set_selection(0) def tearDown(self): qApp.shutdown() def testRenamedFunctions(self): - - class Window(QtWidgets.QWidget): - def __init__(self): - super(Window, self).__init__() - window = Window() window.setWindowTitle('camelCase') # and now the same with snake_case enabled from __feature__ import snake_case - class Window(QtWidgets.QWidget): - def __init__(self): - super(Window, self).__init__() + # Works with the same window! window = Window() + window.set_window_title('snake_case') + def testPropertyAppearVanish(self): window = Window() - window.set_window_title('snake_case') + + self.assertTrue(callable(window.isModal)) + with self.assertRaises(AttributeError): + window.modal + + from __feature__ import snake_case, true_property + + self.assertTrue(isinstance(QtWidgets.QWidget.modal, property)) + self.assertTrue(isinstance(window.modal, bool)) + with self.assertRaises(AttributeError): + window.isModal + + # switching back + __feature__.set_selection(0) + + self.assertTrue(callable(window.isModal)) + with self.assertRaises(AttributeError): + window.modal + if __name__ == '__main__': unittest.main() diff --git a/sources/pyside2/tests/signals/CMakeLists.txt b/sources/pyside2/tests/signals/CMakeLists.txt index 740319f39..14936869f 100644 --- a/sources/pyside2/tests/signals/CMakeLists.txt +++ b/sources/pyside2/tests/signals/CMakeLists.txt @@ -26,6 +26,7 @@ PYSIDE_TEST(segfault_proxyparent_test.py) PYSIDE_TEST(self_connect_test.py) PYSIDE_TEST(short_circuit_test.py) PYSIDE_TEST(signal2signal_connect_test.py) +PYSIDE_TEST(signal_across_threads.py) PYSIDE_TEST(signal_autoconnect_test.py) PYSIDE_TEST(signal_connectiontype_support_test.py) PYSIDE_TEST(signal_enum_test.py) diff --git a/sources/pyside2/tests/signals/signal_across_threads.py b/sources/pyside2/tests/signals/signal_across_threads.py new file mode 100644 index 000000000..907f059a1 --- /dev/null +++ b/sources/pyside2/tests/signals/signal_across_threads.py @@ -0,0 +1,106 @@ +############################################################################# +## +## Copyright (C) 2020 The Qt Company Ltd. +## Contact: https://www.qt.io/licensing/ +## +## This file is part of the test suite of Qt for Python. +## +## $QT_BEGIN_LICENSE:GPL-EXCEPT$ +## Commercial License Usage +## Licensees holding valid commercial Qt licenses may use this file in +## accordance with the commercial license agreement provided with the +## Software or, alternatively, in accordance with the terms contained in +## a written agreement between you and The Qt Company. For licensing terms +## and conditions see https://www.qt.io/terms-conditions. For further +## information use the contact form at https://www.qt.io/contact-us. +## +## GNU General Public License Usage +## Alternatively, this file may be used under the terms of the GNU +## General Public License version 3 as published by the Free Software +## Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +## included in the packaging of this file. Please review the following +## information to ensure the GNU General Public License requirements will +## be met: https://www.gnu.org/licenses/gpl-3.0.html. +## +## $QT_END_LICENSE$ +## +############################################################################# + +'''Test case for PYSIDE-1354: Ensure that slots are invoked from the receiver's +thread context when using derived classes (and thus, a global receiver).''' + +import os +import sys +import unittest + +sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) +from init_paths import init_test_paths +init_test_paths(False) + +from PySide2.QtCore import QObject, QThread, QTimer, Slot +from helper.usesqcoreapplication import UsesQCoreApplication + + +class ReceiverBase(QObject): + def __init__(self, parent=None): + super(ReceiverBase, self).__init__(parent) + self.senderThread = None + + @Slot() + def slot_function(self): + self.senderThread = QThread.currentThread() + + +class Receiver(ReceiverBase): + pass + + +class TestThread(QThread): + def __init__(self, parent=None): + super(TestThread, self).__init__(parent) + + def run(self): + pass + + +class SignalAcrossThreads(UsesQCoreApplication): + def setUp(self): + UsesQCoreApplication.setUp(self) + self._timer_tick = 0 + self._timer = QTimer() + self._timer.setInterval(20) + self._timer.timeout.connect(self._control_test) + self._worker_thread = TestThread() + + def tearDown(self): + UsesQCoreApplication.tearDown(self) + + @Slot() + def _control_test(self): + if self._timer_tick == 0: + self._worker_thread.start() + elif self._timer_tick == 1: + self._worker_thread.wait() + else: + self._timer.stop() + self.app.quit() + self._timer_tick += 1 + + def test(self): + worker_thread_receiver = Receiver() + worker_thread_receiver.moveToThread(self._worker_thread) + self._worker_thread.started.connect(worker_thread_receiver.slot_function) + + main_thread = QThread.currentThread() + main_thread_receiver = Receiver() + self._worker_thread.started.connect(main_thread_receiver.slot_function) + + self._timer.start() + self.app.exec_() + + self.assertEqual(worker_thread_receiver.senderThread, self._worker_thread) + self.assertEqual(main_thread_receiver.senderThread, main_thread) + + +if __name__ == '__main__': + unittest.main() diff --git a/sources/shiboken2/ApiExtractor/abstractmetalang.cpp b/sources/shiboken2/ApiExtractor/abstractmetalang.cpp index cd419837d..9a634c043 100644 --- a/sources/shiboken2/ApiExtractor/abstractmetalang.cpp +++ b/sources/shiboken2/ApiExtractor/abstractmetalang.cpp @@ -2485,6 +2485,7 @@ QString AbstractMetaType::formatPythonSignature() const * When we have a primitive with an indirection, we use that '*' * character for later postprocessing, since those indirections * need to be modified into a result tuple. + * Smart pointer instantiations: Drop the package */ QString result; if (m_pattern == AbstractMetaType::NativePointerAsArrayPattern) @@ -2493,7 +2494,7 @@ QString AbstractMetaType::formatPythonSignature() const // NativePointerAsArrayPattern indicates when we have <array> in XML. // if (m_typeEntry->isPrimitive() && isConstant()) // result += QLatin1String("const "); - if (!m_typeEntry->isPrimitive() && !package().isEmpty()) + if (!m_typeEntry->isPrimitive() && !m_typeEntry->isSmartPointer() && !package().isEmpty()) result += package() + QLatin1Char('.'); if (isArray()) { // Build nested array dimensions a[2][3] in correct order diff --git a/sources/shiboken2/doc/typesystem_codeinjection.rst b/sources/shiboken2/doc/typesystem_codeinjection.rst index 684630dd4..4ca77aa54 100644 --- a/sources/shiboken2/doc/typesystem_codeinjection.rst +++ b/sources/shiboken2/doc/typesystem_codeinjection.rst @@ -114,6 +114,8 @@ The following table describes the semantics of ``inject-code`` tag as used on | | |end |Insert code at the end of the module initialization function | | | | |(``initMODULENAME()``), but before the checking that emits a | | | | |fatal error in case of problems importing the module. | + | | +-----------+--------------------------------------------------------------+ + | | |declaration|Insert code into module header. | +---------------+------+-----------+--------------------------------------------------------------+ @@ -400,3 +402,6 @@ to prevent bad custom code to pass unnoticed. (...) // Start of ``MODULENAME_module_wrapper.cpp`` + +In addition, code can be injected into the module header by specifying ``target`` +and ``declaration``. This is useful for type definitions. diff --git a/sources/shiboken2/generator/shiboken2/cppgenerator.cpp b/sources/shiboken2/generator/shiboken2/cppgenerator.cpp index 5e6ad9b23..7217595ee 100644 --- a/sources/shiboken2/generator/shiboken2/cppgenerator.cpp +++ b/sources/shiboken2/generator/shiboken2/cppgenerator.cpp @@ -288,6 +288,34 @@ static inline bool canGenerateFieldSetter(const AbstractMetaField *field) return !type->isConstant() || isPointerToConst(type); } +static bool isStdSetterName(QString setterName, QString propertyName) +{ + return setterName.size() == propertyName.size() + 3 + && setterName.startsWith(QLatin1String("set")) + && setterName.endsWith(QStringView{propertyName}.right(propertyName.size() - 1)) + && setterName.at(3) == propertyName.at(0).toUpper(); +} + +static QString buildPropertyString(QPropertySpec *spec) +{ + QString text; + text += QLatin1Char('"'); + text += spec->name(); + text += QLatin1Char(':'); + + if (spec->read() != spec->name()) + text += spec->read(); + + if (!spec->write().isEmpty()) { + text += QLatin1Char(':'); + if (!isStdSetterName(spec->write(), spec->name())) + text += spec->write(); + } + + text += QLatin1Char('"'); + return text; +} + /*! Function used to write the class generated binding code on the buffer \param s the output buffer @@ -556,6 +584,22 @@ void CppGenerator::generateClass(QTextStream &s, const GeneratorContext &classCo // Write single method definitions s << singleMethodDefinitions; + if (usePySideExtensions()) { + // PYSIDE-1019: Write a compressed list of all properties `name:getter[:setter]`. + // Default values are suppressed. + QStringList sorter; + for (const auto spec : metaClass->propertySpecs()) + sorter.append(buildPropertyString(spec)); + sorter.sort(); + + s << '\n'; + s << "static const char *" << className << "_properties[] = {\n"; + for (const auto &entry : qAsConst(sorter)) + s << INDENT << entry << ",\n"; + s << INDENT << NULL_PTR << " // Sentinel\n"; + s << "};\n\n"; + } + // Write methods definition s << "static PyMethodDef " << className << "_methods[] = {\n"; s << methodsDefinitions << Qt::endl; @@ -947,8 +991,22 @@ void CppGenerator::writeVirtualMethodNative(QTextStream &s, s << INDENT << returnStatement << '\n'; } + //PYSIDE-1019: Add info about properties. + int propFlag = 0; + if (func->isPropertyReader()) + propFlag |= 1; + if (func->isPropertyWriter()) + propFlag |= 2; + if (propFlag && func->isStatic()) + propFlag |= 4; + QString propStr; + if (propFlag) + propStr = QString::number(propFlag) + QLatin1Char(':'); + s << INDENT << "static PyObject *nameCache[2] = {};\n"; - s << INDENT << "static const char *funcName = \"" << funcName << "\";\n"; + if (propFlag) + s << INDENT << "// This method belongs to a property.\n"; + s << INDENT << "static const char *funcName = \"" << propStr << funcName << "\";\n"; s << INDENT << "Shiboken::AutoDecRef " << PYTHON_OVERRIDE_VAR << "(Shiboken::BindingManager::instance().getOverride(this, nameCache, funcName));\n"; s << INDENT << "if (" << PYTHON_OVERRIDE_VAR << ".isNull()) {\n"; @@ -5127,6 +5185,12 @@ void CppGenerator::writeClassRegister(QTextStream &s, s << INDENT << ");\n"; s << INDENT << Qt::endl; + if (usePySideExtensions()) { + QString className = metaClass->qualifiedCppName(); + s << INDENT << "SbkObjectType_SetPropertyStrings(reinterpret_cast<PyTypeObject *>(" << typePtr << "), " + << chopType(pyTypeName) << "_properties);\n"; + } + if (!classContext.forSmartPointer()) s << INDENT << cpythonTypeNameExt(classTypeEntry) << Qt::endl; else diff --git a/sources/shiboken2/generator/shiboken2/headergenerator.cpp b/sources/shiboken2/generator/shiboken2/headergenerator.cpp index a06efc882..7826e350a 100644 --- a/sources/shiboken2/generator/shiboken2/headergenerator.cpp +++ b/sources/shiboken2/generator/shiboken2/headergenerator.cpp @@ -391,6 +391,12 @@ bool HeaderGenerator::finishGeneration() QString protectedEnumSurrogates; QTextStream protEnumsSurrogates(&protectedEnumSurrogates); + const auto snips = TypeDatabase::instance()->defaultTypeSystemType()->codeSnips(); + if (!snips.isEmpty()) { + writeCodeSnips(macrosStream, snips, TypeSystem::CodeSnipPositionDeclaration, + TypeSystem::TargetLangCode); + } + Indentation indent(INDENT); macrosStream << "// Type indices\nenum : int {\n"; diff --git a/sources/shiboken2/libshiboken/basewrapper.cpp b/sources/shiboken2/libshiboken/basewrapper.cpp index 05f8a4fb8..9017d8f91 100644 --- a/sources/shiboken2/libshiboken/basewrapper.cpp +++ b/sources/shiboken2/libshiboken/basewrapper.cpp @@ -615,6 +615,16 @@ void SbkObjectType_SetReserved(PyTypeObject *type, int value) PepType_SOTP(reinterpret_cast<SbkObjectType *>(type))->pyside_reserved_bits = value; } +const char **SbkObjectType_GetPropertyStrings(PyTypeObject *type) +{ + return PepType_SOTP(type)->propertyStrings; +} + +void SbkObjectType_SetPropertyStrings(PyTypeObject *type, const char **strings) +{ + PepType_SOTP(reinterpret_cast<SbkObjectType *>(type))->propertyStrings = strings; +} + // ////////////////////////////////////////////////////////////////////////////// diff --git a/sources/shiboken2/libshiboken/basewrapper.h b/sources/shiboken2/libshiboken/basewrapper.h index 4fec74464..1190f3187 100644 --- a/sources/shiboken2/libshiboken/basewrapper.h +++ b/sources/shiboken2/libshiboken/basewrapper.h @@ -101,6 +101,10 @@ LIBSHIBOKEN_API void initSelectableFeature(SelectableFeatureHook func); LIBSHIBOKEN_API int SbkObjectType_GetReserved(PyTypeObject *type); LIBSHIBOKEN_API void SbkObjectType_SetReserved(PyTypeObject *type, int value); +// PYSIDE-1019: Get access to PySide property strings. +LIBSHIBOKEN_API const char **SbkObjectType_GetPropertyStrings(PyTypeObject *type); +LIBSHIBOKEN_API void SbkObjectType_SetPropertyStrings(PyTypeObject *type, const char **strings); + extern LIBSHIBOKEN_API PyTypeObject *SbkObjectType_TypeF(void); extern LIBSHIBOKEN_API SbkObjectType *SbkObject_TypeF(void); diff --git a/sources/shiboken2/libshiboken/basewrapper_p.h b/sources/shiboken2/libshiboken/basewrapper_p.h index 482a3ea2a..64f7941b7 100644 --- a/sources/shiboken2/libshiboken/basewrapper_p.h +++ b/sources/shiboken2/libshiboken/basewrapper_p.h @@ -146,6 +146,7 @@ struct SbkObjectTypePrivate void *user_data; DeleteUserDataFunc d_func; void (*subtype_init)(SbkObjectType *, PyObject *, PyObject *); + const char **propertyStrings; }; diff --git a/sources/shiboken2/libshiboken/bindingmanager.cpp b/sources/shiboken2/libshiboken/bindingmanager.cpp index 7b06a4a00..78c03556c 100644 --- a/sources/shiboken2/libshiboken/bindingmanager.cpp +++ b/sources/shiboken2/libshiboken/bindingmanager.cpp @@ -274,13 +274,17 @@ SbkObject *BindingManager::retrieveWrapper(const void *cptr) return iter->second; } -static inline bool mangleNameFlag(PyTypeObject *type) +static inline int currentSelectId(PyTypeObject *type) { - // PYSIDE-1019: See if a dict is set with a snake_case bit. - return (SbkObjectType_GetReserved(type) & 1) != 0; + int sel = SbkObjectType_GetReserved(type); + // This could theoretically be -1 if used too early. + assert(sel >= 0); + return sel; } -PyObject *BindingManager::getOverride(const void *cptr, PyObject *methodNameCache[2], const char *methodName) +PyObject *BindingManager::getOverride(const void *cptr, + PyObject *nameCache[], + const char *methodName) { SbkObject *wrapper = retrieveWrapper(cptr); // The refcount can be 0 if the object is dieing and someone called @@ -288,37 +292,40 @@ PyObject *BindingManager::getOverride(const void *cptr, PyObject *methodNameCach if (!wrapper || reinterpret_cast<const PyObject *>(wrapper)->ob_refcnt == 0) return nullptr; - bool flag = mangleNameFlag(Py_TYPE(wrapper)); - PyObject *pyMethodName = methodNameCache[flag]; // borrowed + int flag = currentSelectId(Py_TYPE(wrapper)); + int propFlag = isdigit(methodName[0]) ? methodName[0] - '0' : 0; + if ((flag & 0x02) != 0 && (propFlag & 3) != 0) { + // PYSIDE-1019: Handle overriding with properties. + // They cannot be overridden (make that sure by the metaclass). + return nullptr; + } + PyObject *pyMethodName = nameCache[(flag & 1) != 0]; // borrowed if (pyMethodName == nullptr) { + if (propFlag) + methodName += 2; // skip the propFlag and ':' pyMethodName = Shiboken::String::getSnakeCaseName(methodName, flag); - methodNameCache[flag] = pyMethodName; + nameCache[(flag & 1) != 0] = pyMethodName; } if (wrapper->ob_dict) { PyObject *method = PyDict_GetItem(wrapper->ob_dict, pyMethodName); if (method) { - Py_INCREF(reinterpret_cast<PyObject *>(method)); + Py_INCREF(method); return method; } } PyObject *method = PyObject_GetAttr(reinterpret_cast<PyObject *>(wrapper), pyMethodName); - // PYSIDE-198: Support for Nuitka compiled methods. - bool isMethod = method && PyMethod_Check(method); - bool isCompiled = !( isMethod - || Py_TYPE(method) == &PyCFunction_Type - || Py_TYPE(method)->tp_call == nullptr); - Shiboken::AutoDecRef meth_self(PyObject_GetAttr(method, Shiboken::PyMagicName::self())); - bool wrapsParent = meth_self.object() == reinterpret_cast<PyObject *>(wrapper); - if ((isMethod && wrapsParent) || isCompiled) { + if (method && PyMethod_Check(method) + && PyMethod_GET_SELF(method) == reinterpret_cast<PyObject *>(wrapper)) { PyObject *defaultMethod; PyObject *mro = Py_TYPE(wrapper)->tp_mro; + int size = PyTuple_GET_SIZE(mro); // The first class in the mro (index 0) is the class being checked and it should not be tested. // The last class in the mro (size - 1) is the base Python object class which should not be tested also. - for (int idx = 1; idx < PyTuple_GET_SIZE(mro) - 1; ++idx) { + for (int idx = 1; idx < size - 1; ++idx) { auto *parent = reinterpret_cast<PyTypeObject *>(PyTuple_GET_ITEM(mro, idx)); if (parent->tp_dict) { defaultMethod = PyDict_GetItem(parent->tp_dict, pyMethodName); @@ -326,9 +333,9 @@ PyObject *BindingManager::getOverride(const void *cptr, PyObject *methodNameCach return method; } } + } else { + Py_XDECREF(method); } - - Py_XDECREF(method); return nullptr; } diff --git a/sources/shiboken2/libshiboken/bindingmanager.h b/sources/shiboken2/libshiboken/bindingmanager.h index 8882f402e..5b2246685 100644 --- a/sources/shiboken2/libshiboken/bindingmanager.h +++ b/sources/shiboken2/libshiboken/bindingmanager.h @@ -73,7 +73,7 @@ public: void addToDeletionInMainThread(const DestructorEntry &); SbkObject *retrieveWrapper(const void *cptr); - PyObject *getOverride(const void *cptr, PyObject *methodNameCache[2], const char *methodName); + PyObject *getOverride(const void *cptr, PyObject *nameCache[], const char *methodName); void addClassInheritance(SbkObjectType *parent, SbkObjectType *child); /** diff --git a/sources/shiboken2/libshiboken/sbkstaticstrings.cpp b/sources/shiboken2/libshiboken/sbkstaticstrings.cpp index 564853edb..672be4009 100644 --- a/sources/shiboken2/libshiboken/sbkstaticstrings.cpp +++ b/sources/shiboken2/libshiboken/sbkstaticstrings.cpp @@ -52,11 +52,11 @@ namespace Shiboken { namespace PyName { // exported: -STATIC_STRING_IMPL(dict_ring, "dict_ring") STATIC_STRING_IMPL(dumps, "dumps") +STATIC_STRING_IMPL(fget, "fget") +STATIC_STRING_IMPL(fset, "fset") STATIC_STRING_IMPL(loads, "loads") STATIC_STRING_IMPL(result, "result") -STATIC_STRING_IMPL(select_id, "select_id") STATIC_STRING_IMPL(value, "value") STATIC_STRING_IMPL(values, "values") diff --git a/sources/shiboken2/libshiboken/sbkstaticstrings.h b/sources/shiboken2/libshiboken/sbkstaticstrings.h index d8744bd8d..09e22b395 100644 --- a/sources/shiboken2/libshiboken/sbkstaticstrings.h +++ b/sources/shiboken2/libshiboken/sbkstaticstrings.h @@ -49,13 +49,13 @@ namespace Shiboken namespace PyName { LIBSHIBOKEN_API PyObject *co_name(); -LIBSHIBOKEN_API PyObject *dict_ring(); LIBSHIBOKEN_API PyObject *dumps(); +LIBSHIBOKEN_API PyObject *fget(); +LIBSHIBOKEN_API PyObject *fset(); LIBSHIBOKEN_API PyObject *f_code(); LIBSHIBOKEN_API PyObject *f_lineno(); LIBSHIBOKEN_API PyObject *loads(); LIBSHIBOKEN_API PyObject *result(); -LIBSHIBOKEN_API PyObject *select_id(); LIBSHIBOKEN_API PyObject *value(); LIBSHIBOKEN_API PyObject *values(); } // namespace PyName diff --git a/sources/shiboken2/libshiboken/sbkstring.cpp b/sources/shiboken2/libshiboken/sbkstring.cpp index 2aae183fe..fff9d05ec 100644 --- a/sources/shiboken2/libshiboken/sbkstring.cpp +++ b/sources/shiboken2/libshiboken/sbkstring.cpp @@ -266,5 +266,13 @@ PyObject *getSnakeCaseName(const char *name, bool lower) return createStaticString(new_name); } +PyObject *getSnakeCaseName(PyObject *name, bool lower) +{ + // This is all static strings, not refcounted. + if (lower) + return getSnakeCaseName(toCString(name), lower); + return name; +} + } // namespace String } // namespace Shiboken diff --git a/sources/shiboken2/libshiboken/sbkstring.h b/sources/shiboken2/libshiboken/sbkstring.h index 3475d3acd..817b8acc2 100644 --- a/sources/shiboken2/libshiboken/sbkstring.h +++ b/sources/shiboken2/libshiboken/sbkstring.h @@ -62,6 +62,7 @@ namespace String LIBSHIBOKEN_API Py_ssize_t len(PyObject *str); LIBSHIBOKEN_API PyObject *createStaticString(const char *str); LIBSHIBOKEN_API PyObject *getSnakeCaseName(const char *name, bool lower); + LIBSHIBOKEN_API PyObject *getSnakeCaseName(PyObject *name, bool lower); } // namespace String } // namespace Shiboken diff --git a/sources/shiboken2/shibokenmodule/files.dir/shibokensupport/__feature__.py b/sources/shiboken2/shibokenmodule/files.dir/shibokensupport/__feature__.py index 3852b3463..64f654d30 100644 --- a/sources/shiboken2/shibokenmodule/files.dir/shibokensupport/__feature__.py +++ b/sources/shiboken2/shibokenmodule/files.dir/shibokensupport/__feature__.py @@ -60,7 +60,7 @@ import sys all_feature_names = [ "snake_case", - "_feature_02", + "true_property", "_feature_04", "_feature_08", "_feature_10", @@ -71,8 +71,8 @@ all_feature_names = [ __all__ = ["all_feature_names", "set_selection", "info"] + all_feature_names -snake_case = 1 -_feature_02 = 0x02 +snake_case = 0x01 +true_property = 0x02 _feature_04 = 0x04 _feature_08 = 0x08 _feature_10 = 0x10 @@ -108,13 +108,7 @@ def _import(name, *args, **kwargs): existing = pyside_feature_dict.get(importing_module, 0) if name == "__feature__" and args[2]: - global _is_initialized - if not _is_initialized: - # use _one_ recursive import... - import PySide2.QtCore - # Initialize all prior imported modules - for name in sys.modules: - pyside_feature_dict.setdefault(name, -1) + __init__() # This is an `import from` statement that corresponds to `IMPORT_NAME`. # The following `IMPORT_FROM` will handle errors. (Confusing, ofc.) @@ -147,6 +141,15 @@ def _import(name, *args, **kwargs): _is_initialized = False +def __init__(): + global _is_initialized + if not _is_initialized: + # use _one_ recursive import... + import PySide2.QtCore + # Initialize all prior imported modules + for name in sys.modules: + pyside_feature_dict.setdefault(name, -1) + def set_selection(select_id, mod_name=None): """ @@ -154,11 +157,12 @@ def set_selection(select_id, mod_name=None): Id == -1: ignore this module in switching. """ mod_name = mod_name or sys._getframe(1).f_globals['__name__'] + __init__() # Reset the features to the given id flag = 0 if isinstance(select_id, int): flag = select_id & 255 - pyside_feature_dict[importing_module] = flag + pyside_feature_dict[mod_name] = flag sys.modules["PySide2.QtCore"].__init_feature__() return _current_selection(flag) diff --git a/sources/shiboken2/shibokenmodule/files.dir/shibokensupport/signature/mapping.py b/sources/shiboken2/shibokenmodule/files.dir/shibokensupport/signature/mapping.py index c39ceeb67..817790c50 100644 --- a/sources/shiboken2/shibokenmodule/files.dir/shibokensupport/signature/mapping.py +++ b/sources/shiboken2/shibokenmodule/files.dir/shibokensupport/signature/mapping.py @@ -265,6 +265,7 @@ type_map.update({ "qulonglong": int, "QVariant": Variant, "QVector": typing.List, + "QSharedPointer": typing.Tuple, "real": float, "short": int, "signed char": int, diff --git a/sources/shiboken2/shibokenmodule/files.dir/shibokensupport/signature/parser.py b/sources/shiboken2/shibokenmodule/files.dir/shibokensupport/signature/parser.py index 9dd7608b3..8a814114a 100644 --- a/sources/shiboken2/shibokenmodule/files.dir/shibokensupport/signature/parser.py +++ b/sources/shiboken2/shibokenmodule/files.dir/shibokensupport/signature/parser.py @@ -340,8 +340,9 @@ def calculate_props(line): _defaults.append(default) defaults = tuple(_defaults) returntype = parsed.returntype - if returntype is not None: - annotations["return"] = _resolve_type(returntype, line, 0, handle_retvar) + # PYSIDE-1383: We need to handle `None` explicitly. + annotations["return"] = (_resolve_type(returntype, line, 0, handle_retvar) + if returntype is not None else None) props = SimpleNamespace() props.defaults = defaults props.kwdefaults = {} diff --git a/sources/shiboken2/tests/samplebinding/renaming_test.py b/sources/shiboken2/tests/samplebinding/renaming_test.py index cb59dce3a..443fda6a3 100644 --- a/sources/shiboken2/tests/samplebinding/renaming_test.py +++ b/sources/shiboken2/tests/samplebinding/renaming_test.py @@ -54,7 +54,7 @@ class RenamingTest(unittest.TestCase): rename_user = RenamedUser() rename_user.useRenamedValue(renamed_value) actual_signature = str(rename_user.useRenamedValue.__signature__) - self.assertTrue(re.match(r"^\(self,\s*v:\s*sample.RenamedValue\)$", + self.assertTrue(re.match(r"^\(self,\s*?v:\s*?sample.RenamedValue\)\s*?->\s*?None$", actual_signature)) |