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/pyside2 | |
parent | 53181fb95d3884cc067d005f37accd92d128bccc (diff) | |
parent | 38814354ff6a30258b79947304fd3a6be4dc7089 (diff) |
Merge remote-tracking branch 'origin/5.15' into dev
Change-Id: I8aa48d07067c45c888c73af87314f6a88c2a6e14
Diffstat (limited to 'sources/pyside2')
-rw-r--r-- | sources/pyside2/PySide2/QtQml/pysideqmlregistertype.h | 11 | ||||
-rw-r--r-- | sources/pyside2/PySide2/QtQml/typesystem_qml.xml | 21 | ||||
-rw-r--r-- | sources/pyside2/PySide2/glue/qtcore.cpp | 6 | ||||
-rw-r--r-- | sources/pyside2/doc/tutorials/expenses/main.py | 2 | ||||
-rw-r--r-- | sources/pyside2/doc/tutorials/expenses/main_snake_prop.py (renamed from sources/pyside2/doc/tutorials/expenses/main_snake_case.py) | 46 | ||||
-rw-r--r-- | sources/pyside2/libpyside/feature_select.cpp | 164 | ||||
-rw-r--r-- | sources/pyside2/libpyside/pyside.cpp | 12 | ||||
-rw-r--r-- | sources/pyside2/libpyside/pysidestaticstrings.cpp | 4 | ||||
-rw-r--r-- | sources/pyside2/libpyside/pysidestaticstrings.h | 4 | ||||
-rw-r--r-- | sources/pyside2/tests/QtCore/CMakeLists.txt | 2 | ||||
-rw-r--r-- | sources/pyside2/tests/QtCore/multiple_feature_test.py | 29 | ||||
-rw-r--r-- | sources/pyside2/tests/QtCore/snake_prop_feature_test.py (renamed from sources/pyside2/tests/QtCore/snake_case_feature_test.py) | 44 | ||||
-rw-r--r-- | sources/pyside2/tests/signals/CMakeLists.txt | 1 | ||||
-rw-r--r-- | sources/pyside2/tests/signals/signal_across_threads.py | 106 |
14 files changed, 372 insertions, 80 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() |