|  | #!/usr/bin/env python3
# -*- coding: utf-8 -*-
# PatchBay Canvas engine using QGraphicsView/Scene
# Copyright (C) 2010-2012 Filipe Coelho <falktx@falktx.com>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# For a full copy of the GNU General Public License see the GPL.txt file
# Imports (Global)
try:
    from PyQt5.QtCore import pyqtSignal, pyqtSlot, qDebug, qCritical, qFatal, qWarning, Qt, QObject
    from PyQt5.QtCore import QAbstractAnimation, QLineF, QPointF, QRectF, QSizeF, QSettings, QTimer
    from PyQt5.QtGui import QColor, QLinearGradient, QPen, QPolygonF, QPainter, QPainterPath
    from PyQt5.QtGui import QCursor, QFont, QFontMetrics
    from PyQt5.QtSvg import QGraphicsSvgItem, QSvgRenderer
    from PyQt5.QtWidgets import QGraphicsScene, QGraphicsItem, QGraphicsLineItem, QGraphicsPathItem
    from PyQt5.QtWidgets import QGraphicsColorizeEffect, QGraphicsDropShadowEffect
    from PyQt5.QtWidgets import QInputDialog, QLineEdit, QMenu
except:
    from PyQt4.QtCore import pyqtSignal, pyqtSlot, qDebug, qCritical, qFatal, qWarning, Qt, QObject
    from PyQt4.QtCore import QAbstractAnimation, QLineF, QPointF, QRectF, QSizeF, QSettings, QTimer
    from PyQt4.QtGui import QColor, QLinearGradient, QPen, QPolygonF, QPainter, QPainterPath
    from PyQt4.QtGui import QCursor, QFont, QFontMetrics
    from PyQt4.QtGui import QGraphicsScene, QGraphicsItem, QGraphicsLineItem, QGraphicsPathItem
    from PyQt4.QtGui import QGraphicsColorizeEffect, QGraphicsDropShadowEffect
    from PyQt4.QtGui import QInputDialog, QLineEdit, QMenu
    from PyQt4.QtSvg import QGraphicsSvgItem, QSvgRenderer
# Imports (Theme)
from patchcanvas_theme import *
# ------------------------------------------------------------------------------
# patchcanvas-api.h
# Port Mode
PORT_MODE_NULL   = 0
PORT_MODE_INPUT  = 1
PORT_MODE_OUTPUT = 2
# Port Type
PORT_TYPE_NULL       = 0
PORT_TYPE_AUDIO_JACK = 1
PORT_TYPE_MIDI_JACK  = 2
PORT_TYPE_MIDI_A2J   = 3
PORT_TYPE_MIDI_ALSA  = 4
# Callback Action
ACTION_GROUP_INFO       = 0 # group_id, N, N
ACTION_GROUP_RENAME     = 1 # group_id, N, new_name
ACTION_GROUP_SPLIT      = 2 # group_id, N, N
ACTION_GROUP_JOIN       = 3 # group_id, N, N
ACTION_PORT_INFO        = 4 # port_id, N, N
ACTION_PORT_RENAME      = 5 # port_id, N, new_name
ACTION_PORTS_CONNECT    = 6 # out_id, in_id, N
ACTION_PORTS_DISCONNECT = 7 # conn_id, N, N
# Icon
ICON_APPLICATION = 0
ICON_HARDWARE    = 1
ICON_DISTRHO     = 2
ICON_FILE        = 3
ICON_PLUGIN      = 4
ICON_LADISH_ROOM = 5
# Split Option
SPLIT_UNDEF = 0
SPLIT_NO    = 1
SPLIT_YES   = 2
# Antialiasing Option
ANTIALIASING_NONE  = 0
ANTIALIASING_SMALL = 1
ANTIALIASING_FULL  = 2
# Eye-Candy Option
EYECANDY_NONE  = 0
EYECANDY_SMALL = 1
EYECANDY_FULL  = 2
# Canvas options
class options_t(object):
    __slots__ = [
        'theme_name',
        'auto_hide_groups',
        'use_bezier_lines',
        'antialiasing',
        'eyecandy'
    ]
# Canvas features
class features_t(object):
    __slots__ = [
        'group_info',
        'group_rename',
        'port_info',
        'port_rename',
        'handle_group_pos'
    ]
# ------------------------------------------------------------------------------
# patchcanvas.h
# object types
CanvasBoxType           = QGraphicsItem.UserType + 1
CanvasIconType          = QGraphicsItem.UserType + 2
CanvasPortType          = QGraphicsItem.UserType + 3
CanvasLineType          = QGraphicsItem.UserType + 4
CanvasBezierLineType    = QGraphicsItem.UserType + 5
CanvasLineMovType       = QGraphicsItem.UserType + 6
CanvasBezierLineMovType = QGraphicsItem.UserType + 7
# object lists
class group_dict_t(object):
    __slots__ = [
        'group_id',
        'group_name',
        'split',
        'icon',
        'widgets'
    ]
class port_dict_t(object):
    __slots__ = [
        'group_id',
        'port_id',
        'port_name',
        'port_mode',
        'port_type',
        'widget'
    ]
class connection_dict_t(object):
    __slots__ = [
        'connection_id',
        'port_in_id',
        'port_out_id',
        'widget'
    ]
class animation_dict_t(object):
    __slots__ = [
        'animation',
        'item'
    ]
# Main Canvas object
class Canvas(object):
    __slots__ = [
        'scene',
        'callback',
        'debug',
        'last_z_value',
        'last_connection_id',
        'initial_pos',
        'size_rect',
        'group_list',
        'port_list',
        'connection_list',
        'animation_list',
        'qobject',
        'settings',
        'theme',
        'initiated'
    ]
# ------------------------------------------------------------------------------
# patchcanvas.cpp
class CanvasObject(QObject):
    def __init__(self, parent=None):
        QObject.__init__(self, parent)
    @pyqtSlot()
    def AnimationIdle(self):
        animation = self.sender()
        if animation:
            CanvasRemoveAnimation(animation)
    @pyqtSlot()
    def AnimationHide(self):
        animation = self.sender()
        if animation:
            item = animation.item()
            if item:
                item.hide()
            CanvasRemoveAnimation(animation)
    @pyqtSlot()
    def AnimationDestroy(self):
        animation = self.sender()
        if animation:
            item = animation.item()
            if item:
                CanvasRemoveItemFX(item)
            CanvasRemoveAnimation(animation)
    @pyqtSlot()
    def PortContextMenuDisconnect(self):
        try:
            connectionId = int(self.sender().data())
        except:
            return
        CanvasCallback(ACTION_PORTS_DISCONNECT, connectionId, 0, "")
# Global objects
canvas = Canvas()
canvas.qobject    = None
canvas.settings   = None
canvas.theme      = None
canvas.initiated  = False
canvas.group_list = []
canvas.port_list  = []
canvas.connection_list = []
canvas.animation_list  = []
options = options_t()
options.theme_name = getDefaultThemeName()
options.auto_hide_groups = False
options.use_bezier_lines = True
options.antialiasing = ANTIALIASING_SMALL
options.eyecandy     = EYECANDY_SMALL
features = features_t()
features.group_info   = False
features.group_rename = False
features.port_info    = False
features.port_rename  = False
features.handle_group_pos = False
# Internal functions
def bool2str(check):
    return "True" if check else "False"
def port_mode2str(port_mode):
    if port_mode == PORT_MODE_NULL:
        return "PORT_MODE_NULL"
    elif port_mode == PORT_MODE_INPUT:
        return "PORT_MODE_INPUT"
    elif port_mode == PORT_MODE_OUTPUT:
        return "PORT_MODE_OUTPUT"
    else:
        return "PORT_MODE_???"
def port_type2str(port_type):
    if port_type == PORT_TYPE_NULL:
        return "PORT_TYPE_NULL"
    elif port_type == PORT_TYPE_AUDIO_JACK:
        return "PORT_TYPE_AUDIO_JACK"
    elif port_type == PORT_TYPE_MIDI_JACK:
        return "PORT_TYPE_MIDI_JACK"
    elif port_type == PORT_TYPE_MIDI_A2J:
        return "PORT_TYPE_MIDI_A2J"
    elif port_type == PORT_TYPE_MIDI_ALSA:
        return "PORT_TYPE_MIDI_ALSA"
    else:
        return "PORT_TYPE_???"
def icon2str(icon):
    if icon == ICON_HARDWARE:
        return "ICON_HARDWARE"
    elif ICON_APPLICATION:
        return "ICON_APPLICATION"
    elif ICON_LADISH_ROOM:
        return "ICON_LADISH_ROOM"
    else:
        return "ICON_???"
def split2str(split):
    if split == SPLIT_UNDEF:
        return "SPLIT_UNDEF"
    elif split == SPLIT_NO:
        return "SPLIT_NO"
    elif split == SPLIT_YES:
        return "SPLIT_YES"
    else:
        return "SPLIT_???"
# PatchCanvas API
def setOptions(new_options):
    if canvas.initiated: return
    options.theme_name       = new_options.theme_name
    options.auto_hide_groups = new_options.auto_hide_groups
    options.use_bezier_lines = new_options.use_bezier_lines
    options.antialiasing = new_options.antialiasing
    options.eyecandy     = new_options.eyecandy
def setFeatures(new_features):
    if canvas.initiated: return
    features.group_info   = new_features.group_info
    features.group_rename = new_features.group_rename
    features.port_info    = new_features.port_info
    features.port_rename  = new_features.port_rename
    features.handle_group_pos = new_features.handle_group_pos
def init(appName, scene, callback, debug=False):
    if debug:
        qDebug("PatchCanvas::init(\"%s\", %s, %s, %s)" % (appName, scene, callback, bool2str(debug)))
    if canvas.initiated:
        qCritical("PatchCanvas::init() - already initiated")
        return
    if not callback:
        qFatal("PatchCanvas::init() - fatal error: callback not set")
        return
    canvas.scene    = scene
    canvas.callback = callback
    canvas.debug    = debug
    canvas.last_z_value = 0
    canvas.last_connection_id = 0
    canvas.initial_pos = QPointF(0, 0)
    canvas.size_rect = QRectF()
    if not canvas.qobject:  canvas.qobject = CanvasObject()
    if not canvas.settings: canvas.settings = QSettings("Cadence", appName)
    if canvas.theme:
        del canvas.theme
        canvas.theme = None
    for i in range(Theme.THEME_MAX):
        this_theme_name = getThemeName(i)
        if this_theme_name == options.theme_name:
            canvas.theme = Theme(i)
            break
    if not canvas.theme:
        canvas.theme = Theme(getDefaultTheme())
    canvas.scene.updateTheme()
    canvas.initiated = True
def clear():
    if canvas.debug:
        qDebug("PatchCanvas::clear()")
    group_list_ids = []
    port_list_ids  = []
    connection_list_ids = []
    for group in canvas.group_list:
        group_list_ids.append(group.group_id)
    for port in canvas.port_list:
        port_list_ids.append(port.port_id)
    for connection in canvas.connection_list:
        connection_list_ids.append(connection.connection_id)
    for idx in connection_list_ids:
        disconnectPorts(idx)
    for idx in port_list_ids:
        removePort(idx)
    for idx in group_list_ids:
        removeGroup(idx)
    canvas.last_z_value = 0
    canvas.last_connection_id = 0
    canvas.group_list = []
    canvas.port_list = []
    canvas.connection_list = []
    canvas.scene.clear()
    canvas.initiated = False
def setInitialPos(x, y):
    if canvas.debug:
        qDebug("PatchCanvas::setInitialPos(%i, %i)" % (x, y))
    canvas.initial_pos.setX(x)
    canvas.initial_pos.setY(y)
