2,641 changes: 0 additions & 2,641 deletions src/patchcanvas.py

This file was deleted.

287 changes: 287 additions & 0 deletions src/patchcanvas/__init__.py
@@ -0,0 +1,287 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

# PatchBay Canvas engine using QGraphicsView/Scene
# Copyright (C) 2010-2019 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 doc/GPL.txt file.

# ------------------------------------------------------------------------------------------------------------
# Imports (Global)

from PyQt5.QtCore import QPointF, QRectF
from PyQt5.QtWidgets import QGraphicsItem

# ------------------------------------------------------------------------------------------------------------
# Imports (Theme)

from .theme import getDefaultThemeName

# ------------------------------------------------------------------------------------------------------------

# Maximum Id for a plugin, treated as invalid/zero if above this value
MAX_PLUGIN_ID_ALLOWED = 0x7FF

# 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_ALSA = 3
PORT_TYPE_PARAMETER = 4

# Callback Action
ACTION_GROUP_INFO = 0 # group_id, N, N
ACTION_GROUP_RENAME = 1 # group_id, N, N
ACTION_GROUP_SPLIT = 2 # group_id, N, N
ACTION_GROUP_JOIN = 3 # group_id, N, N
ACTION_GROUP_POSITION = 4 # group_id, N, N, "x1:y1:x2:y2"
ACTION_PORT_INFO = 5 # group_id, port_id, N
ACTION_PORT_RENAME = 6 # group_id, port_id, N
ACTION_PORTS_CONNECT = 7 # N, N, "outG:outP:inG:inP"
ACTION_PORTS_DISCONNECT = 8 # conn_id, N, N
ACTION_PLUGIN_CLONE = 9 # plugin_id, N, N
ACTION_PLUGIN_EDIT = 10 # plugin_id, N, N
ACTION_PLUGIN_RENAME = 11 # plugin_id, N, N
ACTION_PLUGIN_REPLACE = 12 # plugin_id, N, N
ACTION_PLUGIN_REMOVE = 13 # plugin_id, N, N
ACTION_PLUGIN_SHOW_UI = 14 # plugin_id, N, N
ACTION_BG_RIGHT_CLICK = 15 # N, N, N
ACTION_INLINE_DISPLAY = 16 # plugin_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

# ------------------------------------------------------------------------------------------------------------

# 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
CanvasRubberbandType = QGraphicsItem.UserType + 8

# ------------------------------------------------------------------------------------------------------------

# Canvas options
class options_t(object):
__slots__ = [
'theme_name',
'auto_hide_groups',
'auto_select_items',
'use_bezier_lines',
'antialiasing',
'eyecandy',
'inline_displays'
]

# Canvas features
class features_t(object):
__slots__ = [
'group_info',
'group_rename',
'port_info',
'port_rename',
'handle_group_pos'
]

# Main Canvas object
class Canvas(object):
def __init__(self):
self.qobject = None
self.settings = None
self.theme = None
self.initiated = False

self.group_list = []
self.port_list = []
self.connection_list = []
self.animation_list = []
self.group_plugin_map = {}
self.old_group_pos = {}

self.callback = self.callback
self.debug = False
self.scene = None
self.last_z_value = 0
self.last_connection_id = 0
self.initial_pos = QPointF(0, 0)
self.size_rect = QRectF()

def callback(self, action, value1, value2, value_str):
print("Canvas::callback({}, {}, {}, {})".format(action, value1, value2, value_str))

# ------------------------------------------------------------------------------------------------------------

# object lists
class group_dict_t(object):
__slots__ = [
'group_id',
'group_name',
'split',
'icon',
'plugin_id',
'plugin_ui',
'plugin_inline',
'widgets'
]

class port_dict_t(object):
__slots__ = [
'group_id',
'port_id',
'port_name',
'port_mode',
'port_type',
'is_alternate',
'widget'
]

class connection_dict_t(object):
__slots__ = [
'connection_id',
'group_in_id',
'port_in_id',
'group_out_id',
'port_out_id',
'widget'
]

