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