def setCanvasSize(x, y, width, height):
    if canvas.debug:
        qDebug("PatchCanvas::setCanvasSize(%i, %i, %i, %i)" % (x, y, width, height))
    canvas.size_rect.setX(x)
    canvas.size_rect.setY(y)
    canvas.size_rect.setWidth(width)
    canvas.size_rect.setHeight(height)
def addGroup(group_id, group_name, split=SPLIT_UNDEF, icon=ICON_APPLICATION):
    if canvas.debug:
        qDebug("PatchCanvas::addGroup(%i, %s, %s, %s)" % (group_id, group_name.encode(), split2str(split), icon2str(icon)))
    for group in canvas.group_list:
        if group.group_id == group_id:
            qWarning("PatchCanvas::addGroup(%i, %s, %s, %s) - group already exists" % (group_id, group_name.encode(), split2str(split), icon2str(icon)))
            return
    if split == SPLIT_UNDEF and features.handle_group_pos:
        split = canvas.settings.value("CanvasPositions/%s_SPLIT" % group_name, split, type=int)
    group_box = CanvasBox(group_id, group_name, icon)
    group_dict = group_dict_t()
    group_dict.group_id = group_id
    group_dict.group_name = group_name
    group_dict.split = bool(split == SPLIT_YES)
    group_dict.icon = icon
    group_dict.widgets = [group_box, None]
    if split == SPLIT_YES:
        group_box.setSplit(True, PORT_MODE_OUTPUT)
        if features.handle_group_pos:
            group_box.setPos(canvas.settings.value("CanvasPositions/%s_OUTPUT" % group_name, CanvasGetNewGroupPos(), type=QPointF))
        else:
            group_box.setPos(CanvasGetNewGroupPos())
        group_sbox = CanvasBox(group_id, group_name, icon)
        group_sbox.setSplit(True, PORT_MODE_INPUT)
        group_dict.widgets[1] = group_sbox
        if features.handle_group_pos:
            group_sbox.setPos(canvas.settings.value("CanvasPositions/%s_INPUT" % group_name, CanvasGetNewGroupPos(True), type=QPointF))
        else:
            group_sbox.setPos(CanvasGetNewGroupPos(True))
        canvas.last_z_value += 1
        group_sbox.setZValue(canvas.last_z_value)
        if options.eyecandy == EYECANDY_FULL and not options.auto_hide_groups:
            CanvasItemFX(group_sbox, True)
        group_sbox.checkItemPos()
    else:
        group_box.setSplit(False)
        if features.handle_group_pos:
            group_box.setPos(canvas.settings.value("CanvasPositions/%s" % group_name, CanvasGetNewGroupPos(), type=QPointF))
        else:
            # Special ladish fake-split groups
            horizontal = bool(icon == ICON_HARDWARE or icon == ICON_LADISH_ROOM)
            group_box.setPos(CanvasGetNewGroupPos(horizontal))
    group_box.checkItemPos()
    canvas.last_z_value += 1
    group_box.setZValue(canvas.last_z_value)
    canvas.group_list.append(group_dict)
    if options.eyecandy == EYECANDY_FULL and not options.auto_hide_groups:
        CanvasItemFX(group_box, True)
    QTimer.singleShot(0, canvas.scene.update)
def removeGroup(group_id):
    if canvas.debug:
        qDebug("PatchCanvas::removeGroup(%i)" % group_id)
    for group in canvas.group_list:
        if group.group_id == group_id:
            item = group.widgets[0]
            group_name = group.group_name
            if group.split:
                s_item = group.widgets[1]
                if features.handle_group_pos:
                    canvas.settings.setValue("CanvasPositions/%s_OUTPUT" % group_name, item.pos())
                    canvas.settings.setValue("CanvasPositions/%s_INPUT" % group_name, s_item.pos())
                    canvas.settings.setValue("CanvasPositions/%s_SPLIT" % group_name, SPLIT_YES)
                if options.eyecandy == EYECANDY_FULL:
                    CanvasItemFX(s_item, False, True)
                else:
                    s_item.removeIconFromScene()
                    canvas.scene.removeItem(s_item)
                    del s_item
            else:
                if features.handle_group_pos:
                    canvas.settings.setValue("CanvasPositions/%s" % group_name, item.pos())
                    canvas.settings.setValue("CanvasPositions/%s_SPLIT" % group_name, SPLIT_NO)
            if options.eyecandy == EYECANDY_FULL:
                CanvasItemFX(item, False, True)
            else:
                item.removeIconFromScene()
                canvas.scene.removeItem(item)
                del item
            canvas.group_list.remove(group)
            QTimer.singleShot(0, canvas.scene.update)
            return
    qCritical("PatchCanvas::removeGroup(%i) - unable to find group to remove" % group_id)
def renameGroup(group_id, new_group_name):
    if canvas.debug:
        qDebug("PatchCanvas::renameGroup(%i, %s)" % (group_id, new_group_name.encode()))
    for group in canvas.group_list:
        if group.group_id == group_id:
            group.group_name = new_group_name
            group.widgets[0].setGroupName(new_group_name)
            if group.split and group.widgets[1]:
                group.widgets[1].setGroupName(new_group_name)
            QTimer.singleShot(0, canvas.scene.update)
            return
    qCritical("PatchCanvas::renameGroup(%i, %s) - unable to find group to rename" % (group_id, new_group_name.encode()))
def splitGroup(group_id):
    if canvas.debug:
        qDebug("PatchCanvas::splitGroup(%i)" % group_id)
    item = None
    group_name = ""
    group_icon = ICON_APPLICATION
    ports_data = []
    conns_data = []
    # Step 1 - Store all Item data
    for group in canvas.group_list:
        if group.group_id == group_id:
            if group.split:
                qCritical("PatchCanvas::splitGroup(%i) - group is already splitted" % group_id)
                return
            item = group.widgets[0]
            group_name = group.group_name
            group_icon = group.icon
            break
    if not item:
        qCritical("PatchCanvas::splitGroup(%i) - unable to find group to split" % group_id)
        return
    port_list_ids = list(item.getPortList())
    for port in canvas.port_list:
        if port.port_id in port_list_ids:
            port_dict = port_dict_t()
            port_dict.group_id = port.group_id
            port_dict.port_id = port.port_id
            port_dict.port_name = port.port_name
            port_dict.port_mode = port.port_mode
            port_dict.port_type = port.port_type
            port_dict.widget = None
            ports_data.append(port_dict)
    for connection in canvas.connection_list:
        if connection.port_out_id in port_list_ids or connection.port_in_id in port_list_ids:
            connection_dict = connection_dict_t()
            connection_dict.connection_id = connection.connection_id
            connection_dict.port_in_id = connection.port_in_id
            connection_dict.port_out_id = connection.port_out_id
            connection_dict.widget = None
            conns_data.append(connection_dict)
    # Step 2 - Remove Item and Children
    for conn in conns_data:
        disconnectPorts(conn.connection_id)
    for port_id in port_list_ids:
        removePort(port_id)
    removeGroup(group_id)
    # Step 3 - Re-create Item, now splitted
    addGroup(group_id, group_name, SPLIT_YES, group_icon)
    for port in ports_data:
        addPort(group_id, port.port_id, port.port_name, port.port_mode, port.port_type)
    for conn in conns_data:
        connectPorts(conn.connection_id, conn.port_out_id, conn.port_in_id)
    QTimer.singleShot(0, canvas.scene.update)
def joinGroup(group_id):
    if canvas.debug:
        qDebug("PatchCanvas::joinGroup(%i)" % group_id)
    item   = None
    s_item = None
    group_name = ""
    group_icon = ICON_APPLICATION
    ports_data = []
    conns_data = []
    # Step 1 - Store all Item data
    for group in canvas.group_list:
        if group.group_id == group_id:
            if not group.split:
                qCritical("PatchCanvas::joinGroup(%i) - group is not splitted" % group_id)
                return
            item = group.widgets[0]
            s_item = group.widgets[1]
            group_name = group.group_name
            group_icon = group.icon
            break
    # FIXME
    if not (item and s_item):
        qCritical("PatchCanvas::joinGroup(%i) - unable to find groups to join" % group_id)
        return
    port_list_ids = list(item.getPortList())
    port_list_idss = s_item.getPortList()
    for port_id in port_list_idss:
        if port_id not in port_list_ids:
            port_list_ids.append(port_id)
    for port in canvas.port_list:
        if port.port_id in port_list_ids:
            port_dict = port_dict_t()
            port_dict.group_id = port.group_id
            port_dict.port_id = port.port_id
            port_dict.port_name = port.port_name
            port_dict.port_mode = port.port_mode
            port_dict.port_type = port.port_type
            port_dict.widget = None
            ports_data.append(port_dict)
    for connection in canvas.connection_list:
        if connection.port_out_id in port_list_ids or connection.port_in_id in port_list_ids:
            connection_dict = connection_dict_t()
            connection_dict.connection_id = connection.connection_id
            connection_dict.port_in_id = connection.port_in_id
            connection_dict.port_out_id = connection.port_out_id
            connection_dict.widget = None
            conns_data.append(connection_dict)
    # Step 2 - Remove Item and Children
    for conn in conns_data:
        disconnectPorts(conn.connection_id)
    for port_id in port_list_ids:
        removePort(port_id)
    removeGroup(group_id)
    # Step 3 - Re-create Item, now together
    addGroup(group_id, group_name, SPLIT_NO, group_icon)
    for port in ports_data:
        addPort(group_id, port.port_id, port.port_name, port.port_mode, port.port_type)
    for conn in conns_data:
        connectPorts(conn.connection_id, conn.port_out_id, conn.port_in_id)
    QTimer.singleShot(0, canvas.scene.update)
def getGroupPos(group_id, port_mode=PORT_MODE_OUTPUT):
    if canvas.debug:
        qDebug("PatchCanvas::getGroupPos(%i, %s)" % (group_id, port_mode2str(port_mode)))
    for group in canvas.group_list:
        if group.group_id == group_id:
            if group.split:
                if port_mode == PORT_MODE_OUTPUT:
                    return group.widgets[0].pos()
                elif port_mode == PORT_MODE_INPUT:
                    return group.widgets[1].pos()
                else:
                    return QPointF(0, 0)
            else:
                return group.widgets[0].pos()
    qCritical("PatchCanvas::getGroupPos(%i, %s) - unable to find group" % (group_id, port_mode2str(port_mode)))
    return QPointF(0, 0)
def setGroupPos(group_id, group_pos_x, group_pos_y):
    setGroupPosFull(group_id, group_pos_x, group_pos_y, group_pos_x, group_pos_y)
def setGroupPosFull(group_id, group_pos_x_o, group_pos_y_o, group_pos_x_i, group_pos_y_i):
    if canvas.debug:
        qDebug("PatchCanvas::setGroupPos(%i, %i, %i, %i, %i)" % (group_id, group_pos_x_o, group_pos_y_o, group_pos_x_i, group_pos_y_i))
    for group in canvas.group_list:
        if group.group_id == group_id:
            group.widgets[0].setPos(group_pos_x_o, group_pos_y_o)
            if group.split and group.widgets[1]:
                group.widgets[1].setPos(group_pos_x_i, group_pos_y_i)
            QTimer.singleShot(0, canvas.scene.update)
            return
    qCritical("PatchCanvas::setGroupPos(%i, %i, %i, %i, %i) - unable to find group to reposition" % (group_id, group_pos_x_o, group_pos_y_o, group_pos_x_i, group_pos_y_i))
def setGroupIcon(group_id, icon):
    if canvas.debug:
        qDebug("PatchCanvas::setGroupIcon(%i, %s)" % (group_id, icon2str(icon)))
    for group in canvas.group_list:
        if group.group_id == group_id:
            group.icon = icon
            group.widgets[0].setIcon(icon)
            if group.split and group.widgets[1]:
                group.widgets[1].setIcon(icon)
            QTimer.singleShot(0, canvas.scene.update)
            return
    qCritical("PatchCanvas::setGroupIcon(%i, %s) - unable to find group to change icon" % (group_id, icon2str(icon)))