class animation_dict_t(object):
__slots__ = [
'animation',
'item'
]

# ------------------------------------------------------------------------------------------------------------

# 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_ALSA:
return "PORT_TYPE_MIDI_ALSA"
elif port_type == PORT_TYPE_PARAMETER:
return "PORT_TYPE_MIDI_PARAMETER"
else:
return "PORT_TYPE_???"

def icon2str(icon):
if icon == ICON_APPLICATION:
return "ICON_APPLICATION"
elif icon == ICON_HARDWARE:
return "ICON_HARDWARE"
elif icon == ICON_DISTRHO:
return "ICON_DISTRHO"
elif icon == ICON_FILE:
return "ICON_FILE"
elif icon == ICON_PLUGIN:
return "ICON_PLUGIN"
elif icon == 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_???"

# ------------------------------------------------------------------------------------------------------------

# Global objects
canvas = Canvas()

options = options_t()
options.theme_name = getDefaultThemeName()
options.auto_hide_groups = False
options.auto_select_items = False
options.use_bezier_lines = True
options.antialiasing = ANTIALIASING_SMALL
options.eyecandy = EYECANDY_SMALL
options.inline_displays = False

features = features_t()
features.group_info = False
features.group_rename = False
features.port_info = False
features.port_rename = False
features.handle_group_pos = False

# 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.auto_select_items = new_options.auto_select_items
options.use_bezier_lines = new_options.use_bezier_lines
options.antialiasing = new_options.antialiasing
options.eyecandy = new_options.eyecandy
options.inline_displays = new_options.inline_displays

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
164 changes: 164 additions & 0 deletions src/patchcanvas/canvasbezierline.py
@@ -0,0 +1,164 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

# PatchBay Canvas engine using QGraphicsView/Scene
# Copyright (C) 2010-2019 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 doc/GPL.txt file.

# ------------------------------------------------------------------------------------------------------------
# Imports (Global)

from PyQt5.QtCore import Qt, QPointF
from PyQt5.QtGui import QColor, QLinearGradient, QPainter, QPainterPath, QPen
from PyQt5.QtWidgets import QGraphicsPathItem

# ------------------------------------------------------------------------------------------------------------
# Imports (Custom)

from . import (
canvas,
options,
CanvasBezierLineType,
ACTION_PORTS_DISCONNECT,
EYECANDY_FULL,
PORT_MODE_OUTPUT,
PORT_TYPE_AUDIO_JACK,
PORT_TYPE_MIDI_ALSA,
PORT_TYPE_MIDI_JACK,
PORT_TYPE_PARAMETER,
)

from .canvasportglow import CanvasPortGlow

# ------------------------------------------------------------------------------------------------------------

