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.

701 lines
24KB

  1. #!/usr/bin/env python3
  2. # -*- coding: utf-8 -*-
  3. # Carla patchbay widget code
  4. # Copyright (C) 2011-2013 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 (Global)
  19. from PyQt4.QtGui import QGraphicsView
  20. #QPrinter, QPrintDialog
  21. #QImage
  22. # ------------------------------------------------------------------------------------------------------------
  23. # Imports (Custom Stuff)
  24. import patchcanvas
  25. from carla_widgets import *
  26. # ------------------------------------------------------------------------------------------------------------
  27. # Try Import OpenGL
  28. try:
  29. from PyQt4.QtOpenGL import QGLWidget
  30. hasGL = True
  31. except:
  32. hasGL = False
  33. # ------------------------------------------------------------------------------------------------
  34. # Patchbay widget
  35. class CarlaPatchbayW(QGraphicsView):
  36. def __init__(self, parent, doSetup = True):
  37. QGraphicsView.__init__(self, parent)
  38. # -------------------------------------------------------------
  39. # Internal stuff
  40. self.fParent = parent
  41. self.fPluginCount = 0
  42. self.fPluginList = []
  43. # -------------------------------------------------------------
  44. # Set-up Canvas
  45. self.scene = patchcanvas.PatchScene(self, self) # FIXME?
  46. self.setScene(self.scene)
  47. self.setRenderHint(QPainter.Antialiasing, bool(parent.fSavedSettings[CARLA_KEY_CANVAS_ANTIALIASING] == patchcanvas.ANTIALIASING_FULL))
  48. if parent.fSavedSettings[CARLA_KEY_CANVAS_USE_OPENGL] and hasGL:
  49. self.setViewport(QGLWidget(self))
  50. self.setRenderHint(QPainter.HighQualityAntialiasing, parent.fSavedSettings[CARLA_KEY_CANVAS_HQ_ANTIALIASING])
  51. pOptions = patchcanvas.options_t()
  52. pOptions.theme_name = parent.fSavedSettings[CARLA_KEY_CANVAS_THEME]
  53. pOptions.auto_hide_groups = parent.fSavedSettings[CARLA_KEY_CANVAS_AUTO_HIDE_GROUPS]
  54. pOptions.use_bezier_lines = parent.fSavedSettings[CARLA_KEY_CANVAS_USE_BEZIER_LINES]
  55. pOptions.antialiasing = parent.fSavedSettings[CARLA_KEY_CANVAS_ANTIALIASING]
  56. pOptions.eyecandy = parent.fSavedSettings[CARLA_KEY_CANVAS_EYE_CANDY]
  57. pFeatures = patchcanvas.features_t()
  58. pFeatures.group_info = False
  59. pFeatures.group_rename = False
  60. pFeatures.port_info = False
  61. pFeatures.port_rename = False
  62. pFeatures.handle_group_pos = True
  63. patchcanvas.setOptions(pOptions)
  64. patchcanvas.setFeatures(pFeatures)
  65. patchcanvas.init("Carla2", self.scene, CanvasCallback, False)
  66. tryCanvasSize = parent.fSavedSettings[CARLA_KEY_CANVAS_SIZE].split("x")
  67. if len(tryCanvasSize) == 2 and tryCanvasSize[0].isdigit() and tryCanvasSize[1].isdigit():
  68. canvasWidth = int(tryCanvasSize[0])
  69. canvasHeight = int(tryCanvasSize[1])
  70. else:
  71. canvasWidth = CARLA_DEFAULT_CANVAS_SIZE_WIDTH
  72. canvasHeight = CARLA_DEFAULT_CANVAS_SIZE_HEIGHT
  73. patchcanvas.setCanvasSize(0, 0, canvasWidth, canvasHeight)
  74. patchcanvas.setInitialPos(canvasWidth / 2, canvasHeight / 2)
  75. self.setSceneRect(0, 0, canvasWidth, canvasHeight)
  76. # -------------------------------------------------------------
  77. # Connect actions to functions
  78. if not doSetup: return
  79. parent.ui.act_plugins_enable.triggered.connect(self.slot_pluginsEnable)
  80. parent.ui.act_plugins_disable.triggered.connect(self.slot_pluginsDisable)
  81. parent.ui.act_plugins_volume100.triggered.connect(self.slot_pluginsVolume100)
  82. parent.ui.act_plugins_mute.triggered.connect(self.slot_pluginsMute)
  83. parent.ui.act_plugins_wet100.triggered.connect(self.slot_pluginsWet100)
  84. parent.ui.act_plugins_bypass.triggered.connect(self.slot_pluginsBypass)
  85. parent.ui.act_plugins_center.triggered.connect(self.slot_pluginsCenter)
  86. parent.ui.act_plugins_panic.triggered.connect(self.slot_pluginsDisable)
  87. parent.ui.act_canvas_arrange.setEnabled(False) # TODO, later
  88. parent.ui.act_canvas_arrange.triggered.connect(self.slot_canvasArrange)
  89. parent.ui.act_canvas_refresh.triggered.connect(self.slot_canvasRefresh)
  90. parent.ui.act_canvas_zoom_fit.triggered.connect(self.slot_canvasZoomFit)
  91. parent.ui.act_canvas_zoom_in.triggered.connect(self.slot_canvasZoomIn)
  92. parent.ui.act_canvas_zoom_out.triggered.connect(self.slot_canvasZoomOut)
  93. parent.ui.act_canvas_zoom_100.triggered.connect(self.slot_canvasZoomReset)
  94. parent.ui.act_canvas_print.triggered.connect(self.slot_canvasPrint)
  95. parent.ui.act_canvas_save_image.triggered.connect(self.slot_canvasSaveImage)
  96. parent.ui.act_settings_configure.triggered.connect(self.slot_configureCarla)
  97. #self.ui.miniCanvasPreview-miniCanvasMoved(double, double)"), SLOT("slot_miniCanvasMoved(double, double)"))
  98. #self.ui.graphicsView.horizontalScrollBar()-valueChanged.connect(self.slot_horizontalScrollBarChanged)
  99. #self.ui.graphicsView.verticalScrollBar()-valueChanged.connect(self.slot_verticalScrollBarChanged)
  100. #self.scene-sceneGroupMoved(int, int, QPointF)"), SLOT("slot_canvasItemMoved(int, int, QPointF)"))
  101. #self.scene-scaleChanged(double)"), SLOT("slot_canvasScaleChanged(double)"))
  102. parent.ParameterValueChangedCallback.connect(self.slot_handleParameterValueChangedCallback)
  103. parent.ParameterDefaultChangedCallback.connect(self.slot_handleParameterDefaultChangedCallback)
  104. parent.ParameterMidiChannelChangedCallback.connect(self.slot_handleParameterMidiChannelChangedCallback)
  105. parent.ParameterMidiCcChangedCallback.connect(self.slot_handleParameterMidiCcChangedCallback)
  106. parent.ProgramChangedCallback.connect(self.slot_handleProgramChangedCallback)
  107. parent.MidiProgramChangedCallback.connect(self.slot_handleMidiProgramChangedCallback)
  108. parent.NoteOnCallback.connect(self.slot_handleNoteOnCallback)
  109. parent.NoteOffCallback.connect(self.slot_handleNoteOffCallback)
  110. parent.ShowGuiCallback.connect(self.slot_handleShowGuiCallback)
  111. parent.UpdateCallback.connect(self.slot_handleUpdateCallback)
  112. parent.ReloadInfoCallback.connect(self.slot_handleReloadInfoCallback)
  113. parent.ReloadParametersCallback.connect(self.slot_handleReloadParametersCallback)
  114. parent.ReloadProgramsCallback.connect(self.slot_handleReloadProgramsCallback)
  115. parent.ReloadAllCallback.connect(self.slot_handleReloadAllCallback)
  116. parent.PatchbayClientAddedCallback.connect(self.slot_handlePatchbayClientAddedCallback)
  117. parent.PatchbayClientRemovedCallback.connect(self.slot_handlePatchbayClientRemovedCallback)
  118. parent.PatchbayClientRenamedCallback.connect(self.slot_handlePatchbayClientRenamedCallback)
  119. parent.PatchbayPortAddedCallback.connect(self.slot_handlePatchbayPortAddedCallback)
  120. parent.PatchbayPortRemovedCallback.connect(self.slot_handlePatchbayPortRemovedCallback)
  121. parent.PatchbayPortRenamedCallback.connect(self.slot_handlePatchbayPortRenamedCallback)
  122. parent.PatchbayConnectionAddedCallback.connect(self.slot_handlePatchbayConnectionAddedCallback)
  123. parent.PatchbayConnectionRemovedCallback.connect(self.slot_handlePatchbayConnectionRemovedCallback)
  124. #parent.PatchbayIconChangedCallback.connect(self.slot_handlePatchbayIconChangedCallback)
  125. # -----------------------------------------------------------------
  126. def getPluginCount(self):
  127. return self.fPluginCount
  128. # -----------------------------------------------------------------
  129. def addPlugin(self, pluginId, isProjectLoading):
  130. pitem = PluginEdit(self, pluginId)
  131. self.fPluginList.append(pitem)
  132. self.fPluginCount += 1
  133. def removePlugin(self, pluginId):
  134. if pluginId >= self.fPluginCount:
  135. return
  136. pitem = self.fPluginList[pluginId]
  137. if pitem is None:
  138. return
  139. self.fPluginCount -= 1
  140. self.fPluginList.pop(pluginId)
  141. pitem.close()
  142. del pitem
  143. # push all plugins 1 slot back
  144. for i in range(pluginId, self.fPluginCount):
  145. self.fPluginList[i].fPluginId = i # FIXME ?
  146. def renamePlugin(self, pluginId, newName):
  147. if pluginId >= self.fPluginCount:
  148. return
  149. pitem = self.fPluginList[pluginId]
  150. if pitem is None:
  151. return
  152. pitem.setName(newName)
  153. def disablePlugin(self, pluginId, errorMsg):
  154. pass
  155. def removeAllPlugins(self):
  156. for i in range(self.fPluginCount):
  157. pitem = self.fPluginList[i]
  158. if pitem is None:
  159. break
  160. pitem.close()
  161. del pitem
  162. self.fPluginCount = 0
  163. self.fPluginList = []
  164. # -----------------------------------------------------------------
  165. def engineStarted(self):
  166. pass
  167. def engineStopped(self):
  168. patchcanvas.clear()
  169. def engineChanged(self):
  170. pass
  171. # -----------------------------------------------------------------
  172. def idleFast(self):
  173. pass
  174. def idleSlow(self):
  175. for i in range(self.fPluginCount):
  176. pitem = self.fPluginList[i]
  177. if pitem is None:
  178. break
  179. pitem.idleSlow()
  180. # -----------------------------------------------------------------
  181. def saveSettings(self, settings):
  182. pass
  183. # -----------------------------------------------------------------
  184. def recheckPluginHints(self, hints):
  185. pass
  186. # -----------------------------------------------------------------
  187. @pyqtSlot()
  188. def slot_pluginsEnable(self):
  189. if not Carla.host.is_engine_running():
  190. return
  191. for i in range(self.fPluginCount):
  192. Carla.host.set_active(i, True)
  193. @pyqtSlot()
  194. def slot_pluginsDisable(self):
  195. if not Carla.host.is_engine_running():
  196. return
  197. for i in range(self.fPluginCount):
  198. Carla.host.set_active(i, False)
  199. @pyqtSlot()
  200. def slot_pluginsVolume100(self):
  201. if not Carla.host.is_engine_running():
  202. return
  203. for i in range(self.fPluginCount):
  204. pitem = self.fPluginList[i]
  205. if pitem is None:
  206. break
  207. if pitem.fPluginInfo['hints'] & PLUGIN_CAN_VOLUME:
  208. pitem.setParameterValue(PARAMETER_VOLUME, 1.0)
  209. Carla.host.set_volume(i, 1.0)
  210. @pyqtSlot()
  211. def slot_pluginsMute(self):
  212. if not Carla.host.is_engine_running():
  213. return
  214. for i in range(self.fPluginCount):
  215. pitem = self.fPluginList[i]
  216. if pitem is None:
  217. break
  218. if pitem.fPluginInfo['hints'] & PLUGIN_CAN_VOLUME:
  219. pitem.setParameterValue(PARAMETER_VOLUME, 0.0)
  220. Carla.host.set_volume(i, 0.0)
  221. @pyqtSlot()
  222. def slot_pluginsWet100(self):
  223. if not Carla.host.is_engine_running():
  224. return
  225. for i in range(self.fPluginCount):
  226. pitem = self.fPluginList[i]
  227. if pitem is None:
  228. break
  229. if pitem.fPluginInfo['hints'] & PLUGIN_CAN_DRYWET:
  230. pitem.setParameterValue(PARAMETER_DRYWET, 1.0)
  231. Carla.host.set_drywet(i, 1.0)
  232. @pyqtSlot()
  233. def slot_pluginsBypass(self):
  234. if not Carla.host.is_engine_running():
  235. return
  236. for i in range(self.fPluginCount):
  237. pitem = self.fPluginList[i]
  238. if pitem is None:
  239. break
  240. if pitem.fPluginInfo['hints'] & PLUGIN_CAN_DRYWET:
  241. pitem.setParameterValue(PARAMETER_DRYWET, 0.0)
  242. Carla.host.set_drywet(i, 0.0)
  243. @pyqtSlot()
  244. def slot_pluginsCenter(self):
  245. if not Carla.host.is_engine_running():
  246. return
  247. for i in range(self.fPluginCount):
  248. pitem = self.fPluginList[i]
  249. if pitem is None:
  250. break
  251. if pitem.fPluginInfo['hints'] & PLUGIN_CAN_BALANCE:
  252. pitem.setParameterValue(PARAMETER_BALANCE_LEFT, -1.0)
  253. pitem.setParameterValue(PARAMETER_BALANCE_RIGHT, 1.0)
  254. Carla.host.set_balance_left(i, -1.0)
  255. Carla.host.set_balance_right(i, 1.0)
  256. if pitem.fPluginInfo['hints'] & PLUGIN_CAN_PANNING:
  257. pitem.setParameterValue(PARAMETER_PANNING, 1.0)
  258. Carla.host.set_panning(i, 1.0)
  259. # -----------------------------------------------------------------
  260. @pyqtSlot()
  261. def slot_configureCarla(self):
  262. if self.fParent is None or not self.fParent.openSettingsWindow(True, hasGL):
  263. return
  264. self.fParent.loadSettings(False)
  265. patchcanvas.clear()
  266. pOptions = patchcanvas.options_t()
  267. pOptions.theme_name = self.fParent.fSavedSettings["Canvas/Theme"]
  268. pOptions.auto_hide_groups = self.fParent.fSavedSettings["Canvas/AutoHideGroups"]
  269. pOptions.use_bezier_lines = self.fParent.fSavedSettings["Canvas/UseBezierLines"]
  270. pOptions.antialiasing = self.fParent.fSavedSettings["Canvas/Antialiasing"]
  271. pOptions.eyecandy = self.fParent.fSavedSettings["Canvas/EyeCandy"]
  272. pFeatures = patchcanvas.features_t()
  273. pFeatures.group_info = False
  274. pFeatures.group_rename = False
  275. pFeatures.port_info = False
  276. pFeatures.port_rename = False
  277. pFeatures.handle_group_pos = True
  278. patchcanvas.setOptions(pOptions)
  279. patchcanvas.setFeatures(pFeatures)
  280. patchcanvas.init("Carla2", self.scene, canvasCallback, False)
  281. if Carla.host.is_engine_running():
  282. Carla.host.patchbay_refresh()
  283. # -----------------------------------------------------------------
  284. @pyqtSlot(int, int, float)
  285. def slot_handleParameterValueChangedCallback(self, pluginId, index, value):
  286. if pluginId >= self.fPluginCount:
  287. return
  288. pitem = self.fPluginList[pluginId]
  289. if pitem is None:
  290. return
  291. pitem.setParameterValue(index, value)
  292. @pyqtSlot(int, int, float)
  293. def slot_handleParameterDefaultChangedCallback(self, pluginId, index, value):
  294. if pluginId >= self.fPluginCount:
  295. return
  296. pitem = self.fPluginList[pluginId]
  297. if pitem is None:
  298. return
  299. pitem.setParameterDefault(index, value)
  300. @pyqtSlot(int, int, int)
  301. def slot_handleParameterMidiChannelChangedCallback(self, pluginId, index, channel):
  302. if pluginId >= self.fPluginCount:
  303. return
  304. pitem = self.fPluginList[pluginId]
  305. if pitem is None:
  306. return
  307. pitem.setParameterMidiChannel(index, channel)
  308. @pyqtSlot(int, int, int)
  309. def slot_handleParameterMidiCcChangedCallback(self, pluginId, index, cc):
  310. if pluginId >= self.fPluginCount:
  311. return
  312. pitem = self.fPluginList[pluginId]
  313. if pitem is None:
  314. return
  315. pitem.setParameterMidiControl(index, cc)
  316. # -----------------------------------------------------------------
  317. @pyqtSlot(int, int)
  318. def slot_handleProgramChangedCallback(self, pluginId, index):
  319. if pluginId >= self.fPluginCount:
  320. return
  321. pitem = self.fPluginList[pluginId]
  322. if pitem is None:
  323. return
  324. pitem.setProgram(index)
  325. @pyqtSlot(int, int)
  326. def slot_handleMidiProgramChangedCallback(self, pluginId, index):
  327. if pluginId >= self.fPluginCount:
  328. return
  329. pitem = self.fPluginList[pluginId]
  330. if pitem is None:
  331. return
  332. pitem.setMidiProgram(index)
  333. # -----------------------------------------------------------------
  334. @pyqtSlot(int, int, int, int)
  335. def slot_handleNoteOnCallback(self, pluginId, channel, note, velo):
  336. if pluginId >= self.fPluginCount:
  337. return
  338. pitem = self.fPluginList[pluginId]
  339. if pitem is None:
  340. return
  341. pitem.sendNoteOn(channel, note)
  342. @pyqtSlot(int, int, int)
  343. def slot_handleNoteOffCallback(self, pluginId, channel, note):
  344. if pluginId >= self.fPluginCount:
  345. return
  346. pitem = self.fPluginList[pluginId]
  347. if pitem is None:
  348. return
  349. pitem.sendNoteOff(channel, note)
  350. # -----------------------------------------------------------------
  351. @pyqtSlot(int, int)
  352. def slot_handleShowGuiCallback(self, pluginId, state):
  353. pass
  354. # -----------------------------------------------------------------
  355. @pyqtSlot(int)
  356. def slot_handleUpdateCallback(self, pluginId):
  357. if pluginId >= self.fPluginCount:
  358. return
  359. pitem = self.fPluginList[pluginId]
  360. if pitem is None:
  361. return
  362. pitem.updateInfo()
  363. @pyqtSlot(int)
  364. def slot_handleReloadInfoCallback(self, pluginId):
  365. if pluginId >= self.fPluginCount:
  366. return
  367. pitem = self.fPluginList[pluginId]
  368. if pitem is None:
  369. return
  370. pitem.reloadInfo()
  371. @pyqtSlot(int)
  372. def slot_handleReloadParametersCallback(self, pluginId):
  373. if pluginId >= self.fPluginCount:
  374. return
  375. pitem = self.fPluginList[pluginId]
  376. if pitem is None:
  377. return
  378. pitem.reloadParameters()
  379. @pyqtSlot(int)
  380. def slot_handleReloadProgramsCallback(self, pluginId):
  381. if pluginId >= self.fPluginCount:
  382. return
  383. pitem = self.fPluginList[pluginId]
  384. if pitem is None:
  385. return
  386. pitem.reloadPrograms()
  387. @pyqtSlot(int)
  388. def slot_handleReloadAllCallback(self, pluginId):
  389. if pluginId >= self.fPluginCount:
  390. return
  391. pitem = self.fPluginList[pluginId]
  392. if pitem is None:
  393. return
  394. pitem.reloadAll()
  395. # -----------------------------------------------------------------
  396. @pyqtSlot(int, int, str)
  397. def slot_handlePatchbayClientAddedCallback(self, clientId, clientName):
  398. patchcanvas.addGroup(clientId, clientName)
  399. #QTimer.singleShot(0, self.ui.miniCanvasPreview, SLOT("update()"))
  400. @pyqtSlot(int, str)
  401. def slot_handlePatchbayClientRemovedCallback(self, clientId, clientName):
  402. #if not self.fEngineStarted: return
  403. patchcanvas.removeGroup(clientId)
  404. #QTimer.singleShot(0, self.ui.miniCanvasPreview, SLOT("update()"))
  405. @pyqtSlot(int, str)
  406. def slot_handlePatchbayClientRenamedCallback(self, clientId, newClientName):
  407. patchcanvas.renameGroup(clientId, newClientName)
  408. #QTimer.singleShot(0, self.ui.miniCanvasPreview, SLOT("update()"))
  409. @pyqtSlot(int, int, int, str)
  410. def slot_handlePatchbayPortAddedCallback(self, clientId, portId, portFlags, portName):
  411. if (portFlags & PATCHBAY_PORT_IS_INPUT):
  412. portMode = patchcanvas.PORT_MODE_INPUT
  413. elif (portFlags & PATCHBAY_PORT_IS_OUTPUT):
  414. portMode = patchcanvas.PORT_MODE_OUTPUT
  415. else:
  416. portMode = patchcanvas.PORT_MODE_NULL
  417. if (portFlags & PATCHBAY_PORT_IS_AUDIO):
  418. portType = patchcanvas.PORT_TYPE_AUDIO_JACK
  419. elif (portFlags & PATCHBAY_PORT_IS_MIDI):
  420. portType = patchcanvas.PORT_TYPE_MIDI_JACK
  421. else:
  422. portType = patchcanvas.PORT_TYPE_NULL
  423. patchcanvas.addPort(clientId, portId, portName, portMode, portType)
  424. #QTimer.singleShot(0, self.ui.miniCanvasPreview, SLOT("update()"))
  425. @pyqtSlot(int, int, str)
  426. def slot_handlePatchbayPortRemovedCallback(self, groupId, portId, fullPortName):
  427. #if not self.fEngineStarted: return
  428. patchcanvas.removePort(portId)
  429. #QTimer.singleShot(0, self.ui.miniCanvasPreview, SLOT("update()"))
  430. @pyqtSlot(int, int, str)
  431. def slot_handlePatchbayPortRenamedCallback(self, groupId, portId, newPortName):
  432. patchcanvas.renamePort(portId, newPortName)
  433. #QTimer.singleShot(0, self.ui.miniCanvasPreview, SLOT("update()"))
  434. @pyqtSlot(int, int, int)
  435. def slot_handlePatchbayConnectionAddedCallback(self, connectionId, portOutId, portInId):
  436. patchcanvas.connectPorts(connectionId, portOutId, portInId)
  437. #QTimer.singleShot(0, self.ui.miniCanvasPreview, SLOT("update()"))
  438. @pyqtSlot(int)
  439. def slot_handlePatchbayConnectionRemovedCallback(self, connectionId):
  440. #if not self.fEngineStarted: return
  441. patchcanvas.disconnectPorts(connectionId)
  442. #QTimer.singleShot(0, self.ui.miniCanvasPreview, SLOT("update()"))
  443. @pyqtSlot(int, int)
  444. def slot_handlePatchbayIconChangedCallback(self, clientId, icon):
  445. patchcanvas.setGroupIcon(clientId, icon)
  446. # -----------------------------------------------------------------
  447. @pyqtSlot()
  448. def slot_canvasArrange(self):
  449. patchcanvas.arrange()
  450. @pyqtSlot()
  451. def slot_canvasRefresh(self):
  452. patchcanvas.clear()
  453. if Carla.host.is_engine_running():
  454. Carla.host.patchbay_refresh()
  455. #QTimer.singleShot(1000 if self.fSavedSettings['Canvas/EyeCandy'] else 0, self.ui.miniCanvasPreview, SLOT("update()"))
  456. @pyqtSlot()
  457. def slot_canvasZoomFit(self):
  458. self.scene.zoom_fit()
  459. @pyqtSlot()
  460. def slot_canvasZoomIn(self):
  461. self.scene.zoom_in()
  462. @pyqtSlot()
  463. def slot_canvasZoomOut(self):
  464. self.scene.zoom_out()
  465. @pyqtSlot()
  466. def slot_canvasZoomReset(self):
  467. self.scene.zoom_reset()
  468. @pyqtSlot()
  469. def slot_canvasPrint(self):
  470. self.scene.clearSelection()
  471. self.fExportPrinter = QPrinter()
  472. dialog = QPrintDialog(self.fExportPrinter, self)
  473. if dialog.exec_():
  474. painter = QPainter(self.fExportPrinter)
  475. painter.save()
  476. painter.setRenderHint(QPainter.Antialiasing)
  477. painter.setRenderHint(QPainter.TextAntialiasing)
  478. self.scene.render(painter)
  479. painter.restore()
  480. @pyqtSlot()
  481. def slot_canvasSaveImage(self):
  482. newPath = QFileDialog.getSaveFileName(self, self.tr("Save Image"), filter=self.tr("PNG Image (*.png);;JPEG Image (*.jpg)"))
  483. if newPath:
  484. self.scene.clearSelection()
  485. # FIXME - must be a better way...
  486. if newPath.endswith((".jpg", ".jpG", ".jPG", ".JPG", ".JPg", ".Jpg")):
  487. imgFormat = "JPG"
  488. elif newPath.endswith((".png", ".pnG", ".pNG", ".PNG", ".PNg", ".Png")):
  489. imgFormat = "PNG"
  490. else:
  491. # File-dialog may not auto-add the extension
  492. imgFormat = "PNG"
  493. newPath += ".png"
  494. self.fExportImage = QImage(self.scene.sceneRect().width(), self.scene.sceneRect().height(), QImage.Format_RGB32)
  495. painter = QPainter(self.fExportImage)
  496. painter.save()
  497. painter.setRenderHint(QPainter.Antialiasing) # TODO - set true, cleanup this
  498. painter.setRenderHint(QPainter.TextAntialiasing)
  499. self.scene.render(painter)
  500. self.fExportImage.save(newPath, imgFormat, 100)
  501. painter.restore()
  502. # ------------------------------------------------------------------------------------------------
  503. # Canvas callback
  504. def canvasCallback(action, value1, value2, valueStr):
  505. if action == patchcanvas.ACTION_GROUP_INFO:
  506. pass
  507. elif action == patchcanvas.ACTION_GROUP_RENAME:
  508. pass
  509. elif action == patchcanvas.ACTION_GROUP_SPLIT:
  510. groupId = value1
  511. patchcanvas.splitGroup(groupId)
  512. Carla.gui.ui.miniCanvasPreview.update()
  513. elif action == patchcanvas.ACTION_GROUP_JOIN:
  514. groupId = value1
  515. patchcanvas.joinGroup(groupId)
  516. Carla.gui.ui.miniCanvasPreview.update()
  517. elif action == patchcanvas.ACTION_PORT_INFO:
  518. pass
  519. elif action == patchcanvas.ACTION_PORT_RENAME:
  520. pass
  521. elif action == patchcanvas.ACTION_PORTS_CONNECT:
  522. portIdA = value1
  523. portIdB = value2
  524. if not Carla.host.patchbay_connect(portIdA, portIdB):
  525. print("Connection failed:", Carla.host.get_last_error())
  526. elif action == patchcanvas.ACTION_PORTS_DISCONNECT:
  527. connectionId = value1
  528. if not Carla.host.patchbay_disconnect(connectionId):
  529. print("Disconnect failed:", Carla.host.get_last_error())