def addPort(group_id, port_id, port_name, port_mode, port_type):
    if canvas.debug:
        qDebug("PatchCanvas::addPort(%i, %i, %s, %s, %s)" % (group_id, port_id, port_name.encode(), port_mode2str(port_mode), port_type2str(port_type)))
    for port in canvas.port_list:
        if port.group_id == group_id and port.port_id == port_id:
            qWarning("PatchCanvas::addPort(%i, %i, %s, %s, %s) - port already exists" % (group_id, port_id, port_name.encode(), port_mode2str(port_mode), port_type2str(port_type)))
            return
    box_widget  = None
    port_widget = None
    for group in canvas.group_list:
        if group.group_id == group_id:
            if group.split and group.widgets[0].getSplittedMode() != port_mode and group.widgets[1]:
                n = 1
            else:
                n = 0
            box_widget  = group.widgets[n]
            port_widget = box_widget.addPortFromGroup(port_id, port_mode, port_type, port_name)
            break
    if not (box_widget and port_widget):
        qCritical("PatchCanvas::addPort(%i, %i, %s, %s, %s) - Unable to find parent group" % (group_id, port_id, port_name.encode(), port_mode2str(port_mode), port_type2str(port_type)))
        return
    if options.eyecandy == EYECANDY_FULL:
        CanvasItemFX(port_widget, True)
    port_dict = port_dict_t()
    port_dict.group_id  = group_id
    port_dict.port_id   = port_id
    port_dict.port_name = port_name
    port_dict.port_mode = port_mode
    port_dict.port_type = port_type
    port_dict.widget = port_widget
    canvas.port_list.append(port_dict)
    box_widget.updatePositions()
    QTimer.singleShot(0, canvas.scene.update)
def removePort(port_id):
    if canvas.debug:
        qDebug("PatchCanvas::removePort(%i)" % port_id)
    for port in canvas.port_list:
        if port.port_id == port_id:
            item = port.widget
            item.parentItem().removePortFromGroup(port_id)
            canvas.scene.removeItem(item)
            del item
            canvas.port_list.remove(port)
            QTimer.singleShot(0, canvas.scene.update)
            return
    qCritical("PatchCanvas::removePort(%i) - Unable to find port to remove" % port_id)
def renamePort(port_id, new_port_name):
    if canvas.debug:
        qDebug("PatchCanvas::renamePort(%i, %s)" % (port_id, new_port_name.encode()))
    for port in canvas.port_list:
        if port.port_id == port_id:
            port.port_name = new_port_name
            port.widget.setPortName(new_port_name)
            port.widget.parentItem().updatePositions()
            QTimer.singleShot(0, canvas.scene.update)
            return
    qCritical("PatchCanvas::renamePort(%i, %s) - Unable to find port to rename" % (port_id, new_port_name.encode()))
def connectPorts(connection_id, port_out_id, port_in_id):
    if canvas.debug:
        qDebug("PatchCanvas::connectPorts(%i, %i, %i)" % (connection_id, port_out_id, port_in_id))
    port_out = None
    port_in  = None
    port_out_parent = None
    port_in_parent  = None
    for port in canvas.port_list:
        if port.port_id == port_out_id:
            port_out = port.widget
            port_out_parent = port_out.parentItem()
        elif port.port_id == port_in_id:
            port_in = port.widget
            port_in_parent = port_in.parentItem()
    # FIXME
    if not (port_out and port_in):
        qCritical("PatchCanvas::connectPorts(%i, %i, %i) - unable to find ports to connect" % (connection_id, port_out_id, port_in_id))
        return
    connection_dict = connection_dict_t()
    connection_dict.connection_id = connection_id
    connection_dict.port_out_id = port_out_id
    connection_dict.port_in_id = port_in_id
    if options.use_bezier_lines:
        connection_dict.widget = CanvasBezierLine(port_out, port_in, None)
    else:
        connection_dict.widget = CanvasLine(port_out, port_in, None)
    canvas.scene.addItem(connection_dict.widget)
    port_out_parent.addLineFromGroup(connection_dict.widget, connection_id)
    port_in_parent.addLineFromGroup(connection_dict.widget, connection_id)
    canvas.last_z_value += 1
    port_out_parent.setZValue(canvas.last_z_value)
    port_in_parent.setZValue(canvas.last_z_value)
    canvas.last_z_value += 1
    connection_dict.widget.setZValue(canvas.last_z_value)
    canvas.connection_list.append(connection_dict)
    if options.eyecandy == EYECANDY_FULL:
        item = connection_dict.widget
        CanvasItemFX(item, True)
    QTimer.singleShot(0, canvas.scene.update)
def disconnectPorts(connection_id):
    if canvas.debug:
        qDebug("PatchCanvas::disconnectPorts(%i)" % connection_id)
    port_1_id = port_2_id = 0
    line = None
    item1 = None
    item2 = None
    for connection in canvas.connection_list:
        if connection.connection_id == connection_id:
            port_1_id = connection.port_out_id
            port_2_id = connection.port_in_id
            line = connection.widget
            canvas.connection_list.remove(connection)
            break
    if not line:
        qCritical("PatchCanvas::disconnectPorts(%i) - unable to find connection ports" % connection_id)
        return
    for port in canvas.port_list:
        if port.port_id == port_1_id:
            item1 = port.widget
            break
    if not item1:
        qCritical("PatchCanvas::disconnectPorts(%i) - unable to find output port" % connection_id)
        return
    for port in canvas.port_list:
        if port.port_id == port_2_id:
            item2 = port.widget
            break
    if not item2:
        qCritical("PatchCanvas::disconnectPorts(%i) - unable to find input port" % connection_id)
        return
    item1.parentItem().removeLineFromGroup(connection_id)
    item2.parentItem().removeLineFromGroup(connection_id)
    if options.eyecandy == EYECANDY_FULL:
        CanvasItemFX(line, False, True)
    else:
        line.deleteFromScene()
    QTimer.singleShot(0, canvas.scene.update)
def arrange():
    if canvas.debug:
        qDebug("PatchCanvas::arrange()")
def updateZValues():
    if canvas.debug:
        qDebug("PatchCanvas::updateZValues()")
    for group in canvas.group_list:
        group.widgets[0].resetLinesZValue()
        if group.split and group.widgets[1]:
            group.widgets[1].resetLinesZValue()
# Extra Internal functions
def CanvasGetGroupName(group_id):
    if canvas.debug:
        qDebug("PatchCanvas::CanvasGetGroupName(%i)" % group_id)
    for group in canvas.group_list:
        if group.group_id == group_id:
            return group.group_name
    qCritical("PatchCanvas::CanvasGetGroupName(%i) - unable to find group" % group_id)
    return ""
def CanvasGetGroupPortCount(group_id):
    if canvas.debug:
        qDebug("PatchCanvas::CanvasGetGroupPortCount(%i)" % group_id)
    port_count = 0
    for port in canvas.port_list:
        if port.group_id == group_id:
            port_count += 1
    return port_count
def CanvasGetNewGroupPos(horizontal=False):
    if canvas.debug:
        qDebug("PatchCanvas::CanvasGetNewGroupPos(%s)" % bool2str(horizontal))
    new_pos = QPointF(canvas.initial_pos.x(), canvas.initial_pos.y())
    items = canvas.scene.items()
    break_loop = False
    while not break_loop:
        break_for = False
        for i in range(len(items)):
            item = items[i]
            if item and item.type() == CanvasBoxType:
                if item.sceneBoundingRect().contains(new_pos):
                    if horizontal:
                        new_pos += QPointF(item.boundingRect().width() + 15, 0)
                    else:
                        new_pos += QPointF(0, item.boundingRect().height() + 15)
                    break_for = True
                    break
            if i >= len(items) - 1 and not break_for:
                break_loop = True
    return new_pos
def CanvasGetFullPortName(port_id):
    if canvas.debug:
        qDebug("PatchCanvas::CanvasGetFullPortName(%i)" % port_id)
    for port in canvas.port_list:
        if port.port_id == port_id:
            group_id = port.group_id
            for group in canvas.group_list:
                if group.group_id == group_id:
                    return group.group_name + ":" + port.port_name
            break
    qCritical("PatchCanvas::CanvasGetFullPortName(%i) - unable to find port" % port_id)
    return ""
def CanvasGetPortConnectionList(port_id):
    if canvas.debug:
        qDebug("PatchCanvas::CanvasGetPortConnectionList(%i)" % port_id)
    port_con_list = []
    for connection in canvas.connection_list:
        if connection.port_out_id == port_id or connection.port_in_id == port_id:
            port_con_list.append(connection.connection_id)
    return port_con_list
def CanvasGetConnectedPort(connection_id, port_id):
    if canvas.debug:
        qDebug("PatchCanvas::CanvasGetConnectedPort(%i, %i)" % (connection_id, port_id))
    for connection in canvas.connection_list:
        if connection.connection_id == connection_id:
            if connection.port_out_id == port_id:
                return connection.port_in_id
            else:
                return connection.port_out_id
    qCritical("PatchCanvas::CanvasGetConnectedPort(%i, %i) - unable to find connection" % (connection_id, port_id))
    return 0
def CanvasRemoveAnimation(f_animation):
    if canvas.debug:
        qDebug("PatchCanvas::CanvasRemoveAnimation(%s)" % f_animation)
    for animation in canvas.animation_list:
        if animation.animation == f_animation:
            canvas.animation_list.remove(animation)
            del animation.animation
            break
def CanvasCallback(action, value1, value2, value_str):
    if canvas.debug:
        qDebug("PatchCanvas::CanvasCallback(%i, %i, %i, %s)" % (action, value1, value2, value_str.encode()))
    canvas.callback(action, value1, value2, value_str)
def CanvasItemFX(item, show, destroy=False):
    if canvas.debug:
        qDebug("PatchCanvas::CanvasItemFX(%s, %s, %s)" % (item, bool2str(show), bool2str(destroy)))
    # Check if item already has an animationItemFX
    for animation in canvas.animation_list:
        if animation.item == item:
            animation.animation.stop()
            canvas.animation_list.remove(animation)
            del animation.animation
            break
    animation = CanvasFadeAnimation(item, show)
    animation.setDuration(750 if show else 500)
    animation_dict = animation_dict_t()
    animation_dict.animation = animation
    animation_dict.item = item
    canvas.animation_list.append(animation_dict)
    if show:
        animation.finished.connect(canvas.qobject.AnimationIdle)
    else:
        if destroy:
            animation.finished.connect(canvas.qobject.AnimationDestroy)
        else:
            animation.finished.connect(canvas.qobject.AnimationHide)
    animation.start()
def CanvasRemoveItemFX(item):
    if canvas.debug:
        qDebug("PatchCanvas::CanvasRemoveItemFX(%s)" % item)
    if item.type() == CanvasBoxType:
        item.removeIconFromScene()
        canvas.scene.removeItem(item)
    elif item.type() == CanvasPortType:
        canvas.scene.removeItem(item)
    elif item.type() in (CanvasLineType, CanvasBezierLineType):
        pass #item.deleteFromScene()
    # Force deletion of item if needed
    if item.type() in (CanvasBoxType, CanvasPortType):
        del item