class CanvasBezierLine(QGraphicsPathItem):
def __init__(self, item1, item2, parent):
QGraphicsPathItem.__init__(self)
self.setParentItem(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 isLocked(self):
return self.m_locked

def setLocked(self, yesno):
self.m_locked = yesno

def isLineSelected(self):
return self.m_lineSelected

def updateLineSelected(self):
if self.m_locked:
return

yesno = self.item1.isSelected() or self.item2.isSelected()
if yesno != self.m_lineSelected and 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 triggerDisconnect(self):
for connection in canvas.connection_list:
if (connection.port_out_id == self.item1.getPortId() and connection.port_in_id == self.item2.getPortId()):
canvas.callback(ACTION_PORTS_DISCONNECT, connection.connection_id, 0, "")
break

def updateLinePos(self):
if self.item1.getPortMode() == PORT_MODE_OUTPUT:
rect1 = self.item1.sceneBoundingRect()
rect2 = self.item2.sceneBoundingRect()

item1_x = rect1.right()
item2_x = rect2.left()
item1_y = rect1.top() + float(canvas.theme.port_height)/2
item2_y = rect2.top() + float(canvas.theme.port_height)/2
item1_new_x = item1_x + abs(item1_x - item2_x) / 2
item2_new_x = item2_x - abs(item1_x - item2_x) / 2

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_ALSA:
port_gradient.setColorAt(pos1, canvas.theme.line_midi_alsa_sel if self.m_lineSelected else canvas.theme.line_midi_alsa)
elif port_type1 == PORT_TYPE_PARAMETER:
port_gradient.setColorAt(pos1, canvas.theme.line_parameter_sel if self.m_lineSelected else canvas.theme.line_parameter)

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_ALSA:
port_gradient.setColorAt(pos2, canvas.theme.line_midi_alsa_sel if self.m_lineSelected else canvas.theme.line_midi_alsa)
elif port_type2 == PORT_TYPE_PARAMETER:
port_gradient.setColorAt(pos2, canvas.theme.line_parameter_sel if self.m_lineSelected else canvas.theme.line_parameter)

self.setPen(QPen(port_gradient, 2.00001, Qt.SolidLine, Qt.FlatCap))

def paint(self, painter, option, widget):
painter.save()
painter.setRenderHint(QPainter.Antialiasing, bool(options.antialiasing))

pen = self.pen()
cosm_pen = QPen(pen)
cosm_pen.setCosmetic(True)
cosm_pen.setWidthF(1.00001)

QGraphicsPathItem.paint(self, painter, option, widget)

painter.setPen(cosm_pen)
painter.setBrush(Qt.NoBrush)
painter.setOpacity(0.2)
painter.drawPath(self.path())

painter.restore()

# ------------------------------------------------------------------------------------------------------------
105 changes: 105 additions & 0 deletions src/patchcanvas/canvasbezierlinemov.py
@@ -0,0 +1,105 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

# PatchBay Canvas engine using QGraphicsView/Scene
# Copyright (C) 2010-2019 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 doc/GPL.txt file.

# ------------------------------------------------------------------------------------------------------------
# Imports (Global)

from PyQt5.QtCore import qWarning, Qt, QPointF
from PyQt5.QtGui import QPainter, QPainterPath, QPen
from PyQt5.QtWidgets import QGraphicsPathItem

# ------------------------------------------------------------------------------------------------------------
# Imports (Custom)

from . import (
canvas,
options,
port_mode2str,
port_type2str,
CanvasBezierLineMovType,
PORT_MODE_INPUT,
PORT_MODE_OUTPUT,
PORT_TYPE_AUDIO_JACK,
PORT_TYPE_MIDI_ALSA,
PORT_TYPE_MIDI_JACK,
PORT_TYPE_PARAMETER,
)

# ------------------------------------------------------------------------------------------------------------

class CanvasBezierLineMov(QGraphicsPathItem):
def __init__(self, port_mode, port_type, parent):
QGraphicsPathItem.__init__(self)
self.setParentItem(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 = parent.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_ALSA:
pen = QPen(canvas.theme.line_midi_alsa, 2)
elif port_type == PORT_TYPE_PARAMETER:
pen = QPen(canvas.theme.line_parameter, 2)
else:
qWarning("PatchCanvas::CanvasBezierLineMov({}, {}, {}) - invalid port type".format(
port_mode2str(port_mode), port_type2str(port_type), parent))
pen = QPen(Qt.black)

pen.setCapStyle(Qt.FlatCap)
pen.setWidthF(pen.widthF() + 0.00001)
self.setPen(pen)

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()

# ------------------------------------------------------------------------------------------------------------
789 changes: 789 additions & 0 deletions src/patchcanvas/canvasbox.py

Large diffs are not rendered by default.

55 changes: 55 additions & 0 deletions src/patchcanvas/canvasboxshadow.py
@@ -0,0 +1,55 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

# PatchBay Canvas engine using QGraphicsView/Scene
# Copyright (C) 2010-2019 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 doc/GPL.txt file.

# ------------------------------------------------------------------------------------------------------------
# Imports (Global)

from PyQt5.QtGui import QColor
from PyQt5.QtWidgets import QGraphicsDropShadowEffect

# ------------------------------------------------------------------------------------------------------------
# Imports (Custom)

from . import canvas

# ------------------------------------------------------------------------------------------------------------

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)

# ------------------------------------------------------------------------------------------------------------
90 changes: 90 additions & 0 deletions src/patchcanvas/canvasfadeanimation.py
@@ -0,0 +1,90 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

# PatchBay Canvas engine using QGraphicsView/Scene
# Copyright (C) 2010-2019 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 doc/GPL.txt file.

# ------------------------------------------------------------------------------------------------------------
# Imports (Global)

from PyQt5.QtCore import QAbstractAnimation
from PyQt5.QtWidgets import QGraphicsObject

# ------------------------------------------------------------------------------------------------------------
# Imports (Custom)

from . import canvas, CanvasBoxType

# ------------------------------------------------------------------------------------------------------------

class CanvasFadeAnimation(QAbstractAnimation):
def __init__(self, item, show):
QAbstractAnimation.__init__(self)

self.m_show = show
self.m_duration = 0
self.m_item = item
self.m_item_is_object = isinstance(item, QGraphicsObject)

def item(self):
return self.m_item

def forceStop(self):
self.blockSignals(True)
self.stop()
self.blockSignals(False)

def setDuration(self, time):
if self.m_item.opacity() == 0 and not self.m_show:
self.m_duration = 0
else:
if self.m_item_is_object:
self.m_item.blockSignals(True)
self.m_item.show()
self.m_item.blockSignals(False)
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)

