diff options
author | Friedemann Kleint <Friedemann.Kleint@qt.io> | 2021-08-31 08:40:30 +0200 |
---|---|---|
committer | Friedemann Kleint <Friedemann.Kleint@qt.io> | 2021-08-31 17:28:08 +0200 |
commit | 34611847c6ffa59cde54e43788806c18d063b49d (patch) | |
tree | 5ce94b02e3d28c034a89e22cbae71b089bceef09 | |
parent | 600e15a5ddca2119fab79ef618e22ad5abc8e8f0 (diff) |
PySide6: Add QAccessible::installFactory()
Fixes: PYSIDE-1650
Change-Id: I05926888aa4de8c3a34c712ed2000ba59e524833
Reviewed-by: Cristian Maureira-Fredes <cristian.maureira-fredes@qt.io>
-rw-r--r-- | sources/pyside6/PySide6/QtGui/typesystem_gui_common.xml | 11 | ||||
-rw-r--r-- | sources/pyside6/PySide6/glue/qtgui.cpp | 95 | ||||
-rw-r--r-- | sources/pyside6/tests/QtWidgets/CMakeLists.txt | 1 | ||||
-rw-r--r-- | sources/pyside6/tests/QtWidgets/qaccessible_test.py | 163 |
4 files changed, 270 insertions, 0 deletions
diff --git a/sources/pyside6/PySide6/QtGui/typesystem_gui_common.xml b/sources/pyside6/PySide6/QtGui/typesystem_gui_common.xml index fa68db95b..96fc37c88 100644 --- a/sources/pyside6/PySide6/QtGui/typesystem_gui_common.xml +++ b/sources/pyside6/PySide6/QtGui/typesystem_gui_common.xml @@ -179,6 +179,12 @@ </object-type> <value-type name="QAccessible"> + <extra-includes> + <include file-name="QtGui/QAccessibleInterface" location="global"/> + </extra-includes> + <inject-code class="native" position="beginning" + file="../glue/qtgui.cpp" + snippet="qaccessible-pysidefactory"/> <value-type name="State"/> <enum-type name="Event"/> <enum-type name="Role"/> @@ -186,6 +192,11 @@ <enum-type name="RelationFlag" flags="Relation"/> <enum-type name="InterfaceType"/> <enum-type name="TextBoundaryType"/> + <add-function signature="installFactory(PyCallable)" static="yes"> + <inject-code class="target" position="beginning" + file="../glue/qtgui.cpp" + snippet="qaccessible-installfactory"/> + </add-function> </value-type> <object-type name="QAccessibleActionInterface"/> diff --git a/sources/pyside6/PySide6/glue/qtgui.cpp b/sources/pyside6/PySide6/glue/qtgui.cpp index 5874dc608..81181dac4 100644 --- a/sources/pyside6/PySide6/glue/qtgui.cpp +++ b/sources/pyside6/PySide6/glue/qtgui.cpp @@ -41,6 +41,101 @@ * INJECT CODE ********************************************************************/ +// @snippet qaccessible-pysidefactory +// Helper for QAccessible::installFactory() that forwards the calls to +// Python callables. +class PySideAccessibleFactory +{ + PySideAccessibleFactory() = default; +public: + ~PySideAccessibleFactory(); + + static PySideAccessibleFactory *instance() { return m_instance; } + static PySideAccessibleFactory *ensureInstance(); + + static void installFactory(PyObject *f); + static void cleanup(); + + static QAccessibleInterface *factory(const QString &key, QObject *o); + +private: + QAccessibleInterface *callFactories(const QString &key, QObject *o); + + static PySideAccessibleFactory *m_instance; + + QList<PyObject *> m_factoryFunctions; + QList<PyObject *> m_objects; +}; + +PySideAccessibleFactory *PySideAccessibleFactory::m_instance = nullptr; + +PySideAccessibleFactory::~PySideAccessibleFactory() +{ + QAccessible::removeFactory(PySideAccessibleFactory::factory); + if (!m_factoryFunctions.isEmpty()) { + Shiboken::GilState state; + for (auto *f : m_factoryFunctions) + Py_DECREF(f); + for (auto *o : m_objects) + Py_DECREF(o); + } +} + +PySideAccessibleFactory *PySideAccessibleFactory::ensureInstance() +{ + if (m_instance == nullptr) { + m_instance = new PySideAccessibleFactory; + QAccessible::installFactory(PySideAccessibleFactory::factory); + qAddPostRoutine(PySideAccessibleFactory::cleanup); + } + return m_instance; +} + +void PySideAccessibleFactory::installFactory(PyObject *f) +{ + if (m_instance != nullptr) { + Py_INCREF(f); + m_instance->m_factoryFunctions.append(f); + } +} + +void PySideAccessibleFactory::cleanup() +{ + delete m_instance; + m_instance = nullptr; +} + +QAccessibleInterface *PySideAccessibleFactory::factory(const QString &key, QObject *o) +{ + return m_instance ? m_instance->callFactories(key, o) : nullptr; +} + +QAccessibleInterface *PySideAccessibleFactory::callFactories(const QString &key, QObject *o) +{ + Shiboken::GilState state; + Shiboken::AutoDecRef arglist(PyTuple_New(2)); + PyTuple_SET_ITEM(arglist, 0, %CONVERTTOPYTHON[QString](key)); + PyTuple_SET_ITEM(arglist, 1, %CONVERTTOPYTHON[QObject *](o)); + + for (auto *f : m_factoryFunctions) { + if (PyObject *pyResult = PyObject_CallObject(f, arglist)) { + if (pyResult != Py_None) { + m_objects.append(pyResult); + QAccessibleInterface* result = %CONVERTTOCPP[QAccessibleInterface *](pyResult); + return result; + } + Py_DECREF(pyResult); + } + } + + return nullptr; +} +// @snippet qaccessible-pysidefactory + +// @snippet qaccessible-installfactory +PySideAccessibleFactory::ensureInstance()->installFactory(%1); +// @snippet qaccessible-installfactory + // @snippet glgetshadersource GLsizei bufSize = 4096; GLsizei length = bufSize - 1; diff --git a/sources/pyside6/tests/QtWidgets/CMakeLists.txt b/sources/pyside6/tests/QtWidgets/CMakeLists.txt index afa5eae0a..5ec056a26 100644 --- a/sources/pyside6/tests/QtWidgets/CMakeLists.txt +++ b/sources/pyside6/tests/QtWidgets/CMakeLists.txt @@ -78,6 +78,7 @@ PYSIDE_TEST(parent_method_test.py) PYSIDE_TEST(private_mangle_test.py) PYSIDE_TEST(python_properties_test.py) PYSIDE_TEST(qabstracttextdocumentlayout_test.py) +PYSIDE_TEST(qaccessible_test.py) PYSIDE_TEST(qaction_test.py) PYSIDE_TEST(qapp_issue_585.py) PYSIDE_TEST(qapp_test.py) diff --git a/sources/pyside6/tests/QtWidgets/qaccessible_test.py b/sources/pyside6/tests/QtWidgets/qaccessible_test.py new file mode 100644 index 000000000..fe257659b --- /dev/null +++ b/sources/pyside6/tests/QtWidgets/qaccessible_test.py @@ -0,0 +1,163 @@ +############################################################################# +## +## Copyright (C) 2021 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 cases for QAccessible::installFactory().''' + +import os +import sys +import unittest + +from pathlib import Path +sys.path.append(os.fspath(Path(__file__).resolve().parents[1])) +from init_paths import init_test_paths +init_test_paths(False) + +from PySide6.QtCore import QCoreApplication, Qt +from PySide6.QtGui import QAccessible, QAccessibleInterface, QColor +from PySide6.QtWidgets import QWidget, QLineEdit, QVBoxLayout + +from helper.usesqapplication import UsesQApplication + + +class LineEditAccessible(QAccessibleInterface): + """Mimick a QAccessibleInterface implementation for QLineEdit.""" + + instance_count = 0 + + def __init__(self, widget): + super().__init__() + LineEditAccessible.instance_count += 1 + self._widget = widget + self._name = self._widget.objectName() + print('LineEditAccessible', self._name) + + def __del__(self): + LineEditAccessible.instance_count -= 1 + print('~LineEditAccessible', self._name) + + def actionInterface(self): + return None + + def backgroundColor(self): + return QColor(Qt.white) + + def child(self, index): + return None + + def childAt(self, x, y): + return None + + def childCount(self): + return 0 + + def focusChild(self): + return None + + def foregroundColor(self): + return QColor(Qt.black) + + def indexOfChild(self, child): + return -1 + + def isValid(self): + return True + + def object(self): + return self._widget + + def parent(self): + return None + + def rect(self): + return self._widget.geometry() + + def role(self): + return QAccessible.EditableText + + def setText(self, t, text): + pass + + def state(self): + return QAccessible.State() + + def tableCellInterface(self): + return None + + def tableInterface(self): + return None + + def text(self, t): + return self._widget.text() if t == QAccessible.Value else '' + + def textInterface(self): + return None + + def valueInterface(self): + return None + + def window(self): + return self._widget.window().windowHandle() + + +def accessible_factory(key, obj): + """Factory function for QAccessibleInterface for QLineEdit's.""" + if obj.metaObject().className() == 'QLineEdit': + return LineEditAccessible(obj) + return None + + +class Window(QWidget): + """Test window with 2 QLineEdit's.""" + def __init__(self): + super().__init__() + self.setObjectName('top') + layout = QVBoxLayout(self) + self.m_line_edit1 = QLineEdit("bla") + layout.addWidget(self.m_line_edit1) + self.m_line_edit2 = QLineEdit("bla") + layout.addWidget(self.m_line_edit2) + + +class QAccessibleTest(UsesQApplication): + """Test that LineEditAccessible instances are created for QLineEdit's.""" + + def setUp(self): + super().setUp() + QAccessible.installFactory(accessible_factory) + window = Window() + + def testLineEdits(self): + window = Window() + window.show() + while not window.windowHandle().isExposed(): + QCoreApplication.processEvents() + self.assertEqual(LineEditAccessible.instance_count, 2) + + +if __name__ == "__main__": + unittest.main() |