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 38KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119
  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 utils 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. group_pos = None
  378. plugin_id = -1
  379. plugin_ui = False
  380. plugin_inline = False
  381. ports_data = []
  382. conns_data = []
  383. # Step 1 - Store all Item data
  384. for group in canvas.group_list:
  385. if group.group_id == group_id:
  386. if group.split:
  387. if canvas.debug:
  388. print("PatchCanvas::splitGroup(%i) - group is already split" % group_id)
  389. return
  390. item = group.widgets[0]
  391. group_name = group.group_name
  392. group_icon = group.icon
  393. group_pos = item.pos()
  394. plugin_id = group.plugin_id
  395. plugin_ui = group.plugin_ui
  396. plugin_inline = group.plugin_inline
  397. break
  398. if not item:
  399. qCritical("PatchCanvas::splitGroup(%i) - unable to find group to split" % group_id)
  400. return
  401. port_list_ids = list(item.getPortList())
  402. for port in canvas.port_list:
  403. if port.group_id == group_id and port.port_id in port_list_ids:
  404. port_dict = port_dict_t()
  405. port_dict.group_id = port.group_id
  406. port_dict.port_id = port.port_id
  407. port_dict.port_name = port.port_name
  408. port_dict.port_mode = port.port_mode
  409. port_dict.port_type = port.port_type
  410. port_dict.is_alternate = port.is_alternate
  411. port_dict.widget = None
  412. ports_data.append(port_dict)
  413. for connection in canvas.connection_list:
  414. if (connection.group_in_id == group_id and connection.port_in_id in port_list_ids) or \
  415. (connection.group_out_id == group_id and connection.port_out_id in port_list_ids):
  416. connection_dict = connection_dict_t()
  417. connection_dict.connection_id = connection.connection_id
  418. connection_dict.group_in_id = connection.group_in_id
  419. connection_dict.port_in_id = connection.port_in_id
  420. connection_dict.group_out_id = connection.group_out_id
  421. connection_dict.port_out_id = connection.port_out_id
  422. connection_dict.widget = None
  423. conns_data.append(connection_dict)
  424. # Step 2 - Remove Item and Children
  425. for conn in conns_data:
  426. disconnectPorts(conn.connection_id)
  427. for port_id in port_list_ids:
  428. removePort(group_id, port_id)
  429. removeGroup(group_id)
  430. # Step 3 - Re-create Item, now split
  431. group = addGroup(group_id, group_name, SPLIT_YES, group_icon)
  432. if plugin_id >= 0:
  433. setGroupAsPlugin(group_id, plugin_id, plugin_ui, plugin_inline)
  434. for port in ports_data:
  435. addPort(group_id, port.port_id, port.port_name, port.port_mode, port.port_type, port.is_alternate)
  436. for conn in conns_data:
  437. connectPorts(conn.connection_id, conn.group_out_id, conn.port_out_id, conn.group_in_id, conn.port_in_id, True)
  438. if group is not None:
  439. pos1 = group.widgets[0].pos()
  440. pos2 = group.widgets[1].pos()
  441. group2_pos = QPointF(group_pos.x() + group.widgets[1].boundingRect().width() * 3/2, group_pos.y())
  442. group.widgets[0].blockSignals(True)
  443. group.widgets[0].setPos(group_pos)
  444. group.widgets[0].blockSignals(False)
  445. group.widgets[1].blockSignals(True)
  446. group.widgets[1].setPos(group2_pos)
  447. group.widgets[1].checkItemPos()
  448. group.widgets[1].blockSignals(False)
  449. valueStr = "%i:%i:%i:%i" % (group_pos.x(), group_pos.y(), group2_pos.x(), group2_pos.y())
  450. CanvasCallback(ACTION_GROUP_POSITION, group_id, 0, valueStr)
  451. QTimer.singleShot(0, canvas.scene.update)
  452. def joinGroup(group_id):
  453. if canvas.debug:
  454. print("PatchCanvas::joinGroup(%i)" % group_id)
  455. item = None
  456. s_item = None
  457. group_name = ""
  458. group_icon = ICON_APPLICATION
  459. group_pos = None
  460. plugin_id = -1
  461. plugin_ui = False
  462. plugin_inline = False
  463. ports_data = []
  464. conns_data = []
  465. # Step 1 - Store all Item data
  466. for group in canvas.group_list:
  467. if group.group_id == group_id:
  468. if not group.split:
  469. if canvas.debug:
  470. print("PatchCanvas::joinGroup(%i) - group is not split" % group_id)
  471. return
  472. item = group.widgets[0]
  473. s_item = group.widgets[1]
  474. group_name = group.group_name
  475. group_icon = group.icon
  476. group_pos = item.pos()
  477. plugin_id = group.plugin_id
  478. plugin_ui = group.plugin_ui
  479. plugin_inline = group.plugin_inline
  480. break
  481. # FIXME
  482. if not (item and s_item):
  483. qCritical("PatchCanvas::joinGroup(%i) - unable to find groups to join" % group_id)
  484. return
  485. port_list_ids = list(item.getPortList())
  486. port_list_idss = s_item.getPortList()
  487. for port_id in port_list_idss:
  488. if port_id not in port_list_ids:
  489. port_list_ids.append(port_id)
  490. for port in canvas.port_list:
  491. if port.group_id == group_id and port.port_id in port_list_ids:
  492. port_dict = port_dict_t()
  493. port_dict.group_id = port.group_id
  494. port_dict.port_id = port.port_id
  495. port_dict.port_name = port.port_name
  496. port_dict.port_mode = port.port_mode
  497. port_dict.port_type = port.port_type
  498. port_dict.is_alternate = port.is_alternate
  499. port_dict.widget = None
  500. ports_data.append(port_dict)
  501. for connection in canvas.connection_list:
  502. if (connection.group_in_id == group_id and connection.port_in_id in port_list_ids) or \
  503. (connection.group_out_id == group_id and connection.port_out_id in port_list_ids):
  504. connection_dict = connection_dict_t()
  505. connection_dict.connection_id = connection.connection_id
  506. connection_dict.group_in_id = connection.group_in_id
  507. connection_dict.port_in_id = connection.port_in_id
  508. connection_dict.group_out_id = connection.group_out_id
  509. connection_dict.port_out_id = connection.port_out_id
  510. connection_dict.widget = None
  511. conns_data.append(connection_dict)
  512. # Step 2 - Remove Item and Children
  513. for conn in conns_data:
  514. disconnectPorts(conn.connection_id)
  515. for port_id in port_list_ids:
  516. removePort(group_id, port_id)
  517. removeGroup(group_id)
  518. # Step 3 - Re-create Item, now together
  519. group = addGroup(group_id, group_name, SPLIT_NO, group_icon)
  520. if plugin_id >= 0:
  521. setGroupAsPlugin(group_id, plugin_id, plugin_ui, plugin_inline)
  522. for port in ports_data:
  523. addPort(group_id, port.port_id, port.port_name, port.port_mode, port.port_type, port.is_alternate)
  524. for conn in conns_data:
  525. connectPorts(conn.connection_id, conn.group_out_id, conn.port_out_id, conn.group_in_id, conn.port_in_id, True)
  526. if group is not None:
  527. group.widgets[0].blockSignals(True)
  528. group.widgets[0].setPos(group_pos)
  529. group.widgets[0].checkItemPos()
  530. group.widgets[0].blockSignals(False)
  531. valueStr = "%i:%i:%i:%i" % (group_pos.x(), group_pos.y(), 0, 0)
  532. CanvasCallback(ACTION_GROUP_POSITION, group_id, 0, valueStr)
  533. QTimer.singleShot(0, canvas.scene.update)
  534. # ------------------------------------------------------------------------------------------------------------
  535. def getGroupPos(group_id, port_mode=PORT_MODE_OUTPUT):
  536. if canvas.debug:
  537. print("PatchCanvas::getGroupPos(%i, %s)" % (group_id, port_mode2str(port_mode)))
  538. for group in canvas.group_list:
  539. if group.group_id == group_id:
  540. return group.widgets[1 if (group.split and port_mode == PORT_MODE_INPUT) else 0].pos()
  541. qCritical("PatchCanvas::getGroupPos(%i, %s) - unable to find group" % (group_id, port_mode2str(port_mode)))
  542. return QPointF(0, 0)
  543. def saveGroupPositions():
  544. if canvas.debug:
  545. print("PatchCanvas::getGroupPositions()")
  546. ret = []
  547. for group in canvas.group_list:
  548. if group.split:
  549. pos1 = group.widgets[0].pos()
  550. pos2 = group.widgets[1].pos()
  551. else:
  552. pos1 = group.widgets[0].pos()
  553. pos2 = QPointF(0, 0)
  554. ret.append({
  555. "name" : group.group_name,
  556. "pos1x": pos1.x(),
  557. "pos1y": pos1.y(),
  558. "pos2x": pos2.x(),
  559. "pos2y": pos2.y(),
  560. "split": group.split,
  561. })
  562. return ret
  563. def restoreGroupPositions(dataList):
  564. if canvas.debug:
  565. print("PatchCanvas::restoreGroupPositions(...)")
  566. mapping = {}
  567. for group in canvas.group_list:
  568. mapping[group.group_name] = group
  569. for data in dataList:
  570. name = data['name']
  571. group = mapping.get(name, None)
  572. if group is None:
  573. continue
  574. group.widgets[0].blockSignals(True)
  575. group.widgets[0].setPos(data['pos1x'], data['pos1y'])
  576. group.widgets[0].blockSignals(False)
  577. if group.split and group.widgets[1]:
  578. group.widgets[1].blockSignals(True)
  579. group.widgets[1].setPos(data['pos2x'], data['pos2y'])
  580. group.widgets[1].blockSignals(False)
  581. def setGroupPos(group_id, group_pos_x, group_pos_y):
  582. setGroupPosFull(group_id, group_pos_x, group_pos_y, group_pos_x, group_pos_y)
  583. def setGroupPosFull(group_id, group_pos_x_o, group_pos_y_o, group_pos_x_i, group_pos_y_i):
  584. if canvas.debug:
  585. print("PatchCanvas::setGroupPos(%i, %i, %i, %i, %i)" % (
  586. group_id, group_pos_x_o, group_pos_y_o, group_pos_x_i, group_pos_y_i))
  587. for group in canvas.group_list:
  588. if group.group_id == group_id:
  589. group.widgets[0].blockSignals(True)
  590. group.widgets[0].setPos(group_pos_x_o, group_pos_y_o)
  591. group.widgets[0].checkItemPos()
  592. group.widgets[0].blockSignals(False)
  593. if group.split and group.widgets[1]:
  594. group.widgets[1].blockSignals(True)
  595. group.widgets[1].setPos(group_pos_x_i, group_pos_y_i)
  596. group.widgets[1].checkItemPos()
  597. group.widgets[1].blockSignals(False)
  598. QTimer.singleShot(0, canvas.scene.update)
  599. return
  600. qCritical("PatchCanvas::setGroupPos(%i, %i, %i, %i, %i) - unable to find group to reposition" % (
  601. group_id, group_pos_x_o, group_pos_y_o, group_pos_x_i, group_pos_y_i))
  602. # ------------------------------------------------------------------------------------------------------------
  603. def setGroupIcon(group_id, icon):
  604. if canvas.debug:
  605. print("PatchCanvas::setGroupIcon(%i, %s)" % (group_id, icon2str(icon)))
  606. for group in canvas.group_list:
  607. if group.group_id == group_id:
  608. group.icon = icon
  609. group.widgets[0].setIcon(icon)
  610. if group.split and group.widgets[1]:
  611. group.widgets[1].setIcon(icon)
  612. QTimer.singleShot(0, canvas.scene.update)
  613. return
  614. qCritical("PatchCanvas::setGroupIcon(%i, %s) - unable to find group to change icon" % (group_id, icon2str(icon)))
  615. def setGroupAsPlugin(group_id, plugin_id, hasUI, hasInlineDisplay):
  616. if canvas.debug:
  617. print("PatchCanvas::setGroupAsPlugin(%i, %i, %s, %s)" % (
  618. group_id, plugin_id, bool2str(hasUI), bool2str(hasInlineDisplay)))
  619. for group in canvas.group_list:
  620. if group.group_id == group_id:
  621. group.plugin_id = plugin_id
  622. group.plugin_ui = hasUI
  623. group.plugin_inline = hasInlineDisplay
  624. group.widgets[0].setAsPlugin(plugin_id, hasUI, hasInlineDisplay)
  625. if group.split and group.widgets[1]:
  626. group.widgets[1].setAsPlugin(plugin_id, hasUI, hasInlineDisplay)
  627. canvas.group_plugin_map[plugin_id] = group
  628. return
  629. qCritical("PatchCanvas::setGroupAsPlugin(%i, %i, %s, %s) - unable to find group to set as plugin" % (
  630. group_id, plugin_id, bool2str(hasUI), bool2str(hasInlineDisplay)))
  631. # ------------------------------------------------------------------------------------------------------------
  632. def focusGroupUsingPluginId(plugin_id):
  633. if canvas.debug:
  634. print("PatchCanvas::focusGroupUsingPluginId(%i)" % (plugin_id,))
  635. if plugin_id < 0 or plugin_id >= MAX_PLUGIN_ID_ALLOWED:
  636. return False
  637. for group in canvas.group_list:
  638. if group.plugin_id == plugin_id:
  639. item = group.widgets[0]
  640. canvas.scene.clearSelection()
  641. canvas.scene.getView().centerOn(item)
  642. item.setSelected(True)
  643. return True
  644. def focusGroupUsingGroupName(group_name):
  645. if canvas.debug:
  646. print("PatchCanvas::focusGroupUsingGroupName(%s)" % (group_name,))
  647. for group in canvas.group_list:
  648. if group.group_name == group_name:
  649. item = group.widgets[0]
  650. canvas.scene.clearSelection()
  651. canvas.scene.getView().centerOn(item)
  652. item.setSelected(True)
  653. return True
  654. # ------------------------------------------------------------------------------------------------------------
  655. def addPort(group_id, port_id, port_name, port_mode, port_type, is_alternate=False):
  656. if canvas.debug:
  657. print("PatchCanvas::addPort(%i, %i, %s, %s, %s, %s)" % (
  658. group_id, port_id, port_name.encode(),
  659. port_mode2str(port_mode), port_type2str(port_type), bool2str(is_alternate)))
  660. for port in canvas.port_list:
  661. if port.group_id == group_id and port.port_id == port_id:
  662. qWarning("PatchCanvas::addPort(%i, %i, %s, %s, %s) - port already exists" % (
  663. group_id, port_id, port_name.encode(), port_mode2str(port_mode), port_type2str(port_type)))
  664. return
  665. box_widget = None
  666. port_widget = None
  667. for group in canvas.group_list:
  668. if group.group_id == group_id:
  669. if group.split and group.widgets[0].getSplitMode() != port_mode and group.widgets[1]:
  670. n = 1
  671. else:
  672. n = 0
  673. box_widget = group.widgets[n]
  674. port_widget = box_widget.addPortFromGroup(port_id, port_mode, port_type, port_name, is_alternate)
  675. break
  676. if not (box_widget and port_widget):
  677. qCritical("PatchCanvas::addPort(%i, %i, %s, %s, %s) - Unable to find parent group" % (
  678. group_id, port_id, port_name.encode(), port_mode2str(port_mode), port_type2str(port_type)))
  679. return
  680. port_dict = port_dict_t()
  681. port_dict.group_id = group_id
  682. port_dict.port_id = port_id
  683. port_dict.port_name = port_name
  684. port_dict.port_mode = port_mode
  685. port_dict.port_type = port_type
  686. port_dict.is_alternate = is_alternate
  687. port_dict.widget = port_widget
  688. canvas.port_list.append(port_dict)
  689. box_widget.updatePositions()
  690. if options.eyecandy == EYECANDY_FULL:
  691. CanvasItemFX(port_widget, True, False)
  692. return
  693. QTimer.singleShot(0, canvas.scene.update)
  694. def removePort(group_id, port_id):
  695. if canvas.debug:
  696. print("PatchCanvas::removePort(%i, %i)" % (group_id, port_id))
  697. for port in canvas.port_list:
  698. if port.group_id == group_id and port.port_id == port_id:
  699. item = port.widget
  700. try:
  701. pitem = item.parentItem()
  702. canvas.scene.removeItem(item)
  703. except RuntimeError:
  704. pass
  705. else:
  706. pitem.removePortFromGroup(port_id)
  707. canvas.port_list.remove(port)
  708. del item
  709. QTimer.singleShot(0, canvas.scene.update)
  710. return
  711. qCritical("PatchCanvas::removePort(%i, %i) - Unable to find port to remove" % (group_id, port_id))
  712. def renamePort(group_id, port_id, new_port_name):
  713. if canvas.debug:
  714. print("PatchCanvas::renamePort(%i, %i, %s)" % (group_id, port_id, new_port_name.encode()))
  715. for port in canvas.port_list:
  716. if port.group_id == group_id and port.port_id == port_id:
  717. port.port_name = new_port_name
  718. port.widget.setPortName(new_port_name)
  719. port.widget.parentItem().updatePositions()
  720. QTimer.singleShot(0, canvas.scene.update)
  721. return
  722. qCritical("PatchCanvas::renamePort(%i, %i, %s) - Unable to find port to rename" % (
  723. group_id, port_id, new_port_name.encode()))
  724. def connectPorts(connection_id, group_out_id, port_out_id, group_in_id, port_in_id, fromSplitOrJoin = False):
  725. if canvas.last_connection_id >= connection_id and not fromSplitOrJoin:
  726. print("PatchCanvas::connectPorts(%i, %i, %i, %i, %i) - invalid connection id received (last: %i)" % (
  727. connection_id, group_out_id, port_out_id, group_in_id, port_in_id, canvas.last_connection_id))
  728. return
  729. canvas.last_connection_id = connection_id
  730. if canvas.debug:
  731. print("PatchCanvas::connectPorts(%i, %i, %i, %i, %i)" % (
  732. connection_id, group_out_id, port_out_id, group_in_id, port_in_id))
  733. port_out = None
  734. port_in = None
  735. port_out_parent = None
  736. port_in_parent = None
  737. for port in canvas.port_list:
  738. if port.group_id == group_out_id and port.port_id == port_out_id:
  739. port_out = port.widget
  740. port_out_parent = port_out.parentItem()
  741. elif port.group_id == group_in_id and port.port_id == port_in_id:
  742. port_in = port.widget
  743. port_in_parent = port_in.parentItem()
  744. # FIXME
  745. if not (port_out and port_in):
  746. qCritical("PatchCanvas::connectPorts(%i, %i, %i, %i, %i) - unable to find ports to connect" % (
  747. connection_id, group_out_id, port_out_id, group_in_id, port_in_id))
  748. return
  749. connection_dict = connection_dict_t()
  750. connection_dict.connection_id = connection_id
  751. connection_dict.group_in_id = group_in_id
  752. connection_dict.port_in_id = port_in_id
  753. connection_dict.group_out_id = group_out_id
  754. connection_dict.port_out_id = port_out_id
  755. if options.use_bezier_lines:
  756. connection_dict.widget = CanvasBezierLine(port_out, port_in, None)
  757. else:
  758. connection_dict.widget = CanvasLine(port_out, port_in, None)
  759. canvas.scene.addItem(connection_dict.widget)
  760. port_out_parent.addLineFromGroup(connection_dict.widget, connection_id)
  761. port_in_parent.addLineFromGroup(connection_dict.widget, connection_id)
  762. canvas.last_z_value += 1
  763. port_out_parent.setZValue(canvas.last_z_value)
  764. port_in_parent.setZValue(canvas.last_z_value)
  765. canvas.last_z_value += 1
  766. connection_dict.widget.setZValue(canvas.last_z_value)
  767. canvas.connection_list.append(connection_dict)
  768. if options.eyecandy == EYECANDY_FULL:
  769. item = connection_dict.widget
  770. CanvasItemFX(item, True, False)
  771. return
  772. QTimer.singleShot(0, canvas.scene.update)
  773. def disconnectPorts(connection_id):
  774. if canvas.debug:
  775. print("PatchCanvas::disconnectPorts(%i)" % connection_id)
  776. line = None
  777. item1 = None
  778. item2 = None
  779. group1id = port1id = 0
  780. group2id = port2id = 0
  781. for connection in canvas.connection_list:
  782. if connection.connection_id == connection_id:
  783. group1id = connection.group_out_id
  784. group2id = connection.group_in_id
  785. port1id = connection.port_out_id
  786. port2id = connection.port_in_id
  787. line = connection.widget
  788. canvas.connection_list.remove(connection)
  789. break
  790. if not line:
  791. qCritical("PatchCanvas::disconnectPorts(%i) - unable to find connection ports" % connection_id)
  792. return
  793. for port in canvas.port_list:
  794. if port.group_id == group1id and port.port_id == port1id:
  795. item1 = port.widget
  796. break
  797. if not item1:
  798. qCritical("PatchCanvas::disconnectPorts(%i) - unable to find output port" % connection_id)
  799. return
  800. for port in canvas.port_list:
  801. if port.group_id == group2id and port.port_id == port2id:
  802. item2 = port.widget
  803. break
  804. if not item2:
  805. qCritical("PatchCanvas::disconnectPorts(%i) - unable to find input port" % connection_id)
  806. return
  807. item1p = item1.parentItem()
  808. item2p = item2.parentItem()
  809. if item1p:
  810. item1p.removeLineFromGroup(connection_id)
  811. if item2p:
  812. item2p.removeLineFromGroup(connection_id)
  813. if options.eyecandy == EYECANDY_FULL:
  814. CanvasItemFX(line, False, True)
  815. return
  816. canvas.scene.removeItem(line)
  817. del line
  818. QTimer.singleShot(0, canvas.scene.update)
  819. # ------------------------------------------------------------------------------------------------------------
  820. def arrange():
  821. if canvas.debug:
  822. print("PatchCanvas::arrange()")
  823. # ------------------------------------------------------------------------------------------------------------
  824. def updateZValues():
  825. if canvas.debug:
  826. print("PatchCanvas::updateZValues()")
  827. for group in canvas.group_list:
  828. group.widgets[0].resetLinesZValue()
  829. if group.split and group.widgets[1]:
  830. group.widgets[1].resetLinesZValue()
  831. # ------------------------------------------------------------------------------------------------------------
  832. def redrawPluginGroup(plugin_id):
  833. group = canvas.group_plugin_map.get(plugin_id, None)
  834. if group is None:
  835. #qCritical("PatchCanvas::redrawPluginGroup(%i) - unable to find group" % plugin_id)
  836. return
  837. group.widgets[0].redrawInlineDisplay()
  838. if group.split and group.widgets[1]:
  839. group.widgets[1].redrawInlineDisplay()
  840. def handlePluginRemoved(plugin_id):
  841. if canvas.debug:
  842. print("PatchCanvas::handlePluginRemoved(%i)" % plugin_id)
  843. canvas.scene.clearSelection()
  844. group = canvas.group_plugin_map.pop(plugin_id, None)
  845. if group is not None:
  846. group.plugin_id = -1
  847. group.plugin_ui = False
  848. group.plugin_inline = False
  849. group.widgets[0].removeAsPlugin()
  850. if group.split and group.widgets[1]:
  851. group.widgets[1].removeAsPlugin()
  852. for group in canvas.group_list:
  853. if group.plugin_id < plugin_id or group.plugin_id > MAX_PLUGIN_ID_ALLOWED:
  854. continue
  855. group.plugin_id -= 1
  856. group.widgets[0].m_plugin_id -= 1
  857. if group.split and group.widgets[1]:
  858. group.widgets[1].m_plugin_id -= 1
  859. canvas.group_plugin_map[plugin_id] = group
  860. def handleAllPluginsRemoved():
  861. if canvas.debug:
  862. print("PatchCanvas::handleAllPluginsRemoved()")
  863. canvas.group_plugin_map = {}
  864. for group in canvas.group_list:
  865. if group.plugin_id < 0:
  866. continue
  867. if group.plugin_id > MAX_PLUGIN_ID_ALLOWED:
  868. continue
  869. group.plugin_id = -1
  870. group.plugin_ui = False
  871. group.plugin_inline = False
  872. group.widgets[0].removeAsPlugin()
  873. if group.split and group.widgets[1]:
  874. group.widgets[1].removeAsPlugin()
  875. # ------------------------------------------------------------------------------------------------------------