try:
self.m_item.setOpacity(value)
except RuntimeError:
print("CanvasFadeAnimation::updateCurrentTime() - failed to animate canvas item, already destroyed?")
self.forceStop()
canvas.animation_list.remove(self)
return

if self.m_item.type() == CanvasBoxType:
self.m_item.setShadowOpacity(value)

def updateDirection(self, direction):
pass

def updateState(self, oldState, newState):
pass

# ------------------------------------------------------------------------------------------------------------
136 changes: 136 additions & 0 deletions src/patchcanvas/canvasicon.py
@@ -0,0 +1,136 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

# PatchBay Canvas engine using QGraphicsView/Scene
# Copyright (C) 2010-2019 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 doc/GPL.txt file.

# ------------------------------------------------------------------------------------------------------------
# Imports (Global)

from PyQt5.QtCore import qCritical, QRectF
from PyQt5.QtGui import QPainter
from PyQt5.QtSvg import QGraphicsSvgItem, QSvgRenderer
from PyQt5.QtWidgets import QGraphicsColorizeEffect

# ------------------------------------------------------------------------------------------------------------
# Imports (Custom)

from . import (
canvas,
icon2str,
CanvasIconType,
ICON_APPLICATION,
ICON_HARDWARE,
ICON_DISTRHO,
ICON_FILE,
ICON_PLUGIN,
ICON_LADISH_ROOM,
)

# ------------------------------------------------------------------------------------------------------------

class CanvasIcon(QGraphicsSvgItem):
def __init__(self, icon, name, parent):
QGraphicsSvgItem.__init__(self)
self.setParentItem(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, 16, 16)
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, 16, 16)

elif icon == ICON_FILE:
icon_path = ":/scalable/pb_file.svg"
self.p_size = QRectF(5, 4, 16, 16)

elif icon == ICON_PLUGIN:
icon_path = ":/scalable/pb_plugin.svg"
self.p_size = QRectF(5, 4, 16, 16)

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 not self.m_renderer:
QGraphicsSvgItem.paint(self, painter, option, widget)
return

painter.save()
painter.setRenderHint(QPainter.Antialiasing, False)
painter.setRenderHint(QPainter.TextAntialiasing, False)
self.m_renderer.render(painter, self.p_size)
painter.restore()

# ------------------------------------------------------------------------------------------------------------
157 changes: 157 additions & 0 deletions src/patchcanvas/canvasline.py
@@ -0,0 +1,157 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

# PatchBay Canvas engine using QGraphicsView/Scene
# Copyright (C) 2010-2019 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 doc/GPL.txt file.