# ------------------------------------------------------------------------------
# patchscene.cpp
class PatchScene(QGraphicsScene):
    scaleChanged    = pyqtSignal(float)
    sceneGroupMoved = pyqtSignal(int, int, QPointF)
    def __init__(self, parent, view):
        QGraphicsScene.__init__(self, parent)
        self.m_ctrl_down = False
        self.m_mouse_down_init = False
        self.m_mouse_rubberband = False
        self.addRubberBand()
        self.m_view = view
        if not self.m_view:
            qFatal("PatchCanvas::PatchScene() - invalid view")
    def addRubberBand(self):
        self.m_rubberband = self.addRect(QRectF(0, 0, 0, 0))
        self.m_rubberband.setZValue(-1)
        self.m_rubberband.hide()
        self.m_rubberband_selection = False
        self.m_rubberband_orig_point = QPointF(0, 0)
    def clear(self):
        QGraphicsScene.clear(self)
        # Re-add rubberband, that just got deleted
        self.addRubberBand()
    def fixScaleFactor(self):
        scale = self.m_view.transform().m11()
        if scale > 3.0:
            self.m_view.resetTransform()
            self.m_view.scale(3.0, 3.0)
        elif scale < 0.2:
            self.m_view.resetTransform()
            self.m_view.scale(0.2, 0.2)
        self.scaleChanged.emit(self.m_view.transform().m11())
    def updateTheme(self):
        self.setBackgroundBrush(canvas.theme.canvas_bg)
        self.m_rubberband.setPen(canvas.theme.rubberband_pen)
        self.m_rubberband.setBrush(canvas.theme.rubberband_brush)
    def zoom_fit(self):
        min_x = min_y = max_x = max_y = None
        first_value = True
        items_list = self.items()
        if len(items_list) > 0:
            for item in items_list:
                if item and item.isVisible() and item.type() == CanvasBoxType:
                    pos = item.scenePos()
                    rect = item.boundingRect()
                    if first_value:
                        min_x = pos.x()
                    elif pos.x() < min_x:
                        min_x = pos.x()
                    if first_value:
                        min_y = pos.y()
                    elif pos.y() < min_y:
                        min_y = pos.y()
                    if first_value:
                        max_x = pos.x() + rect.width()
                    elif pos.x() + rect.width() > max_x:
                        max_x = pos.x() + rect.width()
                    if first_value:
                        max_y = pos.y() + rect.height()
                    elif pos.y() + rect.height() > max_y:
                        max_y = pos.y() + rect.height()
                    first_value = False
            if not first_value:
                self.m_view.fitInView(min_x, min_y, abs(max_x - min_x), abs(max_y - min_y), Qt.KeepAspectRatio)
                self.fixScaleFactor()
    def zoom_in(self):
        if self.m_view.transform().m11() < 3.0:
            self.m_view.scale(1.2, 1.2)
        self.scaleChanged.emit(self.m_view.transform().m11())
    def zoom_out(self):
        if self.m_view.transform().m11() > 0.2:
            self.m_view.scale(0.8, 0.8)
        self.scaleChanged.emit(self.m_view.transform().m11())
    def zoom_reset(self):
        self.m_view.resetTransform()
        self.scaleChanged.emit(1.0)
    def keyPressEvent(self, event):
        if not self.m_view:
            return event.ignore()
        if event.key() == Qt.Key_Control:
            self.m_ctrl_down = True
        elif event.key() == Qt.Key_Home:
            self.zoom_fit()
            return event.accept()
        elif self.m_ctrl_down:
            if event.key() == Qt.Key_Plus:
                self.zoom_in()
                return event.accept()
            elif event.key() == Qt.Key_Minus:
                self.zoom_out()
                return event.accept()
            elif event.key() == Qt.Key_1:
                self.zoom_reset()
                return event.accept()
        QGraphicsScene.keyPressEvent(self, event)
    def keyReleaseEvent(self, event):
        if event.key() == Qt.Key_Control:
            self.m_ctrl_down = False
        QGraphicsScene.keyReleaseEvent(self, event)
    def mousePressEvent(self, event):
        self.m_mouse_down_init  = bool(event.button() == Qt.LeftButton)
        self.m_mouse_rubberband = False
        QGraphicsScene.mousePressEvent(self, event)
    def mouseMoveEvent(self, event):
        if self.m_mouse_down_init:
            self.m_mouse_down_init  = False
            self.m_mouse_rubberband = bool(len(self.selectedItems()) == 0)
        if self.m_mouse_rubberband:
            if not self.m_rubberband_selection:
                self.m_rubberband.show()
                self.m_rubberband_selection = True
                self.m_rubberband_orig_point = event.scenePos()
            pos = event.scenePos()
            if pos.x() > self.m_rubberband_orig_point.x():
                x = self.m_rubberband_orig_point.x()
            else:
                x = pos.x()
            if pos.y() > self.m_rubberband_orig_point.y():
                y = self.m_rubberband_orig_point.y()
            else:
                y = pos.y()
            self.m_rubberband.setRect(x, y, abs(pos.x() - self.m_rubberband_orig_point.x()), abs(pos.y() - self.m_rubberband_orig_point.y()))
            return event.accept()
        QGraphicsScene.mouseMoveEvent(self, event)
    def mouseReleaseEvent(self, event):
        if self.m_rubberband_selection:
            items_list = self.items()
            if len(items_list) > 0:
                for item in items_list:
                    if item and item.isVisible() and item.type() == CanvasBoxType:
                        item_rect = item.sceneBoundingRect()
                        item_top_left = QPointF(item_rect.x(), item_rect.y())
                        item_bottom_right = QPointF(item_rect.x() + item_rect.width(), item_rect.y() + item_rect.height())
                        if self.m_rubberband.contains(item_top_left) and self.m_rubberband.contains(item_bottom_right):
                            item.setSelected(True)
                self.m_rubberband.hide()
                self.m_rubberband.setRect(0, 0, 0, 0)
                self.m_rubberband_selection = False
        else:
            items_list = self.selectedItems()
            for item in items_list:
                if item and item.isVisible() and item.type() == CanvasBoxType:
                    item.checkItemPos()
                    self.sceneGroupMoved.emit(item.getGroupId(), item.getSplittedMode(), item.scenePos())
            if len(items_list) > 1:
                canvas.scene.update()
        self.m_mouse_down_init  = False
        self.m_mouse_rubberband = False
        QGraphicsScene.mouseReleaseEvent(self, event)
    def wheelEvent(self, event):
        if not self.m_view:
            return event.ignore()
        if self.m_ctrl_down:
            factor = 1.41 ** (event.delta() / 240.0)
            self.m_view.scale(factor, factor)
            self.fixScaleFactor()
            return event.accept()
        QGraphicsScene.wheelEvent(self, event)
# ------------------------------------------------------------------------------
# canvasfadeanimation.cpp
class CanvasFadeAnimation(QAbstractAnimation):
    def __init__(self, item, show, parent=None):
        QAbstractAnimation.__init__(self, parent)
        self.m_show = show
        self.m_duration = 0
        self.m_item = item
    def item(self):
        return self.m_item
    def setDuration(self, time):
        if self.m_item.opacity() == 0 and not self.m_show:
            self._duration = 0
        else:
            self.m_item.show()
            self.m_duration = time
    def duration(self):
        return self.m_duration
    def updateCurrentTime(self, time):
        if self.m_duration == 0:
            return
        if self.m_show:
            value = float(time) / self.m_duration
        else:
            value = 1.0 - (float(time) / self.m_duration)
        self.m_item.setOpacity(value)
        if self.m_item.type() == CanvasBoxType:
            self.m_item.setShadowOpacity(value)
    def updateDirection(self, direction):
        pass
    def updateState(self, oldState, newState):
        pass
# ------------------------------------------------------------------------------
# canvasline.cpp
class CanvasLine(QGraphicsLineItem):
    def __init__(self, item1, item2, parent):
        QGraphicsLineItem.__init__(self, parent)
        self.item1 = item1
        self.item2 = item2
        self.m_locked = False
        self.m_lineSelected = False
        self.setGraphicsEffect(None)
        self.updateLinePos()
    def deleteFromScene(self):
        canvas.scene.removeItem(self)
        del self
    def isLocked(self):
        return self.m_locked
    def setLocked(self, yesno):
        self.m_locked = yesno
    def isLineSelected(self):
        return self.m_lineSelected
    def setLineSelected(self, yesno):
        if self.m_locked:
            return
        if options.eyecandy == EYECANDY_FULL:
            if yesno:
                self.setGraphicsEffect(CanvasPortGlow(self.item1.getPortType(), self.toGraphicsObject()))
            else:
                self.setGraphicsEffect(None)
        self.m_lineSelected = yesno
        self.updateLineGradient()
    def updateLinePos(self):
        if self.item1.getPortMode() == PORT_MODE_OUTPUT:
            line = QLineF(self.item1.scenePos().x() + self.item1.getPortWidth() + 12,
                          self.item1.scenePos().y() + float(canvas.theme.port_height)/2,
                          self.item2.scenePos().x(),
                          self.item2.scenePos().y() + float(canvas.theme.port_height)/2)
            self.setLine(line)
            self.m_lineSelected = False
            self.updateLineGradient()
    def type(self):
        return CanvasLineType
    def updateLineGradient(self):
        pos_top = self.boundingRect().top()
        pos_bot = self.boundingRect().bottom()
        if self.item2.scenePos().y() >= self.item1.scenePos().y():
            pos1 = 0
            pos2 = 1
        else:
            pos1 = 1
            pos2 = 0
        port_type1 = self.item1.getPortType()
        port_type2 = self.item2.getPortType()
        port_gradient = QLinearGradient(0, pos_top, 0, pos_bot)
        if port_type1 == PORT_TYPE_AUDIO_JACK:
            port_gradient.setColorAt(pos1, canvas.theme.line_audio_jack_sel if self.m_lineSelected else canvas.theme.line_audio_jack)
        elif port_type1 == PORT_TYPE_MIDI_JACK:
            port_gradient.setColorAt(pos1, canvas.theme.line_midi_jack_sel if self.m_lineSelected else canvas.theme.line_midi_jack)
        elif port_type1 == PORT_TYPE_MIDI_A2J:
            port_gradient.setColorAt(pos1, canvas.theme.line_midi_a2j_sel if self.m_lineSelected else canvas.theme.line_midi_a2j)
        elif port_type1 == PORT_TYPE_MIDI_ALSA:
            port_gradient.setColorAt(pos1, canvas.theme.line_midi_alsa_sel if self.m_lineSelected else canvas.theme.line_midi_alsa)
        if port_type2 == PORT_TYPE_AUDIO_JACK:
            port_gradient.setColorAt(pos2, canvas.theme.line_audio_jack_sel if self.m_lineSelected else canvas.theme.line_audio_jack)
        elif port_type2 == PORT_TYPE_MIDI_JACK:
            port_gradient.setColorAt(pos2, canvas.theme.line_midi_jack_sel if self.m_lineSelected else canvas.theme.line_midi_jack)
        elif port_type2 == PORT_TYPE_MIDI_A2J:
            port_gradient.setColorAt(pos2, canvas.theme.line_midi_a2j_sel if self.m_lineSelected else canvas.theme.line_midi_a2j)
        elif port_type2 == PORT_TYPE_MIDI_ALSA:
            port_gradient.setColorAt(pos2, canvas.theme.line_midi_alsa_sel if self.m_lineSelected else canvas.theme.line_midi_alsa)
        self.setPen(QPen(port_gradient, 2))
    def paint(self, painter, option, widget):
        painter.save()
        painter.setRenderHint(QPainter.Antialiasing, bool(options.antialiasing))
        QGraphicsLineItem.paint(self, painter, option, widget)
        painter.restore()
