From c33a2f9f90564dde11b3345aca5b5f429febf744 Mon Sep 17 00:00:00 2001 From: falkTX Date: Wed, 29 Feb 2012 19:47:38 +0000 Subject: [PATCH] Initial port of catarina and patchcanvas --- src/catarina.py | 398 +++++++++++++++++++++++++++++++ src/patchcanvas.py | 493 +++++++++++++++++++++++++++++++++++++++ src/patchcanvas_theme.py | 167 +++++++++++++ src/shared.py | 37 +++ 4 files changed, 1095 insertions(+) create mode 100755 src/catarina.py create mode 100644 src/patchcanvas.py create mode 100644 src/patchcanvas_theme.py diff --git a/src/catarina.py b/src/catarina.py new file mode 100755 index 0000000..d9e72b5 --- /dev/null +++ b/src/catarina.py @@ -0,0 +1,398 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +# PatchCanvas test application +# Copyright (C) 2012 Filipe Coelho +# +# 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 COPYING file + +# Imports (Global) +from PyQt4.QtCore import pyqtSlot, Qt, QSettings +from PyQt4.QtGui import QApplication, QDialog, QDialogButtonBox, QPainter, QMainWindow + +# Imports (Custom Stuff) +import patchcanvas +import ui_catarina, icons_rc +import ui_catarina_addgroup, ui_catarina_removegroup, ui_catarina_renamegroup +import ui_catarina_addport, ui_catarina_removeport, ui_catarina_renameport +import ui_catarina_connectports, ui_catarina_disconnectports +from shared import * + +try: + from PyQt4.QtOpenGL import QGLWidget + hasGL = True +except: + hasGL = False + +iGroupId = 0 +iGroupName = 1 +iGroupSplit = 2 +iGroupIcon = 3 + +iGroupPosId = 0 +iGroupPosX_o = 1 +iGroupPosY_o = 2 +iGroupPosX_i = 3 +iGroupPosY_i = 4 + +iPortGroup = 0 +iPortId = 1 +iPortName = 2 +iPortMode = 3 +iPortType = 4 + +iConnId = 0 +iConnOutput = 1 +iConnInput = 2 + +# Add Group Dialog +class CatarinaAddGroupW(QDialog, ui_catarina_addgroup.Ui_CatarinaAddGroupW): + def __init__(self, parent, group_list): + QDialog.__init__(self, parent) + self.setupUi(self) + + self.buttonBox.button(QDialogButtonBox.Ok).setEnabled(False) + + self.m_group_list_names = [] + + for group in group_list: + self.m_group_list_names.append(group[iGroupName]) + + self.connect(self, SIGNAL("accepted()"), SLOT("slot_setReturn()")) + self.connect(self.le_group_name, SIGNAL("textChanged(QString)"), SLOT("slot_checkText(QString)")) + + self.ret_group_name = "" + self.ret_group_split = False + + @pyqtSlot(str) + def slot_checkText(self, text): + check = bool(text and text not in self.m_group_list_names) + self.buttonBox.button(QDialogButtonBox.Ok).setEnabled(check) + + @pyqtSlot() + def slot_setReturn(self): + self.ret_group_name = self.le_group_name.text() + self.ret_group_split = self.cb_split.isChecked() + +# Remove Group Dialog +class CatarinaRemoveGroupW(QDialog, ui_catarina_removegroup.Ui_CatarinaRemoveGroupW): + def __init__(self, parent, group_list): + QDialog.__init__(self, parent) + self.setupUi(self) + + self.buttonBox.button(QDialogButtonBox.Ok).setEnabled(False) + + i = 0 + for group in group_list: + twi_group_id = QTableWidgetItem(str(group[iGroupId])) + twi_group_name = QTableWidgetItem(group[iGroupName]) + twi_group_split = QTableWidgetItem("Yes" if (group[iGroupSplit]) else "No") + self.tw_group_list.insertRow(i) + self.tw_group_list.setItem(i, 0, twi_group_id) + self.tw_group_list.setItem(i, 1, twi_group_name) + self.tw_group_list.setItem(i, 2, twi_group_split) + i += 1 + + self.connect(self, SIGNAL("accepted()"), SLOT("slot_setReturn()")) + self.connect(self.tw_group_list, SIGNAL("cellDoubleClicked(int, int)"), SLOT("accept()")) + self.connect(self.tw_group_list, SIGNAL("currentCellChanged(int, int, int, int)"), SLOT("slot_checkCell(int)")) + + self.ret_group_id = -1 + + @pyqtSlot(int) + def slot_checkCell(self, row): + check = bool(row >= 0) + self.buttonBox.button(QDialogButtonBox.Ok).setEnabled(check) + + @pyqtSlot() + def slot_setReturn(self): + self.ret_group_id = int(self.tw_group_list.item(self.tw_group_list.currentRow(), 0).text()) + +# Rename Group Dialog +class CatarinaRenameGroupW(QDialog, ui_catarina_renamegroup.Ui_CatarinaRenameGroupW): + def __init__(self, parent, group_list): + QDialog.__init__(self, parent) + self.setupUi(self) + + self.buttonBox.button(QDialogButtonBox.Ok).setEnabled(False) + + for group in group_list: + self.cb_group_to_rename.addItem("%i - %s" % (group[iGroupId], group[iGroupName])) + + self.connect(self, SIGNAL("accepted()"), SLOT("slot_setReturn()")) + self.connect(self.cb_group_to_rename, SIGNAL("currentIndexChanged(int)"), SLOT("slot_checkItem()")) + self.connect(self.le_new_group_name, SIGNAL("textChanged(QString)"), SLOT("slot_checkText(QString)")) + + self.ret_group_id = -1 + self.ret_new_group_name = "" + + @pyqtSlot() + def slot_checkItem(self, index): + self.checkText(self.le_new_group_name.text()) + + @pyqtSlot(str) + def slot_checkText(self, text): + if (self.cb_group_to_rename.count() > 0): + group_name = self.cb_group_to_rename.currentText().split(" - ", 1)[1] + check = bool(text and text != group_name) + self.buttonBox.button(QDialogButtonBox.Ok).setEnabled(check) + + @pyqtSlot() + def slot_setReturn(self): + self.ret_group_id = int(self.cb_group_to_rename.currentText().split(" - ", 1)[0]) + self.ret_new_group_name = self.le_new_group_name.text() + +# Main Window +class CatarinaMainW(QMainWindow, ui_catarina.Ui_CatarinaMainW): + def __init__(self, parent=None): + QMainWindow.__init__(self, parent) + self.setupUi(self) + + self.settings = QSettings("Cadence", "Catarina") + self.loadSettings() + + self.act_project_new.setIcon(getIcon("document-new")) + self.act_project_open.setIcon(getIcon("document-open")) + self.act_project_save.setIcon(getIcon("document-save")) + self.act_project_save_as.setIcon(getIcon("document-save-as")) + self.b_project_new.setIcon(getIcon("document-new")) + self.b_project_open.setIcon(getIcon("document-open")) + self.b_project_save.setIcon(getIcon("document-save")) + self.b_project_save_as.setIcon(getIcon("document-save-as")) + + self.act_patchbay_add_group.setIcon(getIcon("list-add")) + self.act_patchbay_remove_group.setIcon(getIcon("edit-delete")) + self.act_patchbay_rename_group.setIcon(getIcon("edit-rename")) + self.act_patchbay_add_port.setIcon(getIcon("list-add")) + self.act_patchbay_remove_port.setIcon(getIcon("list-remove")) + self.act_patchbay_rename_port.setIcon(getIcon("edit-rename")) + self.act_patchbay_connect_ports.setIcon(getIcon("network-connect")) + self.act_patchbay_disconnect_ports.setIcon(getIcon("network-disconnect")) + self.b_group_add.setIcon(getIcon("list-add")) + self.b_group_remove.setIcon(getIcon("edit-delete")) + self.b_group_rename.setIcon(getIcon("edit-rename")) + self.b_port_add.setIcon(getIcon("list-add")) + self.b_port_remove.setIcon(getIcon("list-remove")) + self.b_port_rename.setIcon(getIcon("edit-rename")) + self.b_ports_connect.setIcon(getIcon("network-connect")) + self.b_ports_disconnect.setIcon(getIcon("network-disconnect")) + + setIcons(self, ("canvas",)) + + self.scene = patchcanvas.PatchScene(self, self.graphicsView) + self.graphicsView.setScene(self.scene) + self.graphicsView.setRenderHint(QPainter.Antialiasing, bool(self.m_savedSettings["Canvas/Antialiasing"] == Qt.Checked)) + self.graphicsView.setRenderHint(QPainter.TextAntialiasing, self.m_savedSettings["Canvas/TextAntialiasing"]) + if (self.m_savedSettings["Canvas/UseOpenGL"] and hasGL): + self.graphicsView.setViewport(QGLWidget(self.graphicsView)) + self.graphicsView.setRenderHint(QPainter.HighQualityAntialiasing, self.m_savedSettings["Canvas/HighQualityAntialiasing"]) + + p_options = patchcanvas.options_t() + p_options.theme_name = self.m_savedSettings["Canvas/Theme"] + p_options.bezier_lines = self.m_savedSettings["Canvas/BezierLines"] + p_options.antialiasing = self.m_savedSettings["Canvas/Antialiasing"] + p_options.auto_hide_groups = self.m_savedSettings["Canvas/AutoHideGroups"] + p_options.fancy_eyecandy = self.m_savedSettings["Canvas/FancyEyeCandy"] + + p_features = patchcanvas.features_t() + p_features.group_info = False + p_features.group_rename = True + p_features.port_info = True + p_features.port_rename = True + p_features.handle_group_pos = False + + patchcanvas.set_options(p_options) + patchcanvas.set_features(p_features) + patchcanvas.init(self.scene, self.canvasCallback, DEBUG) + + self.connect(self.act_project_new, SIGNAL("triggered()"), SLOT("slot_projectNew()")) + #self.connect(self.act_project_open, SIGNAL("triggered()"), SLOT("slot_projectOpen()")) + #self.connect(self.act_project_save, SIGNAL("triggered()"), SLOT("slot_projectSave()")) + #self.connect(self.act_project_save_as, SIGNAL("triggered()"), SLOT("slot_projectSaveAs()")) + self.connect(self.b_project_new, SIGNAL("clicked()"), SLOT("slot_projectNew()")) + #self.connect(self.b_project_open, SIGNAL("clicked()"), SLOT("slot_projectOpen()")) + #self.connect(self.b_project_save, SIGNAL("clicked()"), SLOT("slot_projectSave()")) + #self.connect(self.b_project_save_as, SIGNAL("clicked()"), SLOT("slot_projectSaveAs()")) + self.connect(self.act_patchbay_add_group, SIGNAL("triggered()"), SLOT("slot_groupAdd()")) + self.connect(self.act_patchbay_remove_group, SIGNAL("triggered()"), SLOT("slot_groupRemove()")) + self.connect(self.act_patchbay_rename_group, SIGNAL("triggered()"), SLOT("slot_groupRename()")) + #self.connect(self.act_patchbay_add_port, SIGNAL("triggered()"),SLOT("slot_portAdd()")) + #self.connect(self.act_patchbay_remove_port, SIGNAL("triggered()"), SLOT("slot_portRemove()")) + #self.connect(self.act_patchbay_rename_port, SIGNAL("triggered()"), SLOT("slot_portRename()")) + #self.connect(self.act_patchbay_connect_ports, SIGNAL("triggered()"), SLOT("slot_connectPorts()")) + #self.connect(self.act_patchbay_disconnect_ports, SIGNAL("triggered()"), SLOT("slot_disconnectPorts()")) + self.connect(self.b_group_add, SIGNAL("clicked()"), SLOT("slot_groupAdd()")) + self.connect(self.b_group_remove, SIGNAL("clicked()"), SLOT("slot_groupRemove()")) + self.connect(self.b_group_rename, SIGNAL("clicked()"), SLOT("slot_groupRename()")) + #self.connect(self.b_port_add, SIGNAL("clicked()"), SLOT("slot_portAdd()")) + #self.connect(self.b_port_remove, SIGNAL("clicked()"), SLOT("slot_portRemove()")) + #self.connect(self.b_port_rename, SIGNAL("clicked()"), SLOT("slot_portRename()")) + #self.connect(self.b_ports_connect, SIGNAL("clicked()"), SLOT("slot_connectPorts()")) + #self.connect(self.b_ports_disconnect, SIGNAL("clicked()"), SLOT("slot_disconnectPorts()")) + + #setCanvasConnections(self) + + #self.connect(self.act_settings_configure, SIGNAL("triggered()"), self.configureCatarina) + + self.connect(self.act_help_about, SIGNAL("triggered()"), SLOT("slot_aboutCatarina()")) + self.connect(self.act_help_about_qt, SIGNAL("triggered()"), app, SLOT("aboutQt()")) + + #self.connect(self, SIGNAL("SIGUSR1()"), SLOT("slot_projectSave()")) + + # Dummy timer to keep events active + self.m_updateTimer = self.startTimer(500) + + # Start Empty Project + self.slot_projectNew() + + def canvasCallback(self, action, value1, value2, value_str): + print(action, value1, value2, value_str) + + @pyqtSlot() + def slot_projectNew(self): + self.m_group_list = [] + self.m_group_list_pos = [] + self.m_port_list = [] + self.m_connection_list = [] + self.m_last_group_id = 1 + self.m_last_port_id = 1 + self.m_last_connection_id = 1 + self.m_save_path = None + patchcanvas.clear() + + @pyqtSlot() + def slot_groupAdd(self): + dialog = CatarinaAddGroupW(self, self.m_group_list) + if (dialog.exec_()): + group_id = self.m_last_group_id + group_name = dialog.ret_group_name + group_split = dialog.ret_group_split + group_icon = patchcanvas.ICON_HARDWARE if (group_split) else patchcanvas.ICON_APPLICATION + patchcanvas.addGroup(group_id, group_name, group_split, group_icon) + + group_obj = [None, None, None, None] + group_obj[iGroupId] = group_id + group_obj[iGroupName] = group_name + group_obj[iGroupSplit] = group_split + group_obj[iGroupIcon] = group_icon + + self.m_group_list.append(group_obj) + self.m_last_group_id += 1 + + @pyqtSlot() + def slot_groupRemove(self): + dialog = CatarinaRemoveGroupW(self, self.m_group_list) + if (dialog.exec_()): + group_id = dialog.ret_group_id + + #for port in self.m_port_list: + #if (group_id == port[iPortGroup]): + #port_id = port[iPortId] + + #h = 0 + #for j in range(len(self.connection_list)): + #if (self.connection_list[j-h][iConnOutput] == port_id or self.connection_list[j-h][iConnInput] == port_id): + #patchcanvas.disconnectPorts(self.connection_list[j-h][iConnId]) + #self.connection_list.pop(j-h) + #h += 1 + + #h = 0 + #for i in range(len(self.port_list)): + #if (self.port_list[i-h][iPortGroup] == group_id): + #port_id = self.port_list[i-h][iPortId] + #patchcanvas.removePort(port_id) + #self.port_list.pop(i-h) + #h += 1 + + #patchcanvas.removeGroup(group_id) + + #for i in range(len(self.group_list)): + #if (self.group_list[i][iGroupId] == group_id): + #self.group_list.pop(i) + #break + + @pyqtSlot() + def slot_groupRename(self): + dialog = CatarinaRenameGroupW(self, self.m_group_list) + if (dialog.exec_()): + group_id = dialog.ret_group_id + new_group_name = dialog.ret_new_group_name + patchcanvas.renameGroup(group_id, new_group_name) + + for group in self.m_group_list: + if (group[iGroupId] == group_id): + group[iGroupName] = new_group_name + break + + @pyqtSlot() + def slot_aboutCatarina(self): + QMessageBox.about(self, self.tr("About Catarina"), self.tr("

