aboutsummaryrefslogtreecommitdiffstats
path: root/sources
diff options
context:
space:
mode:
authorFriedemann Kleint <Friedemann.Kleint@qt.io>2020-07-24 16:24:14 +0200
committerFriedemann Kleint <Friedemann.Kleint@qt.io>2020-07-24 16:24:14 +0200
commitb96297d6687445a35ad00e7204dd83956497b8ca (patch)
treef1b0e6753ea416b696887d3cdbd7f9efb4610cfd /sources
parent8ead3d8468fda9fd9eb42b997686bfa0a66f6d00 (diff)
parent2d44c85faa01c7e805ff27bac4e3e1574ab0f5d3 (diff)
Merge remote-tracking branch 'origin/5.15' into dev
Diffstat (limited to 'sources')
-rw-r--r--sources/pyside2/doc/tutorials/expenses/main_snake_case.py209
-rw-r--r--sources/pyside2/libpyside/feature_select.cpp133
-rw-r--r--sources/pyside2/libpyside/feature_select.h6
-rw-r--r--sources/pyside2/libpyside/pyside.cpp2
-rw-r--r--sources/pyside2/tests/QtCore/CMakeLists.txt3
-rw-r--r--sources/pyside2/tests/QtCore/multiple_feature_test.py (renamed from sources/pyside2/tests/QtCore/feature_test.py)36
-rw-r--r--sources/pyside2/tests/QtCore/snake_case_feature_test.py86
-rw-r--r--sources/pyside2/tests/QtQml/signal_arguments.py1
-rw-r--r--sources/pyside2/tests/QtQml/signal_arguments.qml4
-rw-r--r--sources/shiboken2/generator/shiboken2/cppgenerator.cpp16
-rw-r--r--sources/shiboken2/libshiboken/basewrapper.cpp104
-rw-r--r--sources/shiboken2/libshiboken/basewrapper.h5
-rw-r--r--sources/shiboken2/libshiboken/bindingmanager.cpp28
-rw-r--r--sources/shiboken2/libshiboken/bindingmanager.h2
-rw-r--r--sources/shiboken2/libshiboken/pep384impl.cpp3
-rw-r--r--sources/shiboken2/libshiboken/pep384impl.h2
-rw-r--r--sources/shiboken2/libshiboken/sbkstring.cpp44
-rw-r--r--sources/shiboken2/libshiboken/sbkstring.h1
-rw-r--r--sources/shiboken2/shibokenmodule/files.dir/shibokensupport/__feature__.py7
19 files changed, 623 insertions, 69 deletions
diff --git a/sources/pyside2/doc/tutorials/expenses/main_snake_case.py b/sources/pyside2/doc/tutorials/expenses/main_snake_case.py
new file mode 100644
index 000000000..154396b41
--- /dev/null
+++ b/sources/pyside2/doc/tutorials/expenses/main_snake_case.py
@@ -0,0 +1,209 @@
+#############################################################################
+##
+## 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.
+##
+## $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$
+##
+#############################################################################
+
+import sys
+from PySide2.QtCore import Qt, Slot
+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
+
+
+class Widget(QWidget):
+ def __init__(self):
+ QWidget.__init__(self)
+ self.items = 0
+
+ # Example data
+ self._data = {"Water": 24.5, "Electricity": 55.1, "Rent": 850.0,
+ "Supermarket": 230.4, "Internet": 29.99, "Bars": 21.85,
+ "Public transportation": 60.0, "Coffee": 22.45, "Restaurants": 120}
+
+ # 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)
+
+ # Chart
+ self.chart_view = QtCharts.QChartView()
+ self.chart_view.set_render_hint(QPainter.Antialiasing)
+
+ # Right
+ self.description = QLineEdit()
+ self.price = QLineEdit()
+ self.add = QPushButton("Add")
+ self.clear = QPushButton("Clear")
+ self.quit = QPushButton("Quit")
+ self.plot = QPushButton("Plot")
+
+ # Disabling 'Add' button
+ self.add.setEnabled(False)
+
+ self.right = QVBoxLayout()
+ self.right.set_margin(10)
+ self.right.add_widget(QLabel("Description"))
+ self.right.add_widget(self.description)
+ self.right.add_widget(QLabel("Price"))
+ self.right.add_widget(self.price)
+ self.right.add_widget(self.add)
+ self.right.add_widget(self.plot)
+ self.right.add_widget(self.chart_view)
+ self.right.add_widget(self.clear)
+ self.right.add_widget(self.quit)
+
+ # QWidget Layout
+ self.layout = QHBoxLayout()
+
+ #self.table_view.setSizePolicy(size)
+ self.layout.add_widget(self.table)
+ self.layout.add_layout(self.right)
+
+ # Set the layout to the QWidget
+ self.set_layout(self.layout)
+
+ # Signals and Slots
+ self.add.clicked.connect(self.add_element)
+ self.quit.clicked.connect(self.quit_application)
+ self.plot.clicked.connect(self.plot_data)
+ self.clear.clicked.connect(self.clear_table)
+ self.description.textChanged[str].connect(self.check_disable)
+ self.price.textChanged[str].connect(self.check_disable)
+
+ # Fill example data
+ self.fill_table()
+
+ @Slot()
+ def add_element(self):
+ 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)
+
+ 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.items += 1
+
+ @Slot()
+ def check_disable(self, s):
+ if not self.description.text() or not self.price.text():
+ self.add.set_enabled(False)
+ else:
+ self.add.set_enabled(True)
+
+ @Slot()
+ def plot_data(self):
+ # Get table information
+ series = QtCharts.QPieSeries()
+ 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)
+ self.chart_view.set_chart(chart)
+
+ @Slot()
+ def quit_application(self):
+ QApplication.quit()
+
+ def fill_table(self, data=None):
+ data = self._data if not data else data
+ for desc, price in data.items():
+ description_item = QTableWidgetItem(desc)
+ price_item = QTableWidgetItem("{:.2f}".format(price))
+ price_item.set_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)
+ self.items += 1
+
+ @Slot()
+ def clear_table(self):
+ self.table.set_row_count(0)
+ self.items = 0
+
+
+class MainWindow(QMainWindow):
+ def __init__(self, widget):
+ QMainWindow.__init__(self)
+ self.setWindowTitle("Tutorial")
+
+ # Menu
+ self.menu = self.menu_bar()
+ self.file_menu = self.menu.add_menu("File")
+
+ # Exit QAction
+ exit_action = QAction("Exit", self)
+ exit_action.set_shortcut("Ctrl+Q")
+ exit_action.triggered.connect(self.exit_app)
+
+ self.file_menu.add_action(exit_action)
+ self.set_central_widget(widget)
+
+ @Slot()
+ def exit_app(self, checked):
+ QApplication.quit()
+
+
+if __name__ == "__main__":
+ # Qt Application
+ app = QApplication(sys.argv)
+ # QWidget
+ widget = Widget()
+ # QMainWindow using QWidget as central widget
+ window = MainWindow(widget)
+ window.resize(800, 600)
+ window.show()
+
+ # Execute application
+ sys.exit(app.exec_())
diff --git a/sources/pyside2/libpyside/feature_select.cpp b/sources/pyside2/libpyside/feature_select.cpp
index 6e5670d6d..7b98b03f0 100644
--- a/sources/pyside2/libpyside/feature_select.cpp
+++ b/sources/pyside2/libpyside/feature_select.cpp
@@ -111,12 +111,16 @@ looks into the `__name__` attribute of the active module and decides which
version of `tp_dict` is needed. Then the right dict is searched in the ring
and created if not already there.
+Furthermore, we need to overwrite every `tp_getattro` and `tp_setattro`
+with a version that switches dicts before looking up methods.
+The dict changing must follow the `tp_mro` in order to change all names.
+
This is everything that the following code does.
*****************************************************************************/
-namespace PySide { namespace FeatureSelector {
+namespace PySide { namespace Feature {
using namespace Shiboken;
@@ -155,7 +159,6 @@ createDerivedDictType()
return reinterpret_cast<PyTypeObject *>(ChameleonDict);
}
-static PyTypeObject *old_dict_type = Py_TYPE(PyType_Type.tp_dict);
static PyTypeObject *new_dict_type = nullptr;
static void ensureNewDictType()
@@ -285,7 +288,7 @@ static bool createNewFeatureSet(PyTypeObject *type, PyObject *select_id)
Py_INCREF(prev_dict);
if (!addNewDict(type, select_id))
return false;
- int id = PyInt_AsSsize_t(select_id);
+ auto id = PyInt_AsSsize_t(select_id);
if (id == -1)
return false;
FeatureProc *proc = featurePointer;
@@ -307,11 +310,30 @@ static bool createNewFeatureSet(PyTypeObject *type, PyObject *select_id)
return true;
}
+static bool SelectFeatureSetSubtype(PyTypeObject *type, PyObject *select_id)
+{
+ if (Py_TYPE(type->tp_dict) == Py_TYPE(PyType_Type.tp_dict)) {
+ // PYSIDE-1019: On first touch, we initialize the dynamic naming.
+ // The dict type will be replaced after the first call.
+ if (!replaceClassDict(type)) {
+ Py_FatalError("failed to replace class dict!");
+ return false;
+ }
+ }
+ if (!moveToFeatureSet(type, select_id)) {
+ if (!createNewFeatureSet(type, select_id)) {
+ Py_FatalError("failed to create a new feature set!");
+ return false;
+ }
+ }
+ return true;
+}
+
static PyObject *SelectFeatureSet(PyTypeObject *type)
{
/*
* This is the main function of the module.
- * It just makes no sense to make the function public, because
+ * Generated functions call this directly.
* Shiboken will assign it via a public hook of `basewrapper.cpp`.
*/
if (Py_TYPE(type->tp_dict) == Py_TYPE(PyType_Type.tp_dict)) {
@@ -323,16 +345,27 @@ static PyObject *SelectFeatureSet(PyTypeObject *type)
PyObject *select_id = getFeatureSelectID(); // borrowed
AutoDecRef current_id(getSelectId(type->tp_dict));
if (select_id != current_id) {
- if (!moveToFeatureSet(type, select_id))
- if (!createNewFeatureSet(type, select_id)) {
- Py_FatalError("failed to create a new feature set!");
- return nullptr;
- }
+ PyObject *mro = type->tp_mro;
+ Py_ssize_t idx, n = PyTuple_GET_SIZE(mro);
+ // We leave 'Shiboken.Object' and 'object' alone, therefore "n - 2".
+ for (idx = 0; idx < n - 2; idx++) {
+ auto *sub_type = reinterpret_cast<PyTypeObject *>(PyTuple_GET_ITEM(mro, idx));
+ // When any subtype is already resolved (false), we can stop.
+ if (!SelectFeatureSetSubtype(sub_type, select_id))
+ break;
+ }
}
return type->tp_dict;
}
-static bool feature_01_addDummyNames(PyTypeObject *type, PyObject *prev_dict);
+// For cppgenerator:
+void Select(PyObject *obj)
+{
+ auto type = Py_TYPE(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);
@@ -342,7 +375,7 @@ static bool feature_40_addDummyNames(PyTypeObject *type, PyObject *prev_dict);
static bool feature_80_addDummyNames(PyTypeObject *type, PyObject *prev_dict);
static FeatureProc featureProcArray[] = {
- feature_01_addDummyNames,
+ feature_01_addLowerNames,
feature_02_addDummyNames,
feature_04_addDummyNames,
feature_08_addDummyNames,
@@ -363,7 +396,80 @@ void init()
//
// PYSIDE-1019: Support switchable extensions
//
-// Feature 0x01..0x80: A fake switchable option for testing
+// Feature 0x01: Allow snake_case instead of camelCase
+//
+// This functionality is no longer implemented in the signature module, since
+// the PyCFunction getsets do not have to be modified any longer.
+// Instead, we simply exchange the complete class dicts. This is done in the
+// basewrapper.cpp file.
+//
+
+static PyObject *methodWithLowerName(PyTypeObject *type,
+ PyMethodDef *meth,
+ const char *new_name)
+{
+ /*
+ * Create a method with a lower case name.
+ */
+ auto obtype = reinterpret_cast<PyObject *>(type);
+ int len = strlen(new_name);
+ auto name = new char[len + 1];
+ strcpy(name, new_name);
+ auto new_meth = new PyMethodDef;
+ new_meth->ml_name = name;
+ new_meth->ml_meth = meth->ml_meth;
+ new_meth->ml_flags = meth->ml_flags;
+ new_meth->ml_doc = meth->ml_doc;
+ PyObject *descr = nullptr;
+ if (new_meth->ml_flags & METH_STATIC) {
+ AutoDecRef cfunc(PyCFunction_NewEx(new_meth, obtype, nullptr));
+ if (cfunc.isNull())
+ return nullptr;
+ descr = PyStaticMethod_New(cfunc);
+ }
+ else {
+ descr = PyDescr_NewMethod(type, new_meth);
+ }
+ return descr;
+}
+
+static bool feature_01_addLowerNames(PyTypeObject *type, PyObject *prev_dict)
+{
+ /*
+ * Add objects with lower names to `type->tp_dict` from 'prev_dict`.
+ */
+ PyObject *lower_dict = type->tp_dict;
+ PyObject *key, *value;
+ Py_ssize_t pos = 0;
+
+ // We first copy the things over which will not be changed:
+ while (PyDict_Next(prev_dict, &pos, &key, &value)) {
+ if ( Py_TYPE(value) != PepMethodDescr_TypePtr
+ && Py_TYPE(value) != PepStaticMethod_TypePtr) {
+ if (PyDict_SetItem(lower_dict, key, value))
+ return false;
+ continue;
+ }
+ }
+ // Then we walk over the tp_methods to get all methods and insert
+ // them with changed names.
+ PyMethodDef *meth = type->tp_methods;
+ for (; meth != nullptr && meth->ml_name != nullptr; ++meth) {
+ const char *name = String::toCString(String::getSnakeCaseName(meth->ml_name, true));
+ AutoDecRef new_method(methodWithLowerName(type, meth, name));
+ if (new_method.isNull())
+ return false;
+ if (PyDict_SetItemString(lower_dict, name, new_method) < 0)
+ return false;
+ }
+ return true;
+}
+
+//////////////////////////////////////////////////////////////////////////////
+//
+// PYSIDE-1019: Support switchable extensions
+//
+// Feature 0x02..0x80: A fake switchable option for testing
//
#define SIMILAR_FEATURE(xx) \
@@ -378,7 +484,6 @@ static bool feature_##xx##_addDummyNames(PyTypeObject *type, PyObject *prev_dict
return true; \
}
-SIMILAR_FEATURE(01)
SIMILAR_FEATURE(02)
SIMILAR_FEATURE(04)
SIMILAR_FEATURE(08)
@@ -388,4 +493,4 @@ SIMILAR_FEATURE(40)
SIMILAR_FEATURE(80)
} // namespace PySide
-} // namespace FeatureSelector
+} // namespace Feature
diff --git a/sources/pyside2/libpyside/feature_select.h b/sources/pyside2/libpyside/feature_select.h
index 68e29292d..32abffac6 100644
--- a/sources/pyside2/libpyside/feature_select.h
+++ b/sources/pyside2/libpyside/feature_select.h
@@ -41,13 +41,15 @@
#define FEATURE_SELECT_H
#include "pysidemacros.h"
+#include <sbkpython.h>
namespace PySide {
-namespace FeatureSelector {
+namespace Feature {
PYSIDE_API void init();
+PYSIDE_API void Select(PyObject *obj);
+} // namespace Feature
} // namespace PySide
-} // namespace FeatureSelector
#endif // FEATURE_SELECT_H
diff --git a/sources/pyside2/libpyside/pyside.cpp b/sources/pyside2/libpyside/pyside.cpp
index 074fa764b..c1ddfc278 100644
--- a/sources/pyside2/libpyside/pyside.cpp
+++ b/sources/pyside2/libpyside/pyside.cpp
@@ -94,7 +94,7 @@ void init(PyObject *module)
MetaFunction::init(module);
// Init signal manager, so it will register some meta types used by QVariant.
SignalManager::instance();
- FeatureSelector::init();
+ Feature::init();
initQApp();
}
diff --git a/sources/pyside2/tests/QtCore/CMakeLists.txt b/sources/pyside2/tests/QtCore/CMakeLists.txt
index c0ec2ef79..3af239c8c 100644
--- a/sources/pyside2/tests/QtCore/CMakeLists.txt
+++ b/sources/pyside2/tests/QtCore/CMakeLists.txt
@@ -37,12 +37,12 @@ PYSIDE_TEST(deletelater_test.py)
PYSIDE_TEST(destroysignal_test.py)
PYSIDE_TEST(duck_punching_test.py)
PYSIDE_TEST(emoji_string_test.py)
-PYSIDE_TEST(feature_test.py)
PYSIDE_TEST(hash_test.py)
PYSIDE_TEST(inherits_test.py)
PYSIDE_TEST(max_signals.py)
PYSIDE_TEST(missing_symbols_test.py)
PYSIDE_TEST(mockclass_test.py)
+PYSIDE_TEST(multiple_feature_test.py)
PYSIDE_TEST(python_conversion.py)
PYSIDE_TEST(qabs_test.py)
PYSIDE_TEST(qabstractitemmodel_test.py)
@@ -126,6 +126,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(staticMetaObject_test.py)
PYSIDE_TEST(static_method_test.py)
PYSIDE_TEST(thread_signals_test.py)
diff --git a/sources/pyside2/tests/QtCore/feature_test.py b/sources/pyside2/tests/QtCore/multiple_feature_test.py
index cf1e8c3f2..26488326c 100644
--- a/sources/pyside2/tests/QtCore/feature_test.py
+++ b/sources/pyside2/tests/QtCore/multiple_feature_test.py
@@ -37,6 +37,8 @@
##
#############################################################################
+from __future__ import print_function, absolute_import
+
import os
import sys
import unittest
@@ -50,12 +52,13 @@ from PySide2.support.__feature__ import _really_all_feature_names
from textwrap import dedent
"""
-feature_test.py
---------------
+multiple_feature_test.py
+------------------------
This tests the selectable features in PySide.
-There are no real features implemented. They will be added, later.
+The first feature is `snake_case` instead of `camelCase`.
+There is much more to come.
"""
class FeaturesTest(unittest.TestCase):
@@ -66,9 +69,27 @@ class FeaturesTest(unittest.TestCase):
"""
global __name__
- for bit in range(8):
+ def tst_bit0(flag, self):
+ if flag == 0:
+ QtCore.QCborArray.isEmpty
+ QtCore.QCborArray.__dict__["isEmpty"]
+ with self.assertRaises(AttributeError):
+ QtCore.QCborArray.is_empty
+ with self.assertRaises(KeyError):
+ QtCore.QCborArray.__dict__["is_empty"]
+ else:
+ QtCore.QCborArray.is_empty
+ QtCore.QCborArray.__dict__["is_empty"]
+ with self.assertRaises(AttributeError):
+ QtCore.QCborArray.isEmpty
+ with self.assertRaises(KeyError):
+ QtCore.QCborArray.__dict__["isEmpty"]
+
+ edict = {}
+ for bit in range(1, 8):
# We are cheating here, since the functions are in the globals.
- exec(dedent("""
+
+ eval(compile(dedent("""
def tst_bit{0}(flag, self):
if flag == 0:
@@ -80,7 +101,8 @@ class FeaturesTest(unittest.TestCase):
QtCore.QCborArray.fake_feature_{1:02x}
QtCore.QCborArray.__dict__["fake_feature_{1:02x}"]
- """.format(bit, 1 << bit)), globals(), globals())
+ """).format(bit, 1 << bit), "<string>", "exec"), globals(), edict)
+ globals().update(edict)
feature_list = _really_all_feature_names
func_list = [tst_bit0, tst_bit1, tst_bit2, tst_bit3,
tst_bit4, tst_bit5, tst_bit6, tst_bit7]
@@ -95,7 +117,7 @@ class FeaturesTest(unittest.TestCase):
feature = feature_list[bit]
text = "from __feature__ import {}".format(feature)
print(text)
- exec(text)
+ eval(compile(text, "<string>", "exec"), globals(), edict)
for bit in range(8):
value = idx & 1 << bit
func_list[bit](value, self=self)
diff --git a/sources/pyside2/tests/QtCore/snake_case_feature_test.py b/sources/pyside2/tests/QtCore/snake_case_feature_test.py
new file mode 100644
index 000000000..b7f23396e
--- /dev/null
+++ b/sources/pyside2/tests/QtCore/snake_case_feature_test.py
@@ -0,0 +1,86 @@
+#############################################################################
+##
+## Copyright (C) 2020 The Qt Company Ltd.
+## Contact: https://www.qt.io/licensing/
+##
+## This file is part of Qt for Python.
+##
+## $QT_BEGIN_LICENSE:LGPL$
+## 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 Lesser General Public License Usage
+## Alternatively, this file may be used under the terms of the GNU Lesser
+## General Public License version 3 as published by the Free Software
+## Foundation and appearing in the file LICENSE.LGPL3 included in the
+## packaging of this file. Please review the following information to
+## ensure the GNU Lesser General Public License version 3 requirements
+## will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+##
+## GNU General Public License Usage
+## Alternatively, this file may be used under the terms of the GNU
+## General Public License version 2.0 or (at your option) the GNU General
+## Public license version 3 or any later version approved by the KDE Free
+## Qt Foundation. The licenses are as published by the Free Software
+## Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+## 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-2.0.html and
+## https://www.gnu.org/licenses/gpl-3.0.html.
+##
+## $QT_END_LICENSE$
+##
+#############################################################################
+
+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 import QtWidgets
+
+"""
+snake_case_feature_test.py
+--------------------------
+
+Test the snake_case feature.
+
+This works now. More tests needed!
+"""
+
+class RenamingTest(unittest.TestCase):
+ def setUp(self):
+ qApp or QtWidgets.QApplication()
+
+ 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__()
+
+ window = Window()
+ window.set_window_title('snake_case')
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/sources/pyside2/tests/QtQml/signal_arguments.py b/sources/pyside2/tests/QtQml/signal_arguments.py
index 98b52c05c..16257a22f 100644
--- a/sources/pyside2/tests/QtQml/signal_arguments.py
+++ b/sources/pyside2/tests/QtQml/signal_arguments.py
@@ -68,6 +68,7 @@ class TestConnectionWithQml(TimedQApplication):
root = view.rootObject()
self.assertTrue(root)
button = root.findChild(QObject, "button")
+ self.assertTrue(button)
view.show()
button.clicked.emit()
self.assertEqual(obj.value, 42)
diff --git a/sources/pyside2/tests/QtQml/signal_arguments.qml b/sources/pyside2/tests/QtQml/signal_arguments.qml
index 0c65b947f..73a1715fa 100644
--- a/sources/pyside2/tests/QtQml/signal_arguments.qml
+++ b/sources/pyside2/tests/QtQml/signal_arguments.qml
@@ -28,14 +28,14 @@
import QtQuick 2.5
-import QtQuick.Controls 1.4
+import QtQuick.Controls 2.12
import QtQuick.Layouts 1.2
Rectangle {
visible: true
GridLayout {
Button {
- id: "button"
+ id: button
objectName: "button"
text: "sum!"
onClicked: {
diff --git a/sources/shiboken2/generator/shiboken2/cppgenerator.cpp b/sources/shiboken2/generator/shiboken2/cppgenerator.cpp
index 069431e59..b10074f79 100644
--- a/sources/shiboken2/generator/shiboken2/cppgenerator.cpp
+++ b/sources/shiboken2/generator/shiboken2/cppgenerator.cpp
@@ -313,6 +313,7 @@ void CppGenerator::generateClass(QTextStream &s, const GeneratorContext &classCo
<< "#include <pysideproperty.h>\n"
<< "#include <pyside.h>\n"
<< "#include <pysideqenum.h>\n"
+ << "#include <feature_select.h>\n"
<< "#include <qapp_macro.h>\n\n"
<< "QT_WARNING_DISABLE_DEPRECATED\n\n";
}
@@ -949,10 +950,10 @@ void CppGenerator::writeVirtualMethodNative(QTextStream &s,
s << INDENT << returnStatement << '\n';
}
- s << INDENT << "static PyObject *pyFuncName = Shiboken::String::createStaticString(\""
- << funcName << "\");\n";
+ s << INDENT << "static PyObject *nameCache[2] = {};\n";
+ s << INDENT << "static const char *funcName = \"" << funcName << "\";\n";
s << INDENT << "Shiboken::AutoDecRef " << PYTHON_OVERRIDE_VAR
- << "(Shiboken::BindingManager::instance().getOverride(this, pyFuncName));\n";
+ << "(Shiboken::BindingManager::instance().getOverride(this, nameCache, funcName));\n";
s << INDENT << "if (" << PYTHON_OVERRIDE_VAR << ".isNull()) {\n";
{
Indentation indentation(INDENT);
@@ -5371,6 +5372,11 @@ void CppGenerator::writeSetattroFunction(QTextStream &s, AttroCheck attroCheck,
Q_ASSERT(!context.forSmartPointer());
const AbstractMetaClass *metaClass = context.metaClass();
writeSetattroDefinition(s, metaClass);
+
+ // PYSIDE-1019: Switch tp_dict before doing tp_setattro.
+ if (usePySideExtensions())
+ s << INDENT << "PySide::Feature::Select(self);\n";
+
// PYSIDE-803: Detect duck-punching; clear cache if a method is set.
if (attroCheck.testFlag(AttroCheckFlag::SetattroMethodOverride)
&& context.useWrapper()) {
@@ -5458,6 +5464,10 @@ void CppGenerator::writeGetattroFunction(QTextStream &s, AttroCheck attroCheck,
const AbstractMetaClass *metaClass = context.metaClass();
writeGetattroDefinition(s, metaClass);
+ // PYSIDE-1019: Switch tp_dict before doing tp_getattro.
+ if (usePySideExtensions())
+ s << INDENT << "PySide::Feature::Select(self);\n";
+
const QString getattrFunc = usePySideExtensions() && metaClass->isQObject()
? qObjectGetAttroFunction() : QLatin1String("PyObject_GenericGetAttr(self, name)");
diff --git a/sources/shiboken2/libshiboken/basewrapper.cpp b/sources/shiboken2/libshiboken/basewrapper.cpp
index 5c2dc7807..a602688ec 100644
--- a/sources/shiboken2/libshiboken/basewrapper.cpp
+++ b/sources/shiboken2/libshiboken/basewrapper.cpp
@@ -97,21 +97,7 @@ static PyObject *SbkObjectTypeTpNew(PyTypeObject *metatype, PyObject *args, PyOb
static SelectableFeatureHook SelectFeatureSet = nullptr;
-void initSelectableFeature(SelectableFeatureHook func)
-{
- SelectFeatureSet = func;
-}
-
-// PYSIDE-1019: Switch type's tp_dict to the currently active namespace.
-static PyObject *Sbk_TypeGet___dict__(PyTypeObject *type, void *context)
-{
- auto dict = type->tp_dict;
- if (dict == NULL)
- Py_RETURN_NONE;
- if (SelectFeatureSet != nullptr)
- dict = SelectFeatureSet(type);
- return PyDictProxy_New(dict);
-}
+static PyObject *Sbk_TypeGet___dict__(PyTypeObject *type, void *context); // forward
// PYSIDE-908: The function PyType_Modified does not work in PySide, so we need to
// explicitly pass __doc__. For __signature__ it _did_ actually work, because
@@ -140,26 +126,12 @@ static PyObject *SbkObjectType_repr(PyObject *type)
#endif // PY_VERSION_HEX < 0x03000000
-// PYSIDE-1019: Switch type's tp_dict to the currently active namespace.
-static PyObject *(*type_getattro)(PyObject *type, PyObject *name);
-
-static PyObject *mangled_type_getattro(PyTypeObject *type, PyObject *name)
-{
- /*
- * Note: This `type_getattro` version is only the default that comes
- * from `PyType_Type.tp_getattro`. This does *not* interfere in any way
- * with the complex `tp_getattro` of `QObject` and other instances.
- * What we change here is the meta class of `QObject`.
- */
- if (SelectFeatureSet != nullptr)
- type->tp_dict = SelectFeatureSet(type);
- return type_getattro(reinterpret_cast<PyObject *>(type), name);
-}
+static PyObject *(*type_getattro)(PyObject *type, PyObject *name); // forward
+static PyObject *mangled_type_getattro(PyTypeObject *type, PyObject *name); // forward
static PyType_Slot SbkObjectType_Type_slots[] = {
{Py_tp_dealloc, reinterpret_cast<void *>(SbkObjectTypeDealloc)},
{Py_tp_getattro, reinterpret_cast<void *>(mangled_type_getattro)},
- {Py_tp_setattro, reinterpret_cast<void *>(PyObject_GenericSetAttr)},
{Py_tp_base, static_cast<void *>(&PyType_Type)},
{Py_tp_alloc, reinterpret_cast<void *>(PyType_GenericAlloc)},
{Py_tp_new, reinterpret_cast<void *>(SbkObjectTypeTpNew)},
@@ -340,7 +312,12 @@ static int SbkObject_clear(PyObject *self)
return 0;
}
+static PyObject *SbkObject_GenericGetAttr(PyObject *obj, PyObject *name);
+static int SbkObject_GenericSetAttr(PyObject *obj, PyObject *name, PyObject *value);
+
static PyType_Slot SbkObject_Type_slots[] = {
+ {Py_tp_getattro, reinterpret_cast<void *>(SbkObject_GenericGetAttr)},
+ {Py_tp_setattro, reinterpret_cast<void *>(SbkObject_GenericSetAttr)},
{Py_tp_dealloc, reinterpret_cast<void *>(SbkDeallocWrapperWithPrivateDtor)},
{Py_tp_traverse, reinterpret_cast<void *>(SbkObject_traverse)},
{Py_tp_clear, reinterpret_cast<void *>(SbkObject_clear)},
@@ -533,6 +510,71 @@ void SbkObjectTypeDealloc(PyObject *pyObj)
}
}
+//////////////////////////////////////////////////////////////////////////////
+//
+// PYSIDE-1019: Support switchable extensions
+//
+// We simply exchange the complete class dicts.
+// This is done in
+// - mangled_type_getattro which replaces
+// - Sbk_TypeGet___dict__
+// - SbkObjectType_replace_getattro
+// - SbkObjectType_replace_setattro
+//
+
+void initSelectableFeature(SelectableFeatureHook func)
+{
+ SelectFeatureSet = func;
+}
+
+static PyObject *mangled_type_getattro(PyTypeObject *type, PyObject *name)
+{
+ /*
+ * Note: This `type_getattro` version is only the default that comes
+ * from `PyType_Type.tp_getattro`. This does *not* interfere in any way
+ * with the complex `tp_getattro` of `QObject` and other instances.
+ * What we change here is the meta class of `QObject`.
+ */
+ if (SelectFeatureSet != nullptr)
+ type->tp_dict = SelectFeatureSet(type);
+ return type_getattro(reinterpret_cast<PyObject *>(type), name);
+}
+
+static PyObject *Sbk_TypeGet___dict__(PyTypeObject *type, void *context)
+{
+ /*
+ * This is the override for getting a dict.
+ */
+ auto dict = type->tp_dict;
+ if (dict == NULL)
+ Py_RETURN_NONE;
+ if (SelectFeatureSet != nullptr)
+ dict = SelectFeatureSet(type);
+ return PyDictProxy_New(dict);
+}
+
+// These functions replace the standard PyObject_Generic(Get|Set)Attr functions.
+// They provide the default that "object" inherits.
+// Everything else is directly handled by an insertion PyObject_GenericGetAttr
+static PyObject *SbkObject_GenericGetAttr(PyObject *obj, PyObject *name)
+{
+ auto type = Py_TYPE(obj);
+ if (SelectFeatureSet != nullptr)
+ type->tp_dict = SelectFeatureSet(type);
+ return PyObject_GenericGetAttr(obj, name);
+}
+
+static int SbkObject_GenericSetAttr(PyObject *obj, PyObject *name, PyObject *value)
+{
+ auto type = Py_TYPE(obj);
+ if (SelectFeatureSet != nullptr)
+ type->tp_dict = SelectFeatureSet(type);
+ return PyObject_GenericSetAttr(obj, name, value);
+}
+
+//
+//////////////////////////////////////////////////////////////////////////////
+
static PyObject *SbkObjectTypeTpNew(PyTypeObject *metatype, PyObject *args, PyObject *kwds)
{
// Check if all bases are new style before calling type.tp_new
diff --git a/sources/shiboken2/libshiboken/basewrapper.h b/sources/shiboken2/libshiboken/basewrapper.h
index a4a8629fb..47ea89577 100644
--- a/sources/shiboken2/libshiboken/basewrapper.h
+++ b/sources/shiboken2/libshiboken/basewrapper.h
@@ -93,9 +93,14 @@ typedef void (*ObjectDestructor)(void *);
typedef void (*SubTypeInitHook)(SbkObjectType *, PyObject *, PyObject *);
+// PYSIDE-1019: Set the function to select the current feature.
typedef PyObject *(*SelectableFeatureHook)(PyTypeObject *);
LIBSHIBOKEN_API void initSelectableFeature(SelectableFeatureHook func);
+// PYSIDE-1019: Publish the start of setattro.
+LIBSHIBOKEN_API void SbkObject_NotifySetAttr(PyObject *obj, PyObject *name, PyObject *value);
+
+
extern LIBSHIBOKEN_API PyTypeObject *SbkObjectType_TypeF(void);
extern LIBSHIBOKEN_API SbkObjectType *SbkObject_TypeF(void);
diff --git a/sources/shiboken2/libshiboken/bindingmanager.cpp b/sources/shiboken2/libshiboken/bindingmanager.cpp
index 1c38da81c..b35a02ef4 100644
--- a/sources/shiboken2/libshiboken/bindingmanager.cpp
+++ b/sources/shiboken2/libshiboken/bindingmanager.cpp
@@ -44,6 +44,7 @@
#include "sbkdbg.h"
#include "gilstate.h"
#include "sbkstring.h"
+#include "sbkstaticstrings.h"
#include "debugfreehook.h"
#include <cstddef>
@@ -273,7 +274,19 @@ SbkObject *BindingManager::retrieveWrapper(const void *cptr)
return iter->second;
}
-PyObject *BindingManager::getOverride(const void *cptr, PyObject *methodName)
+static bool mangleNameFlag(PyTypeObject *type)
+{
+ // PYSIDE-1019: See if a dict is set with a snake_case bit.
+ static PyTypeObject *old_dict_type = Py_TYPE(PyType_Type.tp_dict);
+ auto dict = type->tp_dict;
+ if (Py_TYPE(dict) == old_dict_type)
+ return false;
+ Shiboken::AutoDecRef select_id(PyObject_GetAttr(dict, Shiboken::PyName::select_id()));
+ auto id = PyInt_AsSsize_t(select_id);
+ return (id & 1) != 0;
+}
+
+PyObject *BindingManager::getOverride(const void *cptr, PyObject *methodNameCache[2], const char *methodName)
{
SbkObject *wrapper = retrieveWrapper(cptr);
// The refcount can be 0 if the object is dieing and someone called
@@ -281,15 +294,22 @@ PyObject *BindingManager::getOverride(const void *cptr, PyObject *methodName)
if (!wrapper || reinterpret_cast<const PyObject *>(wrapper)->ob_refcnt == 0)
return nullptr;
+ bool flag = mangleNameFlag(Py_TYPE(wrapper));
+ PyObject *pyMethodName = methodNameCache[flag]; // borrowed
+ if (pyMethodName == nullptr) {
+ pyMethodName = Shiboken::String::getSnakeCaseName(methodName, flag);
+ methodNameCache[flag] = pyMethodName;
+ }
+
if (wrapper->ob_dict) {
- PyObject *method = PyDict_GetItem(wrapper->ob_dict, methodName);
+ PyObject *method = PyDict_GetItem(wrapper->ob_dict, pyMethodName);
if (method) {
Py_INCREF(reinterpret_cast<PyObject *>(method));
return method;
}
}
- PyObject *method = PyObject_GetAttr(reinterpret_cast<PyObject *>(wrapper), methodName);
+ PyObject *method = PyObject_GetAttr(reinterpret_cast<PyObject *>(wrapper), pyMethodName);
if (method && PyMethod_Check(method)
&& PyMethod_GET_SELF(method) == reinterpret_cast<PyObject *>(wrapper)) {
@@ -301,7 +321,7 @@ PyObject *BindingManager::getOverride(const void *cptr, PyObject *methodName)
for (int i = 1; i < PyTuple_GET_SIZE(mro) - 1; i++) {
auto *parent = reinterpret_cast<PyTypeObject *>(PyTuple_GET_ITEM(mro, i));
if (parent->tp_dict) {
- defaultMethod = PyDict_GetItem(parent->tp_dict, methodName);
+ defaultMethod = PyDict_GetItem(parent->tp_dict, pyMethodName);
if (defaultMethod && PyMethod_GET_FUNCTION(method) != defaultMethod)
return method;
}
diff --git a/sources/shiboken2/libshiboken/bindingmanager.h b/sources/shiboken2/libshiboken/bindingmanager.h
index 0bcde196f..8882f402e 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 *methodName);
+ PyObject *getOverride(const void *cptr, PyObject *methodNameCache[2], const char *methodName);
void addClassInheritance(SbkObjectType *parent, SbkObjectType *child);
/**
diff --git a/sources/shiboken2/libshiboken/pep384impl.cpp b/sources/shiboken2/libshiboken/pep384impl.cpp
index 4149bbcbf..57d3de261 100644
--- a/sources/shiboken2/libshiboken/pep384impl.cpp
+++ b/sources/shiboken2/libshiboken/pep384impl.cpp
@@ -86,6 +86,7 @@ static PyMemberDef probe_members[] = {
#define probe_tp_repr make_dummy(2)
#define probe_tp_call make_dummy(3)
#define probe_tp_getattro make_dummy(16)
+#define probe_tp_setattro make_dummy(17)
#define probe_tp_str make_dummy(4)
#define probe_tp_traverse make_dummy(5)
#define probe_tp_clear make_dummy(6)
@@ -108,6 +109,7 @@ static PyType_Slot typeprobe_slots[] = {
{Py_tp_repr, probe_tp_repr},
{Py_tp_call, probe_tp_call},
{Py_tp_getattro, probe_tp_getattro},
+ {Py_tp_setattro, probe_tp_setattro},
{Py_tp_str, probe_tp_str},
{Py_tp_traverse, probe_tp_traverse},
{Py_tp_clear, probe_tp_clear},
@@ -153,6 +155,7 @@ check_PyTypeObject_valid()
|| probe_tp_repr != check->tp_repr
|| probe_tp_call != check->tp_call
|| probe_tp_getattro != check->tp_getattro
+ || probe_tp_setattro != check->tp_setattro
|| probe_tp_str != check->tp_str
|| probe_tp_traverse != check->tp_traverse
|| probe_tp_clear != check->tp_clear
diff --git a/sources/shiboken2/libshiboken/pep384impl.h b/sources/shiboken2/libshiboken/pep384impl.h
index 2a14d6543..973cf06ce 100644
--- a/sources/shiboken2/libshiboken/pep384impl.h
+++ b/sources/shiboken2/libshiboken/pep384impl.h
@@ -99,7 +99,7 @@ typedef struct _typeobject {
ternaryfunc tp_call;
reprfunc tp_str;
getattrofunc tp_getattro;
- void *X17; // setattrofunc tp_setattro;
+ setattrofunc tp_setattro;
void *X18; // PyBufferProcs *tp_as_buffer;
unsigned long tp_flags;
void *X20; // const char *tp_doc;
diff --git a/sources/shiboken2/libshiboken/sbkstring.cpp b/sources/shiboken2/libshiboken/sbkstring.cpp
index 092745d3d..ed8b61fc8 100644
--- a/sources/shiboken2/libshiboken/sbkstring.cpp
+++ b/sources/shiboken2/libshiboken/sbkstring.cpp
@@ -271,5 +271,49 @@ PyObject *createStaticString(const char *str)
return result;
}
+///////////////////////////////////////////////////////////////////////
+//
+// PYSIDE-1019: Helper function for snake_case vs. camelCase names
+// ---------------------------------------------------------------
+//
+// When renaming dict entries, `BindingManager::getOverride` must
+// use adapted names.
+//
+// This might become more complex when we need to register
+// exceptions from this rule.
+//
+
+PyObject *getSnakeCaseName(const char *name, bool lower)
+{
+ /*
+ * Convert `camelCase` to `snake_case`.
+ * Gives up when there are two consecutive upper chars.
+ *
+ * Also functions beginning with `gl` followed by upper case stay
+ * unchanged since that are the special OpenGL functions.
+ */
+ if (!lower
+ || strlen(name) < 3
+ || (name[0] == 'g' && name[1] == 'l' && isupper(name[2])))
+ return createStaticString(name);
+
+ char new_name[200 + 1] = {};
+ const char *p = name;
+ char *q = new_name;
+ for (; *p && q - new_name < 200; ++p, ++q) {
+ if (isupper(*p)) {
+ if (p != name && isupper(*(p - 1)))
+ return createStaticString(name);
+ *q = '_';
+ ++q;
+ *q = tolower(*p);
+ }
+ else {
+ *q = *p;
+ }
+ }
+ return createStaticString(new_name);
+}
+
} // namespace String
} // namespace Shiboken
diff --git a/sources/shiboken2/libshiboken/sbkstring.h b/sources/shiboken2/libshiboken/sbkstring.h
index 84d7768c5..3475d3acd 100644
--- a/sources/shiboken2/libshiboken/sbkstring.h
+++ b/sources/shiboken2/libshiboken/sbkstring.h
@@ -61,6 +61,7 @@ namespace String
LIBSHIBOKEN_API int compare(PyObject *val1, const char *val2);
LIBSHIBOKEN_API Py_ssize_t len(PyObject *str);
LIBSHIBOKEN_API PyObject *createStaticString(const char *str);
+ LIBSHIBOKEN_API PyObject *getSnakeCaseName(const char *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 58c1ff925..d088ea41b 100644
--- a/sources/shiboken2/shibokenmodule/files.dir/shibokensupport/__feature__.py
+++ b/sources/shiboken2/shibokenmodule/files.dir/shibokensupport/__feature__.py
@@ -49,7 +49,7 @@ similarity to Python's `__future__` file, but also some distinction.
import sys
all_feature_names = [
- "_dummy_feature_01",
+ "snake_case",
"_dummy_feature_02",
"_dummy_feature_04",
"_dummy_feature_08",
@@ -61,7 +61,7 @@ all_feature_names = [
__all__ = ["all_feature_names"] + all_feature_names
-_dummy_feature_01 = 0x01
+snake_case = 1
_dummy_feature_02 = 0x02
_dummy_feature_04 = 0x04
_dummy_feature_08 = 0x08
@@ -107,5 +107,8 @@ def _import(name, *args, **kwargs):
if isinstance(existing, int):
flag |= existing & 255
pyside_feature_dict[importing_module] = flag
+ if importing_module == "__main__":
+ # We need to add all modules here which should see __feature__.
+ pyside_feature_dict["rlcompleter"] = flag
return sys.modules["__feature__"]
return original_import(name, *args, **kwargs)