# ------------------------------------------------------------------------------
# canvasbezierline.cpp
class CanvasBezierLine(QGraphicsPathItem):
    def __init__(self, item1, item2, parent):
        QGraphicsPathItem.__init__(self, parent)
        self.item1 = item1
        self.item2 = item2
        self.m_locked = False
        self.m_lineSelected = False
        self.setBrush(QColor(0, 0, 0, 0))
        self.setGraphicsEffect(None)
        self.updateLinePos()
    def deleteFromScene(self):
        canvas.scene.removeItem(self)
        del self
    def isLocked(self):
        return self.m_locked
    def setLocked(self, yesno):
        self.m_locked = yesno
    def isLineSelected(self):
        return self.m_lineSelected
    def setLineSelected(self, yesno):
        if self.m_locked:
            return
        if options.eyecandy == EYECANDY_FULL:
            if yesno:
                self.setGraphicsEffect(CanvasPortGlow(self.item1.getPortType(), self.toGraphicsObject()))
            else:
                self.setGraphicsEffect(None)
        self.m_lineSelected = yesno
        self.updateLineGradient()
    def updateLinePos(self):
        if self.item1.getPortMode() == PORT_MODE_OUTPUT:
            item1_x = self.item1.scenePos().x() + self.item1.getPortWidth() + 12
            item1_y = self.item1.scenePos().y() + float(canvas.theme.port_height)/2
            item2_x = self.item2.scenePos().x()
            item2_y = self.item2.scenePos().y() + float(canvas.theme.port_height)/2
            item1_mid_x = abs(item1_x - item2_x) / 2
            item1_new_x = item1_x + item1_mid_x
            item2_mid_x = abs(item1_x - item2_x) / 2
            item2_new_x = item2_x - item2_mid_x
            path = QPainterPath(QPointF(item1_x, item1_y))
            path.cubicTo(item1_new_x, item1_y, item2_new_x, item2_y, item2_x, item2_y)
            self.setPath(path)
            self.m_lineSelected = False
            self.updateLineGradient()
    def type(self):
        return CanvasBezierLineType
    def updateLineGradient(self):
        pos_top = self.boundingRect().top()
        pos_bot = self.boundingRect().bottom()
        if self.item2.scenePos().y() >= self.item1.scenePos().y():
            pos1 = 0
            pos2 = 1
        else:
            pos1 = 1
            pos2 = 0
        port_type1 = self.item1.getPortType()
        port_type2 = self.item2.getPortType()
        port_gradient = QLinearGradient(0, pos_top, 0, pos_bot)
        if port_type1 == PORT_TYPE_AUDIO_JACK:
            port_gradient.setColorAt(pos1, canvas.theme.line_audio_jack_sel if self.m_lineSelected else canvas.theme.line_audio_jack)
        elif port_type1 == PORT_TYPE_MIDI_JACK:
            port_gradient.setColorAt(pos1, canvas.theme.line_midi_jack_sel if self.m_lineSelected else canvas.theme.line_midi_jack)
        elif port_type1 == PORT_TYPE_MIDI_A2J:
            port_gradient.setColorAt(pos1, canvas.theme.line_midi_a2j_sel if self.m_lineSelected else canvas.theme.line_midi_a2j)
        elif port_type1 == PORT_TYPE_MIDI_ALSA:
            port_gradient.setColorAt(pos1, canvas.theme.line_midi_alsa_sel if self.m_lineSelected else canvas.theme.line_midi_alsa)
        if port_type2 == PORT_TYPE_AUDIO_JACK:
            port_gradient.setColorAt(pos2, canvas.theme.line_audio_jack_sel if self.m_lineSelected else canvas.theme.line_audio_jack)
        elif port_type2 == PORT_TYPE_MIDI_JACK:
            port_gradient.setColorAt(pos2, canvas.theme.line_midi_jack_sel if self.m_lineSelected else canvas.theme.line_midi_jack)
        elif port_type2 == PORT_TYPE_MIDI_A2J:
            port_gradient.setColorAt(pos2, canvas.theme.line_midi_a2j_sel if self.m_lineSelected else canvas.theme.line_midi_a2j)
        elif port_type2 == PORT_TYPE_MIDI_ALSA:
            port_gradient.setColorAt(pos2, canvas.theme.line_midi_alsa_sel if self.m_lineSelected else canvas.theme.line_midi_alsa)
        self.setPen(QPen(port_gradient, 2))
    def paint(self, painter, option, widget):
        painter.save()
        painter.setRenderHint(QPainter.Antialiasing, bool(options.antialiasing))
        QGraphicsPathItem.paint(self, painter, option, widget)
        painter.restore()
# ------------------------------------------------------------------------------
# canvaslivemov.cpp
class CanvasLineMov(QGraphicsLineItem):
    def __init__(self, port_mode, port_type, parent):
        QGraphicsLineItem.__init__(self, parent)
        self.m_port_mode = port_mode
        self.m_port_type = port_type
        # Port position doesn't change while moving around line
        self.p_lineX = self.scenePos().x()
        self.p_lineY = self.scenePos().y()
        self.p_width = self.parentItem().getPortWidth()
        if port_type == PORT_TYPE_AUDIO_JACK:
            pen = QPen(canvas.theme.line_audio_jack, 2)
        elif port_type == PORT_TYPE_MIDI_JACK:
            pen = QPen(canvas.theme.line_midi_jack, 2)
        elif port_type == PORT_TYPE_MIDI_A2J:
            pen = QPen(canvas.theme.line_midi_a2j, 2)
        elif port_type == PORT_TYPE_MIDI_ALSA:
            pen = QPen(canvas.theme.line_midi_alsa, 2)
        else:
            qWarning("PatchCanvas::CanvasLineMov(%s, %s, %s) - invalid port type" % (port_mode2str(port_mode), port_type2str(port_type), parent))
            pen = QPen(Qt.black)
        self.setPen(pen)
    def deleteFromScene(self):
        canvas.scene.removeItem(self)
        del self
    def updateLinePos(self, scenePos):
        item_pos = [0, 0]
        if self.m_port_mode == PORT_MODE_INPUT:
            item_pos[0] = 0
            item_pos[1] = float(canvas.theme.port_height)/2
        elif self.m_port_mode == PORT_MODE_OUTPUT:
            item_pos[0] = self.p_width + 12
            item_pos[1] = float(canvas.theme.port_height)/2
        else:
            return
        line = QLineF(item_pos[0], item_pos[1], scenePos.x() - self.p_lineX, scenePos.y() - self.p_lineY)
        self.setLine(line)
    def type(self):
        return CanvasLineMovType
    def paint(self, painter, option, widget):
        painter.save()
        painter.setRenderHint(QPainter.Antialiasing, bool(options.antialiasing))
        QGraphicsLineItem.paint(self, painter, option, widget)
        painter.restore()
# ------------------------------------------------------------------------------
# canvasbezierlinemov.cpp
class CanvasBezierLineMov(QGraphicsPathItem):
    def __init__(self, port_mode, port_type, parent):
        QGraphicsPathItem.__init__(self, parent)
        self.m_port_mode = port_mode
        self.m_port_type = port_type
        # Port position doesn't change while moving around line
        self.p_itemX = self.scenePos().x()
        self.p_itemY = self.scenePos().y()
        self.p_width = self.parentItem().getPortWidth()
        if port_type == PORT_TYPE_AUDIO_JACK:
            pen = QPen(canvas.theme.line_audio_jack, 2)
        elif port_type == PORT_TYPE_MIDI_JACK:
            pen = QPen(canvas.theme.line_midi_jack, 2)
        elif port_type == PORT_TYPE_MIDI_A2J:
            pen = QPen(canvas.theme.line_midi_a2j, 2)
        elif port_type == PORT_TYPE_MIDI_ALSA:
            pen = QPen(canvas.theme.line_midi_alsa, 2)
        else:
            qWarning("PatchCanvas::CanvasBezierLineMov(%s, %s, %s) - invalid port type" % (port_mode2str(port_mode), port_type2str(port_type), parent))
            pen = QPen(Qt.black)
        self.setBrush(QColor(0, 0, 0, 0))
        self.setPen(pen)
    def deleteFromScene(self):
        canvas.scene.removeItem(self)
        del self
    def updateLinePos(self, scenePos):
        if self.m_port_mode == PORT_MODE_INPUT:
            old_x = 0
            old_y = float(canvas.theme.port_height)/2
            mid_x = abs(scenePos.x() - self.p_itemX) / 2
            new_x = old_x - mid_x
        elif self.m_port_mode == PORT_MODE_OUTPUT:
            old_x = self.p_width + 12
            old_y = float(canvas.theme.port_height)/2
            mid_x = abs(scenePos.x() - (self.p_itemX + old_x)) / 2
            new_x = old_x + mid_x
        else:
            return
        final_x = scenePos.x() - self.p_itemX
        final_y = scenePos.y() - self.p_itemY
        path = QPainterPath(QPointF(old_x, old_y))
        path.cubicTo(new_x, old_y, new_x, final_y, final_x, final_y)
        self.setPath(path)
    def type(self):
        return CanvasBezierLineMovType
    def paint(self, painter, option, widget):
        painter.save()
        painter.setRenderHint(QPainter.Antialiasing, bool(options.antialiasing))
        QGraphicsPathItem.paint(self, painter, option, widget)
        painter.restore()
