qtproxies.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471
  1. #############################################################################
  2. ##
  3. ## Copyright (C) 2023 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. import sys
  40. import re
  41. from .as_string import as_string
  42. from .indenter import write_code
  43. from .misc import Literal, moduleMember
  44. from .proxy_metaclass import ProxyMetaclass
  45. i18n_strings = []
  46. i18n_context = ""
  47. def i18n_print(string):
  48. i18n_strings.append(string)
  49. def i18n_void_func(name):
  50. def _printer(self, *args):
  51. i18n_print("%s.%s(%s)" % (self, name, ", ".join(map(as_string, args))))
  52. return _printer
  53. def i18n_func(name):
  54. def _printer(self, rname, *args):
  55. i18n_print("%s = %s.%s(%s)" % (rname, self, name, ", ".join(map(as_string, args))))
  56. return Literal(rname)
  57. return _printer
  58. def strict_getattr(module, clsname):
  59. cls = getattr(module, clsname)
  60. if issubclass(cls, LiteralProxyClass):
  61. raise AttributeError(cls)
  62. else:
  63. return cls
  64. class i18n_string(object):
  65. def __init__(self, string, disambig):
  66. self.string = string
  67. self.disambig = disambig
  68. def __str__(self):
  69. if self.disambig is None:
  70. return '_translate("%s", %s)' % (i18n_context, as_string(self.string))
  71. return '_translate("%s", %s, %s)' % (i18n_context, as_string(self.string), as_string(self.disambig))
  72. # Classes with this flag will be handled as literal values. If functions are
  73. # called on these classes, the literal value changes.
  74. # Example:
  75. # the code
  76. # >>> QSize(9,10).expandedTo(...)
  77. # will print just that code.
  78. AS_ARGUMENT = 0x02
  79. # Classes with this flag may have members that are signals which themselves
  80. # will have a connect() member.
  81. AS_SIGNAL = 0x01
  82. # ATTENTION: currently, classes can either be literal or normal. If a class
  83. # should need both kinds of behaviour, the code has to be changed.
  84. class ProxyClassMember(object):
  85. def __init__(self, proxy, function_name, flags):
  86. self.proxy = proxy
  87. self.function_name = function_name
  88. self.flags = flags
  89. def __str__(self):
  90. return "%s.%s" % (self.proxy, self.function_name)
  91. def __call__(self, *args):
  92. if self.function_name == 'setProperty':
  93. str_args = (as_string(args[0]), as_string(args[1]))
  94. else:
  95. str_args = map(as_string, args)
  96. func_call = "%s.%s(%s)" % (self.proxy,
  97. self.function_name,
  98. ", ".join(str_args))
  99. if self.flags & AS_ARGUMENT:
  100. self.proxy._uic_name = func_call
  101. return self.proxy
  102. else:
  103. needs_translation = False
  104. for arg in args:
  105. if isinstance(arg, i18n_string):
  106. needs_translation = True
  107. if needs_translation:
  108. i18n_print(func_call)
  109. else:
  110. if self.function_name == 'connect':
  111. func_call += ' # type: ignore'
  112. write_code(func_call)
  113. def __getattribute__(self, attribute):
  114. """ Reimplemented to create a proxy connect() if requested and this
  115. might be a proxy for a signal.
  116. """
  117. try:
  118. return object.__getattribute__(self, attribute)
  119. except AttributeError:
  120. if attribute == 'connect' and self.flags & AS_SIGNAL:
  121. return ProxyClassMember(self, attribute, 0)
  122. raise
  123. def __getitem__(self, idx):
  124. """ Reimplemented to create a proxy member that should be a signal that
  125. passes arguments. We handle signals without arguments before we get
  126. here and never apply the index notation to them.
  127. """
  128. return ProxySignalWithArguments(self.proxy, self.function_name, idx)
  129. class ProxySignalWithArguments(object):
  130. """ This is a proxy for (what should be) a signal that passes arguments.
  131. """
  132. def __init__(self, sender, signal_name, signal_index):
  133. self._sender = sender
  134. self._signal_name = signal_name
  135. # Convert the signal index, which will be a single argument or a tuple
  136. # of arguments, to quoted strings.
  137. if isinstance(signal_index, tuple):
  138. self._signal_index = ','.join(["'%s'" % a for a in signal_index])
  139. else:
  140. self._signal_index = "'%s'" % signal_index
  141. def connect(self, slot):
  142. write_code("%s.%s[%s].connect(%s) # type: ignore" % (self._sender, self._signal_name, self._signal_index, slot))
  143. class ProxyBase(metaclass=ProxyMetaclass):
  144. """ A base class for proxies using Python v3 syntax for setting the
  145. meta-class.
  146. """
  147. class ProxyClass(ProxyBase):
  148. flags = 0
  149. def __init__(self, object_name, ctor_args=None, ctor_kwargs=None,
  150. is_attribute=False, no_instantiation=True):
  151. if object_name:
  152. if is_attribute:
  153. object_name = 'self.' + object_name
  154. self._uic_name = object_name
  155. else:
  156. self._uic_name = "Unnamed"
  157. if not no_instantiation:
  158. args = [] if ctor_args is None else list(map(str, ctor_args))
  159. if ctor_kwargs is not None:
  160. for k, v in ctor_kwargs.items():
  161. args.append(k + '=' + str(v))
  162. fun_call = '%s(%s)' % \
  163. (moduleMember(self.module, self.__class__.__name__),
  164. ', '.join(args))
  165. if object_name:
  166. fun_call = '%s = %s' % (object_name, fun_call)
  167. write_code(fun_call)
  168. def __str__(self):
  169. return self._uic_name
  170. def __getattribute__(self, attribute):
  171. try:
  172. return object.__getattribute__(self, attribute)
  173. except AttributeError:
  174. return ProxyClassMember(self, attribute, self.flags)
  175. class LiteralProxyClass(ProxyClass):
  176. """LiteralObject(*args) -> new literal class
  177. a literal class can be used as argument in a function call
  178. >>> class Foo(LiteralProxyClass): pass
  179. >>> str(Foo(1,2,3)) == "Foo(1,2,3)"
  180. """
  181. flags = AS_ARGUMENT
  182. def __init__(self, *args):
  183. self._uic_name = "%s(%s)" % \
  184. (moduleMember(self.module, self.__class__.__name__),
  185. ", ".join(map(as_string, args)))
  186. class ProxyNamespace(ProxyBase):
  187. pass
  188. # These are all the Qt classes used by pyuic6 in their namespaces. If a class
  189. # is missing, the compiler will fail, normally with an AttributeError.
  190. #
  191. # For adding new classes:
  192. # - utility classes used as literal values do not need to be listed
  193. # because they are created on the fly as subclasses of LiteralProxyClass
  194. # - classes which are *not* QWidgets inherit from ProxyClass and they
  195. # have to be listed explicitly in the correct namespace. These classes
  196. # are created via a ProxyQObjectCreator
  197. # - new QWidget-derived classes have to inherit from qtproxies.QWidget
  198. # If the widget does not need any special methods, it can be listed
  199. # in _qwidgets
  200. class QtCore(ProxyNamespace):
  201. class Qt(ProxyNamespace):
  202. pass
  203. ## connectSlotsByName and connect have to be handled as class methods,
  204. ## otherwise they would be created as LiteralProxyClasses and never be
  205. ## printed
  206. class QMetaObject(ProxyClass):
  207. @classmethod
  208. def connectSlotsByName(cls, *args):
  209. ProxyClassMember(cls, "connectSlotsByName", 0)(*args)
  210. class QObject(ProxyClass):
  211. flags = AS_SIGNAL
  212. def metaObject(self):
  213. class _FakeMetaObject(object):
  214. def className(*args):
  215. return self.__class__.__name__
  216. return _FakeMetaObject()
  217. def objectName(self):
  218. return self._uic_name.split(".")[-1]
  219. class QtGui(ProxyNamespace):
  220. class QIcon(ProxyClass):
  221. class fromTheme(ProxyClass): pass
  222. class QConicalGradient(ProxyClass): pass
  223. class QLinearGradient(ProxyClass): pass
  224. class QRadialGradient(ProxyClass): pass
  225. class QBrush(ProxyClass): pass
  226. class QPainter(ProxyClass): pass
  227. class QPalette(ProxyClass): pass
  228. class QFont(ProxyClass): pass
  229. class QFontDatabase(ProxyClass): pass
  230. # QActions inherit from QObject for the meta-object stuff and the hierarchy
  231. # has to be correct since we have a isinstance(x, QtWidgets.QLayout) call
  232. # in the UI parser.
  233. class QAction(QtCore.QObject): pass
  234. class QActionGroup(QtCore.QObject): pass
  235. # These sub-class QWidget but aren't themselves sub-classed.
  236. _qwidgets = ('QCalendarWidget', 'QDialogButtonBox', 'QDockWidget', 'QGroupBox',
  237. 'QLineEdit', 'QMainWindow', 'QMenuBar', 'QProgressBar', 'QStatusBar',
  238. 'QToolBar', 'QWizardPage')
  239. class QtWidgets(ProxyNamespace):
  240. class QApplication(QtCore.QObject):
  241. @staticmethod
  242. def translate(uiname, text, disambig):
  243. return i18n_string(text or "", disambig)
  244. class QSpacerItem(ProxyClass): pass
  245. class QSizePolicy(ProxyClass): pass
  246. class QButtonGroup(QtCore.QObject): pass
  247. class QLayout(QtCore.QObject): pass
  248. class QGridLayout(QLayout): pass
  249. class QBoxLayout(QLayout): pass
  250. class QHBoxLayout(QBoxLayout): pass
  251. class QVBoxLayout(QBoxLayout): pass
  252. class QFormLayout(QLayout): pass
  253. class QWidget(QtCore.QObject):
  254. def font(self):
  255. return Literal("%s.font()" % self)
  256. def minimumSizeHint(self):
  257. return Literal("%s.minimumSizeHint()" % self)
  258. def sizePolicy(self):
  259. sp = LiteralProxyClass()
  260. sp._uic_name = "%s.sizePolicy()" % self
  261. return sp
  262. class QDialog(QWidget): pass
  263. class QColorDialog(QDialog): pass
  264. class QFileDialog(QDialog): pass
  265. class QFontDialog(QDialog): pass
  266. class QInputDialog(QDialog): pass
  267. class QMessageBox(QDialog): pass
  268. class QWizard(QDialog): pass
  269. class QAbstractSlider(QWidget): pass
  270. class QDial(QAbstractSlider): pass
  271. class QScrollBar(QAbstractSlider): pass
  272. class QSlider(QAbstractSlider): pass
  273. class QMenu(QWidget):
  274. def menuAction(self):
  275. return Literal("%s.menuAction()" % self)
  276. class QTabWidget(QWidget):
  277. def addTab(self, *args):
  278. text = args[-1]
  279. if isinstance(text, i18n_string):
  280. i18n_print("%s.setTabText(%s.indexOf(%s), %s)" % \
  281. (self._uic_name, self._uic_name, args[0], text))
  282. args = args[:-1] + ("", )
  283. ProxyClassMember(self, "addTab", 0)(*args)
  284. def indexOf(self, page):
  285. return Literal("%s.indexOf(%s)" % (self, page))
  286. class QComboBox(QWidget): pass
  287. class QFontComboBox(QComboBox): pass
  288. class QAbstractSpinBox(QWidget): pass
  289. class QDoubleSpinBox(QAbstractSpinBox): pass
  290. class QSpinBox(QAbstractSpinBox): pass
  291. class QDateTimeEdit(QAbstractSpinBox): pass
  292. class QDateEdit(QDateTimeEdit): pass
  293. class QTimeEdit(QDateTimeEdit): pass
  294. class QFrame(QWidget): pass
  295. class QLabel(QFrame): pass
  296. class QLCDNumber(QFrame): pass
  297. class QSplitter(QFrame): pass
  298. class QStackedWidget(QFrame): pass
  299. class QToolBox(QFrame):
  300. def addItem(self, *args):
  301. text = args[-1]
  302. if isinstance(text, i18n_string):
  303. i18n_print("%s.setItemText(%s.indexOf(%s), %s)" % \
  304. (self._uic_name, self._uic_name, args[0], text))
  305. args = args[:-1] + ("", )
  306. ProxyClassMember(self, "addItem", 0)(*args)
  307. def indexOf(self, page):
  308. return Literal("%s.indexOf(%s)" % (self, page))
  309. def layout(self):
  310. return QtWidgets.QLayout('%s.layout()' % self)
  311. class QAbstractScrollArea(QFrame):
  312. def viewport(self):
  313. return QtWidgets.QWidget('%s.viewport()' % self)
  314. class QGraphicsView(QAbstractScrollArea): pass
  315. class QMdiArea(QAbstractScrollArea): pass
  316. class QPlainTextEdit(QAbstractScrollArea): pass
  317. class QScrollArea(QAbstractScrollArea): pass
  318. class QTextEdit(QAbstractScrollArea): pass
  319. class QTextBrowser(QTextEdit): pass
  320. class QAbstractItemView(QAbstractScrollArea): pass
  321. class QColumnView(QAbstractItemView): pass
  322. class QHeaderView(QAbstractItemView): pass
  323. class QListView(QAbstractItemView): pass
  324. class QTableView(QAbstractItemView):
  325. def horizontalHeader(self):
  326. return QtWidgets.QHeaderView('%s.horizontalHeader()' % self)
  327. def verticalHeader(self):
  328. return QtWidgets.QHeaderView('%s.verticalHeader()' % self)
  329. class QTreeView(QAbstractItemView):
  330. def header(self):
  331. return QtWidgets.QHeaderView('%s.header()' % self)
  332. class QUndoView(QListView): pass
  333. class QListWidgetItem(ProxyClass): pass
  334. class QListWidget(QListView):
  335. setSortingEnabled = i18n_void_func("setSortingEnabled")
  336. isSortingEnabled = i18n_func("isSortingEnabled")
  337. item = i18n_func("item")
  338. class QTableWidgetItem(ProxyClass): pass
  339. class QTableWidget(QTableView):
  340. setSortingEnabled = i18n_void_func("setSortingEnabled")
  341. isSortingEnabled = i18n_func("isSortingEnabled")
  342. item = i18n_func("item")
  343. horizontalHeaderItem = i18n_func("horizontalHeaderItem")
  344. verticalHeaderItem = i18n_func("verticalHeaderItem")
  345. class QTreeWidgetItem(ProxyClass):
  346. def child(self, index):
  347. return QtWidgets.QTreeWidgetItem('%s.child(%i)' % (self, index))
  348. class QTreeWidget(QTreeView):
  349. setSortingEnabled = i18n_void_func("setSortingEnabled")
  350. isSortingEnabled = i18n_func("isSortingEnabled")
  351. def headerItem(self):
  352. return QtWidgets.QWidget('%s.headerItem()' % self)
  353. def topLevelItem(self, index):
  354. return QtWidgets.QTreeWidgetItem(
  355. '%s.topLevelItem(%i)' % (self, index))
  356. class QAbstractButton(QWidget): pass
  357. class QCheckBox(QAbstractButton): pass
  358. class QRadioButton(QAbstractButton): pass
  359. class QToolButton(QAbstractButton): pass
  360. class QPushButton(QAbstractButton): pass
  361. class QCommandLinkButton(QPushButton): pass
  362. class QKeySequenceEdit(QWidget): pass
  363. # Add all remaining classes.
  364. for _class in _qwidgets:
  365. if _class not in locals():
  366. locals()[_class] = type(_class, (QWidget, ), {})