1#############################################################################
2##
3## Copyright (C) 2020 Riverbank Computing Limited.
4## Copyright (C) 2006 Thorsten Marek.
5## All right reserved.
6##
7## This file is part of PyQt.
8##
9## You may use this file under the terms of the GPL v2 or the revised BSD
10## license as follows:
11##
12## "Redistribution and use in source and binary forms, with or without
13## modification, are permitted provided that the following conditions are
14## met:
15##   * Redistributions of source code must retain the above copyright
16##     notice, this list of conditions and the following disclaimer.
17##   * Redistributions in binary form must reproduce the above copyright
18##     notice, this list of conditions and the following disclaimer in
19##     the documentation and/or other materials provided with the
20##     distribution.
21##   * Neither the name of the Riverbank Computing Limited nor the names
22##     of its contributors may be used to endorse or promote products
23##     derived from this software without specific prior written
24##     permission.
25##
26## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
27## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
28## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
29## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
30## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
31## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
32## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
33## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
34## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
35## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
36## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
37##
38#############################################################################
39
40
41import sys
42import re
43
44from .indenter import write_code
45from .misc import Literal, moduleMember
46
47if sys.hexversion >= 0x03000000:
48    from ..port_v3.proxy_base import ProxyBase
49    from ..port_v3.as_string import as_string
50else:
51    from ..port_v2.proxy_base import ProxyBase
52    from ..port_v2.as_string import as_string
53
54
55i18n_strings = []
56i18n_context = ""
57
58def i18n_print(string):
59    i18n_strings.append(string)
60
61def i18n_void_func(name):
62    def _printer(self, *args):
63        i18n_print("%s.%s(%s)" % (self, name, ", ".join(map(as_string, args))))
64    return _printer
65
66def i18n_func(name):
67    def _printer(self, rname, *args):
68        i18n_print("%s = %s.%s(%s)" % (rname, self, name, ", ".join(map(as_string, args))))
69        return Literal(rname)
70
71    return _printer
72
73def strict_getattr(module, clsname):
74    cls = getattr(module, clsname)
75    if issubclass(cls, LiteralProxyClass):
76        raise AttributeError(cls)
77    else:
78        return cls
79
80
81class i18n_string(object):
82    def __init__(self, string, disambig):
83        self.string = string
84        self.disambig = disambig
85
86    def __str__(self):
87        if self.disambig is None:
88            return '_translate("%s", %s)' % (i18n_context, as_string(self.string))
89
90        return '_translate("%s", %s, %s)' % (i18n_context, as_string(self.string), as_string(self.disambig))
91
92
93# Classes with this flag will be handled as literal values. If functions are
94# called on these classes, the literal value changes.
95# Example:
96# the code
97# >>> QSize(9,10).expandedTo(...)
98# will print just that code.
99AS_ARGUMENT = 0x02
100
101# Classes with this flag may have members that are signals which themselves
102# will have a connect() member.
103AS_SIGNAL = 0x01
104
105# ATTENTION: currently, classes can either be literal or normal. If a class
106# should need both kinds of behaviour, the code has to be changed.
107
108class ProxyClassMember(object):
109    def __init__(self, proxy, function_name, flags):
110        self.proxy = proxy
111        self.function_name = function_name
112        self.flags = flags
113
114    def __str__(self):
115        return "%s.%s" % (self.proxy, self.function_name)
116
117    def __call__(self, *args):
118        if self.function_name == 'setProperty':
119            str_args = (as_string(args[0]), as_string(args[1]))
120        else:
121            str_args = map(as_string, args)
122
123        func_call = "%s.%s(%s)" % (self.proxy,
124                                   self.function_name,
125                                   ", ".join(str_args))
126        if self.flags & AS_ARGUMENT:
127            self.proxy._uic_name = func_call
128            return self.proxy
129        else:
130            needs_translation = False
131            for arg in args:
132                if isinstance(arg, i18n_string):
133                    needs_translation = True
134            if needs_translation:
135                i18n_print(func_call)
136            else:
137                write_code(func_call)
138
139    def __getattribute__(self, attribute):
140        """ Reimplemented to create a proxy connect() if requested and this
141        might be a proxy for a signal.
142        """
143
144        try:
145            return object.__getattribute__(self, attribute)
146        except AttributeError:
147            if attribute == 'connect' and self.flags & AS_SIGNAL:
148                return ProxyClassMember(self, attribute, 0)
149
150            raise
151
152    def __getitem__(self, idx):
153        """ Reimplemented to create a proxy member that should be a signal that
154        passes arguments.  We handle signals without arguments before we get
155        here and never apply the index notation to them.
156        """
157
158        return ProxySignalWithArguments(self.proxy, self.function_name, idx)
159
160
161class ProxySignalWithArguments(object):
162    """ This is a proxy for (what should be) a signal that passes arguments.
163    """
164
165    def __init__(self, sender, signal_name, signal_index):
166        self._sender = sender
167        self._signal_name = signal_name
168
169        # Convert the signal index, which will be a single argument or a tuple
170        # of arguments, to quoted strings.
171        if isinstance(signal_index, tuple):
172            self._signal_index = ','.join(["'%s'" % a for a in signal_index])
173        else:
174            self._signal_index = "'%s'" % signal_index
175
176    def connect(self, slot):
177        write_code("%s.%s[%s].connect(%s)" % (self._sender, self._signal_name, self._signal_index, slot))
178
179
180class ProxyClass(ProxyBase):
181    flags = 0
182
183    def __init__(self, objectname, is_attribute, args=(), noInstantiation=False):
184        if objectname:
185            if is_attribute:
186                objectname = "self." + objectname
187
188            self._uic_name = objectname
189        else:
190            self._uic_name = "Unnamed"
191
192        if not noInstantiation:
193            funcall = "%s(%s)" % \
194                    (moduleMember(self.module, self.__class__.__name__),
195                    ", ".join(map(str, args)))
196
197            if objectname:
198                funcall = "%s = %s" % (objectname, funcall)
199
200            write_code(funcall)
201
202    def __str__(self):
203        return self._uic_name
204
205    def __getattribute__(self, attribute):
206        try:
207            return object.__getattribute__(self, attribute)
208        except AttributeError:
209            return ProxyClassMember(self, attribute, self.flags)
210
211
212class LiteralProxyClass(ProxyClass):
213    """LiteralObject(*args) -> new literal class
214
215    a literal class can be used as argument in a function call
216
217    >>> class Foo(LiteralProxyClass): pass
218    >>> str(Foo(1,2,3)) == "Foo(1,2,3)"
219    """
220    flags = AS_ARGUMENT
221
222    def __init__(self, *args):
223        self._uic_name = "%s(%s)" % \
224                     (moduleMember(self.module, self.__class__.__name__),
225                      ", ".join(map(as_string, args)))
226
227
228class ProxyNamespace(ProxyBase):
229    pass
230
231
232# These are all the Qt classes used by pyuic5 in their namespaces. If a class
233# is missing, the compiler will fail, normally with an AttributeError.
234#
235# For adding new classes:
236#     - utility classes used as literal values do not need to be listed
237#       because they are created on the fly as subclasses of LiteralProxyClass
238#     - classes which are *not* QWidgets inherit from ProxyClass and they
239#       have to be listed explicitly in the correct namespace. These classes
240#       are created via a ProxyQObjectCreator
241#     - new QWidget-derived classes have to inherit from qtproxies.QWidget
242#       If the widget does not need any special methods, it can be listed
243#       in _qwidgets
244
245class QtCore(ProxyNamespace):
246    class Qt(ProxyNamespace):
247        pass
248
249    ## connectSlotsByName and connect have to be handled as class methods,
250    ## otherwise they would be created as LiteralProxyClasses and never be
251    ## printed
252    class QMetaObject(ProxyClass):
253        @classmethod
254        def connectSlotsByName(cls, *args):
255            ProxyClassMember(cls, "connectSlotsByName", 0)(*args)
256
257    class QObject(ProxyClass):
258        flags = AS_SIGNAL
259
260        def metaObject(self):
261            class _FakeMetaObject(object):
262                def className(*args):
263                    return self.__class__.__name__
264            return _FakeMetaObject()
265
266        def objectName(self):
267            return self._uic_name.split(".")[-1]
268
269
270class QtGui(ProxyNamespace):
271    class QIcon(ProxyClass):
272        class fromTheme(ProxyClass): pass
273
274    class QConicalGradient(ProxyClass): pass
275    class QLinearGradient(ProxyClass): pass
276    class QRadialGradient(ProxyClass): pass
277    class QBrush(ProxyClass): pass
278    class QPainter(ProxyClass): pass
279    class QPalette(ProxyClass): pass
280    class QFont(ProxyClass): pass
281
282
283# These sub-class QWidget but aren't themselves sub-classed.
284_qwidgets = ("QCalendarWidget", "QDialogButtonBox", "QDockWidget", "QGroupBox",
285        "QLineEdit", "QMainWindow", "QMenuBar", "QOpenGLWidget",
286        "QProgressBar", "QStatusBar", "QToolBar", "QWizardPage")
287
288class QtWidgets(ProxyNamespace):
289    class QApplication(QtCore.QObject):
290        @staticmethod
291        def translate(uiname, text, disambig):
292            return i18n_string(text or "", disambig)
293
294    class QSpacerItem(ProxyClass): pass
295    class QSizePolicy(ProxyClass): pass
296    # QActions inherit from QObject for the meta-object stuff and the hierarchy
297    # has to be correct since we have a isinstance(x, QtWidgets.QLayout) call
298    # in the UI parser.
299    class QAction(QtCore.QObject): pass
300    class QActionGroup(QtCore.QObject): pass
301    class QButtonGroup(QtCore.QObject): pass
302    class QLayout(QtCore.QObject): pass
303    class QGridLayout(QLayout): pass
304    class QBoxLayout(QLayout): pass
305    class QHBoxLayout(QBoxLayout): pass
306    class QVBoxLayout(QBoxLayout): pass
307    class QFormLayout(QLayout): pass
308
309    class QWidget(QtCore.QObject):
310        def font(self):
311            return Literal("%s.font()" % self)
312
313        def minimumSizeHint(self):
314            return Literal("%s.minimumSizeHint()" % self)
315
316        def sizePolicy(self):
317            sp = LiteralProxyClass()
318            sp._uic_name = "%s.sizePolicy()" % self
319            return sp
320
321    class QDialog(QWidget): pass
322    class QColorDialog(QDialog): pass
323    class QFileDialog(QDialog): pass
324    class QFontDialog(QDialog): pass
325    class QInputDialog(QDialog): pass
326    class QMessageBox(QDialog): pass
327    class QWizard(QDialog): pass
328
329    class QAbstractSlider(QWidget): pass
330    class QDial(QAbstractSlider): pass
331    class QScrollBar(QAbstractSlider): pass
332    class QSlider(QAbstractSlider): pass
333
334    class QMenu(QWidget):
335        def menuAction(self):
336            return Literal("%s.menuAction()" % self)
337
338    class QTabWidget(QWidget):
339        def addTab(self, *args):
340            text = args[-1]
341
342            if isinstance(text, i18n_string):
343                i18n_print("%s.setTabText(%s.indexOf(%s), %s)" % \
344                        (self._uic_name, self._uic_name, args[0], text))
345                args = args[:-1] + ("", )
346
347            ProxyClassMember(self, "addTab", 0)(*args)
348
349        def indexOf(self, page):
350            return Literal("%s.indexOf(%s)" % (self, page))
351
352    class QComboBox(QWidget): pass
353    class QFontComboBox(QComboBox): pass
354
355    class QAbstractSpinBox(QWidget): pass
356    class QDoubleSpinBox(QAbstractSpinBox): pass
357    class QSpinBox(QAbstractSpinBox): pass
358
359    class QDateTimeEdit(QAbstractSpinBox): pass
360    class QDateEdit(QDateTimeEdit): pass
361    class QTimeEdit(QDateTimeEdit): pass
362
363    class QFrame(QWidget): pass
364    class QLabel(QFrame): pass
365    class QLCDNumber(QFrame): pass
366    class QSplitter(QFrame): pass
367    class QStackedWidget(QFrame): pass
368
369    class QToolBox(QFrame):
370        def addItem(self, *args):
371            text = args[-1]
372
373            if isinstance(text, i18n_string):
374                i18n_print("%s.setItemText(%s.indexOf(%s), %s)" % \
375                        (self._uic_name, self._uic_name, args[0], text))
376                args = args[:-1] + ("", )
377
378            ProxyClassMember(self, "addItem", 0)(*args)
379
380        def indexOf(self, page):
381            return Literal("%s.indexOf(%s)" % (self, page))
382
383        def layout(self):
384            return QtWidgets.QLayout("%s.layout()" % self,
385                    False, (), noInstantiation=True)
386
387    class QAbstractScrollArea(QFrame):
388        def viewport(self):
389            return QtWidgets.QWidget("%s.viewport()" % self, False, (),
390                    noInstantiation=True)
391
392    class QGraphicsView(QAbstractScrollArea): pass
393    class QMdiArea(QAbstractScrollArea): pass
394    class QPlainTextEdit(QAbstractScrollArea): pass
395    class QScrollArea(QAbstractScrollArea): pass
396
397    class QTextEdit(QAbstractScrollArea): pass
398    class QTextBrowser(QTextEdit): pass
399
400    class QAbstractItemView(QAbstractScrollArea): pass
401    class QColumnView(QAbstractItemView): pass
402    class QHeaderView(QAbstractItemView): pass
403    class QListView(QAbstractItemView): pass
404
405    class QTableView(QAbstractItemView):
406        def horizontalHeader(self):
407            return QtWidgets.QHeaderView("%s.horizontalHeader()" % self,
408                    False, (), noInstantiation=True)
409
410        def verticalHeader(self):
411            return QtWidgets.QHeaderView("%s.verticalHeader()" % self,
412                    False, (), noInstantiation=True)
413
414    class QTreeView(QAbstractItemView):
415        def header(self):
416            return QtWidgets.QHeaderView("%s.header()" % self,
417                    False, (), noInstantiation=True)
418
419    class QUndoView(QListView): pass
420
421    class QListWidgetItem(ProxyClass): pass
422
423    class QListWidget(QListView):
424        setSortingEnabled = i18n_void_func("setSortingEnabled")
425        isSortingEnabled = i18n_func("isSortingEnabled")
426        item = i18n_func("item")
427
428    class QTableWidgetItem(ProxyClass): pass
429
430    class QTableWidget(QTableView):
431        setSortingEnabled = i18n_void_func("setSortingEnabled")
432        isSortingEnabled = i18n_func("isSortingEnabled")
433        item = i18n_func("item")
434        horizontalHeaderItem = i18n_func("horizontalHeaderItem")
435        verticalHeaderItem = i18n_func("verticalHeaderItem")
436
437    class QTreeWidgetItem(ProxyClass):
438        def child(self, index):
439            return QtWidgets.QTreeWidgetItem("%s.child(%i)" % (self, index),
440                    False, (), noInstantiation=True)
441
442    class QTreeWidget(QTreeView):
443        setSortingEnabled = i18n_void_func("setSortingEnabled")
444        isSortingEnabled = i18n_func("isSortingEnabled")
445
446        def headerItem(self):
447            return QtWidgets.QWidget("%s.headerItem()" % self, False, (),
448                    noInstantiation=True)
449
450        def topLevelItem(self, index):
451            return QtWidgets.QTreeWidgetItem("%s.topLevelItem(%i)" % (self, index),
452                    False, (), noInstantiation=True)
453
454    class QAbstractButton(QWidget): pass
455    class QCheckBox(QAbstractButton): pass
456    class QRadioButton(QAbstractButton): pass
457    class QToolButton(QAbstractButton): pass
458
459    class QPushButton(QAbstractButton): pass
460    class QCommandLinkButton(QPushButton): pass
461    class QKeySequenceEdit(QWidget): pass
462
463    # Add all remaining classes.
464    for _class in _qwidgets:
465        if _class not in locals():
466            locals()[_class] = type(_class, (QWidget, ), {})
467