# ------------------------------------------------------------------------------
# canvasport.cpp
class CanvasPort(QGraphicsItem):
    def __init__(self, port_id, port_name, port_mode, port_type, parent):
        QGraphicsItem.__init__(self, parent)
        # Save Variables, useful for later
        self.m_port_id = port_id
        self.m_port_mode = port_mode
        self.m_port_type = port_type
        self.m_port_name = port_name
        # Base Variables
        self.m_port_width  = 15
        self.m_port_height = canvas.theme.port_height
        self.m_port_font = QFont(canvas.theme.port_font_name, canvas.theme.port_font_size, canvas.theme.port_font_state)
        self.m_line_mov = None
        self.m_hover_item = None
        self.m_last_selected_state = False
        self.m_mouse_down = False
        self.m_cursor_moving = False
        self.setFlags(QGraphicsItem.ItemIsSelectable)
    def getPortId(self):
        return self.m_port_id
    def getPortMode(self):
        return self.m_port_mode
    def getPortType(self):
        return self.m_port_type
    def getPortName(self):
        return self.m_port_name
    def getFullPortName(self):
        return self.parentItem().getGroupName() + ":" + self.m_port_name
    def getPortWidth(self):
        return self.m_port_width
    def getPortHeight(self):
        return self.m_port_height
    def setPortMode(self, port_mode):
        self.m_port_mode = port_mode
        self.update()
    def setPortType(self, port_type):
        self.m_port_type = port_type
        self.update()
    def setPortName(self, port_name):
        if QFontMetrics(self.m_port_font).width(port_name) < QFontMetrics(self.m_port_font).width(self.m_port_name):
            QTimer.singleShot(0, canvas.scene.update)
        self.m_port_name = port_name
        self.update()
    def setPortWidth(self, port_width):
        if port_width < self.m_port_width:
            QTimer.singleShot(0, canvas.scene.update)
        self.m_port_width = port_width
        self.update()
    def type(self):
        return CanvasPortType
    def mousePressEvent(self, event):
        self.m_hover_item = None
        self.m_mouse_down = bool(event.button() == Qt.LeftButton)
        self.m_cursor_moving = False
        QGraphicsItem.mousePressEvent(self, event)
    def mouseMoveEvent(self, event):
        if self.m_mouse_down:
            if not self.m_cursor_moving:
                self.setCursor(QCursor(Qt.CrossCursor))
                self.m_cursor_moving = True
                for connection in canvas.connection_list:
                    if connection.port_out_id == self.m_port_id or connection.port_in_id == self.m_port_id:
                        connection.widget.setLocked(True)
            if not self.m_line_mov:
                if options.use_bezier_lines:
                    self.m_line_mov = CanvasBezierLineMov(self.m_port_mode, self.m_port_type, self)
                else:
                    self.m_line_mov = CanvasLineMov(self.m_port_mode, self.m_port_type, self)
                canvas.last_z_value += 1
                self.m_line_mov.setZValue(canvas.last_z_value)
                canvas.last_z_value += 1
                self.parentItem().setZValue(canvas.last_z_value)
            item = None
            items = canvas.scene.items(event.scenePos(), Qt.ContainsItemShape, Qt.AscendingOrder)
            for i in range(len(items)):
                if items[i].type() == CanvasPortType:
                    if items[i] != self:
                        if not item:
                            item = items[i]
                        elif items[i].parentItem().zValue() > item.parentItem().zValue():
                            item = items[i]
            if self.m_hover_item and self.m_hover_item != item:
                self.m_hover_item.setSelected(False)
            if item:
                a2j_connection = bool(item.getPortType() == PORT_TYPE_MIDI_JACK and self.m_port_type == PORT_TYPE_MIDI_A2J) or (item.getPortType() == PORT_TYPE_MIDI_A2J and self.m_port_type == PORT_TYPE_MIDI_JACK)
                if item.getPortMode() != self.m_port_mode and (item.getPortType() == self.m_port_type or a2j_connection):
                    item.setSelected(True)
                    self.m_hover_item = item
                else:
                    self.m_hover_item = None
            else:
                self.m_hover_item = None
            self.m_line_mov.updateLinePos(event.scenePos())
            return event.accept()
        QGraphicsItem.mouseMoveEvent(self, event)
    def mouseReleaseEvent(self, event):
        if self.m_mouse_down:
            if self.m_line_mov:
                self.m_line_mov.deleteFromScene()
                self.m_line_mov = None
            for connection in canvas.connection_list:
                if connection.port_out_id == self.m_port_id or connection.port_in_id == self.m_port_id:
                    connection.widget.setLocked(False)
            if self.m_hover_item:
                check = False
                for connection in canvas.connection_list:
                    if ( (connection.port_out_id == self.m_port_id and connection.port_in_id == self.m_hover_item.getPortId()) or
                         (connection.port_out_id == self.m_hover_item.getPortId() and connection.port_in_id == self.m_port_id) ):
                        canvas.callback(ACTION_PORTS_DISCONNECT, connection.connection_id, 0, "")
                        check = True
                        break
                if not check:
                    if self.m_port_mode == PORT_MODE_OUTPUT:
                        canvas.callback(ACTION_PORTS_CONNECT, self.m_port_id, self.m_hover_item.getPortId(), "")
                    else:
                        canvas.callback(ACTION_PORTS_CONNECT, self.m_hover_item.getPortId(), self.m_port_id, "")
                canvas.scene.clearSelection()
        if self.m_cursor_moving:
            self.setCursor(QCursor(Qt.ArrowCursor))
        self.m_hover_item = None
        self.m_mouse_down = False
        self.m_cursor_moving = False
        QGraphicsItem.mouseReleaseEvent(self, event)
    def contextMenuEvent(self, event):
        canvas.scene.clearSelection()
        self.setSelected(True)
        menu = QMenu()
        discMenu = QMenu("Disconnect", menu)
        port_con_list = CanvasGetPortConnectionList(self.m_port_id)
        if len(port_con_list) > 0:
            for port_id in port_con_list:
                port_con_id = CanvasGetConnectedPort(port_id, self.m_port_id)
                act_x_disc = discMenu.addAction(CanvasGetFullPortName(port_con_id))
                act_x_disc.setData(port_id)
                act_x_disc.triggered.connect(canvas.qobject.PortContextMenuDisconnect)
        else:
            act_x_disc = discMenu.addAction("No connections")
            act_x_disc.setEnabled(False)
        menu.addMenu(discMenu)
        act_x_disc_all = menu.addAction("Disconnect &All")
        act_x_sep_1 = menu.addSeparator()
        act_x_info = menu.addAction("Get &Info")
        act_x_rename = menu.addAction("&Rename")
        if not features.port_info:
            act_x_info.setVisible(False)
        if not features.port_rename:
            act_x_rename.setVisible(False)
        if not (features.port_info and features.port_rename):
            act_x_sep_1.setVisible(False)
        act_selected = menu.exec_(event.screenPos())
        if act_selected == act_x_disc_all:
            for port_id in port_con_list:
                canvas.callback(ACTION_PORTS_DISCONNECT, port_id, 0, "")
        elif act_selected == act_x_info:
            canvas.callback(ACTION_PORT_INFO, self.m_port_id, 0, "")
        elif act_selected == act_x_rename:
            new_name_try = QInputDialog.getText(None, "Rename Port", "New name:", QLineEdit.Normal, self.m_port_name)
            if new_name_try[1] and new_name_try[0]: # 1 - bool ok, 0 - return text
                canvas.callback(ACTION_PORT_RENAME, self.m_port_id, 0, new_name_try[0])
        event.accept()
    def boundingRect(self):
        return QRectF(0, 0, self.m_port_width + 12, self.m_port_height)
    def paint(self, painter, option, widget):
        painter.save()
        painter.setRenderHint(QPainter.Antialiasing, bool(options.antialiasing == ANTIALIASING_FULL))
        poly_locx = [0, 0, 0, 0, 0]
        if self.m_port_mode == PORT_MODE_INPUT:
            text_pos = QPointF(3, canvas.theme.port_text_ypos)
            if canvas.theme.port_mode == Theme.THEME_PORT_POLYGON:
                poly_locx[0] = 0
                poly_locx[1] = self.m_port_width + 5
                poly_locx[2] = self.m_port_width + 12
                poly_locx[3] = self.m_port_width + 5
                poly_locx[4] = 0
            elif canvas.theme.port_mode == Theme.THEME_PORT_SQUARE:
                poly_locx[0] = 0
                poly_locx[1] = self.m_port_width + 5
                poly_locx[2] = self.m_port_width + 5
                poly_locx[3] = self.m_port_width + 5
                poly_locx[4] = 0
            else:
                qCritical("PatchCanvas::CanvasPort.paint() - invalid theme port mode '%s'" % canvas.theme.port_mode)
                return
        elif self.m_port_mode == PORT_MODE_OUTPUT:
            text_pos = QPointF(9, canvas.theme.port_text_ypos)
            if canvas.theme.port_mode == Theme.THEME_PORT_POLYGON:
                poly_locx[0] = self.m_port_width + 12
                poly_locx[1] = 7
                poly_locx[2] = 0
                poly_locx[3] = 7
                poly_locx[4] = self.m_port_width + 12
            elif canvas.theme.port_mode == Theme.THEME_PORT_SQUARE:
                poly_locx[0] = self.m_port_width + 12
                poly_locx[1] = 5
                poly_locx[2] = 5
                poly_locx[3] = 5
                poly_locx[4] = self.m_port_width + 12
            else:
                qCritical("PatchCanvas::CanvasPort.paint() - invalid theme port mode '%s'" % canvas.theme.port_mode)
                return
        else:
            qCritical("PatchCanvas::CanvasPort.paint() - invalid port mode '%s'" % port_mode2str(self.m_port_mode))
            return
        if self.m_port_type == PORT_TYPE_AUDIO_JACK:
            poly_color = canvas.theme.port_audio_jack_bg_sel if self.isSelected() else canvas.theme.port_audio_jack_bg
            poly_pen = canvas.theme.port_audio_jack_pen_sel  if self.isSelected() else canvas.theme.port_audio_jack_pen
            text_pen = canvas.theme.port_audio_jack_text_sel if self.isSelected() else canvas.theme.port_audio_jack_text
            conn_pen = canvas.theme.port_audio_jack_pen_sel
        elif self.m_port_type == PORT_TYPE_MIDI_JACK:
            poly_color = canvas.theme.port_midi_jack_bg_sel if self.isSelected() else canvas.theme.port_midi_jack_bg
            poly_pen = canvas.theme.port_midi_jack_pen_sel  if self.isSelected() else canvas.theme.port_midi_jack_pen
            text_pen = canvas.theme.port_midi_jack_text_sel if self.isSelected() else canvas.theme.port_midi_jack_text
            conn_pen = canvas.theme.port_midi_jack_pen_sel
        elif self.m_port_type == PORT_TYPE_MIDI_A2J:
            poly_color = canvas.theme.port_midi_a2j_bg_sel if self.isSelected() else canvas.theme.port_midi_a2j_bg
            poly_pen = canvas.theme.port_midi_a2j_pen_sel  if self.isSelected() else canvas.theme.port_midi_a2j_pen
            text_pen = canvas.theme.port_midi_a2j_text_sel if self.isSelected() else canvas.theme.port_midi_a2j_text
            conn_pen = canvas.theme.port_midi_a2j_pen_sel
        elif self.m_port_type == PORT_TYPE_MIDI_ALSA:
            poly_color = canvas.theme.port_midi_alsa_bg_sel if self.isSelected() else canvas.theme.port_midi_alsa_bg
            poly_pen = canvas.theme.port_midi_alsa_pen_sel  if self.isSelected() else canvas.theme.port_midi_alsa_pen
            text_pen = canvas.theme.port_midi_alsa_text_sel if self.isSelected() else canvas.theme.port_midi_alsa_text
            conn_pen = canvas.theme.port_midi_alsa_pen_sel
        else:
            qCritical("PatchCanvas::CanvasPort.paint() - invalid port type '%s'" % port_type2str(self.m_port_type))
            return
        polygon  = QPolygonF()
        polygon += QPointF(poly_locx[0], 0)
        polygon += QPointF(poly_locx[1], 0)
        polygon += QPointF(poly_locx[2], float(canvas.theme.port_height)/2)
        polygon += QPointF(poly_locx[3], canvas.theme.port_height)
        polygon += QPointF(poly_locx[4], canvas.theme.port_height)
        if canvas.theme.port_bg_pixmap:
            portRect = polygon.boundingRect()
            portPos  = portRect.topLeft()
            painter.drawTiledPixmap(portRect, canvas.theme.port_bg_pixmap, portPos)
        else:
            painter.setBrush(poly_color)
        painter.setPen(poly_pen)
        painter.drawPolygon(polygon)
        painter.setPen(text_pen)
        painter.setFont(self.m_port_font)
        painter.drawText(text_pos, self.m_port_name)
        if self.isSelected() != self.m_last_selected_state:
            for connection in canvas.connection_list:
                if connection.port_out_id == self.m_port_id or connection.port_in_id == self.m_port_id:
                    connection.widget.setLineSelected(self.isSelected())
        if canvas.theme.idx == Theme.THEME_OOSTUDIO and canvas.theme.port_bg_pixmap:
            painter.setPen(Qt.NoPen)
            painter.setBrush(conn_pen.brush())
            if self.m_port_mode == PORT_MODE_INPUT:
                connRect = QRectF(portRect.topLeft(), QSizeF(2, portRect.height()))
            else:
                connRect = QRectF(QPointF(portRect.right()-2, portRect.top()), QSizeF(2, portRect.height()))
            painter.drawRect(connRect)
        self.m_last_selected_state = self.isSelected()
        painter.restore()
# ------------------------------------------------------------------------------
# canvasbox.cpp
class cb_line_t(object):
    __slots__ = [
        'line',
        'connection_id'
    ]
