1#!/usr/local/bin/python3.8
2# -*- coding: utf-8 -*-
3
4# PatchBay Canvas engine using QGraphicsView/Scene
5# Copyright (C) 2010-2019 Filipe Coelho <falktx@falktx.com>
6#
7# This program is free software; you can redistribute it and/or
8# modify it under the terms of the GNU General Public License as
9# published by the Free Software Foundation; either version 2 of
10# the License, or any later version.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15# GNU General Public License for more details.
16#
17# For a full copy of the GNU General Public License see the doc/GPL.txt file.
18
19# ------------------------------------------------------------------------------------------------------------
20# Imports (Global)
21
22from PyQt5.QtCore import pyqtSlot, qCritical, qFatal, qWarning, QObject
23from PyQt5.QtCore import QPointF, QRectF, QTimer
24from PyQt5.QtWidgets import QGraphicsObject
25
26# ------------------------------------------------------------------------------------------------------------
27# Imports (Custom)
28
29from . import (
30    canvas,
31    features,
32    options,
33    group_dict_t,
34    port_dict_t,
35    connection_dict_t,
36    bool2str,
37    icon2str,
38    split2str,
39    port_mode2str,
40    port_type2str,
41    CanvasIconType,
42    CanvasRubberbandType,
43    ACTION_PORTS_DISCONNECT,
44    EYECANDY_FULL,
45    ICON_APPLICATION,
46    ICON_HARDWARE,
47    ICON_LADISH_ROOM,
48    PORT_MODE_INPUT,
49    PORT_MODE_OUTPUT,
50    SPLIT_YES,
51    SPLIT_NO,
52    SPLIT_UNDEF,
53    MAX_PLUGIN_ID_ALLOWED,
54)
55
56from .canvasbox import CanvasBox
57from .canvasbezierline import CanvasBezierLine
58from .canvasline import CanvasLine
59from .theme import Theme, getDefaultTheme, getThemeName
60from .utils import CanvasCallback, CanvasGetNewGroupPos, CanvasItemFX, CanvasRemoveItemFX
61
62# FIXME
63from . import *
64from .scene import PatchScene
65
66from carla_shared import QSafeSettings
67
68# ------------------------------------------------------------------------------------------------------------
69
70class CanvasObject(QObject):
71    def __init__(self, parent=None):
72        QObject.__init__(self, parent)
73
74    @pyqtSlot()
75    def AnimationFinishedShow(self):
76        animation = self.sender()
77        if animation:
78            animation.forceStop()
79            canvas.animation_list.remove(animation)
80
81    @pyqtSlot()
82    def AnimationFinishedHide(self):
83        animation = self.sender()
84        if animation:
85            animation.forceStop()
86            canvas.animation_list.remove(animation)
87            item = animation.item()
88            if item:
89                if isinstance(item, QGraphicsObject):
90                    item.blockSignals(True)
91                    item.hide()
92                    item.blockSignals(False)
93                else:
94                    item.hide()
95
96    @pyqtSlot()
97    def AnimationFinishedDestroy(self):
98        animation = self.sender()
99        if animation:
100            animation.forceStop()
101            canvas.animation_list.remove(animation)
102            item = animation.item()
103            if item:
104                CanvasRemoveItemFX(item)
105
106    @pyqtSlot()
107    def PortContextMenuConnect(self):
108        try:
109            sources, targets = self.sender().data()
110        except:
111            return
112
113        for port_type in (PORT_TYPE_AUDIO_JACK, PORT_TYPE_MIDI_JACK, PORT_TYPE_MIDI_ALSA, PORT_TYPE_PARAMETER):
114            source_ports = sources[port_type]
115            target_ports = targets[port_type]
116
117            source_ports_len = len(source_ports)
118            target_ports_len = len(target_ports)
119
120            if source_ports_len == 0 or target_ports_len == 0:
121                continue
122
123            for i in range(min(source_ports_len, target_ports_len)):
124                data = "%i:%i:%i:%i" % (source_ports[i][0],
125                                        source_ports[i][1],
126                                        target_ports[i][0],
127                                        target_ports[i][1])
128                CanvasCallback(ACTION_PORTS_CONNECT, 0, 0, data)
129
130            if source_ports_len == 1 and target_ports_len > 1:
131                for i in range(1, target_ports_len):
132                    data = "%i:%i:%i:%i" % (source_ports[0][0],
133                                            source_ports[0][1],
134                                            target_ports[i][0],
135                                            target_ports[i][1])
136                    CanvasCallback(ACTION_PORTS_CONNECT, 0, 0, data)
137
138    @pyqtSlot()
139    def PortContextMenuDisconnect(self):
140        try:
141            connectionId = int(self.sender().data())
142        except:
143            return
144
145        CanvasCallback(ACTION_PORTS_DISCONNECT, connectionId, 0, "")
146
147    @pyqtSlot(int, bool, int, int)
148    def boxPositionChanged(self, groupId, split, x, y):
149        x2 = y2 = 0
150
151        if split:
152            for group in canvas.group_list:
153                if group.group_id == groupId:
154                    if group.split:
155                        pos = group.widgets[1].pos()
156                        x2  = pos.x()
157                        y2  = pos.y()
158                    break
159
160        valueStr = "%i:%i:%i:%i" % (x, y, x2, y2)
161        CanvasCallback(ACTION_GROUP_POSITION, groupId, 0, valueStr)
162
163    @pyqtSlot(int, bool, int, int)
164    def sboxPositionChanged(self, groupId, split, x2, y2):
165        x = y = 0
166
167        for group in canvas.group_list:
168            if group.group_id == groupId:
169                pos = group.widgets[0].pos()
170                x = pos.x()
171                y = pos.y()
172                break
173
174        valueStr = "%i:%i:%i:%i" % (x, y, x2, y2)
175        CanvasCallback(ACTION_GROUP_POSITION, groupId, 0, valueStr)
176
177# ------------------------------------------------------------------------------------------------------------
178
179def getStoredCanvasPosition(key, fallback_pos):
180    try:
181        return canvas.settings.value("CanvasPositions/" + key, fallback_pos, type=QPointF)
182    except:
183        return fallback_pos
184
185def getStoredCanvasSplit(group_name, fallback_split_mode):
186    try:
187        return canvas.settings.value("CanvasPositions/%s_SPLIT" % group_name, fallback_split_mode, type=int)
188    except:
189        return fallback_split_mode
190
191# ------------------------------------------------------------------------------------------------------------
192
193def init(appName, scene, callback, debug=False):
194    if debug:
195        print("PatchCanvas::init(\"%s\", %s, %s, %s)" % (appName, scene, callback, bool2str(debug)))
196
197    if canvas.initiated:
198        qCritical("PatchCanvas::init() - already initiated")
199        return
200
201    if not callback:
202        qFatal("PatchCanvas::init() - fatal error: callback not set")
203        return
204
205    canvas.callback = callback
206    canvas.debug = debug
207    canvas.scene = scene
208
209    canvas.last_z_value = 0
210    canvas.last_connection_id = 0
211    canvas.initial_pos = QPointF(0, 0)
212    canvas.size_rect = QRectF()
213
214    if not canvas.qobject:
215        canvas.qobject = CanvasObject()
216    if not canvas.settings:
217        canvas.settings = QSafeSettings("falkTX", appName)
218
219    if canvas.theme:
220        del canvas.theme
221        canvas.theme = None
222
223    for i in range(Theme.THEME_MAX):
224        this_theme_name = getThemeName(i)
225        if this_theme_name == options.theme_name:
226            canvas.theme = Theme(i)
227            break
228
229    if not canvas.theme:
230        canvas.theme = Theme(getDefaultTheme())
231
232    canvas.scene.updateTheme()
233
234    canvas.initiated = True
235
236def clear():
237    if canvas.debug:
238        print("PatchCanvas::clear()")
239
240    group_pos = {}
241    group_list_ids = []
242    port_list_ids = []
243    connection_list_ids = []
244
245    for group in canvas.group_list:
246        group_pos[group.group_name] = (
247            group.split,
248            group.widgets[0].pos(),
249            group.widgets[1].pos() if group.split else None,
250        )
251        group_list_ids.append(group.group_id)
252
253    for port in canvas.port_list:
254        port_list_ids.append((port.group_id, port.port_id))
255
256    for connection in canvas.connection_list:
257        connection_list_ids.append(connection.connection_id)
258
259    for idx in connection_list_ids:
260        disconnectPorts(idx)
261
262    for group_id, port_id in port_list_ids:
263        removePort(group_id, port_id)
264
265    for idx in group_list_ids:
266        removeGroup(idx)
267
268    canvas.last_z_value = 0
269    canvas.last_connection_id = 0
270
271    canvas.group_list = []
272    canvas.port_list = []
273    canvas.connection_list = []
274    canvas.group_plugin_map = {}
275    canvas.old_group_pos = group_pos
276
277    canvas.scene.clearSelection()
278
279    animatedItems = []
280    for animation in canvas.animation_list:
281        animatedItems.append(animation.item())
282
283    for item in canvas.scene.items():
284        if item.type() in (CanvasIconType, CanvasRubberbandType) or item in animatedItems:
285            continue
286        canvas.scene.removeItem(item)
287        del item
288
289    canvas.initiated = False
290
291    QTimer.singleShot(0, canvas.scene.update)
292
293# ------------------------------------------------------------------------------------------------------------
294
295def setInitialPos(x, y):
296    if canvas.debug:
297        print("PatchCanvas::setInitialPos(%i, %i)" % (x, y))
298
299    canvas.initial_pos.setX(x)
300    canvas.initial_pos.setY(y)
301
302def setCanvasSize(x, y, width, height):
303    if canvas.debug:
304        print("PatchCanvas::setCanvasSize(%i, %i, %i, %i)" % (x, y, width, height))
305
306    canvas.size_rect.setX(x)
307    canvas.size_rect.setY(y)
308    canvas.size_rect.setWidth(width)
309    canvas.size_rect.setHeight(height)
310    canvas.scene.updateLimits()
311    canvas.scene.fixScaleFactor()
312
313def addGroup(group_id, group_name, split=SPLIT_UNDEF, icon=ICON_APPLICATION):
314    if canvas.debug:
315        print("PatchCanvas::addGroup(%i, %s, %s, %s)" % (
316              group_id, group_name.encode(), split2str(split), icon2str(icon)))
317
318    for group in canvas.group_list:
319        if group.group_id == group_id:
320            qWarning("PatchCanvas::addGroup(%i, %s, %s, %s) - group already exists" % (
321                     group_id, group_name.encode(), split2str(split), icon2str(icon)))
322            return None
323
324    old_matching_group = canvas.old_group_pos.pop(group_name, None)
325
326    if split == SPLIT_UNDEF:
327        isHardware = bool(icon == ICON_HARDWARE)
328
329        if features.handle_group_pos:
330            split = getStoredCanvasSplit(group_name, SPLIT_YES if isHardware else split)
331        elif isHardware:
332            split = SPLIT_YES
333        elif old_matching_group is not None and old_matching_group[0]:
334            split = SPLIT_YES
335
336    group_box = CanvasBox(group_id, group_name, icon)
337    group_box.positionChanged.connect(canvas.qobject.boxPositionChanged)
338    group_box.blockSignals(True)
339
340    group_dict = group_dict_t()
341    group_dict.group_id = group_id
342    group_dict.group_name = group_name
343    group_dict.split = bool(split == SPLIT_YES)
344    group_dict.icon = icon
345    group_dict.plugin_id = -1
346    group_dict.plugin_ui = False
347    group_dict.plugin_inline = False
348    group_dict.widgets = [group_box, None]
349
350    if split == SPLIT_YES:
351        group_box.setSplit(True, PORT_MODE_OUTPUT)
352
353        if features.handle_group_pos:
354            group_box.setPos(getStoredCanvasPosition(group_name + "_OUTPUT", CanvasGetNewGroupPos(False)))
355        elif old_matching_group is not None:
356            group_box.setPos(old_matching_group[1])
357        else:
358            group_box.setPos(CanvasGetNewGroupPos(False))
359
360        group_sbox = CanvasBox(group_id, group_name, icon)
361        group_sbox.positionChanged.connect(canvas.qobject.sboxPositionChanged)
362        group_sbox.blockSignals(True)
363        group_sbox.setSplit(True, PORT_MODE_INPUT)
364
365        group_dict.widgets[1] = group_sbox
366
367        if features.handle_group_pos:
368            group_sbox.setPos(getStoredCanvasPosition(group_name + "_INPUT", CanvasGetNewGroupPos(True)))
369        elif old_matching_group is not None and old_matching_group[0]:
370            group_sbox.setPos(old_matching_group[2])
371        else:
372            group_sbox.setPos(group_box.x() + group_box.boundingRect().width() + 300, group_box.y())
373
374        canvas.last_z_value += 1
375        group_sbox.setZValue(canvas.last_z_value)
376
377        if options.eyecandy == EYECANDY_FULL and not options.auto_hide_groups:
378            CanvasItemFX(group_sbox, True, False)
379
380        group_sbox.checkItemPos()
381        group_sbox.blockSignals(False)
382
383    else:
384        group_box.setSplit(False)
385
386        if features.handle_group_pos:
387            group_box.setPos(getStoredCanvasPosition(group_name, CanvasGetNewGroupPos(False)))
388        elif old_matching_group is not None:
389            group_box.setPos(old_matching_group[1])
390        else:
391            # Special ladish fake-split groups
392            horizontal = bool(icon == ICON_HARDWARE or icon == ICON_LADISH_ROOM)
393            group_box.setPos(CanvasGetNewGroupPos(horizontal))
394
395    canvas.last_z_value += 1
396    group_box.setZValue(canvas.last_z_value)
397
398    group_box.checkItemPos()
399    group_box.blockSignals(False)
400
401    canvas.group_list.append(group_dict)
402
403    if options.eyecandy == EYECANDY_FULL and not options.auto_hide_groups:
404        CanvasItemFX(group_box, True, False)
405    else:
406        QTimer.singleShot(0, canvas.scene.update)
407
408    return group_dict
409
410def removeGroup(group_id):
411    if canvas.debug:
412        print("PatchCanvas::removeGroup(%i)" % group_id)
413
414    for group in canvas.group_list:
415        if group.group_id == group_id:
416            item = group.widgets[0]
417            group_name = group.group_name
418
419            if group.split:
420                s_item = group.widgets[1]
421
422                if features.handle_group_pos:
423                    canvas.settings.setValue("CanvasPositions/%s_OUTPUT" % group_name, item.pos())
424                    canvas.settings.setValue("CanvasPositions/%s_INPUT" % group_name, s_item.pos())
425                    canvas.settings.setValue("CanvasPositions/%s_SPLIT" % group_name, SPLIT_YES)
426
427                if options.eyecandy == EYECANDY_FULL:
428                    CanvasItemFX(s_item, False, True)
429                else:
430                    s_item.removeIconFromScene()
431                    canvas.scene.removeItem(s_item)
432                    del s_item
433
434            else:
435                if features.handle_group_pos:
436                    canvas.settings.setValue("CanvasPositions/%s" % group_name, item.pos())
437                    canvas.settings.setValue("CanvasPositions/%s_SPLIT" % group_name, SPLIT_NO)
438
439            if options.eyecandy == EYECANDY_FULL:
440                CanvasItemFX(item, False, True)
441            else:
442                item.removeIconFromScene()
443                canvas.scene.removeItem(item)
444                del item
445
446            canvas.group_list.remove(group)
447            canvas.group_plugin_map.pop(group.plugin_id, None)
448
449            QTimer.singleShot(0, canvas.scene.update)
450            return
451
452    qCritical("PatchCanvas::removeGroup(%i) - unable to find group to remove" % group_id)
453
454def renameGroup(group_id, new_group_name):
455    if canvas.debug:
456        print("PatchCanvas::renameGroup(%i, %s)" % (group_id, new_group_name.encode()))
457
458    for group in canvas.group_list:
459        if group.group_id == group_id:
460            group.group_name = new_group_name
461            group.widgets[0].setGroupName(new_group_name)
462
463            if group.split and group.widgets[1]:
464                group.widgets[1].setGroupName(new_group_name)
465
466            QTimer.singleShot(0, canvas.scene.update)
467            return
468
469    qCritical("PatchCanvas::renameGroup(%i, %s) - unable to find group to rename" % (group_id, new_group_name.encode()))
470
471def splitGroup(group_id):
472    if canvas.debug:
473        print("PatchCanvas::splitGroup(%i)" % group_id)
474
475    item = None
476    group_name = ""
477    group_icon = ICON_APPLICATION
478    group_pos = None
479    plugin_id = -1
480    plugin_ui = False
481    plugin_inline = False
482    ports_data = []
483    conns_data = []
484
485    # Step 1 - Store all Item data
486    for group in canvas.group_list:
487        if group.group_id == group_id:
488            if group.split:
489                if canvas.debug:
490                    print("PatchCanvas::splitGroup(%i) - group is already split" % group_id)
491                return
492
493            item = group.widgets[0]
494            group_name = group.group_name
495            group_icon = group.icon
496            group_pos = item.pos()
497            plugin_id = group.plugin_id
498            plugin_ui = group.plugin_ui
499            plugin_inline = group.plugin_inline
500            break
501
502    if not item:
503        qCritical("PatchCanvas::splitGroup(%i) - unable to find group to split" % group_id)
504        return
505
506    port_list_ids = list(item.getPortList())
507
508    for port in canvas.port_list:
509        if port.group_id == group_id and port.port_id in port_list_ids:
510            port_dict = port_dict_t()
511            port_dict.group_id = port.group_id
512            port_dict.port_id = port.port_id
513            port_dict.port_name = port.port_name
514            port_dict.port_mode = port.port_mode
515            port_dict.port_type = port.port_type
516            port_dict.is_alternate = port.is_alternate
517            port_dict.widget = None
518            ports_data.append(port_dict)
519
520    for connection in canvas.connection_list:
521        if (connection.group_in_id == group_id and connection.port_in_id in port_list_ids) or \
522           (connection.group_out_id == group_id and connection.port_out_id in port_list_ids):
523            connection_dict = connection_dict_t()
524            connection_dict.connection_id = connection.connection_id
525            connection_dict.group_in_id = connection.group_in_id
526            connection_dict.port_in_id = connection.port_in_id
527            connection_dict.group_out_id = connection.group_out_id
528            connection_dict.port_out_id = connection.port_out_id
529            connection_dict.widget = None
530            conns_data.append(connection_dict)
531
532    # Step 2 - Remove Item and Children
533    for conn in conns_data:
534        disconnectPorts(conn.connection_id)
535
536    for port_id in port_list_ids:
537        removePort(group_id, port_id)
538
539    removeGroup(group_id)
540
541    # Step 3 - Re-create Item, now split
542    group = addGroup(group_id, group_name, SPLIT_YES, group_icon)
543
544    if plugin_id >= 0:
545        setGroupAsPlugin(group_id, plugin_id, plugin_ui, plugin_inline)
546
547    for port in ports_data:
548        addPort(group_id, port.port_id, port.port_name, port.port_mode, port.port_type, port.is_alternate)
549
550    for conn in conns_data:
551        connectPorts(conn.connection_id, conn.group_out_id, conn.port_out_id, conn.group_in_id, conn.port_in_id, True)
552
553    if group is not None:
554        pos1 = group.widgets[0].pos()
555        pos2 = group.widgets[1].pos()
556        group2_pos = QPointF(group_pos.x() + group.widgets[1].boundingRect().width() * 3/2, group_pos.y())
557        group.widgets[0].blockSignals(True)
558        group.widgets[0].setPos(group_pos)
559        group.widgets[0].blockSignals(False)
560        group.widgets[1].blockSignals(True)
561        group.widgets[1].setPos(group2_pos)
562        group.widgets[1].checkItemPos()
563        group.widgets[1].blockSignals(False)
564        valueStr = "%i:%i:%i:%i" % (group_pos.x(), group_pos.y(), group2_pos.x(), group2_pos.y())
565        CanvasCallback(ACTION_GROUP_POSITION, group_id, 0, valueStr)
566
567    QTimer.singleShot(0, canvas.scene.update)
568
569def joinGroup(group_id):
570    if canvas.debug:
571        print("PatchCanvas::joinGroup(%i)" % group_id)
572
573    item = None
574    s_item = None
575    group_name = ""
576    group_icon = ICON_APPLICATION
577    group_pos = None
578    plugin_id = -1
579    plugin_ui = False
580    plugin_inline = False
581    ports_data = []
582    conns_data = []
583
584    # Step 1 - Store all Item data
585    for group in canvas.group_list:
586        if group.group_id == group_id:
587            if not group.split:
588                if canvas.debug:
589                    print("PatchCanvas::joinGroup(%i) - group is not split" % group_id)
590                return
591
592            item = group.widgets[0]
593            s_item = group.widgets[1]
594            group_name = group.group_name
595            group_icon = group.icon
596            group_pos = item.pos()
597            plugin_id = group.plugin_id
598            plugin_ui = group.plugin_ui
599            plugin_inline = group.plugin_inline
600            break
601
602    # FIXME
603    if not (item and s_item):
604        qCritical("PatchCanvas::joinGroup(%i) - unable to find groups to join" % group_id)
605        return
606
607    port_list_ids = list(item.getPortList())
608    port_list_idss = s_item.getPortList()
609
610    for port_id in port_list_idss:
611        if port_id not in port_list_ids:
612            port_list_ids.append(port_id)
613
614    for port in canvas.port_list:
615        if port.group_id == group_id and port.port_id in port_list_ids:
616            port_dict = port_dict_t()
617            port_dict.group_id = port.group_id
618            port_dict.port_id = port.port_id
619            port_dict.port_name = port.port_name
620            port_dict.port_mode = port.port_mode
621            port_dict.port_type = port.port_type
622            port_dict.is_alternate = port.is_alternate
623            port_dict.widget = None
624            ports_data.append(port_dict)
625
626    for connection in canvas.connection_list:
627        if (connection.group_in_id == group_id and connection.port_in_id in port_list_ids) or \
628           (connection.group_out_id == group_id and connection.port_out_id in port_list_ids):
629            connection_dict = connection_dict_t()
630            connection_dict.connection_id = connection.connection_id
631            connection_dict.group_in_id = connection.group_in_id
632            connection_dict.port_in_id = connection.port_in_id
633            connection_dict.group_out_id = connection.group_out_id
634            connection_dict.port_out_id = connection.port_out_id
635            connection_dict.widget = None
636            conns_data.append(connection_dict)
637
638    # Step 2 - Remove Item and Children
639    for conn in conns_data:
640        disconnectPorts(conn.connection_id)
641
642    for port_id in port_list_ids:
643        removePort(group_id, port_id)
644
645    removeGroup(group_id)
646
647    # Step 3 - Re-create Item, now together
648    group = addGroup(group_id, group_name, SPLIT_NO, group_icon)
649
650    if plugin_id >= 0:
651        setGroupAsPlugin(group_id, plugin_id, plugin_ui, plugin_inline)
652
653    for port in ports_data:
654        addPort(group_id, port.port_id, port.port_name, port.port_mode, port.port_type, port.is_alternate)
655
656    for conn in conns_data:
657        connectPorts(conn.connection_id, conn.group_out_id, conn.port_out_id, conn.group_in_id, conn.port_in_id, True)
658
659    if group is not None:
660        group.widgets[0].blockSignals(True)
661        group.widgets[0].setPos(group_pos)
662        group.widgets[0].checkItemPos()
663        group.widgets[0].blockSignals(False)
664        valueStr = "%i:%i:%i:%i" % (group_pos.x(), group_pos.y(), 0, 0)
665        CanvasCallback(ACTION_GROUP_POSITION, group_id, 0, valueStr)
666
667    QTimer.singleShot(0, canvas.scene.update)
668
669# ------------------------------------------------------------------------------------------------------------
670
671def getGroupPos(group_id, port_mode=PORT_MODE_OUTPUT):
672    if canvas.debug:
673        print("PatchCanvas::getGroupPos(%i, %s)" % (group_id, port_mode2str(port_mode)))
674
675    for group in canvas.group_list:
676        if group.group_id == group_id:
677            return group.widgets[1 if (group.split and port_mode == PORT_MODE_INPUT) else 0].pos()
678
679    qCritical("PatchCanvas::getGroupPos(%i, %s) - unable to find group" % (group_id, port_mode2str(port_mode)))
680    return QPointF(0, 0)
681
682def saveGroupPositions():
683    if canvas.debug:
684        print("PatchCanvas::getGroupPositions()")
685
686    ret = []
687
688    for group in canvas.group_list:
689        if group.split:
690            pos1 = group.widgets[0].pos()
691            pos2 = group.widgets[1].pos()
692        else:
693            pos1 = group.widgets[0].pos()
694            pos2 = QPointF(0, 0)
695
696        ret.append({
697            "name" : group.group_name,
698            "pos1x": pos1.x(),
699            "pos1y": pos1.y(),
700            "pos2x": pos2.x(),
701            "pos2y": pos2.y(),
702            "split": group.split,
703        })
704
705    return ret
706
707def restoreGroupPositions(dataList):
708    if canvas.debug:
709        print("PatchCanvas::restoreGroupPositions(...)")
710
711    mapping = {}
712
713    for group in canvas.group_list:
714        mapping[group.group_name] = group
715
716    for data in dataList:
717        name = data['name']
718        group = mapping.get(name, None)
719
720        if group is None:
721            continue
722
723        group.widgets[0].blockSignals(True)
724        group.widgets[0].setPos(data['pos1x'], data['pos1y'])
725        group.widgets[0].blockSignals(False)
726
727        if group.split and group.widgets[1]:
728            group.widgets[1].blockSignals(True)
729            group.widgets[1].setPos(data['pos2x'], data['pos2y'])
730            group.widgets[1].blockSignals(False)
731
732def setGroupPos(group_id, group_pos_x, group_pos_y):
733    setGroupPosFull(group_id, group_pos_x, group_pos_y, group_pos_x, group_pos_y)
734
735def setGroupPosFull(group_id, group_pos_x_o, group_pos_y_o, group_pos_x_i, group_pos_y_i):
736    if canvas.debug:
737        print("PatchCanvas::setGroupPos(%i, %i, %i, %i, %i)" % (
738              group_id, group_pos_x_o, group_pos_y_o, group_pos_x_i, group_pos_y_i))
739
740    for group in canvas.group_list:
741        if group.group_id == group_id:
742            group.widgets[0].blockSignals(True)
743            group.widgets[0].setPos(group_pos_x_o, group_pos_y_o)
744            group.widgets[0].checkItemPos()
745            group.widgets[0].blockSignals(False)
746
747            if group.split and group.widgets[1]:
748                group.widgets[1].blockSignals(True)
749                group.widgets[1].setPos(group_pos_x_i, group_pos_y_i)
750                group.widgets[1].checkItemPos()
751                group.widgets[1].blockSignals(False)
752
753            QTimer.singleShot(0, canvas.scene.update)
754            return
755
756    qCritical("PatchCanvas::setGroupPos(%i, %i, %i, %i, %i) - unable to find group to reposition" % (
757              group_id, group_pos_x_o, group_pos_y_o, group_pos_x_i, group_pos_y_i))
758
759# ------------------------------------------------------------------------------------------------------------
760
761def setGroupIcon(group_id, icon):
762    if canvas.debug:
763        print("PatchCanvas::setGroupIcon(%i, %s)" % (group_id, icon2str(icon)))
764
765    for group in canvas.group_list:
766        if group.group_id == group_id:
767            group.icon = icon
768            group.widgets[0].setIcon(icon)
769
770            if group.split and group.widgets[1]:
771                group.widgets[1].setIcon(icon)
772
773            QTimer.singleShot(0, canvas.scene.update)
774            return
775
776    qCritical("PatchCanvas::setGroupIcon(%i, %s) - unable to find group to change icon" % (group_id, icon2str(icon)))
777
778def setGroupAsPlugin(group_id, plugin_id, hasUI, hasInlineDisplay):
779    if canvas.debug:
780        print("PatchCanvas::setGroupAsPlugin(%i, %i, %s, %s)" % (
781              group_id, plugin_id, bool2str(hasUI), bool2str(hasInlineDisplay)))
782
783    for group in canvas.group_list:
784        if group.group_id == group_id:
785            group.plugin_id = plugin_id
786            group.plugin_ui = hasUI
787            group.plugin_inline = hasInlineDisplay
788            group.widgets[0].setAsPlugin(plugin_id, hasUI, hasInlineDisplay)
789
790            if group.split and group.widgets[1]:
791                group.widgets[1].setAsPlugin(plugin_id, hasUI, hasInlineDisplay)
792
793            canvas.group_plugin_map[plugin_id] = group
794            return
795
796    qCritical("PatchCanvas::setGroupAsPlugin(%i, %i, %s, %s) - unable to find group to set as plugin" % (
797              group_id, plugin_id, bool2str(hasUI), bool2str(hasInlineDisplay)))
798
799# ------------------------------------------------------------------------------------------------------------
800
801def focusGroupUsingPluginId(plugin_id):
802    if canvas.debug:
803        print("PatchCanvas::focusGroupUsingPluginId(%i)" % (plugin_id,))
804
805    if plugin_id < 0 or plugin_id >= MAX_PLUGIN_ID_ALLOWED:
806        return False
807
808    for group in canvas.group_list:
809        if group.plugin_id == plugin_id:
810            item = group.widgets[0]
811            canvas.scene.clearSelection()
812            canvas.scene.getView().centerOn(item)
813            item.setSelected(True)
814            return True
815
816def focusGroupUsingGroupName(group_name):
817    if canvas.debug:
818        print("PatchCanvas::focusGroupUsingGroupName(%s)" % (group_name,))
819
820    for group in canvas.group_list:
821        if group.group_name == group_name:
822            item = group.widgets[0]
823            canvas.scene.clearSelection()
824            canvas.scene.getView().centerOn(item)
825            item.setSelected(True)
826            return True
827
828# ------------------------------------------------------------------------------------------------------------
829
830def addPort(group_id, port_id, port_name, port_mode, port_type, is_alternate=False):
831    if canvas.debug:
832        print("PatchCanvas::addPort(%i, %i, %s, %s, %s, %s)" % (
833              group_id, port_id, port_name.encode(),
834              port_mode2str(port_mode), port_type2str(port_type), bool2str(is_alternate)))
835
836    for port in canvas.port_list:
837        if port.group_id == group_id and port.port_id == port_id:
838            qWarning("PatchCanvas::addPort(%i, %i, %s, %s, %s) - port already exists" % (
839                     group_id, port_id, port_name.encode(), port_mode2str(port_mode), port_type2str(port_type)))
840            return
841
842    box_widget = None
843    port_widget = None
844
845    for group in canvas.group_list:
846        if group.group_id == group_id:
847            if group.split and group.widgets[0].getSplitMode() != port_mode and group.widgets[1]:
848                n = 1
849            else:
850                n = 0
851            box_widget = group.widgets[n]
852            port_widget = box_widget.addPortFromGroup(port_id, port_mode, port_type, port_name, is_alternate)
853            break
854
855    if not (box_widget and port_widget):
856        qCritical("PatchCanvas::addPort(%i, %i, %s, %s, %s) - Unable to find parent group" % (
857                  group_id, port_id, port_name.encode(), port_mode2str(port_mode), port_type2str(port_type)))
858        return
859
860    port_dict = port_dict_t()
861    port_dict.group_id = group_id
862    port_dict.port_id = port_id
863    port_dict.port_name = port_name
864    port_dict.port_mode = port_mode
865    port_dict.port_type = port_type
866    port_dict.is_alternate = is_alternate
867    port_dict.widget = port_widget
868    canvas.port_list.append(port_dict)
869
870    box_widget.updatePositions()
871
872    if options.eyecandy == EYECANDY_FULL:
873        CanvasItemFX(port_widget, True, False)
874        return
875
876    QTimer.singleShot(0, canvas.scene.update)
877
878def removePort(group_id, port_id):
879    if canvas.debug:
880        print("PatchCanvas::removePort(%i, %i)" % (group_id, port_id))
881
882    for port in canvas.port_list:
883        if port.group_id == group_id and port.port_id == port_id:
884            item = port.widget
885            try:
886                pitem = item.parentItem()
887                canvas.scene.removeItem(item)
888            except RuntimeError:
889                pass
890            else:
891                pitem.removePortFromGroup(port_id)
892            canvas.port_list.remove(port)
893            del item
894
895            QTimer.singleShot(0, canvas.scene.update)
896            return
897
898    qCritical("PatchCanvas::removePort(%i, %i) - Unable to find port to remove" % (group_id, port_id))
899
900def renamePort(group_id, port_id, new_port_name):
901    if canvas.debug:
902        print("PatchCanvas::renamePort(%i, %i, %s)" % (group_id, port_id, new_port_name.encode()))
903
904    for port in canvas.port_list:
905        if port.group_id == group_id and port.port_id == port_id:
906            port.port_name = new_port_name
907            port.widget.setPortName(new_port_name)
908            port.widget.parentItem().updatePositions()
909
910            QTimer.singleShot(0, canvas.scene.update)
911            return
912
913    qCritical("PatchCanvas::renamePort(%i, %i, %s) - Unable to find port to rename" % (
914              group_id, port_id, new_port_name.encode()))
915
916def connectPorts(connection_id, group_out_id, port_out_id, group_in_id, port_in_id, fromSplitOrJoin = False):
917    if canvas.last_connection_id >= connection_id and not fromSplitOrJoin:
918        print("PatchCanvas::connectPorts(%i, %i, %i, %i, %i) - invalid connection id received (last: %i)" % (
919              connection_id, group_out_id, port_out_id, group_in_id, port_in_id, canvas.last_connection_id))
920        return
921
922    canvas.last_connection_id = connection_id
923
924    if canvas.debug:
925        print("PatchCanvas::connectPorts(%i, %i, %i, %i, %i)" % (
926              connection_id, group_out_id, port_out_id, group_in_id, port_in_id))
927
928    port_out = None
929    port_in = None
930    port_out_parent = None
931    port_in_parent = None
932
933    for port in canvas.port_list:
934        if port.group_id == group_out_id and port.port_id == port_out_id:
935            port_out = port.widget
936            port_out_parent = port_out.parentItem()
937        elif port.group_id == group_in_id and port.port_id == port_in_id:
938            port_in = port.widget
939            port_in_parent = port_in.parentItem()
940
941    # FIXME
942    if not (port_out and port_in):
943        qCritical("PatchCanvas::connectPorts(%i, %i, %i, %i, %i) - unable to find ports to connect" % (
944                  connection_id, group_out_id, port_out_id, group_in_id, port_in_id))
945        return
946
947    connection_dict = connection_dict_t()
948    connection_dict.connection_id = connection_id
949    connection_dict.group_in_id = group_in_id
950    connection_dict.port_in_id = port_in_id
951    connection_dict.group_out_id = group_out_id
952    connection_dict.port_out_id = port_out_id
953
954    if options.use_bezier_lines:
955        connection_dict.widget = CanvasBezierLine(port_out, port_in, None)
956    else:
957        connection_dict.widget = CanvasLine(port_out, port_in, None)
958
959    canvas.scene.addItem(connection_dict.widget)
960
961    port_out_parent.addLineFromGroup(connection_dict.widget, connection_id)
962    port_in_parent.addLineFromGroup(connection_dict.widget, connection_id)
963
964    canvas.last_z_value += 1
965    port_out_parent.setZValue(canvas.last_z_value)
966    port_in_parent.setZValue(canvas.last_z_value)
967
968    canvas.last_z_value += 1
969    connection_dict.widget.setZValue(canvas.last_z_value)
970
971    canvas.connection_list.append(connection_dict)
972
973    if options.eyecandy == EYECANDY_FULL:
974        item = connection_dict.widget
975        CanvasItemFX(item, True, False)
976        return
977
978    QTimer.singleShot(0, canvas.scene.update)
979
980def disconnectPorts(connection_id):
981    if canvas.debug:
982        print("PatchCanvas::disconnectPorts(%i)" % connection_id)
983
984    line = None
985    item1 = None
986    item2 = None
987    group1id = port1id = 0
988    group2id = port2id = 0
989
990    for connection in canvas.connection_list:
991        if connection.connection_id == connection_id:
992            group1id = connection.group_out_id
993            group2id = connection.group_in_id
994            port1id = connection.port_out_id
995            port2id = connection.port_in_id
996            line = connection.widget
997            canvas.connection_list.remove(connection)
998            break
999
1000    if not line:
1001        qCritical("PatchCanvas::disconnectPorts(%i) - unable to find connection ports" % connection_id)
1002        return
1003
1004    for port in canvas.port_list:
1005        if port.group_id == group1id and port.port_id == port1id:
1006            item1 = port.widget
1007            break
1008
1009    if not item1:
1010        qCritical("PatchCanvas::disconnectPorts(%i) - unable to find output port" % connection_id)
1011        return
1012
1013    for port in canvas.port_list:
1014        if port.group_id == group2id and port.port_id == port2id:
1015            item2 = port.widget
1016            break
1017
1018    if not item2:
1019        qCritical("PatchCanvas::disconnectPorts(%i) - unable to find input port" % connection_id)
1020        return
1021
1022    item1p = item1.parentItem()
1023    item2p = item2.parentItem()
1024    if item1p:
1025        item1p.removeLineFromGroup(connection_id)
1026    if item2p:
1027        item2p.removeLineFromGroup(connection_id)
1028
1029    if options.eyecandy == EYECANDY_FULL:
1030        CanvasItemFX(line, False, True)
1031        return
1032
1033    canvas.scene.removeItem(line)
1034    del line
1035
1036    QTimer.singleShot(0, canvas.scene.update)
1037
1038# ------------------------------------------------------------------------------------------------------------
1039
1040def arrange():
1041    if canvas.debug:
1042        print("PatchCanvas::arrange()")
1043
1044# ------------------------------------------------------------------------------------------------------------
1045
1046def updateZValues():
1047    if canvas.debug:
1048        print("PatchCanvas::updateZValues()")
1049
1050    for group in canvas.group_list:
1051        group.widgets[0].resetLinesZValue()
1052
1053        if group.split and group.widgets[1]:
1054            group.widgets[1].resetLinesZValue()
1055
1056# ------------------------------------------------------------------------------------------------------------
1057
1058def redrawPluginGroup(plugin_id):
1059    group = canvas.group_plugin_map.get(plugin_id, None)
1060
1061    if group is None:
1062        #qCritical("PatchCanvas::redrawPluginGroup(%i) - unable to find group" % plugin_id)
1063        return
1064
1065    group.widgets[0].redrawInlineDisplay()
1066
1067    if group.split and group.widgets[1]:
1068        group.widgets[1].redrawInlineDisplay()
1069
1070def handlePluginRemoved(plugin_id):
1071    if canvas.debug:
1072        print("PatchCanvas::handlePluginRemoved(%i)" % plugin_id)
1073
1074    canvas.scene.clearSelection()
1075
1076    group = canvas.group_plugin_map.pop(plugin_id, None)
1077
1078    if group is not None:
1079        group.plugin_id = -1
1080        group.plugin_ui = False
1081        group.plugin_inline = False
1082        group.widgets[0].removeAsPlugin()
1083
1084        if group.split and group.widgets[1]:
1085            group.widgets[1].removeAsPlugin()
1086
1087    for group in canvas.group_list:
1088        if group.plugin_id < plugin_id or group.plugin_id > MAX_PLUGIN_ID_ALLOWED:
1089            continue
1090
1091        group.plugin_id -= 1
1092        group.widgets[0].m_plugin_id -= 1
1093
1094        if group.split and group.widgets[1]:
1095            group.widgets[1].m_plugin_id -= 1
1096
1097        canvas.group_plugin_map[plugin_id] = group
1098
1099def handleAllPluginsRemoved():
1100    if canvas.debug:
1101        print("PatchCanvas::handleAllPluginsRemoved()")
1102
1103    canvas.group_plugin_map = {}
1104
1105    for group in canvas.group_list:
1106        if group.plugin_id < 0:
1107            continue
1108        if group.plugin_id > MAX_PLUGIN_ID_ALLOWED:
1109            continue
1110
1111        group.plugin_id = -1
1112        group.plugin_ui = False
1113        group.plugin_inline = False
1114        group.widgets[0].removeAsPlugin()
1115
1116        if group.split and group.widgets[1]:
1117            group.widgets[1].removeAsPlugin()
1118
1119# ------------------------------------------------------------------------------------------------------------
1120