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.

621 lines
22KB

  1. #!/usr/bin/env python3
  2. # SPDX-FileCopyrightText: 2011-2024 Filipe Coelho <falktx@falktx.com>
  3. # SPDX-License-Identifier: GPL-2.0-or-later
  4. # ------------------------------------------------------------------------------------------------------------
  5. # Imports (Global)
  6. from qt_compat import qt_config
  7. if qt_config == 5:
  8. from PyQt5.QtCore import pyqtSignal, pyqtSlot, QT_VERSION, Qt, QPointF, QRectF, QSize, QTimer
  9. from PyQt5.QtGui import QColor, QPainter, QPen
  10. from PyQt5.QtWidgets import QGraphicsScene, QGraphicsSceneMouseEvent, QMainWindow
  11. elif qt_config == 6:
  12. from PyQt6.QtCore import pyqtSignal, pyqtSlot, QT_VERSION, Qt, QPointF, QRectF, QSize, QTimer
  13. from PyQt6.QtGui import QColor, QPainter, QPen
  14. from PyQt6.QtWidgets import QGraphicsScene, QGraphicsSceneMouseEvent, QMainWindow
  15. # -----------------------------------------------------------------------
  16. # Imports (Custom)
  17. from carla_shared import *
  18. from carla_utils import *
  19. import ui_xycontroller
  20. # -----------------------------------------------------------------------
  21. # Imports (ExternalUI)
  22. from carla_app import CarlaApplication
  23. from externalui import ExternalUI
  24. from widgets.paramspinbox import ParamSpinBox
  25. # ------------------------------------------------------------------------------------------------------------
  26. XYCONTROLLER_PARAMETER_X = 0
  27. XYCONTROLLER_PARAMETER_Y = 1
  28. # ------------------------------------------------------------------------------------------------------------
  29. class XYGraphicsScene(QGraphicsScene):
  30. # signals
  31. cursorMoved = pyqtSignal(float,float)
  32. def __init__(self, parent):
  33. QGraphicsScene.__init__(self, parent)
  34. self.cc_x = 1
  35. self.cc_y = 2
  36. self.rparent = parent
  37. self.m_channels = []
  38. self.m_mouseLock = False
  39. self.m_smooth = False
  40. self.m_smooth_x = 0.0
  41. self.m_smooth_y = 0.0
  42. self.setBackgroundBrush(Qt.black)
  43. cursorPen = QPen(QColor(255, 255, 255), 2)
  44. cursorBrush = QColor(255, 255, 255, 50)
  45. self.m_cursor = self.addEllipse(QRectF(-10, -10, 20, 20), cursorPen, cursorBrush)
  46. linePen = QPen(QColor(200, 200, 200, 100), 1, Qt.DashLine)
  47. self.m_lineH = self.addLine(-9999, 0, 9999, 0, linePen)
  48. self.m_lineV = self.addLine(0, -9999, 0, 9999, linePen)
  49. self.p_size = QRectF(-100, -100, 100, 100)
  50. # -------------------------------------------------------------------
  51. def setControlX(self, x: int):
  52. self.cc_x = x
  53. def setControlY(self, y: int):
  54. self.cc_y = y
  55. def setChannels(self, channels):
  56. self.m_channels = channels
  57. def setPosX(self, x: float, forward: bool = True):
  58. if self.m_mouseLock:
  59. return
  60. posX = x * (self.p_size.x() + self.p_size.width())
  61. self.m_cursor.setPos(posX, self.m_cursor.y())
  62. self.m_lineV.setX(posX)
  63. if forward:
  64. value = posX / (self.p_size.x() + self.p_size.width());
  65. self.sendMIDI(value, None)
  66. else:
  67. self.m_smooth_x = posX;
  68. def setPosY(self, y: float, forward: bool = True):
  69. if self.m_mouseLock:
  70. return;
  71. posY = y * (self.p_size.y() + self.p_size.height())
  72. self.m_cursor.setPos(self.m_cursor.x(), posY)
  73. self.m_lineH.setY(posY)
  74. if forward:
  75. value = posY / (self.p_size.y() + self.p_size.height())
  76. self.sendMIDI(None, value)
  77. else:
  78. self.m_smooth_y = posY
  79. def setSmooth(self, smooth: bool):
  80. self.m_smooth = smooth
  81. def setSmoothValues(self, x: float, y: float):
  82. self.m_smooth_x = x * (self.p_size.x() + self.p_size.width());
  83. self.m_smooth_y = y * (self.p_size.y() + self.p_size.height());
  84. # -------------------------------------------------------------------
  85. def updateSize(self, size: QSize):
  86. self.p_size.setRect(-(float(size.width())/2),
  87. -(float(size.height())/2),
  88. size.width(),
  89. size.height());
  90. def updateSmooth(self):
  91. if not self.m_smooth:
  92. return
  93. if self.m_cursor.x() == self.m_smooth_x and self.m_cursor.y() == self.m_smooth_y:
  94. return
  95. same = 0
  96. if abs(self.m_cursor.x() - self.m_smooth_x) <= 0.0005:
  97. self.m_smooth_x = self.m_cursor.x()
  98. same += 1
  99. if abs(self.m_cursor.y() - self.m_smooth_y) <= 0.0005:
  100. self.m_smooth_y = self.m_cursor.y()
  101. same += 1
  102. if same == 2:
  103. return
  104. newX = float(self.m_smooth_x + self.m_cursor.x()*7) / 8
  105. newY = float(self.m_smooth_y + self.m_cursor.y()*7) / 8
  106. pos = QPointF(newX, newY)
  107. self.m_cursor.setPos(pos)
  108. self.m_lineH.setY(pos.y())
  109. self.m_lineV.setX(pos.x())
  110. xp = pos.x() / (self.p_size.x() + self.p_size.width())
  111. yp = pos.y() / (self.p_size.y() + self.p_size.height())
  112. self.sendMIDI(xp, yp)
  113. self.cursorMoved.emit(xp, yp)
  114. # -------------------------------------------------------------------
  115. def handleMousePos(self, pos: QPointF):
  116. if not self.p_size.contains(pos):
  117. if pos.x() < self.p_size.x():
  118. pos.setX(self.p_size.x())
  119. elif pos.x() > (self.p_size.x() + self.p_size.width()):
  120. pos.setX(self.p_size.x() + self.p_size.width());
  121. if pos.y() < self.p_size.y():
  122. pos.setY(self.p_size.y())
  123. elif pos.y() > (self.p_size.y() + self.p_size.height()):
  124. pos.setY(self.p_size.y() + self.p_size.height())
  125. self.m_smooth_x = pos.x()
  126. self.m_smooth_y = pos.y()
  127. if not self.m_smooth:
  128. self.m_cursor.setPos(pos)
  129. self.m_lineH.setY(pos.y())
  130. self.m_lineV.setX(pos.x())
  131. xp = pos.x() / (self.p_size.x() + self.p_size.width());
  132. yp = pos.y() / (self.p_size.y() + self.p_size.height());
  133. self.sendMIDI(xp, yp)
  134. self.cursorMoved.emit(xp, yp)
  135. def sendMIDI(self, xp, yp):
  136. rate = float(0xff) / 4
  137. msgd = ["cc2" if xp is not None and yp is not None else "cc"]
  138. if xp is not None:
  139. msgd.append(self.cc_x)
  140. msgd.append(int(xp * rate + rate))
  141. if yp is not None:
  142. msgd.append(self.cc_y)
  143. msgd.append(int(yp * rate + rate))
  144. self.rparent.send(msgd)
  145. # -------------------------------------------------------------------
  146. def keyPressEvent(self, event): # QKeyEvent
  147. event.accept()
  148. def wheelEvent(self, event): # QGraphicsSceneWheelEvent
  149. event.accept()
  150. def mousePressEvent(self, event: QGraphicsSceneMouseEvent):
  151. self.m_mouseLock = True
  152. self.handleMousePos(event.scenePos())
  153. self.rparent.setCursor(Qt.CrossCursor)
  154. QGraphicsScene.mousePressEvent(self, event);
  155. def mouseMoveEvent(self, event: QGraphicsSceneMouseEvent):
  156. self.handleMousePos(event.scenePos())
  157. QGraphicsScene.mouseMoveEvent(self, event);
  158. def mouseReleaseEvent(self, event: QGraphicsSceneMouseEvent):
  159. self.m_mouseLock = False
  160. self.rparent.setCursor(Qt.ArrowCursor)
  161. QGraphicsScene.mouseReleaseEvent(self, event)
  162. # -----------------------------------------------------------------------
  163. # External UI
  164. class XYControllerUI(ExternalUI, QMainWindow):
  165. def __init__(self):
  166. ExternalUI.__init__(self)
  167. QMainWindow.__init__(self)
  168. self.ui = ui_xycontroller.Ui_XYControllerW()
  169. self.ui.setupUi(self)
  170. self.fSaveSizeNowChecker = -1
  171. # ---------------------------------------------------------------
  172. # Set-up GUI stuff
  173. self.scene = XYGraphicsScene(self)
  174. self.ui.dial_x.setImage(2)
  175. self.ui.dial_y.setImage(2)
  176. self.ui.dial_x.setLabel("X")
  177. self.ui.dial_y.setLabel("Y")
  178. self.ui.keyboard.setOctaves(10)
  179. self.ui.graphicsView.setScene(self.scene)
  180. self.ui.graphicsView.setRenderHints(QPainter.Antialiasing)
  181. for MIDI_CC in MIDI_CC_LIST:
  182. self.ui.cb_control_x.addItem(MIDI_CC)
  183. self.ui.cb_control_y.addItem(MIDI_CC)
  184. # ---------------------------------------------------------------
  185. # Initial state
  186. self.m_channels = [1]
  187. self.ui.act_ch_01.setChecked(True)
  188. self.ui.act_show_keyboard.setChecked(True)
  189. self.ui.cb_control_y.setCurrentIndex(1)
  190. # ---------------------------------------------------------------
  191. # Connect actions to functions
  192. self.scene.cursorMoved.connect(self.slot_sceneCursorMoved)
  193. self.ui.keyboard.noteOn.connect(self.slot_noteOn)
  194. self.ui.keyboard.noteOff.connect(self.slot_noteOff)
  195. self.ui.cb_smooth.clicked.connect(self.slot_setSmooth)
  196. self.ui.dial_x.realValueChanged.connect(self.slot_knobValueChangedX)
  197. self.ui.dial_y.realValueChanged.connect(self.slot_knobValueChangedY)
  198. if QT_VERSION >= 0x60000:
  199. self.ui.cb_control_x.currentTextChanged.connect(self.slot_checkCC_X)
  200. self.ui.cb_control_y.currentTextChanged.connect(self.slot_checkCC_Y)
  201. else:
  202. self.ui.cb_control_x.currentIndexChanged[str].connect(self.slot_checkCC_X)
  203. self.ui.cb_control_y.currentIndexChanged[str].connect(self.slot_checkCC_Y)
  204. self.ui.act_ch_01.triggered.connect(self.slot_checkChannel)
  205. self.ui.act_ch_02.triggered.connect(self.slot_checkChannel)
  206. self.ui.act_ch_03.triggered.connect(self.slot_checkChannel)
  207. self.ui.act_ch_04.triggered.connect(self.slot_checkChannel)
  208. self.ui.act_ch_05.triggered.connect(self.slot_checkChannel)
  209. self.ui.act_ch_06.triggered.connect(self.slot_checkChannel)
  210. self.ui.act_ch_07.triggered.connect(self.slot_checkChannel)
  211. self.ui.act_ch_08.triggered.connect(self.slot_checkChannel)
  212. self.ui.act_ch_09.triggered.connect(self.slot_checkChannel)
  213. self.ui.act_ch_10.triggered.connect(self.slot_checkChannel)
  214. self.ui.act_ch_11.triggered.connect(self.slot_checkChannel)
  215. self.ui.act_ch_12.triggered.connect(self.slot_checkChannel)
  216. self.ui.act_ch_13.triggered.connect(self.slot_checkChannel)
  217. self.ui.act_ch_14.triggered.connect(self.slot_checkChannel)
  218. self.ui.act_ch_15.triggered.connect(self.slot_checkChannel)
  219. self.ui.act_ch_16.triggered.connect(self.slot_checkChannel)
  220. self.ui.act_ch_all.triggered.connect(self.slot_checkChannel_all)
  221. self.ui.act_ch_none.triggered.connect(self.slot_checkChannel_none)
  222. self.ui.act_show_keyboard.triggered.connect(self.slot_showKeyboard)
  223. # ---------------------------------------------------------------
  224. # Final stuff
  225. self.fIdleTimer = self.startTimer(60)
  226. self.setWindowTitle(self.fUiName)
  227. self.ready()
  228. # -------------------------------------------------------------------
  229. @pyqtSlot()
  230. def slot_updateScreen(self):
  231. self.ui.graphicsView.centerOn(0, 0)
  232. self.scene.updateSize(self.ui.graphicsView.size())
  233. dial_x = self.ui.dial_x.rvalue()
  234. dial_y = self.ui.dial_y.rvalue()
  235. self.scene.setPosX(dial_x / 100, False)
  236. self.scene.setPosY(dial_y / 100, False)
  237. self.scene.setSmoothValues(dial_x / 100, dial_y / 100)
  238. @pyqtSlot(int)
  239. def slot_noteOn(self, note):
  240. self.send(["note", True, note])
  241. @pyqtSlot(int)
  242. def slot_noteOff(self, note):
  243. self.send(["note", False, note])
  244. @pyqtSlot(float)
  245. def slot_knobValueChangedX(self, x: float):
  246. self.sendControl(XYCONTROLLER_PARAMETER_X, x)
  247. self.scene.setPosX(x / 100, True)
  248. self.scene.setSmoothValues(x / 100, self.ui.dial_y.rvalue() / 100)
  249. @pyqtSlot(float)
  250. def slot_knobValueChangedY(self, y: float):
  251. self.sendControl(XYCONTROLLER_PARAMETER_Y, y)
  252. self.scene.setPosY(y / 100, True)
  253. self.scene.setSmoothValues(self.ui.dial_x.rvalue() / 100, y / 100)
  254. @pyqtSlot(str)
  255. def slot_checkCC_X(self, text: str):
  256. if not text:
  257. return
  258. cc_x = int(text.split(" ",1)[0])
  259. self.scene.setControlX(cc_x)
  260. self.sendConfigure("cc_x", str(cc_x))
  261. @pyqtSlot(str)
  262. def slot_checkCC_Y(self, text: str):
  263. if not text:
  264. return
  265. cc_y = int(text.split(" ",1)[0])
  266. self.scene.setControlY(cc_y)
  267. self.sendConfigure("cc_y", str(cc_y))
  268. @pyqtSlot(bool)
  269. def slot_checkChannel(self, clicked):
  270. if not self.sender():
  271. return
  272. channel = int(self.sender().text())
  273. if clicked and channel not in self.m_channels:
  274. self.m_channels.append(channel)
  275. elif not clicked and channel in self.m_channels:
  276. self.m_channels.remove(channel)
  277. self.scene.setChannels(self.m_channels)
  278. self.sendConfigure("channels", ",".join(str(c) for c in self.m_channels))
  279. @pyqtSlot()
  280. def slot_checkChannel_all(self):
  281. self.ui.act_ch_01.setChecked(True)
  282. self.ui.act_ch_02.setChecked(True)
  283. self.ui.act_ch_03.setChecked(True)
  284. self.ui.act_ch_04.setChecked(True)
  285. self.ui.act_ch_05.setChecked(True)
  286. self.ui.act_ch_06.setChecked(True)
  287. self.ui.act_ch_07.setChecked(True)
  288. self.ui.act_ch_08.setChecked(True)
  289. self.ui.act_ch_09.setChecked(True)
  290. self.ui.act_ch_10.setChecked(True)
  291. self.ui.act_ch_11.setChecked(True)
  292. self.ui.act_ch_12.setChecked(True)
  293. self.ui.act_ch_13.setChecked(True)
  294. self.ui.act_ch_14.setChecked(True)
  295. self.ui.act_ch_15.setChecked(True)
  296. self.ui.act_ch_16.setChecked(True)
  297. self.m_channels = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]
  298. self.scene.setChannels(self.m_channels)
  299. self.sendConfigure("channels", ",".join(str(c) for c in self.m_channels))
  300. @pyqtSlot()
  301. def slot_checkChannel_none(self):
  302. self.ui.act_ch_01.setChecked(False)
  303. self.ui.act_ch_02.setChecked(False)
  304. self.ui.act_ch_03.setChecked(False)
  305. self.ui.act_ch_04.setChecked(False)
  306. self.ui.act_ch_05.setChecked(False)
  307. self.ui.act_ch_06.setChecked(False)
  308. self.ui.act_ch_07.setChecked(False)
  309. self.ui.act_ch_08.setChecked(False)
  310. self.ui.act_ch_09.setChecked(False)
  311. self.ui.act_ch_10.setChecked(False)
  312. self.ui.act_ch_11.setChecked(False)
  313. self.ui.act_ch_12.setChecked(False)
  314. self.ui.act_ch_13.setChecked(False)
  315. self.ui.act_ch_14.setChecked(False)
  316. self.ui.act_ch_15.setChecked(False)
  317. self.ui.act_ch_16.setChecked(False)
  318. self.m_channels = []
  319. self.scene.setChannels(self.m_channels)
  320. self.sendConfigure("channels", "")
  321. @pyqtSlot(bool)
  322. def slot_setSmooth(self, smooth):
  323. self.scene.setSmooth(smooth)
  324. self.sendConfigure("smooth", "yes" if smooth else "no")
  325. if smooth:
  326. dial_x = self.ui.dial_x.rvalue()
  327. dial_y = self.ui.dial_y.rvalue()
  328. self.scene.setSmoothValues(dial_x / 100, dial_y / 100)
  329. @pyqtSlot(float, float)
  330. def slot_sceneCursorMoved(self, xp: float, yp: float):
  331. self.ui.dial_x.setValue(xp * 100, False)
  332. self.ui.dial_y.setValue(yp * 100, False)
  333. self.sendControl(XYCONTROLLER_PARAMETER_X, xp * 100)
  334. self.sendControl(XYCONTROLLER_PARAMETER_Y, yp * 100)
  335. @pyqtSlot(bool)
  336. def slot_showKeyboard(self, yesno):
  337. self.ui.scrollArea.setVisible(yesno)
  338. self.sendConfigure("show-midi-keyboard", "yes" if yesno else "no")
  339. QTimer.singleShot(0, self.slot_updateScreen)
  340. # -------------------------------------------------------------------
  341. # DSP Callbacks
  342. def dspParameterChanged(self, index: int, value: float):
  343. if index == XYCONTROLLER_PARAMETER_X:
  344. self.ui.dial_x.setValue(value, False)
  345. self.scene.setPosX(value / 100, False)
  346. elif index == XYCONTROLLER_PARAMETER_Y:
  347. self.ui.dial_y.setValue(value, False)
  348. self.scene.setPosY(value / 100, False)
  349. else:
  350. return
  351. self.scene.setSmoothValues(self.ui.dial_x.rvalue() / 100,
  352. self.ui.dial_y.rvalue() / 100)
  353. def dspStateChanged(self, key: str, value: str):
  354. if key == "guiWidth":
  355. try:
  356. width = int(value)
  357. except:
  358. width = 0
  359. if width > 0:
  360. self.resize(width, self.height())
  361. elif key == "guiHeight":
  362. try:
  363. height = int(value)
  364. except:
  365. height = 0
  366. if height > 0:
  367. self.resize(self.width(), height)
  368. elif key == "smooth":
  369. smooth = (value == "yes")
  370. self.ui.cb_smooth.blockSignals(True)
  371. self.ui.cb_smooth.setChecked(smooth)
  372. self.ui.cb_smooth.blockSignals(False)
  373. self.scene.setSmooth(smooth)
  374. if smooth:
  375. dial_x = self.ui.dial_x.rvalue()
  376. dial_y = self.ui.dial_y.rvalue()
  377. self.scene.setSmoothValues(dial_x / 100, dial_y / 100)
  378. elif key == "show-midi-keyboard":
  379. show = (value == "yes")
  380. self.ui.act_show_keyboard.blockSignals(True)
  381. self.ui.act_show_keyboard.setChecked(show)
  382. self.ui.act_show_keyboard.blockSignals(False)
  383. self.ui.scrollArea.setVisible(show)
  384. elif key == "channels":
  385. if value:
  386. self.m_channels = [int(c) for c in value.split(",")]
  387. else:
  388. self.m_channels = []
  389. self.scene.setChannels(self.m_channels)
  390. self.ui.act_ch_01.setChecked(bool(1 in self.m_channels))
  391. self.ui.act_ch_02.setChecked(bool(2 in self.m_channels))
  392. self.ui.act_ch_03.setChecked(bool(3 in self.m_channels))
  393. self.ui.act_ch_04.setChecked(bool(4 in self.m_channels))
  394. self.ui.act_ch_05.setChecked(bool(5 in self.m_channels))
  395. self.ui.act_ch_06.setChecked(bool(6 in self.m_channels))
  396. self.ui.act_ch_07.setChecked(bool(7 in self.m_channels))
  397. self.ui.act_ch_08.setChecked(bool(8 in self.m_channels))
  398. self.ui.act_ch_09.setChecked(bool(9 in self.m_channels))
  399. self.ui.act_ch_10.setChecked(bool(10 in self.m_channels))
  400. self.ui.act_ch_11.setChecked(bool(11 in self.m_channels))
  401. self.ui.act_ch_12.setChecked(bool(12 in self.m_channels))
  402. self.ui.act_ch_13.setChecked(bool(13 in self.m_channels))
  403. self.ui.act_ch_14.setChecked(bool(14 in self.m_channels))
  404. self.ui.act_ch_15.setChecked(bool(15 in self.m_channels))
  405. self.ui.act_ch_16.setChecked(bool(16 in self.m_channels))
  406. elif key == "cc_x":
  407. cc_x = int(value)
  408. self.scene.setControlX(cc_x)
  409. for cc_index in range(len(MIDI_CC_LIST)):
  410. if cc_x == int(MIDI_CC_LIST[cc_index].split(" ",1)[0]):
  411. self.ui.cb_control_x.blockSignals(True)
  412. self.ui.cb_control_x.setCurrentIndex(cc_index)
  413. self.ui.cb_control_x.blockSignals(False)
  414. break
  415. elif key == "cc_y":
  416. cc_y = int(value)
  417. self.scene.setControlY(cc_y)
  418. for cc_index in range(len(MIDI_CC_LIST)):
  419. if cc_y == int(MIDI_CC_LIST[cc_index].split(" ",1)[0]):
  420. self.ui.cb_control_y.blockSignals(True)
  421. self.ui.cb_control_y.setCurrentIndex(cc_index)
  422. self.ui.cb_control_y.blockSignals(False)
  423. break
  424. def dspNoteReceived(self, onOff, channel, note, velocity):
  425. if channel+1 not in self.m_channels:
  426. return
  427. if onOff:
  428. self.ui.keyboard.sendNoteOn(note, False)
  429. else:
  430. self.ui.keyboard.sendNoteOff(note, False)
  431. # -------------------------------------------------------------------
  432. # ExternalUI Callbacks
  433. def uiShow(self):
  434. self.show()
  435. def uiFocus(self):
  436. self.setWindowState((self.windowState() & ~Qt.WindowMinimized) | Qt.WindowActive)
  437. self.show()
  438. self.raise_()
  439. self.activateWindow()
  440. def uiHide(self):
  441. self.hide()
  442. def uiQuit(self):
  443. self.closeExternalUI()
  444. self.close()
  445. app.quit()
  446. def uiTitleChanged(self, uiTitle):
  447. self.setWindowTitle(uiTitle)
  448. # -------------------------------------------------------------------
  449. # Qt events
  450. def showEvent(self, event):
  451. self.slot_updateScreen()
  452. QMainWindow.showEvent(self, event)
  453. def resizeEvent(self, event):
  454. self.fSaveSizeNowChecker = 0
  455. self.slot_updateScreen()
  456. QMainWindow.resizeEvent(self, event)
  457. def timerEvent(self, event):
  458. if event.timerId() == self.fIdleTimer:
  459. self.idleExternalUI()
  460. self.scene.updateSmooth()
  461. if self.fSaveSizeNowChecker == 11:
  462. self.sendConfigure("guiWidth", str(self.width()))
  463. self.sendConfigure("guiHeight", str(self.height()))
  464. self.fSaveSizeNowChecker = -1
  465. elif self.fSaveSizeNowChecker >= 0:
  466. self.fSaveSizeNowChecker += 1
  467. QMainWindow.timerEvent(self, event)
  468. def closeEvent(self, event):
  469. self.closeExternalUI()
  470. QMainWindow.closeEvent(self, event)
  471. # there might be other qt windows open which will block the UI from quitting
  472. app.quit()
  473. #--------------- main ------------------
  474. if __name__ == '__main__':
  475. import resources_rc
  476. pathBinaries, _ = getPaths()
  477. gCarla.utils = CarlaUtils(os.path.join(pathBinaries, "libcarla_utils." + DLL_EXTENSION))
  478. gCarla.utils.set_process_name("XYController")
  479. app = CarlaApplication("XYController")
  480. gui = XYControllerUI()
  481. app.exit_exec()