class CanvasBox(QGraphicsItem):
    def __init__(self, group_id, group_name, icon, parent=None):
        QGraphicsItem.__init__(self, parent)
        # Save Variables, useful for later
        self.m_group_id   = group_id
        self.m_group_name = group_name
        # Base Variables
        self.p_width  = 50
        self.p_height = canvas.theme.box_header_height + canvas.theme.box_header_spacing + 1
        self.m_last_pos = QPointF()
        self.m_splitted = False
        self.m_splitted_mode = PORT_MODE_NULL
        self.m_cursor_moving = False
        self.m_forced_split = False
        self.m_mouse_down = False
        self.m_port_list_ids = []
        self.m_connection_lines = []
        # Set Font
        self.m_font_name = QFont(canvas.theme.box_font_name, canvas.theme.box_font_size, canvas.theme.box_font_state)
        self.m_font_port = QFont(canvas.theme.port_font_name, canvas.theme.port_font_size, canvas.theme.port_font_state)
        # Icon
        if canvas.theme.box_use_icon:
            self.icon_svg = CanvasIcon(icon, self.m_group_name, self)
        else:
            self.icon_svg = None
        # Shadow
        if options.eyecandy:
            self.shadow = CanvasBoxShadow(self.toGraphicsObject())
            self.shadow.setFakeParent(self)
            self.setGraphicsEffect(self.shadow)
        else:
            self.shadow = None
        # Final touches
        self.setFlags(QGraphicsItem.ItemIsMovable | QGraphicsItem.ItemIsSelectable)
        # Wait for at least 1 port
        if options.auto_hide_groups:
            self.setVisible(False)
        self.updatePositions()
        canvas.scene.addItem(self)
    def getGroupId(self):
        return self.m_group_id
    def getGroupName(self):
        return self.m_group_name
    def isSplitted(self):
        return self.m_splitted
    def getSplittedMode(self):
        return self.m_splitted_mode
    def getPortCount(self):
        return len(self.m_port_list_ids)
    def getPortList(self):
        return self.m_port_list_ids
    def setIcon(self, icon):
        if self.icon_svg:
            self.icon_svg.setIcon(icon, self.m_group_name)
    def setSplit(self, split, mode=PORT_MODE_NULL):
        self.m_splitted = split
        self.m_splitted_mode = mode
    def setGroupName(self, group_name):
        self.m_group_name = group_name
        self.updatePositions()
    def setShadowOpacity(self, opacity):
        if self.shadow:
            self.shadow.setOpacity(opacity)
    def addPortFromGroup(self, port_id, port_mode, port_type, port_name):
        if len(self.m_port_list_ids) == 0:
            if options.auto_hide_groups:
                if options.eyecandy == EYECANDY_FULL:
                    CanvasItemFX(self, True)
                self.setVisible(True)
        new_widget = CanvasPort(port_id, port_name, port_mode, port_type, self)
        port_dict = port_dict_t()
        port_dict.group_id = self.m_group_id
        port_dict.port_id = port_id
        port_dict.port_name = port_name
        port_dict.port_mode = port_mode
        port_dict.port_type = port_type
        port_dict.widget = new_widget
        self.m_port_list_ids.append(port_id)
        return new_widget
    def removePortFromGroup(self, port_id):
        if port_id in self.m_port_list_ids:
            self.m_port_list_ids.remove(port_id)
        else:
            qCritical("PatchCanvas::CanvasBox.removePort(%i) - unable to find port to remove" % port_id)
            return
        if len(self.m_port_list_ids) > 0:
            self.updatePositions()
        elif self.isVisible():
            if options.auto_hide_groups:
                if options.eyecandy == EYECANDY_FULL:
                    CanvasItemFX(self, False)
                else:
                    self.setVisible(False)
    def addLineFromGroup(self, line, connection_id):
        new_cbline = cb_line_t()
        new_cbline.line = line
        new_cbline.connection_id = connection_id
        self.m_connection_lines.append(new_cbline)
    def removeLineFromGroup(self, connection_id):
        for connection in self.m_connection_lines:
            if connection.connection_id == connection_id:
                self.m_connection_lines.remove(connection)
                return
        qCritical("PatchCanvas::CanvasBox.removeLineFromGroup(%i) - unable to find line to remove" % connection_id)
    def checkItemPos(self):
        if not canvas.size_rect.isNull():
            pos = self.scenePos()
            if not (canvas.size_rect.contains(pos) and canvas.size_rect.contains(pos + QPointF(self.p_width, self.p_height))):
                if pos.x() < canvas.size_rect.x():
                    self.setPos(canvas.size_rect.x(), pos.y())
                elif pos.x() + self.p_width > canvas.size_rect.width():
                    self.setPos(canvas.size_rect.width() - self.p_width, pos.y())
                pos = self.scenePos()
                if pos.y() < canvas.size_rect.y():
                    self.setPos(pos.x(), canvas.size_rect.y())
                elif pos.y() + self.p_height > canvas.size_rect.height():
                    self.setPos(pos.x(), canvas.size_rect.height() - self.p_height)
    def removeIconFromScene(self):
        if self.icon_svg:
            canvas.scene.removeItem(self.icon_svg)
    def updatePositions(self):
        self.prepareGeometryChange()
        max_in_width  = 0
        max_in_height = canvas.theme.box_header_height + canvas.theme.box_header_spacing
        max_out_width  = 0
        max_out_height = canvas.theme.box_header_height + canvas.theme.box_header_spacing
        port_spacing   = canvas.theme.port_height + canvas.theme.port_spacing
        have_audio_jack_in = have_midi_jack_in = have_midi_a2j_in = have_midi_alsa_in = False
        have_audio_jack_out = have_midi_jack_out = have_midi_a2j_out = have_midi_alsa_out = False
        # reset box size
        self.p_width  = 50
        self.p_height = canvas.theme.box_header_height + canvas.theme.box_header_spacing + 1
        # Check Text Name size
        app_name_size = QFontMetrics(self.m_font_name).width(self.m_group_name) + 30
        if app_name_size > self.p_width:
            self.p_width = app_name_size
        # Get Port List
        port_list = []
        for port in canvas.port_list:
            if port.port_id in self.m_port_list_ids:
                port_list.append(port)
        # Get Max Box Width/Height
        for port in port_list:
            if port.port_mode == PORT_MODE_INPUT:
                max_in_height += port_spacing
                size = QFontMetrics(self.m_font_port).width(port.port_name)
                if size > max_in_width:
                    max_in_width = size
                if port.port_type == PORT_TYPE_AUDIO_JACK and not have_audio_jack_in:
                    have_audio_jack_in = True
                    max_in_height += canvas.theme.port_spacingT
                elif port.port_type == PORT_TYPE_MIDI_JACK and not have_midi_jack_in:
                    have_midi_jack_in = True
                    max_in_height += canvas.theme.port_spacingT
                elif port.port_type == PORT_TYPE_MIDI_A2J and not have_midi_a2j_in:
                    have_midi_a2j_in = True
                    max_in_height += canvas.theme.port_spacingT
                elif port.port_type == PORT_TYPE_MIDI_ALSA and not have_midi_alsa_in:
                    have_midi_alsa_in = True
                    max_in_height += canvas.theme.port_spacingT
            elif port.port_mode == PORT_MODE_OUTPUT:
                max_out_height += port_spacing
                size = QFontMetrics(self.m_font_port).width(port.port_name)
                if size > max_out_width:
                    max_out_width = size
                if port.port_type == PORT_TYPE_AUDIO_JACK and not have_audio_jack_out:
                    have_audio_jack_out = True
                    max_out_height += canvas.theme.port_spacingT
                elif port.port_type == PORT_TYPE_MIDI_JACK and not have_midi_jack_out:
                    have_midi_jack_out = True
                    max_out_height += canvas.theme.port_spacingT
                elif port.port_type == PORT_TYPE_MIDI_A2J and not have_midi_a2j_out:
                    have_midi_a2j_out = True
                    max_out_height += canvas.theme.port_spacingT
                elif port.port_type == PORT_TYPE_MIDI_ALSA and not have_midi_alsa_out:
                    have_midi_alsa_out = True
                    max_out_height += canvas.theme.port_spacingT
        if canvas.theme.port_spacingT == 0:
            max_in_height  += 2
            max_out_height += 2
        final_width = 30 + max_in_width + max_out_width
        if final_width > self.p_width:
            self.p_width = final_width
        if max_in_height > self.p_height:
            self.p_height = max_in_height
        if max_out_height > self.p_height:
            self.p_height = max_out_height
        # Remove bottom space
        self.p_height -= canvas.theme.port_spacingT
        if canvas.theme.box_header_spacing > 0:
            if len(port_list) == 0:
                self.p_height -= canvas.theme.box_header_spacing
            else:
                self.p_height -= canvas.theme.box_header_spacing/2
        last_in_pos  = canvas.theme.box_header_height + canvas.theme.box_header_spacing
        last_out_pos = canvas.theme.box_header_height + canvas.theme.box_header_spacing
        last_in_type  = PORT_TYPE_NULL
        last_out_type = PORT_TYPE_NULL
        # Re-position ports, AUDIO_JACK
        for port in port_list:
            if port.port_type == PORT_TYPE_AUDIO_JACK:
                if port.port_mode == PORT_MODE_INPUT:
                    port.widget.setPos(QPointF(1 + canvas.theme.port_offset, last_in_pos))
                    port.widget.setPortWidth(max_in_width)
                    last_in_pos += port_spacing
                    last_in_type = port.port_type
                elif port.port_mode == PORT_MODE_OUTPUT:
                    port.widget.setPos(QPointF(self.p_width - max_out_width - canvas.theme.port_offset - 13, last_out_pos))
                    port.widget.setPortWidth(max_out_width)
                    last_out_pos += port_spacing
                    last_out_type = port.port_type
        # Re-position ports, MIDI_JACK
        for port in port_list:
            if port.port_type == PORT_TYPE_MIDI_JACK:
                if port.port_mode == PORT_MODE_INPUT:
                    if last_in_type != PORT_TYPE_NULL and port.port_type != last_in_type:
                        last_in_pos += canvas.theme.port_spacingT
                    port.widget.setPos(QPointF(1 + canvas.theme.port_offset, last_in_pos))
                    port.widget.setPortWidth(max_in_width)
                    last_in_pos += port_spacing
                    last_in_type = port.port_type
                elif port.port_mode == PORT_MODE_OUTPUT:
                    if last_out_type != PORT_TYPE_NULL and port.port_type != last_out_type:
                        last_out_pos += canvas.theme.port_spacingT
                    port.widget.setPos(QPointF(self.p_width - max_out_width - canvas.theme.port_offset - 13, last_out_pos))
                    port.widget.setPortWidth(max_out_width)
                    last_out_pos += port_spacing
                    last_out_type = port.port_type
        # Re-position ports, MIDI_A2J
        for port in port_list:
            if port.port_type == PORT_TYPE_MIDI_A2J:
                if port.port_mode == PORT_MODE_INPUT:
                    if last_in_type != PORT_TYPE_NULL and port.port_type != last_in_type:
                        last_in_pos += canvas.theme.port_spacingT
                    port.widget.setPos(QPointF(1 + canvas.theme.port_offset, last_in_pos))
                    port.widget.setPortWidth(max_in_width)
                    last_in_pos += port_spacing
                    last_in_type = port.port_type
                elif port.port_mode == PORT_MODE_OUTPUT:
                    if last_out_type != PORT_TYPE_NULL and port.port_type != last_out_type:
                        last_out_pos += canvas.theme.port_spacingT
                    port.widget.setPos(QPointF(self.p_width - max_out_width - canvas.theme.port_offset - 13, last_out_pos))
                    port.widget.setPortWidth(max_out_width)
                    last_out_pos += port_spacing
                    last_out_type = port.port_type
        # Re-position ports, MIDI_ALSA
        for port in port_list:
            if port.port_type == PORT_TYPE_MIDI_ALSA:
                if port.port_mode == PORT_MODE_INPUT:
                    if last_in_type != PORT_TYPE_NULL and port.port_type != last_in_type:
                        last_in_pos += canvas.theme.port_spacingT
                    port.widget.setPos(QPointF(1 + canvas.theme.port_offset, last_in_pos))
                    port.widget.setPortWidth(max_in_width)
                    last_in_pos += port_spacing
                    last_in_type = port.port_type
                elif port.port_mode == PORT_MODE_OUTPUT:
                    if last_out_type != PORT_TYPE_NULL and port.port_type != last_out_type:
                        last_out_pos += canvas.theme.port_spacingT
                    port.widget.setPos(QPointF(self.p_width - max_out_width - canvas.theme.port_offset - 13, last_out_pos))
                    port.widget.setPortWidth(max_out_width)
                    last_out_pos += port_spacing
                    last_out_type = port.port_type
        self.repaintLines(True)
        self.update()
    def repaintLines(self, forced=False):
        if self.pos() != self.m_last_pos or forced:
            for connection in self.m_connection_lines:
                connection.line.updateLinePos()
        self.m_last_pos = self.pos()
    def resetLinesZValue(self):
        for connection in canvas.connection_list:
            if connection.port_out_id in self.m_port_list_ids and connection.port_in_id in self.m_port_list_ids:
                z_value = canvas.last_z_value
            else:
                z_value = canvas.last_z_value - 1
            connection.widget.setZValue(z_value)
    def type(self):
        return CanvasBoxType
    def contextMenuEvent(self, event):
        menu = QMenu()
        discMenu = QMenu("Disconnect", menu)
        port_con_list = []
        port_con_list_ids = []
        for port_id in self.m_port_list_ids:
            tmp_port_con_list = CanvasGetPortConnectionList(port_id)
            for port_con_id in tmp_port_con_list:
                if port_con_id not in port_con_list:
                    port_con_list.append(port_con_id)
                    port_con_list_ids.append(port_id)
        if len(port_con_list) > 0:
            for i in range(len(port_con_list)):
                port_con_id = CanvasGetConnectedPort(port_con_list[i], port_con_list_ids[i])
                act_x_disc = discMenu.addAction(CanvasGetFullPortName(port_con_id))
                act_x_disc.setData(port_con_list[i])
                act_x_disc.triggered.connect(canvas.qobject.PortContextMenuDisconnect)
        else:
            act_x_disc = discMenu.addAction("No connections")
            act_x_disc.setEnabled(False)
        menu.addMenu(discMenu)
        act_x_disc_all = menu.addAction("Disconnect &All")
        act_x_sep1 = menu.addSeparator()
        act_x_info = menu.addAction("&Info")
        act_x_rename = menu.addAction("&Rename")
        act_x_sep2 = menu.addSeparator()
        act_x_split_join = menu.addAction("Join" if self.m_splitted else "Split")
        if not features.group_info:
            act_x_info.setVisible(False)
        if not features.group_rename:
            act_x_rename.setVisible(False)
        if not (features.group_info and features.group_rename):
            act_x_sep1.setVisible(False)
        haveIns = haveOuts = False
        for port in canvas.port_list:
            if port.port_id in self.m_port_list_ids:
                if port.port_mode == PORT_MODE_INPUT:
                    haveIns = True
                elif port.port_mode == PORT_MODE_OUTPUT:
                    haveOuts = True
        if not (self.m_splitted or bool(haveIns and haveOuts)):
            act_x_sep2.setVisible(False)
            act_x_split_join.setVisible(False)
        act_selected = menu.exec_(event.screenPos())
        if act_selected == act_x_disc_all:
            for port_id in port_con_list:
                canvas.callback(ACTION_PORTS_DISCONNECT, port_id, 0, "")
        elif act_selected == act_x_info:
            canvas.callback(ACTION_GROUP_INFO, self.m_group_id, 0, "")
        elif act_selected == act_x_rename:
            new_name_try = QInputDialog.getText(None, "Rename Group", "New name:", QLineEdit.Normal, self.m_group_name)
            if new_name_try[1] and new_name_try[0]: # 1 - bool ok, 0 - return text
                canvas.callback(ACTION_GROUP_RENAME, self.m_group_id, 0, new_name_try[0])
        elif act_selected == act_x_split_join:
            if self.m_splitted:
                canvas.callback(ACTION_GROUP_JOIN, self.m_group_id, 0, "")
            else:
                canvas.callback(ACTION_GROUP_SPLIT, self.m_group_id, 0, "")
        event.accept()
    def mousePressEvent(self, event):
        canvas.last_z_value += 1
        self.setZValue(canvas.last_z_value)
        self.resetLinesZValue()
        self.m_cursor_moving = False
        if event.button() == Qt.RightButton:
            canvas.scene.clearSelection()
            self.setSelected(True)
            self.m_mouse_down = False
            return event.accept()
        elif event.button() == Qt.LeftButton:
            if self.sceneBoundingRect().contains(event.scenePos()):
                self.m_mouse_down = True
            else:
                # Fix a weird Qt behaviour with right-click mouseMove
                self.m_mouse_down = False
                return event.ignore()
        else:
            self.m_mouse_down = False
        QGraphicsItem.mousePressEvent(self, event)
    def mouseMoveEvent(self, event):
        if self.m_mouse_down:
            if not self.m_cursor_moving:
                self.setCursor(QCursor(Qt.SizeAllCursor))
                self.m_cursor_moving = True
            self.repaintLines()
        QGraphicsItem.mouseMoveEvent(self, event)
    def mouseReleaseEvent(self, event):
        if self.m_cursor_moving:
            self.setCursor(QCursor(Qt.ArrowCursor))
        self.m_mouse_down = False
        self.m_cursor_moving = False
        QGraphicsItem.mouseReleaseEvent(self, event)
    def boundingRect(self):
        return QRectF(0, 0, self.p_width, self.p_height)
    def paint(self, painter, option, widget):
        painter.save()
        painter.setRenderHint(QPainter.Antialiasing, False)
        # Draw rectangle
        if self.isSelected():
            painter.setPen(canvas.theme.box_pen_sel)
        else:
            painter.setPen(canvas.theme.box_pen)
        if canvas.theme.box_bg_type == Theme.THEME_BG_GRADIENT:
            box_gradient = QLinearGradient(0, 0, 0, self.p_height)
            box_gradient.setColorAt(0, canvas.theme.box_bg_1)
            box_gradient.setColorAt(1, canvas.theme.box_bg_2)
            painter.setBrush(box_gradient)
        else:
            painter.setBrush(canvas.theme.box_bg_1)
        painter.drawRect(0, 0, self.p_width, self.p_height)
        # Draw pixmap header
        if canvas.theme.box_header_pixmap:
            painter.setPen(Qt.NoPen)
            painter.setBrush(canvas.theme.box_bg_2)
            painter.drawRect(1, 1, self.p_width-2, canvas.theme.box_header_height)
            headerPos  = QPointF(1, 1)
            headerRect = QRectF(2, 2, self.p_width-4, canvas.theme.box_header_height-3)
            painter.drawTiledPixmap(headerRect, canvas.theme.box_header_pixmap, headerPos)
        # Draw text
        painter.setFont(self.m_font_name)
        if self.isSelected():
            painter.setPen(canvas.theme.box_text_sel)
        else:
            painter.setPen(canvas.theme.box_text)
        if canvas.theme.box_use_icon:
            textPos = QPointF(25, canvas.theme.box_text_ypos)
        else:
            appNameSize = QFontMetrics(self.m_font_name).width(self.m_group_name)
            rem = self.p_width - appNameSize
            textPos = QPointF(rem/2, canvas.theme.box_text_ypos)
        painter.drawText(textPos, self.m_group_name)
        self.repaintLines()
        painter.restore()