# ------------------------------------------------------------------------------------------------------------
# Imports (Global)

from PyQt5.QtCore import Qt, QLineF
from PyQt5.QtGui import QLinearGradient, QPainter, QPen
from PyQt5.QtWidgets import QGraphicsLineItem

# ------------------------------------------------------------------------------------------------------------
# Imports (Custom)

from . import (
canvas,
options,
CanvasLineType,
ACTION_PORTS_DISCONNECT,
EYECANDY_FULL,
PORT_MODE_OUTPUT,
PORT_TYPE_AUDIO_JACK,
PORT_TYPE_MIDI_ALSA,
PORT_TYPE_MIDI_JACK,
PORT_TYPE_PARAMETER,
)

from .canvasportglow import CanvasPortGlow

# ------------------------------------------------------------------------------------------------------------

class CanvasLine(QGraphicsLineItem):
def __init__(self, item1, item2, parent):
QGraphicsLineItem.__init__(self)
self.setParentItem(parent)

self.item1 = item1
self.item2 = item2

self.m_locked = False
self.m_lineSelected = False

self.setGraphicsEffect(None)
self.updateLinePos()

def isLocked(self):
return self.m_locked

def setLocked(self, yesno):
self.m_locked = yesno

def isLineSelected(self):
return self.m_lineSelected

def updateLineSelected(self):
if self.m_locked:
return

yesno = self.item1.isSelected() or self.item2.isSelected()
if yesno != self.m_lineSelected and 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 triggerDisconnect(self):
for connection in canvas.connection_list:
if (connection.port_out_id == self.item1.getPortId() and connection.port_in_id == self.item2.getPortId()):
canvas.callback(ACTION_PORTS_DISCONNECT, connection.connection_id, 0, "")
break

