1#############################################################################
2##
3## Copyright (C) 2019 The Qt Company Ltd.
4## Contact: https://www.qt.io/licensing/
5##
6## This file is part of Qt for Python.
7##
8## $QT_BEGIN_LICENSE:LGPL$
9## Commercial License Usage
10## Licensees holding valid commercial Qt licenses may use this file in
11## accordance with the commercial license agreement provided with the
12## Software or, alternatively, in accordance with the terms contained in
13## a written agreement between you and The Qt Company. For licensing terms
14## and conditions see https://www.qt.io/terms-conditions. For further
15## information use the contact form at https://www.qt.io/contact-us.
16##
17## GNU Lesser General Public License Usage
18## Alternatively, this file may be used under the terms of the GNU Lesser
19## General Public License version 3 as published by the Free Software
20## Foundation and appearing in the file LICENSE.LGPL3 included in the
21## packaging of this file. Please review the following information to
22## ensure the GNU Lesser General Public License version 3 requirements
23## will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24##
25## GNU General Public License Usage
26## Alternatively, this file may be used under the terms of the GNU
27## General Public License version 2.0 or (at your option) the GNU General
28## Public license version 3 or any later version approved by the KDE Free
29## Qt Foundation. The licenses are as published by the Free Software
30## Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31## included in the packaging of this file. Please review the following
32## information to ensure the GNU General Public License requirements will
33## be met: https://www.gnu.org/licenses/gpl-2.0.html and
34## https://www.gnu.org/licenses/gpl-3.0.html.
35##
36## $QT_END_LICENSE$
37##
38#############################################################################
39
40from __future__ import print_function, absolute_import
41
42"""
43mapping.py
44
45This module has the mapping from the pyside C-modules view of signatures
46to the Python representation.
47
48The PySide modules are not loaded in advance, but only after they appear
49in sys.modules. This minimizes the loading overhead.
50"""
51
52import sys
53import struct
54import os
55
56from shibokensupport.signature import typing
57from shibokensupport.signature.typing import TypeVar, Generic
58from shibokensupport.signature.lib.tool import with_metaclass
59
60class ellipsis(object):
61    def __repr__(self):
62        return "..."
63
64ellipsis = ellipsis()
65Point = typing.Tuple[float, float]
66Variant = typing.Any
67ModelIndexList = typing.List[int]
68QImageCleanupFunction = typing.Callable
69
70# unfortunately, typing.Optional[t] expands to typing.Union[t, NoneType]
71# Until we can force it to create Optional[t] again, we use this.
72NoneType = type(None)
73
74_S = TypeVar("_S")
75
76MultiMap = typing.DefaultDict[str, typing.List[str]]
77
78# ulong_max is only 32 bit on windows.
79ulong_max = 2*sys.maxsize+1 if len(struct.pack("L", 1)) != 4 else 0xffffffff
80ushort_max = 0xffff
81
82GL_COLOR_BUFFER_BIT = 0x00004000
83GL_NEAREST = 0x2600
84
85WId = int
86
87# from 5.9
88GL_TEXTURE_2D = 0x0DE1
89GL_RGBA = 0x1908
90
91
92class _NotCalled(str):
93    """
94    Wrap some text with semantics
95
96    This class is wrapped around text in order to avoid calling it.
97    There are three reasons for this:
98
99      - some instances cannot be created since they are abstract,
100      - some can only be created after qApp was created,
101      - some have an ugly __repr__ with angle brackets in it.
102
103    By using derived classes, good looking instances can be created
104    which can be used to generate source code or .pyi files. When the
105    real object is needed, the wrapper can simply be called.
106    """
107    def __repr__(self):
108        return "{}({})".format(type(self).__name__, self)
109
110    def __call__(self):
111        from shibokensupport.signature.mapping import __dict__ as namespace
112        text = self if self.endswith(")") else self + "()"
113        return eval(text, namespace)
114
115USE_PEP563 = False
116# Note: we cannot know if this feature has been imported.
117# Otherwise it would be "sys.version_info[:2] >= (3, 7)".
118# We *can* eventually inspect sys.modules and look if
119# the calling module has this future statement set,
120# but should we do that?
121
122
123# Some types are abstract. They just show their name.
124class Virtual(_NotCalled):
125    pass
126
127# Other types I simply could not find.
128class Missing(_NotCalled):
129    # The string must be quoted, because the object does not exist.
130    def __repr__(self):
131        if USE_PEP563:
132            return _NotCalled.__repr__(self)
133        return '{}("{}")'.format(type(self).__name__, self)
134
135
136class Invalid(_NotCalled):
137    pass
138
139# Helper types
140class Default(_NotCalled):
141    pass
142
143
144class Instance(_NotCalled):
145    pass
146
147# Parameterized primitive variables
148class _Parameterized(object):
149    def __init__(self, type):
150        self.type = type
151        self.__name__ = self.__class__.__name__
152
153    def __repr__(self):
154        return "{}({})".format(
155            type(self).__name__, self.type.__name__)
156
157# Mark the primitive variables to be moved into the result.
158class ResultVariable(_Parameterized):
159    pass
160
161# Mark the primitive variables to become Sequence, Iterable or List
162# (decided in the parser).
163class ArrayLikeVariable(_Parameterized):
164    pass
165
166StringList = ArrayLikeVariable(str)
167
168
169class Reloader(object):
170    """
171    Reloder class
172
173    This is a singleton class which provides the update function for the
174    shiboken and PySide classes.
175    """
176    def __init__(self):
177        self.sys_module_count = 0
178
179    @staticmethod
180    def module_valid(mod):
181        if getattr(mod, "__file__", None) and not os.path.isdir(mod.__file__):
182            ending = os.path.splitext(mod.__file__)[-1]
183            return ending not in (".py", ".pyc", ".pyo", ".pyi")
184        return False
185
186    def update(self):
187        """
188        'update' imports all binary modules which are already in sys.modules.
189        The reason is to follow all user imports without introducing new ones.
190        This function is called by pyside_type_init to adapt imports
191        when the number of imported modules has changed.
192        """
193        if self.sys_module_count == len(sys.modules):
194            return
195        self.sys_module_count = len(sys.modules)
196        g = globals()
197        # PYSIDE-1009: Try to recognize unknown modules in errorhandler.py
198        candidates = list(mod_name for mod_name in sys.modules.copy()
199                          if self.module_valid(sys.modules[mod_name]))
200        for mod_name in candidates:
201            # 'top' is PySide2 when we do 'import PySide.QtCore'
202            # or Shiboken if we do 'import Shiboken'.
203            # Convince yourself that these two lines below have the same
204            # global effect as "import Shiboken" or "import PySide2.QtCore".
205            top = __import__(mod_name)
206            g[top.__name__] = top
207            proc_name = "init_" + mod_name.replace(".", "_")
208            if proc_name in g:
209                # Modules are in place, we can update the type_map.
210                g.update(g.pop(proc_name)())
211
212
213def check_module(mod):
214    # During a build, there exist the modules already as directories,
215    # although the '*.so' was not yet created. This causes a problem
216    # in Python 3, because it accepts folders as namespace modules
217    # without enforcing an '__init__.py'.
218    if not Reloader.module_valid(mod):
219        mod_name = mod.__name__
220        raise ImportError("Module '{mod_name}' is not a binary module!"
221                          .format(**locals()))
222
223update_mapping = Reloader().update
224type_map = {}
225namespace = globals()  # our module's __dict__
226
227type_map.update({
228    "...": ellipsis,
229    "bool": bool,
230    "char": int,
231    "char*": str,
232    "char*const": str,
233    "double": float,
234    "float": float,
235    "int": int,
236    "List": ArrayLikeVariable,
237    "long": int,
238    "PyCallable": typing.Callable,
239    "PyObject": object,
240    "PySequence": typing.Iterable,  # important for numpy
241    "PyTypeObject": type,
242    "QChar": str,
243    "QHash": typing.Dict,
244    "qint16": int,
245    "qint32": int,
246    "qint64": int,
247    "qint8": int,
248    "qintptr": int,
249    "QList": ArrayLikeVariable,
250    "qlonglong": int,
251    "QMap": typing.Dict,
252    "QPair": typing.Tuple,
253    "qptrdiff": int,
254    "qreal": float,
255    "QSet": typing.Set,
256    "QString": str,
257    "QStringList": StringList,
258    "quint16": int,
259    "quint32": int,
260    "quint32": int,
261    "quint64": int,
262    "quint8": int,
263    "quintptr": int,
264    "qulonglong": int,
265    "QVariant": Variant,
266    "QVector": typing.List,
267    "QSharedPointer": typing.Tuple,
268    "real": float,
269    "short": int,
270    "signed char": int,
271    "signed long": int,
272    "std.list": typing.List,
273    "std.map": typing.Dict,
274    "std.pair": typing.Tuple,
275    "std.vector": typing.List,
276    "str": str,
277    "true": True,
278    "Tuple": typing.Tuple,
279    "uchar": int,
280    "uchar*": str,
281    "uint": int,
282    "ulong": int,
283    "ULONG_MAX": ulong_max,
284    "unsigned char": int, # 5.9
285    "unsigned char*": str,
286    "unsigned int": int,
287    "unsigned long int": int, # 5.6, RHEL 6.6
288    "unsigned long long": int,
289    "unsigned long": int,
290    "unsigned short int": int, # 5.6, RHEL 6.6
291    "unsigned short": int,
292    "Unspecified": None,
293    "ushort": int,
294    "void": int, # be more specific?
295    "WId": WId,
296    "zero(bytes)": b"",
297    "zero(Char)": 0,
298    "zero(float)": 0,
299    "zero(int)": 0,
300    "zero(object)": None,
301    "zero(str)": "",
302    "zero(typing.Any)": None,
303    })
304
305type_map.update({
306    # Handling variables declared as array:
307    "array double*"         : ArrayLikeVariable(float),
308    "array float*"          : ArrayLikeVariable(float),
309    "array GLint*"          : ArrayLikeVariable(int),
310    "array GLuint*"         : ArrayLikeVariable(int),
311    "array int*"            : ArrayLikeVariable(int),
312    "array long long*"      : ArrayLikeVariable(int),
313    "array long*"           : ArrayLikeVariable(int),
314    "array short*"          : ArrayLikeVariable(int),
315    "array signed char*"    : bytes,
316    "array unsigned char*"  : bytes,
317    "array unsigned int*"   : ArrayLikeVariable(int),
318    "array unsigned short*" : ArrayLikeVariable(int),
319    })
320
321type_map.update({
322    # Special cases:
323    "char*"         : bytes,
324    "QChar*"        : bytes,
325    "quint32*"      : int,        # only for QRandomGenerator
326    "quint8*"       : bytearray,  # only for QCborStreamReader and QCborValue
327    "uchar*"        : bytes,
328    "unsigned char*": bytes,
329    })
330
331type_map.update({
332    # Handling variables that are returned, eventually as Tuples:
333    "bool*"         : ResultVariable(bool),
334    "float*"        : ResultVariable(float),
335    "int*"          : ResultVariable(int),
336    "long long*"    : ResultVariable(int),
337    "long*"         : ResultVariable(int),
338    "PStr*"         : ResultVariable(str),  # module sample
339    "qint32*"       : ResultVariable(int),
340    "qint64*"       : ResultVariable(int),
341    "qreal*"        : ResultVariable(float),
342    "QString*"      : ResultVariable(str),
343    "quint16*"      : ResultVariable(int),
344    "uint*"         : ResultVariable(int),
345    "unsigned int*" : ResultVariable(int),
346    "QStringList*"  : ResultVariable(StringList),
347    })
348
349# PYSIDE-1328: We need to handle "self" explicitly.
350type_map.update({
351    "self"  : "self",
352    })
353
354
355# The Shiboken Part
356def init_Shiboken():
357    type_map.update({
358        "PyType": type,
359        "shiboken2.bool": bool,
360        "size_t": int,
361    })
362    return locals()
363
364
365def init_minimal():
366    type_map.update({
367        "MinBool": bool,
368    })
369    return locals()
370
371
372def init_sample():
373    import datetime
374    type_map.update({
375        "char": int,
376        "char**": typing.List[str],
377        "Complex": complex,
378        "double": float,
379        "Foo.HANDLE": int,
380        "HANDLE": int,
381        "Null": None,
382        "nullptr": None,
383        "ObjectType.Identifier": Missing("sample.ObjectType.Identifier"),
384        "OddBool": bool,
385        "PStr": str,
386        "PyDate": datetime.date,
387        "sample.bool": bool,
388        "sample.char": int,
389        "sample.double": float,
390        "sample.int": int,
391        "sample.ObjectType": object,
392        "sample.OddBool": bool,
393        "sample.Photon.TemplateBase[Photon.DuplicatorType]": sample.Photon.ValueDuplicator,
394        "sample.Photon.TemplateBase[Photon.IdentityType]": sample.Photon.ValueIdentity,
395        "sample.Point": Point,
396        "sample.PStr": str,
397        "sample.unsigned char": int,
398        "std.size_t": int,
399        "std.string": str,
400        "ZeroIn": 0,
401        'Str("<unk")': "<unk",
402        'Str("<unknown>")': "<unknown>",
403        'Str("nown>")': "nown>",
404    })
405    return locals()
406
407
408def init_other():
409    import numbers
410    type_map.update({
411        "other.ExtendsNoImplicitConversion": Missing("other.ExtendsNoImplicitConversion"),
412        "other.Number": numbers.Number,
413    })
414    return locals()
415
416
417def init_smart():
418    # This missing type should be defined in module smart. We cannot set it to Missing()
419    # because it is a container type. Therefore, we supply a surrogate:
420    global SharedPtr
421    class SharedPtr(Generic[_S]):
422        __module__ = "smart"
423    smart.SharedPtr = SharedPtr
424    type_map.update({
425        "smart.Smart.Integer2": int,
426    })
427    return locals()
428
429
430# The PySide Part
431def init_PySide2_QtCore():
432    from PySide2.QtCore import Qt, QUrl, QDir
433    from PySide2.QtCore import QRect, QSize, QPoint, QLocale, QByteArray
434    from PySide2.QtCore import QMarginsF # 5.9
435    try:
436        # seems to be not generated by 5.9 ATM.
437        from PySide2.QtCore import Connection
438    except ImportError:
439        pass
440    type_map.update({
441        "' '": " ",
442        "'%'": "%",
443        "'g'": "g",
444        "4294967295UL": 4294967295, # 5.6, RHEL 6.6
445        "CheckIndexOption.NoOption": Instance(
446            "PySide2.QtCore.QAbstractItemModel.CheckIndexOptions.NoOption"), # 5.11
447        "DescriptorType(-1)": int,  # Native handle of QSocketDescriptor
448        "false": False,
449        "list of QAbstractAnimation": typing.List[PySide2.QtCore.QAbstractAnimation],
450        "list of QAbstractState": typing.List[PySide2.QtCore.QAbstractState],
451        "long long": int,
452        "NULL": None, # 5.6, MSVC
453        "nullptr": None, # 5.9
454        "PyByteArray": bytearray,
455        "PyBytes": bytes,
456        "QDeadlineTimer(QDeadlineTimer.Forever)": Instance("PySide2.QtCore.QDeadlineTimer"),
457        "PySide2.QtCore.QUrl.ComponentFormattingOptions":
458            PySide2.QtCore.QUrl.ComponentFormattingOption, # mismatch option/enum, why???
459        "PyUnicode": typing.Text,
460        "Q_NULLPTR": None,
461        "QDir.Filters(AllEntries | NoDotAndDotDot)": Instance(
462            "QDir.Filters(QDir.AllEntries | QDir.NoDotAndDotDot)"),
463        "QDir.SortFlags(Name | IgnoreCase)": Instance(
464            "QDir.SortFlags(QDir.Name | QDir.IgnoreCase)"),
465        "QGenericArgument((0))": ellipsis, # 5.6, RHEL 6.6. Is that ok?
466        "QGenericArgument()": ellipsis,
467        "QGenericArgument(0)": ellipsis,
468        "QGenericArgument(NULL)": ellipsis, # 5.6, MSVC
469        "QGenericArgument(nullptr)": ellipsis, # 5.10
470        "QGenericArgument(Q_NULLPTR)": ellipsis,
471        "QJsonObject": typing.Dict[str, PySide2.QtCore.QJsonValue],
472        "QModelIndex()": Invalid("PySide2.QtCore.QModelIndex"), # repr is btw. very wrong, fix it?!
473        "QModelIndexList": ModelIndexList,
474        "QModelIndexList": ModelIndexList,
475        "QString()": "",
476        "QStringList()": [],
477        "QStringRef": str,
478        "QStringRef": str,
479        "Qt.HANDLE": int, # be more explicit with some constants?
480        "QUrl.FormattingOptions(PrettyDecoded)": Instance(
481            "QUrl.FormattingOptions(QUrl.PrettyDecoded)"),
482        "QVariant()": Invalid(Variant),
483        "QVariant.Type": type, # not so sure here...
484        "QVariantMap": typing.Dict[str, Variant],
485        "QVariantMap": typing.Dict[str, Variant],
486    })
487    try:
488        type_map.update({
489            "PySide2.QtCore.QMetaObject.Connection": PySide2.QtCore.Connection, # wrong!
490        })
491    except AttributeError:
492        # this does not exist on 5.9 ATM.
493        pass
494    return locals()
495
496
497def init_PySide2_QtConcurrent():
498    type_map.update({
499        "PySide2.QtCore.QFuture[QString]":
500        PySide2.QtConcurrent.QFutureQString,
501        "PySide2.QtCore.QFuture[void]":
502        PySide2.QtConcurrent.QFutureVoid,
503    })
504    return locals()
505
506
507def init_PySide2_QtGui():
508    from PySide2.QtGui import QPageLayout, QPageSize # 5.12 macOS
509    type_map.update({
510        "0.0f": 0.0,
511        "1.0f": 1.0,
512        "GL_COLOR_BUFFER_BIT": GL_COLOR_BUFFER_BIT,
513        "GL_NEAREST": GL_NEAREST,
514        "int32_t": int,
515        "QPixmap()": Default("PySide2.QtGui.QPixmap"), # can't create without qApp
516        "QPlatformSurface*": int, # a handle
517        "QVector< QTextLayout.FormatRange >()": [], # do we need more structure?
518        "uint32_t": int,
519        "uint8_t": int,
520        "USHRT_MAX": ushort_max,
521    })
522    return locals()
523
524
525def init_PySide2_QtWidgets():
526    from PySide2.QtWidgets import QWidget, QMessageBox, QStyleOption, QStyleHintReturn, QStyleOptionComplex
527    from PySide2.QtWidgets import QGraphicsItem, QStyleOptionGraphicsItem # 5.9
528    type_map.update({
529        "QMessageBox.StandardButtons(Yes | No)": Instance(
530            "QMessageBox.StandardButtons(QMessageBox.Yes | QMessageBox.No)"),
531        "QWidget.RenderFlags(DrawWindowBackground | DrawChildren)": Instance(
532            "QWidget.RenderFlags(QWidget.DrawWindowBackground | QWidget.DrawChildren)"),
533        "SH_Default": QStyleHintReturn.SH_Default,
534        "SO_Complex": QStyleOptionComplex.SO_Complex,
535        "SO_Default": QStyleOption.SO_Default,
536        "static_cast<Qt.MatchFlags>(Qt.MatchExactly|Qt.MatchCaseSensitive)": Instance(
537            "Qt.MatchFlags(Qt.MatchExactly | Qt.MatchCaseSensitive)"),
538        "Type": PySide2.QtWidgets.QListWidgetItem.Type,
539    })
540    return locals()
541
542
543def init_PySide2_QtSql():
544    from PySide2.QtSql import QSqlDatabase
545    type_map.update({
546        "QLatin1String(defaultConnection)": QSqlDatabase.defaultConnection,
547        "QVariant.Invalid": Invalid("Variant"), # not sure what I should create, here...
548    })
549    return locals()
550
551
552def init_PySide2_QtNetwork():
553    from PySide2.QtNetwork import QNetworkRequest
554    best_structure = typing.OrderedDict if getattr(typing, "OrderedDict", None) else typing.Dict
555    type_map.update({
556        "QMultiMap[PySide2.QtNetwork.QSsl.AlternativeNameEntryType, QString]":
557            best_structure[PySide2.QtNetwork.QSsl.AlternativeNameEntryType, typing.List[str]],
558        "DefaultTransferTimeoutConstant":
559            QNetworkRequest.TransferTimeoutConstant,
560        "QNetworkRequest.DefaultTransferTimeoutConstant":
561            QNetworkRequest.TransferTimeoutConstant,
562    })
563    del best_structure
564    return locals()
565
566
567def init_PySide2_QtXmlPatterns():
568    from PySide2.QtXmlPatterns import QXmlName
569    type_map.update({
570        "QXmlName.NamespaceCode": Missing("PySide2.QtXmlPatterns.QXmlName.NamespaceCode"),
571        "QXmlName.PrefixCode": Missing("PySide2.QtXmlPatterns.QXmlName.PrefixCode"),
572    })
573    return locals()
574
575
576def init_PySide2_QtMultimedia():
577    import PySide2.QtMultimediaWidgets
578    # Check if foreign import is valid. See mapping.py in shiboken2.
579    check_module(PySide2.QtMultimediaWidgets)
580    type_map.update({
581        "QGraphicsVideoItem": PySide2.QtMultimediaWidgets.QGraphicsVideoItem,
582        "qint64": int,
583        "QVideoWidget": PySide2.QtMultimediaWidgets.QVideoWidget,
584    })
585    return locals()
586
587
588def init_PySide2_QtOpenGL():
589    type_map.update({
590        "GLbitfield": int,
591        "GLenum": int,
592        "GLfloat": float, # 5.6, MSVC 15
593        "GLint": int,
594        "GLuint": int,
595    })
596    return locals()
597
598
599def init_PySide2_QtQml():
600    type_map.update({
601        "QJSValueList()": [],
602        "QVariantHash()": typing.Dict[str, Variant],    # from 5.9
603    })
604    return locals()
605
606
607def init_PySide2_QtQuick():
608    type_map.update({
609        "PySide2.QtQuick.QSharedPointer[PySide2.QtQuick.QQuickItemGrabResult]":
610            PySide2.QtQuick.QQuickItemGrabResult,
611        "UnsignedShortType": int,
612    })
613    return locals()
614
615
616def init_PySide2_QtScript():
617    type_map.update({
618        "QScriptValueList()": [],
619    })
620    return locals()
621
622
623def init_PySide2_QtTest():
624    type_map.update({
625        "PySide2.QtTest.QTest.PySideQTouchEventSequence": PySide2.QtTest.QTest.QTouchEventSequence,
626        "PySide2.QtTest.QTouchEventSequence": PySide2.QtTest.QTest.QTouchEventSequence,
627    })
628    return locals()
629
630# from 5.6, MSVC
631def init_PySide2_QtWinExtras():
632    type_map.update({
633        "QList< QWinJumpListItem* >()": [],
634    })
635    return locals()
636
637# from 5.12, macOS
638def init_PySide2_QtDataVisualization():
639    from PySide2.QtDataVisualization import QtDataVisualization
640    QtDataVisualization.QBarDataRow = typing.List[QtDataVisualization.QBarDataItem]
641    QtDataVisualization.QBarDataArray = typing.List[QtDataVisualization.QBarDataRow]
642    QtDataVisualization.QSurfaceDataRow = typing.List[QtDataVisualization.QSurfaceDataItem]
643    QtDataVisualization.QSurfaceDataArray = typing.List[QtDataVisualization.QSurfaceDataRow]
644    type_map.update({
645        "100.0f": 100.0,
646        "QtDataVisualization.QBarDataArray": QtDataVisualization.QBarDataArray,
647        "QtDataVisualization.QBarDataArray*": QtDataVisualization.QBarDataArray,
648        "QtDataVisualization.QSurfaceDataArray": QtDataVisualization.QSurfaceDataArray,
649        "QtDataVisualization.QSurfaceDataArray*": QtDataVisualization.QSurfaceDataArray,
650    })
651    return locals()
652
653
654def init_testbinding():
655    type_map.update({
656        "testbinding.PySideCPP2.TestObjectWithoutNamespace": testbinding.TestObjectWithoutNamespace,
657    })
658    return locals()
659
660# end of file
661