Audio plugin host https://kx.studio/carla
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

pixmapdial.py 19KB

11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
10 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
10 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
10 years ago
10 years ago
11 years ago
11 years ago
11 years ago
11 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527
  1. #!/usr/bin/env python3
  2. # -*- coding: utf-8 -*-
  3. # Pixmap Dial, a custom Qt widget
  4. # Copyright (C) 2011-2017 Filipe Coelho <falktx@falktx.com>
  5. #
  6. # This program is free software; you can redistribute it and/or
  7. # modify it under the terms of the GNU General Public License as
  8. # published by the Free Software Foundation; either version 2 of
  9. # the License, or any later version.
  10. #
  11. # This program is distributed in the hope that it will be useful,
  12. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. # GNU General Public License for more details.
  15. #
  16. # For a full copy of the GNU General Public License see the doc/GPL.txt file.
  17. # ------------------------------------------------------------------------------------------------------------
  18. # Imports (Config)
  19. from carla_config import *
  20. # ------------------------------------------------------------------------------------------------------------
  21. # Imports (Global)
  22. from math import cos, floor, pi, sin
  23. if config_UseQt5:
  24. from PyQt5.QtCore import pyqtSignal, pyqtSlot, Qt, QEvent, QPointF, QRectF, QTimer, QSize
  25. from PyQt5.QtGui import QColor, QConicalGradient, QFont, QFontMetrics
  26. from PyQt5.QtGui import QLinearGradient, QPainter, QPainterPath, QPen, QPixmap
  27. from PyQt5.QtWidgets import QDial
  28. else:
  29. from PyQt4.QtCore import pyqtSignal, pyqtSlot, Qt, QEvent, QPointF, QRectF, QTimer, QSize
  30. from PyQt4.QtGui import QColor, QConicalGradient, QFont, QFontMetrics
  31. from PyQt4.QtGui import QDial, QLinearGradient, QPainter, QPainterPath, QPen, QPixmap
  32. # ------------------------------------------------------------------------------------------------------------
  33. # Widget Class
  34. class PixmapDial(QDial):
  35. # enum CustomPaintMode
  36. CUSTOM_PAINT_MODE_NULL = 0 # default (NOTE: only this mode has label gradient)
  37. CUSTOM_PAINT_MODE_CARLA_WET = 1 # color blue-green gradient (reserved #3)
  38. CUSTOM_PAINT_MODE_CARLA_VOL = 2 # color blue (reserved #3)
  39. CUSTOM_PAINT_MODE_CARLA_L = 3 # color yellow (reserved #4)
  40. CUSTOM_PAINT_MODE_CARLA_R = 4 # color yellow (reserved #4)
  41. CUSTOM_PAINT_MODE_CARLA_PAN = 5 # color yellow (reserved #3)
  42. CUSTOM_PAINT_MODE_COLOR = 6 # color, selectable (reserved #3)
  43. CUSTOM_PAINT_MODE_ZITA = 7 # custom zita knob (reserved #6)
  44. CUSTOM_PAINT_MODE_NO_GRADIENT = 8 # skip label gradient
  45. # enum Orientation
  46. HORIZONTAL = 0
  47. VERTICAL = 1
  48. HOVER_MIN = 0
  49. HOVER_MAX = 9
  50. MODE_DEFAULT = 0
  51. MODE_LINEAR = 1
  52. # signals
  53. realValueChanged = pyqtSignal(float)
  54. def __init__(self, parent, index=0):
  55. QDial.__init__(self, parent)
  56. self.fDialMode = self.MODE_LINEAR
  57. self.fMinimum = 0.0
  58. self.fMaximum = 1.0
  59. self.fRealValue = 0.0
  60. self.fPrecision = 10000
  61. self.fIsInteger = False
  62. self.fIsHovered = False
  63. self.fIsPressed = False
  64. self.fHoverStep = self.HOVER_MIN
  65. self.fLastDragPos = None
  66. self.fLastDragValue = 0.0
  67. self.fIndex = index
  68. self.fPixmap = QPixmap(":/bitmaps/dial_01d.png")
  69. self.fPixmapNum = "01"
  70. if self.fPixmap.width() > self.fPixmap.height():
  71. self.fPixmapOrientation = self.HORIZONTAL
  72. else:
  73. self.fPixmapOrientation = self.VERTICAL
  74. self.fLabel = ""
  75. self.fLabelPos = QPointF(0.0, 0.0)
  76. self.fLabelFont = QFont(self.font())
  77. self.fLabelFont.setPixelSize(8)
  78. self.fLabelWidth = 0
  79. self.fLabelHeight = 0
  80. if self.palette().window().color().lightness() > 100:
  81. # Light background
  82. c = self.palette().dark().color()
  83. self.fLabelGradientColor1 = c
  84. self.fLabelGradientColor2 = QColor(c.red(), c.green(), c.blue(), 0)
  85. self.fLabelGradientColorT = [self.palette().buttonText().color(), self.palette().mid().color()]
  86. else:
  87. # Dark background
  88. self.fLabelGradientColor1 = QColor(0, 0, 0, 255)
  89. self.fLabelGradientColor2 = QColor(0, 0, 0, 0)
  90. self.fLabelGradientColorT = [Qt.white, Qt.darkGray]
  91. self.fLabelGradient = QLinearGradient(0, 0, 0, 1)
  92. self.fLabelGradient.setColorAt(0.0, self.fLabelGradientColor1)
  93. self.fLabelGradient.setColorAt(0.6, self.fLabelGradientColor1)
  94. self.fLabelGradient.setColorAt(1.0, self.fLabelGradientColor2)
  95. self.fLabelGradientRect = QRectF(0.0, 0.0, 0.0, 0.0)
  96. self.fCustomPaintMode = self.CUSTOM_PAINT_MODE_NULL
  97. self.fCustomPaintColor = QColor(0xff, 0xff, 0xff)
  98. self.updateSizes()
  99. # Fake internal value, custom precision
  100. QDial.setMinimum(self, 0)
  101. QDial.setMaximum(self, self.fPrecision)
  102. QDial.setValue(self, 0)
  103. self.valueChanged.connect(self.slot_valueChanged)
  104. def getIndex(self):
  105. return self.fIndex
  106. def getBaseSize(self):
  107. return self.fPixmapBaseSize
  108. def forceWhiteLabelGradientText(self):
  109. self.fLabelGradientColor1 = QColor(0, 0, 0, 255)
  110. self.fLabelGradientColor2 = QColor(0, 0, 0, 0)
  111. self.fLabelGradientColorT = [Qt.white, Qt.darkGray]
  112. def setLabelColor(self, enabled, disabled):
  113. self.fLabelGradientColor1 = QColor(0, 0, 0, 255)
  114. self.fLabelGradientColor2 = QColor(0, 0, 0, 0)
  115. self.fLabelGradientColorT = [enabled, disabled]
  116. def updateSizes(self):
  117. self.fPixmapWidth = self.fPixmap.width()
  118. self.fPixmapHeight = self.fPixmap.height()
  119. if self.fPixmapWidth < 1:
  120. self.fPixmapWidth = 1
  121. if self.fPixmapHeight < 1:
  122. self.fPixmapHeight = 1
  123. if self.fPixmapOrientation == self.HORIZONTAL:
  124. self.fPixmapBaseSize = self.fPixmapHeight
  125. self.fPixmapLayersCount = self.fPixmapWidth / self.fPixmapHeight
  126. else:
  127. self.fPixmapBaseSize = self.fPixmapWidth
  128. self.fPixmapLayersCount = self.fPixmapHeight / self.fPixmapWidth
  129. self.setMinimumSize(self.fPixmapBaseSize, self.fPixmapBaseSize + self.fLabelHeight + 5)
  130. self.setMaximumSize(self.fPixmapBaseSize, self.fPixmapBaseSize + self.fLabelHeight + 5)
  131. if not self.fLabel:
  132. self.fLabelHeight = 0
  133. self.fLabelWidth = 0
  134. return
  135. self.fLabelWidth = QFontMetrics(self.fLabelFont).width(self.fLabel)
  136. self.fLabelHeight = QFontMetrics(self.fLabelFont).height()
  137. self.fLabelPos.setX(float(self.fPixmapBaseSize)/2.0 - float(self.fLabelWidth)/2.0)
  138. if self.fPixmapNum in ("01", "02", "07", "08", "09", "10"):
  139. self.fLabelPos.setY(self.fPixmapBaseSize + self.fLabelHeight)
  140. elif self.fPixmapNum in ("11",):
  141. self.fLabelPos.setY(self.fPixmapBaseSize + self.fLabelHeight*2/3)
  142. else:
  143. self.fLabelPos.setY(self.fPixmapBaseSize + self.fLabelHeight/2)
  144. self.fLabelGradient.setStart(0, float(self.fPixmapBaseSize)/2.0)
  145. self.fLabelGradient.setFinalStop(0, self.fPixmapBaseSize + self.fLabelHeight + 5)
  146. self.fLabelGradientRect = QRectF(float(self.fPixmapBaseSize)/8.0, float(self.fPixmapBaseSize)/2.0, float(self.fPixmapBaseSize*3)/4.0, self.fPixmapBaseSize+self.fLabelHeight+5)
  147. def setCustomPaintMode(self, paintMode):
  148. if self.fCustomPaintMode == paintMode:
  149. return
  150. self.fCustomPaintMode = paintMode
  151. self.update()
  152. def setCustomPaintColor(self, color):
  153. if self.fCustomPaintColor == color:
  154. return
  155. self.fCustomPaintColor = color
  156. self.update()
  157. def setLabel(self, label):
  158. if self.fLabel == label:
  159. return
  160. self.fLabel = label
  161. self.updateSizes()
  162. self.update()
  163. def setIndex(self, index):
  164. self.fIndex = index
  165. def setPixmap(self, pixmapId):
  166. self.fPixmapNum = "%02i" % pixmapId
  167. self.fPixmap.load(":/bitmaps/dial_%s%s.png" % (self.fPixmapNum, "" if self.isEnabled() else "d"))
  168. if self.fPixmap.width() > self.fPixmap.height():
  169. self.fPixmapOrientation = self.HORIZONTAL
  170. else:
  171. self.fPixmapOrientation = self.VERTICAL
  172. # special pixmaps
  173. if self.fCustomPaintMode == self.CUSTOM_PAINT_MODE_NULL:
  174. # reserved for carla-wet, carla-vol, carla-pan and color
  175. if self.fPixmapNum == "03":
  176. self.fCustomPaintMode = self.CUSTOM_PAINT_MODE_COLOR
  177. # reserved for carla-L and carla-R
  178. elif self.fPixmapNum == "04":
  179. self.fCustomPaintMode = self.CUSTOM_PAINT_MODE_CARLA_L
  180. # reserved for zita
  181. elif self.fPixmapNum == "06":
  182. self.fCustomPaintMode = self.CUSTOM_PAINT_MODE_ZITA
  183. self.updateSizes()
  184. self.update()
  185. def setPrecision(self, value, isInteger):
  186. self.fPrecision = value
  187. self.fIsInteger = isInteger
  188. QDial.setMaximum(self, value)
  189. def setMinimum(self, value):
  190. self.fMinimum = value
  191. def setMaximum(self, value):
  192. self.fMaximum = value
  193. def setValue(self, value, emitSignal=False):
  194. if self.fRealValue == value:
  195. return
  196. if value <= self.fMinimum:
  197. qtValue = 0
  198. self.fRealValue = self.fMinimum
  199. elif value >= self.fMaximum:
  200. qtValue = self.fPrecision
  201. self.fRealValue = self.fMaximum
  202. else:
  203. qtValue = round(float(value - self.fMinimum) / float(self.fMaximum - self.fMinimum) * self.fPrecision)
  204. self.fRealValue = value
  205. # Block change signal, we'll handle it ourselves
  206. self.blockSignals(True)
  207. QDial.setValue(self, qtValue)
  208. self.blockSignals(False)
  209. if emitSignal:
  210. self.realValueChanged.emit(self.fRealValue)
  211. @pyqtSlot(int)
  212. def slot_valueChanged(self, value):
  213. self.fRealValue = float(value)/self.fPrecision * (self.fMaximum - self.fMinimum) + self.fMinimum
  214. self.realValueChanged.emit(self.fRealValue)
  215. @pyqtSlot()
  216. def slot_updatePixmap(self):
  217. self.setPixmap(int(self.fPixmapNum))
  218. def minimumSizeHint(self):
  219. return QSize(self.fPixmapBaseSize, self.fPixmapBaseSize)
  220. def sizeHint(self):
  221. return QSize(self.fPixmapBaseSize, self.fPixmapBaseSize)
  222. def changeEvent(self, event):
  223. QDial.changeEvent(self, event)
  224. # Force pixmap update if enabled state changes
  225. if event.type() == QEvent.EnabledChange:
  226. self.setPixmap(int(self.fPixmapNum))
  227. def enterEvent(self, event):
  228. self.fIsHovered = True
  229. if self.fHoverStep == self.HOVER_MIN:
  230. self.fHoverStep = self.HOVER_MIN + 1
  231. QDial.enterEvent(self, event)
  232. def leaveEvent(self, event):
  233. self.fIsHovered = False
  234. if self.fHoverStep == self.HOVER_MAX:
  235. self.fHoverStep = self.HOVER_MAX - 1
  236. QDial.leaveEvent(self, event)
  237. def mousePressEvent(self, event):
  238. if self.fDialMode == self.MODE_DEFAULT:
  239. return QDial.mousePressEvent(self, event)
  240. if event.button() == Qt.LeftButton:
  241. self.fIsPressed = True
  242. self.fLastDragPos = event.pos()
  243. self.fLastDragValue = self.fRealValue
  244. def mouseMoveEvent(self, event):
  245. if self.fDialMode == self.MODE_DEFAULT:
  246. return QDial.mouseMoveEvent(self, event)
  247. if not self.fIsPressed:
  248. return
  249. range = (self.fMaximum - self.fMinimum) / 4.0
  250. pos = event.pos()
  251. dx = range * float(pos.x() - self.fLastDragPos.x()) / self.width()
  252. dy = range * float(pos.y() - self.fLastDragPos.y()) / self.height()
  253. value = self.fLastDragValue + dx - dy
  254. if value < self.fMinimum:
  255. value = self.fMinimum
  256. elif value > self.fMaximum:
  257. value = self.fMaximum
  258. elif self.fIsInteger:
  259. value = float(round(value))
  260. self.setValue(value, True)
  261. def mouseReleaseEvent(self, event):
  262. if self.fDialMode == self.MODE_DEFAULT:
  263. return QDial.mouseReleaseEvent(self, event)
  264. if self.fIsPressed:
  265. self.fIsPressed = False
  266. def paintEvent(self, event):
  267. painter = QPainter(self)
  268. event.accept()
  269. painter.save()
  270. painter.setRenderHint(QPainter.Antialiasing, True)
  271. if self.fLabel:
  272. if self.fCustomPaintMode == self.CUSTOM_PAINT_MODE_NULL:
  273. painter.setPen(self.fLabelGradientColor2)
  274. painter.setBrush(self.fLabelGradient)
  275. painter.drawRect(self.fLabelGradientRect)
  276. painter.setFont(self.fLabelFont)
  277. painter.setPen(self.fLabelGradientColorT[0 if self.isEnabled() else 1])
  278. painter.drawText(self.fLabelPos, self.fLabel)
  279. if self.isEnabled():
  280. normValue = float(self.fRealValue - self.fMinimum) / float(self.fMaximum - self.fMinimum)
  281. target = QRectF(0.0, 0.0, self.fPixmapBaseSize, self.fPixmapBaseSize)
  282. curLayer = int((self.fPixmapLayersCount - 1) * normValue)
  283. if self.fPixmapOrientation == self.HORIZONTAL:
  284. xpos = self.fPixmapBaseSize * curLayer
  285. ypos = 0.0
  286. else:
  287. xpos = 0.0
  288. ypos = self.fPixmapBaseSize * curLayer
  289. source = QRectF(xpos, ypos, self.fPixmapBaseSize, self.fPixmapBaseSize)
  290. painter.drawPixmap(target, self.fPixmap, source)
  291. # Custom knobs (Dry/Wet and Volume)
  292. if self.fCustomPaintMode in (self.CUSTOM_PAINT_MODE_CARLA_WET, self.CUSTOM_PAINT_MODE_CARLA_VOL):
  293. # knob color
  294. colorGreen = QColor(0x5D, 0xE7, 0x3D).lighter(100 + self.fHoverStep*6)
  295. colorBlue = QColor(0x3E, 0xB8, 0xBE).lighter(100 + self.fHoverStep*6)
  296. # draw small circle
  297. ballRect = QRectF(8.0, 8.0, 15.0, 15.0)
  298. ballPath = QPainterPath()
  299. ballPath.addEllipse(ballRect)
  300. #painter.drawRect(ballRect)
  301. tmpValue = (0.375 + 0.75*normValue)
  302. ballValue = tmpValue - floor(tmpValue)
  303. ballPoint = ballPath.pointAtPercent(ballValue)
  304. # draw arc
  305. startAngle = 216*16
  306. spanAngle = -252*16*normValue
  307. if self.fCustomPaintMode == self.CUSTOM_PAINT_MODE_CARLA_WET:
  308. painter.setBrush(colorBlue)
  309. painter.setPen(QPen(colorBlue, 0))
  310. painter.drawEllipse(QRectF(ballPoint.x(), ballPoint.y(), 2.2, 2.2))
  311. gradient = QConicalGradient(15.5, 15.5, -45)
  312. gradient.setColorAt(0.0, colorBlue)
  313. gradient.setColorAt(0.125, colorBlue)
  314. gradient.setColorAt(0.625, colorGreen)
  315. gradient.setColorAt(0.75, colorGreen)
  316. gradient.setColorAt(0.76, colorGreen)
  317. gradient.setColorAt(1.0, colorGreen)
  318. painter.setBrush(gradient)
  319. painter.setPen(QPen(gradient, 3))
  320. else:
  321. painter.setBrush(colorBlue)
  322. painter.setPen(QPen(colorBlue, 0))
  323. painter.drawEllipse(QRectF(ballPoint.x(), ballPoint.y(), 2.2, 2.2))
  324. painter.setBrush(colorBlue)
  325. painter.setPen(QPen(colorBlue, 3))
  326. painter.drawArc(4.0, 4.0, 26.0, 26.0, startAngle, spanAngle)
  327. # Custom knobs (L and R)
  328. elif self.fCustomPaintMode in (self.CUSTOM_PAINT_MODE_CARLA_L, self.CUSTOM_PAINT_MODE_CARLA_R):
  329. # knob color
  330. color = QColor(0xAD, 0xD5, 0x48).lighter(100 + self.fHoverStep*6)
  331. # draw small circle
  332. ballRect = QRectF(7.0, 8.0, 11.0, 12.0)
  333. ballPath = QPainterPath()
  334. ballPath.addEllipse(ballRect)
  335. #painter.drawRect(ballRect)
  336. tmpValue = (0.375 + 0.75*normValue)
  337. ballValue = tmpValue - floor(tmpValue)
  338. ballPoint = ballPath.pointAtPercent(ballValue)
  339. painter.setBrush(color)
  340. painter.setPen(QPen(color, 0))
  341. painter.drawEllipse(QRectF(ballPoint.x(), ballPoint.y(), 2.0, 2.0))
  342. # draw arc
  343. if self.fCustomPaintMode == self.CUSTOM_PAINT_MODE_CARLA_L:
  344. startAngle = 216*16
  345. spanAngle = -252.0*16*normValue
  346. else:
  347. startAngle = 324.0*16
  348. spanAngle = 252.0*16*(1.0-normValue)
  349. painter.setPen(QPen(color, 2))
  350. painter.drawArc(3.5, 4.5, 22.0, 22.0, startAngle, spanAngle)
  351. # Custom knobs (Color)
  352. elif self.fCustomPaintMode == self.CUSTOM_PAINT_MODE_COLOR:
  353. # knob color
  354. color = self.fCustomPaintColor.lighter(100 + self.fHoverStep*6)
  355. # draw small circle
  356. ballRect = QRectF(8.0, 8.0, 15.0, 15.0)
  357. ballPath = QPainterPath()
  358. ballPath.addEllipse(ballRect)
  359. tmpValue = (0.375 + 0.75*normValue)
  360. ballValue = tmpValue - floor(tmpValue)
  361. ballPoint = ballPath.pointAtPercent(ballValue)
  362. # draw arc
  363. startAngle = 216*16
  364. spanAngle = -252*16*normValue
  365. painter.setBrush(color)
  366. painter.setPen(QPen(color, 0))
  367. painter.drawEllipse(QRectF(ballPoint.x(), ballPoint.y(), 2.2, 2.2))
  368. painter.setBrush(color)
  369. painter.setPen(QPen(color, 3))
  370. painter.drawArc(4.0, 4.0, 26.0, 26.0, startAngle, spanAngle)
  371. # Custom knobs (Zita)
  372. elif self.fCustomPaintMode == self.CUSTOM_PAINT_MODE_ZITA:
  373. a = normValue * pi * 1.5 - 2.35
  374. r = 10.0
  375. x = 10.5
  376. y = 10.5
  377. x += r * sin(a)
  378. y -= r * cos(a)
  379. painter.setBrush(Qt.black)
  380. painter.setPen(QPen(Qt.black, 2))
  381. painter.drawLine(QPointF(11.0, 11.0), QPointF(x, y))
  382. # Custom knobs
  383. else:
  384. painter.restore()
  385. return
  386. if self.HOVER_MIN < self.fHoverStep < self.HOVER_MAX:
  387. self.fHoverStep += 1 if self.fIsHovered else -1
  388. QTimer.singleShot(20, self.update)
  389. else: # isEnabled()
  390. target = QRectF(0.0, 0.0, self.fPixmapBaseSize, self.fPixmapBaseSize)
  391. painter.drawPixmap(target, self.fPixmap, target)
  392. painter.restore()
  393. def resizeEvent(self, event):
  394. QDial.resizeEvent(self, event)
  395. self.updateSizes()
  396. # ------------------------------------------------------------------------------------------------------------
  397. # Main Testing
  398. if __name__ == '__main__':
  399. import sys
  400. from PyQt4.QtGui import QApplication
  401. import resources_rc
  402. app = QApplication(sys.argv)
  403. gui = PixmapDial(None)
  404. #gui.setEnabled(True)
  405. #gui.setEnabled(False)
  406. gui.setPixmap(3)
  407. gui.setLabel("hahaha")
  408. gui.show()
  409. sys.exit(app.exec_())