diff --git a/Makefile b/Makefile index 58649be..5102226 100644 --- a/Makefile +++ b/Makefile @@ -58,7 +58,7 @@ locale/%.qm: locale/%.ts # ----------------------------------------------------------------------------------------------------------------------------------------- # UI code -UI: cadence catarina catia tools +UI: cadence catarina catia claudia tools cadence: src/ui_cadence.py \ src/ui_cadence_tb_jack.py src/ui_cadence_tb_alsa.py src/ui_cadence_tb_a2j.py src/ui_cadence_tb_pa.py \ @@ -71,6 +71,11 @@ catarina: src/ui_catarina.py \ catia: src/ui_catia.py +claudia: src/ui_claudia.py \ + src/ui_claudia_studioname.py src/ui_claudia_studiolist.py \ + src/ui_claudia_createroom.py src/ui_claudia_projectname.py src/ui_claudia_projectproperties.py \ + src/ui_claudia_runcustom.py src/ui_claudia_launcher.py src/ui_claudia_launcher_app.py + tools: \ src/ui_logs.py src/ui_render.py \ src/ui_settings_app.py src/ui_settings_jack.py @@ -123,6 +128,8 @@ install: data/cadence-session-start \ data/catarina \ data/catia \ + data/claudia \ + data/claudia-launcher \ c++/jackmeter/cadence-jackmeter \ c++/xycontroller/cadence-xycontroller \ $(DESTDIR)$(PREFIX)/bin/ @@ -135,26 +142,36 @@ install: install -m 644 resources/16x16/cadence.png $(DESTDIR)$(PREFIX)/share/icons/hicolor/16x16/apps/ install -m 644 resources/16x16/catarina.png $(DESTDIR)$(PREFIX)/share/icons/hicolor/16x16/apps/ install -m 644 resources/16x16/catia.png $(DESTDIR)$(PREFIX)/share/icons/hicolor/16x16/apps/ + install -m 644 resources/16x16/claudia.png $(DESTDIR)$(PREFIX)/share/icons/hicolor/16x16/apps/ + install -m 644 resources/16x16/claudia-launcher.png $(DESTDIR)$(PREFIX)/share/icons/hicolor/16x16/apps/ # Install icons, 48x48 install -m 644 resources/48x48/cadence.png $(DESTDIR)$(PREFIX)/share/icons/hicolor/48x48/apps/ install -m 644 resources/48x48/catarina.png $(DESTDIR)$(PREFIX)/share/icons/hicolor/48x48/apps/ install -m 644 resources/48x48/catia.png $(DESTDIR)$(PREFIX)/share/icons/hicolor/48x48/apps/ + install -m 644 resources/48x48/claudia.png $(DESTDIR)$(PREFIX)/share/icons/hicolor/48x48/apps/ + install -m 644 resources/48x48/claudia-launcher.png $(DESTDIR)$(PREFIX)/share/icons/hicolor/48x48/apps/ # Install icons, 128x128 install -m 644 resources/128x128/cadence.png $(DESTDIR)$(PREFIX)/share/icons/hicolor/128x128/apps/ install -m 644 resources/128x128/catarina.png $(DESTDIR)$(PREFIX)/share/icons/hicolor/128x128/apps/ install -m 644 resources/128x128/catia.png $(DESTDIR)$(PREFIX)/share/icons/hicolor/128x128/apps/ + install -m 644 resources/128x128/claudia.png $(DESTDIR)$(PREFIX)/share/icons/hicolor/128x128/apps/ + install -m 644 resources/128x128/claudia-launcher.png $(DESTDIR)$(PREFIX)/share/icons/hicolor/128x128/apps/ # Install icons, 256x256 install -m 644 resources/256x256/cadence.png $(DESTDIR)$(PREFIX)/share/icons/hicolor/256x256/apps/ install -m 644 resources/256x256/catarina.png $(DESTDIR)$(PREFIX)/share/icons/hicolor/256x256/apps/ install -m 644 resources/256x256/catia.png $(DESTDIR)$(PREFIX)/share/icons/hicolor/256x256/apps/ + install -m 644 resources/256x256/claudia.png $(DESTDIR)$(PREFIX)/share/icons/hicolor/256x256/apps/ + install -m 644 resources/256x256/claudia-launcher.png $(DESTDIR)$(PREFIX)/share/icons/hicolor/256x256/apps/ # Install icons, scalable install -m 644 resources/scalable/cadence.svg $(DESTDIR)$(PREFIX)/share/icons/hicolor/scalable/apps/ install -m 644 resources/scalable/catarina.svg $(DESTDIR)$(PREFIX)/share/icons/hicolor/scalable/apps/ install -m 644 resources/scalable/catia.svg $(DESTDIR)$(PREFIX)/share/icons/hicolor/scalable/apps/ + install -m 644 resources/scalable/claudia.svg $(DESTDIR)$(PREFIX)/share/icons/hicolor/scalable/apps/ + install -m 644 resources/scalable/claudia-launcher.svg $(DESTDIR)$(PREFIX)/share/icons/hicolor/scalable/apps/ # Install main code install -m 755 src/*.py $(DESTDIR)$(PREFIX)/share/cadence/src/ @@ -170,6 +187,10 @@ install: install -m 644 data/pulse2loopback/* $(DESTDIR)$(PREFIX)/share/cadence/pulse2loopback/ install -m 755 data/61cadence-session-inject $(X11_RC_DIR) + # Install addtional stuff for Claudia + cp -r data/icons/* $(DESTDIR)$(PREFIX)/share/cadence/icons/ + cp -r data/templates/* $(DESTDIR)$(PREFIX)/share/cadence/templates/ + # Adjust PREFIX value in script files sed -i "s?X-PREFIX-X?$(PREFIX)?" \ $(DESTDIR)$(PREFIX)/bin/cadence \ @@ -182,6 +203,8 @@ install: $(DESTDIR)$(PREFIX)/bin/cadence-session-start \ $(DESTDIR)$(PREFIX)/bin/catarina \ $(DESTDIR)$(PREFIX)/bin/catia \ + $(DESTDIR)$(PREFIX)/bin/claudia \ + $(DESTDIR)$(PREFIX)/bin/claudia-launcher \ $(X11_RC_DIR)/61cadence-session-inject # Delete old files diff --git a/c++/patchcanvas.cpp b/c++/patchcanvas.cpp new file mode 100644 index 0000000..2af801f --- /dev/null +++ b/c++/patchcanvas.cpp @@ -0,0 +1,30 @@ +/* + * Patchbay Canvas engine using QGraphicsView/Scene + * Copyright (C) 2010-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 + */ + +#include "patchcanvas/patchcanvas.cpp" +#include "patchcanvas/patchcanvas-theme.cpp" +#include "patchcanvas/patchscene.cpp" +#include "patchcanvas/canvasbezierline.cpp" +#include "patchcanvas/canvasbezierlinemov.cpp" +#include "patchcanvas/canvasbox.cpp" +#include "patchcanvas/canvasboxshadow.cpp" +#include "patchcanvas/canvasfadeanimation.cpp" +#include "patchcanvas/canvasicon.cpp" +#include "patchcanvas/canvasline.cpp" +#include "patchcanvas/canvaslinemov.cpp" +#include "patchcanvas/canvasport.cpp" +#include "patchcanvas/canvasportglow.cpp" diff --git a/c++/patchcanvas.hpp b/c++/patchcanvas.hpp new file mode 100644 index 0000000..af4266a --- /dev/null +++ b/c++/patchcanvas.hpp @@ -0,0 +1,138 @@ +/* + * Patchbay Canvas engine using QGraphicsView/Scene + * Copyright (C) 2010-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 + */ + +#ifndef PATCHCANVAS_API_HPP +#define PATCHCANVAS_API_HPP + +#define START_NAMESPACE_PATCHCANVAS namespace PatchCanvas { +#define END_NAMESPACE_PATCHCANVAS } + +#ifndef PATCHCANVAS_ORGANISATION_NAME +# define PATCHCANVAS_ORGANISATION_NAME "PatchCanvas" +#endif + +#include "patchcanvas/patchcanvas-theme.h" +#include "patchcanvas/patchscene.h" + +START_NAMESPACE_PATCHCANVAS + +enum PortMode { + PORT_MODE_NULL = 0, + PORT_MODE_INPUT = 1, + PORT_MODE_OUTPUT = 2 +}; + +enum PortType { + PORT_TYPE_NULL = 0, + PORT_TYPE_AUDIO_JACK = 1, + PORT_TYPE_MIDI_JACK = 2, + PORT_TYPE_MIDI_A2J = 3, + PORT_TYPE_MIDI_ALSA = 4 +}; + +enum CallbackAction { + 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 +}; + +enum Icon { + ICON_HARDWARE = 0, + ICON_APPLICATION = 1, + ICON_LADISH_ROOM = 2 +}; + +enum SplitOption { + SPLIT_UNDEF = 0, + SPLIT_NO = 1, + SPLIT_YES = 2 +}; + +enum AntialiasingOption { + ANTIALIASING_NONE = 0, + ANTIALIASING_SMALL = 1, + ANTIALIASING_FULL = 2 +}; + +enum EyeCandyOption { + EYECANDY_NONE = 0, + EYECANDY_SMALL = 1, + EYECANDY_FULL = 2 +}; + +// Canvas options +struct options_t { + QString theme_name; + bool auto_hide_groups; + bool use_bezier_lines; + AntialiasingOption antialiasing; + EyeCandyOption eyecandy; +}; + +// Canvas features +struct features_t { + bool group_info; + bool group_rename; + bool port_info; + bool port_rename; + bool handle_group_pos; +}; + +typedef void (*Callback) (CallbackAction action, int value1, int value2, QString value_str); + +// API starts here +void setOptions(options_t* options); +void setFeatures(features_t* features); +void init(PatchScene* scene, Callback callback, bool debug=false); +void clear(); + +void setInitialPos(int x, int y); +void setCanvasSize(int x, int y, int width, int height); + +void addGroup(int group_id, QString group_name, SplitOption split=SPLIT_UNDEF, Icon icon=ICON_APPLICATION); +void removeGroup(int group_id); +void renameGroup(int group_id, QString new_group_name); +void splitGroup(int group_id); +void joinGroup(int group_id); +QPointF getGroupPos(int group_id, PortMode port_mode=PORT_MODE_OUTPUT); +void setGroupPos(int group_id, int group_pos_x, int group_pos_y); +void setGroupPos(int group_id, int group_pos_x, int group_pos_y, int group_pos_xs, int group_pos_ys); +void setGroupIcon(int group_id, Icon icon); + +void addPort(int group_id, int port_id, QString port_name, PortMode port_mode, PortType port_type); +void removePort(int port_id); +void renamePort(int port_id, QString new_port_name); + +void connectPorts(int connection_id, int port_out_id, int port_in_id); +void disconnectPorts(int connection_id); + +void arrange(); +void updateZValues(); + +// Theme +Theme::List getDefaultTheme(); +QString getThemeName(Theme::List id); +QString getDefaultThemeName(); + +END_NAMESPACE_PATCHCANVAS + +#endif // PATCHCANVAS_API_HPP diff --git a/c++/patchcanvas/abstractcanvasline.h b/c++/patchcanvas/abstractcanvasline.h new file mode 100644 index 0000000..e450388 --- /dev/null +++ b/c++/patchcanvas/abstractcanvasline.h @@ -0,0 +1,63 @@ +/* + * Patchbay Canvas engine using QGraphicsView/Scene + * Copyright (C) 2010-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 + */ + +#ifndef ABSTRACTCANVASLINE_H +#define ABSTRACTCANVASLINE_H + +#include "patchcanvas.h" + +START_NAMESPACE_PATCHCANVAS + +class AbstractCanvasLine +{ +public: + AbstractCanvasLine() {} + + virtual void deleteFromScene() = 0; + + virtual bool isLocked() const = 0; + virtual void setLocked(bool yesno) = 0; + + virtual bool isLineSelected() const = 0; + virtual void setLineSelected(bool yesno) = 0; + + virtual void updateLinePos() = 0; + + virtual int type() const = 0; + + // QGraphicsItem generic calls + virtual void setZValue(qreal z) = 0; +}; + +class AbstractCanvasLineMov +{ +public: + AbstractCanvasLineMov() {} + + virtual void deleteFromScene() = 0; + + virtual void updateLinePos(QPointF scenePos) = 0; + + virtual int type() const = 0; + + // QGraphicsItem generic calls + virtual void setZValue(qreal z) = 0; +}; + +END_NAMESPACE_PATCHCANVAS + +#endif // ABSTRACTCANVASLINE_H diff --git a/c++/patchcanvas/canvasbezierline.cpp b/c++/patchcanvas/canvasbezierline.cpp new file mode 100644 index 0000000..914bd67 --- /dev/null +++ b/c++/patchcanvas/canvasbezierline.cpp @@ -0,0 +1,162 @@ +/* + * Patchbay Canvas engine using QGraphicsView/Scene + * Copyright (C) 2010-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 + */ + +#include "canvasbezierline.h" + +#include + +#include "canvasport.h" +#include "canvasportglow.h" + +START_NAMESPACE_PATCHCANVAS + +CanvasBezierLine::CanvasBezierLine(CanvasPort* item1_, CanvasPort* item2_, QGraphicsItem* parent) : + QGraphicsPathItem(parent, canvas.scene) +{ + item1 = item1_; + item2 = item2_; + + m_locked = false; + m_lineSelected = false; + + setBrush(QColor(0,0,0,0)); + setGraphicsEffect(0); + updateLinePos(); +} + +CanvasBezierLine::~CanvasBezierLine() +{ + setGraphicsEffect(0); +} + +void CanvasBezierLine::deleteFromScene() +{ + canvas.scene->removeItem(this); + delete this; +} + +bool CanvasBezierLine::isLocked() const +{ + return m_locked; +} + +void CanvasBezierLine::setLocked(bool yesno) +{ + m_locked = yesno; +} + +bool CanvasBezierLine::isLineSelected() const +{ + return m_lineSelected; +} + +void CanvasBezierLine::setLineSelected(bool yesno) +{ + if (m_locked) + return; + + if (options.eyecandy == EYECANDY_FULL) + { + if (yesno) + setGraphicsEffect(new CanvasPortGlow(item1->getPortType(), toGraphicsObject())); + else + setGraphicsEffect(0); + } + + m_lineSelected = yesno; + updateLineGradient(); +} + +void CanvasBezierLine::updateLinePos() +{ + if (item1->getPortMode() == PORT_MODE_OUTPUT) + { + int item1_x = item1->scenePos().x() + item1->getPortWidth()+12; + int item1_y = item1->scenePos().y() + 7.5; + + int item2_x = item2->scenePos().x(); + int item2_y = item2->scenePos().y()+7.5; + + int item1_mid_x = abs(item1_x-item2_x)/2; + int item1_new_x = item1_x+item1_mid_x; + + int item2_mid_x = abs(item1_x-item2_x)/2; + int item2_new_x = item2_x-item2_mid_x; + + QPainterPath path(QPointF(item1_x, item1_y)); + path.cubicTo(item1_new_x, item1_y, item2_new_x, item2_y, item2_x, item2_y); + setPath(path); + + m_lineSelected = false; + updateLineGradient(); + } +} + +int CanvasBezierLine::type() const +{ + return CanvasBezierLineType; +} + +void CanvasBezierLine::updateLineGradient() +{ + short pos1, pos2; + int pos_top = boundingRect().top(); + int pos_bot = boundingRect().bottom(); + + if (item2->scenePos().y() >= item1->scenePos().y()) + { + pos1 = 0; + pos2 = 1; + } + else + { + pos1 = 1; + pos2 = 0; + } + + PortType port_type1 = item1->getPortType(); + PortType port_type2 = item2->getPortType(); + QLinearGradient port_gradient(0, pos_top, 0, pos_bot); + + if (port_type1 == PORT_TYPE_AUDIO_JACK) + port_gradient.setColorAt(pos1, m_lineSelected ? canvas.theme->line_audio_jack_sel : canvas.theme->line_audio_jack); + else if (port_type1 == PORT_TYPE_MIDI_JACK) + port_gradient.setColorAt(pos1, m_lineSelected ? canvas.theme->line_midi_jack_sel : canvas.theme->line_midi_jack); + else if (port_type1 == PORT_TYPE_MIDI_A2J) + port_gradient.setColorAt(pos1, m_lineSelected ? canvas.theme->line_midi_a2j_sel : canvas.theme->line_midi_a2j); + else if (port_type1 == PORT_TYPE_MIDI_ALSA) + port_gradient.setColorAt(pos1, m_lineSelected ? canvas.theme->line_midi_alsa_sel : canvas.theme->line_midi_alsa); + + if (port_type2 == PORT_TYPE_AUDIO_JACK) + port_gradient.setColorAt(pos2, m_lineSelected ? canvas.theme->line_audio_jack_sel : canvas.theme->line_audio_jack); + else if (port_type2 == PORT_TYPE_MIDI_JACK) + port_gradient.setColorAt(pos2, m_lineSelected ? canvas.theme->line_midi_jack_sel : canvas.theme->line_midi_jack); + else if (port_type2 == PORT_TYPE_MIDI_A2J) + port_gradient.setColorAt(pos2, m_lineSelected ? canvas.theme->line_midi_a2j_sel : canvas.theme->line_midi_a2j); + else if (port_type2 == PORT_TYPE_MIDI_ALSA) + port_gradient.setColorAt(pos2, m_lineSelected ? canvas.theme->line_midi_alsa_sel : canvas.theme->line_midi_alsa); + + setPen(QPen(port_gradient, 2)); +} + +void CanvasBezierLine::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) +{ + painter->setRenderHint(QPainter::Antialiasing, bool(options.antialiasing)); + QGraphicsPathItem::paint(painter, option, widget); +} + +END_NAMESPACE_PATCHCANVAS diff --git a/c++/patchcanvas/canvasbezierline.h b/c++/patchcanvas/canvasbezierline.h new file mode 100644 index 0000000..0422445 --- /dev/null +++ b/c++/patchcanvas/canvasbezierline.h @@ -0,0 +1,72 @@ +/* + * Patchbay Canvas engine using QGraphicsView/Scene + * Copyright (C) 2010-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 + */ + +#ifndef CANVASBEZIERLINE_H +#define CANVASBEZIERLINE_H + +#include + +#include "abstractcanvasline.h" + +class QPainter; + +START_NAMESPACE_PATCHCANVAS + +class CanvasPort; +class CanvasPortGlow; + +class CanvasBezierLine : + public AbstractCanvasLine, + public QGraphicsPathItem +{ +public: + CanvasBezierLine(CanvasPort* item1, CanvasPort* item2, QGraphicsItem* parent); + ~CanvasBezierLine(); + + virtual void deleteFromScene(); + + virtual bool isLocked() const; + virtual void setLocked(bool yesno); + + virtual bool isLineSelected() const; + virtual void setLineSelected(bool yesno); + + virtual void updateLinePos(); + + virtual int type() const; + + // QGraphicsItem generic calls + virtual void setZValue(qreal z) + { + QGraphicsPathItem::setZValue(z); + } + +private: + CanvasPort* item1; + CanvasPort* item2; + CanvasPortGlow* glow; + bool m_locked; + bool m_lineSelected; + + void updateLineGradient(); + + virtual void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget); +}; + +END_NAMESPACE_PATCHCANVAS + +#endif // CANVASBEZIERLINE_H diff --git a/c++/patchcanvas/canvasbezierlinemov.cpp b/c++/patchcanvas/canvasbezierlinemov.cpp new file mode 100644 index 0000000..b70fe26 --- /dev/null +++ b/c++/patchcanvas/canvasbezierlinemov.cpp @@ -0,0 +1,104 @@ +/* + * Patchbay Canvas engine using QGraphicsView/Scene + * Copyright (C) 2010-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 + */ + +#include "canvasbezierlinemov.h" + +#include + +#include "canvasport.h" + +START_NAMESPACE_PATCHCANVAS + +CanvasBezierLineMov::CanvasBezierLineMov(PortMode port_mode, PortType port_type, QGraphicsItem* parent) : + QGraphicsPathItem(parent, canvas.scene) +{ + m_port_mode = port_mode; + m_port_type = port_type; + + // Port position doesn't change while moving around line + p_itemX = scenePos().x(); + p_itemY = scenePos().y(); + p_width = ((CanvasPort*)parentItem())->getPortWidth(); + + QPen pen; + + if (port_type == PORT_TYPE_AUDIO_JACK) + pen = QPen(canvas.theme->line_audio_jack, 2); + else if (port_type == PORT_TYPE_MIDI_JACK) + pen = QPen(canvas.theme->line_midi_jack, 2); + else if (port_type == PORT_TYPE_MIDI_A2J) + pen = QPen(canvas.theme->line_midi_a2j, 2); + else if (port_type == PORT_TYPE_MIDI_ALSA) + pen = QPen(canvas.theme->line_midi_alsa, 2); + else + { + qWarning("PatchCanvas::CanvasBezierLineMov(%s, %s, %p) - invalid port type", port_mode2str(port_mode), port_type2str(port_type), parent); + pen = QPen(Qt::black); + } + + QColor color(0,0,0,0); + setBrush(color); + setPen(pen); +} + +void CanvasBezierLineMov::deleteFromScene() +{ + canvas.scene->removeItem(this); + delete this; +} + +void CanvasBezierLineMov::updateLinePos(QPointF scenePos) +{ + int old_x, old_y, mid_x, new_x, final_x, final_y; + + if (m_port_mode == PORT_MODE_INPUT) + { + old_x = 0; + old_y = 7.5; + mid_x = abs(scenePos.x()-p_itemX)/2; + new_x = old_x-mid_x; + } + else if (m_port_mode == PORT_MODE_OUTPUT) + { + old_x = p_width+12; + old_y = 7.5; + mid_x = abs(scenePos.x()-(p_itemX+old_x))/2; + new_x = old_x+mid_x; + } + else + return; + + final_x = scenePos.x()-p_itemX; + final_y = scenePos.y()-p_itemY; + + QPainterPath path(QPointF(old_x, old_y)); + path.cubicTo(new_x, old_y, new_x, final_y, final_x, final_y); + setPath(path); +} + +int CanvasBezierLineMov::type() const +{ + return CanvasBezierLineMovType; +} + +void CanvasBezierLineMov::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) +{ + painter->setRenderHint(QPainter::Antialiasing, bool(options.antialiasing)); + QGraphicsPathItem::paint(painter, option, widget); +} + +END_NAMESPACE_PATCHCANVAS diff --git a/c++/patchcanvas/canvasbezierlinemov.h b/c++/patchcanvas/canvasbezierlinemov.h new file mode 100644 index 0000000..857b7ff --- /dev/null +++ b/c++/patchcanvas/canvasbezierlinemov.h @@ -0,0 +1,60 @@ +/* + * Patchbay Canvas engine using QGraphicsView/Scene + * Copyright (C) 2010-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 + */ + +#ifndef CANVASBEZIERLINEMOV_H +#define CANVASBEZIERLINEMOV_H + +#include + +#include "abstractcanvasline.h" + +class QPainter; + +START_NAMESPACE_PATCHCANVAS + +class CanvasBezierLineMov : + public AbstractCanvasLineMov, + public QGraphicsPathItem +{ +public: + CanvasBezierLineMov(PortMode port_mode, PortType port_type, QGraphicsItem* parent); + + virtual void deleteFromScene(); + + virtual void updateLinePos(QPointF scenePos); + + virtual int type() const; + + // QGraphicsItem generic calls + virtual void setZValue(qreal z) + { + QGraphicsPathItem::setZValue(z); + } + +private: + PortMode m_port_mode; + PortType m_port_type; + int p_itemX; + int p_itemY; + int p_width; + + virtual void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget); +}; + +END_NAMESPACE_PATCHCANVAS + +#endif // CANVASBEZIERLINEMOV_H diff --git a/c++/patchcanvas/canvasbox.cpp b/c++/patchcanvas/canvasbox.cpp new file mode 100644 index 0000000..3fff9ab --- /dev/null +++ b/c++/patchcanvas/canvasbox.cpp @@ -0,0 +1,695 @@ +/* + * Patchbay Canvas engine using QGraphicsView/Scene + * Copyright (C) 2010-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 + */ + +#include "canvasbox.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "canvasline.h" +#include "canvasbezierline.h" +#include "canvasport.h" +#include "canvasboxshadow.h" +#include "canvasicon.h" + +START_NAMESPACE_PATCHCANVAS + +CanvasBox::CanvasBox(int group_id, QString group_name, Icon icon, QGraphicsItem* parent) : + QGraphicsItem(parent, canvas.scene) +{ + // Save Variables, useful for later + m_group_id = group_id; + m_group_name = group_name; + + // Base Variables + p_width = 50; + p_height = 25; + + m_last_pos = QPointF(); + m_splitted = false; + m_splitted_mode = PORT_MODE_NULL; + + m_cursor_moving = false; + m_forced_split = false; + m_mouse_down = false; + + m_port_list_ids.clear(); + m_connection_lines.clear(); + + // Set Font + m_font_name = QFont(canvas.theme->box_font_name, canvas.theme->box_font_size, canvas.theme->box_font_state); + m_font_port = QFont(canvas.theme->port_font_name, canvas.theme->port_font_size, canvas.theme->port_font_state); + + // Icon + icon_svg = new CanvasIcon(icon, group_name, this); + + // Shadow + if (options.eyecandy) + { + shadow = new CanvasBoxShadow(toGraphicsObject()); + shadow->setFakeParent(this); + setGraphicsEffect(shadow); + } + else + shadow = 0; + + // Final touches + setFlags(QGraphicsItem::ItemIsMovable|QGraphicsItem::ItemIsSelectable); + + // Wait for at least 1 port + if (options.auto_hide_groups) + setVisible(false); + + updatePositions(); +} + +CanvasBox::~CanvasBox() +{ + if (shadow) + delete shadow; + delete icon_svg; +} + +int CanvasBox::getGroupId() +{ + return m_group_id; +} + +QString CanvasBox::getGroupName() +{ + return m_group_name; +} + +bool CanvasBox::isSplitted() +{ + return m_splitted; +} + +PortMode CanvasBox::getSplittedMode() +{ + return m_splitted_mode; +} + +int CanvasBox::getPortCount() +{ + return m_port_list_ids.count(); +} + +QList CanvasBox::getPortList() +{ + return m_port_list_ids; +} + +void CanvasBox::setIcon(Icon icon) +{ + icon_svg->setIcon(icon, m_group_name); +} + +void CanvasBox::setSplit(bool split, PortMode mode) +{ + m_splitted = split; + m_splitted_mode = mode; +} + +void CanvasBox::setGroupName(QString group_name) +{ + m_group_name = group_name; + updatePositions(); +} + +void CanvasBox::setShadowOpacity(float opacity) +{ + if (shadow) + shadow->setOpacity(opacity); +} + +CanvasPort* CanvasBox::addPortFromGroup(int port_id, QString port_name, PortMode port_mode, PortType port_type) +{ + if (m_port_list_ids.count() == 0) + { + if (options.auto_hide_groups) + { + if (options.eyecandy == EYECANDY_FULL) + CanvasItemFX(this, true); + setVisible(true); + } + } + + CanvasPort* new_widget = new CanvasPort(port_id, port_name, port_mode, port_type, this); + + port_dict_t port_dict; + port_dict.group_id = 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; + + m_port_list_ids.append(port_id); + + return new_widget; +} + +void CanvasBox::removePortFromGroup(int port_id) +{ + if (m_port_list_ids.contains(port_id)) + { + m_port_list_ids.removeOne(port_id); + } + else + { + qCritical("PatchCanvas::CanvasBox->removePort(%i) - unable to find port to remove", port_id); + return; + } + + if (m_port_list_ids.count() > 0) + { + updatePositions(); + } + else if (isVisible()) + { + if (options.auto_hide_groups) + { + if (options.eyecandy == EYECANDY_FULL) + CanvasItemFX(this, false); + else + setVisible(false); + } + } +} + +void CanvasBox::addLineFromGroup(AbstractCanvasLine* line, int connection_id) +{ + cb_line_t new_cbline; + new_cbline.line = line; + new_cbline.connection_id = connection_id; + m_connection_lines.append(new_cbline); +} + +void CanvasBox::removeLineFromGroup(int connection_id) +{ + foreach2 (const cb_line_t& connection, m_connection_lines) + if (connection.connection_id == connection_id) + { + m_connection_lines.takeAt(i); + return; + } + } + + qCritical("PatchCanvas::CanvasBox->removeLineFromGroup(%i) - unable to find line to remove", connection_id); +} + +void CanvasBox::checkItemPos() +{ + if (canvas.size_rect.isNull() == false) + { + QPointF pos = scenePos(); + if (canvas.size_rect.contains(pos) == false || canvas.size_rect.contains(pos+QPointF(p_width, p_height)) == false) + { + if (pos.x() < canvas.size_rect.x()) + setPos(canvas.size_rect.x(), pos.y()); + else if (pos.x()+p_width > canvas.size_rect.width()) + setPos(canvas.size_rect.width()-p_width, pos.y()); + + pos = scenePos(); + if (pos.y() < canvas.size_rect.y()) + setPos(pos.x(), canvas.size_rect.y()); + else if (pos.y()+p_height > canvas.size_rect.height()) + setPos(pos.x(), canvas.size_rect.height()-p_height); + } + } +} + +void CanvasBox::removeIconFromScene() +{ + canvas.scene->removeItem(icon_svg); +} + +void CanvasBox::updatePositions() +{ + prepareGeometryChange(); + + int max_in_width = 0; + int max_in_height = 24; + int max_out_width = 0; + int max_out_height = 24; + bool have_audio_jack_in, have_audio_jack_out, have_midi_jack_in, have_midi_jack_out; + bool have_midi_a2j_in, have_midi_a2j_out, have_midi_alsa_in, have_midi_alsa_out; + 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 + p_width = 50; + p_height = 25; + + // Check Text Name size + int app_name_size = QFontMetrics(m_font_name).width(m_group_name)+30; + if (app_name_size > p_width) + p_width = app_name_size; + + // Get Port List + QList port_list; + foreach (const port_dict_t& port, canvas.port_list) + { + if (m_port_list_ids.contains(port.port_id)) + port_list.append(port); + } + + // Get Max Box Width/Height + foreach (const port_dict_t& port, port_list) + { + if (port.port_mode == PORT_MODE_INPUT) + { + max_in_height += 18; + + int size = QFontMetrics(m_font_port).width(port.port_name); + if (size > max_in_width) + max_in_width = size; + + if (port.port_type == PORT_TYPE_AUDIO_JACK && have_audio_jack_in == false) + { + have_audio_jack_in = true; + max_in_height += 2; + } + else if (port.port_type == PORT_TYPE_MIDI_JACK && have_midi_jack_in == false) + { + have_midi_jack_in = true; + max_in_height += 2; + } + else if (port.port_type == PORT_TYPE_MIDI_A2J && have_midi_a2j_in == false) + { + have_midi_a2j_in = true; + max_in_height += 2; + } + else if (port.port_type == PORT_TYPE_MIDI_ALSA && have_midi_alsa_in == false) + { + have_midi_alsa_in = true; + max_in_height += 2; + } + } + else if (port.port_mode == PORT_MODE_OUTPUT) + { + max_out_height += 18; + + int size = QFontMetrics(m_font_port).width(port.port_name); + if (size > max_out_width) + max_out_width = size; + + if (port.port_type == PORT_TYPE_AUDIO_JACK && have_audio_jack_out == false) + { + have_audio_jack_out = true; + max_out_height += 2; + } + else if (port.port_type == PORT_TYPE_MIDI_JACK && have_midi_jack_out == false) + { + have_midi_jack_out = true; + max_out_height += 2; + } + else if (port.port_type == PORT_TYPE_MIDI_A2J && have_midi_a2j_out == false) + { + have_midi_a2j_out = true; + max_out_height += 2; + } + else if (port.port_type == PORT_TYPE_MIDI_ALSA && have_midi_alsa_out == false) + { + have_midi_alsa_out = true; + max_out_height += 2; + } + } + } + + int final_width = 30 + max_in_width + max_out_width; + if (final_width > p_width) + p_width = final_width; + + if (max_in_height > p_height) + p_height = max_in_height; + + if (max_out_height > p_height) + p_height = max_out_height; + + // Remove bottom space + p_height -= 2; + + int last_in_pos = 24; + int last_out_pos = 24; + PortType last_in_type = PORT_TYPE_NULL; + PortType last_out_type = PORT_TYPE_NULL; + + // Re-position ports, AUDIO_JACK + foreach (const port_dict_t& port, port_list) + { + if (port.port_type == PORT_TYPE_AUDIO_JACK) + { + if (port.port_mode == PORT_MODE_INPUT) + { + port.widget->setPos(QPointF(1, last_in_pos)); + port.widget->setPortWidth(max_in_width); + + last_in_pos += 18; + last_in_type = port.port_type; + } + else if (port.port_mode == PORT_MODE_OUTPUT) + { + port.widget->setPos(QPointF(p_width-max_out_width-13, last_out_pos)); + port.widget->setPortWidth(max_out_width); + + last_out_pos += 18; + last_out_type = port.port_type; + } + } + } + + // Re-position ports, MIDI_JACK + foreach (const port_dict_t& port, port_list) + { + if (port.port_type == PORT_TYPE_MIDI_JACK) + { + if (port.port_mode == PORT_MODE_INPUT) + { + if (last_in_type != PORT_TYPE_NULL && port.port_type != last_in_type) + last_in_pos += 2; + + port.widget->setPos(QPointF(1, last_in_pos)); + port.widget->setPortWidth(max_in_width); + + last_in_pos += 18; + last_in_type = port.port_type; + } + else if (port.port_mode == PORT_MODE_OUTPUT) + { + if (last_out_type != PORT_TYPE_NULL && port.port_type != last_out_type) + last_out_pos += 2; + + port.widget->setPos(QPointF(p_width-max_out_width-13, last_out_pos)); + port.widget->setPortWidth(max_out_width); + + last_out_pos += 18; + last_out_type = port.port_type; + } + } + } + + // Re-position ports, MIDI_A2J + foreach (const port_dict_t& port, port_list) + { + if (port.port_type == PORT_TYPE_MIDI_A2J) + { + if (port.port_mode == PORT_MODE_INPUT) + { + if (last_in_type != PORT_TYPE_NULL && port.port_type != last_in_type) + last_in_pos += 2; + + port.widget->setPos(QPointF(1, last_in_pos)); + port.widget->setPortWidth(max_in_width); + + last_in_pos += 18; + last_in_type = port.port_type; + } + else if (port.port_mode == PORT_MODE_OUTPUT) + { + if (last_out_type != PORT_TYPE_NULL && port.port_type != last_out_type) + last_out_pos += 2; + + port.widget->setPos(QPointF(p_width-max_out_width-13, last_out_pos)); + port.widget->setPortWidth(max_out_width); + + last_out_pos += 18; + last_out_type = port.port_type; + } + } + } + + // Re-position ports, MIDI_ALSA + foreach (const port_dict_t& port, port_list) + { + if (port.port_type == PORT_TYPE_MIDI_ALSA) + { + if (port.port_mode == PORT_MODE_INPUT) + { + if (last_in_type != PORT_TYPE_NULL && port.port_type != last_in_type) + last_in_pos += 2; + + port.widget->setPos(QPointF(1, last_in_pos)); + port.widget->setPortWidth(max_in_width); + + last_in_pos += 18; + last_in_type = port.port_type; + } + else if (port.port_mode == PORT_MODE_OUTPUT) + { + if (last_out_type != PORT_TYPE_NULL && port.port_type != last_out_type) + last_out_pos += 2; + + port.widget->setPos(QPointF(p_width-max_out_width-13, last_out_pos)); + port.widget->setPortWidth(max_out_width); + + last_out_pos += 18; + last_out_type = port.port_type; + } + } + } + + repaintLines(true); + update(); +} + +void CanvasBox::repaintLines(bool forced) +{ + if (pos() != m_last_pos || forced) + { + foreach (const cb_line_t& connection, m_connection_lines) + connection.line->updateLinePos(); + } + + m_last_pos = pos(); +} + +void CanvasBox::resetLinesZValue() +{ + foreach (const connection_dict_t& connection, canvas.connection_list) + { + int z_value; + if (m_port_list_ids.contains(connection.port_out_id) && m_port_list_ids.contains(connection.port_in_id)) + z_value = canvas.last_z_value; + else + z_value = canvas.last_z_value-1; + + connection.widget->setZValue(z_value); + } +} + +int CanvasBox::type() const +{ + return CanvasBoxType; +} + +void CanvasBox::contextMenuEvent(QGraphicsSceneContextMenuEvent* event) +{ + QMenu menu; + QMenu discMenu("Disconnect", &menu); + + QList port_con_list; + QList port_con_list_ids; + + foreach (const int& port_id, m_port_list_ids) + { + QList tmp_port_con_list = CanvasGetPortConnectionList(port_id); + foreach (const int& port_con_id, tmp_port_con_list) + { + if (port_con_list.contains(port_con_id) == false) + { + port_con_list.append(port_con_id); + port_con_list_ids.append(port_id); + } + } + } + + if (port_con_list.count() > 0) + { + for (int i=0; i < port_con_list.count(); i++) + { + int port_con_id = CanvasGetConnectedPort(port_con_list[i], port_con_list_ids[i]); + QAction* act_x_disc = discMenu.addAction(CanvasGetFullPortName(port_con_id)); + act_x_disc->setData(port_con_list[i]); + QObject::connect(act_x_disc, SIGNAL(triggered()), canvas.qobject, SLOT(PortContextMenuDisconnect())); + } + } + else + { + QAction* act_x_disc = discMenu.addAction("No connections"); + act_x_disc->setEnabled(false); + } + + menu.addMenu(&discMenu); + QAction* act_x_disc_all = menu.addAction("Disconnect &All"); + QAction* act_x_sep1 = menu.addSeparator(); + QAction* act_x_info = menu.addAction("&Info"); + QAction* act_x_rename = menu.addAction("&Rename"); + QAction* act_x_sep2 = menu.addSeparator(); + QAction* act_x_split_join = menu.addAction(m_splitted ? "Join" : "Split"); + + if (features.group_info == false) + act_x_info->setVisible(false); + + if (features.group_rename == false) + act_x_rename->setVisible(false); + + if (features.group_info == false && features.group_rename == false) + act_x_sep1->setVisible(false); + + bool haveIns, haveOuts; + haveIns = haveOuts = false; + foreach (const port_dict_t& port, canvas.port_list) + { + if (m_port_list_ids.contains(port.port_id)) + { + if (port.port_mode == PORT_MODE_INPUT) + haveIns = true; + else if (port.port_mode == PORT_MODE_OUTPUT) + haveOuts = true; + } + } + + if (m_splitted == false && (haveIns && haveOuts) == false) + { + act_x_sep2->setVisible(false); + act_x_split_join->setVisible(false); + } + + QAction* act_selected = menu.exec(event->screenPos()); + + if (act_selected == act_x_disc_all) + { + foreach (const int& port_id, port_con_list) + canvas.callback(ACTION_PORTS_DISCONNECT, port_id, 0, ""); + } + else if (act_selected == act_x_info) + { + canvas.callback(ACTION_GROUP_INFO, m_group_id, 0, ""); + } + else if (act_selected == act_x_rename) + { + bool ok_check; + QString new_name = QInputDialog::getText(0, "Rename Group", "New name:", QLineEdit::Normal, m_group_name, &ok_check); + if (ok_check and !new_name.isEmpty()) + { + canvas.callback(ACTION_GROUP_RENAME, m_group_id, 0, new_name); + } + } + else if (act_selected == act_x_split_join) + { + if (m_splitted) + canvas.callback(ACTION_GROUP_JOIN, m_group_id, 0, ""); + else + canvas.callback(ACTION_GROUP_SPLIT, m_group_id, 0, ""); + + } + + event->accept(); +} + +void CanvasBox::mousePressEvent(QGraphicsSceneMouseEvent* event) +{ + canvas.last_z_value += 1; + setZValue(canvas.last_z_value); + resetLinesZValue(); + m_cursor_moving = false; + + if (event->button() == Qt::RightButton) + { + canvas.scene->clearSelection(); + setSelected(true); + m_mouse_down = false; + return event->accept(); + } + else if (event->button() == Qt::LeftButton) + { + if (sceneBoundingRect().contains(event->scenePos())) + m_mouse_down = true; + else + { + // Fixes a weird Qt behaviour with right-click mouseMove + m_mouse_down = false; + return event->ignore(); + } + } + else + m_mouse_down = false; + + QGraphicsItem::mousePressEvent(event); +} + +void CanvasBox::mouseMoveEvent(QGraphicsSceneMouseEvent* event) +{ + if (m_mouse_down) + { + if (m_cursor_moving == false) + { + setCursor(QCursor(Qt::SizeAllCursor)); + m_cursor_moving = true; + } + repaintLines(); + } + QGraphicsItem::mouseMoveEvent(event); +} + +void CanvasBox::mouseReleaseEvent(QGraphicsSceneMouseEvent* event) +{ + if (m_cursor_moving) + setCursor(QCursor(Qt::ArrowCursor)); + m_mouse_down = false; + m_cursor_moving = false; + QGraphicsItem::mouseReleaseEvent(event); +} + +QRectF CanvasBox::boundingRect() const +{ + return QRectF(0, 0, p_width, p_height); +} + +void CanvasBox::paint(QPainter* painter, const QStyleOptionGraphicsItem* /*option*/, QWidget* /*widget*/) +{ + painter->setRenderHint(QPainter::Antialiasing, false); + + if (isSelected()) + painter->setPen(canvas.theme->box_pen_sel); + else + painter->setPen(canvas.theme->box_pen); + + QLinearGradient box_gradient(0, 0, 0, p_height); + box_gradient.setColorAt(0, canvas.theme->box_bg_1); + box_gradient.setColorAt(1, canvas.theme->box_bg_2); + + painter->setBrush(box_gradient); + painter->drawRect(0, 0, p_width, p_height); + + QPointF text_pos(25, 16); + + painter->setFont(m_font_name); + painter->setPen(canvas.theme->box_text); + painter->drawText(text_pos, m_group_name); + + repaintLines(); +} + +END_NAMESPACE_PATCHCANVAS diff --git a/c++/patchcanvas/canvasbox.h b/c++/patchcanvas/canvasbox.h new file mode 100644 index 0000000..25b7eca --- /dev/null +++ b/c++/patchcanvas/canvasbox.h @@ -0,0 +1,108 @@ +/* + * Patchbay Canvas engine using QGraphicsView/Scene + * Copyright (C) 2010-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 + */ + +#ifndef CANVASBOX_H +#define CANVASBOX_H + +#include "patchcanvas.h" + +class QGraphicsSceneContextMenuEvent; +class QGraphicsSceneMouseEvent; +class QPainter; + +START_NAMESPACE_PATCHCANVAS + +class AbstractCanvasLine; +class CanvasBoxShadow; +class CanvasPort; +class CanvasIcon; + +struct cb_line_t { + AbstractCanvasLine* line; + int connection_id; +}; + +class CanvasBox : public QGraphicsItem +{ +public: + CanvasBox(int group_id, QString group_name, Icon icon, QGraphicsItem* parent=0); + virtual ~CanvasBox(); + + int getGroupId(); + QString getGroupName(); + bool isSplitted(); + PortMode getSplittedMode(); + + int getPortCount(); + QList getPortList(); + + void setIcon(Icon icon); + void setSplit(bool split, PortMode mode=PORT_MODE_NULL); + void setGroupName(QString group_name); + + void setShadowOpacity(float opacity); + + CanvasPort* addPortFromGroup(int port_id, QString port_name, PortMode port_mode, PortType port_type); + void removePortFromGroup(int port_id); + void addLineFromGroup(AbstractCanvasLine* line, int connection_id); + void removeLineFromGroup(int connection_id); + + void checkItemPos(); + void removeIconFromScene(); + + void updatePositions(); + void repaintLines(bool forced=false); + void resetLinesZValue(); + + virtual int type() const; + +private: + int m_group_id; + QString m_group_name; + + int p_width; + int p_height; + + QList m_port_list_ids; + QList m_connection_lines; + + QPointF m_last_pos; + bool m_splitted; + PortMode m_splitted_mode; + + bool m_forced_split; + bool m_cursor_moving; + bool m_mouse_down; + + QFont m_font_name; + QFont m_font_port; + + CanvasIcon* icon_svg; + CanvasBoxShadow* shadow; + + virtual void contextMenuEvent(QGraphicsSceneContextMenuEvent* event); + virtual void mousePressEvent(QGraphicsSceneMouseEvent* event); + virtual void mouseMoveEvent(QGraphicsSceneMouseEvent* event); + virtual void mouseReleaseEvent(QGraphicsSceneMouseEvent* event); + + virtual QRectF boundingRect() const; + virtual void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget); +}; + +END_NAMESPACE_PATCHCANVAS + +#endif // CANVASBOX_H diff --git a/c++/patchcanvas/canvasboxshadow.cpp b/c++/patchcanvas/canvasboxshadow.cpp new file mode 100644 index 0000000..fc80dac --- /dev/null +++ b/c++/patchcanvas/canvasboxshadow.cpp @@ -0,0 +1,53 @@ +/* + * Patchbay Canvas engine using QGraphicsView/Scene + * Copyright (C) 2010-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 + */ + +#include "canvasboxshadow.h" + +#include "canvasbox.h" + +START_NAMESPACE_PATCHCANVAS + +CanvasBoxShadow::CanvasBoxShadow(QObject* parent) : + QGraphicsDropShadowEffect(parent) +{ + m_fakeParent = 0; + + setBlurRadius(20); + setColor(canvas.theme->box_shadow); + setOffset(0, 0); +} + +void CanvasBoxShadow::setFakeParent(CanvasBox* fakeParent) +{ + m_fakeParent = fakeParent; +} + +void CanvasBoxShadow::setOpacity(float opacity) +{ + QColor color(canvas.theme->box_shadow); + color.setAlphaF(opacity); + setColor(color); +} + +void CanvasBoxShadow::draw(QPainter* painter) +{ + if (m_fakeParent) + m_fakeParent->repaintLines(); + QGraphicsDropShadowEffect::draw(painter); +} + +END_NAMESPACE_PATCHCANVAS diff --git a/c++/patchcanvas/canvasboxshadow.h b/c++/patchcanvas/canvasboxshadow.h new file mode 100644 index 0000000..f56eb56 --- /dev/null +++ b/c++/patchcanvas/canvasboxshadow.h @@ -0,0 +1,45 @@ +/* + * Patchbay Canvas engine using QGraphicsView/Scene + * Copyright (C) 2010-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 + */ + +#ifndef CANVASBOXSHADOW_H +#define CANVASBOXSHADOW_H + +#include + +#include "patchcanvas.h" + +START_NAMESPACE_PATCHCANVAS + +class CanvasBox; + +class CanvasBoxShadow : public QGraphicsDropShadowEffect +{ +public: + CanvasBoxShadow(QObject* parent); + void setFakeParent(CanvasBox* fakeParent); + void setOpacity(float opacity); + +protected: + virtual void draw(QPainter* painter); + +private: + CanvasBox* m_fakeParent; +}; + +END_NAMESPACE_PATCHCANVAS + +#endif // CANVASBOXSHADOW_H diff --git a/c++/patchcanvas/canvasfadeanimation.cpp b/c++/patchcanvas/canvasfadeanimation.cpp new file mode 100644 index 0000000..0a293e6 --- /dev/null +++ b/c++/patchcanvas/canvasfadeanimation.cpp @@ -0,0 +1,79 @@ +/* + * Patchbay Canvas engine using QGraphicsView/Scene + * Copyright (C) 2010-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 + */ + +#include "canvasfadeanimation.h" + +#include "canvasbox.h" + +START_NAMESPACE_PATCHCANVAS + +CanvasFadeAnimation::CanvasFadeAnimation(QGraphicsItem* item, bool show, QObject* parent) : + QAbstractAnimation(parent) +{ + m_show = show; + m_duration = 0; + m_item = item; +} + +QGraphicsItem* CanvasFadeAnimation::item() +{ + return m_item; +} + +void CanvasFadeAnimation::setDuration(int time) +{ + if (m_show == false && m_item->opacity() == 0.0) + m_duration = 0; + else + { + m_item->show(); + m_duration = time; + } +} + +int CanvasFadeAnimation::duration() const +{ + return m_duration; +} + +void CanvasFadeAnimation::updateCurrentTime(int time) +{ + if (m_duration == 0) + return; + + float value; + + if (m_show) + value = float(time)/m_duration; + else + value = 1.0-(float(time)/m_duration); + + m_item->setOpacity(value); + + if (m_item->type() == CanvasBoxType) + ((CanvasBox*)m_item)->setShadowOpacity(value); +} + +void CanvasFadeAnimation::updateState(QAbstractAnimation::State /*newState*/, QAbstractAnimation::State /*oldState*/) +{ +} + +void CanvasFadeAnimation::updateDirection(QAbstractAnimation::Direction /*direction*/) +{ +} + +END_NAMESPACE_PATCHCANVAS diff --git a/c++/patchcanvas/canvasfadeanimation.h b/c++/patchcanvas/canvasfadeanimation.h new file mode 100644 index 0000000..9f5d005 --- /dev/null +++ b/c++/patchcanvas/canvasfadeanimation.h @@ -0,0 +1,52 @@ +/* + * Patchbay Canvas engine using QGraphicsView/Scene + * Copyright (C) 2010-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 + */ + +#ifndef CANVASFADEANIMATION_H +#define CANVASFADEANIMATION_H + +#include + +#include "patchcanvas.h" + +class QGraphicsItem; + +START_NAMESPACE_PATCHCANVAS + +class CanvasFadeAnimation : public QAbstractAnimation +{ +public: + CanvasFadeAnimation(QGraphicsItem* item, bool show, QObject* parent=0); + + QGraphicsItem* item(); + void setDuration(int time); + + virtual int duration() const; + +protected: + virtual void updateCurrentTime(int time); + virtual void updateState(QAbstractAnimation::State newState, QAbstractAnimation::State oldState); + virtual void updateDirection(QAbstractAnimation::Direction direction); + +private: + bool m_show; + int m_duration; + QGraphicsItem* m_item; +}; + +END_NAMESPACE_PATCHCANVAS + +#endif // CANVASFADEANIMATION_H diff --git a/c++/patchcanvas/canvasicon.cpp b/c++/patchcanvas/canvasicon.cpp new file mode 100644 index 0000000..795ffa7 --- /dev/null +++ b/c++/patchcanvas/canvasicon.cpp @@ -0,0 +1,134 @@ +/* + * Patchbay Canvas engine using QGraphicsView/Scene + * Copyright (C) 2010-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 + */ + +#include "canvasicon.h" + +#include +#include +#include + +START_NAMESPACE_PATCHCANVAS + +CanvasIcon::CanvasIcon(Icon icon, QString name, QGraphicsItem* parent) : + QGraphicsSvgItem(parent) +{ + m_renderer = 0; + p_size = QRectF(0, 0, 0, 0); + + m_colorFX = new QGraphicsColorizeEffect(this); + m_colorFX->setColor(canvas.theme->box_text.color()); + + setGraphicsEffect(m_colorFX); + setIcon(icon, name); +} + +CanvasIcon::~CanvasIcon() +{ + if (m_renderer) + delete m_renderer; + delete m_colorFX; +} + +void CanvasIcon::setIcon(Icon icon, QString name) +{ + name = name.toLower(); + QString icon_path; + + if (icon == ICON_APPLICATION) + { + p_size = QRectF(3, 2, 19, 18); + + if (name.contains("audacious")) + { + p_size = QRectF(5, 4, 16, 16); + icon_path = ":/scalable/pb_audacious.svg"; + } + else if (name.contains("clementine")) + { + p_size = QRectF(5, 4, 16, 16); + icon_path = ":/scalable/pb_clementine.svg"; + } + else if (name.contains("jamin")) + { + p_size = QRectF(5, 3, 16, 16); + icon_path = ":/scalable/pb_jamin.svg"; + } + else if (name.contains("mplayer")) + { + p_size = QRectF(5, 4, 16, 16); + icon_path = ":/scalable/pb_mplayer.svg"; + } + else if (name.contains("vlc")) + { + p_size = QRectF(5, 3, 16, 16); + icon_path = ":/scalable/pb_vlc.svg"; + } + else + { + p_size = QRectF(5, 3, 16, 16); + icon_path = ":/scalable/pb_generic.svg"; + } + } + else if (icon == ICON_HARDWARE) + { + p_size = QRectF(5, 2, 16, 16); + icon_path = ":/scalable/pb_hardware.svg"; + + } + else if (icon == ICON_LADISH_ROOM) + { + p_size = QRectF(5, 2, 16, 16); + icon_path = ":/scalable/pb_hardware.svg"; + } + else + { + p_size = QRectF(0, 0, 0, 0); + qCritical("PatchCanvas::CanvasIcon->setIcon(%s, %s) - unsupported Icon requested", icon2str(icon), name.toUtf8().constData()); + return; + } + + if (m_renderer) + delete m_renderer; + + m_renderer = new QSvgRenderer(icon_path, canvas.scene); + setSharedRenderer(m_renderer); + update(); +} + +int CanvasIcon::type() const +{ + return CanvasIconType; +} + +QRectF CanvasIcon::boundingRect() const +{ + return p_size; +} + +void CanvasIcon::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) +{ + if (m_renderer) + { + painter->setRenderHint(QPainter::Antialiasing, false); + painter->setRenderHint(QPainter::TextAntialiasing, false); + m_renderer->render(painter, p_size); + } + else + QGraphicsSvgItem::paint(painter, option, widget); +} + +END_NAMESPACE_PATCHCANVAS diff --git a/c++/patchcanvas/canvasicon.h b/c++/patchcanvas/canvasicon.h new file mode 100644 index 0000000..2645732 --- /dev/null +++ b/c++/patchcanvas/canvasicon.h @@ -0,0 +1,52 @@ +/* + * Patchbay Canvas engine using QGraphicsView/Scene + * Copyright (C) 2010-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 + */ + +#ifndef CANVASICON_H +#define CANVASICON_H + +#include + +#include "patchcanvas.h" + +class QPainter; +class QGraphicsColorizeEffect; +class QSvgRenderer; + +START_NAMESPACE_PATCHCANVAS + +class CanvasIcon : public QGraphicsSvgItem +{ +public: + CanvasIcon(Icon icon, QString name, QGraphicsItem* parent); + ~CanvasIcon(); + + void setIcon(Icon icon, QString name); + + virtual int type() const; + +private: + QGraphicsColorizeEffect* m_colorFX; + QSvgRenderer* m_renderer; + QRectF p_size; + + virtual QRectF boundingRect() const; + virtual void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget); +}; + +END_NAMESPACE_PATCHCANVAS + +#endif // CANVASICON_H diff --git a/c++/patchcanvas/canvasline.cpp b/c++/patchcanvas/canvasline.cpp new file mode 100644 index 0000000..3313d90 --- /dev/null +++ b/c++/patchcanvas/canvasline.cpp @@ -0,0 +1,148 @@ +/* + * Patchbay Canvas engine using QGraphicsView/Scene + * Copyright (C) 2010-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 + */ + +#include "canvasline.h" + +#include + +#include "canvasport.h" +#include "canvasportglow.h" + +START_NAMESPACE_PATCHCANVAS + +CanvasLine::CanvasLine(CanvasPort* item1_, CanvasPort* item2_, QGraphicsItem* parent) : + QGraphicsLineItem(parent, canvas.scene) +{ + item1 = item1_; + item2 = item2_; + + m_locked = false; + m_lineSelected = false; + + setGraphicsEffect(0); + updateLinePos(); +} + +CanvasLine::~CanvasLine() +{ + setGraphicsEffect(0); +} + +void CanvasLine::deleteFromScene() +{ + canvas.scene->removeItem(this); + delete this; +} + +bool CanvasLine::isLocked() const +{ + return m_locked; +} + +void CanvasLine::setLocked(bool yesno) +{ + m_locked = yesno; +} + +bool CanvasLine::isLineSelected() const +{ + return m_lineSelected; +} + +void CanvasLine::setLineSelected(bool yesno) +{ + if (m_locked) + return; + + if (options.eyecandy == EYECANDY_FULL) + { + if (yesno) + setGraphicsEffect(new CanvasPortGlow(item1->getPortType(), toGraphicsObject())); + else + setGraphicsEffect(0); + } + + m_lineSelected = yesno; + updateLineGradient(); +} + +void CanvasLine::updateLinePos() +{ + if (item1->getPortMode() == PORT_MODE_OUTPUT) + { + QLineF line(item1->scenePos().x() + item1->getPortWidth()+12, item1->scenePos().y()+7.5, item2->scenePos().x(), item2->scenePos().y()+7.5); + setLine(line); + + m_lineSelected = false; + updateLineGradient(); + } +} + +int CanvasLine::type() const +{ + return CanvasLineType; +} + +void CanvasLine::updateLineGradient() +{ + short pos1, pos2; + int pos_top = boundingRect().top(); + int pos_bot = boundingRect().bottom(); + + if (item2->scenePos().y() >= item1->scenePos().y()) + { + pos1 = 0; + pos2 = 1; + } + else + { + pos1 = 1; + pos2 = 0; + } + + PortType port_type1 = item1->getPortType(); + PortType port_type2 = item2->getPortType(); + QLinearGradient port_gradient(0, pos_top, 0, pos_bot); + + if (port_type1 == PORT_TYPE_AUDIO_JACK) + port_gradient.setColorAt(pos1, m_lineSelected ? canvas.theme->line_audio_jack_sel : canvas.theme->line_audio_jack); + else if (port_type1 == PORT_TYPE_MIDI_JACK) + port_gradient.setColorAt(pos1, m_lineSelected ? canvas.theme->line_midi_jack_sel : canvas.theme->line_midi_jack); + else if (port_type1 == PORT_TYPE_MIDI_A2J) + port_gradient.setColorAt(pos1, m_lineSelected ? canvas.theme->line_midi_a2j_sel : canvas.theme->line_midi_a2j); + else if (port_type1 == PORT_TYPE_MIDI_ALSA) + port_gradient.setColorAt(pos1, m_lineSelected ? canvas.theme->line_midi_alsa_sel : canvas.theme->line_midi_alsa); + + if (port_type2 == PORT_TYPE_AUDIO_JACK) + port_gradient.setColorAt(pos2, m_lineSelected ? canvas.theme->line_audio_jack_sel : canvas.theme->line_audio_jack); + else if (port_type2 == PORT_TYPE_MIDI_JACK) + port_gradient.setColorAt(pos2, m_lineSelected ? canvas.theme->line_midi_jack_sel : canvas.theme->line_midi_jack); + else if (port_type2 == PORT_TYPE_MIDI_A2J) + port_gradient.setColorAt(pos2, m_lineSelected ? canvas.theme->line_midi_a2j_sel : canvas.theme->line_midi_a2j); + else if (port_type2 == PORT_TYPE_MIDI_ALSA) + port_gradient.setColorAt(pos2, m_lineSelected ? canvas.theme->line_midi_alsa_sel : canvas.theme->line_midi_alsa); + + setPen(QPen(port_gradient, 2)); +} + +void CanvasLine::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) +{ + painter->setRenderHint(QPainter::Antialiasing, bool(options.antialiasing)); + QGraphicsLineItem::paint(painter, option, widget); +} + +END_NAMESPACE_PATCHCANVAS diff --git a/c++/patchcanvas/canvasline.h b/c++/patchcanvas/canvasline.h new file mode 100644 index 0000000..f0efd28 --- /dev/null +++ b/c++/patchcanvas/canvasline.h @@ -0,0 +1,72 @@ +/* + * Patchbay Canvas engine using QGraphicsView/Scene + * Copyright (C) 2010-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 + */ + +#ifndef CANVASLINE_H +#define CANVASLINE_H + +#include + +#include "abstractcanvasline.h" + +class QPainter; + +START_NAMESPACE_PATCHCANVAS + +class CanvasPort; +class CanvasPortGlow; + +class CanvasLine : + public AbstractCanvasLine, + public QGraphicsLineItem +{ +public: + CanvasLine(CanvasPort* item1, CanvasPort* item2, QGraphicsItem* parent); + ~CanvasLine(); + + virtual void deleteFromScene(); + + virtual bool isLocked() const; + virtual void setLocked(bool yesno); + + virtual bool isLineSelected() const; + virtual void setLineSelected(bool yesno); + + virtual void updateLinePos(); + + virtual int type() const; + + // QGraphicsItem generic calls + virtual void setZValue(qreal z) + { + QGraphicsLineItem::setZValue(z); + } + +private: + CanvasPort* item1; + CanvasPort* item2; + CanvasPortGlow* glow; + bool m_locked; + bool m_lineSelected; + + void updateLineGradient(); + + virtual void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget); +}; + +END_NAMESPACE_PATCHCANVAS + +#endif // CANVASLINE_H diff --git a/c++/patchcanvas/canvaslinemov.cpp b/c++/patchcanvas/canvaslinemov.cpp new file mode 100644 index 0000000..617aa5e --- /dev/null +++ b/c++/patchcanvas/canvaslinemov.cpp @@ -0,0 +1,94 @@ +/* + * Patchbay Canvas engine using QGraphicsView/Scene + * Copyright (C) 2010-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 + */ + +#include "canvaslinemov.h" + +#include + +#include "canvasport.h" + +START_NAMESPACE_PATCHCANVAS + +CanvasLineMov::CanvasLineMov(PortMode port_mode, PortType port_type, QGraphicsItem* parent) : + QGraphicsLineItem(parent, canvas.scene) +{ + m_port_mode = port_mode; + m_port_type = port_type; + + // Port position doesn't change while moving around line + p_lineX = scenePos().x(); + p_lineY = scenePos().y(); + p_width = ((CanvasPort*)parentItem())->getPortWidth(); + + QPen pen; + + if (port_type == PORT_TYPE_AUDIO_JACK) + pen = QPen(canvas.theme->line_audio_jack, 2); + else if (port_type == PORT_TYPE_MIDI_JACK) + pen = QPen(canvas.theme->line_midi_jack, 2); + else if (port_type == PORT_TYPE_MIDI_A2J) + pen = QPen(canvas.theme->line_midi_a2j, 2); + else if (port_type == PORT_TYPE_MIDI_ALSA) + pen = QPen(canvas.theme->line_midi_alsa, 2); + else + { + qWarning("PatchCanvas::CanvasLineMov(%s, %s, %p) - invalid port type", port_mode2str(port_mode), port_type2str(port_type), parent); + pen = QPen(Qt::black); + } + + setPen(pen); +} + +void CanvasLineMov::deleteFromScene() +{ + canvas.scene->removeItem(this); + delete this; +} + +void CanvasLineMov::updateLinePos(QPointF scenePos) +{ + int item_pos[2] = { 0, 0 }; + + if (m_port_mode == PORT_MODE_INPUT) + { + item_pos[0] = 0; + item_pos[1] = 7.5; + } + else if (m_port_mode == PORT_MODE_OUTPUT) + { + item_pos[0] = p_width+12; + item_pos[1] = 7.5; + } + else + return; + + QLineF line(item_pos[0], item_pos[1], scenePos.x()-p_lineX, scenePos.y()-p_lineY); + setLine(line); +} + +int CanvasLineMov::type() const +{ + return CanvasLineMovType; +} + +void CanvasLineMov::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) +{ + painter->setRenderHint(QPainter::Antialiasing, bool(options.antialiasing)); + QGraphicsLineItem::paint(painter, option, widget); +} + +END_NAMESPACE_PATCHCANVAS diff --git a/c++/patchcanvas/canvaslinemov.h b/c++/patchcanvas/canvaslinemov.h new file mode 100644 index 0000000..5b5baf5 --- /dev/null +++ b/c++/patchcanvas/canvaslinemov.h @@ -0,0 +1,60 @@ +/* + * Patchbay Canvas engine using QGraphicsView/Scene + * Copyright (C) 2010-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 + */ + +#ifndef CANVASLINEMOV_H +#define CANVASLINEMOV_H + +#include + +#include "abstractcanvasline.h" + +class QPainter; + +START_NAMESPACE_PATCHCANVAS + +class CanvasLineMov : + public AbstractCanvasLineMov, + public QGraphicsLineItem +{ +public: + CanvasLineMov(PortMode port_mode, PortType port_type, QGraphicsItem* parent); + + virtual void deleteFromScene(); + + virtual void updateLinePos(QPointF scenePos); + + virtual int type() const; + + // QGraphicsItem generic calls + virtual void setZValue(qreal z) + { + QGraphicsLineItem::setZValue(z); + } + +private: + PortMode m_port_mode; + PortType m_port_type; + int p_lineX; + int p_lineY; + int p_width; + + virtual void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget); +}; + +END_NAMESPACE_PATCHCANVAS + +#endif // CANVASLINEMOV_H diff --git a/c++/patchcanvas/canvasport.cpp b/c++/patchcanvas/canvasport.cpp new file mode 100644 index 0000000..96ddb81 --- /dev/null +++ b/c++/patchcanvas/canvasport.cpp @@ -0,0 +1,447 @@ +/* + * Patchbay Canvas engine using QGraphicsView/Scene + * Copyright (C) 2010-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 + */ + +#include "canvasport.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "canvaslinemov.h" +#include "canvasbezierlinemov.h" +#include "canvasbox.h" + +START_NAMESPACE_PATCHCANVAS + +CanvasPort::CanvasPort(int port_id, QString port_name, PortMode port_mode, PortType port_type, QGraphicsItem* parent) : + QGraphicsItem(parent, canvas.scene) +{ + // Save Variables, useful for later + m_port_id = port_id; + m_port_mode = port_mode; + m_port_type = port_type; + m_port_name = port_name; + + // Base Variables + m_port_width = 15; + m_port_height = 15; + m_port_font = QFont(canvas.theme->port_font_name, canvas.theme->port_font_size, canvas.theme->port_font_state); + + m_line_mov = 0; + m_hover_item = 0; + m_last_selected_state = false; + + m_mouse_down = false; + m_cursor_moving = false; + + setFlags(QGraphicsItem::ItemIsSelectable); +} + +int CanvasPort::getPortId() +{ + return m_port_id; +} + +PortMode CanvasPort::getPortMode() +{ + return m_port_mode; +} + +PortType CanvasPort::getPortType() +{ + return m_port_type; +} + +QString CanvasPort::getPortName() +{ + return m_port_name; +} + +QString CanvasPort::getFullPortName() +{ + return ((CanvasBox*)parentItem())->getGroupName()+":"+m_port_name; +} + +int CanvasPort::getPortWidth() +{ + return m_port_width; +} + +int CanvasPort::getPortHeight() +{ + return m_port_height; +} + +void CanvasPort::setPortMode(PortMode port_mode) +{ + m_port_mode = port_mode; + update(); +} + +void CanvasPort::setPortType(PortType port_type) +{ + m_port_type = port_type; + update(); +} + +void CanvasPort::setPortName(QString port_name) +{ + if (QFontMetrics(m_port_font).width(port_name) < QFontMetrics(m_port_font).width(m_port_name)) + QTimer::singleShot(0, canvas.scene, SLOT(update())); + + m_port_name = port_name; + update(); +} + +void CanvasPort::setPortWidth(int port_width) +{ + if (port_width < m_port_width) + QTimer::singleShot(0, canvas.scene, SLOT(update())); + + m_port_width = port_width; + update(); +} + +int CanvasPort::type() const +{ + return CanvasPortType; +} + +void CanvasPort::mousePressEvent(QGraphicsSceneMouseEvent* event) +{ + m_hover_item = 0; + m_mouse_down = (event->button() == Qt::LeftButton); + m_cursor_moving = false; + QGraphicsItem::mousePressEvent(event); +} + +void CanvasPort::mouseMoveEvent(QGraphicsSceneMouseEvent* event) +{ + if (m_mouse_down) + { + if (m_cursor_moving == false) + { + setCursor(QCursor(Qt::CrossCursor)); + m_cursor_moving = true; + + foreach (const connection_dict_t& connection, canvas.connection_list) + { + if (connection.port_out_id == m_port_id || connection.port_in_id == m_port_id) + connection.widget->setLocked(true); + } + } + + if (! m_line_mov) + { + if (options.use_bezier_lines) + m_line_mov = new CanvasBezierLineMov(m_port_mode, m_port_type, this); + else + m_line_mov = new CanvasLineMov(m_port_mode, m_port_type, this); + + canvas.last_z_value += 1; + m_line_mov->setZValue(canvas.last_z_value); + canvas.last_z_value += 1; + parentItem()->setZValue(canvas.last_z_value); + } + + CanvasPort* item = 0; + QList items = canvas.scene->items(event->scenePos(), Qt::ContainsItemShape, Qt::AscendingOrder); + for (int i=0; i < items.count(); i++) + { + if (items[i]->type() == CanvasPortType) + { + if (items[i] != this) + { + if (! item) + item = (CanvasPort*)items[i]; + else if (items[i]->parentItem()->zValue() > item->parentItem()->zValue()) + item = (CanvasPort*)items[i]; + } + } + } + + if (m_hover_item and m_hover_item != item) + m_hover_item->setSelected(false); + + if (item) + { + bool a2j_connection = (item->getPortType() == PORT_TYPE_MIDI_JACK && m_port_type == PORT_TYPE_MIDI_A2J) || (item->getPortType() == PORT_TYPE_MIDI_A2J && m_port_type == PORT_TYPE_MIDI_JACK); + if (item->getPortMode() != m_port_mode && (item->getPortType() == m_port_type || a2j_connection)) + { + item->setSelected(true); + m_hover_item = item; + } + else + m_hover_item = 0; + } + else + m_hover_item = 0; + + m_line_mov->updateLinePos(event->scenePos()); + return event->accept(); + } + + QGraphicsItem::mouseMoveEvent(event); +} + +void CanvasPort::mouseReleaseEvent(QGraphicsSceneMouseEvent* event) +{ + if (m_mouse_down) + { + if (m_line_mov) + { + m_line_mov->deleteFromScene(); + m_line_mov = 0; + } + + foreach (const connection_dict_t& connection, canvas.connection_list) + { + if (connection.port_out_id == m_port_id || connection.port_in_id == m_port_id) + connection.widget->setLocked(false); + } + + if (m_hover_item) + { + bool check = false; + foreach (const connection_dict_t& connection, canvas.connection_list) + { + if ( (connection.port_out_id == m_port_id && connection.port_in_id == m_hover_item->getPortId()) || + (connection.port_out_id == m_hover_item->getPortId() && connection.port_in_id == m_port_id) ) + { + canvas.callback(ACTION_PORTS_DISCONNECT, connection.connection_id, 0, ""); + check = true; + break; + } + } + + if (check == false) + { + if (m_port_mode == PORT_MODE_OUTPUT) + canvas.callback(ACTION_PORTS_CONNECT, m_port_id, m_hover_item->getPortId(), ""); + else + canvas.callback(ACTION_PORTS_CONNECT, m_hover_item->getPortId(), m_port_id, ""); + } + + canvas.scene->clearSelection(); + } + } + + if (m_cursor_moving) + setCursor(QCursor(Qt::ArrowCursor)); + + m_hover_item = 0; + m_mouse_down = false; + m_cursor_moving = false; + QGraphicsItem::mouseReleaseEvent(event); +} + +void CanvasPort::contextMenuEvent(QGraphicsSceneContextMenuEvent* event) +{ + canvas.scene->clearSelection(); + setSelected(true); + + QMenu menu; + QMenu discMenu("Disconnect", &menu); + + QList port_con_list = CanvasGetPortConnectionList(m_port_id); + + if (port_con_list.count() > 0) + { + foreach (int port_id, port_con_list) + { + int port_con_id = CanvasGetConnectedPort(port_id, m_port_id); + QAction* act_x_disc = discMenu.addAction(CanvasGetFullPortName(port_con_id)); + act_x_disc->setData(port_id); + QObject::connect(act_x_disc, SIGNAL(triggered()), canvas.qobject, SLOT(PortContextMenuDisconnect())); + } + } + else + { + QAction* act_x_disc = discMenu.addAction("No connections"); + act_x_disc->setEnabled(false); + } + + menu.addMenu(&discMenu); + QAction* act_x_disc_all = menu.addAction("Disconnect &All"); + QAction* act_x_sep_1 = menu.addSeparator(); + QAction* act_x_info = menu.addAction("Get &Info"); + QAction* act_x_rename = menu.addAction("&Rename"); + + if (features.port_info == false) + act_x_info->setVisible(false); + + if (features.port_rename == false) + act_x_rename->setVisible(false); + + if (features.port_info == false && features.port_rename == false) + act_x_sep_1->setVisible(false); + + QAction* act_selected = menu.exec(event->screenPos()); + + if (act_selected == act_x_disc_all) + { + foreach (int port_id, port_con_list) + canvas.callback(ACTION_PORTS_DISCONNECT, port_id, 0, ""); + } + else if (act_selected == act_x_info) + { + canvas.callback(ACTION_PORT_INFO, m_port_id, 0, ""); + } + else if (act_selected == act_x_rename) + { + bool ok_check; + QString new_name = QInputDialog::getText(0, "Rename Port", "New name:", QLineEdit::Normal, m_port_name, &ok_check); + if (ok_check and new_name.isEmpty() == false) + { + canvas.callback(ACTION_PORT_RENAME, m_port_id, 0, new_name); + } + } + + event->accept(); +} + +QRectF CanvasPort::boundingRect() const +{ + return QRectF(0, 0, m_port_width+12, m_port_height); +} + +void CanvasPort::paint(QPainter* painter, const QStyleOptionGraphicsItem* /*option*/, QWidget* /*widget*/) +{ + painter->setRenderHint(QPainter::Antialiasing, (options.antialiasing == ANTIALIASING_FULL)); + + QPointF text_pos; + int poly_locx[5] = { 0 }; + + if (m_port_mode == PORT_MODE_INPUT) + { + text_pos = QPointF(3, 12); + + if (canvas.theme->port_mode == Theme::THEME_PORT_POLYGON) + { + poly_locx[0] = 0; + poly_locx[1] = m_port_width+5; + poly_locx[2] = m_port_width+12; + poly_locx[3] = m_port_width+5; + poly_locx[4] = 0; + } + else if (canvas.theme->port_mode == Theme::THEME_PORT_SQUARE) + { + poly_locx[0] = 0; + poly_locx[1] = m_port_width+5; + poly_locx[2] = m_port_width+5; + poly_locx[3] = m_port_width+5; + poly_locx[4] = 0; + } + else + { + qCritical("PatchCanvas::CanvasPort->paint() - invalid theme port mode '%i'", canvas.theme->port_mode); + return; + } + } + else if (m_port_mode == PORT_MODE_OUTPUT) + { + text_pos = QPointF(9, 12); + + if (canvas.theme->port_mode == Theme::THEME_PORT_POLYGON) + { + poly_locx[0] = m_port_width+12; + poly_locx[1] = 7; + poly_locx[2] = 0; + poly_locx[3] = 7; + poly_locx[4] = m_port_width+12; + } + else if (canvas.theme->port_mode == Theme::THEME_PORT_SQUARE) + { + poly_locx[0] = m_port_width+12; + poly_locx[1] = 5; + poly_locx[2] = 5; + poly_locx[3] = 5; + poly_locx[4] = m_port_width+12; + } + else + { + qCritical("PatchCanvas::CanvasPort->paint() - invalid theme port mode '%i'", canvas.theme->port_mode); + return; + } + } + else + { + qCritical("PatchCanvas::CanvasPort->paint() - invalid port mode '%s'", port_mode2str(m_port_mode)); + return; + } + + QColor poly_color; + QPen poly_pen; + + if (m_port_type == PORT_TYPE_AUDIO_JACK) + { + poly_color = isSelected() ? canvas.theme->port_audio_jack_bg_sel : canvas.theme->port_audio_jack_bg; + poly_pen = isSelected() ? canvas.theme->port_audio_jack_pen_sel : canvas.theme->port_audio_jack_pen; + } + else if (m_port_type == PORT_TYPE_MIDI_JACK) + { + poly_color = isSelected() ? canvas.theme->port_midi_jack_bg_sel : canvas.theme->port_midi_jack_bg; + poly_pen = isSelected() ? canvas.theme->port_midi_jack_pen_sel : canvas.theme->port_midi_jack_pen; + } + else if (m_port_type == PORT_TYPE_MIDI_A2J) + { + poly_color = isSelected() ? canvas.theme->port_midi_a2j_bg_sel : canvas.theme->port_midi_a2j_bg; + poly_pen = isSelected() ? canvas.theme->port_midi_a2j_pen_sel : canvas.theme->port_midi_a2j_pen; + } + else if (m_port_type == PORT_TYPE_MIDI_ALSA) + { + poly_color = isSelected() ? canvas.theme->port_midi_alsa_bg_sel : canvas.theme->port_midi_alsa_bg; + poly_pen = isSelected() ? canvas.theme->port_midi_alsa_pen_sel : canvas.theme->port_midi_alsa_pen; + } + else + { + qCritical("PatchCanvas::CanvasPort->paint() - invalid port type '%s'", port_type2str(m_port_type)); + return; + } + + QPolygonF polygon; + polygon += QPointF(poly_locx[0], 0); + polygon += QPointF(poly_locx[1], 0); + polygon += QPointF(poly_locx[2], 7.5); + polygon += QPointF(poly_locx[3], 15); + polygon += QPointF(poly_locx[4], 15); + + painter->setBrush(poly_color); + painter->setPen(poly_pen); + painter->drawPolygon(polygon); + + painter->setPen(canvas.theme->port_text); + painter->setFont(m_port_font); + painter->drawText(text_pos, m_port_name); + + if (isSelected() != m_last_selected_state) + { + foreach (const connection_dict_t& connection, canvas.connection_list) + { + if (connection.port_out_id == m_port_id || connection.port_in_id == m_port_id) + connection.widget->setLineSelected(isSelected()); + } + } + + m_last_selected_state = isSelected(); +} + +END_NAMESPACE_PATCHCANVAS diff --git a/c++/patchcanvas/canvasport.h b/c++/patchcanvas/canvasport.h new file mode 100644 index 0000000..7baf3f9 --- /dev/null +++ b/c++/patchcanvas/canvasport.h @@ -0,0 +1,79 @@ +/* + * Patchbay Canvas engine using QGraphicsView/Scene + * Copyright (C) 2010-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 + */ + +#ifndef CANVASPORT_H +#define CANVASPORT_H + +#include "patchcanvas.h" + +class QGraphicsSceneContextMenuEvent; +class QGraphicsSceneMouseEvent; +class QPainter; + +START_NAMESPACE_PATCHCANVAS + +class AbstractCanvasLineMov; + +class CanvasPort : public QGraphicsItem +{ +public: + CanvasPort(int port_id, QString port_name, PortMode port_mode, PortType port_type, QGraphicsItem* parent); + + int getPortId(); + PortMode getPortMode(); + PortType getPortType(); + QString getPortName(); + QString getFullPortName(); + int getPortWidth(); + int getPortHeight(); + + void setPortMode(PortMode port_mode); + void setPortType(PortType port_type); + void setPortName(QString port_name); + void setPortWidth(int port_width); + + virtual int type() const; + +private: + int m_port_id; + PortMode m_port_mode; + PortType m_port_type; + QString m_port_name; + + int m_port_width; + int m_port_height; + QFont m_port_font; + + AbstractCanvasLineMov* m_line_mov; + CanvasPort* m_hover_item; + bool m_last_selected_state; + + bool m_mouse_down; + bool m_cursor_moving; + + virtual void mousePressEvent(QGraphicsSceneMouseEvent* event); + virtual void mouseMoveEvent(QGraphicsSceneMouseEvent* event); + virtual void mouseReleaseEvent(QGraphicsSceneMouseEvent* event); + virtual void contextMenuEvent(QGraphicsSceneContextMenuEvent* event); + + virtual QRectF boundingRect() const; + virtual void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget); +}; + +END_NAMESPACE_PATCHCANVAS + +#endif // CANVASPORT_H diff --git a/c++/patchcanvas/canvasportglow.cpp b/c++/patchcanvas/canvasportglow.cpp new file mode 100644 index 0000000..c18c7c9 --- /dev/null +++ b/c++/patchcanvas/canvasportglow.cpp @@ -0,0 +1,38 @@ +/* + * Patchbay Canvas engine using QGraphicsView/Scene + * Copyright (C) 2010-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 + */ + +#include "canvasportglow.h" + +START_NAMESPACE_PATCHCANVAS + +CanvasPortGlow::CanvasPortGlow(PortType port_type, QObject* parent) : + QGraphicsDropShadowEffect(parent) +{ + setBlurRadius(12); + setOffset(0, 0); + + if (port_type == PORT_TYPE_AUDIO_JACK) + setColor(canvas.theme->line_audio_jack_glow); + else if (port_type == PORT_TYPE_MIDI_JACK) + setColor(canvas.theme->line_midi_jack_glow); + else if (port_type == PORT_TYPE_MIDI_A2J) + setColor(canvas.theme->line_midi_a2j_glow); + else if (port_type == PORT_TYPE_MIDI_ALSA) + setColor(canvas.theme->line_midi_alsa_glow); +} + +END_NAMESPACE_PATCHCANVAS diff --git a/c++/patchcanvas/canvasportglow.h b/c++/patchcanvas/canvasportglow.h new file mode 100644 index 0000000..8b9dbf4 --- /dev/null +++ b/c++/patchcanvas/canvasportglow.h @@ -0,0 +1,35 @@ +/* + * Patchbay Canvas engine using QGraphicsView/Scene + * Copyright (C) 2010-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 + */ + +#ifndef CANVASPORTGLOW_H +#define CANVASPORTGLOW_H + +#include + +#include "patchcanvas.h" + +START_NAMESPACE_PATCHCANVAS + +class CanvasPortGlow : public QGraphicsDropShadowEffect +{ +public: + CanvasPortGlow(PortType port_type, QObject* parent); +}; + +END_NAMESPACE_PATCHCANVAS + +#endif // CANVASPORTGLOW_H diff --git a/c++/patchcanvas/patchcanvas-theme.cpp b/c++/patchcanvas/patchcanvas-theme.cpp new file mode 100644 index 0000000..24cef3a --- /dev/null +++ b/c++/patchcanvas/patchcanvas-theme.cpp @@ -0,0 +1,178 @@ +/* + * Patchbay Canvas engine using QGraphicsView/Scene + * Copyright (C) 2010-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 + */ + +#include "patchcanvas-theme.h" + +START_NAMESPACE_PATCHCANVAS + +Theme::Theme(List id) +{ + switch (id) + { + case THEME_MODERN_DARK: + // Name this theme + name = "Modern Dark"; + + // Canvas + canvas_bg = QColor(0, 0, 0); + + // Boxes + box_pen = QPen(QColor(76,77,78), 1, Qt::SolidLine); + box_pen_sel = QPen(QColor(206,207,208), 1, Qt::DashLine); + box_bg_1 = QColor(32,34,35); + box_bg_2 = QColor(43,47,48); + box_shadow = QColor(89,89,89,180); + + box_text = QPen(QColor(240,240,240), 0); + box_font_name = "Deja Vu Sans"; + box_font_size = 8; + box_font_state = QFont::Bold; + + // Ports + port_audio_jack_pen = QPen(QColor(63,90,126), 1); + port_audio_jack_pen_sel = QPen(QColor(63+30,90+30,126+30), 1); + port_midi_jack_pen = QPen(QColor(159,44,42), 1); + port_midi_jack_pen_sel = QPen(QColor(159+30,44+30,42+30), 1); + port_midi_a2j_pen = QPen(QColor(137,76,43), 1); + port_midi_a2j_pen_sel = QPen(QColor(137+30,76+30,43+30), 1); + port_midi_alsa_pen = QPen(QColor(93,141,46), 1); + port_midi_alsa_pen_sel = QPen(QColor(93+30,141+30,46+30), 1); + + port_audio_jack_bg = QColor(35,61,99); + port_audio_jack_bg_sel = QColor(35+50,61+50,99+50); + port_midi_jack_bg = QColor(120,15,16); + port_midi_jack_bg_sel = QColor(120+50,15+50,16+50); + port_midi_a2j_bg = QColor(101,47,16); + port_midi_a2j_bg_sel = QColor(101+50,47+50,16+50); + port_midi_alsa_bg = QColor(64,112,18); + port_midi_alsa_bg_sel = QColor(64+50,112+50,18+50); + + port_text = QPen(QColor(250,250,250), 0); + port_font_name = "Deja Vu Sans"; + port_font_size = 8; + port_font_state = QFont::Normal; + port_mode = THEME_PORT_POLYGON; + + // Lines + line_audio_jack = QColor(63,90,126); + line_audio_jack_sel = QColor(63+90,90+90,126+90); + line_audio_jack_glow = QColor(100,100,200); + line_midi_jack = QColor(159,44,42); + line_midi_jack_sel = QColor(159+90,44+90,42+90); + line_midi_jack_glow = QColor(200,100,100); + line_midi_a2j = QColor(137,76,43); + line_midi_a2j_sel = QColor(137+90,76+90,43+90); + line_midi_a2j_glow = QColor(166,133,133); + line_midi_alsa = QColor(93,141,46); + line_midi_alsa_sel = QColor(93+90,141+90,46+90); + line_midi_alsa_glow = QColor(100,200,100); + + rubberband_pen = QPen(QColor(206,207,208), 1, Qt::SolidLine); + rubberband_brush = QColor(76,77,78,100); + break; + + case THEME_CLASSIC_DARK: + // Name this theme + name = "Classic Dark"; + + // Canvas + canvas_bg = QColor(0,0,0); + + // Boxes + box_pen = QPen(QColor(147-70,151-70,143-70), 2, Qt::SolidLine); + box_pen_sel = QPen(QColor(147,151,143), 2, Qt::DashLine); + box_bg_1 = QColor(30,34,36); + box_bg_2 = QColor(30,34,36); + box_shadow = QColor(89,89,89,180); + + box_text = QPen(QColor(255,255,255), 0); + box_font_name = "Sans"; + box_font_size = 9; + box_font_state = QFont::Normal; + + // Ports + port_audio_jack_pen = QPen(QColor(35,61,99), 0); + port_audio_jack_pen_sel = QPen(QColor(255,0,0), 0); + port_midi_jack_pen = QPen(QColor(120,15,16), 0); + port_midi_jack_pen_sel = QPen(QColor(255,0,0), 0); + port_midi_a2j_pen = QPen(QColor(101,47,17), 0); + port_midi_a2j_pen_sel = QPen(QColor(255,0,0), 0); + port_midi_alsa_pen = QPen(QColor(63,112,19), 0); + port_midi_alsa_pen_sel = QPen(QColor(255,0,0), 0); + + port_audio_jack_bg = QColor(35,61,99); + port_audio_jack_bg_sel = QColor(255,0,0); + port_midi_jack_bg = QColor(120,15,16); + port_midi_jack_bg_sel = QColor(255,0,0); + port_midi_a2j_bg = QColor(101,47,17); + port_midi_a2j_bg_sel = QColor(255,0,0); + port_midi_alsa_bg = QColor(63,112,19); + port_midi_alsa_bg_sel = QColor(255,0,0); + + port_text = QPen(QColor(250,250,250), 0); + port_font_name = "Sans"; + port_font_size = 8; + port_font_state = QFont::Normal; + port_mode = THEME_PORT_SQUARE; + + // Lines + line_audio_jack = QColor(53,78,116); + line_audio_jack_sel = QColor(255,0,0); + line_audio_jack_glow = QColor(255,0,0); + line_midi_jack = QColor(139,32,32); + line_midi_jack_sel = QColor(255,0,0); + line_midi_jack_glow = QColor(255,0,0); + line_midi_a2j = QColor(120,65,33); + line_midi_a2j_sel = QColor(255,0,0); + line_midi_a2j_glow = QColor(255,0,0); + line_midi_alsa = QColor(81,130,36); + line_midi_alsa_sel = QColor(255,0,0); + line_midi_alsa_glow = QColor(255,0,0); + + rubberband_pen = QPen(QColor(147,151,143), 2, Qt::SolidLine); + rubberband_brush = QColor(35,61,99,100); + break; + + default: + break; + } +} + +Theme::List getDefaultTheme() +{ + return Theme::THEME_MODERN_DARK; +} + +QString getThemeName(Theme::List id) +{ + switch (id) + { + case Theme::THEME_MODERN_DARK: + return "Modern Dark"; + case Theme::THEME_CLASSIC_DARK: + return "Classic Dark"; + default: + return ""; + } +} + +QString getDefaultThemeName() +{ + return "Modern Dark"; +} + +END_NAMESPACE_PATCHCANVAS diff --git a/c++/patchcanvas/patchcanvas-theme.h b/c++/patchcanvas/patchcanvas-theme.h new file mode 100644 index 0000000..26fba27 --- /dev/null +++ b/c++/patchcanvas/patchcanvas-theme.h @@ -0,0 +1,102 @@ +/* + * Patchbay Canvas engine using QGraphicsView/Scene + * Copyright (C) 2010-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 + */ + +#ifndef PATCHCANVAS_THEME_H +#define PATCHCANVAS_THEME_H + +#include +#include +#include + +#include "../patchcanvas.h" + +START_NAMESPACE_PATCHCANVAS + +class Theme +{ +public: + enum PortType { + THEME_PORT_SQUARE = 0, + THEME_PORT_POLYGON = 1 + }; + + enum List { + THEME_MODERN_DARK = 0, + THEME_CLASSIC_DARK = 1, + THEME_MAX = 2 + }; + + Theme(List id); + + // Canvas + QString name; + + // Boxes + QColor canvas_bg; + QPen box_pen; + QPen box_pen_sel; + QColor box_bg_1; + QColor box_bg_2; + QColor box_shadow; + QPen box_text; + QString box_font_name; + int box_font_size; + QFont::Weight box_font_state; + + // Ports + QPen port_audio_jack_pen; + QPen port_audio_jack_pen_sel; + QPen port_midi_jack_pen; + QPen port_midi_jack_pen_sel; + QPen port_midi_a2j_pen; + QPen port_midi_a2j_pen_sel; + QPen port_midi_alsa_pen; + QPen port_midi_alsa_pen_sel; + QColor port_audio_jack_bg; + QColor port_audio_jack_bg_sel; + QColor port_midi_jack_bg; + QColor port_midi_jack_bg_sel; + QColor port_midi_a2j_bg; + QColor port_midi_a2j_bg_sel; + QColor port_midi_alsa_bg; + QColor port_midi_alsa_bg_sel; + QPen port_text; + QString port_font_name; + int port_font_size; + QFont::Weight port_font_state; + PortType port_mode; + + // Lines + QColor line_audio_jack; + QColor line_audio_jack_sel; + QColor line_audio_jack_glow; + QColor line_midi_jack; + QColor line_midi_jack_sel; + QColor line_midi_jack_glow; + QColor line_midi_a2j; + QColor line_midi_a2j_sel; + QColor line_midi_a2j_glow; + QColor line_midi_alsa; + QColor line_midi_alsa_sel; + QColor line_midi_alsa_glow; + QPen rubberband_pen; + QColor rubberband_brush; +}; + +END_NAMESPACE_PATCHCANVAS + +#endif // PATCHCANVAS_THEME_H diff --git a/c++/patchcanvas/patchcanvas.cpp b/c++/patchcanvas/patchcanvas.cpp new file mode 100644 index 0000000..bbb9d76 --- /dev/null +++ b/c++/patchcanvas/patchcanvas.cpp @@ -0,0 +1,1202 @@ +/* + * Patchbay Canvas engine using QGraphicsView/Scene + * Copyright (C) 2010-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 + */ + +#include "patchcanvas.h" +#include "patchscene.h" + +#include +#include +#include + +#include "canvasfadeanimation.h" +#include "canvasline.h" +#include "canvasbezierline.h" +#include "canvasport.h" +#include "canvasbox.h" + +CanvasObject::CanvasObject(QObject* parent) : QObject(parent) {} + +void CanvasObject::AnimationIdle() +{ + PatchCanvas::CanvasFadeAnimation* animation = (PatchCanvas::CanvasFadeAnimation*)sender(); + if (animation) + PatchCanvas::CanvasRemoveAnimation(animation); +} + +void CanvasObject::AnimationHide() +{ + PatchCanvas::CanvasFadeAnimation* animation = (PatchCanvas::CanvasFadeAnimation*)sender(); + if (animation) + { + if (animation->item()) + animation->item()->hide(); + PatchCanvas::CanvasRemoveAnimation(animation); + } +} + +void CanvasObject::AnimationDestroy() +{ + PatchCanvas::CanvasFadeAnimation* animation = (PatchCanvas::CanvasFadeAnimation*)sender(); + if (animation) + { + if (animation->item()) + PatchCanvas::CanvasRemoveItemFX(animation->item()); + PatchCanvas::CanvasRemoveAnimation(animation); + } +} + +void CanvasObject::CanvasPostponedGroups() +{ + PatchCanvas::CanvasPostponedGroups(); +} + +void CanvasObject::PortContextMenuDisconnect() +{ + bool ok; + int connection_id = ((QAction*)sender())->data().toInt(&ok); + if (ok) + PatchCanvas::CanvasCallback(PatchCanvas::ACTION_PORTS_DISCONNECT, connection_id, 0, ""); +} + +START_NAMESPACE_PATCHCANVAS + +/* contructor and destructor */ +Canvas::Canvas() +{ + qobject = 0; + settings = 0; + theme = 0; + initiated = false; +} + +Canvas::~Canvas() +{ + if (qobject) + delete qobject; + if (settings) + delete settings; + if (theme) + delete theme; +} + +/* Global objects */ +Canvas canvas; + +options_t options = { + /* theme_name */ getDefaultThemeName(), + /* auto_hide_groups */ false, + /* use_bezier_lines */ true, + /* antialiasing */ ANTIALIASING_SMALL, + /* eyecandy */ EYECANDY_SMALL +}; + +features_t features = { + /* group_info */ false, + /* group_rename */ false, + /* port_info */ false, + /* port_rename */ false, + /* handle_group_pos */ false +}; + +/* Internal functions */ +const char* bool2str(bool check) +{ + return check ? "true" : "false"; +} + +const char* port_mode2str(PortMode port_mode) +{ + if (port_mode == PORT_MODE_NULL) + return "PORT_MODE_NULL"; + else if (port_mode == PORT_MODE_INPUT) + return "PORT_MODE_INPUT"; + else if (port_mode == PORT_MODE_OUTPUT) + return "PORT_MODE_OUTPUT"; + else + return "PORT_MODE_???"; +} + +const char* port_type2str(PortType port_type) +{ + if (port_type == PORT_TYPE_NULL) + return "PORT_TYPE_NULL"; + else if (port_type == PORT_TYPE_AUDIO_JACK) + return "PORT_TYPE_AUDIO_JACK"; + else if (port_type == PORT_TYPE_MIDI_JACK) + return "PORT_TYPE_MIDI_JACK"; + else if (port_type == PORT_TYPE_MIDI_A2J) + return "PORT_TYPE_MIDI_A2J"; + else if (port_type == PORT_TYPE_MIDI_ALSA) + return "PORT_TYPE_MIDI_ALSA"; + else + return "PORT_TYPE_???"; +} + +const char* icon2str(Icon icon) +{ + if (icon == ICON_HARDWARE) + return "ICON_HARDWARE"; + else if (ICON_APPLICATION) + return "ICON_APPLICATION"; + else if (ICON_LADISH_ROOM) + return "ICON_LADISH_ROOM"; + else + return "ICON_???"; +} + +const char* split2str(SplitOption split) +{ + if (split == SPLIT_UNDEF) + return "SPLIT_UNDEF"; + else if (split == SPLIT_NO) + return "SPLIT_NO"; + else if (split == SPLIT_YES) + return "SPLIT_YES"; + else + return "SPLIT_???"; +} + +/* PatchCanvas API */ +void setOptions(options_t* 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; +} + +void setFeatures(features_t* 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; +} + +void init(PatchScene* scene, Callback callback, bool debug) +{ + if (debug) + qDebug("PatchCanvas::init(%p, %p, %s)", scene, callback, bool2str(debug)); + + if (canvas.initiated) + { + qCritical("PatchCanvas::init() - already initiated"); + return; + } + + if (!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 (!canvas.qobject) canvas.qobject = new CanvasObject(); + if (!canvas.settings) canvas.settings = new QSettings(PATCHCANVAS_ORGANISATION_NAME, "PatchCanvas"); + + if (canvas.theme) + { + delete canvas.theme; + canvas.theme = 0; + } + + for (int i=0; i(i)); + if (this_theme_name == options.theme_name) + { + canvas.theme = new Theme(static_cast(i)); + break; + } + } + + if (!canvas.theme) + canvas.theme = new Theme(getDefaultTheme()); + + canvas.scene->updateTheme(); + + canvas.initiated = true; +} + +void clear() +{ + if (canvas.debug) + qDebug("PatchCanvas::clear()"); + + QList group_list_ids; + QList port_list_ids; + QList connection_list_ids; + + foreach (const group_dict_t& group, canvas.group_list) + group_list_ids.append(group.group_id); + + foreach (const port_dict_t& port, canvas.port_list) + port_list_ids.append(port.port_id); + + foreach (const connection_dict_t& connection, canvas.connection_list) + connection_list_ids.append(connection.connection_id); + + foreach (const int& idx, connection_list_ids) + disconnectPorts(idx); + + foreach (const int& idx, port_list_ids) + removePort(idx); + + foreach (const int& idx, group_list_ids) + removeGroup(idx); + + canvas.last_z_value = 0; + canvas.last_connection_id = 0; + + canvas.group_list.clear(); + canvas.port_list.clear(); + canvas.connection_list.clear(); + + canvas.initiated = false; +} + +void setInitialPos(int x, int y) +{ + if (canvas.debug) + qDebug("PatchCanvas::setInitialPos(%i, %i)", x, y); + + canvas.initial_pos.setX(x); + canvas.initial_pos.setY(y); +} + +void setCanvasSize(int x, int y, int width, int 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); +} + +void addGroup(int group_id, QString group_name, SplitOption split, Icon icon) +{ + if (canvas.debug) + qDebug("PatchCanvas::addGroup(%i, %s, %s, %s)", group_id, group_name.toUtf8().constData(), split2str(split), icon2str(icon)); + + foreach (const group_dict_t& group, canvas.group_list) + { + if (group.group_id == group_id) + { + qWarning("PatchCanvas::addGroup(%i, %s, %s, %s) - group already exists", group_id, group_name.toUtf8().constData(), split2str(split), icon2str(icon)); + return; + } + } + + if (split == SPLIT_UNDEF && features.handle_group_pos) + split = static_cast(canvas.settings->value(QString("CanvasPositions/%1_SPLIT").arg(group_name), split).toInt()); + + CanvasBox* group_box = new CanvasBox(group_id, group_name, icon); + + group_dict_t group_dict; + group_dict.group_id = group_id; + group_dict.group_name = group_name; + group_dict.split = (split == SPLIT_YES); + group_dict.icon = icon; + group_dict.widgets[0] = group_box; + group_dict.widgets[1] = 0; + + if (split == SPLIT_YES) + { + group_box->setSplit(true, PORT_MODE_OUTPUT); + + if (features.handle_group_pos) + group_box->setPos(canvas.settings->value(QString("CanvasPositions/%1_OUTPUT").arg(group_name), CanvasGetNewGroupPos()).toPointF()); + else + group_box->setPos(CanvasGetNewGroupPos()); + + CanvasBox* group_sbox = new 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(QString("CanvasPositions/%1_INPUT").arg(group_name), CanvasGetNewGroupPos(true)).toPointF()); + else + group_sbox->setPos(CanvasGetNewGroupPos(true)); + + canvas.last_z_value += 1; + group_sbox->setZValue(canvas.last_z_value); + + if (options.auto_hide_groups == false && options.eyecandy == EYECANDY_FULL) + CanvasItemFX(group_sbox, true); + } + else + { + group_box->setSplit(false); + + if (features.handle_group_pos) + group_box->setPos(canvas.settings->value(QString("CanvasPositions/%1").arg(group_name), CanvasGetNewGroupPos()).toPointF()); + else + { + // Special ladish fake-split groups + bool horizontal = (icon == ICON_HARDWARE || icon == ICON_LADISH_ROOM); + group_box->setPos(CanvasGetNewGroupPos(horizontal)); + } + } + + canvas.last_z_value += 1; + group_box->setZValue(canvas.last_z_value); + + canvas.group_list.append(group_dict); + + if (options.auto_hide_groups == false && options.eyecandy == EYECANDY_FULL) + CanvasItemFX(group_box, true); + + QTimer::singleShot(0, canvas.scene, SLOT(update())); +} + +void removeGroup(int group_id) +{ + if (canvas.debug) + qDebug("PatchCanvas::removeGroup(%i)", group_id); + + foreach2 (const group_dict_t& group, canvas.group_list) + if (group.group_id == group_id) + { + CanvasBox* item = group.widgets[0]; + QString group_name = group.group_name; + + if (group.split) + { + CanvasBox* s_item = group.widgets[1]; + if (features.handle_group_pos) + { + canvas.settings->setValue(QString("CanvasPositions/%1_OUTPUT").arg(group_name), item->pos()); + canvas.settings->setValue(QString("CanvasPositions/%1_INPUT").arg(group_name), s_item->pos()); + canvas.settings->setValue(QString("CanvasPositions/%1_SPLIT").arg(group_name), SPLIT_YES); + } + + if (options.eyecandy == EYECANDY_FULL) + { + CanvasItemFX(s_item, false, true); + } + else + { + s_item->removeIconFromScene(); + canvas.scene->removeItem(s_item); + delete s_item; + } + } + else + { + if (features.handle_group_pos) + { + canvas.settings->setValue(QString("CanvasPositions/%1").arg(group_name), item->pos()); + canvas.settings->setValue(QString("CanvasPositions/%1_SPLIT").arg(group_name), SPLIT_NO); + } + } + + if (options.eyecandy == EYECANDY_FULL) + { + CanvasItemFX(item, false, true); + } + else + { + item->removeIconFromScene(); + canvas.scene->removeItem(item); + delete item; + } + + canvas.group_list.takeAt(i); + + QTimer::singleShot(0, canvas.scene, SLOT(update())); + return; + } + } + + qCritical("PatchCanvas::removeGroup(%i) - unable to find group to remove", group_id); +} + +void renameGroup(int group_id, QString new_group_name) +{ + if (canvas.debug) + qDebug("PatchCanvas::renameGroup(%i, %s)", group_id, new_group_name.toUtf8().constData()); + + foreach2 (group_dict_t& group, 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 && group.widgets[1]) + group.widgets[1]->setGroupName(new_group_name); + + QTimer::singleShot(0, canvas.scene, SLOT(update())); + return; + } + } + + qCritical("PatchCanvas::renameGroup(%i, %s) - unable to find group to rename", group_id, new_group_name.toUtf8().constData()); +} + +void splitGroup(int group_id) +{ + if (canvas.debug) + qDebug("PatchCanvas::splitGroup(%i)", group_id); + + CanvasBox* item = 0; + QString group_name; + Icon group_icon = ICON_APPLICATION; + QList ports_data; + QList conns_data; + + // Step 1 - Store all Item data + foreach (const group_dict_t& group, 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 (!item) + { + qCritical("PatchCanvas::splitGroup(%i) - unable to find group to split", group_id); + return; + } + + QList port_list_ids = QList(item->getPortList()); + + foreach (const port_dict_t& port, canvas.port_list) + { + if (port_list_ids.contains(port.port_id)) + { + port_dict_t port_dict; + 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 = 0; + ports_data.append(port_dict); + } + } + + foreach (const connection_dict_t& connection, canvas.connection_list) + { + if (port_list_ids.contains(connection.port_out_id) || port_list_ids.contains(connection.port_in_id)) + { + connection_dict_t connection_dict; + 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 = 0; + conns_data.append(connection_dict); + } + } + + // Step 2 - Remove Item and Children + foreach (const connection_dict_t& conn, conns_data) + disconnectPorts(conn.connection_id); + + foreach (const int& port_id, 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); + + foreach (const port_dict_t& port, ports_data) + addPort(group_id, port.port_id, port.port_name, port.port_mode, port.port_type); + + foreach (const connection_dict_t& conn, conns_data) + connectPorts(conn.connection_id, conn.port_out_id, conn.port_in_id); + + QTimer::singleShot(0, canvas.scene, SLOT(update())); +} + +void joinGroup(int group_id) +{ + if (canvas.debug) + qDebug("PatchCanvas::joinGroup(%i)", group_id); + + CanvasBox* item = 0; + CanvasBox* s_item = 0; + QString group_name; + Icon group_icon = ICON_APPLICATION; + QList ports_data; + QList conns_data; + + // Step 1 - Store all Item data + foreach (const group_dict_t& group, canvas.group_list) + { + if (group.group_id == group_id) + { + if (group.split == false) + { + 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; + } + } + + if (!item || !s_item) + { + qCritical("PatchCanvas::joinGroup(%i) - Unable to find groups to join", group_id); + return; + } + + QList port_list_ids = QList(item->getPortList()); + QList port_list_idss = s_item->getPortList(); + + foreach (const int& port_id, port_list_idss) + { + if (port_list_ids.contains(port_id) == false) + port_list_ids.append(port_id); + } + + foreach (const port_dict_t& port, canvas.port_list) + { + if (port_list_ids.contains(port.port_id)) + { + port_dict_t port_dict; + 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 = 0; + ports_data.append(port_dict); + } + } + + foreach (const connection_dict_t& connection, canvas.connection_list) + { + if (port_list_ids.contains(connection.port_out_id) || port_list_ids.contains(connection.port_in_id)) + { + connection_dict_t connection_dict; + 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 = 0; + conns_data.append(connection_dict); + } + } + + // Step 2 - Remove Item and Children + foreach (const connection_dict_t& conn, conns_data) + disconnectPorts(conn.connection_id); + + foreach (const int& port_id, 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); + + foreach (const port_dict_t& port, ports_data) + addPort(group_id, port.port_id, port.port_name, port.port_mode, port.port_type); + + foreach (const connection_dict_t& conn, conns_data) + connectPorts(conn.connection_id, conn.port_out_id, conn.port_in_id); + + QTimer::singleShot(0, canvas.scene, SLOT(update())); +} + +QPointF getGroupPos(int group_id, PortMode port_mode) +{ + if (canvas.debug) + qDebug("PatchCanvas::getGroupPos(%i, %s)", group_id, port_mode2str(port_mode)); + + foreach (const group_dict_t& group, canvas.group_list) + { + if (group.group_id == group_id) + { + if (group.split) + { + if (port_mode == PORT_MODE_OUTPUT) + return group.widgets[0]->pos(); + else if (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); +} + +void setGroupPos(int group_id, int group_pos_x, int group_pos_y) +{ + setGroupPos(group_id, group_pos_x, group_pos_y, group_pos_x, group_pos_y); +} + +void setGroupPos(int group_id, int group_pos_x, int group_pos_y, int group_pos_xs, int group_pos_ys) +{ + if (canvas.debug) + qDebug("PatchCanvas::setGroupPos(%i, %i, %i, %i, %i)", group_id, group_pos_x, group_pos_y, group_pos_xs, group_pos_ys); + + foreach (const group_dict_t& group, canvas.group_list) + { + if (group.group_id == group_id) + { + group.widgets[0]->setPos(group_pos_x, group_pos_y); + + if (group.split && group.widgets[1]) + { + group.widgets[1]->setPos(group_pos_xs, group_pos_ys); + } + + QTimer::singleShot(0, canvas.scene, SLOT(update())); + return; + } + } + + qCritical("PatchCanvas::setGroupPos(%i, %i, %i, %i, %i) - unable to find group to reposition", group_id, group_pos_x, group_pos_y, group_pos_xs, group_pos_ys); +} + +void setGroupIcon(int group_id, Icon icon) +{ + if (canvas.debug) + qDebug("PatchCanvas::setGroupIcon(%i, %s)", group_id, icon2str(icon)); + + foreach2 (group_dict_t& group, canvas.group_list) + if (group.group_id == group_id) + { + group.icon = icon; + group.widgets[0]->setIcon(icon); + + if (group.split && group.widgets[1]) + group.widgets[1]->setIcon(icon); + + QTimer::singleShot(0, canvas.scene, SLOT(update())); + return; + } + } + + qCritical("PatchCanvas::setGroupIcon(%i, %s) - unable to find group to change icon", group_id, icon2str(icon)); +} + +void addPort(int group_id, int port_id, QString port_name, PortMode port_mode, PortType port_type) +{ + if (canvas.debug) + qDebug("PatchCanvas::addPort(%i, %i, %s, %s, %s)", group_id, port_id, port_name.toUtf8().constData(), port_mode2str(port_mode), port_type2str(port_type)); + + foreach (const port_dict_t& port, 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.toUtf8().constData(), port_mode2str(port_mode), port_type2str(port_type)); + return; + } + } + + CanvasBox* box_widget = 0; + CanvasPort* port_widget = 0; + + foreach (const group_dict_t& group, canvas.group_list) + { + if (group.group_id == group_id) + { + int n; + if (group.split && group.widgets[0]->getSplittedMode() != port_mode && group.widgets[1]) + n = 1; + else + n = 0; + box_widget = group.widgets[n]; + port_widget = box_widget->addPortFromGroup(port_id, port_name, port_mode, port_type); + break; + } + } + + if (!box_widget || !port_widget) + { + qCritical("PatchCanvas::addPort(%i, %i, %s, %s, %s) - unable to find parent group", group_id, port_id, port_name.toUtf8().constData(), port_mode2str(port_mode), port_type2str(port_type)); + return; + } + + if (options.eyecandy == EYECANDY_FULL) + CanvasItemFX(port_widget, true); + + port_dict_t port_dict; + 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, SLOT(update())); +} + +void removePort(int port_id) +{ + if (canvas.debug) + qDebug("PatchCanvas::removePort(%i)", port_id); + + foreach2 (const port_dict_t& port, canvas.port_list) + if (port.port_id == port_id) + { + CanvasPort* item = port.widget; + ((CanvasBox*)item->parentItem())->removePortFromGroup(port_id); + canvas.scene->removeItem(item); + delete item; + + canvas.port_list.takeAt(i); + + QTimer::singleShot(0, canvas.scene, SLOT(update())); + return; + } + } + + qCritical("PatchCanvas::removePort(%i) - unable to find port to remove", port_id); +} + +void renamePort(int port_id, QString new_port_name) +{ + if (canvas.debug) + qDebug("PatchCanvas::renamePort(%i, %s)", port_id, new_port_name.toUtf8().constData()); + + foreach2 (port_dict_t& port, canvas.port_list) + if (port.port_id == port_id) + { + port.port_name = new_port_name; + port.widget->setPortName(new_port_name); + ((CanvasBox*)port.widget->parentItem())->updatePositions(); + + QTimer::singleShot(0, canvas.scene, SLOT(update())); + return; + } + } + + qCritical("PatchCanvas::renamePort(%i, %s) - unable to find port to rename", port_id, new_port_name.toUtf8().constData()); +} + +void connectPorts(int connection_id, int port_out_id, int port_in_id) +{ + if (canvas.debug) + qDebug("PatchCanvas::connectPorts(%i, %i, %i)", connection_id, port_out_id, port_in_id); + + CanvasPort* port_out = 0; + CanvasPort* port_in = 0; + CanvasBox* port_out_parent = 0; + CanvasBox* port_in_parent = 0; + + foreach (const port_dict_t& port, canvas.port_list) + { + if (port.port_id == port_out_id) + { + port_out = port.widget; + port_out_parent = (CanvasBox*)port_out->parentItem(); + } + else if (port.port_id == port_in_id) + { + port_in = port.widget; + port_in_parent = (CanvasBox*)port_in->parentItem(); + } + } + + if (!port_out || !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_t connection_dict; + 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 = new CanvasBezierLine(port_out, port_in, 0); + else + connection_dict.widget = new CanvasLine(port_out, port_in, 0); + + 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) + { + QGraphicsItem* item = (options.use_bezier_lines) ? (QGraphicsItem*)(CanvasBezierLine*)connection_dict.widget : (QGraphicsItem*)(CanvasLine*)connection_dict.widget; + CanvasItemFX(item, true); + } + + QTimer::singleShot(0, canvas.scene, SLOT(update())); +} + +void disconnectPorts(int connection_id) +{ + if (canvas.debug) + qDebug("PatchCanvas::disconnectPorts(%i)", connection_id); + + int port_1_id, port_2_id; + AbstractCanvasLine* line = 0; + QGraphicsItem* item1 = 0; + QGraphicsItem* item2 = 0; + + foreach2 (const connection_dict_t& connection, 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.takeAt(i); + break; + } + } + + if (!line) + { + qCritical("PatchCanvas::disconnectPorts(%i) - unable to find connection ports", connection_id); + return; + } + + foreach (const port_dict_t& port, canvas.port_list) + { + if (port.port_id == port_1_id) + { + item1 = port.widget; + break; + } + } + + if (!item1) + { + qCritical("PatchCanvas::disconnectPorts(%i) - unable to find output port", connection_id); + return; + } + + foreach (const port_dict_t& port, canvas.port_list) + { + if (port.port_id == port_2_id) + { + item2 = port.widget; + break; + } + } + + if (!item2) + { + qCritical("PatchCanvas::disconnectPorts(%i) - unable to find input port", connection_id); + return; + } + + ((CanvasBox*)item1->parentItem())->removeLineFromGroup(connection_id); + ((CanvasBox*)item2->parentItem())->removeLineFromGroup(connection_id); + + if (options.eyecandy == EYECANDY_FULL) + { + QGraphicsItem* item = (options.use_bezier_lines) ? (QGraphicsItem*)(CanvasBezierLine*)line : (QGraphicsItem*)(CanvasLine*)line; + CanvasItemFX(item, false, true); + } + else + line->deleteFromScene(); + + QTimer::singleShot(0, canvas.scene, SLOT(update())); +} + +void arrange() +{ + if (canvas.debug) + qDebug("PatchCanvas::Arrange()"); +} + +void updateZValues() +{ + if (canvas.debug) + qDebug("PatchCanvas::updateZValues()"); + + + foreach (const group_dict_t& group, canvas.group_list) + { + group.widgets[0]->resetLinesZValue(); + + if (group.split and group.widgets[1]) + group.widgets[1]->resetLinesZValue(); + } +} + +/* Extra Internal functions */ + +QString CanvasGetGroupName(int group_id) +{ + if (canvas.debug) + qDebug("PatchCanvas::CanvasGetGroupName(%i)", group_id); + + foreach (const group_dict_t& group, canvas.group_list) + { + if (group.group_id == group_id) + return group.group_name; + } + + qCritical("PatchCanvas::CanvasGetGroupName(%i) - unable to find group", group_id); + return ""; +} + +int CanvasGetGroupPortCount(int group_id) +{ + if (canvas.debug) + qDebug("PatchCanvas::CanvasGetGroupPortCount(%i)", group_id); + + int port_count = 0; + foreach (const port_dict_t& port, canvas.port_list) + { + if (port.group_id == group_id) + port_count += 1; + } + + return port_count; +} + +QPointF CanvasGetNewGroupPos(bool horizontal) +{ + if (canvas.debug) + qDebug("PatchCanvas::CanvasGetNewGroupPos(%s)", bool2str(horizontal)); + + QPointF new_pos(canvas.initial_pos.x(), canvas.initial_pos.y()); + QList items = canvas.scene->items(); + + bool break_loop = false; + while (break_loop == false) + { + bool break_for = false; + for (int i=0; i < items.count(); i++) + { + QGraphicsItem* item = items[i]; + if (item && 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; + } + } + if (i >= items.count()-1 && break_for == false) + break_loop = true; + } + } + + return new_pos; +} + +QString CanvasGetFullPortName(int port_id) +{ + if (canvas.debug) + qDebug("PatchCanvas::CanvasGetFullPortName(%i)", port_id); + + foreach (const port_dict_t& port, canvas.port_list) + { + if (port.port_id == port_id) + { + int group_id = port.group_id; + foreach (const group_dict_t& group, 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 ""; +} + +QList CanvasGetPortConnectionList(int port_id) +{ + if (canvas.debug) + qDebug("PatchCanvas::CanvasGetPortConnectionList(%i)", port_id); + + QList port_con_list; + + foreach (const connection_dict_t& connection, canvas.connection_list) + { + if (connection.port_out_id == port_id || connection.port_in_id == port_id) + port_con_list.append(connection.connection_id); + } + + return port_con_list; +} + +int CanvasGetConnectedPort(int connection_id, int port_id) +{ + if (canvas.debug) + qDebug("PatchCanvas::CanvasGetConnectedPort(%i, %i)", connection_id, port_id); + + foreach (const connection_dict_t& connection, 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; +} + +void CanvasRemoveAnimation(CanvasFadeAnimation* f_animation) +{ + if (canvas.debug) + qDebug("PatchCanvas::CanvasRemoveAnimation(%p)", f_animation); + + foreach2 (const animation_dict_t& animation, canvas.animation_list) + if (animation.animation == f_animation) + { + delete animation.animation; + canvas.animation_list.takeAt(i); + break; + } + } +} + +void CanvasPostponedGroups() +{ + if (canvas.debug) + qDebug("PatchCanvas::CanvasPostponedGroups()"); +} + +void CanvasCallback(CallbackAction action, int value1, int value2, QString value_str) +{ + if (canvas.debug) + qDebug("PatchCanvas::CanvasCallback(%i, %i, %i, %s)", action, value1, value2, value_str.toStdString().data()); + + canvas.callback(action, value1, value2, value_str); +} + +void CanvasItemFX(QGraphicsItem* item, bool show, bool destroy) +{ + if (canvas.debug) + qDebug("PatchCanvas::CanvasItemFX(%p, %s, %s)", item, bool2str(show), bool2str(destroy)); + + // Check if item already has an animationItemFX + foreach2 (const animation_dict_t& animation, canvas.animation_list) + if (animation.item == item) + { + if (animation.animation) + { + animation.animation->stop(); + delete animation.animation; + } + canvas.animation_list.takeAt(i); + break; + } + } + + CanvasFadeAnimation* animation = new CanvasFadeAnimation(item, show); + animation->setDuration(show ? 750 : 500); + + animation_dict_t animation_dict; + animation_dict.animation = animation; + animation_dict.item = item; + canvas.animation_list.append(animation_dict); + + if (show) + { + QObject::connect(animation, SIGNAL(finished()), canvas.qobject, SLOT(AnimationIdle())); + } + else + { + if (destroy) + QObject::connect(animation, SIGNAL(finished()), canvas.qobject, SLOT(AnimationDestroy())); + else + QObject::connect(animation, SIGNAL(finished()), canvas.qobject, SLOT(AnimationHide())); + } + + animation->start(); +} + +void CanvasRemoveItemFX(QGraphicsItem* item) +{ + if (canvas.debug) + qDebug("PatchCanvas::CanvasRemoveItemFX(%p)", item); + + switch (item->type()) + { + case CanvasBoxType: + { + CanvasBox* box = (CanvasBox*)item; + box->removeIconFromScene(); + canvas.scene->removeItem(box); + delete box; + } + case CanvasPortType: + { + CanvasPort* port = (CanvasPort*)item; + canvas.scene->removeItem(port); + delete port; + } + case CanvasLineType: + case CanvasBezierLineType: + { + AbstractCanvasLine* line = (AbstractCanvasLine*)item; + line->deleteFromScene(); + } + default: + break; + } +} + +END_NAMESPACE_PATCHCANVAS diff --git a/c++/patchcanvas/patchcanvas.h b/c++/patchcanvas/patchcanvas.h new file mode 100644 index 0000000..9991585 --- /dev/null +++ b/c++/patchcanvas/patchcanvas.h @@ -0,0 +1,142 @@ +/* + * Patchbay Canvas engine using QGraphicsView/Scene + * Copyright (C) 2010-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 + */ + +#ifndef PATCHCANVAS_H +#define PATCHCANVAS_H + +#include + +#include "../patchcanvas.h" + +#define foreach2(var, list) \ + for (int i=0; i < list.count(); i++) { var = list[i]; + +class QSettings; +class QTimer; + +class CanvasObject : public QObject { + Q_OBJECT + +public: + CanvasObject(QObject* parent=0); + +public slots: + void AnimationIdle(); + void AnimationHide(); + void AnimationDestroy(); + void CanvasPostponedGroups(); + void PortContextMenuDisconnect(); +}; + +START_NAMESPACE_PATCHCANVAS + +class AbstractCanvasLine; +class CanvasFadeAnimation; +class CanvasBox; +class CanvasPort; +class Theme; + +// object types +enum CanvasType { + 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 +struct group_dict_t { + int group_id; + QString group_name; + bool split; + Icon icon; + CanvasBox* widgets[2]; +}; + +struct port_dict_t { + int group_id; + int port_id; + QString port_name; + PortMode port_mode; + PortType port_type; + CanvasPort* widget; +}; + +struct connection_dict_t { + int connection_id; + int port_in_id; + int port_out_id; + AbstractCanvasLine* widget; +}; + +struct animation_dict_t { + CanvasFadeAnimation* animation; + QGraphicsItem* item; +}; + +// Main Canvas object +class Canvas { +public: + Canvas(); + ~Canvas(); + + PatchScene* scene; + Callback callback; + bool debug; + unsigned long last_z_value; + int last_connection_id; + QPointF initial_pos; + QRectF size_rect; + QList group_list; + QList port_list; + QList connection_list; + QList animation_list; + CanvasObject* qobject; + QSettings* settings; + Theme* theme; + bool initiated; +}; + +const char* bool2str(bool check); +const char* port_mode2str(PortMode port_mode); +const char* port_type2str(PortType port_type); +const char* icon2str(Icon icon); +const char* split2str(SplitOption split); + +QString CanvasGetGroupName(int group_id); +int CanvasGetGroupPortCount(int group_id); +QPointF CanvasGetNewGroupPos(bool horizontal=false); +QString CanvasGetFullPortName(int port_id); +QList CanvasGetPortConnectionList(int port_id); +int CanvasGetConnectedPort(int connection_id, int port_id); +void CanvasRemoveAnimation(CanvasFadeAnimation* f_animation); +void CanvasPostponedGroups(); +void CanvasCallback(CallbackAction action, int value1, int value2, QString value_str); +void CanvasItemFX(QGraphicsItem* item, bool show, bool destroy=false); +void CanvasRemoveItemFX(QGraphicsItem* item); + +// global objects +extern Canvas canvas; +extern options_t options; +extern features_t features; + +END_NAMESPACE_PATCHCANVAS + +#endif // PATCHCANVAS_H diff --git a/c++/patchcanvas/patchscene.cpp b/c++/patchcanvas/patchscene.cpp new file mode 100644 index 0000000..5493d88 --- /dev/null +++ b/c++/patchcanvas/patchscene.cpp @@ -0,0 +1,290 @@ +/* + * Patchbay Canvas engine using QGraphicsView/Scene + * Copyright (C) 2010-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 + */ + +#include "patchscene.h" + +#include +#include +#include +#include +#include +#include + +#include "patchcanvas/patchcanvas.h" +#include "patchcanvas/canvasbox.h" + +using namespace PatchCanvas; + +PatchScene::PatchScene(QObject* parent, QGraphicsView* view) : + QGraphicsScene(parent) +{ + m_ctrl_down = false; + m_mouse_down_init = false; + m_mouse_rubberband = false; + + m_rubberband = addRect(QRectF(0, 0, 0, 0)); + m_rubberband->setZValue(-1); + m_rubberband->hide(); + m_rubberband_selection = false; + m_rubberband_orig_point = QPointF(0, 0); + + m_view = view; + if (! m_view) + qFatal("PatchCanvas::PatchScene() - invalid view"); +} + +void PatchScene::fixScaleFactor() +{ + qreal scale = m_view->transform().m11(); + if (scale > 3.0) + { + m_view->resetTransform(); + m_view->scale(3.0, 3.0); + } + else if (scale < 0.2) + { + m_view->resetTransform(); + m_view->scale(0.2, 0.2); + } + emit scaleChanged(m_view->transform().m11()); +} + +void PatchScene::updateTheme() +{ + setBackgroundBrush(canvas.theme->canvas_bg); + m_rubberband->setPen(canvas.theme->rubberband_pen); + m_rubberband->setBrush(canvas.theme->rubberband_brush); +} + +void PatchScene::zoom_fit() +{ + qreal min_x, min_y, max_x, max_y; + bool first_value = true; + + QList items_list = items(); + + if (items_list.count() > 0) + { + foreach (const QGraphicsItem* item, items_list) + { + if (item && item->isVisible() and item->type() == CanvasBoxType) + { + QPointF pos = item->scenePos(); + QRectF rect = item->boundingRect(); + + if (first_value) + min_x = pos.x(); + else if (pos.x() < min_x) + min_x = pos.x(); + + if (first_value) + min_y = pos.y(); + else if (pos.y() < min_y) + min_y = pos.y(); + + if (first_value) + max_x = pos.x()+rect.width(); + else if (pos.x()+rect.width() > max_x) + max_x = pos.x()+rect.width(); + + if (first_value) + max_y = pos.y()+rect.height(); + else if (pos.y()+rect.height() > max_y) + max_y = pos.y()+rect.height(); + + first_value = false; + } + } + + if (first_value == false) + { + m_view->fitInView(min_x, min_y, abs(max_x-min_x), abs(max_y-min_y), Qt::KeepAspectRatio); + fixScaleFactor(); + } + } +} + +void PatchScene::zoom_in() +{ + if (m_view->transform().m11() < 3.0) + m_view->scale(1.2, 1.2); + emit scaleChanged(m_view->transform().m11()); +} + +void PatchScene::zoom_out() +{ + if (m_view->transform().m11() > 0.2) + m_view->scale(0.8, 0.8); + emit scaleChanged(m_view->transform().m11()); +} + +void PatchScene::zoom_reset() +{ + m_view->resetTransform(); + emit scaleChanged(1.0); +} + +void PatchScene::keyPressEvent(QKeyEvent* event) +{ + if (! m_view) + return event->ignore(); + + if (event->key() == Qt::Key_Control) + { + m_ctrl_down = true; + } + else if (event->key() == Qt::Key_Home) + { + zoom_fit(); + return event->accept(); + } + else if (m_ctrl_down) + { + if (event->key() == Qt::Key_Plus) + { + zoom_in(); + return event->accept(); + } + else if (event->key() == Qt::Key_Minus) + { + zoom_out(); + return event->accept(); + } + else if (event->key() == Qt::Key_1) + { + zoom_reset(); + return event->accept(); + } + } + + QGraphicsScene::keyPressEvent(event); +} + +void PatchScene::keyReleaseEvent(QKeyEvent* event) +{ + if (event->key() == Qt::Key_Control) + m_ctrl_down = false; + QGraphicsScene::keyReleaseEvent(event); +} + +void PatchScene::mousePressEvent(QGraphicsSceneMouseEvent* event) +{ + m_mouse_down_init = (event->button() == Qt::LeftButton); + m_mouse_rubberband = false; + QGraphicsScene::mousePressEvent(event); +} + +void PatchScene::mouseMoveEvent(QGraphicsSceneMouseEvent* event) +{ + if (m_mouse_down_init) + { + m_mouse_down_init = false; + m_mouse_rubberband = (selectedItems().count() == 0); + } + + if (m_mouse_rubberband) + { + if (m_rubberband_selection == false) + { + m_rubberband->show(); + m_rubberband_selection = true; + m_rubberband_orig_point = event->scenePos(); + } + + int x, y; + QPointF pos = event->scenePos(); + + if (pos.x() > m_rubberband_orig_point.x()) + x = m_rubberband_orig_point.x(); + else + x = pos.x(); + + if (pos.y() > m_rubberband_orig_point.y()) + y = m_rubberband_orig_point.y(); + else + y = pos.y(); + + m_rubberband->setRect(x, y, abs(pos.x()-m_rubberband_orig_point.x()), abs(pos.y()-m_rubberband_orig_point.y())); + return event->accept(); + } + + QGraphicsScene::mouseMoveEvent(event); +} + +void PatchScene::mouseReleaseEvent(QGraphicsSceneMouseEvent* event) +{ + if (m_rubberband_selection) + { + QList items_list = items(); + if (items_list.count() > 0) + { + foreach (QGraphicsItem* item, items_list) + { + if (item && item->isVisible() && item->type() == CanvasBoxType) + { + QRectF item_rect = item->sceneBoundingRect(); + QPointF item_top_left = QPointF(item_rect.x(), item_rect.y()); + QPointF item_bottom_right = QPointF(item_rect.x()+item_rect.width(), item_rect.y()+item_rect.height()); + + if (m_rubberband->contains(item_top_left) && m_rubberband->contains(item_bottom_right)) + item->setSelected(true); + } + } + + m_rubberband->hide(); + m_rubberband->setRect(0, 0, 0, 0); + m_rubberband_selection = false; + } + } + else + { + QList items_list = selectedItems(); + foreach (QGraphicsItem* item, items_list) + { + if (item && item->isVisible() && item->type() == CanvasBoxType) + { + CanvasBox* citem = (CanvasBox*)item; + citem->checkItemPos(); + emit sceneGroupMoved(citem->getGroupId(), citem->getSplittedMode(), citem->scenePos()); + } + } + + if (items_list.count() > 1) + canvas.scene->update(); + } + + m_mouse_down_init = false; + m_mouse_rubberband = false; + QGraphicsScene::mouseReleaseEvent(event); +} + +void PatchScene::wheelEvent(QGraphicsSceneWheelEvent* event) +{ + if (! m_view) + return event->ignore(); + + if (m_ctrl_down) + { + double factor = std::pow(1.41, (event->delta()/240.0)); + m_view->scale(factor, factor); + + fixScaleFactor(); + return event->accept(); + } + + QGraphicsScene::wheelEvent(event); +} diff --git a/c++/patchcanvas/patchscene.h b/c++/patchcanvas/patchscene.h new file mode 100644 index 0000000..f4bad40 --- /dev/null +++ b/c++/patchcanvas/patchscene.h @@ -0,0 +1,67 @@ +/* + * Patchbay Canvas engine using QGraphicsView/Scene + * Copyright (C) 2010-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 + */ + +#ifndef PATCHSCENE_H +#define PATCHSCENE_H + +#include + +class QKeyEvent; +class QGraphicsRectItem; +class QGraphicsSceneMouseEvent; +class QGraphicsSceneWheelEvent; +class QGraphicsView; + +class PatchScene : public QGraphicsScene +{ + Q_OBJECT + +public: + PatchScene(QObject* parent, QGraphicsView* view); + + void fixScaleFactor(); + void updateTheme(); + + void zoom_fit(); + void zoom_in(); + void zoom_out(); + void zoom_reset(); + +signals: + void scaleChanged(double); + void sceneGroupMoved(int, int, QPointF); + +private: + bool m_ctrl_down; + bool m_mouse_down_init; + bool m_mouse_rubberband; + + QGraphicsRectItem* m_rubberband; + bool m_rubberband_selection; + QPointF m_rubberband_orig_point; + + QGraphicsView* m_view; + + virtual void keyPressEvent(QKeyEvent* event); + virtual void keyReleaseEvent(QKeyEvent* event); + virtual void mousePressEvent(QGraphicsSceneMouseEvent* event); + virtual void mouseMoveEvent(QGraphicsSceneMouseEvent* event); + virtual void mouseReleaseEvent(QGraphicsSceneMouseEvent* event); + virtual void wheelEvent(QGraphicsSceneWheelEvent* event); +}; + +#endif // PATCHSCENE_H diff --git a/data/claudia b/data/claudia new file mode 100755 index 0000000..4517c51 --- /dev/null +++ b/data/claudia @@ -0,0 +1,38 @@ +#!/bin/bash + +# Check if already running + +PROCS=`ps -f -u $USER | grep /share/cadence/src/claudia.py | grep python` + +if [ x"$PROCS" != x"" ]; then + + # One instance only + PROC=`echo "$PROCS" | head -n 1` + + # Get PID + PID=`echo "$PROC" | awk '{printf$2}'` + + # Last check, just to make sure + if [ x"$PID" != x"" ]; then + + # Tell user about this + echo "Claudia already started, showing GUI now..." + + # Send SIGUSR2, shows the GUI + kill -USR2 "$PID" + + # Quit now + exit + + fi +fi + +if [ -f /usr/bin/python3 ]; then + PYTHON=/usr/bin/python3 +else + PYTHON=python +fi + +# We only got here if not running yet +INSTALL_PREFIX="X-PREFIX-X" +exec $PYTHON $INSTALL_PREFIX/share/cadence/src/claudia.py "$@" diff --git a/data/claudia-launcher b/data/claudia-launcher new file mode 100755 index 0000000..cec6538 --- /dev/null +++ b/data/claudia-launcher @@ -0,0 +1,38 @@ +#!/bin/bash + +# Check if already running + +PROCS=`ps -f -u $USER | grep /share/cadence/src/claudia-launcher.py | grep python` + +if [ x"$PROCS" != x"" ]; then + + # One instance only + PROC=`echo "$PROCS" | head -n 1` + + # Get PID + PID=`echo "$PROC" | awk '{printf$2}'` + + # Last check, just to make sure + if [ x"$PID" != x"" ]; then + + # Tell user about this + echo "Claudia-Launcher already started, showing GUI now..." + + # Send SIGUSR2, shows the GUI + kill -USR2 "$PID" + + # Quit now + exit + + fi +fi + +if [ -f /usr/bin/python3 ]; then + PYTHON=/usr/bin/python3 +else + PYTHON=python +fi + +# We only got here if not running yet +INSTALL_PREFIX="X-PREFIX-X" +exec $PYTHON $INSTALL_PREFIX/share/cadence/src/claudia_launcher.py "$@" diff --git a/data/claudia-launcher.desktop b/data/claudia-launcher.desktop new file mode 100644 index 0000000..d9a4dde --- /dev/null +++ b/data/claudia-launcher.desktop @@ -0,0 +1,11 @@ +[Desktop Entry] +Name=Claudia Launcher +GenericName=Claudia Launcher +GenericName[fr]=Lanceur Claudia +Comment=Multimedia Applications Launcher +Comment[fr]=Lanceur d'applications multimédia +Exec=claudia-launcher +Icon=claudia-launcher +Terminal=false +Type=Application +Categories=AudioVideo;AudioEditing;Qt; diff --git a/data/claudia.desktop b/data/claudia.desktop new file mode 100644 index 0000000..6daa548 --- /dev/null +++ b/data/claudia.desktop @@ -0,0 +1,10 @@ +[Desktop Entry] +Name=Claudia +GenericName=Claudia +Comment=LADISH Frontend +Comment[fr]=Interface graphique utilisateur pour LADISH +Exec=claudia +Icon=claudia +Terminal=false +Type=Application +Categories=AudioVideo;AudioEditing;Qt; diff --git a/resources/ui/cadence.ui b/resources/ui/cadence.ui index a37b124..2416343 100644 --- a/resources/ui/cadence.ui +++ b/resources/ui/cadence.ui @@ -6,8 +6,8 @@ 0 0 - 723 - 682 + 740 + 564 @@ -164,7 +164,49 @@ System Checks - + + + + + + + :/16x16/dialog-information.png + + + Qt::AlignCenter + + + + + + + + 0 + 0 + + + + Result + + + + + + + + 0 + 0 + + + + Test Name: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + 0 @@ -199,18 +241,9 @@ - - - 50 - false - - JACK Status - - false - @@ -429,26 +462,17 @@ - - - - Qt::Horizontal - - - QSizePolicy::Preferred - - - - 38 - 20 - + + + + Start - + - - + + - Configure + Stop @@ -459,17 +483,10 @@ - - - - Start - - - - - + + - Stop + Configure @@ -480,10 +497,33 @@ - + + + + Qt::Horizontal + + + QSizePolicy::Preferred + + + + 38 + 20 + + + + + - Auto-start JACK at login + Auto-start JACK or LADISH at login + + + + + + + ... @@ -504,16 +544,7 @@ JACK Bridges - - 2 - - - 2 - - - 2 - - + 2 @@ -541,8 +572,8 @@ 0 0 - 392 - 130 + 360 + 100 @@ -689,8 +720,8 @@ 0 0 - 392 - 118 + 360 + 97 @@ -823,8 +854,8 @@ 0 0 - 392 - 118 + 360 + 97 @@ -1062,30 +1093,30 @@ - + - Render + Claudia - + - + 0 - 128 + 117 16777215 - 128 + 117 - :/screens/cadence-render.png + :/screens/claudia.png Qt::AlignCenter @@ -1093,9 +1124,73 @@ - + - Render a JACK project + LADISH frontend; like Catia, but focused at session management + + + Qt::AlignCenter + + + true + + + + + + + + + + Meter In / Out + + + + + + 0 + + + + + + + + :/screens/cadence-jackmeter-in.png + + + + + + + + 0 + 117 + + + + + 16777215 + 117 + + + + + + + :/screens/cadence-jackmeter-out.png + + + Qt::AlignCenter + + + + + + + + + Digital peak meter, auto-connected to all system inputs or outputs Qt::AlignCenter @@ -1206,57 +1301,40 @@ - + - Meter In / Out + Render - + - - - 0 + + + + 0 + 128 + - - - - - - - :/screens/cadence-jackmeter-in.png - - - - - - - - 0 - 117 - - - - - 16777215 - 117 - - - - - - - :/screens/cadence-jackmeter-out.png - - - Qt::AlignCenter - - - - + + + 16777215 + 128 + + + + + + + :/screens/cadence-render.png + + + Qt::AlignCenter + + - + - Digital peak meter, auto-connected to all system inputs or outputs + Render a JACK project Qt::AlignCenter @@ -1494,7 +1572,7 @@ Audio Plugins PATH - AlignCenter + AlignHCenter|AlignVCenter|AlignCenter ItemIsSelectable|ItemIsEnabled @@ -1505,7 +1583,7 @@ Default Applications - AlignCenter + AlignHCenter|AlignVCenter|AlignCenter ItemIsSelectable|ItemIsEnabled @@ -1516,7 +1594,7 @@ WineASIO - AlignCenter + AlignHCenter|AlignVCenter|AlignCenter ItemIsSelectable|ItemIsEnabled @@ -1535,16 +1613,7 @@ - - 0 - - - 0 - - - 0 - - + 0 @@ -1612,8 +1681,8 @@ 0 0 - 396 - 401 + 416 + 334 @@ -1642,8 +1711,8 @@ 0 0 - 91 - 89 + 94 + 66 @@ -1672,8 +1741,8 @@ 0 0 - 91 - 89 + 94 + 66 @@ -1702,8 +1771,8 @@ 0 0 - 91 - 89 + 94 + 66 @@ -1763,21 +1832,12 @@ - - 0 - - - 0 - - - 0 - - - 0 - 20 + + 0 + @@ -2003,16 +2063,7 @@ - - 0 - - - 0 - - - 0 - - + 0 diff --git a/resources/ui/claudia.ui b/resources/ui/claudia.ui new file mode 100644 index 0000000..3283316 --- /dev/null +++ b/resources/ui/claudia.ui @@ -0,0 +1,1518 @@ + + + ClaudiaMainW + + + + 0 + 0 + 877 + 566 + + + + Claudia + + + + + + + + 0 + 0 + + + + + 4 + + + 0 + + + + + Studio + + + + + + + 23 + 16777215 + + + + New Studio + + + + + + + :/16x16/document-new.png:/16x16/document-new.png + + + + + + + + 23 + 16777215 + + + + Load Studio + + + + + + + :/16x16/document-open.png:/16x16/document-open.png + + + Ctrl+Shift+L + + + + + + + + 23 + 16777215 + + + + Save Studio + + + + + + + :/16x16/document-save.png:/16x16/document-save.png + + + + + + + + 23 + 16777215 + + + + Save Studio As + + + + + + + :/16x16/document-save-as.png:/16x16/document-save-as.png + + + + + + + + 23 + 16777215 + + + + Start Studio + + + + + + + :/16x16/media-playback-start.png:/16x16/media-playback-start.png + + + + + + + + 23 + 16777215 + + + + Stop Studio + + + + + + + :/16x16/media-playback-stop.png:/16x16/media-playback-stop.png + + + + + + + + + + Project + + + + + + + 23 + 16777215 + + + + New Project + + + + + + + :/16x16/document-new.png:/16x16/document-new.png + + + + + + + + 23 + 16777215 + + + + Open Project + + + + + + + :/16x16/document-open.png:/16x16/document-open.png + + + + + + + + 23 + 16777215 + + + + Save Project + + + + + + + :/16x16/document-save.png:/16x16/document-save.png + + + + + + + + 23 + 16777215 + + + + Save Project As + + + + + + + :/16x16/document-save-as.png:/16x16/document-save-as.png + + + + + + + + + + Transport + + + + + + + 23 + 16777215 + + + + Transport Play/Pause + + + + + + + :/16x16/media-playback-start.png:/16x16/media-playback-start.png + + + true + + + + + + + + 23 + 16777215 + + + + Transport Stop + + + + + + + :/16x16/media-playback-stop.png:/16x16/media-playback-stop.png + + + + + + + + 23 + 16777215 + + + + Transport Backwards + + + + + + + :/16x16/media-seek-backward.png:/16x16/media-seek-backward.png + + + true + + + + + + + + 23 + 16777215 + + + + Transport Forwards + + + + + + + :/16x16/media-seek-forward.png:/16x16/media-seek-forward.png + + + true + + + + + + + Qt::Vertical + + + + + + + + DejaVu Sans Mono + 9 + + + + Qt::CustomContextMenu + + + ??:??:?? + + + + + + + Beats per Minute + + + true + + + QAbstractSpinBox::NoButtons + + + BPM + + + 2 + + + 999.000000000000000 + + + 120.000000000000000 + + + + + + + + + + Canvas + + + + + + + 23 + 16777215 + + + + Zoom Auto-Fit + + + + + + + :/16x16/zoom-fit-best.png:/16x16/zoom-fit-best.png + + + + + + + + 23 + 16777215 + + + + Zoom In + + + + + + + :/16x16/zoom-in.png:/16x16/zoom-in.png + + + + + + + + 23 + 16777215 + + + + Zoom Out + + + + + + + :/16x16/zoom-out.png:/16x16/zoom-out.png + + + + + + + + 23 + 16777215 + + + + Zoom 100% + + + + + + + :/16x16/zoom-original.png:/16x16/zoom-original.png + + + + + + + + + + Tools + + + + + + + 23 + 16777215 + + + + Clear Xruns + + + + + + + :/16x16/edit-clear.png:/16x16/edit-clear.png + + + + + + + + 23 + 16777215 + + + + Configure JACK + + + + + + + :/16x16/configure.png:/16x16/configure.png + + + + + + + + 23 + 16777215 + + + + JACK Render + + + + + + + :/16x16/media-record.png:/16x16/media-record.png + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + 0 + 0 + + + + Qt::Horizontal + + + + + + + + 0 + 0 + + + + Qt::CustomContextMenu + + + QAbstractItemView::NoEditTriggers + + + false + + + true + + + false + + + + 1 + + + + + + + + QFrame::StyledPanel + + + QFrame::Sunken + + + + + + + + + + + + <table><tr><td width='100'>&nbsp;</td><td>If you've started ladish for the first time, you should:<br></td></tr><tr><td>&nbsp;</td><td> + 1. Create a new studio (in the menu, Studio -> New Studio)<br> + 2. Configure JACK (in the menu, Tools -> Configure JACK)<br> + 3. Start the studio (in the menu, Studio -> Start Studio)<br> + 4. Start apps (in the menu, Application -> Run)<br> + 5. Connect their ports by click &amp; drag on canvas<br> + 6. Save the studio (in the menu, Studio -> Save Studio) +</td></tr></table> + + + Qt::AlignCenter + + + + + + + Qt::ScrollBarAlwaysOn + + + Qt::ScrollBarAlwaysOn + + + + + + + + + + + + 0 + 0 + + + + + 16777215 + 32 + + + + QFrame::NoFrame + + + QFrame::Sunken + + + + 2 + + + + + Buffer Size: + + + + + + + + + + Qt::Vertical + + + + + + + Sample Rate: + + + + + + + + + + Qt::Vertical + + + + + + + RT + + + + + + + Qt::Vertical + + + + + + + ? Xruns + + + true + + + + + + + Qt::Vertical + + + + + + + 0 + + + DSP Load: %p% + + + + + + + + + + + + 0 + 0 + 877 + 20 + + + + + &Studio + + + + &Load Studio + + + + :/16x16/document-open.png:/16x16/document-open.png + + + + + &Delete Studio + + + + :/16x16/edit-delete.png:/16x16/edit-delete.png + + + + + + + + + + + + + + + + + + + &Room + + + + Delete Room + + + + :/16x16/edit-delete.png:/16x16/edit-delete.png + + + + + + + + &Project + + + + Load Project + + + + :/16x16/document-open.png:/16x16/document-open.png + + + + + + + + + + + + + &Application + + + + + + + &Transport + + + + + + + + + &Tools + + + + ALSA-MIDI Bridge + + + + + + + + + + + + + + + + S&ettings + + + + + + + + + &Help + + + + + + + &Canvas + + + + Zoom + + + + + + + + + + + + + + + + + + + + + + + + + + &About + + + + + + :/16x16/document-new.png:/16x16/document-new.png + + + &New Studio... + + + Ctrl+Shift+N + + + + + + :/16x16/media-playback-start.png:/16x16/media-playback-start.png + + + Start Studio + + + F5 + + + + + + :/16x16/media-playback-stop.png:/16x16/media-playback-stop.png + + + Stop Studio + + + F6 + + + + + + :/16x16/edit-rename.png:/16x16/edit-rename.png + + + &Rename Studio... + + + Ctrl+Shift+R + + + + + + :/16x16/document-save.png:/16x16/document-save.png + + + &Save Studio + + + Ctrl+Shift+S + + + + + + :/16x16/document-save-as.png:/16x16/document-save-as.png + + + Save Studio &As... + + + + + + :/16x16/dialog-close.png:/16x16/dialog-close.png + + + &Unload Studio + + + Ctrl+Shift+U + + + + + + :/16x16/application-exit.png:/16x16/application-exit.png + + + &Quit + + + Ctrl+Q + + + + + + :/16x16/list-add.png:/16x16/list-add.png + + + Create Room... + + + Ctrl+Shift+C + + + + + + :/16x16/document-save.png:/16x16/document-save.png + + + Save Project + + + Ctrl+S + + + + + + :/16x16/document-save-as.png:/16x16/document-save-as.png + + + Save Project As... + + + + + + :/16x16/dialog-close.png:/16x16/dialog-close.png + + + Unload Project + + + Ctrl+U + + + + + + :/16x16/list-add.png:/16x16/list-add.png + + + Add &New... + + + Shift+F2 + + + + + + :/16x16/system-run.png:/16x16/system-run.png + + + Run &Custom... + + + F2 + + + + + true + + + + :/16x16/media-playback-start.png:/16x16/media-playback-start.png + + + &Play + + + Ctrl+Shift+P + + + + + + :/16x16/media-playback-stop.png:/16x16/media-playback-stop.png + + + &Stop + + + Ctrl+Shift+X + + + + + + :/16x16/media-seek-backward.png:/16x16/media-seek-backward.png + + + &Backwards + + + Ctrl+Shift+B + + + + + + :/16x16/media-seek-forward.png:/16x16/media-seek-forward.png + + + &Forwards + + + Ctrl+Shift+F + + + + + + :/16x16/edit-clear.png:/16x16/edit-clear.png + + + Clear &Xruns + + + + + + :/16x16/configure.png:/16x16/configure.png + + + &Configure JACK + + + + + + :/16x16/view-refresh.png:/16x16/view-refresh.png + + + Reactivate ladishd + + + + + true + + + 32 + + + + + true + + + 64 + + + + + true + + + 128 + + + + + true + + + 256 + + + + + true + + + 512 + + + + + true + + + 1024 + + + + + true + + + 2048 + + + + + true + + + 4096 + + + + + true + + + 8192 + + + + + true + + + Show &Statusbar + + + + + true + + + Show &Toolbar + + + + + + :/16x16/configure.png:/16x16/configure.png + + + Configure Claudia + + + Ctrl+P + + + + + + :/16x16/view-sort-ascending.png:/16x16/view-sort-ascending.png + + + &Arrange + + + Ctrl+G + + + + + + :/16x16/view-refresh.png:/16x16/view-refresh.png + + + &Refresh + + + Ctrl+R + + + + + + :/16x16/zoom-fit-best.png:/16x16/zoom-fit-best.png + + + Auto-Fit + + + Home + + + + + + :/16x16/zoom-in.png:/16x16/zoom-in.png + + + Zoom In + + + Ctrl++ + + + + + + :/16x16/zoom-out.png:/16x16/zoom-out.png + + + Zoom Out + + + Ctrl+- + + + + + + :/16x16/zoom-original.png:/16x16/zoom-original.png + + + Zoom 100% + + + Ctrl+1 + + + + + Save Image... + + + + + About Qt + + + + + + :/16x16/media-record.png:/16x16/media-record.png + + + JACK R&ender + + + + + &Logs + + + + + + :/16x16/document-new.png:/16x16/document-new.png + + + New Project... + + + Ctrl+N + + + + + Load from folder... + + + Ctrl+L + + + + + + :/16x16/edit-rename.png:/16x16/edit-rename.png + + + Project Properties... + + + + + + :/16x16/media-playback-start.png:/16x16/media-playback-start.png + + + Start Bridge + + + + + + :/16x16/media-playback-stop.png:/16x16/media-playback-stop.png + + + Stop Bridge + + + + + Export Hardware Ports... + + + + + + CanvasPreviewFrame + QFrame +
canvaspreviewframe.h
+ 1 +
+
+ + + + + + act_quit + triggered() + ClaudiaMainW + close() + + + -1 + -1 + + + 314 + 276 + + + + + act_settings_show_toolbar + triggered(bool) + frame_toolbar + setVisible(bool) + + + -1 + -1 + + + 374 + 60 + + + + + act_settings_show_statusbar + triggered(bool) + frame_statusbar + setVisible(bool) + + + -1 + -1 + + + 374 + 536 + + + + + act_settings_show_toolbar + triggered(bool) + frame_toolbar + setEnabled(bool) + + + -1 + -1 + + + 438 + 52 + + + + + act_settings_show_statusbar + triggered(bool) + frame_statusbar + setEnabled(bool) + + + -1 + -1 + + + 438 + 546 + + + + +
diff --git a/resources/ui/claudia_createroom.ui b/resources/ui/claudia_createroom.ui new file mode 100644 index 0000000..57243b3 --- /dev/null +++ b/resources/ui/claudia_createroom.ui @@ -0,0 +1,88 @@ + + + CreateRoomW + + + + 0 + 0 + 245 + 239 + + + + Create room + + + + + + Name + + + + + + + + + + + + Create from template + + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + CreateRoomW + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + CreateRoomW + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/resources/ui/claudia_launcher.ui b/resources/ui/claudia_launcher.ui new file mode 100644 index 0000000..6187550 --- /dev/null +++ b/resources/ui/claudia_launcher.ui @@ -0,0 +1,2165 @@ + + + ClaudiaLauncherW + + + + 0 + 0 + 928 + 518 + + + + Form + + + + + + 0 + + + + DAW + + + + + + + 16777215 + 16 + + + + Digital Audio Workstations, Loopers, Sequencers and Trackers + + + Qt::AlignCenter + + + + + + + Qt::Horizontal + + + + + + + Qt::Horizontal + + + + Qt::CustomContextMenu + + + QAbstractItemView::NoEditTriggers + + + true + + + QAbstractItemView::SingleSelection + + + QAbstractItemView::SelectRows + + + true + + + true + + + false + + + 22 + + + + + + + + + Name + + + + + Type + + + + + Template? + + + + + + + 200 + 0 + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + + 0 + 48 + + + + + 16777215 + 48 + + + + ICO + + + Qt::AlignCenter + + + + + + + App Name + + + Qt::AlignCenter + + + + + + + Qt::Horizontal + + + + + + + Plugin support + + + Qt::AlignCenter + + + + + + LADSPA: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + ico + + + + + + + DSSI: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + ico + + + + + + + LV2: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + ico + + + + + + + VST: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 22 + 16777215 + + + + ico + + + + + + + mode + + + + + + + + + + Features + + + Qt::AlignCenter + + + + + + JACK transport: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Session Level: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + ico + + + + + + + MIDI Mode: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 22 + 16777215 + + + + ico + + + + + + + mode + + + + + + + # + + + + + + + + + + Help + + + Qt::AlignCenter + + + + + + Documentation + + + Qt::AlignCenter + + + true + + + + + + + WebSite + + + Qt::AlignCenter + + + true + + + + + + + (Nothing available) + + + Qt::AlignCenter + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + + Host + + + + + + + 16777215 + 16 + + + + Hosts for various kinds of Instruments, Effects and Tools + + + Qt::AlignCenter + + + + + + + Qt::Horizontal + + + + + + + Qt::Horizontal + + + + Qt::CustomContextMenu + + + QAbstractItemView::NoEditTriggers + + + true + + + QAbstractItemView::SingleSelection + + + QAbstractItemView::SelectRows + + + true + + + true + + + false + + + 22 + + + + + + + + + Name + + + + + Instruments + + + + + Effects + + + + + Template? + + + + + + + 200 + 0 + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + + 0 + 48 + + + + + 16777215 + 48 + + + + ICO + + + Qt::AlignCenter + + + + + + + App Name + + + Qt::AlignCenter + + + + + + + Qt::Horizontal + + + + + + + Plugin support + + + Qt::AlignCenter + + + + + + LADSPA: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + ico + + + + + + + DSSI: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + ico + + + + + + + LV2: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + ico + + + + + + + VST: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 22 + 16777215 + + + + ico + + + + + + + mode + + + + + + + Internal: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + ico + + + + + + + + + + Features + + + Qt::AlignCenter + + + + + + MIDI Mode: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + mode + + + + + + + Session Level: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + # + + + + + + + + + + Help + + + Qt::AlignCenter + + + + + + Documentation + + + Qt::AlignCenter + + + true + + + + + + + WebSite + + + Qt::AlignCenter + + + true + + + + + + + (Nothing available) + + + Qt::AlignCenter + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + + Instrument + + + + + + + 16777215 + 16 + + + + Instruments, Synths and Samplers + + + Qt::AlignCenter + + + + + + + Qt::Horizontal + + + + + + + Qt::Horizontal + + + + Qt::CustomContextMenu + + + QAbstractItemView::NoEditTriggers + + + true + + + QAbstractItemView::SingleSelection + + + QAbstractItemView::SelectRows + + + true + + + true + + + false + + + 22 + + + + + + + + + Name + + + + + Type + + + + + Template? + + + + + + + 200 + 0 + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + + 0 + 48 + + + + + 16777215 + 48 + + + + ICO + + + Qt::AlignCenter + + + + + + + App Name + + + Qt::AlignCenter + + + + + + + Qt::Horizontal + + + + + + + Features + + + Qt::AlignCenter + + + + + + MIDI Mode: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + mode + + + + + + + Session Level: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + # + + + + + + + Built-in FX: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + ico + + + + + + + Audio Input: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + ico + + + + + + + + + + Help + + + Qt::AlignCenter + + + + + + Documentation + + + Qt::AlignCenter + + + true + + + + + + + WebSite + + + Qt::AlignCenter + + + true + + + + + + + (Nothing available) + + + Qt::AlignCenter + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + + Bristol + + + + + + + 16777215 + 16 + + + + Bristol Vintage Synths + + + Qt::AlignCenter + + + + + + + Qt::Horizontal + + + + + + + Qt::Horizontal + + + + Qt::CustomContextMenu + + + QAbstractItemView::NoEditTriggers + + + true + + + QAbstractItemView::SingleSelection + + + QAbstractItemView::SelectRows + + + true + + + true + + + false + + + 22 + + + + + + + + + Short Name + + + + + Full Name + + + + + + + 200 + 0 + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + + 0 + 48 + + + + + 16777215 + 48 + + + + ICO + + + Qt::AlignCenter + + + + + + + App Name + + + Qt::AlignCenter + + + + + + + Qt::Horizontal + + + + + + + Features + + + Qt::AlignCenter + + + + + + MIDI Mode: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + mode + + + + + + + Session Level: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + # + + + + + + + Built-in FX: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Audio Input: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + ico + + + + + + + ico + + + + + + + + + + Help + + + Qt::AlignCenter + + + + + + Documentation + + + Qt::AlignCenter + + + true + + + + + + + WebSite + + + Qt::AlignCenter + + + true + + + + + + + (Nothing available) + + + Qt::AlignCenter + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + + Plugin + + + + + + + 16777215 + 16 + + + + <html><head/><body><p>Audio Plugins, loaded via Carla</p></body></html> + + + Qt::AlignCenter + + + + + + + Qt::Horizontal + + + + + + + Qt::Horizontal + + + + Qt::CustomContextMenu + + + QAbstractItemView::NoEditTriggers + + + true + + + QAbstractItemView::SingleSelection + + + QAbstractItemView::SelectRows + + + true + + + 3 + + + true + + + false + + + 22 + + + + Name + + + + + Author + + + + + Category + + + + + + + 200 + 0 + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + + 0 + 48 + + + + + 16777215 + 48 + + + + ICO + + + Qt::AlignCenter + + + + + + + Plugin Name + + + Qt::AlignCenter + + + + + + + Qt::Horizontal + + + + + + + Information + + + Qt::AlignCenter + + + + + + # + + + + + + + Audio Inputs: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Audio Outputs: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + MIDI Inputs: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + MIDI Outputs: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + # + + + + + + + # + + + + + + + # + + + + + + + Parameter Inputs: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Parameter Outputs: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + # + + + + + + + # + + + + + + + + + + Help + + + Qt::AlignCenter + + + + + + Documentation + + + Qt::AlignCenter + + + true + + + + + + + WebSite + + + Qt::AlignCenter + + + true + + + + + + + (Nothing available) + + + Qt::AlignCenter + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + + Effect + + + + + + + 16777215 + 16 + + + + Effects, EQs and Mixers + + + Qt::AlignCenter + + + + + + + Qt::Horizontal + + + + + + + Qt::Horizontal + + + + Qt::CustomContextMenu + + + QAbstractItemView::NoEditTriggers + + + true + + + QAbstractItemView::SingleSelection + + + QAbstractItemView::SelectRows + + + true + + + true + + + false + + + 22 + + + + + + + + + Name + + + + + Type + + + + + Template? + + + + + + + 200 + 0 + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + + 0 + 48 + + + + + 16777215 + 48 + + + + ICO + + + Qt::AlignCenter + + + + + + + App Name + + + Qt::AlignCenter + + + + + + + Qt::Horizontal + + + + + + + Features + + + Qt::AlignCenter + + + + + + MIDI Mode: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + mode + + + + + + + Session Level: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + # + + + + + + + Stereo: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + ico + + + + + + + + + + Help + + + Qt::AlignCenter + + + + + + Documentation + + + Qt::AlignCenter + + + true + + + + + + + WebSite + + + Qt::AlignCenter + + + true + + + + + + + (Nothing available) + + + Qt::AlignCenter + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + + Tool + + + + + + + 16777215 + 16 + + + + Misc Tools and Utilities + + + Qt::AlignCenter + + + + + + + Qt::Horizontal + + + + + + + Qt::Horizontal + + + + Qt::CustomContextMenu + + + QAbstractItemView::NoEditTriggers + + + true + + + QAbstractItemView::SingleSelection + + + QAbstractItemView::SelectRows + + + true + + + true + + + false + + + 22 + + + + + + + + + Name + + + + + Type + + + + + Template? + + + + + + + 200 + 0 + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + + 0 + 48 + + + + + 16777215 + 48 + + + + ICO + + + Qt::AlignCenter + + + + + + + App Name + + + Qt::AlignCenter + + + + + + + Qt::Horizontal + + + + + + + Features + + + Qt::AlignCenter + + + + + + MIDI Mode: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + mode + + + + + + + Session Level: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + # + + + + + + + JACK Transport: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + ico + + + + + + + + + + Help + + + Qt::AlignCenter + + + + + + Documentation + + + Qt::AlignCenter + + + true + + + + + + + WebSite + + + Qt::AlignCenter + + + true + + + + + + + (Nothing available) + + + Qt::AlignCenter + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + + + + + + diff --git a/resources/ui/claudia_launcher_app.ui b/resources/ui/claudia_launcher_app.ui new file mode 100644 index 0000000..065a977 --- /dev/null +++ b/resources/ui/claudia_launcher_app.ui @@ -0,0 +1,284 @@ + + + ClaudiaLauncherApp + + + + 0 + 0 + 830 + 133 + + + + Claudia Launcher + + + + + + + + + + 0 + 48 + + + + &Start + + + + :/16x16/arrow-right.png:/16x16/arrow-right.png + + + + + + + + 0 + 48 + + + + &Add to LADISH + + + + :/16x16/list-add.png:/16x16/list-add.png + + + + + + + Enable LADISH + + + Qt::AlignCenter + + + true + + + false + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Default BPM: + + + + + + + 1 + + + 999.000000000000000 + + + 130.000000000000000 + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Qt::Vertical + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Default Sample Rate: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Qt::Vertical + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + LADI Room: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + + 22 + 16777215 + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + Project folder: + + + + + + + + + + + 22 + 16777215 + + + + + + + + + + + + + + + + + + + + + + &Close + + + + + &About + + + + + + ClaudiaLauncher + QWidget +
claudia_launcher.h
+ 1 +
+
+ + + + +
diff --git a/resources/ui/claudia_projectname.ui b/resources/ui/claudia_projectname.ui new file mode 100644 index 0000000..bba01d1 --- /dev/null +++ b/resources/ui/claudia_projectname.ui @@ -0,0 +1,109 @@ + + + ProjectNameW + + + + 0 + 0 + 423 + 102 + + + + Save project + + + + + + Path: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + + 22 + 22 + + + + + + + + :/16x16/document-open.png:/16x16/document-open.png + + + + + + + Name: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + + + buttonBox + accepted() + ProjectNameW + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + ProjectNameW + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/resources/ui/claudia_projectproperties.ui b/resources/ui/claudia_projectproperties.ui new file mode 100644 index 0000000..8df61e9 --- /dev/null +++ b/resources/ui/claudia_projectproperties.ui @@ -0,0 +1,175 @@ + + + ProjectPropertiesW + + + + 0 + 0 + 373 + 417 + + + + Project Properties + + + + + + Project name + + + + + + + + + + + + Description + + + + + + + + + + + + Notes + + + + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Save Now + + + true + + + + + + + Qt::Horizontal + + + QSizePolicy::Preferred + + + + 40 + 20 + + + + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 10 + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + ProjectPropertiesW + accept() + + + 226 + 401 + + + 157 + 274 + + + + + buttonBox + rejected() + ProjectPropertiesW + reject() + + + 294 + 406 + + + 286 + 274 + + + + + cb_save_now + toggled(bool) + le_name + setEnabled(bool) + + + 274 + 359 + + + 223 + 41 + + + + + diff --git a/resources/ui/claudia_runcustom.ui b/resources/ui/claudia_runcustom.ui new file mode 100644 index 0000000..46d8c49 --- /dev/null +++ b/resources/ui/claudia_runcustom.ui @@ -0,0 +1,135 @@ + + + RunCustomW + + + + 0 + 0 + 400 + 234 + + + + New application + + + + + + + + Command: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + le_command + + + + + + + + + + Name: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + le_name + + + + + + + + + + + + Run in Terminal + + + + + + + Level 0 + + + + + + + Level 1 + + + + + + + Level 2 (LASH) + + + + + + + Level 2 (Jack Session) + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + RunCustomW + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + RunCustomW + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/resources/ui/claudia_studiolist.ui b/resources/ui/claudia_studiolist.ui new file mode 100644 index 0000000..0ee8461 --- /dev/null +++ b/resources/ui/claudia_studiolist.ui @@ -0,0 +1,111 @@ + + + StudioListW + + + + 0 + 0 + 345 + 362 + + + + Load Studio + + + + + + Studio list + + + + + + QAbstractItemView::NoEditTriggers + + + false + + + false + + + QAbstractItemView::SingleSelection + + + QAbstractItemView::SelectRows + + + true + + + false + + + 22 + + + + Name + + + + + Date + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + StudioListW + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + StudioListW + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/resources/ui/claudia_studioname.ui b/resources/ui/claudia_studioname.ui new file mode 100644 index 0000000..4d22048 --- /dev/null +++ b/resources/ui/claudia_studioname.ui @@ -0,0 +1,76 @@ + + + StudioNameW + + + + 0 + 0 + 312 + 106 + + + + Set name + + + + + + Studio name + + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + StudioNameW + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + StudioNameW + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/resources/ui/logs.ui b/resources/ui/logs.ui index 0d27c5c..ac83350 100644 --- a/resources/ui/logs.ui +++ b/resources/ui/logs.ui @@ -14,52 +14,10 @@ Logs
- - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - Qt::Horizontal - - - - - - - Purge all logs - - - - :/16x16/edit-delete.png:/16x16/edit-delete.png - - - - - - - Close - - - - :/16x16/window-close.png:/16x16/window-close.png - - - - 1 + 0 @@ -113,12 +71,106 @@
+ + + LASH + + + + + + Qt::ScrollBarAlwaysOn + + + Qt::ScrollBarAlwaysOn + + + QPlainTextEdit::NoWrap + + + Loading... + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + + LADISH + + + + + + Qt::ScrollBarAlwaysOn + + + Qt::ScrollBarAlwaysOn + + + QPlainTextEdit::NoWrap + + + Loading... + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + +
+
+ + + + Qt::Horizontal + + + + + + + Purge all logs + + + + :/16x16/edit-delete.png:/16x16/edit-delete.png + + + + + Close + + + + :/16x16/window-close.png:/16x16/window-close.png + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + +
- + diff --git a/src/cadence.py b/src/cadence.py index c8131a3..e67b11f 100755 --- a/src/cadence.py +++ b/src/cadence.py @@ -630,6 +630,61 @@ class ForceWaitDialog(QDialog, ui_cadence_rwait.Ui_Dialog): QDialog.done(self, r) self.close() +# Additional JACK options +class ToolBarJackDialog(QDialog, ui_cadence_tb_jack.Ui_Dialog): + def __init__(self, parent): + QDialog.__init__(self, parent) + self.setupUi(self) + + self.m_ladishLoaded = False + + if haveDBus: + if GlobalSettings.value("JACK/AutoLoadLadishStudio", False, type=bool): + self.rb_ladish.setChecked(True) + self.m_ladishLoaded = True + elif "org.ladish" in gDBus.bus.list_names(): + self.m_ladishLoaded = True + else: + self.rb_ladish.setEnabled(False) + self.rb_jack.setChecked(True) + + if self.m_ladishLoaded: + self.fillStudioNames() + + self.accepted.connect(self.slot_setOptions) + self.rb_ladish.clicked.connect(self.slot_maybeFillStudioNames) + + def fillStudioNames(self): + gDBus.ladish_control = gDBus.bus.get_object("org.ladish", "/org/ladish/Control") + + ladishStudioName = dbus.String(GlobalSettings.value("JACK/LadishStudioName", "", type=str)) + ladishStudioListDump = gDBus.ladish_control.GetStudioList() + + if len(ladishStudioListDump) == 0: + self.rb_ladish.setEnabled(False) + self.rb_jack.setChecked(True) + else: + i=0 + for thisStudioName, thisStudioDict in ladishStudioListDump: + self.cb_studio_name.addItem(thisStudioName) + if ladishStudioName and thisStudioName == ladishStudioName: + self.cb_studio_name.setCurrentIndex(i) + i += 1 + + @pyqtSlot() + def slot_maybeFillStudioNames(self): + if not self.m_ladishLoaded: + self.fillStudioNames() + self.m_ladishLoaded = True + + @pyqtSlot() + def slot_setOptions(self): + GlobalSettings.setValue("JACK/AutoLoadLadishStudio", self.rb_ladish.isChecked()) + GlobalSettings.setValue("JACK/LadishStudioName", self.cb_studio_name.currentText()) + + def done(self, r): + QDialog.done(self, r) + self.close() # Additional ALSA Audio options class ToolBarAlsaAudioDialog(QDialog, ui_cadence_tb_alsa.Ui_Dialog): @@ -799,6 +854,10 @@ class CadenceMainW(QMainWindow, ui_cadence.Ui_CadenceMainW): # ------------------------------------------------------------- # Set-up GUI (System Checks) + #self.label_check_helper1.setVisible(False) + #self.label_check_helper2.setVisible(False) + #self.label_check_helper3.setVisible(False) + index = 2 checksLayout = self.groupBox_checks.layout() @@ -844,6 +903,7 @@ class CadenceMainW(QMainWindow, ui_cadence.Ui_CadenceMainW): if sys.argv[0].endswith("/cadence"): self.groupBox_bridges.setEnabled(False) self.cb_jack_autostart.setEnabled(False) + self.tb_jack_options.setEnabled(False) # ------------------------------------------------------------- # Set-up GUI (Tweaks) @@ -1054,6 +1114,7 @@ class CadenceMainW(QMainWindow, ui_cadence.Ui_CadenceMainW): self.systray.addMenu("tools", self.tr("Tools")) self.systray.addMenuAction("tools", "app_catarina", "Catarina") self.systray.addMenuAction("tools", "app_catia", "Catia") + self.systray.addMenuAction("tools", "app_claudia", "Claudia") self.systray.addMenuSeparator("tools", "tools_sep") self.systray.addMenuAction("tools", "app_logs", self.tr("Logs")) self.systray.addMenuAction("tools", "app_meter_in", self.tr("Meter (Inputs)")) @@ -1064,6 +1125,7 @@ class CadenceMainW(QMainWindow, ui_cadence.Ui_CadenceMainW): self.systray.connect("app_catarina", self.func_start_catarina) self.systray.connect("app_catia", self.func_start_catia) + self.systray.connect("app_claudia", self.func_start_claudia) self.systray.connect("app_logs", self.func_start_logs) self.systray.connect("app_meter_in", self.func_start_jackmeter_in) self.systray.connect("app_meter_out", self.func_start_jackmeter) @@ -1081,6 +1143,7 @@ class CadenceMainW(QMainWindow, ui_cadence.Ui_CadenceMainW): self.b_jack_restart.clicked.connect(self.slot_JackServerForceRestart) self.b_jack_configure.clicked.connect(self.slot_JackServerConfigure) self.b_jack_switchmaster.clicked.connect(self.slot_JackServerSwitchMaster) + self.tb_jack_options.clicked.connect(self.slot_JackOptions) self.b_alsa_start.clicked.connect(self.slot_AlsaBridgeStart) self.b_alsa_stop.clicked.connect(self.slot_AlsaBridgeStop) @@ -1094,6 +1157,7 @@ class CadenceMainW(QMainWindow, ui_cadence.Ui_CadenceMainW): self.b_pulse_channels.clicked.connect(self.slot_PulseAudioBridgeChannels) self.pic_catia.clicked.connect(self.func_start_catia) + self.pic_claudia.clicked.connect(self.func_start_claudia) self.pic_meter_in.clicked.connect(self.func_start_jackmeter_in) self.pic_meter_out.clicked.connect(self.func_start_jackmeter) self.pic_logs.clicked.connect(self.func_start_logs) @@ -1513,6 +1577,10 @@ class CadenceMainW(QMainWindow, ui_cadence.Ui_CadenceMainW): def func_start_catia(self): self.func_start_tool("catia") + @pyqtSlot() + def func_start_claudia(self): + self.func_start_tool("claudia") + @pyqtSlot() def func_start_logs(self): self.func_start_tool("cadence-logs") @@ -1688,6 +1756,10 @@ class CadenceMainW(QMainWindow, ui_cadence.Ui_CadenceMainW): self.jackStarted() + @pyqtSlot() + def slot_JackOptions(self): + ToolBarJackDialog(self).exec_() + @pyqtSlot() def slot_JackClearXruns(self): if gDBus.jack: diff --git a/src/cadence_session_start.py b/src/cadence_session_start.py index 9fc73f7..28e774e 100755 --- a/src/cadence_session_start.py +++ b/src/cadence_session_start.py @@ -2,9 +2,11 @@ # -*- coding: utf-8 -*- # Imports (Global) -import dbus -import sys -from PyQt5.QtCore import QCoreApplication +import dbus, sys +if True: + from PyQt5.QtCore import QCoreApplication +else: + from PyQt4.QtCore import QCoreApplication # Imports (Custom Stuff) from shared_cadence import * @@ -73,7 +75,48 @@ def startSession(systemStarted, secondSystemStartAttempt): except: DBus.a2j = None - startJack() + if GlobalSettings.value("JACK/AutoLoadLadishStudio", False, type=bool): + try: + ladish_control = DBus.bus.get_object("org.ladish", "/org/ladish/Control") + except: + startJack() + return False + + try: + ladish_conf = DBus.bus.get_object("org.ladish.conf", "/org/ladish/conf") + except: + ladish_conf = None + + ladishStudioName = dbus.String(GlobalSettings.value("JACK/LadishStudioName", "", type=str)) + ladishStudioListDump = ladish_control.GetStudioList() + + for thisStudioName, thisStudioDict in ladishStudioListDump: + if ladishStudioName == thisStudioName: + try: + if ladish_conf and ladish_conf.get('/org/ladish/daemon/notify')[0] == "true": + ladish_conf.set('/org/ladish/daemon/notify', "false") + ladishNotifyHack = True + else: + ladishNotifyHack = False + except: + ladishNotifyHack = False + + ladish_control.LoadStudio(thisStudioName) + ladish_studio = DBus.bus.get_object("org.ladish", "/org/ladish/Studio") + + if not bool(ladish_studio.IsStarted()): + ladish_studio.Start() + + if ladishNotifyHack: + ladish_conf.set('/org/ladish/daemon/notify', "true") + + break + + else: + startJack() + + else: + startJack() if not bool(DBus.jack.IsStarted()): print("JACK Failed to Start") diff --git a/src/claudia.py b/src/claudia.py new file mode 100755 index 0000000..9f3fff6 --- /dev/null +++ b/src/claudia.py @@ -0,0 +1,2793 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +# LADISH frontend +# Copyright (C) 2010-2018 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 time import ctime + +if True: + from PyQt5.QtCore import QPointF + from PyQt5.QtWidgets import QAction, QApplication, QCheckBox, QHBoxLayout, QVBoxLayout, QTableWidgetItem, QTreeWidgetItem +else: + from PyQt4.QtCore import QPointF + from PyQt4.QtGui import QAction, QApplication, QCheckBox, QHBoxLayout, QVBoxLayout, QTableWidgetItem, QTreeWidgetItem + +# ------------------------------------------------------------------------------------------------------------ +# Imports (Custom Stuff) + +import claudia_launcher +import systray +import ui_claudia +import ui_claudia_studioname +import ui_claudia_studiolist +import ui_claudia_createroom +import ui_claudia_projectname +import ui_claudia_projectproperties +import ui_claudia_runcustom +from shared_canvasjack import * +from shared_settings import * +from shared_i18n import * + +# ------------------------------------------------------------------------------------------------------------ +# Try Import DBus + +try: + import dbus + from dbus.mainloop.pyqt5 import DBusQtMainLoop + haveDBus = True +except: + haveDBus = False + +# ------------------------------------------------------------------------------------------------------------ +# Try Import OpenGL + +try: + from PyQt5.QtOpenGL import QGLWidget + hasGL = True +except: + hasGL = False + +# ------------------------------------------------------------------------------------------------------------ +# Static Variables + +# NOTE - set to true when supported +USE_CLAUDIA_ADD_NEW = True + +# internal indexes +iConnId = 0 +iConnOutput = 1 +iConnInput = 2 + +iAppCommand = 0 +iAppName = 1 +iAppTerminal = 2 +iAppLevel = 3 +iAppActive = 4 + +iAppPropName = 0 +iAppPropDescription = 1 +iAppPropNotes = 2 +iAppPropSaveNow = 3 + +iItemPropNumber = 0 +iItemPropName = 1 +iItemPropActive = 2 +iItemPropTerminal = 3 +iItemPropLevel = 4 + +iItemPropRoomPath = 0 +iItemPropRoomName = 1 + +# jackdbus indexes +iGraphVersion = 0 +iJackClientId = 1 +iJackClientName = 2 +iJackPortId = 3 +iJackPortName = 4 +iJackPortNewName = 5 +iJackPortFlags = 5 +iJackPortType = 6 + +iRenamedId = 1 +iRenamedOldName = 2 +iRenamedNewName = 3 + +iSourceClientId = 1 +iSourceClientName = 2 +iSourcePortId = 3 +iSourcePortName = 4 +iTargetClientId = 5 +iTargetClientName = 6 +iTargetPortId = 7 +iTargetPortName = 8 +iJackConnId = 9 + +# ladish indexes +iStudioListName = 0 +iStudioListDict = 1 + +iStudioRenamedName = 0 + +iRoomAppearedPath = 0 +iRoomAppearedDict = 1 + +iProjChangedId = 0 +iProjChangedDict = 1 + +iAppChangedNumber = 1 +iAppChangedName = 2 +iAppChangedActive = 3 +iAppChangedTerminal = 4 +iAppChangedLevel = 5 + +# internal defines +ITEM_TYPE_NULL = 0 +ITEM_TYPE_STUDIO = 1 +ITEM_TYPE_STUDIO_APP = 2 +ITEM_TYPE_ROOM = 3 +ITEM_TYPE_ROOM_APP = 4 + +# C defines +JACKDBUS_PORT_FLAG_INPUT = 0x01 +JACKDBUS_PORT_FLAG_OUTPUT = 0x02 +JACKDBUS_PORT_FLAG_PHYSICAL = 0x04 +JACKDBUS_PORT_FLAG_CAN_MONITOR = 0x08 +JACKDBUS_PORT_FLAG_TERMINAL = 0x10 + +JACKDBUS_PORT_TYPE_AUDIO = 0 +JACKDBUS_PORT_TYPE_MIDI = 1 + +GRAPH_DICT_OBJECT_TYPE_GRAPH = 0 +GRAPH_DICT_OBJECT_TYPE_CLIENT = 1 +GRAPH_DICT_OBJECT_TYPE_PORT = 2 +GRAPH_DICT_OBJECT_TYPE_CONNECTION = 3 + +URI_A2J_PORT = "http://ladish.org/ns/a2j" +URI_CANVAS_WIDTH = "http://ladish.org/ns/canvas/width" +URI_CANVAS_HEIGHT = "http://ladish.org/ns/canvas/height" +URI_CANVAS_X = "http://ladish.org/ns/canvas/x" +URI_CANVAS_Y = "http://ladish.org/ns/canvas/y" +URI_CANVAS_SPLIT = "http://kxstudio.sf.net/ns/canvas/split" +URI_CANVAS_X_SPLIT = "http://kxstudio.sf.net/ns/canvas/x_split" +URI_CANVAS_Y_SPLIT = "http://kxstudio.sf.net/ns/canvas/y_split" +URI_CANVAS_ICON = "http://kxstudio.sf.net/ns/canvas/icon" + +DEFAULT_CANVAS_WIDTH = 3100 +DEFAULT_CANVAS_HEIGHT = 2400 + +RECENT_PROJECTS_STORE_MAX_ITEMS = 50 + +# ------------------------------------------------------------------------------------------------------------ +# Set default project folder + +DEFAULT_PROJECT_FOLDER = os.path.join(HOME, "ladish-projects") +setDefaultProjectFolder(DEFAULT_PROJECT_FOLDER) + +# ------------------------------------------------------------------------------------------------------------ +# Studio Name Dialog + +class StudioNameW(QDialog): + NEW = 1 + RENAME = 2 + SAVE_AS = 3 + + def __init__(self, parent, mode): + QDialog.__init__(self, parent) + self.ui = ui_claudia_studioname.Ui_StudioNameW() + self.ui.setupUi(self) + + self.ui.buttonBox.button(QDialogButtonBox.Ok).setEnabled(False) + + if mode == self.NEW: + self.setWindowTitle(self.tr("New studio")) + elif mode == self.RENAME: + self.setWindowTitle(self.tr("Rename studio")) + elif mode == self.SAVE_AS: + self.setWindowTitle(self.tr("Save studio as")) + + self.fMode = mode + self.fStudioList = [] + + if mode == self.RENAME and bool(gDBus.ladish_control.IsStudioLoaded()): + currentName = str(gDBus.ladish_studio.GetName()) + self.fStudioList.append(currentName) + self.ui.le_name.setText(currentName) + + studioList = gDBus.ladish_control.GetStudioList() + for studio in studioList: + self.fStudioList.append(str(studio[iStudioListName])) + + self.accepted.connect(self.slot_setReturn) + self.ui.le_name.textChanged.connect(self.slot_checkText) + + self.fRetStudioName = "" + + @pyqtSlot(str) + def slot_checkText(self, text): + if self.fMode == self.SAVE_AS: + check = bool(text) + else: + check = bool(text and text not in self.fStudioList) + + self.ui.buttonBox.button(QDialogButtonBox.Ok).setEnabled(check) + + @pyqtSlot() + def slot_setReturn(self): + self.fRetStudioName = self.ui.le_name.text() + + def done(self, r): + QDialog.done(self, r) + self.close() + +# Studio List Dialog +class StudioListW(QDialog, ui_claudia_studiolist.Ui_StudioListW): + def __init__(self, parent): + QDialog.__init__(self, parent) + self.setupUi(self) + + self.buttonBox.button(QDialogButtonBox.Ok).setEnabled(False) + self.tableWidget.setColumnWidth(0, 125) + + index = 0 + studio_list = gDBus.ladish_control.GetStudioList() + for studio in studio_list: + name = str(studio[iStudioListName]) + date = ctime(float(studio[iStudioListDict]["Modification Time"])) + + w_name = QTableWidgetItem(name) + w_date = QTableWidgetItem(date) + self.tableWidget.insertRow(index) + self.tableWidget.setItem(index, 0, w_name) + self.tableWidget.setItem(index, 1, w_date) + + index += 1 + + self.accepted.connect(self.slot_setReturn) + self.tableWidget.cellDoubleClicked.connect(self.accept) + self.tableWidget.currentCellChanged.connect(self.slot_checkSelection) + + if self.tableWidget.rowCount() > 0: + self.tableWidget.setCurrentCell(0, 0) + + self.ret_studio_name = "" + + @pyqtSlot(int) + def slot_checkSelection(self, row): + check = bool(row >= 0) + self.buttonBox.button(QDialogButtonBox.Ok).setEnabled(check) + + @pyqtSlot() + def slot_setReturn(self): + if self.tableWidget.rowCount() >= 0: + self.ret_studio_name = self.tableWidget.item(self.tableWidget.currentRow(), 0).text() + + def done(self, r): + QDialog.done(self, r) + self.close() + +# Create Room Dialog +class CreateRoomW(QDialog, ui_claudia_createroom.Ui_CreateRoomW): + def __init__(self, parent): + QDialog.__init__(self, parent) + self.setupUi(self) + + self.buttonBox.button(QDialogButtonBox.Ok).setEnabled(False) + + templates_list = gDBus.ladish_control.GetRoomTemplateList() + for template_name, template_dict in templates_list: + self.lw_templates.addItem(template_name) + + self.accepted.connect(self.slot_setReturn) + self.le_name.textChanged.connect(self.slot_checkText) + + if self.lw_templates.count() > 0: + self.lw_templates.setCurrentRow(0) + + self.ret_room_name = "" + self.ret_room_template = "" + + @pyqtSlot(str) + def slot_checkText(self, text): + check = bool(text and self.lw_templates.currentRow() >= 0) + self.buttonBox.button(QDialogButtonBox.Ok).setEnabled(check) + + @pyqtSlot() + def slot_setReturn(self): + if self.lw_templates.count() > 0: + self.ret_room_name = self.le_name.text() + self.ret_room_template = self.lw_templates.currentItem().text() + + def done(self, r): + QDialog.done(self, r) + self.close() + +# Project Name Dialog +class ProjectNameW(QDialog, ui_claudia_projectname.Ui_ProjectNameW): + NEW = 1 + SAVE_AS = 2 + + def __init__(self, parent, mode, proj_folder, path="", name=""): + QDialog.__init__(self, parent) + self.setupUi(self) + + self.buttonBox.button(QDialogButtonBox.Ok).setEnabled(False) + + if mode == self.NEW: + self.setWindowTitle(self.tr("New project")) + elif mode == self.SAVE_AS: + self.setWindowTitle(self.tr("Save project as")) + self.le_path.setText(path) + self.le_name.setText(name) + self.checkText(path, name) + + self.m_proj_folder = proj_folder + + self.accepted.connect(self.slot_setReturn) + self.b_open.clicked.connect(self.slot_checkFolder) + self.le_path.textChanged.connect(self.slot_checkText_path) + self.le_name.textChanged.connect(self.slot_checkText_name) + + self.ret_project_name = "" + self.ret_project_path = "" + + def checkText(self, name, path): + check = bool(name and path and os.path.exists(path) and os.path.isdir(path)) + self.buttonBox.button(QDialogButtonBox.Ok).setEnabled(check) + + @pyqtSlot() + def slot_checkFolder(self): + # Create default project folder if the project has not been set yet + if not self.le_path.text(): + if not os.path.exists(self.m_proj_folder): + os.mkdir(self.m_proj_folder) + + if self.le_path.text(): + proj_path = self.le_path.text() + else: + proj_path = self.m_proj_folder + + getAndSetPath(self, proj_path, self.le_path) + + @pyqtSlot(str) + def slot_checkText_name(self, text): + self.checkText(text, self.le_path.text()) + + @pyqtSlot(str) + def slot_checkText_path(self, text): + self.checkText(self.le_name.text(), text) + + @pyqtSlot() + def slot_setReturn(self): + self.ret_project_name = self.le_name.text() + self.ret_project_path = self.le_path.text() + + def done(self, r): + QDialog.done(self, r) + self.close() + +# Project Properties Dialog +class ProjectPropertiesW(QDialog, ui_claudia_projectproperties.Ui_ProjectPropertiesW): + def __init__(self, parent, name, description, notes): + QDialog.__init__(self, parent) + self.setupUi(self) + + self.m_default_name = name + self.m_last_name = name + + self.accepted.connect(self.slot_setReturn) + self.le_name.textChanged.connect(self.slot_checkText_name) + self.cb_save_now.clicked.connect(self.slot_checkSaveNow) + + self.le_name.setText(name) + self.le_description.setText(description) + self.le_notes.setPlainText(notes) + + self.ret_obj = None + + @pyqtSlot() + def slot_setReturn(self): + self.ret_obj = [None, None, None, None] + self.ret_obj[iAppPropName] = self.le_name.text() + self.ret_obj[iAppPropDescription] = self.le_description.text() + self.ret_obj[iAppPropNotes] = self.le_notes.toPlainText() # plainText() + self.ret_obj[iAppPropSaveNow] = self.cb_save_now.isChecked() + + @pyqtSlot(bool) + def slot_checkSaveNow(self, save): + if save: + self.le_name.setText(self.m_last_name) + else: + self.m_last_name = self.le_name.text() + self.le_name.setText(self.m_default_name) + + @pyqtSlot(str) + def slot_checkText_name(self, text): + check = bool(text) + self.buttonBox.button(QDialogButtonBox.Ok).setEnabled(check) + + def done(self, r): + QDialog.done(self, r) + self.close() + +# Run Custom App Dialog +class RunCustomW(QDialog, ui_claudia_runcustom.Ui_RunCustomW): + def __init__(self, parent, isRoom, app_obj=None): + QDialog.__init__(self, parent) + self.setupUi(self) + + self.buttonBox.button(QDialogButtonBox.Ok).setEnabled(bool(app_obj)) + + if app_obj: + self.le_command.setText(app_obj[iAppCommand]) + self.le_name.setText(app_obj[iAppName]) + self.cb_terminal.setChecked(app_obj[iAppTerminal]) + + level = app_obj[iAppLevel] + if level == "0": + self.rb_level_0.setChecked(True) + elif level == "1": + self.rb_level_1.setChecked(True) + elif level == "lash": + self.rb_level_lash.setChecked(True) + elif level == "jacksession": + self.rb_level_js.setChecked(True) + else: + self.rb_level_0.setChecked(True) + + if app_obj[iAppActive]: + self.le_command.setEnabled(False) + self.cb_terminal.setEnabled(False) + self.rb_level_0.setEnabled(False) + self.rb_level_1.setEnabled(False) + self.rb_level_lash.setEnabled(False) + self.rb_level_js.setEnabled(False) + else: + self.rb_level_0.setChecked(True) + + if not isRoom: + self.rb_level_lash.setEnabled(False) + self.rb_level_js.setEnabled(False) + + self.accepted.connect(self.slot_setReturn) + self.le_command.textChanged.connect(self.slot_checkText) + + self.ret_app_obj = None + + @pyqtSlot(str) + def slot_checkText(self, text): + check = bool(text) + self.buttonBox.button(QDialogButtonBox.Ok).setEnabled(check) + + @pyqtSlot() + def slot_setReturn(self): + if self.rb_level_0.isChecked(): + level = "0" + elif self.rb_level_1.isChecked(): + level = "1" + elif self.rb_level_lash.isChecked(): + level = "lash" + elif self.rb_level_js.isChecked(): + level = "jacksession" + else: + return + + self.ret_app_obj = [None, None, None, None, None] + self.ret_app_obj[iAppCommand] = self.le_command.text() + self.ret_app_obj[iAppName] = self.le_name.text() + self.ret_app_obj[iAppTerminal] = self.cb_terminal.isChecked() + self.ret_app_obj[iAppLevel] = level + self.ret_app_obj[iAppActive] = False + + def done(self, r): + QDialog.done(self, r) + self.close() + +# Add Application Dialog +class ClaudiaLauncherW(QDialog): + def __init__(self, parent, appBus, proj_folder, is_room, bpm, sample_rate): + QDialog.__init__(self, parent) + + self.launcher = claudia_launcher.ClaudiaLauncher(self) + self.buttonBox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Close, Qt.Horizontal, self) + self.checkBox = QCheckBox(self) + self.checkBox.setText(self.tr("Auto-close")) + self.checkBox.setChecked(True) + + self.layoutV = QVBoxLayout(self) + self.layoutH = QHBoxLayout() + + #if QDialogButtonBox.ButtonLayout: + self.layoutH.addWidget(self.checkBox) + self.layoutH.addWidget(self.buttonBox) + #else: + #self.layoutH.addWidget(self.buttonBox) + #self.layoutH.addWidget(self.checkBox) + + self.layoutV.addWidget(self.launcher) + self.layoutV.addLayout(self.layoutH) + + self.settings = QSettings("Cadence", "Claudia-Launcher") + self.launcher.setCallbackApp(self, self.settings, True) + self.loadSettings() + self.setWindowTitle("Claudia Launcher") + + self.m_appBus = appBus + self.m_proj_folder = proj_folder + self.m_is_room = is_room + self.m_bpm = bpm + self.m_sampleRate = sample_rate + + self.test_url = True + self.test_selected = False + + self.buttonBox.button(QDialogButtonBox.Ok).setEnabled(False) + + self.buttonBox.button(QDialogButtonBox.Ok).clicked.connect(self.slot_addAppToLADISH) + self.buttonBox.button(QDialogButtonBox.Close).clicked.connect(self.reject) + + # ---------------------------------------- + # Callbacks + + def callback_checkGUI(self, test_selected=None): + if test_selected != None: + self.test_selected = test_selected + + if self.test_url and self.test_selected: + self.buttonBox.button(QDialogButtonBox.Ok).setEnabled(True) + else: + self.buttonBox.button(QDialogButtonBox.Ok).setEnabled(False) + + def callback_getProjectFolder(self): + return self.m_proj_folder + + def callback_getAppBus(self): + return self.m_appBus + + def callback_getBPM(self): + if self.m_bpm < 30: + return 120.0 + else: + return self.m_bpm + + def callback_getSampleRate(self): + return self.m_sampleRate + + def callback_isLadishRoom(self): + return self.m_is_room + + # ---------------------------------------- + + @pyqtSlot() + def slot_addAppToLADISH(self): + self.launcher.addAppToLADISH() + + if self.checkBox.isChecked(): + self.accept() + + def saveSettings(self): + self.settings.setValue("Geometry", self.saveGeometry()) + self.launcher.saveSettings() + + def loadSettings(self): + if self.settings.contains("Geometry"): + self.restoreGeometry(self.settings.value("Geometry", b"")) + else: + self.resize(850, 500) + self.launcher.loadSettings() + + def closeEvent(self, event): + self.saveSettings() + QDialog.closeEvent(self, event) + + def done(self, r): + QDialog.done(self, r) + self.close() + +# ------------------------------------------------------------------------------------------------------------ +# Claudia Main Window + +class ClaudiaMainW(AbstractCanvasJackClass): + DBusCrashCallback = pyqtSignal(str) + DBusServerStartedCallback = pyqtSignal() + DBusServerStoppedCallback = pyqtSignal() + DBusClientAppearedCallback = pyqtSignal(int, str) + DBusClientDisappearedCallback = pyqtSignal(int) + DBusClientRenamedCallback = pyqtSignal(int, str) + DBusPortAppearedCallback = pyqtSignal(int, int, str, int, int) + DBusPortDisppearedCallback = pyqtSignal(int) + DBusPortRenamedCallback = pyqtSignal(int, str) + DBusPortsConnectedCallback = pyqtSignal(int, int, int) + DBusPortsDisconnectedCallback = pyqtSignal(int) + DBusStudioAppearedCallback = pyqtSignal() + DBusStudioDisappearedCallback = pyqtSignal() + DBusQueueExecutionHaltedCallback = pyqtSignal() + DBusCleanExitCallback = pyqtSignal() + DBusStudioStartedCallback = pyqtSignal() + DBusStudioStoppedCallback = pyqtSignal() + DBusStudioRenamedCallback = pyqtSignal(str) + DBusStudioCrashedCallback = pyqtSignal() + DBusRoomAppearedCallback = pyqtSignal(str, str) + DBusRoomDisappearedCallback = pyqtSignal(str) + DBusRoomChangedCallback = pyqtSignal() + DBusProjectPropertiesChanged = pyqtSignal(str, str) + DBusAppAdded2Callback = pyqtSignal(str, int, str, bool, bool, str) + DBusAppRemovedCallback = pyqtSignal(str, int) + DBusAppStateChanged2Callback = pyqtSignal(str, int, str, bool, bool, str) + + def __init__(self, parent=None): + AbstractCanvasJackClass.__init__(self, "Claudia", ui_claudia.Ui_ClaudiaMainW, parent) + + self.m_lastItemType = None + self.m_lastRoomPath = None + + self.m_crashedJACK = False + self.m_crashedLADISH = False + + self.loadSettings(True) + + # ------------------------------------------------------------- + # Set-up GUI + + setIcons(self, ["canvas", "jack", "transport"]) + + self.ui.act_studio_new.setIcon(getIcon("document-new")) + self.ui.menu_studio_load.setIcon(getIcon("document-open")) + self.ui.act_studio_start.setIcon(getIcon("media-playback-start")) + self.ui.act_studio_stop.setIcon(getIcon("media-playback-stop")) + self.ui.act_studio_rename.setIcon(getIcon("edit-rename")) + self.ui.act_studio_save.setIcon(getIcon("document-save")) + self.ui.act_studio_save_as.setIcon(getIcon("document-save-as")) + self.ui.act_studio_unload.setIcon(getIcon("window-close")) + self.ui.menu_studio_delete.setIcon(getIcon("edit-delete")) + self.ui.b_studio_new.setIcon(getIcon("document-new")) + self.ui.b_studio_load.setIcon(getIcon("document-open")) + self.ui.b_studio_save.setIcon(getIcon("document-save")) + self.ui.b_studio_save_as.setIcon(getIcon("document-save-as")) + self.ui.b_studio_start.setIcon(getIcon("media-playback-start")) + self.ui.b_studio_stop.setIcon(getIcon("media-playback-stop")) + + self.ui.act_room_create.setIcon(getIcon("list-add")) + self.ui.menu_room_delete.setIcon(getIcon("edit-delete")) + + self.ui.act_project_new.setIcon(getIcon("document-new")) + self.ui.menu_project_load.setIcon(getIcon("document-open")) + self.ui.act_project_save.setIcon(getIcon("document-save")) + self.ui.act_project_save_as.setIcon(getIcon("document-save-as")) + self.ui.act_project_unload.setIcon(getIcon("window-close")) + self.ui.act_project_properties.setIcon(getIcon("edit-rename")) + self.ui.b_project_new.setIcon(getIcon("document-new")) + self.ui.b_project_load.setIcon(getIcon("document-open")) + self.ui.b_project_save.setIcon(getIcon("document-save")) + self.ui.b_project_save_as.setIcon(getIcon("document-save-as")) + + self.ui.act_app_add_new.setIcon(getIcon("list-add")) + self.ui.act_app_run_custom.setIcon(getIcon("system-run")) + + self.ui.act_tools_reactivate_ladishd.setIcon(getIcon("view-refresh")) + self.ui.act_quit.setIcon(getIcon("application-exit")) + self.ui.act_settings_configure.setIcon(getIcon("configure")) + + self.ui.cb_buffer_size.clear() + self.ui.cb_sample_rate.clear() + + for bufferSize in BUFFER_SIZE_LIST: + self.ui.cb_buffer_size.addItem(str(bufferSize)) + + for sampleRate in SAMPLE_RATE_LIST: + self.ui.cb_sample_rate.addItem(str(sampleRate)) + + # ------------------------------------------------------------- + # Set-up Systray + + if self.fSavedSettings["Main/UseSystemTray"]: + self.systray = systray.GlobalSysTray(self, "Claudia", "claudia") + + self.systray.addAction("studio_new", self.tr("New Studio...")) + self.systray.addSeparator("sep1") + self.systray.addAction("studio_start", self.tr("Start Studio")) + self.systray.addAction("studio_stop", self.tr("Stop Studio")) + self.systray.addSeparator("sep2") + self.systray.addAction("studio_save", self.tr("Save Studio")) + self.systray.addAction("studio_save_as", self.tr("Save Studio As...")) + self.systray.addAction("studio_rename", self.tr("Rename Studio...")) + self.systray.addAction("studio_unload", self.tr("Unload Studio")) + self.systray.addSeparator("sep3") + self.systray.addMenu("tools", self.tr("Tools")) + self.systray.addMenuAction("tools", "tools_configure_jack", self.tr("Configure JACK")) + self.systray.addMenuAction("tools", "tools_render", self.tr("JACK Render")) + self.systray.addMenuAction("tools", "tools_logs", self.tr("Logs")) + self.systray.addMenuSeparator("tools", "tools_sep") + self.systray.addMenuAction("tools", "tools_clear_xruns", self.tr("Clear Xruns")) + self.systray.addAction("configure", self.tr("Configure Claudia")) + + self.systray.setActionIcon("studio_new", "document-new") + self.systray.setActionIcon("studio_start", "media-playback-start") + self.systray.setActionIcon("studio_stop", "media-playback-stop") + self.systray.setActionIcon("studio_save", "document-save") + self.systray.setActionIcon("studio_save_as", "document-save-as") + self.systray.setActionIcon("studio_rename", "edit-rename") + self.systray.setActionIcon("studio_unload", "dialog-close") + self.systray.setActionIcon("tools_configure_jack", "configure") + self.systray.setActionIcon("tools_render", "media-record") + self.systray.setActionIcon("tools_clear_xruns", "edit-clear") + self.systray.setActionIcon("configure", "configure") + + self.systray.connect("studio_new", self.slot_studio_new) + self.systray.connect("studio_start", self.slot_studio_start) + self.systray.connect("studio_stop", self.slot_studio_stop) + self.systray.connect("studio_save", self.slot_studio_save) + self.systray.connect("studio_save_as", self.slot_studio_save_as) + self.systray.connect("studio_rename", self.slot_studio_rename) + self.systray.connect("studio_unload", self.slot_studio_unload) + self.systray.connect("tools_configure_jack", self.slot_showJackSettings) + self.systray.connect("tools_render", self.slot_showRender) + self.systray.connect("tools_logs", self.slot_showLogs) + self.systray.connect("tools_clear_xruns", self.slot_JackClearXruns) + self.systray.connect("configure", self.slot_configureClaudia) + + self.systray.setToolTip("LADISH Frontend") + self.systray.show() + + else: + self.systray = None + + # ------------------------------------------------------------- + # Set-up Canvas + + self.scene = patchcanvas.PatchScene(self, self.ui.graphicsView) + self.ui.graphicsView.setScene(self.scene) + self.ui.graphicsView.setRenderHint(QPainter.Antialiasing, bool(self.fSavedSettings["Canvas/Antialiasing"] == patchcanvas.ANTIALIASING_FULL)) + if self.fSavedSettings["Canvas/UseOpenGL"] and hasGL: + self.ui.graphicsView.setViewport(QGLWidget(self.ui.graphicsView)) + self.ui.graphicsView.setRenderHint(QPainter.HighQualityAntialiasing, self.fSavedSettings["Canvas/HighQualityAntialiasing"]) + + pOptions = patchcanvas.options_t() + pOptions.theme_name = self.fSavedSettings["Canvas/Theme"] + pOptions.auto_hide_groups = self.fSavedSettings["Canvas/AutoHideGroups"] + pOptions.use_bezier_lines = self.fSavedSettings["Canvas/UseBezierLines"] + pOptions.antialiasing = self.fSavedSettings["Canvas/Antialiasing"] + pOptions.eyecandy = self.fSavedSettings["Canvas/EyeCandy"] + + pFeatures = patchcanvas.features_t() + pFeatures.group_info = False + pFeatures.group_rename = True + pFeatures.port_info = True + pFeatures.port_rename = True + pFeatures.handle_group_pos = False + + patchcanvas.setOptions(pOptions) + patchcanvas.setFeatures(pFeatures) + patchcanvas.init("Claudia", self.scene, self.canvasCallback, DEBUG) + + patchcanvas.setCanvasSize(0, 0, DEFAULT_CANVAS_WIDTH, DEFAULT_CANVAS_HEIGHT) + patchcanvas.setInitialPos(DEFAULT_CANVAS_WIDTH / 2, DEFAULT_CANVAS_HEIGHT / 2) + self.ui.graphicsView.setSceneRect(0, 0, DEFAULT_CANVAS_WIDTH, DEFAULT_CANVAS_HEIGHT) + + # ------------------------------------------------------------- + # Set-up Canvas Preview + + self.ui.miniCanvasPreview.setRealParent(self) + self.ui.miniCanvasPreview.setViewTheme(patchcanvas.canvas.theme.canvas_bg, patchcanvas.canvas.theme.rubberband_brush, patchcanvas.canvas.theme.rubberband_pen.color()) + self.ui.miniCanvasPreview.init(self.scene, DEFAULT_CANVAS_WIDTH, DEFAULT_CANVAS_HEIGHT) + QTimer.singleShot(100, self.slot_miniCanvasInit) + + # ------------------------------------------------------------- + # Check DBus + + if gDBus.jack.IsStarted(): + self.jackStarted() + else: + self.jackStopped() + + if gDBus.a2j: + if gDBus.a2j.is_started(): + self.a2jStarted() + else: + self.a2jStopped() + else: + self.ui.act_tools_a2j_start.setEnabled(False) + self.ui.act_tools_a2j_stop.setEnabled(False) + self.ui.act_tools_a2j_export_hw.setEnabled(False) + self.ui.menu_A2J_Bridge.setEnabled(False) + + if gDBus.ladish_control.IsStudioLoaded(): + self.studioLoaded() + if gDBus.ladish_studio.IsStarted(): + self.studioStarted() + self.initPorts() + else: + self.studioStopped() + else: + self.studioUnloaded() + + # ------------------------------------------------------------- + # Set-up Timers + + self.m_timer120 = self.startTimer(self.fSavedSettings["Main/RefreshInterval"]) + self.m_timer600 = self.startTimer(self.fSavedSettings["Main/RefreshInterval"] * 5) + + # ------------------------------------------------------------- + # Set-up Connections + + self.setCanvasConnections() + self.setJackConnections(["jack", "transport", "misc"]) + + self.ui.act_studio_new.triggered.connect(self.slot_studio_new) + self.ui.act_studio_start.triggered.connect(self.slot_studio_start) + self.ui.act_studio_stop.triggered.connect(self.slot_studio_stop) + self.ui.act_studio_save.triggered.connect(self.slot_studio_save) + self.ui.act_studio_save_as.triggered.connect(self.slot_studio_save_as) + self.ui.act_studio_rename.triggered.connect(self.slot_studio_rename) + self.ui.act_studio_unload.triggered.connect(self.slot_studio_unload) + self.ui.act_tools_a2j_start.triggered.connect(self.slot_A2JBridgeStart) + self.ui.act_tools_a2j_stop.triggered.connect(self.slot_A2JBridgeStop) + self.ui.act_tools_a2j_export_hw.triggered.connect(self.slot_A2JBridgeExportHW) + self.ui.b_studio_new.clicked.connect(self.slot_studio_new) + self.ui.b_studio_load.clicked.connect(self.slot_studio_load_b) + self.ui.b_studio_save.clicked.connect(self.slot_studio_save) + self.ui.b_studio_save_as.clicked.connect(self.slot_studio_save_as) + self.ui.b_studio_start.clicked.connect(self.slot_studio_start) + self.ui.b_studio_stop.clicked.connect(self.slot_studio_stop) + self.ui.menu_studio_load.aboutToShow.connect(self.slot_updateMenuStudioList_Load) + self.ui.menu_studio_delete.aboutToShow.connect(self.slot_updateMenuStudioList_Delete) + + self.ui.act_room_create.triggered.connect(self.slot_room_create) + self.ui.menu_room_delete.aboutToShow.connect(self.slot_updateMenuRoomList) + + self.ui.act_project_new.triggered.connect(self.slot_project_new) + self.ui.act_project_save.triggered.connect(self.slot_project_save) + self.ui.act_project_save_as.triggered.connect(self.slot_project_save_as) + self.ui.act_project_unload.triggered.connect(self.slot_project_unload) + self.ui.act_project_properties.triggered.connect(self.slot_project_properties) + self.ui.b_project_new.clicked.connect(self.slot_project_new) + self.ui.b_project_load.clicked.connect(self.slot_project_load) + self.ui.b_project_save.clicked.connect(self.slot_project_save) + self.ui.b_project_save_as.clicked.connect(self.slot_project_save_as) + self.ui.menu_project_load.aboutToShow.connect(self.slot_updateMenuProjectList) + + self.ui.act_app_add_new.triggered.connect(self.slot_app_add_new) + self.ui.act_app_run_custom.triggered.connect(self.slot_app_run_custom) + + self.ui.treeWidget.itemSelectionChanged.connect(self.slot_checkCurrentRoom) + #self.ui.treeWidget.itemPressed.connect(self.slot_checkCurrentRoom) + self.ui.treeWidget.itemDoubleClicked.connect(self.slot_doubleClickedAppList) + self.ui.treeWidget.customContextMenuRequested.connect(self.slot_showAppListCustomMenu) + + self.ui.miniCanvasPreview.miniCanvasMoved.connect(self.slot_miniCanvasMoved) + + self.ui.graphicsView.horizontalScrollBar().valueChanged.connect(self.slot_horizontalScrollBarChanged) + self.ui.graphicsView.verticalScrollBar().valueChanged.connect(self.slot_verticalScrollBarChanged) + + self.scene.sceneGroupMoved.connect(self.slot_canvasItemMoved) + self.scene.scaleChanged.connect(self.slot_canvasScaleChanged) + + self.ui.act_settings_configure.triggered.connect(self.slot_configureClaudia) + + self.ui.act_help_about.triggered.connect(self.slot_aboutClaudia) + self.ui.act_help_about_qt.triggered.connect(app.aboutQt) + + # org.freedesktop.DBus + self.DBusCrashCallback.connect(self.slot_DBusCrashCallback) + + # org.jackaudio.JackControl + self.DBusServerStartedCallback.connect(self.slot_DBusServerStartedCallback) + self.DBusServerStoppedCallback.connect(self.slot_DBusServerStoppedCallback) + + # org.jackaudio.JackPatchbay + self.DBusClientAppearedCallback.connect(self.slot_DBusClientAppearedCallback) + self.DBusClientDisappearedCallback.connect(self.slot_DBusClientDisappearedCallback) + self.DBusClientRenamedCallback.connect(self.slot_DBusClientRenamedCallback) + self.DBusPortAppearedCallback.connect(self.slot_DBusPortAppearedCallback) + self.DBusPortDisppearedCallback.connect(self.slot_DBusPortDisppearedCallback) + self.DBusPortRenamedCallback.connect(self.slot_DBusPortRenamedCallback) + self.DBusPortsConnectedCallback.connect(self.slot_DBusPortsConnectedCallback) + self.DBusPortsDisconnectedCallback.connect(self.slot_DBusPortsDisconnectedCallback) + + # org.ladish.Control + self.DBusStudioAppearedCallback.connect(self.slot_DBusStudioAppearedCallback) + self.DBusStudioDisappearedCallback.connect(self.slot_DBusStudioDisappearedCallback) + self.DBusQueueExecutionHaltedCallback.connect(self.slot_DBusQueueExecutionHaltedCallback) + self.DBusCleanExitCallback.connect(self.slot_DBusCleanExitCallback) + + # org.ladish.Studio + self.DBusStudioStartedCallback.connect(self.slot_DBusStudioStartedCallback) + self.DBusStudioStoppedCallback.connect(self.slot_DBusStudioStoppedCallback) + self.DBusStudioRenamedCallback.connect(self.slot_DBusStudioRenamedCallback) + self.DBusStudioCrashedCallback.connect(self.slot_DBusStudioCrashedCallback) + self.DBusRoomAppearedCallback.connect(self.slot_DBusRoomAppearedCallback) + self.DBusRoomDisappearedCallback.connect(self.slot_DBusRoomDisappearedCallback) + #self.DBusRoomChangedCallback.connect(self.slot_DBusRoomChangedCallback) + + # org.ladish.Room + self.DBusProjectPropertiesChanged.connect(self.slot_DBusProjectPropertiesChanged) + + # org.ladish.AppSupervisor + self.DBusAppAdded2Callback.connect(self.slot_DBusAppAdded2Callback) + self.DBusAppRemovedCallback.connect(self.slot_DBusAppRemovedCallback) + self.DBusAppStateChanged2Callback.connect(self.slot_DBusAppStateChanged2Callback) + + # JACK + self.BufferSizeCallback.connect(self.slot_JackBufferSizeCallback) + self.SampleRateCallback.connect(self.slot_JackSampleRateCallback) + self.ShutdownCallback.connect(self.slot_JackShutdownCallback) + + # ------------------------------------------------------------- + # Set-up DBus + + gDBus.bus.add_signal_receiver(self.DBusSignalReceiver, destination_keyword="dest", path_keyword="path", + member_keyword="member", interface_keyword="interface", sender_keyword="sender") + + # ------------------------------------------------------------- + + def canvasCallback(self, action, value1, value2, value_str): + if action == patchcanvas.ACTION_GROUP_INFO: + pass + + elif action == patchcanvas.ACTION_GROUP_RENAME: + group_id = value1 + group_name = value_str + gDBus.ladish_manager.RenameClient(group_id, group_name) + + elif action == patchcanvas.ACTION_GROUP_SPLIT: + group_id = value1 + gDBus.ladish_graph.Set(GRAPH_DICT_OBJECT_TYPE_CLIENT, group_id, URI_CANVAS_SPLIT, "true") + + patchcanvas.splitGroup(group_id) + self.ui.miniCanvasPreview.update() + + elif action == patchcanvas.ACTION_GROUP_JOIN: + group_id = value1 + gDBus.ladish_graph.Set(GRAPH_DICT_OBJECT_TYPE_CLIENT, group_id, URI_CANVAS_SPLIT, "false") + + patchcanvas.joinGroup(group_id) + self.ui.miniCanvasPreview.update() + + elif action == patchcanvas.ACTION_PORT_INFO: + this_port_id = value1 + breakNow = False + + port_id = 0 + port_flags = 0 + port_name = "" + port_type_jack = 0 + + version, groups, conns = gDBus.patchbay.GetGraph(0) + + for group in groups: + group_id, group_name, ports = group + + for port in ports: + port_id, port_name, port_flags, port_type_jack = port + + if this_port_id == port_id: + breakNow = True + break + + if breakNow: + break + + else: + return + + flags = [] + if port_flags & JACKDBUS_PORT_FLAG_INPUT: + flags.append(self.tr("Input")) + if port_flags & JACKDBUS_PORT_FLAG_OUTPUT: + flags.append(self.tr("Output")) + if port_flags & JACKDBUS_PORT_FLAG_PHYSICAL: + flags.append(self.tr("Physical")) + if port_flags & JACKDBUS_PORT_FLAG_CAN_MONITOR: + flags.append(self.tr("Can Monitor")) + if port_flags & JACKDBUS_PORT_FLAG_TERMINAL: + flags.append(self.tr("Terminal")) + + flags_text = "" + for flag in flags: + if flags_text: + flags_text += " | " + flags_text += flag + + if port_type_jack == JACKDBUS_PORT_TYPE_AUDIO: + type_text = self.tr("Audio") + elif port_type_jack == JACKDBUS_PORT_TYPE_MIDI: + type_text = self.tr("MIDI") + else: + type_text = self.tr("Unknown") + + port_full_name = "%s:%s" % (group_name, port_name) + + info = self.tr("" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "
Group ID: %i
Group Name: %s
Port ID: %i
Port Name: %s
Full Port Name: %s
 
Port Flags: %s
Port Type: %s
" + % (group_id, group_name, port_id, port_name, port_full_name, flags_text, type_text)) + + QMessageBox.information(self, self.tr("Port Information"), info) + + elif action == patchcanvas.ACTION_PORT_RENAME: + port_id = value1 + port_name = value_str + gDBus.ladish_manager.RenamePort(port_id, port_name) + + elif action == patchcanvas.ACTION_PORTS_CONNECT: + port_a = value1 + port_b = value2 + gDBus.patchbay.ConnectPortsByID(port_a, port_b) + + elif action == patchcanvas.ACTION_PORTS_DISCONNECT: + connection_id = value1 + gDBus.patchbay.DisconnectPortsByConnectionID(connection_id) + + def init_jack(self): + self.fXruns = -1 + self.fNextSampleRate = 0.0 + + self.fLastBPM = None + self.fLastTransportState = None + + bufferSize = int(jacklib.get_buffer_size(gJack.client)) + sampleRate = int(jacklib.get_sample_rate(gJack.client)) + realtime = bool(int(jacklib.is_realtime(gJack.client))) + + self.ui_setBufferSize(bufferSize) + self.ui_setSampleRate(sampleRate) + self.ui_setRealTime(realtime) + + self.refreshDSPLoad() + self.refreshTransport() + self.refreshXruns() + + self.init_callbacks() + + jacklib.activate(gJack.client) + + def init_callbacks(self): + jacklib.set_buffer_size_callback(gJack.client, self.JackBufferSizeCallback, None) + jacklib.set_sample_rate_callback(gJack.client, self.JackSampleRateCallback, None) + jacklib.on_shutdown(gJack.client, self.JackShutdownCallback, None) + + def init_studio(self): + self.ui.treeWidget.clear() + + studio_item = QTreeWidgetItem(ITEM_TYPE_STUDIO) + studio_item.setText(0, str(gDBus.ladish_studio.GetName())) + self.ui.treeWidget.insertTopLevelItem(0, studio_item) + self.ui.treeWidget.setCurrentItem(studio_item) + + self.m_lastItemType = ITEM_TYPE_STUDIO + self.m_lastRoomPath = None + + self.init_apps() + + def init_apps(self): + studio_iface = dbus.Interface(gDBus.ladish_studio, 'org.ladish.AppSupervisor') + studio_item = self.ui.treeWidget.topLevelItem(0) + + graph_version, app_list = studio_iface.GetAll2() + + for app in app_list: + number, name, active, terminal, level = app + + prop_obj = [None, None, None, None, None] + prop_obj[iItemPropNumber] = int(number) + prop_obj[iItemPropName] = str(name) + prop_obj[iItemPropActive] = bool(active) + prop_obj[iItemPropTerminal] = bool(terminal) + prop_obj[iItemPropLevel] = str(level) + + text = "[" + if level.isdigit(): + text += "L%s" % level + elif level == "jacksession": + text += "JS" + else: + text += level.upper() + text += "] " + if not active: + text += "(inactive) " + text += name + + item = QTreeWidgetItem(ITEM_TYPE_STUDIO_APP) + item.properties = prop_obj + item.setText(0, text) + studio_item.addChild(item) + + room_list = gDBus.ladish_studio.GetRoomList() + + for room in room_list: + room_path, room_dict = room + ladish_room = gDBus.bus.get_object("org.ladish", room_path) + room_name = ladish_room.GetName() + + room_app_iface = dbus.Interface(ladish_room, 'org.ladish.AppSupervisor') + room_item = self.room_add(room_path, room_name) + + graph_version, app_list = room_app_iface.GetAll2() + + for app in app_list: + number, name, active, terminal, level = app + + prop_obj = [None, None, None, None, None] + prop_obj[iItemPropNumber] = int(number) + prop_obj[iItemPropName] = str(name) + prop_obj[iItemPropActive] = bool(active) + prop_obj[iItemPropTerminal] = bool(terminal) + prop_obj[iItemPropLevel] = str(level) + + text = "[" + if level.isdigit(): + text += "L%s" % level + elif level == "jacksession": + text += "JS" + else: + text += level.upper() + text += "] " + if not active: + text += "(inactive) " + text += name + + item = QTreeWidgetItem(ITEM_TYPE_ROOM_APP) + item.properties = prop_obj + item.setText(0, text) + room_item.addChild(item) + + self.ui.treeWidget.expandAll() + + def initPorts(self): + if not (gJack.client and gDBus.patchbay): + return + + version, groups, conns = gDBus.patchbay.GetGraph(0) + + # Graph Ports + for group in groups: + group_id, group_name, ports = group + self.canvas_add_group(int(group_id), str(group_name)) + + for port in ports: + port_id, port_name, port_flags, port_type_jack = port + + if port_flags & JACKDBUS_PORT_FLAG_INPUT: + port_mode = patchcanvas.PORT_MODE_INPUT + elif port_flags & JACKDBUS_PORT_FLAG_OUTPUT: + port_mode = patchcanvas.PORT_MODE_OUTPUT + else: + port_mode = patchcanvas.PORT_MODE_NULL + + if port_type_jack == JACKDBUS_PORT_TYPE_AUDIO: + port_type = patchcanvas.PORT_TYPE_AUDIO_JACK + elif port_type_jack == JACKDBUS_PORT_TYPE_MIDI: + if gDBus.ladish_graph.Get(GRAPH_DICT_OBJECT_TYPE_PORT, port_id, URI_A2J_PORT) == "yes": + port_type = patchcanvas.PORT_TYPE_MIDI_A2J + else: + port_type = patchcanvas.PORT_TYPE_MIDI_JACK + else: + port_type = patchcanvas.PORT_TYPE_NULL + + self.canvas_add_port(int(group_id), int(port_id), str(port_name), port_mode, port_type) + + # Graph Connections + for conn in conns: + source_group_id, source_group_name, source_port_id, source_port_name, target_group_id, target_group_name, target_port_id, target_port_name, conn_id = conn + self.canvas_connect_ports(int(conn_id), int(source_port_id), int(target_port_id)) + + QTimer.singleShot(1000 if (self.fSavedSettings['Canvas/EyeCandy']) else 0, self.ui.miniCanvasPreview.update) + + def room_add(self, room_path, room_name): + room_index = int(room_path.replace("/org/ladish/Room", "")) + room_object = gDBus.bus.get_object("org.ladish", room_path) + room_project_properties = room_object.GetProjectProperties() + + # Remove old unused item if needed + iItem = self.ui.treeWidget.topLevelItem(room_index) + if iItem and not iItem.isVisible(): + self.ui.treeWidget.takeTopLevelItem(room_index) + + # Insert padding of items if needed + for i in range(room_index): + if not self.ui.treeWidget.topLevelItem(i): + fake_item = QTreeWidgetItem(ITEM_TYPE_NULL) + self.ui.treeWidget.insertTopLevelItem(i, fake_item) + fake_item.setHidden(True) + + graph_version, project_properties = room_project_properties + + if len(project_properties) > 0: + item_string = " (%s)" % project_properties['name'] + else: + item_string = "" + + prop_obj = [None, None] + prop_obj[iItemPropRoomPath] = room_path + prop_obj[iItemPropRoomName] = room_name + + item = QTreeWidgetItem(ITEM_TYPE_ROOM) + item.properties = prop_obj + item.setText(0, "%s%s" % (room_name, item_string)) + + self.ui.treeWidget.insertTopLevelItem(room_index, item) + self.ui.treeWidget.expandItem(item) + + return item + + def canvas_add_group(self, groupId, groupName): + # TODO - request ladish client type + + #if (False): + #icon = patchcanvas.ICON_HARDWARE + #split = patchcanvas.SPLIT_NO + #elif (False): + #icon = patchcanvas.ICON_LADISH_ROOM + #split = patchcanvas.SPLIT_NO + #else: + + splitTry = gDBus.ladish_graph.Get(GRAPH_DICT_OBJECT_TYPE_CLIENT, groupId, URI_CANVAS_SPLIT) + + if splitTry == "true": + groupSplit = patchcanvas.SPLIT_YES + elif splitTry == "false": + groupSplit = patchcanvas.SPLIT_NO + else: + groupSplit = patchcanvas.SPLIT_UNDEF + + groupIcon = patchcanvas.ICON_APPLICATION + + if gJack.client: + ret, data, dataSize = jacklib.custom_get_data(gJack.client, groupName, URI_CANVAS_ICON) + + if ret == 0: + iconName = voidptr2str(data) + jacklib.free(data) + + if iconName == "hardware": + groupIcon = patchcanvas.ICON_HARDWARE + if groupSplit == patchcanvas.SPLIT_UNDEF: + groupSplit = patchcanvas.SPLIT_YES + #elif iconName =="carla": + #groupIcon = patchcanvas.ICON_CARLA + elif iconName =="distrho": + groupIcon = patchcanvas.ICON_DISTRHO + elif iconName =="file": + groupIcon = patchcanvas.ICON_FILE + elif iconName =="plugin": + groupIcon = patchcanvas.ICON_PLUGIN + + patchcanvas.addGroup(groupId, groupName, groupSplit, groupIcon) + + x = gDBus.ladish_graph.Get(GRAPH_DICT_OBJECT_TYPE_CLIENT, groupId, URI_CANVAS_X) + y = gDBus.ladish_graph.Get(GRAPH_DICT_OBJECT_TYPE_CLIENT, groupId, URI_CANVAS_Y) + x2 = gDBus.ladish_graph.Get(GRAPH_DICT_OBJECT_TYPE_CLIENT, groupId, URI_CANVAS_X_SPLIT) + y2 = gDBus.ladish_graph.Get(GRAPH_DICT_OBJECT_TYPE_CLIENT, groupId, URI_CANVAS_Y_SPLIT) + + if x != None and y != None: + if x2 is None: x2 = "%f" % (float(x) + 50) + if y2 is None: y2 = "%f" % (float(y) + 50) + patchcanvas.setGroupPosFull(groupId, float(x), float(y), float(x2), float(y2)) + + QTimer.singleShot(0, self.ui.miniCanvasPreview.update) + + def canvas_remove_group(self, group_id): + patchcanvas.removeGroup(group_id) + QTimer.singleShot(0, self.ui.miniCanvasPreview.update) + + def canvas_rename_group(self, group_id, new_group_name): + patchcanvas.renameGroup(group_id, new_group_name) + QTimer.singleShot(0, self.ui.miniCanvasPreview.update) + + def canvas_add_port(self, group_id, port_id, port_name, port_mode, port_type): + patchcanvas.addPort(group_id, port_id, port_name, port_mode, port_type) + QTimer.singleShot(0, self.ui.miniCanvasPreview.update) + + def canvas_remove_port(self, port_id): + patchcanvas.removePort(port_id) + QTimer.singleShot(0, self.ui.miniCanvasPreview.update) + + def canvas_rename_port(self, port_id, new_port_name): + patchcanvas.renamePort(port_id, new_port_name) + QTimer.singleShot(0, self.ui.miniCanvasPreview.update) + + def canvas_connect_ports(self, connection_id, port_a, port_b): + patchcanvas.connectPorts(connection_id, port_a, port_b) + QTimer.singleShot(0, self.ui.miniCanvasPreview.update) + + def canvas_disconnect_ports(self, connection_id): + patchcanvas.disconnectPorts(connection_id) + QTimer.singleShot(0, self.ui.miniCanvasPreview.update) + + def jackStarted(self): + if jacksettings.needsInit(): + self.DBusReconnect() + + if not gJack.client: + gJack.client = jacklib.client_open("claudia", jacklib.JackNoStartServer, None) + if not gJack.client: + return self.jackStopped() + + canRender = render.canRender() + + self.ui.act_jack_render.setEnabled(canRender) + self.ui.b_jack_render.setEnabled(canRender) + self.menuJackTransport(True) + self.menuA2JBridge(False) + + self.ui.cb_buffer_size.setEnabled(True) + self.ui.cb_sample_rate.setEnabled(True) # jacksettings.getSampleRate() != -1 + + if self.systray: + self.systray.setActionEnabled("tools_render", canRender) + + self.ui.pb_dsp_load.setMaximum(100) + self.ui.pb_dsp_load.setValue(0) + self.ui.pb_dsp_load.update() + + self.init_jack() + + self.m_crashedJACK = False + + def jackStopped(self): + #self.DBusReconnect() + + # client already closed + gJack.client = None + + if self.fNextSampleRate: + self.jack_setSampleRate(self.fNextSampleRate) + + bufferSize = jacksettings.getBufferSize() + sampleRate = jacksettings.getSampleRate() + bufferSizeTest = bool(bufferSize != -1) + sampleRateTest = bool(sampleRate != -1) + + if bufferSizeTest: + self.ui_setBufferSize(bufferSize) + + if sampleRateTest: + self.ui_setSampleRate(sampleRate) + + self.ui_setRealTime(jacksettings.isRealtime()) + self.ui_setXruns(-1) + + self.ui.cb_buffer_size.setEnabled(bufferSizeTest) + self.ui.cb_sample_rate.setEnabled(sampleRateTest) + + self.ui.act_jack_render.setEnabled(False) + self.ui.b_jack_render.setEnabled(False) + self.menuJackTransport(False) + self.menuA2JBridge(False) + + if self.systray: + self.systray.setActionEnabled("tools_render", False) + + if self.fCurTransportView == TRANSPORT_VIEW_HMS: + self.ui.label_time.setText("00:00:00") + elif self.fCurTransportView == TRANSPORT_VIEW_BBT: + self.ui.label_time.setText("000|0|0000") + elif self.fCurTransportView == TRANSPORT_VIEW_FRAMES: + self.ui.label_time.setText("000'000'000") + + self.ui.pb_dsp_load.setValue(0) + self.ui.pb_dsp_load.setMaximum(0) + self.ui.pb_dsp_load.update() + + def studioStarted(self): + self.ui.act_studio_start.setEnabled(False) + self.ui.act_studio_stop.setEnabled(True) + self.ui.act_studio_save.setEnabled(True) + self.ui.act_studio_save_as.setEnabled(True) + + self.ui.b_studio_save.setEnabled(True) + self.ui.b_studio_save_as.setEnabled(True) + self.ui.b_studio_start.setEnabled(False) + self.ui.b_studio_stop.setEnabled(True) + + if self.systray: + self.systray.setActionEnabled("studio_start", False) + self.systray.setActionEnabled("studio_stop", True) + self.systray.setActionEnabled("studio_save", True) + self.systray.setActionEnabled("studio_save_as", True) + + def studioStopped(self): + self.ui.act_studio_start.setEnabled(True) + self.ui.act_studio_stop.setEnabled(False) + self.ui.act_studio_save.setEnabled(False) + self.ui.act_studio_save_as.setEnabled(False) + + self.ui.b_studio_save.setEnabled(False) + self.ui.b_studio_save_as.setEnabled(False) + self.ui.b_studio_start.setEnabled(True) + self.ui.b_studio_stop.setEnabled(False) + + if self.systray: + self.systray.setActionEnabled("studio_start", True) + self.systray.setActionEnabled("studio_stop", False) + self.systray.setActionEnabled("studio_save", False) + self.systray.setActionEnabled("studio_save_as", False) + + def studioLoaded(self): + gDBus.ladish_studio = gDBus.bus.get_object("org.ladish", "/org/ladish/Studio") + gDBus.ladish_graph = dbus.Interface(gDBus.ladish_studio, 'org.ladish.GraphDict') + gDBus.ladish_manager = dbus.Interface(gDBus.ladish_studio, 'org.ladish.GraphManager') + gDBus.ladish_app_iface = dbus.Interface(gDBus.ladish_studio, 'org.ladish.AppSupervisor') + gDBus.patchbay = dbus.Interface(gDBus.ladish_studio, 'org.jackaudio.JackPatchbay') + + self.ui.label_first_time.setVisible(False) + self.ui.graphicsView.setVisible(True) + self.ui.miniCanvasPreview.setVisible(True) + #if (self.ui.miniCanvasPreview.is_initiated): + #self.checkMiniCanvasSize() + + self.ui.menu_Room.setEnabled(True) + self.ui.menu_Project.setEnabled(False) + self.ui.menu_Application.setEnabled(True) + self.ui.group_project.setEnabled(False) + + self.ui.act_studio_rename.setEnabled(True) + self.ui.act_studio_unload.setEnabled(True) + + if self.systray: + self.systray.setActionEnabled("studio_rename", True) + self.systray.setActionEnabled("studio_unload", True) + + self.init_studio() + + self.m_crashedLADISH = False + + def studioUnloaded(self): + gDBus.ladish_studio = None + gDBus.ladish_graph = None + gDBus.ladish_manager = None + gDBus.ladish_app_iface = None + gDBus.patchbay = None + + self.m_lastItemType = None + self.m_lastRoomPath = None + + self.ui.label_first_time.setVisible(True) + self.ui.graphicsView.setVisible(False) + self.ui.miniCanvasPreview.setVisible(False) + + self.ui.menu_Room.setEnabled(False) + self.ui.menu_Project.setEnabled(False) + self.ui.menu_Application.setEnabled(False) + self.ui.group_project.setEnabled(False) + + self.ui.act_studio_start.setEnabled(False) + self.ui.act_studio_stop.setEnabled(False) + self.ui.act_studio_rename.setEnabled(False) + self.ui.act_studio_save.setEnabled(False) + self.ui.act_studio_save_as.setEnabled(False) + self.ui.act_studio_unload.setEnabled(False) + + self.ui.b_studio_save.setEnabled(False) + self.ui.b_studio_save_as.setEnabled(False) + self.ui.b_studio_start.setEnabled(False) + self.ui.b_studio_stop.setEnabled(False) + + if self.systray: + self.systray.setActionEnabled("studio_start", False) + self.systray.setActionEnabled("studio_stop", False) + self.systray.setActionEnabled("studio_rename", False) + self.systray.setActionEnabled("studio_save", False) + self.systray.setActionEnabled("studio_save_as", False) + self.systray.setActionEnabled("studio_unload", False) + + self.ui.treeWidget.clear() + + patchcanvas.clear() + + def a2jStarted(self): + self.menuA2JBridge(True) + + def a2jStopped(self): + self.menuA2JBridge(False) + + def menuJackTransport(self, enabled): + self.ui.act_transport_play.setEnabled(enabled) + self.ui.act_transport_stop.setEnabled(enabled) + self.ui.act_transport_backwards.setEnabled(enabled) + self.ui.act_transport_forwards.setEnabled(enabled) + self.ui.menu_Transport.setEnabled(enabled) + self.ui.group_transport.setEnabled(enabled) + + def menuA2JBridge(self, started): + if not gDBus.jack.IsStarted(): + self.ui.act_tools_a2j_start.setEnabled(False) + self.ui.act_tools_a2j_stop.setEnabled(False) + self.ui.act_tools_a2j_export_hw.setEnabled(bool(gDBus.a2j) and not gDBus.a2j.is_started()) + else: + self.ui.act_tools_a2j_start.setEnabled(not started) + self.ui.act_tools_a2j_stop.setEnabled(started) + self.ui.act_tools_a2j_export_hw.setEnabled(not started) + + def DBusSignalReceiver(self, *args, **kwds): + if kwds['interface'] == "org.freedesktop.DBus" and kwds['path'] == "/org/freedesktop/DBus" and kwds['member'] == "NameOwnerChanged": + appInterface, appId, newId = args + #print("appInterface crashed", appInterface) + + if not newId: + # Something crashed + if appInterface == "org.gna.home.a2jmidid": + QTimer.singleShot(0, self.slot_handleCrash_a2j) + elif appInterface in ("org.jackaudio.service", "org.ladish"): + # Prevent any more dbus calls + gDBus.jack = None + gJack.client = None + jacksettings.initBus(None) + self.DBusCrashCallback.emit(appInterface) + + elif kwds['interface'] == "org.jackaudio.JackControl": + if DEBUG: print("DBus signal @org.jackaudio.JackControl,", kwds['member']) + if kwds['member'] == "ServerStarted": + self.DBusServerStartedCallback.emit() + elif kwds['member'] == "ServerStopped": + self.DBusServerStoppedCallback.emit() + + elif kwds['interface'] == "org.jackaudio.JackPatchbay": + if gDBus.patchbay and kwds['path'] == gDBus.patchbay.object_path: + if DEBUG: print("DBus signal @org.jackaudio.JackPatchbay,", kwds['member']) + if kwds['member'] == "ClientAppeared": + self.DBusClientAppearedCallback.emit(args[iJackClientId], args[iJackClientName]) + elif kwds['member'] == "ClientDisappeared": + self.DBusClientDisappearedCallback.emit(args[iJackClientId]) + elif kwds['member'] == "ClientRenamed": + self.DBusClientRenamedCallback.emit(args[iRenamedId], args[iRenamedNewName]) + elif kwds['member'] == "PortAppeared": + self.DBusPortAppearedCallback.emit(args[iJackClientId], args[iJackPortId], args[iJackPortName], args[iJackPortFlags], args[iJackPortType]) + elif kwds['member'] == "PortDisappeared": + self.DBusPortDisppearedCallback.emit(args[iJackPortId]) + elif kwds['member'] == "PortRenamed": + self.DBusPortRenamedCallback.emit(args[iJackPortId], args[iJackPortNewName]) + elif kwds['member'] == "PortsConnected": + self.DBusPortsConnectedCallback.emit(args[iJackConnId], args[iSourcePortId], args[iTargetPortId]) + elif kwds['member'] == "PortsDisconnected": + self.DBusPortsDisconnectedCallback.emit(args[iJackConnId]) + + elif kwds['interface'] == "org.ladish.Control": + if DEBUG: print("DBus signal @org.ladish.Control,", kwds['member']) + if kwds['member'] == "StudioAppeared": + self.DBusStudioAppearedCallback.emit() + elif kwds['member'] == "StudioDisappeared": + self.DBusStudioDisappearedCallback.emit() + elif kwds['member'] == "QueueExecutionHalted": + self.DBusQueueExecutionHaltedCallback.emit() + elif kwds['member'] == "CleanExit": + self.DBusCleanExitCallback.emit() + + elif kwds['interface'] == "org.ladish.Studio": + if DEBUG: print("DBus signal @org.ladish.Studio,", kwds['member']) + if kwds['member'] == "StudioStarted": + self.DBusStudioStartedCallback.emit() + elif kwds['member'] == "StudioStopped": + self.DBusStudioStoppedCallback.emit() + elif kwds['member'] == "StudioRenamed": + self.DBusStudioRenamedCallback.emit(args[iStudioRenamedName]) + elif kwds['member'] == "StudioCrashed": + self.DBusStudioCrashedCallback.emit() + elif kwds['member'] == "RoomAppeared": + self.DBusRoomAppearedCallback.emit(args[iRoomAppearedPath], args[iRoomAppearedDict]['name']) + elif kwds['member'] == "RoomDisappeared": + self.DBusRoomDisappearedCallback.emit(args[iRoomAppearedPath]) + #elif kwds['member'] == "RoomChanged": + #self.DBusRoomChangedCallback.emit() + + elif kwds['interface'] == "org.ladish.Room": + if DEBUG: print("DBus signal @org.ladish.Room,", kwds['member']) + if kwds['member'] == "ProjectPropertiesChanged": + if "name" in args[iProjChangedDict].keys(): + self.DBusProjectPropertiesChanged.emit(kwds['path'], args[iProjChangedDict]['name']) + else: + self.DBusProjectPropertiesChanged.emit(kwds['path'], "") + + elif kwds['interface'] == "org.ladish.AppSupervisor": + if DEBUG: print("DBus signal @org.ladish.AppSupervisor,", kwds['member']) + if kwds['member'] == "AppAdded2": + self.DBusAppAdded2Callback.emit(kwds['path'], args[iAppChangedNumber], args[iAppChangedName], args[iAppChangedActive], args[iAppChangedTerminal], args[iAppChangedLevel]) + elif kwds['member'] == "AppRemoved": + self.DBusAppRemovedCallback.emit(kwds['path'], args[iAppChangedNumber]) + elif kwds['member'] == "AppStateChanged2": + self.DBusAppStateChanged2Callback.emit(kwds['path'], args[iAppChangedNumber], args[iAppChangedName], args[iAppChangedActive], args[iAppChangedTerminal], args[iAppChangedLevel]) + + elif kwds['interface'] == "org.gna.home.a2jmidid.control": + if kwds['member'] == "bridge_started": + self.a2jStarted() + elif kwds['member'] == "bridge_stopped": + self.a2jStopped() + + def DBusReconnect(self): + gDBus.jack = gDBus.bus.get_object("org.jackaudio.service", "/org/jackaudio/Controller") + gDBus.ladish_control = gDBus.bus.get_object("org.ladish", "/org/ladish/Control") + gDBus.ladish_studio = None + gDBus.ladish_room = None + gDBus.ladish_graph = None + gDBus.ladish_manager = None + gDBus.ladish_app_iface = None + gDBus.patchbay = None + + try: + gDBus.ladish_app_daemon = gDBus.bus.get_object("org.ladish.appdb", "/") + except: + gDBus.ladish_app_daemon = None + + try: + gDBus.a2j = dbus.Interface(gDBus.bus.get_object("org.gna.home.a2jmidid", "/"), "org.gna.home.a2jmidid.control") + except: + gDBus.a2j = None + + jacksettings.initBus(gDBus.bus) + + def refreshXruns(self): + if not gDBus.jack: + #if not self.m_crashedJACK: + #self.DBusReconnect() + return + + xruns = int(gDBus.jack.GetXruns()) + if self.fXruns != xruns: + self.ui_setXruns(xruns) + self.fXruns = xruns + + def JackBufferSizeCallback(self, buffer_size, arg): + if DEBUG: print("JackBufferSizeCallback(%i)" % buffer_size) + self.BufferSizeCallback.emit(buffer_size) + return 0 + + def JackSampleRateCallback(self, sample_rate, arg): + if DEBUG: print("JackSampleRateCallback(%i)" % sample_rate) + self.SampleRateCallback.emit(sample_rate) + return 0 + + def JackShutdownCallback(self, arg): + if DEBUG: print("JackShutdownCallback") + self.ShutdownCallback.emit() + return 0 + + @pyqtSlot() + def slot_studio_new(self): + dialog = StudioNameW(self, StudioNameW.NEW) + if dialog.exec_(): + gDBus.ladish_control.NewStudio(dialog.fRetStudioName) + + @pyqtSlot() + def slot_studio_load_b(self): + dialog = StudioListW(self) + if dialog.exec_(): + gDBus.ladish_control.LoadStudio(dialog.ret_studio_name) + + @pyqtSlot() + def slot_studio_load_m(self): + studio_name = self.sender().property("data") + if studio_name: + gDBus.ladish_control.LoadStudio(studio_name) + + @pyqtSlot() + def slot_studio_start(self): + gDBus.ladish_studio.Start() + + @pyqtSlot() + def slot_studio_stop(self): + gDBus.ladish_studio.Stop() + + @pyqtSlot() + def slot_studio_rename(self): + dialog = StudioNameW(self, StudioNameW.RENAME) + if dialog.exec_(): + gDBus.ladish_studio.Rename(dialog.fRetStudioName) + + @pyqtSlot() + def slot_studio_save(self): + gDBus.ladish_studio.Save() + + @pyqtSlot() + def slot_studio_save_as(self): + dialog = StudioNameW(self, StudioNameW.SAVE_AS) + if dialog.exec_(): + gDBus.ladish_studio.SaveAs(dialog.fRetStudioName) + + @pyqtSlot() + def slot_studio_unload(self): + gDBus.ladish_studio.Unload() + + @pyqtSlot() + def slot_studio_delete_m(self): + studio_name = self.sender().property("data") + if studio_name: + gDBus.ladish_control.DeleteStudio(studio_name) + + @pyqtSlot() + def slot_room_create(self): + dialog = CreateRoomW(self) + if dialog.exec_(): + gDBus.ladish_studio.CreateRoom(dialog.ret_room_name, dialog.ret_room_template) + + @pyqtSlot() + def slot_room_delete_m(self): + room_name = self.sender().property("data") + if room_name: + gDBus.ladish_studio.DeleteRoom(room_name) + + @pyqtSlot() + def slot_project_new(self): + dialog = ProjectNameW(self, ProjectNameW.NEW, self.fSavedSettings["Main/DefaultProjectFolder"]) + if dialog.exec_(): + # Check if a project is already loaded, if yes unload it first + project_graph_version, project_properties = gDBus.ladish_room.GetProjectProperties() + if len(project_properties) > 0: + gDBus.ladish_room.UnloadProject() + gDBus.ladish_room.SaveProject(dialog.ret_project_path, dialog.ret_project_name) + + @pyqtSlot() + def slot_project_load(self): + project_path = QFileDialog.getExistingDirectory(self, self.tr("Open Project"), self.fSavedSettings["Main/DefaultProjectFolder"]) + if project_path: + if os.path.exists(os.path.join(project_path, "ladish-project.xml")): + gDBus.ladish_room.LoadProject(project_path) + else: + QMessageBox.warning(self, self.tr("Warning"), self.tr("The selected folder does not contain a ladish project")) + + @pyqtSlot() + def slot_project_load_m(self): + act_x_text = self.sender().property("data") + if act_x_text: + proj_path = "/" + act_x_text.rsplit("[/", 1)[-1].rsplit("]", 1)[0] + gDBus.ladish_room.LoadProject(proj_path) + + @pyqtSlot() + def slot_project_save(self): + project_graph_version, project_properties = gDBus.ladish_room.GetProjectProperties() + if len(project_properties) > 0: + path = dbus.String(project_properties['dir']) + name = dbus.String(project_properties['name']) + gDBus.ladish_room.SaveProject(path, name) + else: + self.slot_project_new() + + @pyqtSlot() + def slot_project_save_as(self): + project_graph_version, project_properties = gDBus.ladish_room.GetProjectProperties() + + if len(project_properties) > 0: + path = str(project_properties['dir']) + name = str(project_properties['name']) + dialog = ProjectNameW(self, ProjectNameW.SAVE_AS, self.fSavedSettings["Main/DefaultProjectFolder"], path, name) + + if dialog.exec_(): + gDBus.ladish_room.SaveProject(dialog.ret_project_path, dialog.ret_project_name) + + else: + self.slot_project_new() + + @pyqtSlot() + def slot_project_unload(self): + gDBus.ladish_room.UnloadProject() + + @pyqtSlot() + def slot_project_properties(self): + project_graph_version, project_properties = gDBus.ladish_room.GetProjectProperties() + + path = str(project_properties['dir']) + name = str(project_properties['name']) + + if "description" in project_properties.keys(): + description = str(project_properties['description']) + else: + description = "" + + if "notes" in project_properties.keys(): + notes = str(project_properties['notes']) + else: + notes = "" + + dialog = ProjectPropertiesW(self, name, description, notes) + + if dialog.exec_(): + gDBus.ladish_room.SetProjectDescription(dialog.ret_obj[iAppPropDescription]) + gDBus.ladish_room.SetProjectNotes(dialog.ret_obj[iAppPropNotes]) + + if dialog.ret_obj[iAppPropSaveNow]: + gDBus.ladish_room.SaveProject(path, dialog.ret_obj[iAppPropName]) + + @pyqtSlot() + def slot_app_add_new(self): + proj_folder = "" + + if self.m_lastItemType == ITEM_TYPE_STUDIO or self.m_lastItemType == ITEM_TYPE_STUDIO_APP: + proj_folder = self.fSavedSettings['Main/DefaultProjectFolder'] + is_room = False + + elif self.m_lastItemType == ITEM_TYPE_ROOM or self.m_lastItemType == ITEM_TYPE_ROOM_APP: + project_graph_version, project_properties = gDBus.ladish_room.GetProjectProperties() + + if len(project_properties) > 0: + proj_folder = str(project_properties['dir']) + is_room = True + else: + proj_folder = self.fSavedSettings['Main/DefaultProjectFolder'] + is_room = False + + else: + print("Invalid m_last_item_type value") + return + + dialog = ClaudiaLauncherW(self, gDBus.ladish_app_iface, proj_folder, is_room, self.fLastBPM, self.fSampleRate) + dialog.exec_() + + @pyqtSlot() + def slot_app_run_custom(self): + dialog = RunCustomW(self, bool(self.m_lastItemType in (ITEM_TYPE_ROOM, ITEM_TYPE_ROOM_APP))) + if dialog.exec_() and dialog.ret_app_obj: + app_obj = dialog.ret_app_obj + gDBus.ladish_app_iface.RunCustom2(app_obj[iAppTerminal], app_obj[iAppCommand], app_obj[iAppName], app_obj[iAppLevel]) + + @pyqtSlot() + def slot_checkCurrentRoom(self): + item = self.ui.treeWidget.currentItem() + room_path = None + + if not item: + return + + if item.type() in (ITEM_TYPE_STUDIO, ITEM_TYPE_STUDIO_APP): + self.ui.menu_Project.setEnabled(False) + self.ui.group_project.setEnabled(False) + self.ui.menu_Application.setEnabled(True) + + gDBus.ladish_room = None + gDBus.ladish_app_iface = dbus.Interface(gDBus.ladish_studio, "org.ladish.AppSupervisor") + ITEM_TYPE = ITEM_TYPE_STUDIO + + elif item.type() in (ITEM_TYPE_ROOM, ITEM_TYPE_ROOM_APP): + self.ui.menu_Project.setEnabled(True) + self.ui.group_project.setEnabled(True) + + if item.type() == ITEM_TYPE_ROOM: + room_path = item.properties[iItemPropRoomPath] + elif item.type() == ITEM_TYPE_ROOM_APP: + room_path = item.parent().properties[iItemPropRoomPath] + else: + return + + gDBus.ladish_room = gDBus.bus.get_object("org.ladish", room_path) + gDBus.ladish_app_iface = dbus.Interface(gDBus.ladish_room, "org.ladish.AppSupervisor") + ITEM_TYPE = ITEM_TYPE_ROOM + + project_graph_version, project_properties = gDBus.ladish_room.GetProjectProperties() + + has_project = bool(len(project_properties) > 0) + self.ui.act_project_save.setEnabled(has_project) + self.ui.act_project_save_as.setEnabled(has_project) + self.ui.act_project_unload.setEnabled(has_project) + self.ui.act_project_properties.setEnabled(has_project) + self.ui.b_project_save.setEnabled(has_project) + self.ui.b_project_save_as.setEnabled(has_project) + self.ui.menu_Application.setEnabled(has_project) + + else: + return + + if ITEM_TYPE != self.m_lastItemType or room_path != self.m_lastRoomPath: + if ITEM_TYPE == ITEM_TYPE_STUDIO: + object_path = gDBus.ladish_studio + elif ITEM_TYPE == ITEM_TYPE_ROOM: + object_path = gDBus.ladish_room + else: + return + + patchcanvas.clear() + gDBus.patchbay = dbus.Interface(object_path, 'org.jackaudio.JackPatchbay') + gDBus.ladish_graph = dbus.Interface(object_path, 'org.ladish.GraphDict') + gDBus.ladish_manager = dbus.Interface(object_path, 'org.ladish.GraphManager') + self.initPorts() + + self.m_lastItemType = ITEM_TYPE + self.m_lastRoomPath = room_path + + @pyqtSlot(QTreeWidgetItem, int) + def slot_doubleClickedAppList(self, item, row): + if item.type() in (ITEM_TYPE_STUDIO_APP, ITEM_TYPE_ROOM_APP): + if item.properties[iItemPropActive]: + gDBus.ladish_app_iface.StopApp(item.properties[iItemPropNumber]) + else: + gDBus.ladish_app_iface.StartApp(item.properties[iItemPropNumber]) + + @pyqtSlot() + def slot_updateMenuStudioList_Load(self): + self.ui.menu_studio_load.clear() + + studio_list = gDBus.ladish_control.GetStudioList() + if len(studio_list) == 0: + act_no_studio = QAction(self.tr("Empty studio list"), self.ui.menu_studio_load) + act_no_studio.setEnabled(False) + self.ui.menu_studio_load.addAction(act_no_studio) + else: + for studio in studio_list: + studio_name = str(studio[iStudioListName]) + act_x_studio = QAction(studio_name, self.ui.menu_studio_load) + act_x_studio.setProperty("data", studio_name); + self.ui.menu_studio_load.addAction(act_x_studio) + act_x_studio.triggered.connect(self.slot_studio_load_m) + + @pyqtSlot() + def slot_updateMenuStudioList_Delete(self): + self.ui.menu_studio_delete.clear() + + studio_list = gDBus.ladish_control.GetStudioList() + if len(studio_list) == 0: + act_no_studio = QAction(self.tr("Empty studio list"), self.ui.menu_studio_delete) + act_no_studio.setEnabled(False) + self.ui.menu_studio_delete.addAction(act_no_studio) + else: + for studio in studio_list: + studio_name = str(studio[iStudioListName]) + act_x_studio = QAction(studio_name, self.ui.menu_studio_delete) + act_x_studio.setProperty("data", studio_name); + self.ui.menu_studio_delete.addAction(act_x_studio) + act_x_studio.triggered.connect(self.slot_studio_delete_m) + + @pyqtSlot() + def slot_updateMenuRoomList(self): + self.ui.menu_room_delete.clear() + if gDBus.ladish_control.IsStudioLoaded(): + room_list = gDBus.ladish_studio.GetRoomList() + if len(room_list) == 0: + self.createEmptyMenuRoomActon() + else: + for room_path, room_dict in room_list: + ladish_room = gDBus.bus.get_object("org.ladish", room_path) + room_name = ladish_room.GetName() + act_x_room = QAction(room_name, self.ui.menu_room_delete) + act_x_room.setProperty("data", room_name); + self.ui.menu_room_delete.addAction(act_x_room) + act_x_room.triggered.connect(self.slot_room_delete_m) + else: + self.createEmptyMenuRoomActon() + + def createEmptyMenuRoomActon(self): + act_no_room = QAction(self.tr("Empty room list"), self.ui.menu_room_delete) + act_no_room.setEnabled(False) + self.ui.menu_room_delete.addAction(act_no_room) + + @pyqtSlot() + def slot_updateMenuProjectList(self): + self.ui.menu_project_load.clear() + act_project_load = QAction(self.tr("Load from folder..."), self.ui.menu_project_load) + self.ui.menu_project_load.addAction(act_project_load) + act_project_load.triggered.connect(self.slot_project_load) + + ladish_recent_iface = dbus.Interface(gDBus.ladish_room, "org.ladish.RecentItems") + proj_list = ladish_recent_iface.get(RECENT_PROJECTS_STORE_MAX_ITEMS) + + if len(proj_list) > 0: + self.ui.menu_project_load.addSeparator() + for proj_path, proj_dict in proj_list: + if "name" in proj_dict.keys(): + proj_name = proj_dict['name'] + else: + continue + + act_x_text = "%s [%s]" % (proj_name, proj_path) + act_x_proj = QAction(act_x_text, self.ui.menu_project_load) + act_x_proj.setProperty("data", act_x_text); + self.ui.menu_project_load.addAction(act_x_proj) + act_x_proj.triggered.connect(self.slot_project_load_m) + + @pyqtSlot() + def slot_showAppListCustomMenu(self): + item = self.ui.treeWidget.currentItem() + if item: + cMenu = QMenu() + if item.type() == ITEM_TYPE_STUDIO: + act_x_add_new = cMenu.addAction(self.tr("Add New...")) + act_x_run_custom = cMenu.addAction(self.tr("Run Custom...")) + cMenu.addSeparator() + act_x_create_room = cMenu.addAction(self.tr("Create Room...")) + + act_x_add_new.setIcon(QIcon.fromTheme("list-add", QIcon(":/16x16/list-add.png"))) + act_x_run_custom.setIcon(QIcon.fromTheme("system-run", QIcon(":/16x16/system-run.png"))) + act_x_create_room.setIcon(QIcon.fromTheme("list-add", QIcon(":/16x16/list-add.png"))) + act_x_add_new.setEnabled(self.ui.act_app_add_new.isEnabled()) + + elif item.type() == ITEM_TYPE_ROOM: + act_x_add_new = cMenu.addAction(self.tr("Add New...")) + act_x_run_custom = cMenu.addAction(self.tr("Run Custom...")) + cMenu.addSeparator() + act_x_new = cMenu.addAction(self.tr("New Project...")) + cMenu.addMenu(self.ui.menu_project_load) + act_x_save = cMenu.addAction(self.tr("Save Project")) + act_x_save_as = cMenu.addAction(self.tr("Save Project As...")) + act_x_unload = cMenu.addAction(self.tr("Unload Project")) + cMenu.addSeparator() + act_x_properties = cMenu.addAction(self.tr("Project Properties...")) + cMenu.addSeparator() + act_x_delete_room = cMenu.addAction(self.tr("Delete Room")) + + act_x_add_new.setIcon(QIcon.fromTheme("list-add", QIcon(":/16x16/list-add.png"))) + act_x_run_custom.setIcon(QIcon.fromTheme("system-run", QIcon(":/16x16/system-run.png"))) + act_x_new.setIcon(QIcon.fromTheme("document-new", QIcon(":/16x16/document-new.png"))) + act_x_save.setIcon(QIcon.fromTheme("document-save", QIcon(":/16x16/document-save.png"))) + act_x_save_as.setIcon(QIcon.fromTheme("document-save-as", QIcon(":/16x16/document-save-as.png"))) + act_x_unload.setIcon(QIcon.fromTheme("window-close", QIcon(":/16x16/dialog-close.png"))) + act_x_properties.setIcon(QIcon.fromTheme("edit-rename", QIcon(":/16x16/edit-rename.png"))) + act_x_delete_room.setIcon(QIcon.fromTheme("edit-delete", QIcon(":/16x16/edit-delete.png"))) + + act_x_add_new.setEnabled(self.ui.menu_Application.isEnabled() and self.ui.act_app_add_new.isEnabled()) + + project_graph_version, project_properties = gDBus.ladish_room.GetProjectProperties() + + if len(project_properties) == 0: + act_x_run_custom.setEnabled(False) + act_x_save.setEnabled(False) + act_x_save_as.setEnabled(False) + act_x_unload.setEnabled(False) + act_x_properties.setEnabled(False) + + elif item.type() in (ITEM_TYPE_STUDIO_APP, ITEM_TYPE_ROOM_APP): + if item.properties[iItemPropActive]: + act_x_start = None + act_x_stop = cMenu.addAction(self.tr("Stop")) + act_x_kill = cMenu.addAction(self.tr("Kill")) + act_x_stop.setIcon(QIcon.fromTheme("media-playback-stop", QIcon(":/16x16/media-playback-stop.png"))) + act_x_kill.setIcon(QIcon.fromTheme("dialog-close", QIcon(":/16x16/dialog-close.png"))) + else: + act_x_start = cMenu.addAction(self.tr("Start")) + act_x_stop = None + act_x_kill = None + act_x_start.setIcon(QIcon.fromTheme("media-playback-start", QIcon(":/16x16/media-playback-start.png"))) + act_x_properties = cMenu.addAction(self.tr("Properties")) + cMenu.addSeparator() + act_x_remove = cMenu.addAction(self.tr("Remove")) + act_x_properties.setIcon(QIcon.fromTheme("edit-rename", QIcon(":/16x16/edit-rename.png"))) + act_x_remove.setIcon(QIcon.fromTheme("edit-delete", QIcon(":/16x16/edit-delete.png"))) + + else: + return + + act_x_sel = cMenu.exec_(QCursor.pos()) + + if act_x_sel: + if item.type() == ITEM_TYPE_STUDIO: + if act_x_sel == act_x_add_new: + self.slot_app_add_new() + elif act_x_sel == act_x_run_custom: + self.slot_app_run_custom() + elif act_x_sel == act_x_create_room: + self.slot_room_create() + + elif item.type() == ITEM_TYPE_ROOM: + if act_x_sel == act_x_add_new: + self.slot_app_add_new() + elif act_x_sel == act_x_run_custom: + self.slot_app_run_custom() + elif act_x_sel == act_x_new: + self.slot_project_new() + elif act_x_sel == act_x_save: + self.slot_project_save() + elif act_x_sel == act_x_save_as: + self.slot_project_save_as() + elif act_x_sel == act_x_unload: + self.slot_project_unload() + elif act_x_sel == act_x_properties: + self.slot_project_properties() + elif act_x_sel == act_x_delete_room: + room_name = gDBus.ladish_room.GetName() + gDBus.ladish_studio.DeleteRoom(room_name) + + elif item.type() in (ITEM_TYPE_STUDIO_APP, ITEM_TYPE_ROOM_APP): + number = item.properties[iItemPropNumber] + + if act_x_sel == act_x_start: + gDBus.ladish_app_iface.StartApp(number) + elif act_x_sel == act_x_stop: + gDBus.ladish_app_iface.StopApp(number) + elif act_x_sel == act_x_kill: + gDBus.ladish_app_iface.KillApp(number) + elif act_x_sel == act_x_properties: + name, command, active, terminal, level = gDBus.ladish_app_iface.GetAppProperties2(number) + + app_obj = [None, None, None, None, None] + app_obj[iAppCommand] = str(command) + app_obj[iAppName] = str(name) + app_obj[iAppTerminal] = bool(terminal) + app_obj[iAppLevel] = str(level) + app_obj[iAppActive] = bool(active) + + dialog = RunCustomW(self, bool(item.type() == ITEM_TYPE_ROOM_APP), app_obj) + dialog.setWindowTitle(self.tr("App properties")) + if dialog.exec_(): + app_obj = dialog.ret_app_obj + gDBus.ladish_app_iface.SetAppProperties2(number, app_obj[iAppName], app_obj[iAppCommand], app_obj[iAppTerminal], app_obj[iAppLevel]) + + elif act_x_sel == act_x_remove: + gDBus.ladish_app_iface.RemoveApp(number) + + @pyqtSlot(float) + def slot_canvasScaleChanged(self, scale): + self.ui.miniCanvasPreview.setViewScale(scale) + + @pyqtSlot(int, int, QPointF) + def slot_canvasItemMoved(self, group_id, split_mode, pos): + if split_mode == patchcanvas.PORT_MODE_INPUT: + canvas_x = URI_CANVAS_X_SPLIT + canvas_y = URI_CANVAS_Y_SPLIT + else: + canvas_x = URI_CANVAS_X + canvas_y = URI_CANVAS_Y + + gDBus.ladish_graph.Set(GRAPH_DICT_OBJECT_TYPE_CLIENT, group_id, canvas_x, str(pos.x())) + gDBus.ladish_graph.Set(GRAPH_DICT_OBJECT_TYPE_CLIENT, group_id, canvas_y, str(pos.y())) + + self.ui.miniCanvasPreview.update() + + @pyqtSlot(int) + def slot_horizontalScrollBarChanged(self, value): + maximum = self.ui.graphicsView.horizontalScrollBar().maximum() + if maximum == 0: + xp = 0 + else: + xp = float(value) / maximum + self.ui.miniCanvasPreview.setViewPosX(xp) + + @pyqtSlot(int) + def slot_verticalScrollBarChanged(self, value): + maximum = self.ui.graphicsView.verticalScrollBar().maximum() + if maximum == 0: + yp = 0 + else: + yp = float(value) / maximum + self.ui.miniCanvasPreview.setViewPosY(yp) + + @pyqtSlot() + def slot_miniCanvasInit(self): + settings = QSettings() + self.ui.graphicsView.horizontalScrollBar().setValue(settings.value("HorizontalScrollBarValue", DEFAULT_CANVAS_WIDTH / 3, type=int)) + self.ui.graphicsView.verticalScrollBar().setValue(settings.value("VerticalScrollBarValue", DEFAULT_CANVAS_HEIGHT * 3 / 8, type=int)) + + @pyqtSlot(float, float) + def slot_miniCanvasMoved(self, xp, yp): + self.ui.graphicsView.horizontalScrollBar().setValue(xp * DEFAULT_CANVAS_WIDTH) + self.ui.graphicsView.verticalScrollBar().setValue(yp * DEFAULT_CANVAS_HEIGHT) + + @pyqtSlot() + def slot_miniCanvasCheckAll(self): + self.slot_miniCanvasCheckSize() + self.slot_horizontalScrollBarChanged(self.ui.graphicsView.horizontalScrollBar().value()) + self.slot_verticalScrollBarChanged(self.ui.graphicsView.verticalScrollBar().value()) + + @pyqtSlot() + def slot_miniCanvasCheckSize(self): + self.ui.miniCanvasPreview.setViewSize(float(self.ui.graphicsView.width()) / DEFAULT_CANVAS_WIDTH, float(self.ui.graphicsView.height()) / DEFAULT_CANVAS_HEIGHT) + + @pyqtSlot() + def slot_handleCrash_jack(self): + patchcanvas.clear() + self.DBusReconnect() + self.studioUnloaded() + + if gDBus.a2j: + if gDBus.a2j.is_started(): + self.a2jStarted() + else: + self.a2jStopped() + else: + self.ui.act_tools_a2j_start.setEnabled(False) + self.ui.act_tools_a2j_stop.setEnabled(False) + self.ui.act_tools_a2j_export_hw.setEnabled(False) + self.ui.menu_A2J_Bridge.setEnabled(False) + + @pyqtSlot() + def slot_handleCrash_ladish(self): + self.ui.treeWidget.clear() + patchcanvas.clear() + self.DBusReconnect() + QMessageBox.warning(self, self.tr("Error"), self.tr("ladish daemon has crashed")) + + @pyqtSlot() + def slot_handleCrash_studio(self): + QMessageBox.warning(self, self.tr("Error"), self.tr("jackdbus has crashed")) + + @pyqtSlot() + def slot_handleCrash_a2j(self): + try: + gDBus.a2j = dbus.Interface(gDBus.bus.get_object("org.gna.home.a2jmidid", "/"), "org.gna.home.a2jmidid.control") + except: + gDBus.a2j = None + + if gDBus.a2j: + if gDBus.a2j.is_started(): + self.a2jStarted() + else: + self.a2jStopped() + else: + self.ui.act_tools_a2j_start.setEnabled(False) + self.ui.act_tools_a2j_stop.setEnabled(False) + self.ui.act_tools_a2j_export_hw.setEnabled(False) + self.ui.menu_A2J_Bridge.setEnabled(False) + + @pyqtSlot(str) + def slot_DBusCrashCallback(self, appInterface): + if appInterface == "org.jackaudio.service": + if not (self.m_crashedJACK or self.m_crashedLADISH): + self.m_crashedJACK = True + QTimer.singleShot(1000, self.slot_handleCrash_jack) + elif appInterface == "org.ladish": + if not (self.m_crashedJACK or self.m_crashedLADISH): + self.m_crashedLADISH = True + QTimer.singleShot(1000, self.slot_handleCrash_ladish) + + @pyqtSlot() + def slot_DBusServerStartedCallback(self): + self.jackStarted() + + @pyqtSlot() + def slot_DBusServerStoppedCallback(self): + self.jackStopped() + + @pyqtSlot(int, str) + def slot_DBusClientAppearedCallback(self, group_id, group_name): + self.canvas_add_group(group_id, group_name) + + @pyqtSlot(int) + def slot_DBusClientDisappearedCallback(self, group_id): + self.canvas_remove_group(group_id) + + @pyqtSlot(int, str) + def slot_DBusClientRenamedCallback(self, group_id, new_group_name): + self.canvas_rename_group(group_id, new_group_name) + + @pyqtSlot(int, int, str, int, int) + def slot_DBusPortAppearedCallback(self, group_id, port_id, port_name, port_flags, port_type_jack): + if port_flags & JACKDBUS_PORT_FLAG_INPUT: + port_mode = patchcanvas.PORT_MODE_INPUT + elif port_flags & JACKDBUS_PORT_FLAG_OUTPUT: + port_mode = patchcanvas.PORT_MODE_OUTPUT + else: + port_mode = patchcanvas.PORT_MODE_NULL + + if port_type_jack == JACKDBUS_PORT_TYPE_AUDIO: + port_type = patchcanvas.PORT_TYPE_AUDIO_JACK + elif port_type_jack == JACKDBUS_PORT_TYPE_MIDI: + if gDBus.ladish_graph.Get(GRAPH_DICT_OBJECT_TYPE_PORT, port_id, URI_A2J_PORT) == "yes": + port_type = patchcanvas.PORT_TYPE_MIDI_A2J + else: + port_type = patchcanvas.PORT_TYPE_MIDI_JACK + else: + port_type = patchcanvas.PORT_TYPE_NULL + + self.canvas_add_port(group_id, port_id, port_name, port_mode, port_type) + + @pyqtSlot(int) + def slot_DBusPortDisppearedCallback(self, port_id): + self.canvas_remove_port(port_id) + + @pyqtSlot(int, str) + def slot_DBusPortRenamedCallback(self, port_id, new_port_name): + self.canvas_rename_port(port_id, new_port_name) + + @pyqtSlot(int, int, int) + def slot_DBusPortsConnectedCallback(self, connection_id, source_port_id, target_port_id): + self.canvas_connect_ports(connection_id, source_port_id, target_port_id) + + @pyqtSlot(int) + def slot_DBusPortsDisconnectedCallback(self, connection_id): + self.canvas_disconnect_ports(connection_id) + + @pyqtSlot() + def slot_DBusStudioAppearedCallback(self): + self.studioLoaded() + if gDBus.ladish_studio.IsStarted(): + self.studioStarted() + else: + self.studioStopped() + + @pyqtSlot() + def slot_DBusStudioDisappearedCallback(self): + self.studioUnloaded() + + @pyqtSlot() + def slot_DBusQueueExecutionHaltedCallback(self): + log_path = os.path.join(HOME, ".log", "ladish", "ladish.log") + if os.path.exists(log_path): + log_file = open(log_path) + log_text = logs.fixLogText(log_file.read().split("ERROR: ")[-1].split("\n")[0]) + log_file.close() + else: + log_text = None + + msgbox = QMessageBox(QMessageBox.Critical, self.tr("Execution Halted"), + self.tr("Something went wrong with ladish so the last action was not sucessful.\n"), QMessageBox.Ok, self) + + if log_text: + msgbox.setInformativeText(self.tr("You can check the ladish log file (or click in the 'Show Details' button) to find out what went wrong.")) + msgbox.setDetailedText(log_text) + else: + msgbox.setInformativeText(self.tr("You can check the ladish log file to find out what went wrong.")) + + msgbox.show() + + @pyqtSlot() + def slot_DBusCleanExitCallback(self): + pass # TODO + #self.timer1000.stop() + #QTimer.singleShot(1000, self.DBusReconnect) + #QTimer.singleShot(1500, self.timer1000.start) + + @pyqtSlot() + def slot_DBusStudioStartedCallback(self): + self.studioStarted() + + @pyqtSlot() + def slot_DBusStudioStoppedCallback(self): + self.studioStopped() + + @pyqtSlot(str) + def slot_DBusStudioRenamedCallback(self, new_name): + self.ui.treeWidget.topLevelItem(0).setText(0, new_name) + + @pyqtSlot() + def slot_DBusStudioCrashedCallback(self): + QTimer.singleShot(0, self.slot_handleCrash_studio) + + @pyqtSlot(str, str) + def slot_DBusRoomAppearedCallback(self, room_path, room_name): + self.room_add(room_path, room_name) + + @pyqtSlot(str) + def slot_DBusRoomDisappearedCallback(self, room_path): + for i in range(self.ui.treeWidget.topLevelItemCount()): + item = self.ui.treeWidget.topLevelItem(i) + + if i == 0: + continue + + if item and item.type() == ITEM_TYPE_ROOM and item.properties[iItemPropRoomPath] == room_path: + for j in range(item.childCount()): + item.takeChild(j) + + self.ui.treeWidget.takeTopLevelItem(i) + break + + else: + print("Claudia - room delete failed") + + @pyqtSlot() + def slot_DBusRoomChangedCallback(self): + # Unused in ladish v1.0 + return + + @pyqtSlot(str, str) + def slot_DBusProjectPropertiesChanged(self, path, name): + has_project = bool(name) + + if has_project: + item_string = " (%s)" % name + else: + item_string = "" + + self.ui.act_project_save.setEnabled(has_project) + self.ui.act_project_save_as.setEnabled(has_project) + self.ui.act_project_unload.setEnabled(has_project) + self.ui.act_project_properties.setEnabled(has_project) + self.ui.b_project_save.setEnabled(has_project) + self.ui.b_project_save_as.setEnabled(has_project) + self.ui.menu_Application.setEnabled(has_project) + + if path == "/org/ladish/Studio": + top_level_item = self.ui.treeWidget.topLevelItem(0) + room_name = "" + + else: + for i in range(self.ui.treeWidget.topLevelItemCount()): + if i == 0: + continue + top_level_item = self.ui.treeWidget.topLevelItem(i) + if top_level_item and top_level_item.type() == ITEM_TYPE_ROOM and top_level_item.properties[iItemPropRoomPath] == path: + room_name = top_level_item.properties[iItemPropRoomName] + break + else: + return + + top_level_item.setText(0, "%s%s" % (room_name, item_string)) + + @pyqtSlot(str, int, str, bool, bool, str) + def slot_DBusAppAdded2Callback(self, path, number, name, active, terminal, level): + if path == "/org/ladish/Studio": + ITEM_TYPE = ITEM_TYPE_STUDIO_APP + top_level_item = self.ui.treeWidget.topLevelItem(0) + else: + ITEM_TYPE = ITEM_TYPE_ROOM_APP + for i in range(self.ui.treeWidget.topLevelItemCount()): + if i == 0: + continue + top_level_item = self.ui.treeWidget.topLevelItem(i) + if top_level_item and top_level_item.type() == ITEM_TYPE_ROOM and top_level_item.properties[iItemPropRoomPath] == path: + break + else: + return + + for i in range(top_level_item.childCount()): + if top_level_item.child(i).properties[iItemPropNumber] == number: + # App was added before, probably during reload/init + return + + prop_obj = [None, None, None, None, None] + prop_obj[iItemPropNumber] = number + prop_obj[iItemPropName] = name + prop_obj[iItemPropActive] = active + prop_obj[iItemPropTerminal] = terminal + prop_obj[iItemPropLevel] = level + + text = "[" + if level.isdigit(): + text += "L%s" % level + elif level == "jacksession": + text += "JS" + else: + text += level.upper() + text += "] " + if not active: + text += "(inactive) " + text += name + + item = QTreeWidgetItem(ITEM_TYPE) + item.properties = prop_obj + item.setText(0, text) + top_level_item.addChild(item) + + @pyqtSlot(str, int) + def slot_DBusAppRemovedCallback(self, path, number): + if path == "/org/ladish/Studio": + top_level_item = self.ui.treeWidget.topLevelItem(0) + else: + for i in range(self.ui.treeWidget.topLevelItemCount()): + if i == 0: + continue + top_level_item = self.ui.treeWidget.topLevelItem(i) + if top_level_item and top_level_item.type() == ITEM_TYPE_ROOM and top_level_item.properties[iItemPropRoomPath] == path: + break + else: + return + + for i in range(top_level_item.childCount()): + if top_level_item.child(i).properties[iItemPropNumber] == number: + top_level_item.takeChild(i) + break + + @pyqtSlot(str, int, str, bool, bool, str) + def slot_DBusAppStateChanged2Callback(self, path, number, name, active, terminal, level): + if path == "/org/ladish/Studio": + top_level_item = self.ui.treeWidget.topLevelItem(0) + else: + for i in range(self.ui.treeWidget.topLevelItemCount()): + if i == 0: + continue + top_level_item = self.ui.treeWidget.topLevelItem(i) + if top_level_item and top_level_item.type() == ITEM_TYPE_ROOM and top_level_item.properties[iItemPropRoomPath] == path: + break + else: + return + + prop_obj = [None, None, None, None, None] + prop_obj[iItemPropNumber] = number + prop_obj[iItemPropName] = name + prop_obj[iItemPropActive] = active + prop_obj[iItemPropTerminal] = terminal + prop_obj[iItemPropLevel] = level + + text = "[" + if level.isdigit(): + text += "L%s" % level + elif level == "jacksession": + text += "JS" + else: + text += level.upper() + text += "] " + if not active: + text += "(inactive) " + text += name + + for i in range(top_level_item.childCount()): + item = top_level_item.child(i) + if item.properties[iItemPropNumber] == number: + item.properties = prop_obj + item.setText(0, text) + break + + @pyqtSlot() + def slot_JackClearXruns(self): + if gJack.client: + gDBus.jack.ResetXruns() + + @pyqtSlot(int) + def slot_JackBufferSizeCallback(self, bufferSize): + self.ui_setBufferSize(bufferSize) + + @pyqtSlot(int) + def slot_JackSampleRateCallback(self, sampleRate): + self.ui_setSampleRate(sampleRate) + + @pyqtSlot() + def slot_JackShutdownCallback(self): + self.jackStopped() + + @pyqtSlot() + def slot_A2JBridgeStart(self): + ret = False + if gDBus.a2j: + ret = bool(gDBus.a2j.start()) + return ret + + @pyqtSlot() + def slot_A2JBridgeStop(self): + ret = False + if gDBus.a2j: + ret = bool(gDBus.a2j.stop()) + return ret + + @pyqtSlot() + def slot_A2JBridgeExportHW(self): + if gDBus.a2j: + ask = QMessageBox.question(self, self.tr("A2J Hardware Export"), self.tr("Enable Hardware Export on the A2J Bridge?"), QMessageBox.Yes | QMessageBox.No | QMessageBox.Cancel, QMessageBox.No) + if ask == QMessageBox.Yes: + gDBus.a2j.set_hw_export(True) + elif ask == QMessageBox.No: + gDBus.a2j.set_hw_export(False) + + @pyqtSlot() + def slot_configureClaudia(self): + # Save groups position now + if gDBus.patchbay: + version, groups, conns = gDBus.patchbay.GetGraph(0) + + for group in groups: + group_id, group_name, ports = group + + group_pos_i = patchcanvas.getGroupPos(group_id, patchcanvas.PORT_MODE_OUTPUT) + group_pos_o = patchcanvas.getGroupPos(group_id, patchcanvas.PORT_MODE_INPUT) + + gDBus.ladish_graph.Set(GRAPH_DICT_OBJECT_TYPE_CLIENT, group_id, URI_CANVAS_X, str(group_pos_o.x())) + gDBus.ladish_graph.Set(GRAPH_DICT_OBJECT_TYPE_CLIENT, group_id, URI_CANVAS_Y, str(group_pos_o.y())) + gDBus.ladish_graph.Set(GRAPH_DICT_OBJECT_TYPE_CLIENT, group_id, URI_CANVAS_X_SPLIT, str(group_pos_i.x())) + gDBus.ladish_graph.Set(GRAPH_DICT_OBJECT_TYPE_CLIENT, group_id, URI_CANVAS_Y_SPLIT, str(group_pos_i.y())) + + try: + ladish_config = gDBus.bus.get_object("org.ladish.conf", "/org/ladish/conf") + except: + ladish_config = None + + if ladish_config: + try: + key_notify = bool(ladish_config.get(LADISH_CONF_KEY_DAEMON_NOTIFY)[0] == "true") + except: + key_notify = LADISH_CONF_KEY_DAEMON_NOTIFY_DEFAULT + + try: + key_shell = str(ladish_config.get(LADISH_CONF_KEY_DAEMON_SHELL)[0]) + except: + key_shell = LADISH_CONF_KEY_DAEMON_SHELL_DEFAULT + + try: + key_terminal = str(ladish_config.get(LADISH_CONF_KEY_DAEMON_TERMINAL)[0]) + except: + key_terminal = LADISH_CONF_KEY_DAEMON_TERMINAL_DEFAULT + + try: + key_studio_autostart = bool(ladish_config.get(LADISH_CONF_KEY_DAEMON_STUDIO_AUTOSTART)[0] == "true") + except: + key_studio_autostart = LADISH_CONF_KEY_DAEMON_STUDIO_AUTOSTART_DEFAULT + + try: + key_js_save_delay = int(ladish_config.get(LADISH_CONF_KEY_DAEMON_JS_SAVE_DELAY)[0]) + except: + key_js_save_delay = LADISH_CONF_KEY_DAEMON_JS_SAVE_DELAY_DEFAULT + + settings = QSettings() + settings.setValue(LADISH_CONF_KEY_DAEMON_NOTIFY, key_notify) + settings.setValue(LADISH_CONF_KEY_DAEMON_SHELL, key_shell) + settings.setValue(LADISH_CONF_KEY_DAEMON_TERMINAL, key_terminal) + settings.setValue(LADISH_CONF_KEY_DAEMON_STUDIO_AUTOSTART, key_studio_autostart) + settings.setValue(LADISH_CONF_KEY_DAEMON_JS_SAVE_DELAY, key_js_save_delay) + del settings + + dialog = SettingsW(self, "claudia", hasGL) + + if not ladish_config: + dialog.ui.lw_page.hideRow(2) + + if dialog.exec_(): + if ladish_config: + settings = QSettings() + ladish_config.set(LADISH_CONF_KEY_DAEMON_NOTIFY, "true" if (settings.value(LADISH_CONF_KEY_DAEMON_NOTIFY, LADISH_CONF_KEY_DAEMON_NOTIFY_DEFAULT, type=bool)) else "false") + ladish_config.set(LADISH_CONF_KEY_DAEMON_SHELL, settings.value(LADISH_CONF_KEY_DAEMON_SHELL, LADISH_CONF_KEY_DAEMON_SHELL_DEFAULT, type=str)) + ladish_config.set(LADISH_CONF_KEY_DAEMON_TERMINAL, settings.value(LADISH_CONF_KEY_DAEMON_TERMINAL, LADISH_CONF_KEY_DAEMON_TERMINAL_DEFAULT, type=str)) + ladish_config.set(LADISH_CONF_KEY_DAEMON_STUDIO_AUTOSTART, "true" if (settings.value(LADISH_CONF_KEY_DAEMON_STUDIO_AUTOSTART, LADISH_CONF_KEY_DAEMON_STUDIO_AUTOSTART_DEFAULT, type=bool)) else "false") + ladish_config.set(LADISH_CONF_KEY_DAEMON_JS_SAVE_DELAY, str(settings.value(LADISH_CONF_KEY_DAEMON_JS_SAVE_DELAY, LADISH_CONF_KEY_DAEMON_JS_SAVE_DELAY_DEFAULT, type=int))) + del settings + + self.loadSettings(False) + patchcanvas.clear() + + pOptions = patchcanvas.options_t() + pOptions.theme_name = self.fSavedSettings["Canvas/Theme"] + pOptions.auto_hide_groups = self.fSavedSettings["Canvas/AutoHideGroups"] + pOptions.use_bezier_lines = self.fSavedSettings["Canvas/UseBezierLines"] + pOptions.antialiasing = self.fSavedSettings["Canvas/Antialiasing"] + pOptions.eyecandy = self.fSavedSettings["Canvas/EyeCandy"] + + pFeatures = patchcanvas.features_t() + pFeatures.group_info = False + pFeatures.group_rename = True + pFeatures.port_info = True + pFeatures.port_rename = True + pFeatures.handle_group_pos = False + + patchcanvas.setOptions(pOptions) + patchcanvas.setFeatures(pFeatures) + patchcanvas.init("Claudia", self.scene, self.canvasCallback, DEBUG) + + self.ui.miniCanvasPreview.setViewTheme(patchcanvas.canvas.theme.canvas_bg, patchcanvas.canvas.theme.rubberband_brush, patchcanvas.canvas.theme.rubberband_pen.color()) + + if gDBus.ladish_control.IsStudioLoaded() and gDBus.ladish_studio and gDBus.ladish_studio.IsStarted(): + self.initPorts() + + @pyqtSlot() + def slot_aboutClaudia(self): + QMessageBox.about(self, self.tr("About Claudia"), self.tr("

Claudia

" + "
Version %s" + "
Claudia is a Graphical User Interface to LADISH.
" + "
Copyright (C) 2010-2018 falkTX" % VERSION)) + + def saveSettings(self): + settings = QSettings() + + settings.setValue("Geometry", self.saveGeometry()) + settings.setValue("SplitterSizes", self.ui.splitter.saveState()) + settings.setValue("ShowToolbar", self.ui.frame_toolbar.isEnabled()) + settings.setValue("ShowStatusbar", self.ui.frame_statusbar.isEnabled()) + settings.setValue("TransportView", self.fCurTransportView) + settings.setValue("HorizontalScrollBarValue", self.ui.graphicsView.horizontalScrollBar().value()) + settings.setValue("VerticalScrollBarValue", self.ui.graphicsView.verticalScrollBar().value()) + + def loadSettings(self, geometry): + settings = QSettings() + + if geometry: + self.restoreGeometry(settings.value("Geometry", b"")) + + splitterSizes = settings.value("SplitterSizes", "") + if splitterSizes: + self.ui.splitter.restoreState(splitterSizes) + else: + self.ui.splitter.setSizes((100, 400)) + + showToolbar = settings.value("ShowToolbar", True, type=bool) + self.ui.act_settings_show_toolbar.setChecked(showToolbar) + self.ui.frame_toolbar.setVisible(showToolbar) + + showStatusbar = settings.value("ShowStatusbar", True, type=bool) + self.ui.act_settings_show_statusbar.setChecked(showStatusbar) + self.ui.frame_statusbar.setVisible(showStatusbar) + + self.setTransportView(settings.value("TransportView", TRANSPORT_VIEW_HMS, type=int)) + + self.fSavedSettings = { + "Main/DefaultProjectFolder": settings.value("Main/DefaultProjectFolder", DEFAULT_PROJECT_FOLDER, type=str), + "Main/UseSystemTray": settings.value("Main/UseSystemTray", True, type=bool), + "Main/CloseToTray": settings.value("Main/CloseToTray", False, type=bool), + "Main/RefreshInterval": settings.value("Main/RefreshInterval", 120, type=int), + "Canvas/Theme": settings.value("Canvas/Theme", patchcanvas.getDefaultThemeName(), type=str), + "Canvas/AutoHideGroups": settings.value("Canvas/AutoHideGroups", False, type=bool), + "Canvas/UseBezierLines": settings.value("Canvas/UseBezierLines", True, type=bool), + "Canvas/EyeCandy": settings.value("Canvas/EyeCandy", patchcanvas.EYECANDY_SMALL, type=int), + "Canvas/UseOpenGL": settings.value("Canvas/UseOpenGL", False, type=bool), + "Canvas/Antialiasing": settings.value("Canvas/Antialiasing", patchcanvas.ANTIALIASING_SMALL, type=int), + "Canvas/HighQualityAntialiasing": settings.value("Canvas/HighQualityAntialiasing", False, type=bool) + } + + self.ui.act_app_add_new.setEnabled(USE_CLAUDIA_ADD_NEW) + + def resizeEvent(self, event): + QTimer.singleShot(0, self.slot_miniCanvasCheckSize) + QMainWindow.resizeEvent(self, event) + + def timerEvent(self, event): + if event.timerId() == self.m_timer120: + if gJack.client: + self.refreshTransport() + self.refreshXruns() + elif event.timerId() == self.m_timer600: + if gJack.client: + self.refreshDSPLoad() + else: + self.update() + QMainWindow.timerEvent(self, event) + + def closeEvent(self, event): + self.saveSettings() + if self.systray: + if self.fSavedSettings["Main/CloseToTray"]: + if self.systray.handleQtCloseEvent(event): + patchcanvas.clear() + return + self.systray.close() + patchcanvas.clear() + QMainWindow.closeEvent(self, event) + +#--------------- main ------------------ +if __name__ == '__main__': + # App initialization + app = QApplication(sys.argv) + app.setApplicationName("Claudia") + app.setApplicationVersion(VERSION) + app.setOrganizationName("Cadence") + app.setWindowIcon(QIcon(":/scalable/claudia.svg")) + setup_i18n() + + if not haveDBus: + QMessageBox.critical(None, app.translate("ClaudiaMainW", "Error"), app.translate("ClaudiaMainW", + "DBus is not available, Claudia cannot start without it!")) + sys.exit(1) + + gDBus.loop = DBusQtMainLoop(set_as_default=True) + gDBus.bus = dbus.SessionBus(mainloop=gDBus.loop) + gDBus.jack = gDBus.bus.get_object("org.jackaudio.service", "/org/jackaudio/Controller") + gDBus.ladish_control = gDBus.bus.get_object("org.ladish", "/org/ladish/Control") + + try: + gDBus.ladish_app_daemon = gDBus.bus.get_object("org.ladish.appdb", "/") + except: + gDBus.ladish_app_daemon = None + + try: + gDBus.a2j = dbus.Interface(gDBus.bus.get_object("org.gna.home.a2jmidid", "/"), "org.gna.home.a2jmidid.control") + except: + gDBus.a2j = None + + jacksettings.initBus(gDBus.bus) + + # Show GUI + gui = ClaudiaMainW() + + if gui.systray and "--minimized" in app.arguments(): + gui.hide() + gui.systray.setActionText("show", gui.tr("Restore")) + else: + gui.show() + + # Set-up custom signal handling + setUpSignals(gui) + + # App-Loop + if gui.systray: + ret = gui.systray.exec_(app) + else: + ret = app.exec_() + + # Close Jack + if gJack.client: + jacklib.deactivate(gJack.client) + jacklib.client_close(gJack.client) + + # Exit properly + sys.exit(ret) diff --git a/src/claudia_database.py b/src/claudia_database.py new file mode 100644 index 0000000..644f45a --- /dev/null +++ b/src/claudia_database.py @@ -0,0 +1,627 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +# Package database + +generic_audio_icon = "audio-x-generic" +generic_midi_icon = "audio-midi" + +LEVEL_0 = "Lv. 0" +LEVEL_1 = "Lv. 1" +LEVEL_LASH = "LASH" +LEVEL_JS = "JACK-Session" +LEVEL_NSM = "NSM" + +TEMPLATE_YES = "Yes" +TEMPLATE_NO = "No" + +USING_KXSTUDIO = False + +# ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +# DAW + +# (L, D, L, V, VST-Mode, T, M, MIDI-Mode) -> (LADSPA, DSSI, LV2, VST, VST-Mode, Transport, MIDI, MIDI-Mode) + +list_DAW = [ + # Package AppName Type Binary Icon Template? Level (L, D, L, V, VST-Mode, T, M, MIDI-Mode) (doc-file, website) + [ "ardour", "Ardour 6", "DAW", "ardour", "/usr/share/pixmaps/ardour.svg", TEMPLATE_NO, LEVEL_NSM, (1, 0, 1, 0, "Native", 1, 1, "JACK"), ("", "http://www.ardour.org/") ], + + [ "ariamaestosa", "Aria Maestosa", "MIDI Sequencer", "Aria", "/usr/share/Aria/aria64.png", TEMPLATE_NO, LEVEL_0, (0, 0, 0, 0, "", 0, 1, "ALSA | JACK"), ("", "http://ariamaestosa.sf.net/") ], + + [ "giada", "Giada", "Audio Looper", "giada", "giada", TEMPLATE_NO, LEVEL_0, (0, 0, 0, 0, "", 0, 0, ""), ("", "http://www.monocasual.com/giada/") ], + + [ "hydrogen", "Hydrogen", "Drum Sequencer", "hydrogen -d jack", "/usr/share/icons/hicolor/scalable/apps/org.hydrogenmusic.Hydrogen.svg", TEMPLATE_YES, LEVEL_NSM, (1, 0, 0, 0, "", 1, 1, "ALSA | JACK"), ("file:///usr/share/hydrogen/data/doc/manual_en.html", "http://www.hydrogen-music.org/") ], + + [ "jacker", "Jacker", "MIDI Tracker", "jacker", "jacker", TEMPLATE_YES, LEVEL_0, (0, 0, 0, 0, "", 1, 1, "JACK"), ("", "https://bitbucket.org/paniq/jacker/wiki/Home") ], + + [ "klystrack", "Klystrack", "Chiptune Tracker", "klystrack", "klystrack", TEMPLATE_NO, LEVEL_0, (0, 0, 0, 0, "", 0, 0, "----"), ("", "http://kometbomb.github.io/klystrack/") ], + + [ "lmms", "LMMS", "DAW", "lmms", "lmms", TEMPLATE_YES, LEVEL_0, (1, 0, 0, 1, "Windows", 0, 1, "ALSA"), ("", "http://lmms.sf.net/") ], + + [ "milkytracker", "MilkyTracker", "Tracker", "milkytracker", "milkytracker", TEMPLATE_NO, LEVEL_0, (0, 0, 0, 0, "", 0, 1, "ALSA"), ("", "https://milkytracker.org/") ], + + [ "muse", "MusE", "DAW", "muse", "muse_icon", TEMPLATE_YES, LEVEL_0, (1, 1, 0, 1, "Native", 1, 1, "ALSA + JACK"), ("", "http://www.muse-sequencer.org/") ], + + [ "musescore3", "MuseScore 3", "MIDI Composer", "mscore3", "mscore3", TEMPLATE_NO, LEVEL_0, (0, 0, 0, 0, "", 1, 1, "ALSA | JACK"), ("", "http://www.musescore.org/") ], + + [ "non-sequencer", "Non-Sequencer", "MIDI Sequencer", "non-sequencer", "non-sequencer", TEMPLATE_YES, LEVEL_NSM, (0, 0, 0, 0, "", 1, 1, "JACK"), ("file:///usr/share/doc/non-sequencer/MANUAL.html", "http://non.tuxfamily.org/wiki/Non%20Sequencer") ], + [ "non-timeline", "Non-Timeline", "DAW", "non-timeline", "non-timeline", TEMPLATE_YES, LEVEL_NSM, (0, 0, 0, 0, "", 1, 0, "CV + OSC"), ("file:///usr/share/doc/non-timeline/MANUAL.html", "http://non.tuxfamily.org/wiki/Non%20Timeline") ], + + [ "protrekkr", "ProTrekkr", "Tracker", "protrekkr", "protrekkr", TEMPLATE_NO, LEVEL_0, (0, 0, 0, 1, "", 0, 1, "ALSA"), ("", "https://code.google.com/p/protrekkr/") ], + + [ "qtractor", "Qtractor", "DAW", "qtractor", "qtractor", TEMPLATE_YES, LEVEL_1, (1, 1, 1, 1, "Native", 1, 1, "ALSA"), ("", "http://qtractor.sf.net/") ], + + [ "rosegarden", "Rosegarden", "MIDI Sequencer", "rosegarden", "rosegarden", TEMPLATE_YES, LEVEL_1, (1, 1, 0, 0, "", 1, 1, "ALSA"), ("", "http://www.rosegardenmusic.com/") ], + + [ "schism", "Schism", "Impulse Tracker clone", "schismtracker", "schism-icon-128", TEMPLATE_YES, LEVEL_0, (0, 0, 0, 0, "", 0, 1, "ALSA"), ("", "http://schismtracker.org/") ], + + [ "seq24", "Seq24", "MIDI Sequencer", "seq24", "seq24", TEMPLATE_YES, LEVEL_1, (0, 0, 0, 0, "", 1, 1, "ALSA"), ("", "http://www.filter24.org/seq24/") ], + + [ "sooperlooper", "SooperLooper", "Audio Looper", "slgui", "sooperlooper", TEMPLATE_NO, LEVEL_0, (0, 0, 0, 0, "", 0, 1, "JACK"), ("", "http://essej.net/sooperlooper/download.html") ], + + [ "tutka", "Tutka", "MIDI Tracker", "tutka", "tutka", TEMPLATE_NO, LEVEL_0, (0, 0, 0, 0, "", 0, 1, "JACK"), ("", "http://www.nongnu.org/tutka/") ], +] + +iDAW_Package, iDAW_AppName, iDAW_Type, iDAW_Binary, iDAW_Icon, iDAW_Template, iDAW_Level, iDAW_Features, iDAW_Docs = range(0, len(list_DAW[0])) + +if USING_KXSTUDIO: + # Jacker + list_DAW[4][iDAW_Level] = LEVEL_1 + +# ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +# Host + +# (I, L, D, L, V, VST-Mode, MIDI-Mode) -> (Internal, LADSPA, DSSI, LV2, VST, VST-Mode, MIDI-Mode) + +list_Host = [ + # Package AppName Ins? FX? Binary Icon Template? Level (I, L, D, L, V, VST-Mode, MIDI-Mode) (doc-file, website) + [ "ams", "AlsaModularSynth", "Yes", "Yes", "ams", "ams_32", TEMPLATE_NO, LEVEL_0, (1, 1, 0, 0, 0, "", "ALSA"), ("", "http://alsamodular.sourceforge.net/") ], + + [ "calf-plugins", "Calf Jack Host", "Yes", "Yes", "calfjackhost", "calf", TEMPLATE_YES, LEVEL_1, (1, 0, 0, 0, 0, "", "JACK"), ("file:///usr/share/doc/calf/index.html", "http://calf.sf.net/") ], + [ "calf-plugins-git", "Calf Jack Host (GIT)", "Yes", "Yes", "calfjackhost", "calf", TEMPLATE_YES, LEVEL_1, (1, 0, 0, 0, 0, "", "JACK"), ("file:///usr/share/doc/calf/index.html", "http://calf.sf.net/") ], + + [ "carla", "Carla", "Yes", "Yes", "carla", "carla", TEMPLATE_YES, LEVEL_1, (1, 1, 1, 1, 1, "Both", "ALSA | JACK"), ("", "http://kxstudio.sf.net/Applications:Carla") ], + [ "carla-git", "Carla (GIT)", "Yes", "Yes", "carla", "carla", TEMPLATE_YES, LEVEL_NSM, (1, 1, 1, 1, 1, "Both", "ALSA | JACK"), ("", "http://kxstudio.sf.net/Applications:Carla") ], + + [ "festige", "FeSTige", "Yes", "Yes", "festige", "festige", TEMPLATE_NO, LEVEL_1, (0, 0, 0, 0, 1, "Windows", "ALSA | JACK"), ("", "http://festige.sf.net/") ], + + [ "ingen", "Ingen", "Yes", "Yes", "ingen -eg", "ingen", TEMPLATE_NO, LEVEL_0, (1, 0, 0, 1, 0, "", "JACK"), ("", "http://drobilla.net/blog/software/ingen/") ], + + [ "jack-rack", "Jack Rack", "No", "Yes", "jack-rack", "/usr/share/pixmaps/jack-rack-icon.png", TEMPLATE_YES, LEVEL_0, (0, 1, 0, 0, 0, "", "ALSA"), ("", "http://jack-rack.sf.net/") ], + + [ "jalv.select", "Jalv.select", "No", "No", "jalv.select", "/usr/share/pixmaps/lv2.png", TEMPLATE_YES, LEVEL_0, (0, 0, 0, 1, 0, "", "---"), ("", "https://github.com/brummer10/jalv_select") ], + + [ "mod-app", "MOD App", "Yes", "Yes", "mod-app", "mod", TEMPLATE_NO, LEVEL_0, (0, 0, 0, 1, 0, "", "JACK"), ("", "http://moddevices.com/") ], + + [ "spiralsynthmodular", "SpiralSynthModular", "Yes", "Yes", "spiralsynthmodular", generic_audio_icon, TEMPLATE_NO, LEVEL_0, (1, 0, 0, 0, 0, "", "ALSA"), ("", "http://jack-rack.sf.net/") ], +] + +iHost_Package, iHost_AppName, iHost_Ins, iHost_FX, iHost_Binary, iHost_Icon, iHost_Template, iHost_Level, iHost_Features, iDAW_Docs = range(0, len(list_Host[0])) + +# ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +# Instrument + +# (F, I, MIDI-Mode) -> (Built-in FX, Audio Input, MIDI-Mode) + +list_Instrument = [ + # Package AppName Type Binary Icon Template? Level (F, I, MIDI-Mode) (doc-file, website) + [ "6pm", "6 PM", "Synth", "6pm", "6PM-icon", TEMPLATE_NO, LEVEL_0, (0, 0, "JACK"), ("", "https://sourceforge.net/projects/mv-6pm/") ], + + [ "add64", "Add64", "Synth", "Add64", "add64", TEMPLATE_NO, LEVEL_0, (1, 0, "JACK"), ("", "https://sourceforge.net/projects/add64/") ], + + [ "aeolus", "Aeolus", "Synth", "aeolus -J", "audio-x-generic", TEMPLATE_NO, LEVEL_0, (0, 0, "JACK"), ("", "http://www.kokkinizita.net/linuxaudio/aeolus/index.html") ], + + [ "amsynth", "AmSynth", "Synth", "amsynth", "amsynth", TEMPLATE_NO, LEVEL_0, (0, 0, "ALSA | JACK"), ("", "http://amsynth.github.io/") ], + + [ "azr3-jack", "AZR3", "Synth", "azr3", "/usr/share/pixmaps/azr3-jack.xpm", TEMPLATE_NO, LEVEL_0, (1, 0, "JACK"), ("", "http://ll-plugins.nongnu.org/azr3/") ], + + [ "borderlands", "Borderlands", "Sampler", "Borderlands", "borderlands", TEMPLATE_NO, LEVEL_0, (0, 1, "JACK"), ("", "https://ccrma.stanford.edu/~carlsonc/256a/Borderlands/") ], + + [ "coldgaze", "Coldgaze", "Synth", "microSynth", "coldgaze", TEMPLATE_NO, LEVEL_0, (0, 0, "ALSA"), ("", "https://sourceforge.net/projects/coldgaze/") ], + + [ "cursynth", "cursynth", "Synth", "x-terminal-emulator -e cursynth", generic_audio_icon, TEMPLATE_NO, LEVEL_0, (0, 0, "ALSA"), ("", "http://www.gnu.org/software/cursynth/") ], + + [ "distrho-lv2", "Dexed", "Yamaha DX7 Emulator", "Dexed", "dexed", TEMPLATE_NO, LEVEL_0, (0, 0, "JACK"), ("", "https://distrho.sourceforge.io/ports.php") ], + + [ "din", "DIN", "Musical Instrument", "din", "din", TEMPLATE_NO, LEVEL_0, (0, 0, "JACK"), ("", "https://dinisnoise.org/") ], + + [ "drumkv1", "Drumk-V1", "Drum Sampler", "drumkv1_jack", "drumkv1", TEMPLATE_NO, LEVEL_JS, (0, 0, "ALSA | JACK"), ("", "https://drumkv1.sourceforge.io/") ], + + [ "distrho-lv2", "DrumSynth", "Drum synth", "DrumSynth", "drumsynth", TEMPLATE_NO, LEVEL_0, (0, 0, "JACK"), ("", "https://distrho.sourceforge.io/ports.php") ], + + [ "fabla", "Fabla", "Drum Sampler", "fabla", "fabla", TEMPLATE_NO, LEVEL_0, (0, 0, "JACK"), ("", "http://openavproductions.com/fabla/") ], + + [ "fabla2", "Fabla 2", "Sampler", "fabla2", "fabla2", TEMPLATE_NO, LEVEL_0, (0, 1, "JACK"), ("", "http://openavproductions.com/fabla2/") ], + + [ "foo-yc20", "Foo YC20", "Organ Synth", "foo-yc20", "/usr/share/foo-yc20/graphics/icon.png", TEMPLATE_NO, LEVEL_0, (0, 0, "JACK"), ("", "https://foo-yc20.codeforcode.com/") ], + + [ "grandorgue", "GrandOrgue", "Grand Organ Sampler", "GrandOrgue", "/usr/share/pixmaps/GrandOrgue.png", TEMPLATE_NO, LEVEL_0, (0, 0, "JACK"), ("", "https://sourceforge.net/p/ourorgan/wiki/Home/") ], + + [ "helm", "Helm", "Synth", "helm", "helm_icon_128_1x", TEMPLATE_NO, LEVEL_0, (1, 1, "JACK"), ("", "https://tytel.org/helm/") ], + + [ "hexter", "Hexter", "Synth", "jack-dssi-host hexter.so", "hexter", TEMPLATE_NO, LEVEL_0, (0, 0, "ALSA"), ("", "http://smbolton.com/hexter.html") ], + + [ "horgand", "Horgand", "Synth", "horgand", "horgand128", TEMPLATE_NO, LEVEL_0, (0, 0, "JACK"), ("", "https://sourceforge.net/projects/horgand.berlios/") ], + + [ "infamous-plugins", "Infamous Cellular Automaton Synth", "Synth", "infamous-casynth", "infamous-casynth", TEMPLATE_NO, LEVEL_0, (0, 1, "JACK"), ("", "http://ssj71.github.io/infamousPlugins/") ], + + [ "jsampler", "JSampler Fantasia", "Sampler", "jsampler-bin", "jsampler", TEMPLATE_NO, LEVEL_0, (0, 0, "ALSA + JACK"), ("file:///usr/share/kxstudio/docs/jsampler/jsampler.html", "http://www.linuxsampler.org/") ], + + [ "distrho-lv2", "Juce Demo Plugin", "Synth", "JuceDemoPlugin", "jucedemoplugin", TEMPLATE_NO, LEVEL_0, (0, 0, "JACK"), ("", "https://distrho.sourceforge.io/ports.php") ], + + [ "dpf-plugins", "Kars", "Virtual Instrument", "Kars", "kars", TEMPLATE_NO, LEVEL_0, (0, 0, "JACK"), ("", "https://distrho.sourceforge.io/plugins.php") ], + + [ "dpf-plugins", "Nekobi", "Roland TB-303 Emulator", "Nekobi", "nekobi", TEMPLATE_NO, LEVEL_0, (0, 0, "JACK"), ("", "https://distrho.sourceforge.io/plugins.php") ], + + [ "distrho-lv2", "TAL Noize Mak3r", "Synth", "NoizeMak3r", "noizemak3r", TEMPLATE_NO, LEVEL_0, (1, 0, "JACK"), ("", "https://distrho.sourceforge.io/ports.php") ], + + [ "distrho-lv2", "Obxd", "Ob-x, ob-xa & ob8 Emulators", "Obxd", "obxd", TEMPLATE_NO, LEVEL_0, (0, 0, "JACK"), ("", "https://distrho.sourceforge.io/ports.php") ], + + [ "padthv1", "Padth-V1", "Synth", "padthv1_jack", "padthv1", TEMPLATE_NO, LEVEL_NSM, (0, 0, "ALSA | JACK"), ("", "https://padthv1.sourceforge.io/") ], + + [ "petri-foo", "Petri-Foo", "Sampler", "petri-foo", "petri-foo", TEMPLATE_NO, LEVEL_NSM, (0, 0, "ALSA + JACK"), ("", "http://petri-foo.sf.net/") ], + + [ "phasex", "Phasex", "Synth", "phasex", "phasex", TEMPLATE_NO, LEVEL_0, (1, 1, "ALSA"), ("file:///usr/share/phasex/help/parameters.help", "") ], + + [ "polyphone", "Polyphone", "Sampler", "polyphone", "polyphone", TEMPLATE_NO, LEVEL_0, (1, 0, "JACK"), ("", "http://www.polyphone-soundfonts.com/") ], + + [ "qsampler", "Qsampler", "Sampler", "qsampler", "qsampler", TEMPLATE_YES, LEVEL_0, (0, 0, "ALSA + JACK"), ("", "http://qsampler.sf.net/") ], + + [ "qsynth", "Qsynth", "SoundFont Player", "qsynth -a jack -m jack", "qsynth", TEMPLATE_NO, LEVEL_0, (1, 0, "ALSA | JACK"), ("", "http://qsynth.sf.net/") ], + + [ "samplv1", "Sampl-V1", "Sampler", "samplv1_jack", "samplv1", TEMPLATE_NO, LEVEL_NSM, (0, 0, "ALSA | JACK"), ("", "https://samplv1.sourceforge.io/") ], + + [ "setbfree", "SetBfree", "Organ Emulator", "setBfreeUI", "setBfree", TEMPLATE_NO, LEVEL_0, (0, 0, "JACK"), ("", "http://setbfree.org/") ], + + [ "sorcer", "Sorcer", "Polyphonic Synth", "sorcer", "sorcer", TEMPLATE_NO, LEVEL_NSM, (0, 0, "JACK"), ("", "http://openavproductions.com/sorcer/") ], + + [ "spectmorph", "Spectmorph", "Morph Synth", "smjack", "smjack", TEMPLATE_NO, LEVEL_0, (0, 0, "JACK"), ("", "http://www.spectmorph.org/") ], + + [ "stegosaurus", "Stegosaurus", "Analog Drum Synth", "stegosaurus", "stegosaurus", TEMPLATE_NO, LEVEL_0, (0, 0, "JACK"), ("", "http://www.thunderox.com/") ], + + [ "synthv1", "Synth-V1", "Synth", "synthv1_jack", "synthv1", TEMPLATE_NO, LEVEL_NSM, (0, 0, "ALSA | JACK"), ("", "https://synthv1.sourceforge.io/") ], + + [ "tapeutape", "Tapeutape", "Sampler", "tapeutape", "tapeutape", TEMPLATE_NO, LEVEL_0, (0, 1, "ALSA"), ("", "http://hitmuri.net/index.php/Software/Tapeutape") ], + + [ "triceratops-lv2", "Triceratops", "Polyphonic Synth", "triceratops", "triceratops", TEMPLATE_NO, LEVEL_0, (0, 0, "JACK"), ("", "http://www.thunderox.com/triceratops.html") ], + + [ "vcvrack", "VCV Rack", "Modular Synth", "vcvrack", "vcvrack", TEMPLATE_NO, LEVEL_0, (0, 0, "---"), ("", "https://vcvrack.com/") ], + + [ "distrho-lv2", "Vex", "Synth", "Vex", "vex", TEMPLATE_NO, LEVEL_0, (0, 0, "JACK"), ("", "https://distrho.sourceforge.io/ports.php") ], + + [ "whysynth", "Whysynth", "Synth", "whysynth", "whysynth", TEMPLATE_NO, LEVEL_0, (1, 0, "JACK"), ("", "http://smbolton.com/whysynth.html") ], + + [ "distrho-lv2", "Wolpertinger", "Synth", "Wolpertinger", "/usr/share/pixmaps/wolpertinger.xpm", TEMPLATE_NO, LEVEL_0, (0, 0, "JACK"), ("", "https://distrho.sourceforge.io/ports.php") ], + + [ "wsynth-dssi", "Wsynth", "WaveTable Synth", "jack-dssi-host wsynth-dssi.so", "wsynth-dssi", TEMPLATE_NO, LEVEL_0, (1, 0, "JACK"), ("", "http://www.linuxsynths.com/WsynthBanksDemos/wsynth.html") ], + + [ "xsynth-dssi", "Xsynth", "Analog Synth", "jack-dssi-host xsynth-dssi.so", "xsynth-dssi", TEMPLATE_NO, LEVEL_0, (1, 0, "JACK"), ("", "http://dssi.sourceforge.net/download.html#Xsynth-DSSI") ], + + [ "yoshimi", "Yoshimi", "Synth", "yoshimi -j -J", "/usr/share/pixmaps/yoshimi.png", TEMPLATE_NO, LEVEL_1, (1, 0, "ALSA | JACK"), ("", "http://yoshimi.sf.net/") ], + + [ "zynaddsubfx", "ZynAddSubFX", "Synth", "zynaddsubfx", "zynaddsubfx", TEMPLATE_NO, LEVEL_NSM, (1, 0, "ALSA | JACK"), ("", "http://zynaddsubfx.sf.net/") ], + [ "zynaddsubfx-git", "ZynAddSubFX (GIT)", "Synth", "zynaddsubfx", "zynaddsubfx", TEMPLATE_NO, LEVEL_NSM, (1, 0, "ALSA | JACK"), ("", "http://zynaddsubfx.sf.net/") ], +] + +iInstrument_Package, iInstrument_AppName, iInstrument_Type, iInstrument_Binary, iInstrument_Icon, iInstrument_Template, iInstrument_Level, iInstrument_Features, iInstrument_Docs = range(0, len(list_Instrument[0])) + +# ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +# Bristol + +# Need name: bit99, bit100 + +list_Bristol = [ + # Package AppName Type Short-name Icon Template? Level (F, I, MIDI-Mode) (doc-file, website) + [ "bristol", "Moog Voyager", "Synth", "explorer", "bristol_explorer", TEMPLATE_NO, LEVEL_1, (1, 1, "ALSA | JACK"), ("", "http://bristol.sf.net/explorer.html") ], + [ "bristol", "Moog Mini", "Synth", "mini", "bristol_mini", TEMPLATE_NO, LEVEL_1, (1, 1, "ALSA | JACK"), ("", "http://bristol.sf.net/mini.html") ], + [ "bristol", "Sequential Circuits Prophet-52", "Synth", "prophet52", "bristol_prophet52", TEMPLATE_NO, LEVEL_1, (1, 1, "ALSA | JACK"), ("", "http://bristol.sf.net/prophet52.html") ], + + [ "bristol", "Moog/Realistic MG-1", "Synth", "realistic", "bristol_realistic", TEMPLATE_NO, LEVEL_1, (1, 1, "ALSA | JACK"), ("", "http://bristol.sf.net/realistic.html") ], + [ "bristol", "Memory Moog", "Synth", "memoryMoog", "bristol_memoryMoog", TEMPLATE_NO, LEVEL_1, (1, 1, "ALSA | JACK"), ("", "http://bristol.sf.net/memorymoog.html") ], + [ "bristol", "Baumann BME-700", "Synth", "BME700", "bristol_BME700", TEMPLATE_NO, LEVEL_1, (1, 1, "ALSA | JACK"), ("", "http://bristol.sf.net/bme700.shtml") ], + #[ "bristol", "Synthi Aks", "Synth", "aks", "bristol_aks", TEMPLATE_NO, LEVEL_1, (1, 1, "ALSA | JACK"), ("", "http://bristol.sf.net/aks.html") ], + + [ "bristol", "Moog Voyager Blue Ice", "Synth", "voyager", "bristol_voyager", TEMPLATE_NO, LEVEL_1, (1, 1, "ALSA | JACK"), ("", "http://bristol.sf.net/voyager.html") ], + [ "bristol", "Moog Sonic-6", "Synth", "sonic6", "bristol_sonic6", TEMPLATE_NO, LEVEL_1, (1, 1, "ALSA | JACK"), ("", "http://bristol.sf.net/sonic6.html") ], + [ "bristol", "Hammond B3", "Synth", "hammondB3", "bristol_hammondB3", TEMPLATE_NO, LEVEL_1, (1, 1, "ALSA | JACK"), ("", "http://bristol.sf.net/hammond.html") ], + [ "bristol", "Sequential Circuits Prophet-5", "Synth", "prophet", "bristol_prophet", TEMPLATE_NO, LEVEL_1, (1, 1, "ALSA | JACK"), ("", "http://bristol.sf.net/prophet5.html") ], + [ "bristol", "Sequential Circuits Prophet-10", "Synth", "prophet10", "bristol_prophet10", TEMPLATE_NO, LEVEL_1, (1, 1, "ALSA | JACK"), ("", "http://bristol.sf.net/prophet10.html") ], + [ "bristol", "Sequential Circuits Pro-1", "Synth", "pro1", "bristol_pro1", TEMPLATE_NO, LEVEL_1, (1, 1, "ALSA | JACK"), ("", "http://bristol.sf.net/pro1.html") ], + [ "bristol", "Fender Rhodes Stage-73", "Synth", "rhodes", "bristol_rhodes", TEMPLATE_NO, LEVEL_1, (1, 1, "ALSA | JACK"), ("", "http://bristol.sf.net/rhodes.html") ], + [ "bristol", "Rhodes Bass Piano", "Synth", "rhodesbass", "bristol_rhodesbass", TEMPLATE_NO, LEVEL_1, (1, 1, "ALSA | JACK"), ("", "http://bristol.sf.net/rhodes.html") ], + [ "bristol", "Crumar Roadrunner", "Synth", "roadrunner", "bristol_roadrunner", TEMPLATE_NO, LEVEL_1, (1, 1, "ALSA | JACK"), ("", "http://bristol.sf.net/roadrunner.html") ], + [ "bristol", "Crumar Bit-1", "Synth", "bitone", "bristol_bitone", TEMPLATE_NO, LEVEL_1, (1, 1, "ALSA | JACK"), ("", "http://bristol.sf.net/bitone.html") ], + [ "bristol", "Crumar Stratus", "Synth", "stratus", "bristol_stratus", TEMPLATE_NO, LEVEL_1, (1, 1, "ALSA | JACK"), ("", "http://bristol.sf.net/stratus.html") ], + [ "bristol", "Crumar Trilogy", "Synth", "trilogy", "bristol_trilogy", TEMPLATE_NO, LEVEL_1, (1, 1, "ALSA | JACK"), ("", "http://bristol.sf.net/trilogy.html") ], + [ "bristol", "Oberheim OB-X", "Synth", "obx", "bristol_obx", TEMPLATE_NO, LEVEL_1, (1, 1, "ALSA | JACK"), ("", "http://bristol.sf.net/obx.html") ], + [ "bristol", "Oberheim OB-Xa", "Synth", "obxa", "bristol_obxa", TEMPLATE_NO, LEVEL_1, (1, 1, "ALSA | JACK"), ("", "http://bristol.sf.net/obxa.html") ], + [ "bristol", "ARP Axxe", "Synth", "axxe", "bristol_axxe", TEMPLATE_NO, LEVEL_1, (1, 1, "ALSA | JACK"), ("", "http://bristol.sf.net/axxe.html") ], + [ "bristol", "ARP Odyssey", "Synth", "odyssey", "bristol_odyssey", TEMPLATE_NO, LEVEL_1, (1, 1, "ALSA | JACK"), ("", "http://bristol.sf.net/odyssey.html") ], + [ "bristol", "ARP 2600", "Synth", "arp2600", "bristol_arp2600", TEMPLATE_NO, LEVEL_1, (1, 1, "ALSA | JACK"), ("", "http://bristol.sf.net/arp2600.html") ], + [ "bristol", "ARP Solina Strings", "Synth", "solina", "bristol_solina", TEMPLATE_NO, LEVEL_1, (1, 1, "ALSA | JACK"), ("", "http://bristol.sf.net/solina.html") ], + [ "bristol", "Korg Poly-800", "Synth", "poly800", "bristol_poly800", TEMPLATE_NO, LEVEL_1, (1, 1, "ALSA | JACK"), ("", "http://bristol.sf.net/poly800.shtml") ], + [ "bristol", "Korg Mono/Poly", "Synth", "monopoly", "bristol_monopoly", TEMPLATE_NO, LEVEL_1, (1, 1, "ALSA | JACK"), ("", "http://bristol.sf.net/mono.html") ], + [ "bristol", "Korg Polysix", "Synth", "poly", "bristol_poly", TEMPLATE_NO, LEVEL_1, (1, 1, "ALSA | JACK"), ("", "http://bristol.sf.net/poly.html") ], + [ "bristol", "Korg MS-20 (*)", "Synth", "ms20", "bristol_ms20", TEMPLATE_NO, LEVEL_1, (1, 1, "ALSA | JACK"), ("", "http://bristol.sf.net/ms20.html") ], + [ "bristol", "VOX Continental", "Synth", "vox", "bristol_vox", TEMPLATE_NO, LEVEL_1, (1, 1, "ALSA | JACK"), ("", "http://bristol.sf.net/vox.html") ], + [ "bristol", "VOX Continental 300", "Synth", "voxM2", "bristol_voxM2", TEMPLATE_NO, LEVEL_1, (1, 1, "ALSA | JACK"), ("", "http://bristol.sf.net/vox300.html") ], + [ "bristol", "Roland Juno-6", "Synth", "juno", "bristol_juno", TEMPLATE_NO, LEVEL_1, (1, 1, "ALSA | JACK"), ("", "http://bristol.sf.net/juno.html") ], + [ "bristol", "Roland Jupiter 8", "Synth", "jupiter8", "bristol_jupiter8", TEMPLATE_NO, LEVEL_1, (1, 1, "ALSA | JACK"), ("", "http://bristol.sf.net/jupiter8.html") ], + #[ "bristol", "Bristol BassMaker", "Synth", "bassmaker", "bristol_bassmaker", TEMPLATE_NO, LEVEL_1, (1, 1, "ALSA | JACK"), ("", "") ], + [ "bristol", "Yamaha DX", "Synth", "dx", "bristol_dx", TEMPLATE_NO, LEVEL_1, (1, 1, "ALSA | JACK"), ("", "http://bristol.sf.net/dx.html") ], + #[ "bristol", "Yamaha CS-80", "Synth", "cs80", "bristol_cs80", TEMPLATE_NO, LEVEL_1, (1, 1, "ALSA | JACK"), ("", "http://bristol.sf.net/cs80.html") ], + [ "bristol", "Bristol SID Softsynth", "Synth", "sidney", "bristol_sidney", TEMPLATE_NO, LEVEL_1, (1, 1, "ALSA | JACK"), ("", "http://bristol.sf.net/sidney.shtml") ], + #[ "bristol", "Commodore-64 SID polysynth", "Synth", "melbourne", "bristol_sidney", TEMPLATE_NO, LEVEL_1, (1, 1, "ALSA | JACK"), ("", "") ], #FIXME - needs icon + #[ "bristol", "Bristol Granular Synthesiser", "Synth", "granular", "bristol_granular", TEMPLATE_NO, LEVEL_1, (1, 1, "ALSA | JACK"), ("", "") ], + #[ "bristol", "Bristol Realtime Mixer", "Synth", "mixer", "bristol_mixer", TEMPLATE_NO, LEVEL_1, (1, 1, "ALSA | JACK"), ("", "http://bristol.sf.net/mixer.html") ], +] + +iBristol_Package, iBristol_AppName, iBristol_Type, iBristol_ShortName, iBristol_Icon, iBristol_Template, iBristol_Level, iBristol_Features, iBristol_Docs = range(0, len(list_Bristol[0])) + +# ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +# Effect + +# (S, MIDI-Mode) -> (Stereo, MIDI-Mode) + +list_Effect = [ + # Package AppName Type Binary Icon Template? Level (S, MIDI-Mode) (doc, website) + [ "ambdec", "AmbDec", "Ambisonic Decoder", "ambdec", "ambdec", TEMPLATE_NO, LEVEL_0, (1, "---"), ("", "http://kokkinizita.linuxaudio.org/linuxaudio/index.html") ], + + [ "guitarix", "Guitarix", "Guitar FX", "guitarix", "gx_head", TEMPLATE_NO, LEVEL_0, (0, "JACK"), ("", "http://guitarix.sf.net/") ], + + [ "fogpad-port", "FogPad", "Reverb", "fogpad", generic_audio_icon, TEMPLATE_NO, LEVEL_0, (0, "JACK"), ("", "https://github.com/linuxmao-org/fogpad-port") ], + + [ "freqtweak", "Freqtweak", "FFT-based realtime audio spectral manipulation", "freqtweak", "freqtweak_logo32x18", TEMPLATE_NO, LEVEL_0, (1, "---"), ("", "https://freqtweak.sourceforge.io/") ], + + [ "jamin", "Jamin", "Mastering", "jamin", "jamin", TEMPLATE_NO, LEVEL_0, (1, "---"), ("", "http://jamin.sf.net/") ], + + [ "lsp-plugins", "LSP comp-delay-mono", "Compressor", "lsp-plugins-comp-delay-mono", generic_audio_icon, TEMPLATE_NO, LEVEL_0, (0, "---"), ("", "https://lsp-plug.in/") ], + + [ "lsp-plugins", "LSP comp-delay-stereo", "Compressor", "lsp-plugins-comp-delay-stereo", generic_audio_icon, TEMPLATE_NO, LEVEL_0, (1, "---"), ("", "https://lsp-plug.in/") ], + + [ "lsp-plugins", "LSP comp-delay-x2-stereo", "Compressor", "lsp-plugins-comp-delay-x2-stereo", generic_audio_icon, TEMPLATE_NO, LEVEL_0, (1, "---"), ("", "https://lsp-plug.in/") ], + + [ "lsp-plugins", "LSP phase-detector", "Phase Detector", "lsp-plugins-phase-detector", generic_audio_icon, TEMPLATE_NO, LEVEL_0, (1, "---"), ("", "https://lsp-plug.in/") ], + + [ "dpf-plugins", "MVerb", "Reverb Effect", "MVerb", "mverb", TEMPLATE_NO, LEVEL_0, (1, "---"), ("", "https://distrho.sourceforge.io/ports.php") ], + + [ "paulstretch", "PaulStretch", "Extreme Stretch", "paulstretch", "paulstretch", TEMPLATE_NO, LEVEL_0, (1, "---"), ("", "http://hypermammut.sourceforge.net/paulstretch/") ], + + [ "radium-compressor", "Radium Compressor", "Compressor", "radium_compressor", "/usr/share/pixmaps/radium-compressor.xpm", TEMPLATE_NO, LEVEL_0, (1, "---"), ("", "http://users.notam02.no/~kjetism/radium/compressor_plugin.php") ], + + [ "rakarrack", "Rakarrack", "Guitar FX", "rakarrack", "icono_rakarrack_128x128", TEMPLATE_NO, LEVEL_0, (1, "ALSA + JACK"), ("file:///usr/share/doc/rakarrack/html/help.html", "http://rakarrack.sf.net") ], + + [ "tap-reverbed", "Reverbed", "Reverb", "reverbed", "reverbed", TEMPLATE_NO, LEVEL_0, (1, "---"), ("", "http://tap-plugins.sourceforge.net/reverbed.html") ], + + [ "snokoder", "SnoKoder", "Vocoder", "snokoder", "snokoder", TEMPLATE_NO, LEVEL_0, (0, "ALSA"), ("", "https://www.transformate.de/x/pawfaliki.php?page=DownLoads") ], + + [ "x42-plugins", "X42 - Darc", "Compressor", "x42-darc", "x42-darc", TEMPLATE_NO, LEVEL_0, (0, "---"), ("", "https://x42-plugins.com/x42/x42-compressor") ], + + [ "x42-plugins", "X42 - Fil4 mono", "4 Bands EQ", "x42-fil4", "x42-fil4", TEMPLATE_NO, LEVEL_0, (0, "---"), ("", "https://x42-plugins.com/x42/x42-eq") ], + + [ "x42-plugins", "X42 - Fil4 stéréo", "4 Bands EQ", "x42-fil4 1", "x42-fil4", TEMPLATE_NO, LEVEL_0, (1, "---"), ("", "https://x42-plugins.com/x42/x42-eq") ], + + [ "x42-plugins", "X42 - Fat1", "Auto-tuner", "x42-fat1", "x42-fat1", TEMPLATE_NO, LEVEL_0, (0, "---"), ("", "https://x42-plugins.com/x42/x42-autotune") ], + + [ "setbfree", "X42 - Whirl", "Leslie Emulator", "x42-whirl", "x42-whirl", TEMPLATE_NO, LEVEL_0, (0, "---"), ("", "https://x42-plugins.com/x42/x42-whirl") ], + + [ "zam-plugins", "ZamAutoSat", "Saturator", "ZamAutoSat", "zam", TEMPLATE_NO, LEVEL_0, (0, "---"), ("", "http://www.zamaudio.com/?p=976") ], + + [ "zam-plugins", "ZamComp", "Mono Compressor", "ZamComp", "zam", TEMPLATE_NO, LEVEL_0, (0, "---"), ("", "http://www.zamaudio.com/?p=976") ], + + [ "zam-plugins", "ZamCompX2", "Stereo Compressor", "ZamCompX2", "zam", TEMPLATE_NO, LEVEL_0, (1, "---"), ("", "http://www.zamaudio.com/?p=976") ], + + [ "zam-plugins", "ZamDelay", "Delay", "ZamDelay", "zam", TEMPLATE_NO, LEVEL_0, (0, "---"), ("", "http://www.zamaudio.com/?p=976") ], + + [ "zam-plugins", "ZamDynamicEQ", "Dynamic Mono EQ", "ZamDynamicEQ", "zam", TEMPLATE_NO, LEVEL_0, (0, "---"), ("", "http://www.zamaudio.com/?p=976") ], + + [ "zam-plugins", "ZamEQ2", "2 Bands EQ", "ZamEQ2", "zam", TEMPLATE_NO, LEVEL_0, (0, "---"), ("", "http://www.zamaudio.com/?p=976") ], + + [ "zam-plugins", "ZamGate", "Mono Gate", "ZamGate", "zam", TEMPLATE_NO, LEVEL_0, (0, "---"), ("", "http://www.zamaudio.com/?p=976") ], + + [ "zam-plugins", "ZamGateX2", "Stereo Gate", "ZamGateX2", "zam", TEMPLATE_NO, LEVEL_0, (1, "---"), ("", "http://www.zamaudio.com/?p=976") ], + + [ "zam-plugins", "ZamGEQ31", "31 Bands EQ", "ZamGEQ31", "zam", TEMPLATE_NO, LEVEL_0, (0, "---"), ("", "http://www.zamaudio.com/?p=976") ], + + [ "zam-plugins", "ZamHeadX2", "Stereo Enhancer", "ZamHeadX2", "zam", TEMPLATE_NO, LEVEL_0, (1, "---"), ("", "http://www.zamaudio.com/?p=976") ], + + [ "zam-plugins", "ZaMaximX2", "Maximiser", "ZaMaximX2", "zam", TEMPLATE_NO, LEVEL_0, (1, "---"), ("", "http://www.zamaudio.com/?p=976") ], + + [ "zam-plugins", "ZaMultiComp", "Mo Multi-Compressor", "ZaMultiComp", "zam", TEMPLATE_NO, LEVEL_0, (0, "---"), ("", "http://www.zamaudio.com/?p=976") ], + + [ "zam-plugins", "ZaMultiCompX2", "St Multi-Compressor", "ZaMultiCompX2", "zam", TEMPLATE_NO, LEVEL_0, (1, "---"), ("", "http://www.zamaudio.com/?p=976") ], + + [ "zam-plugins", "ZamPhono", "Phono Filters", "ZamPhono", "zam", TEMPLATE_NO, LEVEL_0, (0, "---"), ("", "http://www.zamaudio.com/?p=976") ], + + [ "zam-plugins", "ZamTube", "Tube Amp Emulator", "ZamTube", "zam", TEMPLATE_NO, LEVEL_0, (0, "JACK"), ("", "http://www.zamaudio.com/?p=976") ], + + [ "zam-plugins", "ZamVerb", "Reverb", "ZamVerb", "zam", TEMPLATE_NO, LEVEL_0, (1, "JACK"), ("", "http://www.zamaudio.com/?p=976") ], + + [ "zita-at1", "Zita-at1", "Auto-tuner", "zita-at1", "zita-at1", TEMPLATE_NO, LEVEL_0, (0, "JACK"), ("", "http://kokkinizita.linuxaudio.org/linuxaudio/index.html") ], + + [ "zita-rev1", "Zita-rev1", "Reverb", "zita-rev1", "zita-rev1", TEMPLATE_NO, LEVEL_0, (1, "JACK"), ("", "http://kokkinizita.linuxaudio.org/linuxaudio/index.html") ], + + [ "zita-rev1", "Zita-rev1", "Reverb - Ambisonic", "zita-rev1 -B", "zita-rev1", TEMPLATE_NO, LEVEL_0, (1, "JACK"), ("", "http://kokkinizita.linuxaudio.org/linuxaudio/index.html") ], + +] + +iEffect_Package, iEffect_AppName, iEffect_Type, iEffect_Binary, iEffect_Icon, iEffect_Template, iEffect_Level, iEffect_Features, iEffect_Docs = range(0, len(list_Effect[0])) + +# ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +# Tool + +# (MIDI-M, T) -> (MIDI-Mode, Transport) + +list_Tool = [ + # Package AppName Type Binary Icon Template? Level (MIDI-M, T) (doc, website) + [ "aliki", "Aliki (ALSA)", "Impulse Response Measurements Tool", "aliki -a", "aliki_32x32", TEMPLATE_NO, LEVEL_0, ("---", 0), ("", "kokkinizita.linuxaudio.org/linuxaudio/index.html") ], + + [ "aliki", "Aliki (JACK)", "Impulse Response Measurements Tool", "aliki -j", "aliki_32x32", TEMPLATE_NO, LEVEL_0, ("---", 0), ("", "kokkinizita.linuxaudio.org/linuxaudio/index.html") ], + + [ "arpage", "Arpage", "MIDI Arpeggiator", "arpage", "arpage", TEMPLATE_NO, LEVEL_0, ("JACK", 1), ("", "") ], + [ "arpage", "Zonage", "MIDI Mapper", "zonage", "zonage", TEMPLATE_NO, LEVEL_0, ("JACK", 0), ("", "") ], + + [ "audacity", "Audacity", "Audio Editor", "audacity", "audacity", TEMPLATE_NO, LEVEL_0, ("---", 0), ("", "https://www.audacityteam.org/") ], + + [ "bitmeter", "Bitmeter", "JACK Diagnotic Tool", "bitmeter", "bitmeter", TEMPLATE_NO, LEVEL_0, ("---", 0), ("", "") ], + + [ "cadence", "Cadence", "JACK Toolbox", "cadence", "cadence", TEMPLATE_NO, LEVEL_0, ("---", 0), ("", "") ], + [ "cadence-tools", "Cadence XY-Controller", "XY Controller", "cadence-xycontroller", "cadence", TEMPLATE_NO, LEVEL_0, ("JACK", 0), ("", "") ], + [ "catia", "Catia", "Patch Bay", "catia", "catia", TEMPLATE_NO, LEVEL_0, ("JACK", 1), ("", "") ], + [ "carla-control", "Carla OSC Control", "OSC Control", "carla-control", "carla-control", TEMPLATE_NO, LEVEL_0, ("JACK", 1), ("", "") ], + + [ "denemo", "Denemo", "Music Notation Editor", "denemo", "denemo", TEMPLATE_NO, LEVEL_0, ("---", 0), ("", "http://denemo.org/") ], + + [ "digitalscratch", "Digital-Scratch", "DJ with time-coded vinyls", "digitalscratch", "digitalscratch-icon_2decks", TEMPLATE_NO, LEVEL_0, ("---", 0), ("", "http://www.digital-scratch.org/") ], + + [ "drumstick-tools", "Drumstick Virtual Piano", "Virtual Keyboard", "drumstick-vpiano", "drumstick", TEMPLATE_NO, LEVEL_0, ("ALSA", 0), ("", "https://drumstick.sourceforge.io/") ], + + [ "easytag", "EasyTAG", "Audio File Metadata Management", "easytag", "easytag", TEMPLATE_NO, LEVEL_0, ("---", 0), ("", "https://wiki.gnome.org/Apps/EasyTAG") ], + + [ "etktab", "eTktab", "Music Notation Editor", "eTktab", "eTktab", TEMPLATE_NO, LEVEL_0, ("---", 0), ("", "http://etktab.sourceforge.net/") ], + + [ "ebumeter", "Ebumeter", "EBU-r128 Loudness Indicator", "ebumeter", "ebumeter32x32", TEMPLATE_NO, LEVEL_0, ("---", 0), ("", "http://kokkinizita.linuxaudio.org/linuxaudio/") ], + + [ "alsa-tools-gui", "Echomixer", "Echoaudio Mixer", "echomixer", "alsa-tools", TEMPLATE_NO, LEVEL_0, ("---", 0), ("", "") ], + + [ "elektroid", "Elektroid", "Soft for Elektron Devices", "elektroid", "elektroid", TEMPLATE_NO, LEVEL_0, ("---", 0), ("", "https://github.com/dagargo/elektroid/") ], + + [ "alsa-tools-gui", "Envy24Control", "Ice1712 Mixer", "envy24control", "alsa-tools", TEMPLATE_NO, LEVEL_0, ("---", 0), ("", "https://alsa.opensrc.org/Envy24control") ], + + [ "audio-recorder", "Audio-recorder", "Audio Recorder", "audio-recorder", "audio-recorder", TEMPLATE_NO, LEVEL_0, ("---", 0), ("", "https://launchpad.net/audio-recorder") ], + + [ "ffado-mixer-qt4", "FFADO-mixer", "FireWire Audio Interfaces Mixer", "ffado-mixer", "hi64-apps-ffado", TEMPLATE_NO, LEVEL_0, ("---", 0), ("", "http://ffado.org/") ], + + [ "fmit", "Music Instrument Tuner", "Instrument Tuner", "fmit", "fmit", TEMPLATE_NO, LEVEL_0, ("---", 0), ("", "http://gillesdegottex.github.io/fmit/") ], + + [ "frescobaldi", "Frescobaldi", "Music Notation Editor", "frescobaldi", "org.frescobaldi.Frescobaldi", TEMPLATE_NO, LEVEL_0, ("---", 0), ("", "https://www.frescobaldi.org/") ], + + [ "gdigi", "Gdigi", "Digitech Pedals Control", "gdigi", "gdigi", TEMPLATE_NO, LEVEL_0, ("---", 0), ("", "https://desowin.org/gdigi/") ], + + [ "gigedit", "Gigedit", "Instrument Editor", "gigedit", generic_audio_icon, TEMPLATE_NO, LEVEL_0, ("---", 0), ("file:///usr/share/doc/gigedit/gigedit_quickstart.html", "") ], + + [ "gjacktransport", "GJackClock", "Transport Tool", "gjackclock", "gjackclock", TEMPLATE_NO, LEVEL_0, ("---", 1), ("", "http://gjacktransport.sourceforge.net/") ], + [ "gjacktransport", "GJackTransport", "Transport Tool", "gjacktransport", "gjacktransport", TEMPLATE_NO, LEVEL_0, ("---", 1), ("", "http://gjacktransport.sourceforge.net/") ], + + [ "gladish", "Gladish", "LADISH Studio Manager", "gladish", "gladish", TEMPLATE_NO, LEVEL_0, ("JACK | ALSA", 0), ("", "http://ladish.org/") ], + + [ "dpf-plugins", "glBars", "Audio Visualizer", "glBars", "glbars", TEMPLATE_NO, LEVEL_0, ("JACK", 0), ("", "https://distrho.sourceforge.io/plugins.php") ], + + [ "gmidimonitor", "Gmidimonitor (ALSA)", "MIDI-ALSA Monitor", "gmidimonitor --alsa", "gmidimonitor_32x32", TEMPLATE_NO, LEVEL_0, ("JACK | ALSA", 0), ("", "") ], + [ "gmidimonitor", "Gmidimonitor (JACK)", "MIDI-JACK Monitor", "gmidimonitor --jack", "gmidimonitor_32x32", TEMPLATE_NO, LEVEL_0, ("JACK | ALSA", 0), ("", "") ], + + [ "gwc", "Gtk Wave Cleaner", "Audio File Cleaner", "gtk-wave-cleaner", "gtk-wave-cleaner", TEMPLATE_NO, LEVEL_0, ("---", 0), ("", "https://sourceforge.net/projects/gwc/") ], + + [ "gninjam", "Gtk NINJAM client", "Music Collaboration", "gninjam", "gninjam", TEMPLATE_NO, LEVEL_0, ("---", 1), ("", "") ], + + [ "gtklick", "Gtklick", "Metronome", "gtklick", "gtklick", TEMPLATE_NO, LEVEL_0, ("---", 0), ("", "http://das.nasophon.de/gtklick/") ], + + [ "gxtuner", "Gxtuner", "Instrument Tuner", "gxtuner", "gxtuner", TEMPLATE_NO, LEVEL_JS, ("---", 0), ("", "") ], + + [ "handbrake", "Handbrake", "DVD, Bluray, and other Medias Transcoder", "ghb", "hb-icon", TEMPLATE_NO, LEVEL_0, ("---", 0), ("", "https://handbrake.fr/") ], + + [ "alsa-tools-gui", "HdaJackRetask", "Intel HDA Control", "hdajackretask", "alsa-tools", TEMPLATE_NO, LEVEL_0, ("---", 0), ("", "") ], + [ "alsa-tools-gui", "HDSPconf", "HDSP Control", "hdspconf", "alsa-tools", TEMPLATE_NO, LEVEL_0, ("---", 0), ("", "") ], + [ "alsa-tools-gui", "HDSPmixer", "HDSP Mixer", "hdspmixer", "alsa-tools", TEMPLATE_NO, LEVEL_0, ("---", 0), ("", "") ], + + [ "jaaa", "Jaaa-ALSA", "Analyzes/Generates an Audio Signal", "jaaa -A", "jaaa", TEMPLATE_NO, LEVEL_0, ("---", 0), ("", "https://kokkinizita.linuxaudio.org/linuxaudio/index.html") ], + [ "jaaa", "Jaaa-JACK", "Analyzes/Generates an Audio Signal", "jaaa -J", "jaaa", TEMPLATE_NO, LEVEL_0, ("---", 0), ("", "https://kokkinizita.linuxaudio.org/linuxaudio/index.html") ], + + [ "jack-capture", "Jack-Capture-GUI", "JACK-Audio Capture GUI", "jack_capture_gui", "jack_capture_gui", TEMPLATE_NO, LEVEL_0, ("---", 0), ("", "https://github.com/kmatheussen/jack_capture") ], + + [ "jackeq", "JackEQ", "DJ Console GUI", "jackeq", "/usr/share/pixmaps/logo-jackeq-s.png", TEMPLATE_NO, LEVEL_0, ("---", 0), ("", "http://djcj.org/jackeq/") ], + + [ "jack-keyboard", "Jack Keyboard", "Virtual Keyboard", "jack-keyboard", "jack-keyboard", TEMPLATE_NO, LEVEL_0, ("JACK", 0), ("file:///usr/share/kxstudio/docs/jack-keyboard/manual.html", "http://jack-keyboard.sf.net/") ], + + [ "jack-mixer", "Jack Mixer", "Mixer", "jack_mixer", "jack_mixer", TEMPLATE_NO, LEVEL_NSM, ("JACK", 0), ("", "http://home.gna.org/jackmixer/") ], + + [ "jamulus", "Jamulus", "Music Collaboration", "jamulus", "jamulus", TEMPLATE_NO, LEVEL_0, ("---", 0), ("", "http://llcon.sourceforge.net/") ], + + [ "japa", "Japa-ALSA", "Audio Signal Analysis", "japa -A", "japa", TEMPLATE_NO, LEVEL_0, ("---", 0), ("", "http://kokkinizita.linuxaudio.org/linuxaudio/index.html") ], + [ "japa", "Japa-JACK", "Audio Signal Analysis", "japa -J", "japa", TEMPLATE_NO, LEVEL_0, ("---", 0), ("", "http://kokkinizita.linuxaudio.org/linuxaudio/index.html") ], + + [ "jkmeter", "Jkmeter", "Audio Level Measurement", "jkmeter", "jkmeter32x32", TEMPLATE_NO, LEVEL_0, ("---", 0), ("", "http://kokkinizita.linuxaudio.org/linuxaudio/index.html") ], + + [ "jmeters", "Jmeters", "Multi Canal Audio Level Measurement", "jmeters", "jmeters_32x32", TEMPLATE_NO, LEVEL_0, ("---", 0), ("", "http://kokkinizita.linuxaudio.org/linuxaudio/index.html") ], + +# [ "jnoise", "Jnoise", "White & Pink Noise Generation", "jnoise", generic_audio_icon, TEMPLATE_NO, LEVEL_0, ("---", 0), ("", "http://kokkinizita.linuxaudio.org/linuxaudio/index.html") ], + + [ "jnoisemeter", "Jnoisemeter", "Test Audio Signal Meter", "jnoisemeter", "jnoisemeter_32x32", TEMPLATE_NO, LEVEL_0, ("---", 0), ("", "http://librazik.tuxfamily.org/doc2/logiciels/jnoisemeter") ], + + [ "kmetronome", "KMetronome", "Metronome", "kmetronome", "kmetronome", TEMPLATE_NO, LEVEL_0, ("ALSA", 0), ("", "http://kmetronome.sf.net/kmetronome.shtml") ], + [ "kmidimon", "KMidimon", "Monitor", "kmidimon", "kmidimon", TEMPLATE_NO, LEVEL_0, ("ALSA", 0), ("", "http://kmidimon.sf.net/") ], + + [ "laditools", "LADI Log", "Log Viewer", "ladi-system-log", "ladi-system-log", TEMPLATE_NO, LEVEL_0, ("---", 0), ("", "") ], + [ "laditools", "LADI Tray", "Session Handler", "ladi-system-tray", "laditools", TEMPLATE_NO, LEVEL_0, ("---", 0), ("", "") ], + + [ "lenmus-all", "Lenmus", "Music Theory Studying", "lenmus", "lenmus", TEMPLATE_NO, LEVEL_0, ("---", 0), ("", "http://www.lenmus.org/en/noticias") ], + + [ "lingot", "Lingot", "Instrument Tuner", "lingot", "org.nongnu.lingot", TEMPLATE_NO, LEVEL_0, ("---", 0), ("", "http://www.nongnu.org/lingot/") ], + + [ "linux-show-player", "Linux Show Player", "Sample player", "linux-show-player", "linuxshowplayer", TEMPLATE_NO, LEVEL_0, ("JACK", 0), ("", "https://www.linux-show-player.org/") ], + + [ "lives", "LiVES", "VJ / Video Editor", "lives", "lives", TEMPLATE_NO, LEVEL_0, ("---", 1), ("", "http://lives.sf.net/") ], + + [ "luppp", "Luppp", "Audio Looper", "luppp", "luppp", TEMPLATE_NO, LEVEL_0, ("---", 0), ("", "http://openavproductions.com/luppp/") ], + + [ "mamba", "Mamba", "Virtual Keyboard", "mamba", "Mamba", TEMPLATE_NO, LEVEL_NSM, ("ALSA", 0), ("", "https://github.com/brummer10/Mamba") ], + + [ "mcpdisp", "MCP Disp", "Mackie Display Emulator", "mcpdisp", "mcpdisp", TEMPLATE_NO, LEVEL_0, ("---", 0), ("", "https://github.com/ovenwerks/mcpdisp") ], + + [ "meterbridge", "MeterBridge Classic VU", "VU / Peak Analyzer", "meterbridge -t vu :", "meterbridge32x32", TEMPLATE_NO, LEVEL_0, ("---", 0), ("", "http://plugin.org.uk/meterbridge/") ], + [ "meterbridge", "MeterBridge PPM Meter", "VU / Peak Analyzer", "meterbridge -t ppm :", "meterbridge32x32", TEMPLATE_NO, LEVEL_0, ("---", 0), ("", "http://plugin.org.uk/meterbridge/") ], + [ "meterbridge", "MeterBridge Digital Peak Meter", "VU / Peak Analyzer", "meterbridge -t dpm -c 2 : :", "meterbridge32x32", TEMPLATE_NO, LEVEL_0, ("---", 0), ("", "http://plugin.org.uk/meterbridge/") ], + [ "meterbridge", "MeterBridge 'Jellyfish' Phase Meter", "VU / Peak Analyzer", "meterbridge -t jf -c 2 : :", "meterbridge32x32", TEMPLATE_NO, LEVEL_0, ("---", 0), ("", "http://plugin.org.uk/meterbridge/") ], + [ "meterbridge", "MeterBridge Oscilloscope Meter", "VU / Peak Analyzer", "meterbridge -t sco :", "meterbridge32x32", TEMPLATE_NO, LEVEL_0, ("---", 0), ("", "http://plugin.org.uk/meterbridge/") ], + + [ "mhwaveedit", "MhWaveEdit", "Audio Editor", "mhwaveedit", "mhwaveedit", TEMPLATE_NO, LEVEL_0, ("---", 0), ("", "http://gna.org/projects/mhwaveedit/") ], + + [ "mixxx", "Mixxx", "DJ", "mixxx", "mixxx_icon", TEMPLATE_NO, LEVEL_0, ("ALSA", 0), ("", "http://mixxx.sf.net/") ], + + [ "mpk-m2-editor", "MPK M2 Editor", "Editor for MPK M2", "mpk_m2-editor", "mpk-m2-editor", TEMPLATE_NO, LEVEL_0, ("---", 0), ("", "https://github.com/PiOverFour/MPK-M2-editor") ], + + [ "mudita24", "Mudita24", "Ice1712 Mixer", "mudita24", "mudita24", TEMPLATE_NO, LEVEL_0, ("---", 0), ("", "https://github.com/NielsMayer/mudita24") ], + + [ "nano-basket", "Nano-Basket", "Nano Hardware Management", "nano-basket", "nano-basket", TEMPLATE_NO, LEVEL_0, ("---", 0), ("", "https://github.com/royvegard/Nano-Basket") ], + + [ "non-mixer", "Non-Mixer", "Mixer", "non-mixer", "non-mixer", TEMPLATE_NO, LEVEL_0, ("CV", 0), ("file:///usr/share/doc/non-mixer/MANUAL.html", "http://non.tuxfamily.org/wiki/Non%20Mixer") ], + + [ "nootka", "Nootka", "Learn music notation on sheet music", "nootka", "nootka", TEMPLATE_NO, LEVEL_0, ("---", 0), ("", "https://nootka.sourceforge.io/") ], + + [ "patchage", "Patchage", "Patch Bay", "patchage", "patchage", TEMPLATE_NO, LEVEL_0, ("ALSA + JACK", 0), ("", "http://drobilla.net/blog/software/patchage/") ], + + [ "pd-l2ork", "Pd-L2Ork", "Pure-Data Environnement", "pd-l2ork -rt -audiobuf 20 -inchannels 2 -outchannels 2 -alsamidi -mididev 0 %U", "pd-l2ork", TEMPLATE_NO, LEVEL_0, ("ALSA", 0), ("", "http://l2ork.music.vt.edu/main/make-your-own-l2ork/software/") ], + + [ "pianobooster", "Piano Booster", "Piano Teacher", "pianobooster", "pianobooster", TEMPLATE_NO, LEVEL_0, ("ALSA", 0), ("", "http://pianobooster.sourceforge.net/") ], + + [ "playitslowly", "Play it Slowly", "Strech-Player", "playitslowly", "ch.x29a.playitslowly", TEMPLATE_NO, LEVEL_0, ("---", 0), ("", "https://29a.ch/playitslowly/") ], + + [ "dpf-plugins", "ProM", "Music Visualizer", "ProM", "prom", TEMPLATE_NO, LEVEL_0, ("JACK", 0), ("", "https://distrho.sourceforge.io/plugins.php") ], + + [ "qamix", "QAMix", "Mixer", "qamix", "qamix", TEMPLATE_NO, LEVEL_0, ("ALSA", 0), ("", "") ], + [ "qarecord", "QARecord", "Recorder", "qarecord --jack", "qarecord_48", TEMPLATE_NO, LEVEL_0, ("ALSA", 0), ("", "") ], + + [ "qjackctl", "QJackControl", "JACK Control", "qjackctl", "qjackctl", TEMPLATE_NO, LEVEL_0, ("ALSA + JACK", 1), ("", "https://qjackctl.sourceforge.io/") ], + + [ "qlcplus", "QLCplus", "Light-Show Controller", "qlcplus", "qlcplus", TEMPLATE_NO, LEVEL_0, ("ALSA", 0), ("", "https://www.qlcplus.org/") ], + + [ "qlcplus", "Fixture editor for QLC+", "Fixture editor for QLC+", "qlcplus-fixtureeditor", "qlcplus-fixtureeditor", TEMPLATE_NO, LEVEL_0, ("---", 0), ("", "https://www.qlcplus.org/") ], + + [ "qloud", "Qloud", "Audio measurement tool for JACK", "qloud", "qloud", TEMPLATE_NO, LEVEL_0, ("---", 0), ("", "http://gaydenko.com/qloud/") ], + + [ "qmidiarp", "QMidiArp", "MIDI Arpeggiator", "qmidiarp", "qmidiarp", TEMPLATE_NO, LEVEL_0, ("ALSA + JACK", 0), ("", "http://qmidiarp.sourceforge.net/") ], + + [ "qmidiroute", "QMidiRoute", "MIDI router & event processor", "qmidiroute", "/usr/share/pixmaps/qmidiroute_32x32.xpm", TEMPLATE_NO, LEVEL_0, ("ALSA", 0), ("", "") ], + + [ "qmidictl", "QmidiCtl", "MIDI controller over the network", "qmidictl", "qmidictl", TEMPLATE_NO, LEVEL_0, ("---", 0), ("", "https://qmidictl.sourceforge.io/") ], + + [ "qmidinet", "QmidiNet", "MIDI Network Gateway", "qmidinet", "qmidinet", TEMPLATE_NO, LEVEL_0, ("---", 0), ("", "https://qmidinet.sourceforge.io/") ], + + [ "qrest", "Qrest", "Calculator for delay, LFO,...", "qrest", "qrest", TEMPLATE_NO, LEVEL_0, ("---", 0), ("", "") ], + + [ "qxgedit", "QXGedit", "XG HardWare Controler", "qxgedit", "qxgedit", TEMPLATE_NO, LEVEL_0, ("---", 0), ("", "https://qxgedit.sourceforge.io/") ], + + [ "raysession", "Ray Session", "NSM Session Manager", "raysession", "raysession", TEMPLATE_NO, LEVEL_0, ("---", 0), ("", "https://github.com/Houston4444/RaySession") ], + + [ "recjack", "RecJack", "Recorder", "recjack", "recjack", TEMPLATE_NO, LEVEL_0, ("---", 1), ("", "http://mein-neues-blog.de/2015/02/07/mein-neues-blog-deb-repository/#recjack") ], + + [ "rezound", "ReZound", "Audio Editor", "rezound", "rezound32", TEMPLATE_NO, LEVEL_0, ("---", 0), ("", "http://rezound.sourceforge.net/") ], + + [ "alsa-tools-gui", "RME Digi Control", "RME Digi Mixer", "rmedigicontrol", "alsa-tools", TEMPLATE_NO, LEVEL_0, ("---", 0), ("", "") ], + + [ "showq", "ShowQ", "Sampler Player", "showq", "showq_32x32", TEMPLATE_NO, LEVEL_0, ("ALSA", 0), ("", "") ], + + [ "shuriken", "Shuriken", "Beat Slicer", "shuriken", "shuriken", TEMPLATE_NO, LEVEL_1, ("JACK+ALSA", 1), ("", "https://rock-hopper.github.io/shuriken/") ], + + [ "simplescreenrecorder", "Simple Screen Recorder", "ScreenCast Recorder", "simplescreenrecorder", "simplescreenrecorder", TEMPLATE_NO, LEVEL_0, ("---", 0), ("", "https://www.maartenbaert.be/simplescreenrecorder/") ], + + [ "smplayer", "Smplayer", "Multimédia Player", "smplayer", "smplayer", TEMPLATE_NO, LEVEL_0, ("---", 0), ("", "https://www.smplayer.info/") ], + + [ "solfege", "Solfege", "Ear Training", "solfege", "solfege", TEMPLATE_NO, LEVEL_0, ("---", 0), ("", "https://www.gnu.org/software/solfege/") ], + + [ "songwrite", "Songwrite 3", "Guitar tablature reader and editor", "songwrite", "songwrite3", TEMPLATE_NO, LEVEL_0, ("---", 0), ("", "http://www.lesfleursdunormal.fr/static/informatique/songwrite/index_en.html") ], + + [ "sonic-visualiser", "Sonic Visualiser", "Signal Visualization and Analysis", "sonic-visualiser", "sv-icon", TEMPLATE_NO, LEVEL_0, ("JACK", 0), ("", "https://sonicvisualiser.org/") ], + + [ "spek", "Spek", "Spectrum Analyzer", "spek", "spek", TEMPLATE_NO, LEVEL_0, ("---", 0), ("", "http://spek.cc/") ], + + [ "stretchplayer", "Stretch Player", "Stretch Player", "stretchplayer", "stretchplayer", TEMPLATE_NO, LEVEL_0, ("---", 0), ("", "https://www.teuton.org/~gabriel/stretchplayer/") ], + + [ "swami", "Swami", "SF2 instruments editor", "swami", "swami", TEMPLATE_NO, LEVEL_0, ("ALSA", 0), ("", "http://www.swamiproject.org/") ], + + [ "tinyeartrainer", "Tiny Ear Trainer", "Ear Training", "tinyeartrainer", "tinyeartrainer", TEMPLATE_NO, LEVEL_0, ("---", 0), ("", "https://29a.ch/tinyeartrainer/") ], + + [ "timemachine", "TimeMachine", "Recorder", "timemachine", "/usr/share/timemachine/pixmaps/timemachine-icon.png", TEMPLATE_NO, LEVEL_0, ("---", 0), ("", "http://plugin.org.uk/timemachine/") ], + + [ "tmlauncher", "TM Launcher", "Recorder", "TMLauncher.py", "TMLauncher", TEMPLATE_NO, LEVEL_0, ("---", 0), ("", "") ], + + [ "tuxguitar", "TuxGuitar", "Guitar Score", "tuxguitar", "tuxguitar", TEMPLATE_NO, LEVEL_0, ("---", 0), ("", "http://www.tuxguitar.com.ar/") ], + + [ "vkeybd", "Virtual Keyboard", "Virtual Keyboard", "vkeybd", "vkeybd", TEMPLATE_NO, LEVEL_0, ("ALSA", 0), ("", "https://github.com/tiwai/vkeybd") ], + + [ "vmpk", "Virtual MIDI Piano Keyboard (ALSA)","Virtual Keyboard", "vmpk", "vmpk", TEMPLATE_NO, LEVEL_0, ("ALSA", 0), ("file:///usr/share/doc/vmpk/help.html", "http://vmpk.sf.net/") ], + [ "vmpk-jack", "Virtual MIDI Piano Keyboard (JACK)","Virtual Keyboard", "vmpk-jack", "vmpk", TEMPLATE_NO, LEVEL_0, ("JACK", 0), ("file:///usr/share/doc/vmpk/help.html", "http://vmpk.sf.net/") ], + + [ "x42-plugins", "X42 - StepSeq", "Step By Step Sequencer", "x42-stepseq", "x42-stepseq", TEMPLATE_NO, LEVEL_0, ("JACK", 0), ("", "https://x42-plugins.com/x42/x42-stepseq-8x8") ], + + [ "x42-plugins", "X42-Meter - EBU R128", "Meter", "x42-meter", "x42-meters", TEMPLATE_NO, LEVEL_0, ("---", 0), ("", "https://x42-plugins.com/x42/x42-meters") ], + + [ "x42-plugins", "X42-Meter - K20", "Meter", "x42-meter 1", "x42-meters", TEMPLATE_NO, LEVEL_0, ("---", 0), ("", "https://x42-plugins.com/x42/x42-meters") ], + + [ "x42-plugins", "X42-Meter - K14", "Meter", "x42-meter 2", "x42-meters", TEMPLATE_NO, LEVEL_0, ("---", 0), ("", "https://x42-plugins.com/x42/x42-meters") ], + + [ "x42-plugins", "X42-Meter - K12", "Meter", "x42-meter 3", "x42-meters", TEMPLATE_NO, LEVEL_0, ("---", 0), ("", "https://x42-plugins.com/x42/x42-meters") ], + + [ "x42-plugins", "X42-Meter - BBC", "Meter", "x42-meter 4", "x42-meters", TEMPLATE_NO, LEVEL_0, ("---", 0), ("", "https://x42-plugins.com/x42/x42-meters") ], + + [ "x42-plugins", "X42-Meter - BBC M-6", "Meter", "x42-meter 5", "x42-meters", TEMPLATE_NO, LEVEL_0, ("---", 0), ("", "https://x42-plugins.com/x42/x42-meters") ], + + [ "x42-plugins", "X42-Meter - DIN", "Meter", "x42-meter 6", "x42-meters", TEMPLATE_NO, LEVEL_0, ("---", 0), ("", "https://x42-plugins.com/x42/x42-meters") ], + + [ "x42-plugins", "X42-Meter - EBU", "Meter", "x42-meter 7", "x42-meters", TEMPLATE_NO, LEVEL_0, ("---", 0), ("", "https://x42-plugins.com/x42/x42-meters") ], + + [ "x42-plugins", "X42-Meter - Nordic", "Meter", "x42-meter 8", "x42-meters", TEMPLATE_NO, LEVEL_0, ("---", 0), ("", "https://x42-plugins.com/x42/x42-meters") ], + + [ "x42-plugins", "X42-Meter - VU", "Meter", "x42-meter 9", "x42-meters", TEMPLATE_NO, LEVEL_0, ("---", 0), ("", "https://x42-plugins.com/x42/x42-meters") ], + + [ "x42-plugins", "X42-Meter - True Peak et RMS", "Meter", "x42-meter 10", "x42-meters", TEMPLATE_NO, LEVEL_0, ("---", 0), ("", "https://x42-plugins.com/x42/x42-meters") ], + + [ "x42-plugins", "X42-Meter - Dynamic Range", "Meter", "x42-meter 11", "x42-meters", TEMPLATE_NO, LEVEL_0, ("---", 0), ("", "https://x42-plugins.com/x42/x42-meters") ], + + [ "x42-plugins", "X42-Meter - Phase Correlation ", "Meter", "x42-meter 12", "x42-meters", TEMPLATE_NO, LEVEL_0, ("---", 0), ("", "https://x42-plugins.com/x42/x42-meters") ], + + [ "x42-plugins", "X42-Meter - Goniometer", "Meter", "x42-meter 13", "x42-meters", TEMPLATE_NO, LEVEL_0, ("---", 0), ("", "https://x42-plugins.com/x42/x42-meters") ], + + [ "x42-plugins", "X42-Meter - Phase Frequency Wheel", "Meter", "x42-meter 14", "x42-meters", TEMPLATE_NO, LEVEL_0, ("---", 0), ("", "https://x42-plugins.com/x42/x42-meters") ], + + [ "x42-plugins", "X42-Meter - 1/3 Octave Spectrum", "Meter", "x42-meter 15", "x42-meters", TEMPLATE_NO, LEVEL_0, ("---", 0), ("", "https://x42-plugins.com/x42/x42-meters") ], + + [ "x42-plugins", "X42-Meter - Frequency Scope", "Meter", "x42-meter 16", "x42-meters", TEMPLATE_NO, LEVEL_0, ("---", 0), ("", "https://x42-plugins.com/x42/x42-meters") ], + + [ "x42-plugins", "X42-Meter - Distribution Histogram", "Meter", "x42-meter 17", "x42-meters", TEMPLATE_NO, LEVEL_0, ("---", 0), ("", "https://x42-plugins.com/x42/x42-meters") ], + + [ "x42-plugins", "X42-Meter - Bitmeter", "Meter", "x42-meter 18", "x42-meters", TEMPLATE_NO, LEVEL_0, ("---", 0), ("", "https://x42-plugins.com/x42/x42-meters") ], + + [ "x42-plugins", "X42-Meter - Surround Analysor", "Meter", "x42-meter 19", "x42-meters", TEMPLATE_NO, LEVEL_0, ("---", 0), ("", "https://x42-plugins.com/x42/x42-meters") ], + + [ "x42-plugins", "X42 - Mixtri", "Mixer/Trigger Preprocessor", "x42-mixtri", "x42-mixtri", TEMPLATE_NO, LEVEL_0, ("---", 0), ("", "https://x42-plugins.com/x42/x42-mixtrix") ], + + [ "x42-plugins", "X42 - Oscilloscope", "Oscilloscope", "x42-scope", "x42-scope", TEMPLATE_NO, LEVEL_0, ("---", 0), ("", "https://x42-plugins.com/x42/x42-scope") ], + + [ "x42-plugins", "X42 - Tuna", "Instrument Tuner", "x42-tuna", "x42-tuna", TEMPLATE_NO, LEVEL_0, ("---", 0), ("", "https://x42-plugins.com/x42/x42-tuner") ], + + [ "x42-plugins", "X42 - Tuna - spectral", "Instrument Tuner - spectrum", "x42-tuna 1", "x42-tuna", TEMPLATE_NO, LEVEL_0, ("---", 0), ("", "https://x42-plugins.com/x42/x42-tuner") ], + + [ "xjadeo", "XJadeo", "Video Player", "qjadeo", "qjadeo", TEMPLATE_NO, LEVEL_0, ("---", 1), ("", "http://xjadeo.sf.net/") ], + + [ "zita-bls1", "Zita-bls1", "Binaural -> stereo signal converter", "zita-bls1", "zita-bls1", TEMPLATE_NO, LEVEL_0, ("---", 0), ("", "http://kokkinizita.linuxaudio.org/linuxaudio/index.html") ], + + [ "zita-mu1", "Zita-mu1", "Stereo Monitoring Organizer", "zita-mu1", "zita-mu1", TEMPLATE_NO, LEVEL_0, ("---", 0), ("", "http://kokkinizita.linuxaudio.org/linuxaudio/index.html") ], + +] + +iTool_Package, iTool_AppName, iTool_Type, iTool_Binary, iTool_Icon, iTool_Template, iTool_Level, iTool_Features, iTool_Docs = range(0, len(list_Tool[0])) + +if USING_KXSTUDIO: + # Non-Mixer + list_Tool[10][iDAW_Level] = LEVEL_1 diff --git a/src/claudia_launcher.py b/src/claudia_launcher.py new file mode 100755 index 0000000..05e2ba6 --- /dev/null +++ b/src/claudia_launcher.py @@ -0,0 +1,1383 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +# ... TODO +# Copyright (C) 2010-2018 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 random import randint + +if True: + from PyQt5.QtCore import pyqtSlot, Qt, QTimer, QSettings + from PyQt5.QtWidgets import QMainWindow, QTableWidgetItem, QWidget +else: + from PyQt4.QtCore import pyqtSlot, Qt, QTimer, QSettings + from PyQt4.QtGui import QMainWindow, QTableWidgetItem, QWidget + +# ------------------------------------------------------------------------------------------------------------ +# Imports (Custom) + +import claudia_database as database +import ui_claudia_launcher +from shared import * +from shared_i18n import * + +# ------------------------------------------------------------------------------------------------------------ +# Imports (Carla) + +try: + from carla_utils import * + haveCarla = True +except: + haveCarla = False + +# ------------------------------------------------------------------------------------------------------------ +# Safe import getoutput + +if sys.version_info >= (3, 0): + from subprocess import getoutput +else: + from commands import getoutput + +# ------------------------------------------------------------------------------------------------------------ +# Debug Mode + +SHOW_ALL = False + +# ------------------------------------------------------------------------------------------------------------ +# Tab Indexes + +iTabDAW = 0 +iTabHost = 1 +iTabInstrument = 2 +iTabBristol = 3 +iTabPlugin = 4 +iTabEffect = 5 +iTabTool = 6 + +EXTRA_ICON_PATHS = [ + "/usr/share/icons", + "/usr/share/pixmaps", + "/usr/local/share/pixmaps" +] + +# ------------------------------------------------------------------------------------------------------------ +# XIcon class + +class XIcon(object): + def __init__(self): + object.__init__(self) + + def addIconPath(self, path): + iconPaths = QIcon.themeSearchPaths() + iconPaths.append(path) + QIcon.setThemeSearchPaths(iconPaths) + + def getIcon(self, name): + if os.path.exists(name): + icon = QIcon(name) + else: + icon = QIcon.fromTheme(name) + + if icon.isNull(): + for iEXTRA_PATH in EXTRA_ICON_PATHS: + if os.path.exists(os.path.join(iEXTRA_PATH, name + ".png")): + icon = QIcon(os.path.join(iEXTRA_PATH, name + ".png")) + break + elif os.path.exists(os.path.join(iEXTRA_PATH, name + ".svg")): + icon = QIcon(os.path.join(iEXTRA_PATH, name + ".svg")) + break + elif os.path.exists(os.path.join(iEXTRA_PATH, name + ".xpm")): + icon = QIcon(os.path.join(iEXTRA_PATH, name + ".xpm")) + break + else: + print("XIcon::getIcon(%s) - Failed to find icon" % name) + + return icon + +# ------------------------------------------------------------------------------------------------------------ +# Launcher object + +class ClaudiaLauncher(QWidget, ui_claudia_launcher.Ui_ClaudiaLauncherW): + def __init__(self, parent): + QWidget.__init__(self, parent) + self.setupUi(self) + + self._parent = None + self._settings = None + self.m_ladish_only = False + + self.listDAW.setColumnWidth(0, 22) + self.listDAW.setColumnWidth(1, 225) + self.listDAW.setColumnWidth(2, 150) + self.listHost.setColumnWidth(0, 22) + self.listHost.setColumnWidth(1, 225) + self.listHost.setColumnWidth(2, 100) + self.listHost.setColumnWidth(3, 100) + self.listInstrument.setColumnWidth(0, 22) + self.listInstrument.setColumnWidth(1, 225) + self.listInstrument.setColumnWidth(2, 150) + self.listBristol.setColumnWidth(0, 22) + self.listBristol.setColumnWidth(1, 150) + self.listPlugin.setColumnWidth(0, 225) + self.listPlugin.setColumnWidth(1, 175) + self.listEffect.setColumnWidth(0, 22) + self.listEffect.setColumnWidth(1, 225) + self.listEffect.setColumnWidth(2, 150) + self.listTool.setColumnWidth(0, 22) + self.listTool.setColumnWidth(1, 225) + self.listTool.setColumnWidth(2, 150) + + # For the custom icons + self.ClaudiaIcons = XIcon() + + self.icon_yes = QIcon(self.getIcon("dialog-ok-apply")) + self.icon_no = QIcon(self.getIcon("dialog-cancel")) + + self.m_lastThemeName = QIcon.themeName() + + # Copy our icons, so we can then set the fallback icon theme as the current theme + iconPath = os.path.join(TMP, ".claudia-icons") + + if not os.path.exists(iconPath): + os.mkdir(iconPath) + + syspath = sys.path[0] + if os.path.exists(os.path.join(syspath, "..", "icons")): + os.system("cp -r '%s' '%s'" % (os.path.join(syspath, "..", "icons", "claudia-hicolor"), iconPath)) + elif os.path.exists(os.path.join(syspath, "..", "data", "icons")): + os.system("cp -r '%s' '%s'" % (os.path.join(syspath, "..", "data", "icons", "claudia-hicolor"), iconPath)) + + os.system("sed -i 's/X-CURRENT-THEME-X/%s/' '%s'" % (self.m_lastThemeName, os.path.join(iconPath, "claudia-hicolor", "index.theme"))) + + self.ClaudiaIcons.addIconPath(iconPath) + QIcon.setThemeName("claudia-hicolor") + + self.clearInfo_DAW() + self.clearInfo_Host() + self.clearInfo_Intrument() + self.clearInfo_Bristol() + self.clearInfo_Plugin() + self.clearInfo_Effect() + self.clearInfo_Tool() + + self.refreshAll() + + self.tabWidget.currentChanged.connect(self.slot_checkSelectedTab) + + self.listDAW.currentCellChanged.connect(self.slot_checkSelectedDAW) + self.listHost.currentCellChanged.connect(self.slot_checkSelectedHost) + self.listInstrument.currentCellChanged.connect(self.slot_checkSelectedInstrument) + self.listBristol.currentCellChanged.connect(self.slot_checkSelectedBristol) + self.listPlugin.currentCellChanged.connect(self.slot_checkSelectedPlugin) + self.listEffect.currentCellChanged.connect(self.slot_checkSelectedEffect) + self.listTool.currentCellChanged.connect(self.slot_checkSelectedTool) + self.listDAW.cellDoubleClicked.connect(self.slot_doubleClickedList) + self.listHost.cellDoubleClicked.connect(self.slot_doubleClickedList) + self.listInstrument.cellDoubleClicked.connect(self.slot_doubleClickedList) + self.listBristol.cellDoubleClicked.connect(self.slot_doubleClickedList) + self.listPlugin.cellDoubleClicked.connect(self.slot_doubleClickedList) + self.listEffect.cellDoubleClicked.connect(self.slot_doubleClickedList) + self.listTool.cellDoubleClicked.connect(self.slot_doubleClickedList) + + def getSelectedAppAndBinary(self): + tabIndex = self.tabWidget.currentIndex() + + if tabIndex == iTabDAW: + item = self.listDAW.item(self.listDAW.currentRow(), 0).data(Qt.UserRole) + return (item[database.iDAW_AppName], item[database.iDAW_Binary]) + + if tabIndex == iTabHost: + item = self.listHost.item(self.listHost.currentRow(), 0).data(Qt.UserRole) + return (item[database.iHost_AppName], item[database.iHost_Binary]) + + if tabIndex == iTabInstrument: + item = self.listInstrument.item(self.listInstrument.currentRow(), 0).data(Qt.UserRole) + return (item[database.iInstrument_AppName], item[database.iInstrument_Binary]) + + if tabIndex == iTabBristol: + item = self.listBristol.item(self.listBristol.currentRow(), 0).data(Qt.UserRole) + return (item[database.iBristol_AppName], "startBristol -audio jack -midi jack -%s" % item[database.iBristol_ShortName]) + + if tabIndex == iTabPlugin: + plugin = self.listPlugin.item(self.listPlugin.currentRow(), 0).data(Qt.UserRole) + return (plugin["name"], "carla-single lv2 %s" % plugin["label"]) + + if tabIndex == iTabEffect: + item = self.listEffect.item(self.listEffect.currentRow(), 0).data(Qt.UserRole) + return (item[database.iEffect_AppName], item[database.iEffect_Binary]) + + if tabIndex == iTabTool: + item = self.listTool.item(self.listTool.currentRow(), 0).data(Qt.UserRole) + return (item[database.iTool_AppName], item[database.iTool_Binary]) + + return ("", "") + + def startApp(self): + app, binary = self.getSelectedAppAndBinary() + + if not (app and binary): + return + + os.system("cd '%s' && %s &" % (self.callback_getProjectFolder(), binary)) + + def addAppToLADISH(self): + app, binary = self.getSelectedAppAndBinary() + + if not (app and binary): + return + + if binary.startswith("startBristol") or binary.startswith("carla-single"): + self.createAppTemplate(app, binary) + + elif binary in ("ardour", + "ardour3", + "hydrogen", + "jacker", + "lmms", + "muse", + "non-sequencer", + "non-timeline", + "qtractor", + "rosegarden", + "seq24", + "calfjackhost", + "carla", + "jack-rack", + "qsampler", + "jack-mixer"): + self.createAppTemplate(app, binary) + + else: + appBus = self.callback_getAppBus() + appBus.RunCustom2(False, binary, app, "0") + + def createAppTemplate(self, app, binary): + rand_check = randint(1, 99999) + proj_bpm = str(self.callback_getBPM()) + proj_srate = str(self.callback_getSampleRate()) + proj_folder = self.callback_getProjectFolder() + + tmplte_dir = None + tmplte_file = None + tmplte_cmd = "" + tmplte_lvl = "0" + + syspath = sys.path[0] + if os.path.exists(os.path.join(syspath, "..", "templates")): + tmplte_dir = os.path.join(syspath, "..", "templates") + elif os.path.exists(os.path.join(syspath, "..", "data", "templates")): + tmplte_dir = os.path.join(syspath, "..", "data", "templates") + else: + app = None + tmplte_cmd = binary + print("ClaudiaLauncher::createAppTemplate() - Failed to find template dir") + return False + + if not os.path.exists(proj_folder): + os.mkdir(proj_folder) + + if binary.startswith("startBristol"): + module = binary.replace("startBristol -audio jack -midi jack -", "") + tmplte_folder = os.path.join(proj_folder, "bristol_%s_%i" % (module, rand_check)) + os.mkdir(tmplte_folder) + + if self.callback_isLadishRoom(): + tmplte_folder = os.path.basename(tmplte_folder) + + tmplte_cmd = binary + tmplte_cmd += " -emulate %s" % module + tmplte_cmd += " -cache '%s'" % tmplte_folder + tmplte_cmd += " -memdump '%s'" % tmplte_folder + tmplte_cmd += " -import '%s'" % os.path.join(tmplte_folder, "memory") + tmplte_cmd += " -exec" + tmplte_lvl = "1" + + elif binary.startswith("carla-single"): + tmplte_cmd = binary + " " + proj_folder + tmplte_lvl = "1" + + elif binary == "ardour": + tmplte_folder = os.path.join(proj_folder, "Ardour2_%i" % rand_check) + tmplte_file = os.path.join(tmplte_folder, "Ardour2_%i.ardour" % rand_check) + os.mkdir(tmplte_folder) + + os.system("cp '%s' '%s'" % (os.path.join(tmplte_dir, "Ardour2", "Ardour2.ardour"), tmplte_file)) + os.system("cp '%s' '%s'" % (os.path.join(tmplte_dir, "Ardour2", "instant.xml"), tmplte_folder)) + os.mkdir(os.path.join(tmplte_folder, "analysis")) + os.mkdir(os.path.join(tmplte_folder, "dead_sounds")) + os.mkdir(os.path.join(tmplte_folder, "export")) + os.mkdir(os.path.join(tmplte_folder, "interchange")) + os.mkdir(os.path.join(tmplte_folder, "interchange", "Ardour")) + os.mkdir(os.path.join(tmplte_folder, "interchange", "Ardour", "audiofiles")) + os.mkdir(os.path.join(tmplte_folder, "peaks")) + + tmplte_cmd = binary + tmplte_cmd += " '%s'" % (os.path.basename(tmplte_folder) if self.callback_isLadishRoom() else tmplte_folder) + + elif binary == "ardour3": + projName = "Ardour3_%i" % rand_check + tmplte_folder = os.path.join(proj_folder, projName) + tmplte_file = os.path.join(tmplte_folder, "%s.ardour" % projName) + os.mkdir(tmplte_folder) + + os.system("cp '%s' '%s'" % (os.path.join(tmplte_dir, "Ardour3", "Ardour3.ardour"), tmplte_file)) + os.system("cp '%s' '%s'" % (os.path.join(tmplte_dir, "Ardour3", "instant.xml"), tmplte_folder)) + os.mkdir(os.path.join(tmplte_folder, "analysis")) + os.mkdir(os.path.join(tmplte_folder, "dead")) + os.mkdir(os.path.join(tmplte_folder, "export")) + os.mkdir(os.path.join(tmplte_folder, "externals")) + os.mkdir(os.path.join(tmplte_folder, "interchange")) + os.mkdir(os.path.join(tmplte_folder, "interchange", projName)) + os.mkdir(os.path.join(tmplte_folder, "interchange", projName, "audiofiles")) + os.mkdir(os.path.join(tmplte_folder, "interchange", projName, "midifiles")) + os.mkdir(os.path.join(tmplte_folder, "peaks")) + os.mkdir(os.path.join(tmplte_folder, "plugins")) + + tmplte_cmd = binary + tmplte_cmd += " '%s'" % (os.path.basename(tmplte_folder) if self.callback_isLadishRoom() else tmplte_folder) + + if self.callback_isLadishRoom(): + tmplte_lvl = "jacksession" + + elif binary == "hydrogen": + tmplte_file = os.path.join(proj_folder, "Hydrogen_%i.h2song" % rand_check) + + os.system("cp '%s' '%s'" % (os.path.join(tmplte_dir, "Hydrogen.h2song"), tmplte_file)) + + tmplte_cmd = binary + tmplte_cmd += " -s '%s'" % (os.path.basename(tmplte_file) if self.callback_isLadishRoom() else tmplte_file) + + if self.callback_isLadishRoom(): + tmplte_lvl = "jacksession" + else: + tmplte_lvl = "1" + + elif binary == "jacker": + tmplte_file = os.path.join(proj_folder, "Jacker_%i.jsong" % rand_check) + + os.system("cp '%s' '%s'" % (os.path.join(tmplte_dir, "Jacker.jsong"), tmplte_file)) + + tmplte_cmd = binary + tmplte_cmd += " '%s'" % (os.path.basename(tmplte_file) if self.callback_isLadishRoom() else tmplte_file) + + if database.USING_KXSTUDIO: + tmplte_lvl = "1" + + # No decimal bpm support + proj_bpm = proj_bpm.split(".")[0] + + elif binary == "lmms": + tmplte_file = os.path.join(proj_folder, "LMMS_%i.mmp" % rand_check) + + os.system("cp '%s' '%s'" % (os.path.join(tmplte_dir, "LMMS.mmp"), tmplte_file)) + + tmplte_cmd = binary + tmplte_cmd += " '%s'" % (os.path.basename(tmplte_file) if self.callback_isLadishRoom() else tmplte_file) + + # No decimal bpm support + proj_bpm = proj_bpm.split(".",1)[0] + + elif binary == "muse": + tmplte_file = os.path.join(proj_folder, "MusE_%i.med" % rand_check) + + os.system("cp '%s' '%s'" % (os.path.join(tmplte_dir, "MusE.med"), tmplte_file)) + + tmplte_cmd = binary + tmplte_cmd += " '%s'" % (os.path.basename(tmplte_file) if self.callback_isLadishRoom() else tmplte_file) + + elif binary == "non-sequencer": + tmplte_file_r = os.path.join(proj_folder, "Non-Sequencer_%i.non" % rand_check) + + os.system("cp '%s' '%s'" % (os.path.join(tmplte_dir, "Non-Sequencer.non"), tmplte_file_r)) + + tmplte_cmd = binary + tmplte_cmd += " '%s'" % (os.path.basename(tmplte_file_r) if self.callback_isLadishRoom() else tmplte_file_r) + + elif binary == "non-timeline": + tmplte_folder = os.path.join(proj_folder, "Non-Timeline_%i" % rand_check) + os.mkdir(tmplte_folder) + + os.system("cp '%s' '%s'" % (os.path.join(tmplte_dir, "Non-Timeline", "history"), tmplte_folder)) + os.system("cp '%s' '%s'" % (os.path.join(tmplte_dir, "Non-Timeline", "info"), tmplte_folder)) + os.mkdir(os.path.join(tmplte_folder, "sources")) + + os.system('sed -i "s/X_SR_X-CLAUDIA-X_SR_X/%s/" "%s"' % (proj_srate, os.path.join(tmplte_folder, "info"))) + os.system('sed -i "s/X_BPM_X-CLAUDIA-X_BPM_X/%s/" "%s"' % (proj_bpm, os.path.join(tmplte_folder, "history"))) + + tmplte_cmd = binary + tmplte_cmd += " '%s'" % (os.path.basename(tmplte_folder) if self.callback_isLadishRoom() else tmplte_folder) + + elif binary == "qtractor": + tmplte_file = os.path.join(proj_folder, "Qtractor_%i.qtr" % rand_check) + + os.system("cp '%s' '%s'" % (os.path.join(tmplte_dir, "Qtractor.qtr"), tmplte_file)) + + tmplte_cmd = binary + tmplte_cmd += " '%s'" % (os.path.basename(tmplte_file) if self.callback_isLadishRoom() else tmplte_file) + tmplte_lvl = "1" + + elif binary == "rosegarden": + tmplte_file = os.path.join(proj_folder, "Rosegarden_%i.rg" % rand_check) + + os.system("cp '%s' '%s'" % (os.path.join(tmplte_dir, "Rosegarden.rg"), tmplte_file)) + + tmplte_cmd = binary + tmplte_cmd += " '%s'" % (os.path.basename(tmplte_file) if self.callback_isLadishRoom() else tmplte_file) + tmplte_lvl = "1" + + elif binary == "seq24": + tmplte_file_r = os.path.join(proj_folder, "Seq24_%i.midi" % rand_check) + + os.system("cp '%s' '%s'" % (os.path.join(tmplte_dir, "Seq24.midi"), tmplte_file_r)) + + tmplte_cmd = binary + tmplte_cmd += " '%s'" % (os.path.basename(tmplte_file_r) if self.callback_isLadishRoom() else tmplte_file_r) + tmplte_lvl = "1" + + elif binary == "calfjackhost": + tmplte_file = os.path.join(proj_folder, "CalfJackHost_%i" % rand_check) + + os.system("cp '%s' '%s'" % (os.path.join(tmplte_dir, "CalfJackHost"), tmplte_file)) + + tmplte_cmd = binary + tmplte_cmd += " --load '%s'" % (os.path.basename(tmplte_file) if self.callback_isLadishRoom() else tmplte_file) + tmplte_lvl = "1" + + elif binary == "carla": + tmplte_file = os.path.join(proj_folder, "Carla_%i.carxp" % rand_check) + + os.system("cp '%s' '%s'" % (os.path.join(tmplte_dir, "Carla.carxp"), tmplte_file)) + + tmplte_cmd = binary + tmplte_cmd += " '%s'" % (os.path.basename(tmplte_file) if self.callback_isLadishRoom() else tmplte_file) + tmplte_lvl = "1" + + elif binary == "jack-rack": + tmplte_file = os.path.join(proj_folder, "Jack-Rack_%i.xml" % rand_check) + + os.system("cp '%s' '%s'" % (os.path.join(tmplte_dir, "Jack-Rack.xml"), tmplte_file)) + + tmplte_cmd = binary + tmplte_cmd += " '%s'" % (os.path.basename(tmplte_file) if self.callback_isLadishRoom() else tmplte_file) + + elif binary == "qsampler": + tmplte_file = os.path.join(proj_folder, "Qsampler_%i.lscp" % rand_check) + + os.system("cp '%s' '%s'" % (os.path.join(tmplte_dir, "Qsampler.lscp"), tmplte_file)) + + tmplte_cmd = binary + tmplte_cmd += " '%s'" % (os.path.basename(tmplte_file) if self.callback_isLadishRoom() else tmplte_file) + + elif binary == "jack-mixer": + tmplte_file = os.path.join(proj_folder, "Jack-Mixer_%i.xml" % rand_check) + + os.system("cp '%s' '%s'" % (os.path.join(tmplte_dir, "Jack-Mixer.xml"), tmplte_file)) + + tmplte_cmd = binary + tmplte_cmd += " -c '%s'" % (os.path.basename(tmplte_file) if self.callback_isLadishRoom() else tmplte_file) + + else: + print("ClaudiaLauncher::createAppTemplate(%s) - Failed to parse app name" % app) + return False + + if tmplte_file is not None: + os.system('sed -i "s|X_SR_X-CLAUDIA-X_SR_X|%s|" "%s"' % (proj_srate, tmplte_file)) + os.system('sed -i "s|X_BPM_X-CLAUDIA-X_BPM_X|%s|" "%s"' % (proj_bpm, tmplte_file)) + os.system('sed -i "s|X_FOLDER_X-CLAUDIA-X_FOLDER_X|%s|" "%s"' % (proj_folder.replace("|", "\|").replace("$", "\$"), tmplte_file)) + + appBus = self.callback_getAppBus() + appBus.RunCustom2(False, tmplte_cmd, app, tmplte_lvl) + return True + + def parentR(self): + return self._parent + + def settings(self): + return self._settings + + def getIcon(self, icon): + return self.ClaudiaIcons.getIcon(icon) + + def getIconForYesNo(self, yesno): + return self.icon_yes if yesno else self.icon_no + + def setCallbackApp(self, parent, settings, ladish_only): + self._parent = parent + self._settings = settings + self.m_ladish_only = ladish_only + + def clearInfo_DAW(self): + self.ico_app_daw.setPixmap(self.getIcon("start-here").pixmap(48, 48)) + self.label_name_daw.setText(self.tr("App Name")) + self.ico_ladspa_daw.setPixmap(self.getIconForYesNo(False).pixmap(16, 16)) + self.ico_dssi_daw.setPixmap(self.getIconForYesNo(False).pixmap(16, 16)) + self.ico_lv2_daw.setPixmap(self.getIconForYesNo(False).pixmap(16, 16)) + self.ico_vst_daw.setPixmap(self.getIconForYesNo(False).pixmap(16, 16)) + self.label_vst_mode_daw.setText("") + self.ico_jack_transport_daw.setPixmap(self.getIconForYesNo(False).pixmap(16, 16)) + self.label_midi_mode_daw.setText("---") + self.label_session_level_daw.setText(database.LEVEL_0) + self.frame_DAW.setEnabled(False) + self.showDoc_DAW("", "") + + def clearInfo_Host(self): + self.ico_app_host.setPixmap(self.getIcon("start-here").pixmap(48, 48)) + self.label_name_host.setText(self.tr("App Name")) + self.ico_ladspa_host.setPixmap(self.getIconForYesNo(False).pixmap(16, 16)) + self.ico_dssi_host.setPixmap(self.getIconForYesNo(False).pixmap(16, 16)) + self.ico_lv2_host.setPixmap(self.getIconForYesNo(False).pixmap(16, 16)) + self.ico_vst_host.setPixmap(self.getIconForYesNo(False).pixmap(16, 16)) + self.label_vst_mode_host.setText("") + self.label_midi_mode_host.setText("---") + self.label_session_level_host.setText(database.LEVEL_0) + self.frame_Host.setEnabled(False) + self.showDoc_Host("", "") + + def clearInfo_Intrument(self): + self.ico_app_ins.setPixmap(self.getIcon("start-here").pixmap(48, 48)) + self.label_name_ins.setText(self.tr("App Name")) + self.ico_builtin_fx_ins.setPixmap(self.getIconForYesNo(False).pixmap(16, 16)) + self.ico_audio_input_ins.setPixmap(self.getIconForYesNo(False).pixmap(16, 16)) + self.label_midi_mode_ins.setText("---") + self.label_session_level_ins.setText(database.LEVEL_0) + self.frame_Instrument.setEnabled(False) + self.showDoc_Instrument("", "") + + def clearInfo_Bristol(self): + self.ico_app_bristol.setPixmap(self.getIcon("start-here").pixmap(48, 48)) + self.label_name_bristol.setText(self.tr("App Name")) + self.ico_builtin_fx_bristol.setPixmap(self.getIconForYesNo(False).pixmap(16, 16)) + self.ico_audio_input_bristol.setPixmap(self.getIconForYesNo(False).pixmap(16, 16)) + self.label_midi_mode_bristol.setText("---") + self.label_session_level_bristol.setText(database.LEVEL_0) + self.frame_Bristol.setEnabled(False) + self.showDoc_Bristol("", "") + + def clearInfo_Plugin(self): + self.ico_app_plugin.setPixmap(self.getIcon("lv2").pixmap(48, 48)) + self.label_name_plugin.setText(self.tr("Plugin Name")) + self.label_plugin_audio_ins.setText("0") + self.label_plugin_audio_outs.setText("0") + self.label_plugin_midi_ins.setText("0") + self.label_plugin_midi_outs.setText("0") + self.label_plugin_parameter_ins.setText("0") + self.label_plugin_parameter_outs.setText("0") + self.frame_Plugin.setEnabled(False) + self.showDoc_Plugin("", "") + + def clearInfo_Effect(self): + self.ico_app_effect.setPixmap(self.getIcon("start-here").pixmap(48, 48)) + self.label_name_effect.setText(self.tr("App Name")) + self.ico_stereo_effect.setPixmap(self.getIconForYesNo(False).pixmap(16, 16)) + self.label_midi_mode_effect.setText("---") + self.label_session_level_effect.setText(database.LEVEL_0) + self.frame_Effect.setEnabled(False) + self.showDoc_Effect("", "") + + def clearInfo_Tool(self): + self.ico_app_tool.setPixmap(self.getIcon("start-here").pixmap(48, 48)) + self.label_name_tool.setText(self.tr("App Name")) + self.label_midi_mode_tool.setText("---") + self.label_session_level_tool.setText(database.LEVEL_0) + self.frame_Tool.setEnabled(False) + self.showDoc_Tool("", "") + + def showDoc_DAW(self, doc, web): + self.url_documentation_daw.setVisible(bool(doc)) + self.url_website_daw.setVisible(bool(web)) + self.label_no_help_daw.setVisible(not(doc or web)) + + if doc: self.setDocUrl(self.url_documentation_daw, doc) + if web: self.setWebUrl(self.url_website_daw, web) + + def showDoc_Host(self, doc, web): + self.url_documentation_host.setVisible(bool(doc)) + self.url_website_host.setVisible(bool(web)) + self.label_no_help_host.setVisible(not(doc or web)) + + if doc: self.setDocUrl(self.url_documentation_host, doc) + if web: self.setWebUrl(self.url_website_host, web) + + def showDoc_Instrument(self, doc, web): + self.url_documentation_ins.setVisible(bool(doc)) + self.url_website_ins.setVisible(bool(web)) + self.label_no_help_ins.setVisible(not(doc or web)) + + if doc: self.setDocUrl(self.url_documentation_ins, doc) + if web: self.setWebUrl(self.url_website_ins, web) + + def showDoc_Bristol(self, doc, web): + self.url_documentation_bristol.setVisible(bool(doc)) + self.url_website_bristol.setVisible(bool(web)) + self.label_no_help_bristol.setVisible(not(doc or web)) + + if doc: self.setDocUrl(self.url_documentation_bristol, doc) + if web: self.setWebUrl(self.url_website_bristol, web) + + def showDoc_Plugin(self, doc, web): + self.url_documentation_plugin.setVisible(bool(doc)) + self.url_website_plugin.setVisible(bool(web)) + self.label_no_help_plugin.setVisible(not(doc or web)) + + if doc: self.setDocUrl(self.url_documentation_plugin, doc) + if web: self.setWebUrl(self.url_website_plugin, web) + + def showDoc_Effect(self, doc, web): + self.url_documentation_effect.setVisible(bool(doc)) + self.url_website_effect.setVisible(bool(web)) + self.label_no_help_effect.setVisible(not(doc or web)) + + if doc: self.setDocUrl(self.url_documentation_effect, doc) + if web: self.setWebUrl(self.url_website_effect, web) + + def showDoc_Tool(self, doc, web): + self.url_documentation_tool.setVisible(bool(doc)) + self.url_website_tool.setVisible(bool(web)) + self.label_no_help_tool.setVisible(not(doc or web)) + + if doc: self.setDocUrl(self.url_documentation_tool, doc) + if web: self.setWebUrl(self.url_website_tool, web) + + def setDocUrl(self, item, link): + item.setText(self.tr("Documentation" % link)) + + def setWebUrl(self, item, link): + item.setText(self.tr("WebSite" % link)) + + def clearAll(self): + self.listDAW.clearContents() + self.listHost.clearContents() + self.listInstrument.clearContents() + self.listBristol.clearContents() + self.listPlugin.clearContents() + self.listEffect.clearContents() + self.listTool.clearContents() + for x in range(self.listDAW.rowCount()): + self.listDAW.removeRow(0) + for x in range(self.listHost.rowCount()): + self.listHost.removeRow(0) + for x in range(self.listInstrument.rowCount()): + self.listInstrument.removeRow(0) + for x in range(self.listBristol.rowCount()): + self.listBristol.removeRow(0) + for x in range(self.listPlugin.rowCount()): + self.listPlugin.removeRow(0) + for x in range(self.listEffect.rowCount()): + self.listEffect.removeRow(0) + for x in range(self.listTool.rowCount()): + self.listTool.removeRow(0) + + def refreshAll(self): + self.clearAll() + pkglist = [] + + if not SHOW_ALL: + if os.path.exists("/usr/bin/yaourt"): + pkg_out = getoutput("env LANG=C LC_ALL=C /usr/bin/yaourt -Qsq 2>/dev/null").split("\n") + for package in pkg_out: + pkglist.append(package) + + elif os.path.exists("/usr/bin/pacman"): + pkg_out = getoutput("env LANG=C LC_ALL=C /usr/bin/pacman -Qsq 2>/dev/null").split("\n") + for package in pkg_out: + pkglist.append(package) + + elif os.path.exists("/usr/bin/dpkg"): + pkg_out = getoutput("env LANG=C LC_ALL=C /usr/bin/dpkg --get-selections 2>/dev/null").split("\n") + for pkg_info in pkg_out: + package, installed = pkg_info.rsplit("\t", 1) + if installed == "install": + pkglist.append(package.strip()) + + elif os.path.exists("/bin/rpm"): + pkg_out = getoutput("env LANG=C /bin/rpm -qa --qf \"%{NAME}\n\" 2>/dev/null").split("\n") + for package in pkg_out: + pkglist.append(package) + + if not "bristol" in pkglist: + self.tabWidget.setTabEnabled(iTabBristol, False) + + if not "carla-git" in pkglist: + self.tabWidget.setTabEnabled(iTabPlugin, False) + + last_pos = 0 + for item in database.list_DAW: + Package, AppName, Type, Binary, Icon, Save, Level, Features, Docs = item + if SHOW_ALL or Package in pkglist: + w_icon = QTableWidgetItem("") + w_icon.setData(Qt.UserRole, item) + w_icon.setIcon(QIcon(self.getIcon(Icon))) + w_name = QTableWidgetItem(AppName) + w_type = QTableWidgetItem(Type) + w_save = QTableWidgetItem(Save) + + self.listDAW.insertRow(last_pos) + self.listDAW.setItem(last_pos, 0, w_icon) + self.listDAW.setItem(last_pos, 1, w_name) + self.listDAW.setItem(last_pos, 2, w_type) + self.listDAW.setItem(last_pos, 3, w_save) + + last_pos += 1 + + last_pos = 0 + for item in database.list_Host: + Package, AppName, Instruments, Effects, Binary, Icon, Save, Level, Features, Docs = item + if SHOW_ALL or Package in pkglist: + w_icon = QTableWidgetItem("") + w_icon.setData(Qt.UserRole, item) + w_icon.setIcon(QIcon(self.getIcon(Icon))) + w_name = QTableWidgetItem(AppName) + w_h_in = QTableWidgetItem(Instruments) + w_h_ef = QTableWidgetItem(Effects) + w_save = QTableWidgetItem(Save) + + self.listHost.insertRow(last_pos) + self.listHost.setItem(last_pos, 0, w_icon) + self.listHost.setItem(last_pos, 1, w_name) + self.listHost.setItem(last_pos, 2, w_h_in) + self.listHost.setItem(last_pos, 3, w_h_ef) + self.listHost.setItem(last_pos, 4, w_save) + + last_pos += 1 + + last_pos = 0 + for item in database.list_Instrument: + Package, AppName, Type, Binary, Icon, Save, Level, Features, Docs = item + if SHOW_ALL or Package in pkglist: + w_icon = QTableWidgetItem("") + w_icon.setData(Qt.UserRole, item) + w_icon.setIcon(QIcon(self.getIcon(Icon))) + w_name = QTableWidgetItem(AppName) + w_type = QTableWidgetItem(Type) + w_save = QTableWidgetItem(Save) + + self.listInstrument.insertRow(last_pos) + self.listInstrument.setItem(last_pos, 0, w_icon) + self.listInstrument.setItem(last_pos, 1, w_name) + self.listInstrument.setItem(last_pos, 2, w_type) + self.listInstrument.setItem(last_pos, 3, w_save) + + last_pos += 1 + + last_pos = 0 + for item in database.list_Bristol: + Package, FullName, Type, ShortName, Icon, Save, Level, Features, Docs = item + if SHOW_ALL or Package in pkglist: + w_icon = QTableWidgetItem("") + w_icon.setData(Qt.UserRole, item) + w_icon.setIcon(QIcon(self.getIcon(Icon))) + w_fullname = QTableWidgetItem(FullName) + w_shortname = QTableWidgetItem(ShortName) + + self.listBristol.insertRow(last_pos) + self.listBristol.setItem(last_pos, 0, w_icon) + self.listBristol.setItem(last_pos, 1, w_shortname) + self.listBristol.setItem(last_pos, 2, w_fullname) + + last_pos += 1 + + last_pos = 0 + for item in database.list_Effect: + Package, AppName, Type, Binary, Icon, Save, Level, Features, Docs = item + if SHOW_ALL or Package in pkglist: + w_icon = QTableWidgetItem("") + w_icon.setData(Qt.UserRole, item) + w_icon.setIcon(QIcon(self.getIcon(Icon))) + w_name = QTableWidgetItem(AppName) + w_type = QTableWidgetItem(Type) + w_save = QTableWidgetItem(Save) + + self.listEffect.insertRow(last_pos) + self.listEffect.setItem(last_pos, 0, w_icon) + self.listEffect.setItem(last_pos, 1, w_name) + self.listEffect.setItem(last_pos, 2, w_type) + self.listEffect.setItem(last_pos, 3, w_save) + + last_pos += 1 + + last_pos = 0 + for item in database.list_Tool: + Package, AppName, Type, Binary, Icon, Save, Level, Features, Docs = item + if SHOW_ALL or Package in pkglist: + w_icon = QTableWidgetItem("") + w_icon.setData(Qt.UserRole, item) + w_icon.setIcon(QIcon(self.getIcon(Icon))) + w_name = QTableWidgetItem(AppName) + w_type = QTableWidgetItem(Type) + w_save = QTableWidgetItem(Save) + + self.listTool.insertRow(last_pos) + self.listTool.setItem(last_pos, 0, w_icon) + self.listTool.setItem(last_pos, 1, w_name) + self.listTool.setItem(last_pos, 2, w_type) + self.listTool.setItem(last_pos, 3, w_save) + + last_pos += 1 + + if haveCarla and os.path.exists("/usr/lib/carla/libcarla_utils.so"): + utils = CarlaUtils("/usr/lib/carla/libcarla_utils.so") + last_pos = 0 + lv2path = os.getenv("LV2_PATH", "~/.lv2:/usr/lib/lv2:/usr/local/lib/lv2") + for i in range(utils.get_cached_plugin_count(PLUGIN_LV2, lv2path)): + plugin = utils.get_cached_plugin_info(PLUGIN_LV2, i) + + if (plugin["hints"] & PLUGIN_HAS_CUSTOM_UI) == 0: + continue + + catgtext = "" + + if plugin["category"] == PLUGIN_CATEGORY_SYNTH: + catgtext = "Synth" + elif plugin["category"] == PLUGIN_CATEGORY_DELAY: + catgtext = "Delay" + elif plugin["category"] == PLUGIN_CATEGORY_EQ: + catgtext = "Equalizer" + elif plugin["category"] == PLUGIN_CATEGORY_FILTER: + catgtext = "Filter" + elif plugin["category"] == PLUGIN_CATEGORY_DISTORTION: + catgtext = "Distortion" + elif plugin["category"] == PLUGIN_CATEGORY_DYNAMICS: + catgtext = "Dynamics" + elif plugin["category"] == PLUGIN_CATEGORY_MODULATOR: + catgtext = "Modulator" + elif plugin["category"] == PLUGIN_CATEGORY_UTILITY: + catgtext = "Utility" + elif plugin["category"] == PLUGIN_CATEGORY_OTHER: + catgtext = "Other" + + if not plugin["category"]: + if plugin["hints"] & PLUGIN_IS_SYNTH: + catgtext = "Synth" + else: + catgtext = "FX" + + w_name = QTableWidgetItem(plugin["name"]) + w_auth = QTableWidgetItem(plugin["maker"]) + w_catg = QTableWidgetItem(catgtext) + + w_name.setData(Qt.UserRole, plugin) + + self.listPlugin.insertRow(last_pos) + self.listPlugin.setItem(last_pos, 0, w_name) + self.listPlugin.setItem(last_pos, 1, w_auth) + self.listPlugin.setItem(last_pos, 2, w_catg) + + last_pos += 1 + + self.listDAW.setCurrentCell(-1, -1) + self.listHost.setCurrentCell(-1, -1) + self.listInstrument.setCurrentCell(-1, -1) + self.listBristol.setCurrentCell(-1, -1) + self.listPlugin.setCurrentCell(-1, -1) + self.listEffect.setCurrentCell(-1, -1) + self.listTool.setCurrentCell(-1, -1) + + self.listDAW.sortByColumn(1, Qt.AscendingOrder) + self.listHost.sortByColumn(1, Qt.AscendingOrder) + self.listInstrument.sortByColumn(1, Qt.AscendingOrder) + self.listBristol.sortByColumn(2, Qt.AscendingOrder) + self.listPlugin.sortByColumn(0, Qt.AscendingOrder) + self.listEffect.sortByColumn(1, Qt.AscendingOrder) + self.listTool.sortByColumn(1, Qt.AscendingOrder) + + @pyqtSlot(int) + def slot_checkSelectedTab(self, tabIndex): + if tabIndex == iTabDAW: + test_selected = (len(self.listDAW.selectedItems()) > 0) + elif tabIndex == iTabHost: + test_selected = (len(self.listHost.selectedItems()) > 0) + elif tabIndex == iTabInstrument: + test_selected = (len(self.listInstrument.selectedItems()) > 0) + elif tabIndex == iTabBristol: + test_selected = (len(self.listBristol.selectedItems()) > 0) + elif tabIndex == iTabPlugin: + test_selected = (len(self.listPlugin.selectedItems()) > 0) + elif tabIndex == iTabEffect: + test_selected = (len(self.listEffect.selectedItems()) > 0) + elif tabIndex == iTabTool: + test_selected = (len(self.listTool.selectedItems()) > 0) + else: + test_selected = False + + self.callback_checkGUI(test_selected) + + @pyqtSlot(int) + def slot_checkSelectedDAW(self, row): + if row >= 0: + item = self.listDAW.item(row, 0).data(Qt.UserRole) + Package, AppName, Type, Binary, Icon, Save, Level, Features, Docs = item + + self.frame_DAW.setEnabled(True) + self.ico_app_daw.setPixmap(QIcon(self.getIcon(Icon)).pixmap(48, 48)) + self.ico_ladspa_daw.setPixmap(QIcon(self.getIconForYesNo(Features[0])).pixmap(16, 16)) + self.ico_dssi_daw.setPixmap(QIcon(self.getIconForYesNo(Features[1])).pixmap(16, 16)) + self.ico_lv2_daw.setPixmap(QIcon(self.getIconForYesNo(Features[2])).pixmap(16, 16)) + self.ico_vst_daw.setPixmap(QIcon(self.getIconForYesNo(Features[3])).pixmap(16, 16)) + self.ico_jack_transport_daw.setPixmap(QIcon(self.getIconForYesNo(Features[5])).pixmap(16, 16)) + self.label_name_daw.setText(AppName) + self.label_vst_mode_daw.setText(Features[4]) + self.ico_midi_mode_daw.setPixmap(QIcon(self.getIconForYesNo(Features[6])).pixmap(16, 16)) + self.label_midi_mode_daw.setText(Features[7]) + self.label_session_level_daw.setText(Level) + + Docs0 = Docs[0] if (os.path.exists(Docs[0].replace("file://", ""))) else "" + self.showDoc_DAW(Docs0, Docs[1]) + else: + self.clearInfo_DAW() + + self.callback_checkGUI(row >= 0) + + @pyqtSlot(int) + def slot_checkSelectedHost(self, row): + if row >= 0: + item = self.listHost.item(row, 0).data(Qt.UserRole) + Package, AppName, Instruments, Effects, Binary, Icon, Save, Level, Features, Docs = item + + self.frame_Host.setEnabled(True) + self.ico_app_host.setPixmap(self.getIcon(Icon).pixmap(48, 48)) + self.ico_internal_host.setPixmap(self.getIconForYesNo(Features[0]).pixmap(16, 16)) + self.ico_ladspa_host.setPixmap(self.getIconForYesNo(Features[1]).pixmap(16, 16)) + self.ico_dssi_host.setPixmap(self.getIconForYesNo(Features[2]).pixmap(16, 16)) + self.ico_lv2_host.setPixmap(self.getIconForYesNo(Features[3]).pixmap(16, 16)) + self.ico_vst_host.setPixmap(self.getIconForYesNo(Features[4]).pixmap(16, 16)) + self.label_name_host.setText(AppName) + self.label_vst_mode_host.setText(Features[5]) + self.label_midi_mode_host.setText(Features[6]) + self.label_session_level_host.setText(str(Level)) + + Docs0 = Docs[0] if (os.path.exists(Docs[0].replace("file://", ""))) else "" + self.showDoc_Host(Docs0, Docs[1]) + else: + self.clearInfo_DAW() + + self.callback_checkGUI(row >= 0) + + @pyqtSlot(int) + def slot_checkSelectedInstrument(self, row): + if row >= 0: + item = self.listInstrument.item(row, 0).data(Qt.UserRole) + Package, AppName, Type, Binary, Icon, Save, Level, Features, Docs = item + + self.frame_Instrument.setEnabled(True) + self.ico_app_ins.setPixmap(self.getIcon(Icon).pixmap(48, 48)) + self.ico_builtin_fx_ins.setPixmap(self.getIconForYesNo(Features[0]).pixmap(16, 16)) + self.ico_audio_input_ins.setPixmap(self.getIconForYesNo(Features[1]).pixmap(16, 16)) + self.label_name_ins.setText(AppName) + self.label_midi_mode_ins.setText(Features[2]) + self.label_session_level_ins.setText(str(Level)) + + Docs0 = Docs[0] if (os.path.exists(Docs[0].replace("file://", ""))) else "" + self.showDoc_Instrument(Docs0, Docs[1]) + else: + self.clearInfo_Intrument() + + self.callback_checkGUI(row >= 0) + + @pyqtSlot(int) + def slot_checkSelectedBristol(self, row): + if row >= 0: + item = self.listBristol.item(row, 0).data(Qt.UserRole) + Package, AppName, Type, ShortName, Icon, Save, Level, Features, Docs = item + + self.frame_Bristol.setEnabled(True) + self.ico_app_bristol.setPixmap(self.getIcon(Icon).pixmap(48, 48)) + self.ico_builtin_fx_bristol.setPixmap(self.getIconForYesNo(Features[0]).pixmap(16, 16)) + self.ico_audio_input_bristol.setPixmap(self.getIconForYesNo(Features[1]).pixmap(16, 16)) + self.label_name_bristol.setText(AppName) + self.label_midi_mode_bristol.setText(Features[2]) + self.label_session_level_bristol.setText(str(Level)) + + Docs0 = Docs[0] if (os.path.exists(Docs[0].replace("file://", ""))) else "" + self.showDoc_Bristol(Docs0, Docs[1]) + else: + self.clearInfo_Bristol() + + self.callback_checkGUI(row >= 0) + + @pyqtSlot(int) + def slot_checkSelectedPlugin(self, row): + if row >= 0: + plugin = self.listPlugin.item(row, 0).data(Qt.UserRole) + + self.frame_Plugin.setEnabled(True) + self.ico_app_bristol.setPixmap(self.getIcon("lv2").pixmap(48, 48)) + self.label_name_plugin.setText(plugin["name"]) + self.label_plugin_audio_ins.setText(str(plugin["audioIns"])) + self.label_plugin_audio_outs.setText(str(plugin["audioOuts"])) + self.label_plugin_midi_ins.setText(str(plugin["midiIns"])) + self.label_plugin_midi_outs.setText(str(plugin["midiOuts"])) + self.label_plugin_parameter_ins.setText(str(plugin["parameterIns"])) + self.label_plugin_parameter_outs.setText(str(plugin["parameterOuts"])) + + #Docs0 = Docs[0] if (os.path.exists(Docs[0].replace("file://", ""))) else "" + #self.showDoc_Plugin(Docs0, Docs[1]) + else: + self.clearInfo_Plugin() + + self.callback_checkGUI(row >= 0) + + @pyqtSlot(int) + def slot_checkSelectedEffect(self, row): + if row >= 0: + item = self.listEffect.item(row, 0).data(Qt.UserRole) + Package, AppName, Type, Binary, Icon, Save, Level, Features, Docs = item + + self.frame_Effect.setEnabled(True) + self.ico_app_effect.setPixmap(self.getIcon(Icon).pixmap(48, 48)) + self.ico_stereo_effect.setPixmap(self.getIconForYesNo(Features[0]).pixmap(16, 16)) + self.label_name_effect.setText(AppName) + self.label_midi_mode_effect.setText(Features[1]) + self.label_session_level_effect.setText(str(Level)) + + Docs0 = Docs[0] if (os.path.exists(Docs[0].replace("file://", ""))) else "" + self.showDoc_Effect(Docs0, Docs[1]) + else: + self.clearInfo_Effect() + + self.callback_checkGUI(row >= 0) + + @pyqtSlot(int) + def slot_checkSelectedTool(self, row): + if row >= 0: + item = self.listTool.item(row, 0).data(Qt.UserRole) + Package, AppName, Type, Binary, Icon, Save, Level, Features, Docs = item + + self.frame_Tool.setEnabled(True) + self.ico_app_tool.setPixmap(self.getIcon(Icon).pixmap(48, 48)) + self.label_name_tool.setText(AppName) + self.label_midi_mode_tool.setText(Features[0]) + self.ico_jack_transport_tool.setPixmap(self.getIconForYesNo(Features[1]).pixmap(16, 16)) + self.label_session_level_tool.setText(str(Level)) + + Docs0 = Docs[0] if (os.path.exists(Docs[0].replace("file://", ""))) else "" + self.showDoc_Tool(Docs0, Docs[1]) + else: + self.clearInfo_Tool() + + self.callback_checkGUI(row >= 0) + + @pyqtSlot() + def slot_doubleClickedList(self): + if self.m_ladish_only: + self.addAppToLADISH() + else: + self.startApp() + + def saveSettings(self): + if self.settings(): + self.settings().setValue("SplitterDAW", self.splitter_DAW.saveState()) + self.settings().setValue("SplitterHost", self.splitter_Host.saveState()) + self.settings().setValue("SplitterInstrument", self.splitter_Instrument.saveState()) + self.settings().setValue("SplitterBristol", self.splitter_Bristol.saveState()) + self.settings().setValue("SplitterPlugin", self.splitter_Plugin.saveState()) + self.settings().setValue("SplitterEffect", self.splitter_Effect.saveState()) + self.settings().setValue("SplitterTool", self.splitter_Tool.saveState()) + + QIcon.setThemeName(self.m_lastThemeName) + + def loadSettings(self): + if self.settings() and self.settings().contains("SplitterPlugin"): + self.splitter_DAW.restoreState(self.settings().value("SplitterDAW", "")) + self.splitter_Host.restoreState(self.settings().value("SplitterHost", "")) + self.splitter_Instrument.restoreState(self.settings().value("SplitterInstrument", "")) + self.splitter_Bristol.restoreState(self.settings().value("SplitterBristol", "")) + self.splitter_Plugin.restoreState(self.settings().value("SplitterPlugin", "")) + self.splitter_Effect.restoreState(self.settings().value("SplitterEffect", "")) + self.splitter_Tool.restoreState(self.settings().value("SplitterTool", "")) + else: # First-Run? + self.splitter_DAW.setSizes([500, 200]) + self.splitter_Host.setSizes([500, 200]) + self.splitter_Instrument.setSizes([500, 200]) + self.splitter_Bristol.setSizes([500, 200]) + self.splitter_Plugin.setSizes([500, 200]) + self.splitter_Effect.setSizes([500, 200]) + self.splitter_Tool.setSizes([500, 200]) + + # ---------------------------------------- + # Callbacks + + def callback_checkGUI(self, selected): + if self.parentR(): + self.parentR().callback_checkGUI(selected) + + def callback_getProjectFolder(self): + if self.parentR(): + return self.parentR().callback_getProjectFolder() + return HOME + + def callback_getAppBus(self): + return self.parentR().callback_getAppBus() + + def callback_getBPM(self): + return self.parentR().callback_getBPM() + + def callback_getSampleRate(self): + return self.parentR().callback_getSampleRate() + + def callback_isLadishRoom(self): + return self.parentR().callback_isLadishRoom() + +#--------------- main ------------------ +if __name__ == '__main__': + import dbus + from signal import signal, SIG_IGN, SIGUSR1 + from PyQt5.QtWidgets import QApplication + import jacklib, ui_claudia_launcher_app + + # DBus connections + class DBus(object): + __slots__ = [ + 'loopBus', + 'controlBus', + 'studioBus', + 'appBus', + ] + + DBus = DBus() + + class ClaudiaLauncherApp(QMainWindow, ui_claudia_launcher_app.Ui_ClaudiaLauncherApp): + def __init__(self, parent=None): + QMainWindow.__init__(self, parent) + self.setupUi(self) + + self.settings = QSettings("Cadence", "Claudia-Launcher") + self.launcher.setCallbackApp(self, self.settings, False) + self.loadSettings() + + self.test_url = True + self.test_selected = False + self.studio_root_folder = HOME + + # Check for JACK + self.jack_client = jacklib.client_open("klaudia", jacklib.JackNoStartServer, None) + if not self.jack_client: + QTimer.singleShot(0, self.slot_showJackError) + return + + # Set-up GUI + self.b_start.setIcon(self.getIcon("go-next")) + self.b_add.setIcon(self.getIcon("list-add")) + self.b_refresh.setIcon(self.getIcon("view-refresh")) + self.b_open.setIcon(self.getIcon("document-open")) + self.b_start.setEnabled(False) + self.b_add.setEnabled(False) + + self.le_url.setText(self.studio_root_folder) + self.co_sample_rate.addItem(str(self.getJackSampleRate())) + self.sb_bpm.setValue(self.getJackBPM()) + + self.refreshStudioList() + + if DBus.controlBus: + self.slot_enableLADISH(True) + else: + for iPATH in PATH: + if os.path.exists(os.path.join(iPATH, "ladishd")): + break + else: + self.slot_enableLADISH(False) + + self.b_start.clicked.connect(self.slot_startApp) + self.b_add.clicked.connect(self.slot_addAppToLADISH) + self.b_refresh.clicked.connect(self.slot_refreshStudioList) + + self.co_ladi_room.currentIndexChanged[int].connect(self.slot_checkSelectedRoom) + self.groupLADISH.toggled.connect(self.slot_enableLADISH) + + self.le_url.textChanged.connect(self.slot_checkFolderUrl) + self.b_open.clicked.connect(self.slot_getAndSetPath) + + def getIcon(self, icon): + return self.launcher.getIcon(icon) + + def getJackBPM(self): + pos = jacklib.jack_position_t() + pos.valid = 0 + jacklib.transport_query(self.jack_client, jacklib.pointer(pos)) + + if pos.valid & jacklib.JackPositionBBT: + return pos.beats_per_minute + return 120.0 + + def getJackSampleRate(self): + return jacklib.get_sample_rate(self.jack_client) + + def refreshStudioList(self): + self.co_ladi_room.clear() + self.co_ladi_room.addItem("") + if DBus.controlBus: + studio_bus = DBus.loopBus.get_object("org.ladish", "/org/ladish/Studio") + studio_list_dump = studio_bus.GetRoomList() + for i in range(len(studio_list_dump)): + self.co_ladi_room.addItem("%s - %s" % ( + str(studio_list_dump[i][0]).replace("/org/ladish/Room", ""), studio_list_dump[i][1]['name'])) + + # ---------------------------------------- + # Callbacks + + def callback_checkGUI(self, test_selected=None): + if test_selected != None: + self.test_selected = test_selected + + if self.test_url and self.test_selected: + self.b_add.setEnabled(bool(DBus.controlBus)) + self.b_start.setEnabled(True) + else: + self.b_add.setEnabled(False) + self.b_start.setEnabled(False) + + def callback_getProjectFolder(self): + return self.le_url.text() + + def callback_getAppBus(self): + return DBus.appBus + + def callback_getBPM(self): + return self.getJackBPM() + + def callback_getSampleRate(self): + return self.getJackSampleRate() + + def callback_isLadishRoom(self): + return not self.le_url.isEnabled() + + # ---------------------------------------- + + @pyqtSlot(int) + def slot_checkSelectedRoom(self, co_n): + if co_n == -1 or not DBus.controlBus: + pass + elif co_n == 0: + DBus.studioBus = DBus.loopBus.get_object("org.ladish", "/org/ladish/Studio") + DBus.appBus = dbus.Interface(DBus.studioBus, 'org.ladish.AppSupervisor') + self.b_open.setEnabled(True) + self.le_url.setEnabled(True) + self.le_url.setText(self.studio_root_folder) + else: + room_number = self.co_ladi_room.currentText().split(" ")[0] + room_name = "/org/ladish/Room" + room_number + DBus.studioBus = DBus.loopBus.get_object("org.ladish", room_name) + DBus.appBus = dbus.Interface(DBus.studioBus, 'org.ladish.AppSupervisor') + room_properties = DBus.studioBus.GetProjectProperties() + if len(room_properties[1]) > 0: + self.b_open.setEnabled(False) + self.le_url.setEnabled(False) + self.le_url.setText(room_properties[1]['dir']) + else: + self.b_open.setEnabled(True) + self.le_url.setEnabled(True) + self.studio_root_folder = self.le_url.text() + + @pyqtSlot(str) + def slot_checkFolderUrl(self, url): + if os.path.exists(url): + self.test_url = True + if self.le_url.isEnabled(): + self.studio_root_folder = url + else: + self.test_url = False + self.callback_checkGUI() + + @pyqtSlot(bool) + def slot_enableLADISH(self, yesno): + self.groupLADISH.setCheckable(False) + + if yesno: + try: + DBus.controlBus = DBus.loopBus.get_object("org.ladish", "/org/ladish/Control") + self.groupLADISH.setTitle(self.tr("LADISH is enabled")) + except: + self.groupLADISH.setEnabled(False) + self.groupLADISH.setTitle(self.tr("LADISH is sick")) + return + + DBus.studioBus = DBus.loopBus.get_object("org.ladish", "/org/ladish/Studio") + DBus.appBus = dbus.Interface(DBus.studioBus, 'org.ladish.AppSupervisor') + + self.refreshStudioList() + self.callback_checkGUI() + + else: + self.groupLADISH.setEnabled(False) + self.groupLADISH.setTitle(self.tr("LADISH is not available")) + + @pyqtSlot() + def slot_startApp(self): + self.launcher.startApp() + + @pyqtSlot() + def slot_addAppToLADISH(self): + self.launcher.addAppToLADISH() + + @pyqtSlot() + def slot_getAndSetPath(self): + getAndSetPath(self, self.le_url.text(), self.le_url) + + @pyqtSlot() + def slot_refreshStudioList(self): + self.refreshStudioList() + + @pyqtSlot() + def slot_showJackError(self): + QMessageBox.critical(self, self.tr("Error"), + self.tr("JACK is not started!\nPlease start it first, then re-run Claudia-Launcher again.")) + self.close() + + def saveSettings(self): + self.settings.setValue("Geometry", self.saveGeometry()) + self.launcher.saveSettings() + + def loadSettings(self): + self.restoreGeometry(self.settings.value("Geometry", b"")) + self.launcher.loadSettings() + + def closeEvent(self, event): + self.saveSettings() + if self.jack_client: + jacklib.client_close(self.jack_client) + QMainWindow.closeEvent(self, event) + + # App initialization + app = QApplication(sys.argv) + app.setApplicationName("Claudia-Launcher") + app.setApplicationVersion(VERSION) + app.setOrganizationName("Cadence") + app.setWindowIcon(QIcon(":/scalable/claudia-launcher.svg")) + setup_i18n() + + # Do not close on SIGUSR1 + signal(SIGUSR1, SIG_IGN) + + # Connect to DBus + DBus.loopBus = dbus.SessionBus() + + if "org.ladish" in DBus.loopBus.list_names(): + DBus.controlBus = DBus.loopBus.get_object("org.ladish", "/org/ladish/Control") + DBus.studioBus = DBus.loopBus.get_object("org.ladish", "/org/ladish/Studio") + DBus.appBus = dbus.Interface(DBus.studioBus, "org.ladish.AppSupervisor") + else: + DBus.controlBus = None + DBus.studioBus = None + DBus.appBus = None + + # Show GUI + gui = ClaudiaLauncherApp() + gui.show() + + # App-Loop + sys.exit(app.exec_()) diff --git a/src/logs.py b/src/logs.py index 4da4c82..bfb425e 100755 --- a/src/logs.py +++ b/src/logs.py @@ -139,6 +139,8 @@ class LogsReadThread(QThread): self.LOG_FILE_JACK = LogsW.LOG_FILE_JACK self.LOG_FILE_A2J = LogsW.LOG_FILE_A2J + self.LOG_FILE_LASH = LogsW.LOG_FILE_LASH + self.LOG_FILE_LADISH = LogsW.LOG_FILE_LADISH # ------------------------------------------------------------- # Init logs @@ -161,6 +163,24 @@ class LogsReadThread(QThread): if self.fLogFileA2J.size() > self.MAX_INITIAL_SIZE: self.fLogStreamA2J.seek(self.fLogFileA2J.size() - self.MAX_INITIAL_SIZE) + if self.LOG_FILE_LASH is not None: + self.fLogFileLASH = QFile(self.LOG_FILE_LASH) + self.fLogFileLASH.open(QIODevice.ReadOnly) + self.fLogStreamLASH = QTextStream(self.fLogFileLASH) + self.fLogStreamLASH.setCodec("UTF-8") + + if self.fLogFileLASH.size() > self.MAX_INITIAL_SIZE: + self.fLogStreamLASH.seek(self.fLogFileLASH.size() - self.MAX_INITIAL_SIZE) + + if self.LOG_FILE_LADISH is not None: + self.fLogFileLADISH = QFile(self.LOG_FILE_LADISH) + self.fLogFileLADISH.open(QIODevice.ReadOnly) + self.fLogStreamLADISH = QTextStream(self.fLogFileLADISH) + self.fLogStreamLADISH.setCodec("UTF-8") + + if self.fLogFileLADISH.size() > self.MAX_INITIAL_SIZE: + self.fLogStreamLADISH.seek(self.fLogFileLADISH.size() - self.MAX_INITIAL_SIZE) + def closeNow(self): self.fCloseNow = True @@ -187,6 +207,20 @@ class LogsReadThread(QThread): self.fLogFileA2J.close() self.fLogFileA2J.open(QIODevice.ReadOnly) + if self.LOG_FILE_LASH: + self.fLogStreamLASH.flush() + self.fLogFileLASH.close() + self.fLogFileLASH.open(QIODevice.WriteOnly) + self.fLogFileLASH.close() + self.fLogFileLASH.open(QIODevice.ReadOnly) + + if self.LOG_FILE_LADISH: + self.fLogStreamLADISH.flush() + self.fLogFileLADISH.close() + self.fLogFileLADISH.open(QIODevice.WriteOnly) + self.fLogFileLADISH.close() + self.fLogFileLADISH.open(QIODevice.ReadOnly) + self.fPurgeLogs = False else: @@ -200,7 +234,17 @@ class LogsReadThread(QThread): else: textA2J = "" - self.fRealParent.setLogsText(textJACK, textA2J) + if self.LOG_FILE_LASH: + textLASH = fixLogText(self.fLogStreamLASH.readAll()).strip() + else: + textLASH = "" + + if self.LOG_FILE_LADISH: + textLADISH = fixLogText(self.fLogStreamLADISH.readAll()).strip() + else: + textLADISH = "" + + self.fRealParent.setLogsText(textJACK, textA2J, textLASH, textLADISH) self.updateLogs.emit() if not self.fCloseNow: @@ -215,6 +259,12 @@ class LogsReadThread(QThread): if self.LOG_FILE_A2J: self.fLogFileA2J.close() + if self.LOG_FILE_LASH: + self.fLogFileLASH.close() + + if self.LOG_FILE_LADISH: + self.fLogFileLADISH.close() + # ------------------------------------------------------------------------------------------------------------ # Logs Window @@ -223,6 +273,8 @@ class LogsW(QDialog): LOG_FILE_JACK = os.path.join(LOG_PATH, "jack", "jackdbus.log") LOG_FILE_A2J = os.path.join(LOG_PATH, "a2j", "a2j.log") + LOG_FILE_LASH = os.path.join(LOG_PATH, "lash", "lash.log") + LOG_FILE_LADISH = os.path.join(LOG_PATH, "ladish", "ladish.log") if not os.path.exists(LOG_FILE_JACK): LOG_FILE_JACK = None @@ -230,6 +282,12 @@ class LogsW(QDialog): if not os.path.exists(LOG_FILE_A2J): LOG_FILE_A2J = None + if not os.path.exists(LOG_FILE_LASH): + LOG_FILE_LASH = None + + if not os.path.exists(LOG_FILE_LADISH): + LOG_FILE_LADISH = None + SIGTERM = pyqtSignal() SIGUSR1 = pyqtSignal() SIGUSR2 = pyqtSignal() @@ -246,6 +304,8 @@ class LogsW(QDialog): self.fTextJACK = "" self.fTextA2J = "" + self.fTextLASH = "" + self.fTextLADISH = "" # ------------------------------------------------------------- # Set-up GUI @@ -266,6 +326,14 @@ class LogsW(QDialog): self.ui.tabWidget.removeTab(1 - tabIndex) tabIndex += 1 + if self.LOG_FILE_LASH is None: + self.ui.tabWidget.removeTab(2 - tabIndex) + tabIndex += 1 + + if self.LOG_FILE_LADISH is None: + self.ui.tabWidget.removeTab(3 - tabIndex) + tabIndex += 1 + # ------------------------------------------------------------- # Init logs viewers @@ -277,6 +345,14 @@ class LogsW(QDialog): self.fSyntaxA2J = SyntaxHighlighter_A2J(self.ui.pte_a2j) self.fSyntaxA2J.setDocument(self.ui.pte_a2j.document()) + if self.LOG_FILE_LASH: + self.fSyntaxLASH = SyntaxHighlighter_LASH(self.ui.pte_lash) + self.fSyntaxLASH.setDocument(self.ui.pte_lash.document()) + + if self.LOG_FILE_LADISH: + self.SyntaxLADISH = SyntaxHighlighter_LADISH(self.ui.pte_ladish) + self.SyntaxLADISH.setDocument(self.ui.pte_ladish.document()) + # ------------------------------------------------------------- # Init file read thread @@ -291,11 +367,13 @@ class LogsW(QDialog): # ------------------------------------------------------------- - def setLogsText(self, textJACK, textA2J): + def setLogsText(self, textJACK, textA2J, textLASH, textLADISH): QMutexLocker(self.fTextLock) self.fTextJACK = textJACK self.fTextA2J = textA2J + self.fTextLASH = textLASH + self.fTextLADISH = textLADISH @pyqtSlot() def slot_updateLogs(self): @@ -304,6 +382,8 @@ class LogsW(QDialog): if self.fFirstRun: self.ui.pte_jack.clear() self.ui.pte_a2j.clear() + self.ui.pte_lash.clear() + self.ui.pte_ladish.clear() if self.LOG_FILE_JACK and self.fTextJACK: self.ui.pte_jack.appendPlainText(self.fTextJACK) @@ -311,11 +391,21 @@ class LogsW(QDialog): if self.LOG_FILE_A2J and self.fTextA2J: self.ui.pte_a2j.appendPlainText(self.fTextA2J) + if self.LOG_FILE_LASH and self.fTextLASH: + self.ui.pte_lash.appendPlainText(self.fTextLASH) + + if self.LOG_FILE_LADISH and self.fTextLADISH: + self.ui.pte_ladish.appendPlainText(self.fTextLADISH) + if self.fFirstRun: self.ui.pte_jack.horizontalScrollBar().setValue(0) self.ui.pte_jack.verticalScrollBar().setValue(self.ui.pte_jack.verticalScrollBar().maximum()) self.ui.pte_a2j.horizontalScrollBar().setValue(0) self.ui.pte_a2j.verticalScrollBar().setValue(self.ui.pte_a2j.verticalScrollBar().maximum()) + self.ui.pte_lash.horizontalScrollBar().setValue(0) + self.ui.pte_lash.verticalScrollBar().setValue(self.ui.pte_lash.verticalScrollBar().maximum()) + self.ui.pte_ladish.horizontalScrollBar().setValue(0) + self.ui.pte_ladish.verticalScrollBar().setValue(self.ui.pte_ladish.verticalScrollBar().maximum()) self.fFirstRun = False @pyqtSlot() @@ -323,6 +413,8 @@ class LogsW(QDialog): self.fReadThread.purgeLogs() self.ui.pte_jack.clear() self.ui.pte_a2j.clear() + self.ui.pte_lash.clear() + self.ui.pte_ladish.clear() def loadSettings(self): settings = QSettings("Cadence", "Cadence-Logs") diff --git a/src/shared_settings.py b/src/shared_settings.py index 4b72986..19412d6 100644 --- a/src/shared_settings.py +++ b/src/shared_settings.py @@ -19,8 +19,12 @@ # ------------------------------------------------------------------------------------------------------------ # Imports (Global) -from PyQt5.QtCore import pyqtSlot, QSettings -from PyQt5.QtWidgets import QDialog, QDialogButtonBox +if True: + from PyQt5.QtCore import pyqtSlot, QSettings + from PyQt5.QtWidgets import QDialog, QDialogButtonBox +else: + from PyQt4.QtCore import pyqtSlot, QSettings + from PyQt4.QtGui import QDialog, QDialogButtonBox # ------------------------------------------------------------------------------------------------------------ # Imports (Custom Stuff) @@ -102,6 +106,13 @@ class SettingsW(QDialog): self.ui.lw_page.hideRow(TAB_INDEX_LADISH) self.ui.lw_page.setCurrentCell(TAB_INDEX_MAIN, 0) + elif appName == "claudia": + self.ui.cb_jack_port_alias.setEnabled(False) + self.ui.cb_jack_port_alias.setVisible(False) + self.ui.label_jack_port_alias.setEnabled(False) + self.ui.label_jack_port_alias.setVisible(False) + self.ui.lw_page.setCurrentCell(TAB_INDEX_MAIN, 0) + else: self.ui.lw_page.hideRow(TAB_INDEX_MAIN) self.ui.lw_page.hideRow(TAB_INDEX_CANVAS) diff --git a/src/systray.py b/src/systray.py index 8cfb7e7..34dcc3d 100755 --- a/src/systray.py +++ b/src/systray.py @@ -567,6 +567,41 @@ class GlobalSysTray(object): print("systray.py - Failed to get menu index for %s" % menu_name_id) return -1 + #def get_parent_menu_widget(self, parent_menu_id): + #if parent_menu_id != None: + #menu_index = self.get_menu_index(parent_menu_id) + #if menu_index >= 0: + #return self.menu_indexes[menu_index][1] + #else: + #print("systray.py::Failed to get parent Menu widget for", parent_menu_id) + #return None + #else: + #return self.menu + + #def remove_actions_by_menu_name_id(self, menu_name_id): + #h = 0 + #for i in range(len(self.act_indexes)): + #act_name_id, act_widget, parent_menu_id, act_func = self.act_indexes[i - h] + #if parent_menu_id == menu_name_id: + #self.act_indexes.pop(i - h) + #h += 1 + + #def remove_separators_by_menu_name_id(self, menu_name_id): + #h = 0 + #for i in range(len(self.sep_indexes)): + #sep_name_id, sep_widget, parent_menu_id = self.sep_indexes[i - h] + #if parent_menu_id == menu_name_id: + #self.sep_indexes.pop(i - h) + #h += 1 + + #def remove_submenus_by_menu_name_id(self, submenu_name_id): + #h = 0 + #for i in range(len(self.menu_indexes)): + #menu_name_id, menu_widget, parent_menu_id = self.menu_indexes[i - h] + #if parent_menu_id == submenu_name_id: + #self.menu_indexes.pop(i - h) + #h += 1 + # ------------------------------------------------------------------------------------------- def gtk_call_func(self, gtkmenu, act_name_id): @@ -615,3 +650,39 @@ class GlobalSysTray(object): def __raiseWindow(self): self._parent.activateWindow() self._parent.raise_() + +#--------------- main ------------------ +if __name__ == '__main__': + from PyQt5.QtWidgets import QApplication, QDialog, QMessageBox + + class ExampleGUI(QDialog): + def __init__(self, parent=None): + QDialog.__init__(self, parent) + + self.setWindowIcon(getIcon("audacity")) + + self.systray = GlobalSysTray(self, "Claudia", "claudia") + self.systray.addAction("about", self.tr("About")) + self.systray.setIcon("audacity") + self.systray.setToolTip("Demo systray app") + + self.systray.connect("about", self.about) + + self.systray.show() + + def about(self): + QMessageBox.about(self, self.tr("About"), self.tr("Systray Demo")) + + def done(self, r): + QDialog.done(self, r) + self.close() + + def closeEvent(self, event): + self.systray.close() + QDialog.closeEvent(self, event) + + app = QApplication(sys.argv) + setup_i18n() + gui = ExampleGUI() + gui.show() + sys.exit(gui.systray.exec_(app))