Catarina

" + "
Version %s" + "
Catarina is a testing ground for the 'PatchCanvas' module.
" + "
Copyright (C) 2010-2012 falkTX") % (VERSION)) + + def saveSettings(self): + self.settings.setValue("Geometry", self.saveGeometry()) + self.settings.setValue("ShowToolbar", self.frame_toolbar.isVisible()) + + def loadSettings(self): + self.restoreGeometry(self.settings.value("Geometry", "")) + + show_toolbar = self.settings.value("ShowToolbar", True, type=bool) + self.act_settings_show_toolbar.setChecked(show_toolbar) + self.frame_toolbar.setVisible(show_toolbar) + + self.m_savedSettings = { + "Canvas/Theme": self.settings.value("Canvas/Theme", patchcanvas.getThemeName(patchcanvas.getDefaultTheme), type=str), + "Canvas/BezierLines": self.settings.value("Canvas/BezierLines", True, type=bool), + "Canvas/AutoHideGroups": self.settings.value("Canvas/AutoHideGroups", False, type=bool), + "Canvas/FancyEyeCandy": self.settings.value("Canvas/FancyEyeCandy", False, type=bool), + "Canvas/UseOpenGL": self.settings.value("Canvas/UseOpenGL", False, type=bool), + "Canvas/Antialiasing": self.settings.value("Canvas/Antialiasing", Qt.PartiallyChecked, type=int), + "Canvas/TextAntialiasing": self.settings.value("Canvas/TextAntialiasing", True, type=bool), + "Canvas/HighQualityAntialiasing": self.settings.value("Canvas/HighQualityAntialiasing", False, type=bool) + } + + def timerEvent(self, event): + if (event.timerId() == self.m_updateTimer): + self.update() + QMainWindow.timerEvent(self, event) + + def closeEvent(self, event): + self.saveSettings() + QMainWindow.closeEvent(self, event) + +#--------------- main ------------------ +if __name__ == '__main__': + + # App initialization + app = QApplication(sys.argv) + app.setApplicationName("Catarina") + app.setApplicationVersion(VERSION) + app.setOrganizationName("falkTX") + app.setWindowIcon(QIcon(":/scalable/catarina.svg")) + + # Show GUI + gui = CatarinaMainW() + + # Set-up custom signal handling + set_up_signals(gui) + + #if (app.arguments().count() > 1): + #gui.save_path = QStringStr(app.arguments()[1]) + #gui.prepareToloadFile() + + gui.show() + + # App-Loop + sys.exit(app.exec_()) diff --git a/src/patchcanvas.py b/src/patchcanvas.py new file mode 100644 index 0000000..b67215f --- /dev/null +++ b/src/patchcanvas.py @@ -0,0 +1,493 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +# Patchbay Canvas engine using QGraphicsView/Scene +# Copyright (C) 2012 Filipe Coelho +# +# 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 COPYING file + +# Imports (Global) +from PyQt4.QtCore import pyqtSlot, qDebug, qCritical, qFatal, Qt, QObject, SIGNAL, SLOT +from PyQt4.QtCore import QPointF, QRectF, QSettings +from PyQt4.QtGui import QGraphicsScene, QGraphicsItem + +# Imports (Theme) +from patchcanvas_theme import * + +PATCHCANVAS_ORGANISATION_NAME = "Cadence" + +# ------------------------------------------------------------------------------ +# 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_HARDWARE = 0 +ICON_APPLICATION = 1 +ICON_LADISH_ROOM = 2 + +# Split Option +SPLIT_UNDEF = 0 +SPLIT_NO = 1 +SPLIT_YES = 2 + +# Canvas options +class options_t(object): + __slots__ = [ + 'theme_name', + 'bezier_lines', + 'antialiasing', + 'auto_hide_groups', + 'fancy_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' + ] + +# Main Canvas object +class Canvas(object): + __slots__ = [ + 'scene', + 'callback', + 'debug', + 'last_z_value', + 'last_group_id', + 'last_connection_id', + 'initial_pos', + 'group_list', + 'port_list', + 'connection_list', + 'postponed_groups', + 'qobject', + 'settings', + 'theme', + 'size_rect', + 'initiated' + ] + +# ------------------------------------------------------------------------------ +# patchcanvas.cpp + +class CanvasObject(QObject): + def __init__(self, parent): + QObject.__init__(self, parent) + + @pyqtSlot() + def CanvasPostponedGroups(self): + CanvasPostponedGroups() + + @pyqtSlot() + def PortContextMenuDisconnect(self): + connection_id_try = self.sender().data().toInt() + if (connection_id_try[1]): + CanvasCallback(ACTION_PORTS_DISCONNECT, connection_id_try[0], 0, "") + +# Global objects +canvas = Canvas() +canvas.qobject = None +canvas.settings = None +canvas.theme = None +canvas.initiated = False + +options = options_t() +options.theme_name = getThemeName(getDefaultTheme()) +options.bezier_lines = True +options.antialiasing = Qt.PartiallyChecked +options.auto_hide_groups = True +options.fancy_eyecandy = False + +features = features_t() +features.group_info = False +features.group_rename = True +features.port_info = False +features.port_rename = True +features.handle_group_pos = False + +# Internal functions +def bool2str(check): + return "True" if check else "False" + +# PatchCanvas API +def set_options(new_options): + if (canvas.initiated): return + options.theme_name = new_options.theme_name + options.bezier_lines = new_options.bezier_lines + options.antialiasing = new_options.antialiasing + options.auto_hide_groups = new_options.auto_hide_groups + options.fancy_eyecandy = new_options.fancy_eyecandy + +def set_features(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(scene, callback, debug=False): + if (debug): + qDebug("PatchCanvas::init(%s, %s, %s)" % (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_group_id = 0 + canvas.last_connection_id = 0 + canvas.initial_pos = QPointF(0, 0) + + canvas.group_list = [] + canvas.port_list = [] + canvas.connection_list = [] + canvas.postponed_groups = [] + + if (not canvas.qobject): canvas.qobject = CanvasObject(None) + if (not canvas.settings): canvas.settings = QSettings(PATCHCANVAS_ORGANISATION_NAME, "PatchCanvas") + + for i in range(Theme.THEME_MAX): + this_theme_name = getThemeName(i) + if (this_theme_name == options.theme_name): + canvas.theme = Theme(i) + break + else: + canvas.theme = Theme(getDefaultTheme()) + + canvas.size_rect = QRectF() + + canvas.scene.rubberbandByTheme() + canvas.scene.setBackgroundBrush(canvas.theme.canvas_bg) + + canvas.initiated = True + +def clear(): + if (canvas.debug): + qDebug("patchcanvas::clear()") + + group_list_ids = [] + port_list_ids = [] + connection_list_ids = [] + + for i in range(len(canvas.group_list)): + group_list_ids.append(canvas.group_list[i].group_id) + + for i in range(len(canvas.port_list)): + port_list_ids.append(canvas.port_list[i].port_id) + + for i in range(len(canvas.connection_list)): + connection_list_ids.append(canvas.connection_list[i].connection_id) + + for idx in connection_list_ids: + disconnectPorts(idx) + + for idx in port_list_ids: + removePort(tmp_port_list[i]) + + for idx in group_list_ids: + removeGroup(idx) + + canvas.last_z_value = 0 + canvas.last_group_id = 0 + canvas.last_connection_id = 0 + + canvas.group_list = [] + canvas.port_list = [] + canvas.connection_list = [] + canvas.postponed_groups = [] + + 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) + +# ------------------------------------------------------------------------------ +# patchscene.cpp + +class PatchScene(QGraphicsScene): + 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.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) + + self.m_view = view + if (not self.m_view): + qFatal("PatchCanvas::PatchScene() - Invalid view") + + 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.emit(SIGNAL("scaleChanged(double)"), self.m_view.transform().m11()) + + def rubberbandByTheme(self): + 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 + items_list = self.items() + + if (len(items_list) > 0): + for i in range(len(items_list)): + if (items_list[i].isVisible() and items_list[i].type() == CanvasBoxType): + pos = items_list[i].scenePos() + rect = items_list[i].boundingRect() + + if (min_x == None): + min_x = pos.x() + elif (pos.x() < min_x): + min_x = pos.x() + + if (min_y == None): + min_y = pos.y() + elif (pos.y() < min_y): + min_y = pos.y() + + if (max_x == None): + max_x = pos.x()+rect.width() + elif (pos.x()+rect.width() > max_x): + max_x = pos.x()+rect.width() + + if (max_y == None): + max_y = pos.y()+rect.height() + elif (pos.y()+rect.height() > max_y): + max_y = pos.y()+rect.height() + + 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.emit(SIGNAL("scaleChanged(double)"), 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.emit(SIGNAL("scaleChanged(double)"), self.m_view.transform().m11()) + + def zoom_reset(self): + self.m_view.resetTransform() + self.emit(SIGNAL("scaleChanged(double)"), 1.0) + + def keyPressEvent(self, event): + if (not self.m_view): + event.reject() + + if (event.key() == Qt.Key_Control): + self.m_ctrl_down = True + + if (event.key() == Qt.Key_Home): + self.zoom_fit() + event.accept() + + elif (self.m_ctrl_down): + if (event.key() == Qt.Key_Plus): + self.zoom_in() + event.accept() + elif (event.key() == Qt.Key_Minus): + self.zoom_out() + event.accept() + elif (event.key() == Qt.Key_1): + self.zoom_reset() + event.accept() + else: + QGraphicsScene.keyPressEvent(self, event) + + else: + 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): + if (event.button() == Qt.LeftButton): + self.m_mouse_down_init = True + else: + self.m_mouse_down_init = False + self.m_mouse_rubberband = False + QGraphicsScene.mousePressEvent(self, event) + + def mouseMoveEvent(self, event): + if (self.m_mouse_down_init): + self.m_mouse_rubberband = (len(self.selectedItems()) == 0) + self.m_mouse_down_init = False + + if (self.m_mouse_rubberband): + if (self.m_rubberband_selection == False): + 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())) + + event.accept() + + else: + 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.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.isVisible() and item.type() == CanvasBoxType): + item.checkItemPos() + self.emit(SIGNAL("sceneGroupMoved(int, int, QPointF)"), item.getGroupId(), item.getSplittedMode(), item.scenePos()) + + self.m_mouse_down_init = False + self.m_mouse_rubberband = False + QGraphicsScene.mouseReleaseEvent(self, event) + + def wheelEvent(self, event): + if (not self.m_view): + event.reject() + + if (self.m_ctrl_down): + factor = 1.41 ** (event.delta()/240.0) + self.m_view.scale(factor, factor) + + self.fixScaleFactor() + event.accept() + + else: + QGraphicsScene.wheelEvent(self, event) diff --git a/src/patchcanvas_theme.py b/src/patchcanvas_theme.py new file mode 100644 index 0000000..f4a5526 --- /dev/null +++ b/src/patchcanvas_theme.py @@ -0,0 +1,167 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +# Patchbay Canvas Themes +# Copyright (C) 2012 Filipe Coelho +# +# 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 COPYING file + +from PyQt4.QtCore import Qt +from PyQt4.QtGui import QColor, QFont, QPen + +class Theme(object): + + # enum PortType + THEME_PORT_SQUARE = 0 + THEME_PORT_POLYGON = 1 + + # enum List + THEME_MODERN_DARK = 0 + THEME_CLASSIC_DARK = 1 + THEME_MAX = 2 + + def __init__(self, idx): + super(Theme, self).__init__() + + if (idx == self.THEME_MODERN_DARK): + # Name this theme + self.m_name = "Modern Dark" + + # Canvas + self.canvas_bg = QColor(0,0,0) + + # Boxes + self.box_pen = QPen(QColor(76,77,78), 1, Qt.SolidLine) + self.box_pen_sel = QPen(QColor(206,207,208), 1, Qt.DashLine) + self.box_bg_1 = QColor(32,34,35) + self.box_bg_2 = QColor(43,47,48) + self.box_shadow = QColor(89,89,89,180) + + self.box_text = QPen(QColor(240,240,240), 0) + self.box_font_name = "Deja Vu Sans" + self.box_font_size = 8 + self.box_font_state = QFont.Bold + + # Ports + self.port_audio_jack_pen = QPen(QColor(63,90,126), 1) + self.port_audio_jack_pen_sel = QPen(QColor(63+30,90+30,126+30), 1) + self.port_midi_jack_pen = QPen(QColor(159,44,42), 1) + self.port_midi_jack_pen_sel = QPen(QColor(159+30,44+30,42+30), 1) + self.port_midi_a2j_pen = QPen(QColor(137,76,43), 1) + self.port_midi_a2j_pen_sel = QPen(QColor(137+30,76+30,43+30), 1) + self.port_midi_alsa_pen = QPen(QColor(93,141,46), 1) + self.port_midi_alsa_pen_sel = QPen(QColor(93+30,141+30,46+30), 1) + + self.port_audio_jack_bg = QColor(35,61,99) + self.port_audio_jack_bg_sel = QColor(35+50,61+50,99+50) + self.port_midi_jack_bg = QColor(120,15,16) + self.port_midi_jack_bg_sel = QColor(120+50,15+50,16+50) + self.port_midi_a2j_bg = QColor(101,47,16) + self.port_midi_a2j_bg_sel = QColor(101+50,47+50,16+50) + self.port_midi_alsa_bg = QColor(64,112,18) + self.port_midi_alsa_bg_sel = QColor(64+50,112+50,18+50) + + self.port_text = QPen(QColor(250,250,250), 0) + self.port_font_name = "Deja Vu Sans" + self.port_font_size = 8 + self.port_font_state = QFont.Normal + self.port_mode = self.THEME_PORT_POLYGON + + # Lines + self.line_audio_jack = QColor(63,90,126) + self.line_audio_jack_sel = QColor(63+90,90+90,126+90) + self.line_audio_jack_glow = QColor(100,100,200) + self.line_midi_jack = QColor(159,44,42) + self.line_midi_jack_sel = QColor(159+90,44+90,42+90) + self.line_midi_jack_glow = QColor(200,100,100) + self.line_midi_a2j = QColor(137,76,43) + self.line_midi_a2j_sel = QColor(137+90,76+90,43+90) + self.line_midi_a2j_glow = QColor(166,133,133) + self.line_midi_alsa = QColor(93,141,46) + self.line_midi_alsa_sel = QColor(93+90,141+90,46+90) + self.line_midi_alsa_glow = QColor(100,200,100) + + self.rubberband_pen = QPen(QColor(206,207,208), 1, Qt.SolidLine) + self.rubberband_brush = QColor(76,77,78,100) + + elif (idx == self.THEME_CLASSIC_DARK): + # Name this theme + self.m_name = "Classic Dark" + + # Canvas + self.canvas_bg = QColor(0,0,0) + + # Boxes + self.box_pen = QPen(QColor(147-70,151-70,143-70), 2, Qt.SolidLine) + self.box_pen_sel = QPen(QColor(147,151,143), 2, Qt.DashLine) + self.box_bg_1 = QColor(30,34,36) + self.box_bg_2 = QColor(30,34,36) + self.box_shadow = QColor(89,89,89,180) + + self.box_text = QPen(QColor(255,255,255), 0) + self.box_font_name = "Sans" + self.box_font_size = 9 + self.box_font_state = QFont.Normal + + # Ports + self.port_audio_jack_pen = QPen(QColor(35,61,99), 0) + self.port_audio_jack_pen_sel = QPen(QColor(255,0,0), 0) + self.port_midi_jack_pen = QPen(QColor(120,15,16), 0) + self.port_midi_jack_pen_sel = QPen(QColor(255,0,0), 0) + self.port_midi_a2j_pen = QPen(QColor(101,47,17), 0) + self.port_midi_a2j_pen_sel = QPen(QColor(255,0,0), 0) + self.port_midi_alsa_pen = QPen(QColor(63,112,19), 0) + self.port_midi_alsa_pen_sel = QPen(QColor(255,0,0), 0) + + self.port_audio_jack_bg = QColor(35,61,99) + self.port_audio_jack_bg_sel = QColor(255,0,0) + self.port_midi_jack_bg = QColor(120,15,16) + self.port_midi_jack_bg_sel = QColor(255,0,0) + self.port_midi_a2j_bg = QColor(101,47,17) + self.port_midi_a2j_bg_sel = QColor(255,0,0) + self.port_midi_alsa_bg = QColor(63,112,19) + self.port_midi_alsa_bg_sel = QColor(255,0,0) + + self.port_text = QPen(QColor(250,250,250), 0) + self.port_font_name = "Sans" + self.port_font_size = 8 + self.port_font_state = QFont.Normal + self.port_mode = self.THEME_PORT_SQUARE + + # Lines + self.line_audio_jack = QColor(53,78,116) + self.line_audio_jack_sel = QColor(255,0,0) + self.line_audio_jack_glow = QColor(255,0,0) + self.line_midi_jack = QColor(139,32,32) + self.line_midi_jack_sel = QColor(255,0,0) + self.line_midi_jack_glow = QColor(255,0,0) + self.line_midi_a2j = QColor(120,65,33) + self.line_midi_a2j_sel = QColor(255,0,0) + self.line_midi_a2j_glow = QColor(255,0,0) + self.line_midi_alsa = QColor(81,130,36) + self.line_midi_alsa_sel = QColor(255,0,0) + self.line_midi_alsa_glow = QColor(255,0,0) + + self.rubberband_pen = QPen(QColor(147,151,143), 2, Qt.SolidLine) + self.rubberband_brush = QColor(35,61,99,100) + +def getDefaultTheme(): + return Theme.THEME_MODERN_DARK + +def getThemeName(idx): + if (idx == Theme.THEME_MODERN_DARK): + return "Modern Dark" + elif (idx == Theme.THEME_CLASSIC_DARK): + return "Classic Dark" + else: + return "" diff --git a/src/shared.py b/src/shared.py index 1064aa1..94da5dd 100644 --- a/src/shared.py +++ b/src/shared.py @@ -210,3 +210,40 @@ def showWindow(self): self.showMaximized() else: self.showNormal() + +# Shared Icons +def setIcons(self, modes): + if ("canvas" in modes): + self.act_canvas_arrange.setIcon(getIcon("view-sort-ascending")) + self.act_canvas_refresh.setIcon(getIcon("view-refresh")) + self.act_canvas_zoom_fit.setIcon(getIcon("zoom-fit-best")) + self.act_canvas_zoom_in.setIcon(getIcon("zoom-in")) + self.act_canvas_zoom_out.setIcon(getIcon("zoom-out")) + self.act_canvas_zoom_100.setIcon(getIcon("zoom-original")) + self.act_canvas_print.setIcon(getIcon("document-print")) + self.b_canvas_zoom_fit.setIcon(getIcon("zoom-fit-best")) + self.b_canvas_zoom_in.setIcon(getIcon("zoom-in")) + self.b_canvas_zoom_out.setIcon(getIcon("zoom-out")) + self.b_canvas_zoom_100.setIcon(getIcon("zoom-original")) + + if ("jack" in modes): + self.act_jack_clear_xruns.setIcon(getIcon("edit-clear")) + self.act_jack_configure.setIcon(getIcon("configure")) + self.act_jack_render.setIcon(getIcon("media-record")) + self.b_jack_clear_xruns.setIcon(getIcon("edit-clear")) + self.b_jack_configure.setIcon(getIcon("configure")) + self.b_jack_render.setIcon(getIcon("media-record")) + + if ("transport" in modes): + self.act_transport_play.setIcon(getIcon("media-playback-start")) + self.act_transport_stop.setIcon(getIcon("media-playback-stop")) + self.act_transport_backwards.setIcon(getIcon("media-seek-backward")) + self.act_transport_forwards.setIcon(getIcon("media-seek-forward")) + self.b_transport_play.setIcon(getIcon("media-playback-start")) + self.b_transport_stop.setIcon(getIcon("media-playback-stop")) + self.b_transport_backwards.setIcon(getIcon("media-seek-backward")) + self.b_transport_forwards.setIcon(getIcon("media-seek-forward")) + + if ("misc" in modes): + self.act_quit.setIcon(getIcon("application-exit")) + self.act_configure.setIcon(getIcon("configure"))