def updateLinePos(self):
if self.item1.getPortMode() == PORT_MODE_OUTPUT:
rect1 = self.item1.sceneBoundingRect()
rect2 = self.item2.sceneBoundingRect()
line = QLineF(rect1.right(),
rect1.top() + float(canvas.theme.port_height)/2,
rect2.left(),
rect2.top() + 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_ALSA:
port_gradient.setColorAt(pos1, canvas.theme.line_midi_alsa_sel if self.m_lineSelected else canvas.theme.line_midi_alsa)
elif port_type1 == PORT_TYPE_PARAMETER:
port_gradient.setColorAt(pos1, canvas.theme.line_parameter_sel if self.m_lineSelected else canvas.theme.line_parameter)

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_ALSA:
port_gradient.setColorAt(pos2, canvas.theme.line_midi_alsa_sel if self.m_lineSelected else canvas.theme.line_midi_alsa)
elif port_type2 == PORT_TYPE_PARAMETER:
port_gradient.setColorAt(pos2, canvas.theme.line_parameter_sel if self.m_lineSelected else canvas.theme.line_parameter)

self.setPen(QPen(port_gradient, 2.00001, Qt.SolidLine, Qt.RoundCap))

def paint(self, painter, option, widget):
painter.save()
painter.setRenderHint(QPainter.Antialiasing, bool(options.antialiasing))

pen = self.pen()
cosm_pen = QPen(pen)
cosm_pen.setCosmetic(True)
cosm_pen.setWidthF(1.00001)

QGraphicsLineItem.paint(self, painter, option, widget)

painter.setPen(cosm_pen)
painter.setBrush(Qt.NoBrush)
painter.setOpacity(0.2)
painter.drawLine(self.line())

painter.restore()

# ------------------------------------------------------------------------------------------------------------
99 changes: 99 additions & 0 deletions src/patchcanvas/canvaslinemov.py
@@ -0,0 +1,99 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

# PatchBay Canvas engine using QGraphicsView/Scene
# Copyright (C) 2010-2019 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 doc/GPL.txt file.

# ------------------------------------------------------------------------------------------------------------
# Imports (Global)

from PyQt5.QtCore import qWarning, Qt, QLineF
from PyQt5.QtGui import QPainter, QPen
from PyQt5.QtWidgets import QGraphicsLineItem

# ------------------------------------------------------------------------------------------------------------
# Imports (Custom)

from . import (
canvas,
options,
port_mode2str,
port_type2str,
CanvasLineMovType,
PORT_MODE_INPUT,
PORT_MODE_OUTPUT,
PORT_TYPE_AUDIO_JACK,
PORT_TYPE_MIDI_ALSA,
PORT_TYPE_MIDI_JACK,
PORT_TYPE_PARAMETER,
)

# ------------------------------------------------------------------------------------------------------------

class CanvasLineMov(QGraphicsLineItem):
def __init__(self, port_mode, port_type, parent):
QGraphicsLineItem.__init__(self)
self.setParentItem(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 = parent.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_ALSA:
pen = QPen(canvas.theme.line_midi_alsa, 2)
elif port_type == PORT_TYPE_PARAMETER:
pen = QPen(canvas.theme.line_parameter, 2)
else:
qWarning("PatchCanvas::CanvasLineMov({}, {}, {}) - invalid port type".format(
port_mode2str(port_mode), port_type2str(port_type), parent))
pen = QPen(Qt.black)

pen.setCapStyle(Qt.RoundCap)
pen.setWidthF(pen.widthF() + 0.00001)
self.setPen(pen)

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()

# ------------------------------------------------------------------------------------------------------------
477 changes: 477 additions & 0 deletions src/patchcanvas/canvasport.py

Large diffs are not rendered by default.

53 changes: 53 additions & 0 deletions src/patchcanvas/canvasportglow.py
@@ -0,0 +1,53 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

# PatchBay Canvas engine using QGraphicsView/Scene
# Copyright (C) 2010-2019 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 doc/GPL.txt file.

# ------------------------------------------------------------------------------------------------------------
# Imports (Global)

from PyQt5.QtWidgets import QGraphicsDropShadowEffect

# ------------------------------------------------------------------------------------------------------------
# Imports (Custom)

from . import (
canvas,
PORT_TYPE_AUDIO_JACK,
PORT_TYPE_MIDI_ALSA,
PORT_TYPE_MIDI_JACK,
PORT_TYPE_PARAMETER,
)

# ------------------------------------------------------------------------------------------------------------

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_ALSA:
self.setColor(canvas.theme.line_midi_alsa_glow)
elif port_type == PORT_TYPE_PARAMETER:
self.setColor(canvas.theme.line_parameter_glow)

# ------------------------------------------------------------------------------------------------------------
1,093 changes: 1,093 additions & 0 deletions src/patchcanvas/patchcanvas.py

Large diffs are not rendered by default.

422 changes: 422 additions & 0 deletions src/patchcanvas/scene.py

Large diffs are not rendered by default.

234 changes: 118 additions & 116 deletions src/patchcanvas_theme.py → src/patchcanvas/theme.py

Large diffs are not rendered by default.

137 changes: 137 additions & 0 deletions src/patchcanvas/utils.py
@@ -0,0 +1,137 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

# PatchBay Canvas engine using QGraphicsView/Scene
# Copyright (C) 2010-2019 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 doc/GPL.txt file.

# ------------------------------------------------------------------------------------------------------------
# Imports (Global)

from PyQt5.QtCore import qCritical, QPointF, QTimer

# ------------------------------------------------------------------------------------------------------------
# Imports (Custom)

from . import bool2str, canvas, CanvasBoxType
from .canvasfadeanimation import CanvasFadeAnimation

# ------------------------------------------------------------------------------------------------------------

def CanvasGetNewGroupPos(horizontal):
if canvas.debug:
print("PatchCanvas::CanvasGetNewGroupPos(%s)" % bool2str(horizontal))

new_pos = QPointF(canvas.initial_pos)
items = canvas.scene.items()

#break_loop = False
while True:
break_for = False
for i, item in enumerate(items):
if item and item.type() == CanvasBoxType:
if item.sceneBoundingRect().adjusted(-5, -5, 5, 5).contains(new_pos):
itemRect = item.boundingRect()
if horizontal:
new_pos += QPointF(itemRect.width() + 50, 0)
else:
itemHeight = itemRect.height()
if itemHeight < 30:
new_pos += QPointF(0, itemHeight + 50)
else:
new_pos.setY(item.scenePos().y() + itemHeight + 20)
break_for = True
break
else:
if not break_for:
break
#break_loop = True

return new_pos

def CanvasGetFullPortName(group_id, port_id):
if canvas.debug:
print("PatchCanvas::CanvasGetFullPortName(%i, %i)" % (group_id, port_id))

for port in canvas.port_list:
if port.group_id == group_id and 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, %i) - unable to find port" % (group_id, port_id))
return ""

def CanvasGetPortConnectionList(group_id, port_id):
if canvas.debug:
print("PatchCanvas::CanvasGetPortConnectionList(%i, %i)" % (group_id, port_id))

conn_list = []

for connection in canvas.connection_list:
if connection.group_out_id == group_id and connection.port_out_id == port_id:
conn_list.append((connection.connection_id, connection.group_in_id, connection.port_in_id))
elif connection.group_in_id == group_id and connection.port_in_id == port_id:
conn_list.append((connection.connection_id, connection.group_out_id, connection.port_out_id))

return conn_list

def CanvasCallback(action, value1, value2, value_str):
if canvas.debug:
print("PatchCanvas::CanvasCallback(%i, %i, %i, %s)" % (action, value1, value2, value_str.encode()))

canvas.callback(action, value1, value2, value_str)

def CanvasItemFX(item, show, destroy):
if canvas.debug:
print("PatchCanvas::CanvasItemFX(%s, %s, %s)" % (item, bool2str(show), bool2str(destroy)))

# Check if the item already has an animation
for animation in canvas.animation_list:
if animation.item() == item:
animation.forceStop()
canvas.animation_list.remove(animation)
del animation
break

animation = CanvasFadeAnimation(item, show)
animation.setDuration(750 if show else 500)

if show:
animation.finished.connect(canvas.qobject.AnimationFinishedShow)
else:
if destroy:
animation.finished.connect(canvas.qobject.AnimationFinishedDestroy)
else:
animation.finished.connect(canvas.qobject.AnimationFinishedHide)

canvas.animation_list.append(animation)

animation.start()

def CanvasRemoveItemFX(item):
if canvas.debug:
print("PatchCanvas::CanvasRemoveItemFX(%s)" % item)

if item.type() == CanvasBoxType:
item.removeIconFromScene()

canvas.scene.removeItem(item)
del item

QTimer.singleShot(0, canvas.scene.update)

# ------------------------------------------------------------------------------------------------------------
45 changes: 30 additions & 15 deletions src/shared.py
Expand Up @@ -16,19 +16,19 @@
#
# For a full copy of the GNU General Public License see the COPYING file

# ------------------------------------------------------------------------------------------------------------
# ---------------------------------------------------------------------------------------------------------------------
# Imports (Global)

import os
import sys
from codecs import open as codecopen
from unicodedata import normalize

from PyQt5.QtCore import pyqtSignal, qWarning
from PyQt5.QtCore import pyqtSignal, qWarning, QSettings
from PyQt5.QtGui import QIcon
from PyQt5.QtWidgets import QApplication, QFileDialog, QMessageBox

# ------------------------------------------------------------------------------------------------------------
# ---------------------------------------------------------------------------------------------------------------------
# Set Platform

if sys.platform == "darwin":
Expand Down Expand Up @@ -58,7 +58,7 @@
MACOS = False
WINDOWS = False

# ------------------------------------------------------------------------------------------------------------
# ---------------------------------------------------------------------------------------------------------------------
# Try Import Signal

try:
Expand All @@ -67,31 +67,31 @@
except:
haveSignal = False

# ------------------------------------------------------------------------------------------------------------
# ---------------------------------------------------------------------------------------------------------------------
# Safe exception hook, needed for PyQt5

def sys_excepthook(typ, value, tback):
return sys.__excepthook__(typ, value, tback)

sys.excepthook = sys_excepthook

# ------------------------------------------------------------------------------------------------------------
# ---------------------------------------------------------------------------------------------------------------------
# Set Version

VERSION = "0.9.0"

# ------------------------------------------------------------------------------------------------------------
# ---------------------------------------------------------------------------------------------------------------------
# Set Debug mode

DEBUG = bool("-d" in sys.argv or "-debug" in sys.argv or "--debug" in sys.argv)

# ------------------------------------------------------------------------------------------------------------
# ---------------------------------------------------------------------------------------------------------------------
# Global variables

global gGui
gGui = None

# ------------------------------------------------------------------------------------------------------------
# ---------------------------------------------------------------------------------------------------------------------
# Set TMP

TMP = os.getenv("TMP")
Expand All @@ -103,7 +103,7 @@ def sys_excepthook(typ, value, tback):
else:
TMP = "/tmp"

# ------------------------------------------------------------------------------------------------------------
# ---------------------------------------------------------------------------------------------------------------------
# Set HOME

HOME = os.getenv("HOME")
Expand All @@ -118,7 +118,7 @@ def sys_excepthook(typ, value, tback):
qWarning("HOME does not exist")
HOME = TMP

# ------------------------------------------------------------------------------------------------------------
# ---------------------------------------------------------------------------------------------------------------------
# Set PATH

PATH = os.getenv("PATH")
Expand All @@ -136,13 +136,13 @@ def sys_excepthook(typ, value, tback):
else:
PATH = PATH.split(os.pathsep)

# ------------------------------------------------------------------------------------------------------------
# ---------------------------------------------------------------------------------------------------------------------
# Remove/convert non-ascii chars from a string

def asciiString(string):
return normalize("NFKD", string).encode("ascii", "ignore").decode("utf-8")

# ------------------------------------------------------------------------------------------------------------
# ---------------------------------------------------------------------------------------------------------------------
# Convert a ctypes c_char_p into a python string

def cString(value):
Expand All @@ -152,13 +152,13 @@ def cString(value):
return value
return value.decode("utf-8", errors="ignore")

# ------------------------------------------------------------------------------------------------------------
# ---------------------------------------------------------------------------------------------------------------------
# Get Icon from user theme, using our own as backup (Oxygen)

def getIcon(icon, size=16):
return QIcon.fromTheme(icon, QIcon(":/%ix%i/%s.svgz" % (size, size, icon)))

# ------------------------------------------------------------------------------------------------------------
# ---------------------------------------------------------------------------------------------------------------------
# Signal handler

def setUpSignals(self_):
Expand Down Expand Up @@ -213,3 +213,18 @@ def showWindowHandler():
gGui.showMaximized()
else:
gGui.showNormal()

# ---------------------------------------------------------------------------------------------------------------------
# Safer QSettings class, which does not throw if type mismatches

class QSafeSettings(QSettings):
def value(self, key, defaultValue, valueType):
if not isinstance(defaultValue, valueType):
print("QSafeSettings.value() - defaultValue type mismatch for key", key)

try:
return QSettings.value(self, key, defaultValue, valueType)
except:
return defaultValue

# ---------------------------------------------------------------------------------------------------------------------
3 changes: 2 additions & 1 deletion src/shared_canvasjack.py
Expand Up @@ -26,10 +26,11 @@
# ------------------------------------------------------------------------------------------------------------
# Imports (Custom Stuff)

import patchcanvas
from shared import *
from jacklib_helpers import *

from patchcanvas import patchcanvas

# ------------------------------------------------------------------------------------------------------------
# Have JACK2 ?

Expand Down
2 changes: 1 addition & 1 deletion src/shared_settings.py
Expand Up @@ -27,7 +27,7 @@

import ui_settings_app
from shared import *
from patchcanvas_theme import *
from patchcanvas.theme import *

# ------------------------------------------------------------------------------------------------------------
# Global variables
Expand Down