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.

patchcanvas.py 37KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093
  1. #!/usr/bin/env python3
  2. # -*- coding: utf-8 -*-
  3. # PatchBay Canvas engine using QGraphicsView/Scene
  4. # Copyright (C) 2010-2019 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 PyQt5.QtCore import pyqtSlot, qCritical, qFatal, qWarning, QObject
  20. from PyQt5.QtCore import QPointF, QRectF, QTimer
  21. from PyQt5.QtWidgets import QGraphicsObject
  22. # ------------------------------------------------------------------------------------------------------------
  23. # Imports (Custom)
  24. from . import (
  25. canvas,
  26. features,
  27. options,
  28. group_dict_t,
  29. port_dict_t,
  30. connection_dict_t,
  31. bool2str,
  32. icon2str,
  33. split2str,
  34. port_mode2str,
  35. port_type2str,
  36. CanvasIconType,
  37. CanvasRubberbandType,
  38. ACTION_PORTS_DISCONNECT,
  39. EYECANDY_FULL,
  40. ICON_APPLICATION,
  41. ICON_HARDWARE,
  42. ICON_LADISH_ROOM,
  43. PORT_MODE_INPUT,
  44. PORT_MODE_OUTPUT,
  45. SPLIT_YES,
  46. SPLIT_NO,
  47. SPLIT_UNDEF,
  48. MAX_PLUGIN_ID_ALLOWED,
  49. )
  50. from .canvasbox import CanvasBox
  51. from .canvasbezierline import CanvasBezierLine
  52. from .canvasline import CanvasLine
  53. from .theme import Theme, getDefaultTheme, getThemeName
  54. from .utils import CanvasCallback, CanvasGetNewGroupPos, CanvasItemFX, CanvasRemoveItemFX
  55. # FIXME
  56. from . import *
  57. from .scene import PatchScene
  58. from carla_shared import QSafeSettings
  59. # ------------------------------------------------------------------------------------------------------------
  60. class CanvasObject(QObject):
  61. def __init__(self, parent=None):
  62. QObject.__init__(self, parent)
  63. @pyqtSlot()
  64. def AnimationFinishedShow(self):
  65. animation = self.sender()
  66. if animation:
  67. animation.forceStop()
  68. canvas.animation_list.remove(animation)
  69. @pyqtSlot()
  70. def AnimationFinishedHide(self):
  71. animation = self.sender()
  72. if animation:
  73. animation.forceStop()
  74. canvas.animation_list.remove(animation)
  75. item = animation.item()
  76. if item:
  77. if isinstance(item, QGraphicsObject):
  78. item.blockSignals(True)
  79. item.hide()
  80. item.blockSignals(False)
  81. else:
  82. item.hide()
  83. @pyqtSlot()
  84. def AnimationFinishedDestroy(self):
  85. animation = self.sender()
  86. if animation:
  87. animation.forceStop()
  88. canvas.animation_list.remove(animation)
  89. item = animation.item()
  90. if item:
  91. CanvasRemoveItemFX(item)
  92. @pyqtSlot()
  93. def PortContextMenuConnect(self):
  94. try:
  95. sources, targets = self.sender().data()
  96. except:
  97. return
  98. for port_type in (PORT_TYPE_AUDIO_JACK, PORT_TYPE_MIDI_JACK, PORT_TYPE_MIDI_ALSA, PORT_TYPE_PARAMETER):
  99. source_ports = sources[port_type]
  100. target_ports = targets[port_type]
  101. source_ports_len = len(source_ports)
  102. target_ports_len = len(target_ports)
  103. if source_ports_len == 0 or target_ports_len == 0:
  104. continue
  105. for i in range(min(source_ports_len, target_ports_len)):
  106. data = "%i:%i:%i:%i" % (source_ports[i][0],
  107. source_ports[i][1],
  108. target_ports[i][0],
  109. target_ports[i][1])
  110. CanvasCallback(ACTION_PORTS_CONNECT, 0, 0, data)
  111. if source_ports_len == 1 and target_ports_len > 1:
  112. for i in range(1, target_ports_len):
  113. data = "%i:%i:%i:%i" % (source_ports[0][0],
  114. source_ports[0][1],
  115. target_ports[i][0],
  116. target_ports[i][1])
  117. CanvasCallback(ACTION_PORTS_CONNECT, 0, 0, data)
  118. @pyqtSlot()
  119. def PortContextMenuDisconnect(self):
  120. try:
  121. connectionId = int(self.sender().data())
  122. except:
  123. return
  124. CanvasCallback(ACTION_PORTS_DISCONNECT, connectionId, 0, "")
  125. @pyqtSlot(int, bool, int, int)
  126. def boxPositionChanged(self, groupId, split, x, y):
  127. x2 = y2 = 0
  128. if split:
  129. for group in canvas.group_list:
  130. if group.group_id == groupId:
  131. if group.split:
  132. pos = group.widgets[1].pos()
  133. x2 = pos.x()
  134. y2 = pos.y()
  135. break
  136. valueStr = "%i:%i:%i:%i" % (x, y, x2, y2)
  137. CanvasCallback(ACTION_GROUP_POSITION, groupId, 0, valueStr)
  138. @pyqtSlot(int, bool, int, int)
  139. def sboxPositionChanged(self, groupId, split, x2, y2):
  140. x = y = 0
  141. for group in canvas.group_list:
  142. if group.group_id == groupId:
  143. pos = group.widgets[0].pos()
  144. x = pos.x()
  145. y = pos.y()
  146. break
  147. valueStr = "%i:%i:%i:%i" % (x, y, x2, y2)
  148. CanvasCallback(ACTION_GROUP_POSITION, groupId, 0, valueStr)
  149. # ------------------------------------------------------------------------------------------------------------
  150. def getStoredCanvasPosition(key, fallback_pos):
  151. try:
  152. return canvas.settings.value("CanvasPositions/" + key, fallback_pos, type=QPointF)
  153. except:
  154. return fallback_pos
  155. def getStoredCanvasSplit(group_name, fallback_split_mode):
  156. try:
  157. return canvas.settings.value("CanvasPositions/%s_SPLIT" % group_name, fallback_split_mode, type=int)
  158. except:
  159. return fallback_split_mode
  160. # ------------------------------------------------------------------------------------------------------------
  161. def init(appName, scene, callback, debug=False):
  162. if debug:
  163. print("PatchCanvas::init(\"%s\", %s, %s, %s)" % (appName, scene, callback, bool2str(debug)))
  164. if canvas.initiated:
  165. qCritical("PatchCanvas::init() - already initiated")
  166. return
  167. if not callback:
  168. qFatal("PatchCanvas::init() - fatal error: callback not set")
  169. return
  170. canvas.callback = callback
  171. canvas.debug = debug
  172. canvas.scene = scene
  173. canvas.last_z_value = 0
  174. canvas.last_connection_id = 0
  175. canvas.initial_pos = QPointF(0, 0)
  176. canvas.size_rect = QRectF()
  177. if not canvas.qobject:
  178. canvas.qobject = CanvasObject()
  179. if not canvas.settings:
  180. canvas.settings = QSafeSettings("falkTX", appName)
  181. if canvas.theme:
  182. del canvas.theme
  183. canvas.theme = None
  184. for i in range(Theme.THEME_MAX):
  185. this_theme_name = getThemeName(i)
  186. if this_theme_name == options.theme_name:
  187. canvas.theme = Theme(i)
  188. break
  189. if not canvas.theme:
  190. canvas.theme = Theme(getDefaultTheme())
  191. canvas.scene.updateTheme()
  192. canvas.initiated = True
  193. def clear():
  194. if canvas.debug:
  195. print("PatchCanvas::clear()")
  196. group_pos = {}
  197. group_list_ids = []
  198. port_list_ids = []
  199. connection_list_ids = []
  200. for group in canvas.group_list:
  201. group_pos[group.group_name] = (
  202. group.split,
  203. group.widgets[0].pos(),
  204. group.widgets[1].pos() if group.split else None,
  205. )
  206. group_list_ids.append(group.group_id)
  207. for port in canvas.port_list:
  208. port_list_ids.append((port.group_id, port.port_id))
  209. for connection in canvas.connection_list:
  210. connection_list_ids.append(connection.connection_id)
  211. for idx in connection_list_ids:
  212. disconnectPorts(idx)
  213. for group_id, port_id in port_list_ids:
  214. removePort(group_id, port_id)
  215. for idx in group_list_ids:
  216. removeGroup(idx)
  217. canvas.last_z_value = 0
  218. canvas.last_connection_id = 0
  219. canvas.group_list = []
  220. canvas.port_list = []
  221. canvas.connection_list = []
  222. canvas.group_plugin_map = {}
  223. canvas.old_group_pos = group_pos
  224. canvas.scene.clearSelection()
  225. animatedItems = []
  226. for animation in canvas.animation_list:
  227. animatedItems.append(animation.item())
  228. for item in canvas.scene.items():
  229. if item.type() in (CanvasIconType, CanvasRubberbandType) or item in animatedItems:
  230. continue
  231. canvas.scene.removeItem(item)
  232. del item
  233. canvas.initiated = False
  234. QTimer.singleShot(0, canvas.scene.update)
  235. # ------------------------------------------------------------------------------------------------------------
  236. def setInitialPos(x, y):
  237. if canvas.debug:
  238. print("PatchCanvas::setInitialPos(%i, %i)" % (x, y))
  239. canvas.initial_pos.setX(x)
  240. canvas.initial_pos.setY(y)
  241. def setCanvasSize(x, y, width, height):
  242. if canvas.debug:
  243. print("PatchCanvas::setCanvasSize(%i, %i, %i, %i)" % (x, y, width, height))
  244. canvas.size_rect.setX(x)
  245. canvas.size_rect.setY(y)
  246. canvas.size_rect.setWidth(width)
  247. canvas.size_rect.setHeight(height)
  248. canvas.scene.updateLimits()
  249. canvas.scene.fixScaleFactor()
  250. def addGroup(group_id, group_name, split=SPLIT_UNDEF, icon=ICON_APPLICATION):
  251. if canvas.debug:
  252. print("PatchCanvas::addGroup(%i, %s, %s, %s)" % (
  253. group_id, group_name.encode(), split2str(split), icon2str(icon)))
  254. for group in canvas.group_list:
  255. if group.group_id == group_id:
  256. qWarning("PatchCanvas::addGroup(%i, %s, %s, %s) - group already exists" % (
  257. group_id, group_name.encode(), split2str(split), icon2str(icon)))
  258. return None
  259. old_matching_group = canvas.old_group_pos.pop(group_name, None)
  260. if split == SPLIT_UNDEF:
  261. isHardware = bool(icon == ICON_HARDWARE)
  262. if features.handle_group_pos:
  263. split = getStoredCanvasSplit(group_name, SPLIT_YES if isHardware else split)
  264. elif isHardware:
  265. split = SPLIT_YES
  266. elif old_matching_group is not None and old_matching_group[0]:
  267. split = SPLIT_YES
  268. group_box = CanvasBox(group_id, group_name, icon)
  269. group_box.positionChanged.connect(canvas.qobject.boxPositionChanged)
  270. group_box.blockSignals(True)
  271. group_dict = group_dict_t()
  272. group_dict.group_id = group_id
  273. group_dict.group_name = group_name
  274. group_dict.split = bool(split == SPLIT_YES)
  275. group_dict.icon = icon
  276. group_dict.plugin_id = -1
  277. group_dict.plugin_ui = False
  278. group_dict.plugin_inline = False
  279. group_dict.widgets = [group_box, None]
  280. if split == SPLIT_YES:
  281. group_box.setSplit(True, PORT_MODE_OUTPUT)
  282. if features.handle_group_pos:
  283. group_box.setPos(getStoredCanvasPosition(group_name + "_OUTPUT", CanvasGetNewGroupPos(False)))
  284. elif old_matching_group is not None:
  285. group_box.setPos(old_matching_group[1])
  286. else:
  287. group_box.setPos(CanvasGetNewGroupPos(False))
  288. group_sbox = CanvasBox(group_id, group_name, icon)
  289. group_sbox.positionChanged.connect(canvas.qobject.sboxPositionChanged)
  290. group_sbox.blockSignals(True)
  291. group_sbox.setSplit(True, PORT_MODE_INPUT)
  292. group_dict.widgets[1] = group_sbox
  293. if features.handle_group_pos:
  294. group_sbox.setPos(getStoredCanvasPosition(group_name + "_INPUT", CanvasGetNewGroupPos(True)))
  295. elif old_matching_group is not None and old_matching_group[0]:
  296. group_sbox.setPos(old_matching_group[2])
  297. else:
  298. group_sbox.setPos(group_box.x() + group_box.boundingRect().width() + 300, group_box.y())
  299. canvas.last_z_value += 1
  300. group_sbox.setZValue(canvas.last_z_value)
  301. if options.eyecandy == EYECANDY_FULL and not options.auto_hide_groups:
  302. CanvasItemFX(group_sbox, True, False)
  303. group_sbox.checkItemPos()
  304. group_sbox.blockSignals(False)
  305. else:
  306. group_box.setSplit(False)
  307. if features.handle_group_pos:
  308. group_box.setPos(getStoredCanvasPosition(group_name, CanvasGetNewGroupPos(False)))
  309. elif old_matching_group is not None:
  310. group_box.setPos(old_matching_group[1])
  311. else:
  312. # Special ladish fake-split groups
  313. horizontal = bool(icon == ICON_HARDWARE or icon == ICON_LADISH_ROOM)
  314. group_box.setPos(CanvasGetNewGroupPos(horizontal))
  315. canvas.last_z_value += 1
  316. group_box.setZValue(canvas.last_z_value)
  317. group_box.checkItemPos()
  318. group_box.blockSignals(False)
  319. canvas.group_list.append(group_dict)
  320. if options.eyecandy == EYECANDY_FULL and not options.auto_hide_groups:
  321. CanvasItemFX(group_box, True, False)
  322. else:
  323. QTimer.singleShot(0, canvas.scene.update)
  324. return group_dict
  325. def removeGroup(group_id):
  326. if canvas.debug:
  327. print("PatchCanvas::removeGroup(%i)" % group_id)
  328. for group in canvas.group_list:
  329. if group.group_id == group_id:
  330. item = group.widgets[0]
  331. group_name = group.group_name
  332. if group.split:
  333. s_item = group.widgets[1]
  334. if features.handle_group_pos:
  335. canvas.settings.setValue("CanvasPositions/%s_OUTPUT" % group_name, item.pos())
  336. canvas.settings.setValue("CanvasPositions/%s_INPUT" % group_name, s_item.pos())
  337. canvas.settings.setValue("CanvasPositions/%s_SPLIT" % group_name, SPLIT_YES)
  338. if options.eyecandy == EYECANDY_FULL:
  339. CanvasItemFX(s_item, False, True)
  340. else:
  341. s_item.removeIconFromScene()
  342. canvas.scene.removeItem(s_item)
  343. del s_item
  344. else:
  345. if features.handle_group_pos:
  346. canvas.settings.setValue("CanvasPositions/%s" % group_name, item.pos())
  347. canvas.settings.setValue("CanvasPositions/%s_SPLIT" % group_name, SPLIT_NO)
  348. if options.eyecandy == EYECANDY_FULL:
  349. CanvasItemFX(item, False, True)
  350. else:
  351. item.removeIconFromScene()
  352. canvas.scene.removeItem(item)
  353. del item
  354. canvas.group_list.remove(group)
  355. canvas.group_plugin_map.pop(group.plugin_id, None)
  356. QTimer.singleShot(0, canvas.scene.update)
  357. return
  358. qCritical("PatchCanvas::removeGroup(%i) - unable to find group to remove" % group_id)
  359. def renameGroup(group_id, new_group_name):
  360. if canvas.debug:
  361. print("PatchCanvas::renameGroup(%i, %s)" % (group_id, new_group_name.encode()))
  362. for group in canvas.group_list:
  363. if group.group_id == group_id:
  364. group.group_name = new_group_name
  365. group.widgets[0].setGroupName(new_group_name)
  366. if group.split and group.widgets[1]:
  367. group.widgets[1].setGroupName(new_group_name)
  368. QTimer.singleShot(0, canvas.scene.update)
  369. return
  370. qCritical("PatchCanvas::renameGroup(%i, %s) - unable to find group to rename" % (group_id, new_group_name.encode()))
  371. def splitGroup(group_id):
  372. if canvas.debug:
  373. print("PatchCanvas::splitGroup(%i)" % group_id)
  374. item = None
  375. group_name = ""
  376. group_icon = ICON_APPLICATION
  377. plugin_id = -1
  378. plugin_ui = False
  379. plugin_inline = False
  380. ports_data = []
  381. conns_data = []
  382. # Step 1 - Store all Item data
  383. for group in canvas.group_list:
  384. if group.group_id == group_id:
  385. if group.split:
  386. if canvas.debug:
  387. print("PatchCanvas::splitGroup(%i) - group is already split" % group_id)
  388. return
  389. item = group.widgets[0]
  390. group_name = group.group_name
  391. group_icon = group.icon
  392. plugin_id = group.plugin_id
  393. plugin_ui = group.plugin_ui
  394. plugin_inline = group.plugin_inline
  395. break
  396. if not item:
  397. qCritical("PatchCanvas::splitGroup(%i) - unable to find group to split" % group_id)
  398. return
  399. port_list_ids = list(item.getPortList())
  400. for port in canvas.port_list:
  401. if port.port_id in port_list_ids:
  402. port_dict = port_dict_t()
  403. port_dict.group_id = port.group_id
  404. port_dict.port_id = port.port_id
  405. port_dict.port_name = port.port_name
  406. port_dict.port_mode = port.port_mode
  407. port_dict.port_type = port.port_type
  408. port_dict.is_alternate = port.is_alternate
  409. port_dict.widget = None
  410. ports_data.append(port_dict)
  411. for connection in canvas.connection_list:
  412. if connection.port_out_id in port_list_ids or connection.port_in_id in port_list_ids:
  413. connection_dict = connection_dict_t()
  414. connection_dict.connection_id = connection.connection_id
  415. connection_dict.group_in_id = connection.group_in_id
  416. connection_dict.port_in_id = connection.port_in_id
  417. connection_dict.group_out_id = connection.group_out_id
  418. connection_dict.port_out_id = connection.port_out_id
  419. connection_dict.widget = None
  420. conns_data.append(connection_dict)
  421. # Step 2 - Remove Item and Children
  422. for conn in conns_data:
  423. disconnectPorts(conn.connection_id)
  424. for port_id in port_list_ids:
  425. removePort(group_id, port_id)
  426. removeGroup(group_id)
  427. # Step 3 - Re-create Item, now split
  428. group = addGroup(group_id, group_name, SPLIT_YES, group_icon)
  429. if plugin_id >= 0:
  430. setGroupAsPlugin(group_id, plugin_id, plugin_ui, plugin_inline)
  431. for port in ports_data:
  432. addPort(group_id, port.port_id, port.port_name, port.port_mode, port.port_type, port.is_alternate)
  433. for conn in conns_data:
  434. connectPorts(conn.connection_id, conn.group_out_id, conn.port_out_id, conn.group_in_id, conn.port_in_id)
  435. if group is not None:
  436. pos1 = group.widgets[0].pos()
  437. pos2 = group.widgets[1].pos()
  438. valueStr = "%i:%i:%i:%i" % (pos1.x(), pos1.y(), pos2.x(), pos2.y())
  439. CanvasCallback(ACTION_GROUP_POSITION, group_id, 0, valueStr)
  440. QTimer.singleShot(0, canvas.scene.update)
  441. def joinGroup(group_id):
  442. if canvas.debug:
  443. print("PatchCanvas::joinGroup(%i)" % group_id)
  444. item = None
  445. s_item = None
  446. group_name = ""
  447. group_icon = ICON_APPLICATION
  448. plugin_id = -1
  449. plugin_ui = False
  450. plugin_inline = False
  451. ports_data = []
  452. conns_data = []
  453. # Step 1 - Store all Item data
  454. for group in canvas.group_list:
  455. if group.group_id == group_id:
  456. if not group.split:
  457. if canvas.debug:
  458. print("PatchCanvas::joinGroup(%i) - group is not split" % group_id)
  459. return
  460. item = group.widgets[0]
  461. s_item = group.widgets[1]
  462. group_name = group.group_name
  463. group_icon = group.icon
  464. plugin_id = group.plugin_id
  465. plugin_ui = group.plugin_ui
  466. plugin_inline = group.plugin_inline
  467. break
  468. # FIXME
  469. if not (item and s_item):
  470. qCritical("PatchCanvas::joinGroup(%i) - unable to find groups to join" % group_id)
  471. return
  472. port_list_ids = list(item.getPortList())
  473. port_list_idss = s_item.getPortList()
  474. for port_id in port_list_idss:
  475. if port_id not in port_list_ids:
  476. port_list_ids.append(port_id)
  477. for port in canvas.port_list:
  478. if port.port_id in port_list_ids:
  479. port_dict = port_dict_t()
  480. port_dict.group_id = port.group_id
  481. port_dict.port_id = port.port_id
  482. port_dict.port_name = port.port_name
  483. port_dict.port_mode = port.port_mode
  484. port_dict.port_type = port.port_type
  485. port_dict.is_alternate = port.is_alternate
  486. port_dict.widget = None
  487. ports_data.append(port_dict)
  488. for connection in canvas.connection_list:
  489. if connection.port_out_id in port_list_ids or connection.port_in_id in port_list_ids:
  490. connection_dict = connection_dict_t()
  491. connection_dict.connection_id = connection.connection_id
  492. connection_dict.group_in_id = connection.group_in_id
  493. connection_dict.port_in_id = connection.port_in_id
  494. connection_dict.group_out_id = connection.group_out_id
  495. connection_dict.port_out_id = connection.port_out_id
  496. connection_dict.widget = None
  497. conns_data.append(connection_dict)
  498. # Step 2 - Remove Item and Children
  499. for conn in conns_data:
  500. disconnectPorts(conn.connection_id)
  501. for port_id in port_list_ids:
  502. removePort(group_id, port_id)
  503. removeGroup(group_id)
  504. # Step 3 - Re-create Item, now together
  505. group = addGroup(group_id, group_name, SPLIT_NO, group_icon)
  506. if plugin_id >= 0:
  507. setGroupAsPlugin(group_id, plugin_id, plugin_ui, plugin_inline)
  508. for port in ports_data:
  509. addPort(group_id, port.port_id, port.port_name, port.port_mode, port.port_type, port.is_alternate)
  510. for conn in conns_data:
  511. connectPorts(conn.connection_id, conn.group_out_id, conn.port_out_id, conn.group_in_id, conn.port_in_id)
  512. if group is not None:
  513. pos = group.widgets[0].pos()
  514. valueStr = "%i:%i:%i:%i" % (pos.x(), pos.y(), 0, 0)
  515. CanvasCallback(ACTION_GROUP_POSITION, group_id, 0, valueStr)
  516. QTimer.singleShot(0, canvas.scene.update)
  517. # ------------------------------------------------------------------------------------------------------------
  518. def getGroupPos(group_id, port_mode=PORT_MODE_OUTPUT):
  519. if canvas.debug:
  520. print("PatchCanvas::getGroupPos(%i, %s)" % (group_id, port_mode2str(port_mode)))
  521. for group in canvas.group_list:
  522. if group.group_id == group_id:
  523. return group.widgets[1 if (group.split and port_mode == PORT_MODE_INPUT) else 0].pos()
  524. qCritical("PatchCanvas::getGroupPos(%i, %s) - unable to find group" % (group_id, port_mode2str(port_mode)))
  525. return QPointF(0, 0)
  526. def saveGroupPositions():
  527. if canvas.debug:
  528. print("PatchCanvas::getGroupPositions()")
  529. ret = []
  530. for group in canvas.group_list:
  531. if group.split:
  532. pos1 = group.widgets[0].pos()
  533. pos2 = group.widgets[1].pos()
  534. else:
  535. pos1 = group.widgets[0].pos()
  536. pos2 = QPointF(0, 0)
  537. ret.append({
  538. "name" : group.group_name,
  539. "pos1x": pos1.x(),
  540. "pos1y": pos1.y(),
  541. "pos2x": pos2.x(),
  542. "pos2y": pos2.y(),
  543. "split": group.split,
  544. })
  545. return ret
  546. def restoreGroupPositions(dataList):
  547. if canvas.debug:
  548. print("PatchCanvas::restoreGroupPositions(...)")
  549. mapping = {}
  550. for group in canvas.group_list:
  551. mapping[group.group_name] = group
  552. for data in dataList:
  553. name = data['name']
  554. group = mapping.get(name, None)
  555. if group is None:
  556. continue
  557. group.widgets[0].blockSignals(True)
  558. group.widgets[0].setPos(data['pos1x'], data['pos1y'])
  559. group.widgets[0].blockSignals(False)
  560. if group.split and group.widgets[1]:
  561. group.widgets[1].blockSignals(True)
  562. group.widgets[1].setPos(data['pos2x'], data['pos2y'])
  563. group.widgets[1].blockSignals(False)
  564. def setGroupPos(group_id, group_pos_x, group_pos_y):
  565. setGroupPosFull(group_id, group_pos_x, group_pos_y, group_pos_x, group_pos_y)
  566. def setGroupPosFull(group_id, group_pos_x_o, group_pos_y_o, group_pos_x_i, group_pos_y_i):
  567. if canvas.debug:
  568. print("PatchCanvas::setGroupPos(%i, %i, %i, %i, %i)" % (
  569. group_id, group_pos_x_o, group_pos_y_o, group_pos_x_i, group_pos_y_i))
  570. for group in canvas.group_list:
  571. if group.group_id == group_id:
  572. group.widgets[0].blockSignals(True)
  573. group.widgets[0].setPos(group_pos_x_o, group_pos_y_o)
  574. group.widgets[0].checkItemPos()
  575. group.widgets[0].blockSignals(False)
  576. if group.split and group.widgets[1]:
  577. group.widgets[1].blockSignals(True)
  578. group.widgets[1].setPos(group_pos_x_i, group_pos_y_i)
  579. group.widgets[1].checkItemPos()
  580. group.widgets[1].blockSignals(False)
  581. QTimer.singleShot(0, canvas.scene.update)
  582. return
  583. qCritical("PatchCanvas::setGroupPos(%i, %i, %i, %i, %i) - unable to find group to reposition" % (
  584. group_id, group_pos_x_o, group_pos_y_o, group_pos_x_i, group_pos_y_i))
  585. # ------------------------------------------------------------------------------------------------------------
  586. def setGroupIcon(group_id, icon):
  587. if canvas.debug:
  588. print("PatchCanvas::setGroupIcon(%i, %s)" % (group_id, icon2str(icon)))
  589. for group in canvas.group_list:
  590. if group.group_id == group_id:
  591. group.icon = icon
  592. group.widgets[0].setIcon(icon)
  593. if group.split and group.widgets[1]:
  594. group.widgets[1].setIcon(icon)
  595. QTimer.singleShot(0, canvas.scene.update)
  596. return
  597. qCritical("PatchCanvas::setGroupIcon(%i, %s) - unable to find group to change icon" % (group_id, icon2str(icon)))
  598. def setGroupAsPlugin(group_id, plugin_id, hasUI, hasInlineDisplay):
  599. if canvas.debug:
  600. print("PatchCanvas::setGroupAsPlugin(%i, %i, %s, %s)" % (
  601. group_id, plugin_id, bool2str(hasUI), bool2str(hasInlineDisplay)))
  602. for group in canvas.group_list:
  603. if group.group_id == group_id:
  604. group.plugin_id = plugin_id
  605. group.plugin_ui = hasUI
  606. group.plugin_inline = hasInlineDisplay
  607. group.widgets[0].setAsPlugin(plugin_id, hasUI, hasInlineDisplay)
  608. if group.split and group.widgets[1]:
  609. group.widgets[1].setAsPlugin(plugin_id, hasUI, hasInlineDisplay)
  610. canvas.group_plugin_map[plugin_id] = group
  611. return
  612. qCritical("PatchCanvas::setGroupAsPlugin(%i, %i, %s, %s) - unable to find group to set as plugin" % (
  613. group_id, plugin_id, bool2str(hasUI), bool2str(hasInlineDisplay)))
  614. # ------------------------------------------------------------------------------------------------------------
  615. def focusGroupUsingPluginId(plugin_id):
  616. if canvas.debug:
  617. print("PatchCanvas::focusGroupUsingPluginId(%i)" % (plugin_id,))
  618. if plugin_id < 0 or plugin_id >= MAX_PLUGIN_ID_ALLOWED:
  619. return False
  620. for group in canvas.group_list:
  621. if group.plugin_id == plugin_id:
  622. item = group.widgets[0]
  623. canvas.scene.clearSelection()
  624. canvas.scene.getView().centerOn(item)
  625. item.setSelected(True)
  626. return True
  627. def focusGroupUsingGroupName(group_name):
  628. if canvas.debug:
  629. print("PatchCanvas::focusGroupUsingGroupName(%s)" % (group_name,))
  630. for group in canvas.group_list:
  631. if group.group_name == group_name:
  632. item = group.widgets[0]
  633. canvas.scene.clearSelection()
  634. canvas.scene.getView().centerOn(item)
  635. item.setSelected(True)
  636. return True
  637. # ------------------------------------------------------------------------------------------------------------
  638. def addPort(group_id, port_id, port_name, port_mode, port_type, is_alternate=False):
  639. if canvas.debug:
  640. print("PatchCanvas::addPort(%i, %i, %s, %s, %s, %s)" % (
  641. group_id, port_id, port_name.encode(),
  642. port_mode2str(port_mode), port_type2str(port_type), bool2str(is_alternate)))
  643. for port in canvas.port_list:
  644. if port.group_id == group_id and port.port_id == port_id:
  645. qWarning("PatchCanvas::addPort(%i, %i, %s, %s, %s) - port already exists" % (
  646. group_id, port_id, port_name.encode(), port_mode2str(port_mode), port_type2str(port_type)))
  647. return
  648. box_widget = None
  649. port_widget = None
  650. for group in canvas.group_list:
  651. if group.group_id == group_id:
  652. if group.split and group.widgets[0].getSplittedMode() != port_mode and group.widgets[1]:
  653. n = 1
  654. else:
  655. n = 0
  656. box_widget = group.widgets[n]
  657. port_widget = box_widget.addPortFromGroup(port_id, port_mode, port_type, port_name, is_alternate)
  658. break
  659. if not (box_widget and port_widget):
  660. qCritical("PatchCanvas::addPort(%i, %i, %s, %s, %s) - Unable to find parent group" % (
  661. group_id, port_id, port_name.encode(), port_mode2str(port_mode), port_type2str(port_type)))
  662. return
  663. port_dict = port_dict_t()
  664. port_dict.group_id = group_id
  665. port_dict.port_id = port_id
  666. port_dict.port_name = port_name
  667. port_dict.port_mode = port_mode
  668. port_dict.port_type = port_type
  669. port_dict.is_alternate = is_alternate
  670. port_dict.widget = port_widget
  671. canvas.port_list.append(port_dict)
  672. box_widget.updatePositions()
  673. if options.eyecandy == EYECANDY_FULL:
  674. CanvasItemFX(port_widget, True, False)
  675. return
  676. QTimer.singleShot(0, canvas.scene.update)
  677. def removePort(group_id, port_id):
  678. if canvas.debug:
  679. print("PatchCanvas::removePort(%i, %i)" % (group_id, port_id))
  680. for port in canvas.port_list:
  681. if port.group_id == group_id and port.port_id == port_id:
  682. item = port.widget
  683. item.parentItem().removePortFromGroup(port_id)
  684. canvas.scene.removeItem(item)
  685. canvas.port_list.remove(port)
  686. del item
  687. QTimer.singleShot(0, canvas.scene.update)
  688. return
  689. qCritical("PatchCanvas::removePort(%i, %i) - Unable to find port to remove" % (group_id, port_id))
  690. def renamePort(group_id, port_id, new_port_name):
  691. if canvas.debug:
  692. print("PatchCanvas::renamePort(%i, %i, %s)" % (group_id, port_id, new_port_name.encode()))
  693. for port in canvas.port_list:
  694. if port.group_id == group_id and port.port_id == port_id:
  695. port.port_name = new_port_name
  696. port.widget.setPortName(new_port_name)
  697. port.widget.parentItem().updatePositions()
  698. QTimer.singleShot(0, canvas.scene.update)
  699. return
  700. qCritical("PatchCanvas::renamePort(%i, %i, %s) - Unable to find port to rename" % (
  701. group_id, port_id, new_port_name.encode()))
  702. def connectPorts(connection_id, group_out_id, port_out_id, group_in_id, port_in_id):
  703. if canvas.last_connection_id >= connection_id:
  704. print("PatchCanvas::connectPorts(%i, %i, %i, %i, %i) - invalid connection id received" % (
  705. connection_id, group_out_id, port_out_id, group_in_id, port_in_id))
  706. return
  707. canvas.last_connection_id = connection_id
  708. if canvas.debug:
  709. print("PatchCanvas::connectPorts(%i, %i, %i, %i, %i)" % (
  710. connection_id, group_out_id, port_out_id, group_in_id, port_in_id))
  711. port_out = None
  712. port_in = None
  713. port_out_parent = None
  714. port_in_parent = None
  715. for port in canvas.port_list:
  716. if port.group_id == group_out_id and port.port_id == port_out_id:
  717. port_out = port.widget
  718. port_out_parent = port_out.parentItem()
  719. elif port.group_id == group_in_id and port.port_id == port_in_id:
  720. port_in = port.widget
  721. port_in_parent = port_in.parentItem()
  722. # FIXME
  723. if not (port_out and port_in):
  724. qCritical("PatchCanvas::connectPorts(%i, %i, %i, %i, %i) - unable to find ports to connect" % (
  725. connection_id, group_out_id, port_out_id, group_in_id, port_in_id))
  726. return
  727. connection_dict = connection_dict_t()
  728. connection_dict.connection_id = connection_id
  729. connection_dict.group_in_id = group_in_id
  730. connection_dict.port_in_id = port_in_id
  731. connection_dict.group_out_id = group_out_id
  732. connection_dict.port_out_id = port_out_id
  733. if options.use_bezier_lines:
  734. connection_dict.widget = CanvasBezierLine(port_out, port_in, None)
  735. else:
  736. connection_dict.widget = CanvasLine(port_out, port_in, None)
  737. canvas.scene.addItem(connection_dict.widget)
  738. port_out_parent.addLineFromGroup(connection_dict.widget, connection_id)
  739. port_in_parent.addLineFromGroup(connection_dict.widget, connection_id)
  740. canvas.last_z_value += 1
  741. port_out_parent.setZValue(canvas.last_z_value)
  742. port_in_parent.setZValue(canvas.last_z_value)
  743. canvas.last_z_value += 1
  744. connection_dict.widget.setZValue(canvas.last_z_value)
  745. canvas.connection_list.append(connection_dict)
  746. if options.eyecandy == EYECANDY_FULL:
  747. item = connection_dict.widget
  748. CanvasItemFX(item, True, False)
  749. return
  750. QTimer.singleShot(0, canvas.scene.update)
  751. def disconnectPorts(connection_id):
  752. if canvas.debug:
  753. print("PatchCanvas::disconnectPorts(%i)" % connection_id)
  754. line = None
  755. item1 = None
  756. item2 = None
  757. group1id = port1id = 0
  758. group2id = port2id = 0
  759. for connection in canvas.connection_list:
  760. if connection.connection_id == connection_id:
  761. group1id = connection.group_out_id
  762. group2id = connection.group_in_id
  763. port1id = connection.port_out_id
  764. port2id = connection.port_in_id
  765. line = connection.widget
  766. canvas.connection_list.remove(connection)
  767. break
  768. if not line:
  769. qCritical("PatchCanvas::disconnectPorts(%i) - unable to find connection ports" % connection_id)
  770. return
  771. for port in canvas.port_list:
  772. if port.group_id == group1id and port.port_id == port1id:
  773. item1 = port.widget
  774. break
  775. if not item1:
  776. qCritical("PatchCanvas::disconnectPorts(%i) - unable to find output port" % connection_id)
  777. return
  778. for port in canvas.port_list:
  779. if port.group_id == group2id and port.port_id == port2id:
  780. item2 = port.widget
  781. break
  782. if not item2:
  783. qCritical("PatchCanvas::disconnectPorts(%i) - unable to find input port" % connection_id)
  784. return
  785. item1.parentItem().removeLineFromGroup(connection_id)
  786. item2.parentItem().removeLineFromGroup(connection_id)
  787. if options.eyecandy == EYECANDY_FULL:
  788. CanvasItemFX(line, False, True)
  789. return
  790. canvas.scene.removeItem(line)
  791. del line
  792. QTimer.singleShot(0, canvas.scene.update)
  793. # ------------------------------------------------------------------------------------------------------------
  794. def arrange():
  795. if canvas.debug:
  796. print("PatchCanvas::arrange()")
  797. # ------------------------------------------------------------------------------------------------------------
  798. def updateZValues():
  799. if canvas.debug:
  800. print("PatchCanvas::updateZValues()")
  801. for group in canvas.group_list:
  802. group.widgets[0].resetLinesZValue()
  803. if group.split and group.widgets[1]:
  804. group.widgets[1].resetLinesZValue()
  805. # ------------------------------------------------------------------------------------------------------------
  806. def redrawPluginGroup(plugin_id):
  807. group = canvas.group_plugin_map.get(plugin_id, None)
  808. if group is None:
  809. #qCritical("PatchCanvas::redrawPluginGroup(%i) - unable to find group" % plugin_id)
  810. return
  811. group.widgets[0].redrawInlineDisplay()
  812. if group.split and group.widgets[1]:
  813. group.widgets[1].redrawInlineDisplay()
  814. def handlePluginRemoved(plugin_id):
  815. if canvas.debug:
  816. print("PatchCanvas::handlePluginRemoved(%i)" % plugin_id)
  817. canvas.scene.clearSelection()
  818. group = canvas.group_plugin_map.pop(plugin_id, None)
  819. if group is not None:
  820. group.plugin_id = -1
  821. group.plugin_ui = False
  822. group.plugin_inline = False
  823. group.widgets[0].removeAsPlugin()
  824. if group.split and group.widgets[1]:
  825. group.widgets[1].removeAsPlugin()
  826. for group in canvas.group_list:
  827. if group.plugin_id < plugin_id or group.plugin_id > MAX_PLUGIN_ID_ALLOWED:
  828. continue
  829. group.plugin_id -= 1
  830. group.widgets[0].m_plugin_id -= 1
  831. if group.split and group.widgets[1]:
  832. group.widgets[1].m_plugin_id -= 1
  833. canvas.group_plugin_map[plugin_id] = group
  834. def handleAllPluginsRemoved():
  835. if canvas.debug:
  836. print("PatchCanvas::handleAllPluginsRemoved()")
  837. canvas.group_plugin_map = {}
  838. for group in canvas.group_list:
  839. if group.plugin_id < 0:
  840. continue
  841. if group.plugin_id > MAX_PLUGIN_ID_ALLOWED:
  842. continue
  843. group.plugin_id = -1
  844. group.plugin_ui = False
  845. group.plugin_inline = False
  846. group.widgets[0].removeAsPlugin()
  847. if group.split and group.widgets[1]:
  848. group.widgets[1].removeAsPlugin()
  849. # ------------------------------------------------------------------------------------------------------------