# ------------------------------------------------------------------------------
# canvasicon.cpp
class CanvasIcon(QGraphicsSvgItem):
    def __init__(self, icon, name, parent):
        QGraphicsSvgItem.__init__(self, parent)
        self.m_renderer = None
        self.p_size = QRectF(0, 0, 0, 0)
        self.m_colorFX = QGraphicsColorizeEffect(self)
        self.m_colorFX.setColor(canvas.theme.box_text.color())
        self.setGraphicsEffect(self.m_colorFX)
        self.setIcon(icon, name)
    def setIcon(self, icon, name):
        name = name.lower()
        icon_path = ""
        if icon == ICON_APPLICATION:
            self.p_size = QRectF(3, 2, 19, 18)
            if "audacious" in name:
                icon_path   = ":/scalable/pb_audacious.svg"
                self.p_size = QRectF(5, 4, 16, 16)
            elif "clementine" in name:
                icon_path = ":/scalable/pb_clementine.svg"
                self.p_size = QRectF(5, 4, 16, 16)
            elif "distrho" in name:
                icon_path = ":/scalable/pb_distrho.svg"
                self.p_size = QRectF(5, 4, 14, 14)
            elif "jamin" in name:
                icon_path = ":/scalable/pb_jamin.svg"
                self.p_size = QRectF(5, 3, 16, 16)
            elif "mplayer" in name:
                icon_path = ":/scalable/pb_mplayer.svg"
                self.p_size = QRectF(5, 4, 16, 16)
            elif "vlc" in name:
                icon_path = ":/scalable/pb_vlc.svg"
                self.p_size = QRectF(5, 3, 16, 16)
            else:
                icon_path = ":/scalable/pb_generic.svg"
                self.p_size = QRectF(4, 3, 16, 16)
        elif icon == ICON_HARDWARE:
            icon_path = ":/scalable/pb_hardware.svg"
            self.p_size = QRectF(5, 2, 16, 16)
        elif icon == ICON_DISTRHO:
            icon_path = ":/scalable/pb_distrho.svg"
            self.p_size = QRectF(5, 4, 14, 14)
        elif icon == ICON_FILE:
            icon_path = ":/scalable/pb_file.svg"
            self.p_size = QRectF(5, 4, 12, 14)
        elif icon == ICON_PLUGIN:
            icon_path = ":/scalable/pb_plugin.svg"
            self.p_size = QRectF(5, 4, 14, 14)
        elif icon == ICON_LADISH_ROOM:
            # TODO - make a unique ladish-room icon
            icon_path = ":/scalable/pb_hardware.svg"
            self.p_size = QRectF(5, 2, 16, 16)
        else:
            self.p_size = QRectF(0, 0, 0, 0)
            qCritical("PatchCanvas::CanvasIcon.setIcon(%s, %s) - unsupported icon requested" % (icon2str(icon), name.encode()))
            return
        self.m_renderer = QSvgRenderer(icon_path, canvas.scene)
        self.setSharedRenderer(self.m_renderer)
        self.update()
    def type(self):
        return CanvasIconType
    def boundingRect(self):
        return self.p_size
    def paint(self, painter, option, widget):
        if self.m_renderer:
            painter.save()
            painter.setRenderHint(QPainter.Antialiasing, False)
            painter.setRenderHint(QPainter.TextAntialiasing, False)
            self.m_renderer.render(painter, self.p_size)
            painter.restore()
        else:
            QGraphicsSvgItem.paint(self, painter, option, widget)
# ------------------------------------------------------------------------------
# canvasportglow.cpp
class CanvasPortGlow(QGraphicsDropShadowEffect):
    def __init__(self, port_type, parent):
        QGraphicsDropShadowEffect.__init__(self, parent)
        self.setBlurRadius(12)
        self.setOffset(0, 0)
        if port_type == PORT_TYPE_AUDIO_JACK:
            self.setColor(canvas.theme.line_audio_jack_glow)
        elif port_type == PORT_TYPE_MIDI_JACK:
            self.setColor(canvas.theme.line_midi_jack_glow)
        elif port_type == PORT_TYPE_MIDI_A2J:
            self.setColor(canvas.theme.line_midi_a2j_glow)
        elif port_type == PORT_TYPE_MIDI_ALSA:
            self.setColor(canvas.theme.line_midi_alsa_glow)
# ------------------------------------------------------------------------------
# canvasboxshadow.cpp
class CanvasBoxShadow(QGraphicsDropShadowEffect):
    def __init__(self, parent):
        QGraphicsDropShadowEffect.__init__(self, parent)
        self.m_fakeParent = None
        self.setBlurRadius(20)
        self.setColor(canvas.theme.box_shadow)
        self.setOffset(0, 0)
    def setFakeParent(self, fakeParent):
        self.m_fakeParent = fakeParent
    def setOpacity(self, opacity):
        color = QColor(canvas.theme.box_shadow)
        color.setAlphaF(opacity)
        self.setColor(color)
    def draw(self, painter):
        if self.m_fakeParent:
            self.m_fakeParent.repaintLines()
        QGraphicsDropShadowEffect.draw(self, painter)
 |