Browse Source

First go at backend-side canvas positions; Safer jack callbacks

Signed-off-by: falkTX <falktx@falktx.com>
tags/v2.2.0-RC1
falkTX 5 years ago
parent
commit
c178105a15
Signed by: falkTX <falktx@falktx.com> GPG Key ID: CDBAA37ABC74FBA0
18 changed files with 1324 additions and 337 deletions
  1. +11
    -1
      source/backend/CarlaBackend.h
  2. +10
    -3
      source/backend/CarlaEngine.hpp
  3. +9
    -0
      source/backend/CarlaHost.h
  4. +10
    -0
      source/backend/CarlaStandalone.cpp
  5. +188
    -25
      source/backend/engine/CarlaEngine.cpp
  6. +258
    -13
      source/backend/engine/CarlaEngineGraph.cpp
  7. +14
    -0
      source/backend/engine/CarlaEngineGraph.hpp
  8. +600
    -217
      source/backend/engine/CarlaEngineJack.cpp
  9. +16
    -0
      source/backend/engine/CarlaEngineNative.cpp
  10. +28
    -0
      source/frontend/carla_backend.py
  11. +1
    -0
      source/frontend/carla_backend_qt.py
  12. +21
    -19
      source/frontend/carla_host.py
  13. +14
    -12
      source/frontend/patchcanvas/__init__.py
  14. +57
    -25
      source/frontend/patchcanvas/canvasbox.py
  15. +63
    -2
      source/frontend/patchcanvas/patchcanvas.py
  16. +1
    -3
      source/frontend/patchcanvas/scene.py
  17. +2
    -0
      source/utils/CarlaBackendUtils.hpp
  18. +21
    -17
      source/utils/CarlaPatchbayUtils.hpp

+ 11
- 1
source/backend/CarlaBackend.h View File

@@ -1141,7 +1141,17 @@ typedef enum {
* @a value1 Parameter index * @a value1 Parameter index
* @a valueStr New mapped range as "%f:%f" syntax * @a valueStr New mapped range as "%f:%f" syntax
*/ */
ENGINE_CALLBACK_PARAMETER_MAPPED_RANGE_CHANGED = 46
ENGINE_CALLBACK_PARAMETER_MAPPED_RANGE_CHANGED = 46,

/*!
* A patchbay client position has changed.
* @a pluginId Client Id
* @a value1 X position 1
* @a value2 Y position 1
* @a value3 X position 2
* @a valuef Y position 2
*/
ENGINE_CALLBACK_PATCHBAY_CLIENT_POSITION_CHANGED = 47,


} EngineCallbackOpcode; } EngineCallbackOpcode;




+ 10
- 3
source/backend/CarlaEngine.hpp View File

@@ -1168,6 +1168,12 @@ public:
*/ */
virtual bool patchbayDisconnect(bool external, uint connectionId); virtual bool patchbayDisconnect(bool external, uint connectionId);


/*!
* Set the position of a group.
*/
virtual bool patchbaySetGroupPos(bool sendHost, bool sendOSC, bool external,
uint groupId, int x1, int y1, int x2, int y2);

/*! /*!
* Force the engine to resend all patchbay clients, ports and connections again. * Force the engine to resend all patchbay clients, ports and connections again.
*/ */
@@ -1348,10 +1354,11 @@ protected:
* Virtual functions for handling patchbay state. * Virtual functions for handling patchbay state.
* Do not free returned data. * Do not free returned data.
*/ */
struct PatchbayPosition { const char* name; int x1, y1, x2, y2; bool dealloc; };
virtual const char* const* getPatchbayConnections(bool external) const; virtual const char* const* getPatchbayConnections(bool external) const;
virtual void restorePatchbayConnection(bool external,
const char* sourcePort,
const char* targetPort);
virtual const PatchbayPosition* getPatchbayPositions(bool external, uint& count) const;
virtual void restorePatchbayConnection(bool external, const char* sourcePort, const char* targetPort);
virtual void restorePatchbayGroupPosition(bool external, const PatchbayPosition& ppos);


/*! /*!
* Virtual functions for handling external graph ports. * Virtual functions for handling external graph ports.


+ 9
- 0
source/backend/CarlaHost.h View File

@@ -550,6 +550,15 @@ CARLA_EXPORT bool carla_patchbay_connect(CarlaHostHandle handle, bool external,
*/ */
CARLA_EXPORT bool carla_patchbay_disconnect(CarlaHostHandle handle, bool external, uint connectionId); CARLA_EXPORT bool carla_patchbay_disconnect(CarlaHostHandle handle, bool external, uint connectionId);


/*!
* Set the position of a group.
* This is purely cached and saved in the project file, Carla backend does nothing with the value.
* When loading a project, callbacks are used to inform of the previously saved positions.
* @see ENGINE_CALLBACK_PATCHBAY_CLIENT_POSITION_CHANGED
*/
CARLA_EXPORT bool carla_patchbay_set_group_pos(CarlaHostHandle handle, bool external,
uint groupId, int x1, int y1, int x2, int y2);

/*! /*!
* Force the engine to resend all patchbay clients, ports and connections again. * Force the engine to resend all patchbay clients, ports and connections again.
* @param external Wherever to show external/hardware ports instead of internal ones. * @param external Wherever to show external/hardware ports instead of internal ones.


+ 10
- 0
source/backend/CarlaStandalone.cpp View File

@@ -970,6 +970,16 @@ bool carla_patchbay_disconnect(CarlaHostHandle handle, bool external, uint conne
return handle->engine->patchbayDisconnect(external, connectionId); return handle->engine->patchbayDisconnect(external, connectionId);
} }


bool carla_patchbay_set_group_pos(CarlaHostHandle handle, bool external, uint groupId, int x1, int y1, int x2, int y2)
{
CARLA_SAFE_ASSERT_WITH_LAST_ERROR_RETURN(handle->engine != nullptr, "Engine is not initialized", false);

carla_debug("carla_patchbay_set_group_pos(%p, %s, %u, %i, %i, %i, %i)",
handle, bool2str(external), groupId, x1, y1, x2, y2);

return handle->engine->patchbaySetGroupPos(true, false, external, groupId, x1, y1, x2, y2);
}

bool carla_patchbay_refresh(CarlaHostHandle handle, bool external) bool carla_patchbay_refresh(CarlaHostHandle handle, bool external)
{ {
CARLA_SAFE_ASSERT_WITH_LAST_ERROR_RETURN(handle->engine != nullptr, "Engine is not initialized", false); CARLA_SAFE_ASSERT_WITH_LAST_ERROR_RETURN(handle->engine != nullptr, "Engine is not initialized", false);


+ 188
- 25
source/backend/engine/CarlaEngine.cpp View File

@@ -2198,67 +2198,149 @@ void CarlaEngine::saveProjectInternal(water::MemoryOutputStream& outStream) cons
// save internal connections // save internal connections
if (pData->options.processMode == ENGINE_PROCESS_MODE_PATCHBAY) if (pData->options.processMode == ENGINE_PROCESS_MODE_PATCHBAY)
{ {
if (const char* const* const patchbayConns = getPatchbayConnections(false))
uint posCount = 0;
const char* const* const patchbayConns = getPatchbayConnections(false);
const PatchbayPosition* const patchbayPos = getPatchbayPositions(false, posCount);

if (patchbayConns != nullptr || patchbayPos != nullptr)
{ {
MemoryOutputStream outPatchbay(2048); MemoryOutputStream outPatchbay(2048);


outPatchbay << "\n <Patchbay>\n"; outPatchbay << "\n <Patchbay>\n";


for (int i=0; patchbayConns[i] != nullptr && patchbayConns[i+1] != nullptr; ++i, ++i )
if (patchbayConns != nullptr)
{ {
const char* const connSource(patchbayConns[i]);
const char* const connTarget(patchbayConns[i+1]);
for (int i=0; patchbayConns[i] != nullptr && patchbayConns[i+1] != nullptr; ++i, ++i)
{
const char* const connSource(patchbayConns[i]);
const char* const connTarget(patchbayConns[i+1]);


CARLA_SAFE_ASSERT_CONTINUE(connSource != nullptr && connSource[0] != '\0');
CARLA_SAFE_ASSERT_CONTINUE(connTarget != nullptr && connTarget[0] != '\0');
CARLA_SAFE_ASSERT_CONTINUE(connSource != nullptr && connSource[0] != '\0');
CARLA_SAFE_ASSERT_CONTINUE(connTarget != nullptr && connTarget[0] != '\0');


outPatchbay << " <Connection>\n";
outPatchbay << " <Source>" << xmlSafeString(connSource, true) << "</Source>\n";
outPatchbay << " <Target>" << xmlSafeString(connTarget, true) << "</Target>\n";
outPatchbay << " </Connection>\n";
outPatchbay << " <Connection>\n";
outPatchbay << " <Source>" << xmlSafeString(connSource, true) << "</Source>\n";
outPatchbay << " <Target>" << xmlSafeString(connTarget, true) << "</Target>\n";
outPatchbay << " </Connection>\n";
}
}

if (patchbayPos != nullptr && posCount != 0)
{
outPatchbay << " <Positions>\n";

for (uint i=0; i<posCount; ++i)
{
const PatchbayPosition& ppos(patchbayPos[i]);

CARLA_SAFE_ASSERT_CONTINUE(ppos.name != nullptr && ppos.name[0] != '\0');

outPatchbay << " <Position x1=\"" << ppos.x1 << "\" y1=\"" << ppos.y1;
if (ppos.x2 != 0 || ppos.y2 != 0)
outPatchbay << "\" x2=\"" << ppos.x2 << "\" y2=\"" << ppos.y2;
outPatchbay << "\">\n";
outPatchbay << " <Name>" << xmlSafeString(ppos.name, true) << "</Name>\n";
outPatchbay << " </Position>\n";

if (ppos.dealloc)
delete[] ppos.name;
}

outPatchbay << " </Positions>\n";
} }


outPatchbay << " </Patchbay>\n"; outPatchbay << " </Patchbay>\n";
outStream << outPatchbay; outStream << outPatchbay;

delete[] patchbayPos;
} }

} }


// if we're running inside some session-manager (and using JACK), let them handle the connections // if we're running inside some session-manager (and using JACK), let them handle the connections
bool saveExternalConnections;
bool saveExternalConnections, saveExternalPositions = true;


/**/ if (isPlugin) /**/ if (isPlugin)
{
saveExternalConnections = false; saveExternalConnections = false;
saveExternalPositions = false;
}
else if (std::strcmp(getCurrentDriverName(), "JACK") != 0) else if (std::strcmp(getCurrentDriverName(), "JACK") != 0)
{
saveExternalConnections = true; saveExternalConnections = true;
}
else if (std::getenv("CARLA_DONT_MANAGE_CONNECTIONS") != nullptr) else if (std::getenv("CARLA_DONT_MANAGE_CONNECTIONS") != nullptr)
{
saveExternalConnections = false; saveExternalConnections = false;
}
else if (std::getenv("LADISH_APP_NAME") != nullptr) else if (std::getenv("LADISH_APP_NAME") != nullptr)
{
saveExternalConnections = false; saveExternalConnections = false;
}
else if (std::getenv("NSM_URL") != nullptr) else if (std::getenv("NSM_URL") != nullptr)
{
saveExternalConnections = false; saveExternalConnections = false;
}
else else
{
saveExternalConnections = true; saveExternalConnections = true;
}


if (saveExternalConnections)
if (saveExternalConnections || saveExternalPositions)
{ {
if (const char* const* const patchbayConns = getPatchbayConnections(true))
uint posCount = 0;
const char* const* const patchbayConns = saveExternalConnections
? getPatchbayConnections(true)
: nullptr;
const PatchbayPosition* const patchbayPos = saveExternalPositions
? getPatchbayPositions(true, posCount)
: nullptr;

if (patchbayConns != nullptr || patchbayPos != nullptr)
{ {
MemoryOutputStream outPatchbay(2048); MemoryOutputStream outPatchbay(2048);


outPatchbay << "\n <ExternalPatchbay>\n"; outPatchbay << "\n <ExternalPatchbay>\n";


for (int i=0; patchbayConns[i] != nullptr && patchbayConns[i+1] != nullptr; ++i, ++i )
if (patchbayConns != nullptr)
{ {
const char* const connSource(patchbayConns[i]);
const char* const connTarget(patchbayConns[i+1]);
for (int i=0; patchbayConns[i] != nullptr && patchbayConns[i+1] != nullptr; ++i, ++i )
{
const char* const connSource(patchbayConns[i]);
const char* const connTarget(patchbayConns[i+1]);


CARLA_SAFE_ASSERT_CONTINUE(connSource != nullptr && connSource[0] != '\0');
CARLA_SAFE_ASSERT_CONTINUE(connTarget != nullptr && connTarget[0] != '\0');
CARLA_SAFE_ASSERT_CONTINUE(connSource != nullptr && connSource[0] != '\0');
CARLA_SAFE_ASSERT_CONTINUE(connTarget != nullptr && connTarget[0] != '\0');


outPatchbay << " <Connection>\n";
outPatchbay << " <Source>" << xmlSafeString(connSource, true) << "</Source>\n";
outPatchbay << " <Target>" << xmlSafeString(connTarget, true) << "</Target>\n";
outPatchbay << " </Connection>\n";
outPatchbay << " <Connection>\n";
outPatchbay << " <Source>" << xmlSafeString(connSource, true) << "</Source>\n";
outPatchbay << " <Target>" << xmlSafeString(connTarget, true) << "</Target>\n";
outPatchbay << " </Connection>\n";
}
}

if (patchbayPos != nullptr && posCount != 0)
{
outPatchbay << " <Positions>\n";

for (uint i=0; i<posCount; ++i)
{
const PatchbayPosition& ppos(patchbayPos[i]);

CARLA_SAFE_ASSERT_CONTINUE(ppos.name != nullptr && ppos.name[0] != '\0');

outPatchbay << " <Position x1=\"" << ppos.x1 << "\" y1=\"" << ppos.y1;
if (ppos.x2 != 0 || ppos.y2 != 0)
outPatchbay << "\" x2=\"" << ppos.x2 << "\" y2=\"" << ppos.y2;
outPatchbay << "\">\n";
outPatchbay << " <Name>" << xmlSafeString(ppos.name, true) << "</Name>\n";
outPatchbay << " </Position>\n";

if (ppos.dealloc)
delete[] ppos.name;
}

outPatchbay << " </Positions>\n";
} }


outPatchbay << " </ExternalPatchbay>\n"; outPatchbay << " </ExternalPatchbay>\n";
@@ -2843,8 +2925,9 @@ bool CarlaEngine::loadProjectInternal(water::XmlDocument& xmlDoc)
// plus external connections too // plus external connections too
if (loadExternalConnections) if (loadExternalConnections)
{ {
const bool loadingAsExternal = pData->options.processMode == ENGINE_PROCESS_MODE_PATCHBAY &&
hasInternalConnections;
bool loadingAsExternal = hasInternalConnections &&
(pData->options.processMode == ENGINE_PROCESS_MODE_CONTINUOUS_RACK ||
pData->options.processMode == ENGINE_PROCESS_MODE_PATCHBAY);


for (XmlElement* elem = xmlElement->getFirstChildElement(); elem != nullptr; elem = elem->getNextElement()) for (XmlElement* elem = xmlElement->getFirstChildElement(); elem != nullptr; elem = elem->getNextElement())
{ {
@@ -2857,7 +2940,11 @@ bool CarlaEngine::loadProjectInternal(water::XmlDocument& xmlDoc)
continue; continue;
} }
// or load external patchbay connections // or load external patchbay connections
else if (tagName != "ExternalPatchbay")
else if (tagName == "ExternalPatchbay")
{
loadingAsExternal = true;
}
else
{ {
continue; continue;
} }
@@ -2891,6 +2978,82 @@ bool CarlaEngine::loadProjectInternal(water::XmlDocument& xmlDoc)
break; break;
} }
} }

// finally, we handle positions
if (XmlElement* const elemPatchbay = xmlElement->getChildByName("Patchbay"))
{
if (XmlElement* const elemPositions = elemPatchbay->getChildByName("Positions"))
{
String name;
PatchbayPosition ppos = { nullptr, 0, 0, 0, 0, false };

for (XmlElement* patchElem = elemPositions->getFirstChildElement(); patchElem != nullptr; patchElem = patchElem->getNextElement())
{
const String& patchTag(patchElem->getTagName());

if (patchTag != "Position")
continue;

XmlElement* const patchName = patchElem->getChildByName("Name");
CARLA_SAFE_ASSERT_CONTINUE(patchName != nullptr);

const String nameText(patchName->getAllSubText().trim());
name = xmlSafeString(nameText, false);

ppos.name = name.toRawUTF8();
ppos.x1 = patchElem->getIntAttribute("x1");
ppos.y1 = patchElem->getIntAttribute("y1");
ppos.x2 = patchElem->getIntAttribute("x2");
ppos.y2 = patchElem->getIntAttribute("y2");

if (name.isNotEmpty())
restorePatchbayGroupPosition(false, ppos);
}

callback(true, true, ENGINE_CALLBACK_IDLE, 0, 0, 0, 0, 0.0f, nullptr);

if (pData->aboutToClose)
return true;

if (pData->actionCanceled)
{
setLastError("Project load canceled");
return false;
}
}
}

if (XmlElement* const elemPatchbay = xmlElement->getChildByName("ExternalPatchbay"))
{
if (XmlElement* const elemPositions = elemPatchbay->getChildByName("Positions"))
{
String name;
PatchbayPosition ppos = { nullptr, 0, 0, 0, 0, false };

for (XmlElement* patchElem = elemPositions->getFirstChildElement(); patchElem != nullptr; patchElem = patchElem->getNextElement())
{
const String& patchTag(patchElem->getTagName());

if (patchTag != "Position")
continue;

XmlElement* const patchName = patchElem->getChildByName("Name");
CARLA_SAFE_ASSERT_CONTINUE(patchName != nullptr);

const String nameText(patchName->getAllSubText().trim());
name = xmlSafeString(nameText, false);

ppos.name = name.toRawUTF8();
ppos.x1 = patchElem->getIntAttribute("x1");
ppos.y1 = patchElem->getIntAttribute("y1");
ppos.x2 = patchElem->getIntAttribute("x2");
ppos.y2 = patchElem->getIntAttribute("y2");

if (name.isNotEmpty())
restorePatchbayGroupPosition(true, ppos);
}
}
}
#endif #endif


if (pData->options.resetXruns) if (pData->options.resetXruns)


+ 258
- 13
source/backend/engine/CarlaEngineGraph.cpp View File

@@ -134,8 +134,12 @@ ExternalGraph::ExternalGraph(CarlaEngine* const engine) noexcept
: connections(), : connections(),
audioPorts(), audioPorts(),
midiPorts(), midiPorts(),
positions(),
retCon(), retCon(),
kEngine(engine) {}
kEngine(engine)
{
carla_zeroStruct(positions);
}


void ExternalGraph::clear() noexcept void ExternalGraph::clear() noexcept
{ {
@@ -313,11 +317,24 @@ bool ExternalGraph::disconnect(const bool sendHost, const bool sendOSC,
return false; return false;
} }


void ExternalGraph::setGroupPos(const bool sendHost, const bool sendOSC,
const uint groupId, const int x1, const int y1, const int x2, const int y2)
{
CARLA_SAFE_ASSERT_UINT_RETURN(groupId >= kExternalGraphGroupCarla && groupId < kExternalGraphGroupMax, groupId,);

positions[groupId] = { true, x1, y1, x2, y2 };

kEngine->callback(sendHost, sendOSC,
ENGINE_CALLBACK_PATCHBAY_CLIENT_POSITION_CHANGED,
groupId, x1, y1, x2, static_cast<float>(y2),
nullptr);
}

void ExternalGraph::refresh(const bool sendHost, const bool sendOSC, const char* const deviceName) void ExternalGraph::refresh(const bool sendHost, const bool sendOSC, const char* const deviceName)
{ {
CARLA_SAFE_ASSERT_RETURN(deviceName != nullptr,); CARLA_SAFE_ASSERT_RETURN(deviceName != nullptr,);


const bool isRack(kEngine->getOptions().processMode == ENGINE_PROCESS_MODE_CONTINUOUS_RACK);
const bool isRack = kEngine->getOptions().processMode == ENGINE_PROCESS_MODE_CONTINUOUS_RACK;


// Main // Main
{ {
@@ -591,6 +608,39 @@ const char* const* ExternalGraph::getConnections() const noexcept
return retCon; return retCon;
} }


bool ExternalGraph::getGroupFromName(const char* groupName, uint& groupId) const noexcept
{
CARLA_SAFE_ASSERT_RETURN(groupName != nullptr && groupName[0] != '\0', false);

if (std::strcmp(groupName, "Carla") == 0)
{
groupId = kExternalGraphGroupCarla;
return true;
}
if (std::strcmp(groupName, "AudioIn") == 0)
{
groupId = kExternalGraphGroupAudioIn;
return true;
}
if (std::strcmp(groupName, "AudioOut") == 0)
{
groupId = kExternalGraphGroupAudioOut;
return true;
}
if (std::strcmp(groupName, "MidiIn") == 0)
{
groupId = kExternalGraphGroupMidiIn;
return true;
}
if (std::strcmp(groupName, "MidiOut") == 0)
{
groupId = kExternalGraphGroupMidiOut;
return true;
}

return false;
}

bool ExternalGraph::getGroupAndPortIdFromFullName(const char* const fullPortName, uint& groupId, uint& portId) const noexcept bool ExternalGraph::getGroupAndPortIdFromFullName(const char* const fullPortName, uint& groupId, uint& portId) const noexcept
{ {
CARLA_SAFE_ASSERT_RETURN(fullPortName != nullptr && fullPortName[0] != '\0', false); CARLA_SAFE_ASSERT_RETURN(fullPortName != nullptr && fullPortName[0] != '\0', false);
@@ -1230,16 +1280,19 @@ const String getProcessorFullPortName(AudioProcessor* const proc, const uint32_t


static inline static inline
void addNodeToPatchbay(const bool sendHost, const bool sendOSC, CarlaEngine* const engine, void addNodeToPatchbay(const bool sendHost, const bool sendOSC, CarlaEngine* const engine,
const uint32_t groupId, const int clientId, const AudioProcessor* const proc)
AudioProcessorGraph::Node* const node, const int pluginId, const AudioProcessor* const proc)
{ {
CARLA_SAFE_ASSERT_RETURN(engine != nullptr,); CARLA_SAFE_ASSERT_RETURN(engine != nullptr,);
CARLA_SAFE_ASSERT_RETURN(node != nullptr,);
CARLA_SAFE_ASSERT_RETURN(proc != nullptr,); CARLA_SAFE_ASSERT_RETURN(proc != nullptr,);


const uint groupId = node->nodeId;

engine->callback(sendHost, sendOSC, engine->callback(sendHost, sendOSC,
ENGINE_CALLBACK_PATCHBAY_CLIENT_ADDED, ENGINE_CALLBACK_PATCHBAY_CLIENT_ADDED,
groupId, groupId,
clientId >= 0 ? PATCHBAY_ICON_PLUGIN : PATCHBAY_ICON_HARDWARE,
clientId,
pluginId >= 0 ? PATCHBAY_ICON_PLUGIN : PATCHBAY_ICON_HARDWARE,
pluginId,
0, 0.0f, 0, 0.0f,
proc->getName().toRawUTF8()); proc->getName().toRawUTF8());


@@ -1308,6 +1361,18 @@ void addNodeToPatchbay(const bool sendHost, const bool sendOSC, CarlaEngine* con
0, 0.0f, 0, 0.0f,
proc->getOutputChannelName(AudioProcessor::ChannelTypeMIDI, i).toRawUTF8()); proc->getOutputChannelName(AudioProcessor::ChannelTypeMIDI, i).toRawUTF8());
} }

if (node->properties.contains("x1"))
{
engine->callback(sendHost, sendOSC,
ENGINE_CALLBACK_PATCHBAY_CLIENT_POSITION_CHANGED,
groupId,
node->properties.getWithDefault("x1", 0),
node->properties.getWithDefault("y1", 0),
node->properties.getWithDefault("x2", 0),
static_cast<float>(node->properties.getWithDefault("y2", 0)),
nullptr);
}
} }


static inline static inline
@@ -1813,7 +1878,7 @@ void PatchbayGraph::addPlugin(CarlaPlugin* const plugin)
node->properties.set("isPlugin", true); node->properties.set("isPlugin", true);
node->properties.set("pluginId", static_cast<int>(plugin->getId())); node->properties.set("pluginId", static_cast<int>(plugin->getId()));


addNodeToPatchbay(sendHost, sendOSC, kEngine, node->nodeId, static_cast<int>(plugin->getId()), instance);
addNodeToPatchbay(sendHost, sendOSC, kEngine, node, static_cast<int>(plugin->getId()), instance);
} }


void PatchbayGraph::replacePlugin(CarlaPlugin* const oldPlugin, CarlaPlugin* const newPlugin) void PatchbayGraph::replacePlugin(CarlaPlugin* const oldPlugin, CarlaPlugin* const newPlugin)
@@ -1845,7 +1910,7 @@ void PatchbayGraph::replacePlugin(CarlaPlugin* const oldPlugin, CarlaPlugin* con
node->properties.set("isPlugin", true); node->properties.set("isPlugin", true);
node->properties.set("pluginId", static_cast<int>(newPlugin->getId())); node->properties.set("pluginId", static_cast<int>(newPlugin->getId()));


addNodeToPatchbay(sendHost, sendOSC, kEngine, node->nodeId, static_cast<int>(newPlugin->getId()), instance);
addNodeToPatchbay(sendHost, sendOSC, kEngine, node, static_cast<int>(newPlugin->getId()), instance);
} }


void PatchbayGraph::renamePlugin(CarlaPlugin* const plugin, const char* const newName) void PatchbayGraph::renamePlugin(CarlaPlugin* const plugin, const char* const newName)
@@ -2093,6 +2158,26 @@ void PatchbayGraph::disconnectInternalGroup(const uint groupId) noexcept
} }
} }


void PatchbayGraph::setGroupPos(const bool sendHost, const bool sendOSC, const bool external,
uint groupId, int x1, int y1, int x2, int y2)
{
if (external)
return extGraph.setGroupPos(sendHost, sendOSC, groupId, x1, y1, x2, y2);

AudioProcessorGraph::Node* const node(graph.getNodeForId(groupId));
CARLA_SAFE_ASSERT_RETURN(node != nullptr,);

node->properties.set("x1", x1);
node->properties.set("y1", y1);
node->properties.set("x2", x2);
node->properties.set("y2", y2);

kEngine->callback(sendHost, sendOSC,
ENGINE_CALLBACK_PATCHBAY_CLIENT_POSITION_CHANGED,
groupId, x1, y1, x2, static_cast<float>(y2),
nullptr);
}

void PatchbayGraph::refresh(const bool sendHost, const bool sendOSC, const bool external, const char* const deviceName) void PatchbayGraph::refresh(const bool sendHost, const bool sendOSC, const bool external, const char* const deviceName)
{ {
if (external) if (external)
@@ -2111,13 +2196,13 @@ void PatchbayGraph::refresh(const bool sendHost, const bool sendOSC, const bool
AudioProcessor* const proc(node->getProcessor()); AudioProcessor* const proc(node->getProcessor());
CARLA_SAFE_ASSERT_CONTINUE(proc != nullptr); CARLA_SAFE_ASSERT_CONTINUE(proc != nullptr);


int clientId = -1;
int pluginId = -1;


// plugin node // plugin node
if (node->properties.getWithDefault("isPlugin", false) == water::var(true)) if (node->properties.getWithDefault("isPlugin", false) == water::var(true))
clientId = node->properties.getWithDefault("pluginId", -1);
pluginId = node->properties.getWithDefault("pluginId", -1);


addNodeToPatchbay(sendHost, sendOSC, kEngine, node->nodeId, clientId, proc);
addNodeToPatchbay(sendHost, sendOSC, kEngine, node, pluginId, proc);
} }


char strBuf[STR_MAX+1]; char strBuf[STR_MAX+1];
@@ -2212,6 +2297,115 @@ const char* const* PatchbayGraph::getConnections(const bool external) const
return retCon; return retCon;
} }


const CarlaEngine::PatchbayPosition* PatchbayGraph::getPositions(bool external, uint& count) const
{
CarlaEngine::PatchbayPosition* ret;

if (external)
{
try {
ret = new CarlaEngine::PatchbayPosition[kExternalGraphGroupMax];
} CARLA_SAFE_EXCEPTION_RETURN("new CarlaEngine::PatchbayPosition", nullptr);

count = 0;

for (uint i=kExternalGraphGroupCarla; i<kExternalGraphGroupMax; ++i)
{
const PatchbayPosition& eppos(extGraph.positions[i]);

if (! eppos.active)
continue;

CarlaEngine::PatchbayPosition& ppos(ret[count++]);

switch (i)
{
case kExternalGraphGroupCarla:
ppos.name = kEngine->getName();
break;
case kExternalGraphGroupAudioIn:
ppos.name = "Capture";
break;
case kExternalGraphGroupAudioOut:
ppos.name = "Playback";
break;
case kExternalGraphGroupMidiIn:
ppos.name = "Readable MIDI ports";
break;
case kExternalGraphGroupMidiOut:
ppos.name = "Writable MIDI ports";
break;
}
ppos.dealloc = false;

ppos.x1 = eppos.x1;
ppos.y1 = eppos.y1;
ppos.x2 = eppos.x2;
ppos.y2 = eppos.y2;
}

return ret;
}
else
{
const int numNodes = graph.getNumNodes();
CARLA_SAFE_ASSERT_RETURN(numNodes > 0, nullptr);

try {
ret = new CarlaEngine::PatchbayPosition[numNodes];
} CARLA_SAFE_EXCEPTION_RETURN("new CarlaEngine::PatchbayPosition", nullptr);

count = 0;

for (int i=numNodes; --i >= 0;)
{
AudioProcessorGraph::Node* const node(graph.getNode(i));
CARLA_SAFE_ASSERT_CONTINUE(node != nullptr);

if (! node->properties.contains("x1"))
continue;

AudioProcessor* const proc(node->getProcessor());
CARLA_SAFE_ASSERT_CONTINUE(proc != nullptr);

CarlaEngine::PatchbayPosition& ppos(ret[count++]);

ppos.name = carla_strdup(proc->getName().toRawUTF8());
ppos.dealloc = true;

ppos.x1 = node->properties.getWithDefault("x1", 0);
ppos.y1 = node->properties.getWithDefault("y1", 0);
ppos.x2 = node->properties.getWithDefault("x2", 0);
ppos.y2 = node->properties.getWithDefault("y2", 0);
}

return ret;
}
}

bool PatchbayGraph::getGroupFromName(bool external, const char* groupName, uint& groupId) const
{
if (external)
return extGraph.getGroupFromName(groupName, groupId);

for (int i=0, count=graph.getNumNodes(); i<count; ++i)
{
AudioProcessorGraph::Node* const node(graph.getNode(i));
CARLA_SAFE_ASSERT_CONTINUE(node != nullptr);

AudioProcessor* const proc(node->getProcessor());
CARLA_SAFE_ASSERT_CONTINUE(proc != nullptr);

if (proc->getName() != groupName)
continue;

groupId = node->nodeId;
return true;
}

return false;
}

bool PatchbayGraph::getGroupAndPortIdFromFullName(const bool external, const char* const fullPortName, uint& groupId, uint& portId) const bool PatchbayGraph::getGroupAndPortIdFromFullName(const bool external, const char* const fullPortName, uint& groupId, uint& portId) const
{ {
if (external) if (external)
@@ -2607,8 +2801,6 @@ bool CarlaEngine::patchbayConnect(const bool external,


return graph->connect(external, groupA, portA, groupB, portB); return graph->connect(external, groupA, portA, groupB, portB);
} }

return false;
} }


bool CarlaEngine::patchbayDisconnect(const bool external, const uint connectionId) bool CarlaEngine::patchbayDisconnect(const bool external, const uint connectionId)
@@ -2631,8 +2823,28 @@ bool CarlaEngine::patchbayDisconnect(const bool external, const uint connectionI


return graph->disconnect(external, connectionId); return graph->disconnect(external, connectionId);
} }
}


return false;
bool CarlaEngine::patchbaySetGroupPos(const bool sendHost, const bool sendOSC, const bool external,
const uint groupId, const int x1, const int y1, const int x2, const int y2)
{
CARLA_SAFE_ASSERT_RETURN(pData->options.processMode == ENGINE_PROCESS_MODE_CONTINUOUS_RACK || pData->options.processMode == ENGINE_PROCESS_MODE_PATCHBAY, false);
CARLA_SAFE_ASSERT_RETURN(pData->graph.isReady(), false);
carla_debug("CarlaEngine::patchbaySetGroupPos(%u, %i, %i, %i, %i)", groupId, x1, y1, x2, y2);

if (pData->options.processMode == ENGINE_PROCESS_MODE_CONTINUOUS_RACK)
{
// we don't bother to save position in this case, there is only midi in/out
return true;
}
else
{
PatchbayGraph* const graph = pData->graph.getPatchbayGraph();
CARLA_SAFE_ASSERT_RETURN(graph != nullptr, false);

graph->setGroupPos(sendHost, sendOSC, external, groupId, x1, y1, x2, y2);
return true;
}
} }


bool CarlaEngine::patchbayRefresh(const bool sendHost, const bool sendOSC, const bool external) bool CarlaEngine::patchbayRefresh(const bool sendHost, const bool sendOSC, const bool external)
@@ -2687,6 +2899,22 @@ const char* const* CarlaEngine::getPatchbayConnections(const bool external) cons
return nullptr; return nullptr;
} }


const CarlaEngine::PatchbayPosition* CarlaEngine::getPatchbayPositions(bool external, uint& count) const
{
CARLA_SAFE_ASSERT_RETURN(pData->graph.isReady(), nullptr);
carla_debug("CarlaEngine::getPatchbayPositions(%s)", bool2str(external));

if (pData->options.processMode == ENGINE_PROCESS_MODE_PATCHBAY)
{
PatchbayGraph* const graph = pData->graph.getPatchbayGraph();
CARLA_SAFE_ASSERT_RETURN(graph != nullptr, nullptr);

return graph->getPositions(external, count);
}

return nullptr;
}

void CarlaEngine::restorePatchbayConnection(const bool external, void CarlaEngine::restorePatchbayConnection(const bool external,
const char* const sourcePort, const char* const sourcePort,
const char* const targetPort) const char* const targetPort)
@@ -2726,6 +2954,23 @@ void CarlaEngine::restorePatchbayConnection(const bool external,
} }
} }


void CarlaEngine::restorePatchbayGroupPosition(const bool external, const PatchbayPosition& ppos)
{
CARLA_SAFE_ASSERT_RETURN(pData->graph.isReady(),);
CARLA_SAFE_ASSERT_RETURN(ppos.name != nullptr && ppos.name[0] != '\0',);

if (pData->options.processMode == ENGINE_PROCESS_MODE_PATCHBAY)
{
PatchbayGraph* const graph = pData->graph.getPatchbayGraph();
CARLA_SAFE_ASSERT_RETURN(graph != nullptr,);

uint groupId;
CARLA_SAFE_ASSERT_RETURN(graph->getGroupFromName(external, ppos.name, groupId),);

graph->setGroupPos(true, true, external, groupId, ppos.x1, ppos.y1, ppos.x2, ppos.y2);
}
}

// ----------------------------------------------------------------------- // -----------------------------------------------------------------------


bool CarlaEngine::connectExternalGraphPort(const uint connectionType, const uint portId, const char* const portName) bool CarlaEngine::connectExternalGraphPort(const uint connectionType, const uint portId, const char* const portName)


+ 14
- 0
source/backend/engine/CarlaEngineGraph.hpp View File

@@ -33,6 +33,13 @@ using water::MidiBuffer;


CARLA_BACKEND_START_NAMESPACE CARLA_BACKEND_START_NAMESPACE


// -----------------------------------------------------------------------

struct PatchbayPosition {
bool active;
int x1, y1, x2, y2;
};

// ----------------------------------------------------------------------- // -----------------------------------------------------------------------
// External Graph stuff // External Graph stuff


@@ -80,6 +87,7 @@ struct ExternalGraphPorts {
struct ExternalGraph { struct ExternalGraph {
PatchbayConnectionList connections; PatchbayConnectionList connections;
ExternalGraphPorts audioPorts, midiPorts; ExternalGraphPorts audioPorts, midiPorts;
PatchbayPosition positions[kExternalGraphGroupMax];
mutable CharStringListPtr retCon; mutable CharStringListPtr retCon;
ExternalGraph(CarlaEngine* engine) noexcept; ExternalGraph(CarlaEngine* engine) noexcept;


@@ -89,10 +97,13 @@ struct ExternalGraph {
uint groupA, uint portA, uint groupB, uint portB) noexcept; uint groupA, uint portA, uint groupB, uint portB) noexcept;
bool disconnect(bool sendHost, bool sendOSC, bool disconnect(bool sendHost, bool sendOSC,
uint connectionId) noexcept; uint connectionId) noexcept;
void setGroupPos(bool sendHost, bool sendOSC,
uint groupId, int x1, int y1, int x2, int y2);
void refresh(bool sendHost, bool sendOSC, void refresh(bool sendHost, bool sendOSC,
const char* deviceName); const char* deviceName);


const char* const* getConnections() const noexcept; const char* const* getConnections() const noexcept;
bool getGroupFromName(const char* groupName, uint& groupId) const noexcept;
bool getGroupAndPortIdFromFullName(const char* fullPortName, uint& groupId, uint& portId) const noexcept; bool getGroupAndPortIdFromFullName(const char* fullPortName, uint& groupId, uint& portId) const noexcept;


CarlaEngine* const kEngine; CarlaEngine* const kEngine;
@@ -190,9 +201,12 @@ public:
bool connect(bool external, uint groupA, uint portA, uint groupB, uint portB); bool connect(bool external, uint groupA, uint portA, uint groupB, uint portB);
bool disconnect(bool external, uint connectionId); bool disconnect(bool external, uint connectionId);
void disconnectInternalGroup(uint groupId) noexcept; void disconnectInternalGroup(uint groupId) noexcept;
void setGroupPos(bool sendHost, bool sendOsc, bool external, uint groupId, int x1, int y1, int x2, int y2);
void refresh(bool sendHost, bool sendOsc, bool external, const char* deviceName); void refresh(bool sendHost, bool sendOsc, bool external, const char* deviceName);


const char* const* getConnections(bool external) const; const char* const* getConnections(bool external) const;
const CarlaEngine::PatchbayPosition* getPositions(bool external, uint& count) const;
bool getGroupFromName(bool external, const char* groupName, uint& groupId) const;
bool getGroupAndPortIdFromFullName(bool external, const char* fullPortName, uint& groupId, uint& portId) const; bool getGroupAndPortIdFromFullName(bool external, const char* fullPortName, uint& groupId, uint& portId) const;


void process(CarlaEngine::ProtectedData* data, void process(CarlaEngine::ProtectedData* data,


+ 600
- 217
source/backend/engine/CarlaEngineJack.cpp
File diff suppressed because it is too large
View File


+ 16
- 0
source/backend/engine/CarlaEngineNative.cpp View File

@@ -1816,6 +1816,22 @@ bool CarlaEngineNativeUI::msgReceived(const char* const msg) noexcept
ok = fEngine->patchbayDisconnect(external, connectionId); ok = fEngine->patchbayDisconnect(external, connectionId);
} CARLA_SAFE_EXCEPTION("patchbayDisconnect"); } CARLA_SAFE_EXCEPTION("patchbayDisconnect");
} }
else if (std::strcmp(msg, "patchbay_set_group_pos") == 0)
{
bool external;
uint32_t groupId;
int x1, y1, x2, y2;
CARLA_SAFE_ASSERT_RETURN(readNextLineAsBool(external), true);
CARLA_SAFE_ASSERT_RETURN(readNextLineAsUInt(groupId), true);
CARLA_SAFE_ASSERT_RETURN(readNextLineAsInt(x1), true);
CARLA_SAFE_ASSERT_RETURN(readNextLineAsInt(y1), true);
CARLA_SAFE_ASSERT_RETURN(readNextLineAsInt(x2), true);
CARLA_SAFE_ASSERT_RETURN(readNextLineAsInt(y2), true);

try {
ok = fEngine->patchbaySetGroupPos(true, false, external, groupId, x1, y1, x2, y2);
} CARLA_SAFE_EXCEPTION("patchbaySetGroupPos");
}
else if (std::strcmp(msg, "patchbay_refresh") == 0) else if (std::strcmp(msg, "patchbay_refresh") == 0)
{ {
bool external; bool external;


+ 28
- 0
source/frontend/carla_backend.py View File

@@ -818,6 +818,14 @@ ENGINE_CALLBACK_PATCHBAY_PORT_GROUP_CHANGED = 45
# @a valueStr New mapped range as "%f:%f" syntax # @a valueStr New mapped range as "%f:%f" syntax
ENGINE_CALLBACK_PARAMETER_MAPPED_RANGE_CHANGED = 46 ENGINE_CALLBACK_PARAMETER_MAPPED_RANGE_CHANGED = 46


# A patchbay client position has changed.
# @a pluginId Client Id
# @a value1 X position 1
# @a value2 Y position 1
# @a value3 X position 2
# @a valuef Y position 2
ENGINE_CALLBACK_PATCHBAY_CLIENT_POSITION_CHANGED = 47

# ------------------------------------------------------------------------------------------------------------ # ------------------------------------------------------------------------------------------------------------
# NSM Callback Opcode # NSM Callback Opcode
# NSM callback opcodes. # NSM callback opcodes.
@@ -1678,6 +1686,14 @@ class CarlaHostMeta(object):
def patchbay_disconnect(self, external, connectionId): def patchbay_disconnect(self, external, connectionId):
raise NotImplementedError raise NotImplementedError


# Set the position of a group.
# This is purely cached and saved in the project file, Carla backend does nothing with the value.
# When loading a project, callbacks are used to inform of the previously saved positions.
# @see ENGINE_CALLBACK_PATCHBAY_CLIENT_POSITION_CHANGED
@abstractmethod
def patchbay_set_group_pos(self, external, groupId, x1, y1, x2, y2):
raise NotImplementedError

# Force the engine to resend all patchbay clients, ports and connections again. # Force the engine to resend all patchbay clients, ports and connections again.
# @param external Wherever to show external/hardware ports instead of internal ones. # @param external Wherever to show external/hardware ports instead of internal ones.
# Only valid in patchbay engine mode, other modes will ignore this. # Only valid in patchbay engine mode, other modes will ignore this.
@@ -2306,6 +2322,9 @@ class CarlaHostNull(CarlaHostMeta):
def patchbay_disconnect(self, external, connectionId): def patchbay_disconnect(self, external, connectionId):
return False return False


def patchbay_set_group_pos(self, external, groupId, x1, y1, x2, y2):
return False

def patchbay_refresh(self, external): def patchbay_refresh(self, external):
return False return False


@@ -2624,6 +2643,9 @@ class CarlaHostDLL(CarlaHostMeta):
self.lib.carla_patchbay_disconnect.argtypes = (c_void_p, c_bool, c_uint) self.lib.carla_patchbay_disconnect.argtypes = (c_void_p, c_bool, c_uint)
self.lib.carla_patchbay_disconnect.restype = c_bool self.lib.carla_patchbay_disconnect.restype = c_bool


self.lib.carla_patchbay_set_group_pos.argtypes = (c_void_p, c_bool, c_uint, c_int, c_int, c_int, c_int)
self.lib.carla_patchbay_set_group_pos.restype = c_bool

self.lib.carla_patchbay_refresh.argtypes = (c_void_p, c_bool) self.lib.carla_patchbay_refresh.argtypes = (c_void_p, c_bool)
self.lib.carla_patchbay_refresh.restype = c_bool self.lib.carla_patchbay_refresh.restype = c_bool


@@ -2933,6 +2955,9 @@ class CarlaHostDLL(CarlaHostMeta):
def patchbay_disconnect(self, external, connectionId): def patchbay_disconnect(self, external, connectionId):
return bool(self.lib.carla_patchbay_disconnect(self.handle, external, connectionId)) return bool(self.lib.carla_patchbay_disconnect(self.handle, external, connectionId))


def patchbay_set_group_pos(self, external, groupId, x1, y1, x2, y2):
return bool(self.lib.carla_patchbay_set_group_pos(self.handle, external, groupId, x1, y1, x2, y2))

def patchbay_refresh(self, external): def patchbay_refresh(self, external):
return bool(self.lib.carla_patchbay_refresh(self.handle, external)) return bool(self.lib.carla_patchbay_refresh(self.handle, external))


@@ -3325,6 +3350,9 @@ class CarlaHostPlugin(CarlaHostMeta):
def patchbay_disconnect(self, external, connectionId): def patchbay_disconnect(self, external, connectionId):
return self.sendMsgAndSetError(["patchbay_disconnect", external, connectionId]) return self.sendMsgAndSetError(["patchbay_disconnect", external, connectionId])


def patchbay_set_group_pos(self, external, groupId, x1, y1, x2, y2):
return self.sendMsgAndSetError(["patchbay_set_group_pos", external, groupId, x1, y1, x2, y2])

def patchbay_refresh(self, external): def patchbay_refresh(self, external):
return self.sendMsgAndSetError(["patchbay_refresh", external]) return self.sendMsgAndSetError(["patchbay_refresh", external])




+ 1
- 0
source/frontend/carla_backend_qt.py View File

@@ -56,6 +56,7 @@ class CarlaHostSignals(QObject):
PatchbayClientRemovedCallback = pyqtSignal(int) PatchbayClientRemovedCallback = pyqtSignal(int)
PatchbayClientRenamedCallback = pyqtSignal(int, str) PatchbayClientRenamedCallback = pyqtSignal(int, str)
PatchbayClientDataChangedCallback = pyqtSignal(int, int, int) PatchbayClientDataChangedCallback = pyqtSignal(int, int, int)
PatchbayClientPositionChangedCallback = pyqtSignal(int, int, int, int, int)
PatchbayPortAddedCallback = pyqtSignal(int, int, int, int, str) PatchbayPortAddedCallback = pyqtSignal(int, int, int, int, str)
PatchbayPortRemovedCallback = pyqtSignal(int, int) PatchbayPortRemovedCallback = pyqtSignal(int, int)
PatchbayPortChangedCallback = pyqtSignal(int, int, int, int, str) PatchbayPortChangedCallback = pyqtSignal(int, int, int, int, str)


+ 21
- 19
source/frontend/carla_host.py View File

@@ -505,7 +505,6 @@ class HostWindow(QMainWindow):
self.ui.graphicsView.verticalScrollBar().valueChanged.connect(self.slot_verticalScrollBarChanged) self.ui.graphicsView.verticalScrollBar().valueChanged.connect(self.slot_verticalScrollBarChanged)
self.ui.miniCanvasPreview.miniCanvasMoved.connect(self.slot_miniCanvasMoved) self.ui.miniCanvasPreview.miniCanvasMoved.connect(self.slot_miniCanvasMoved)
self.scene.scaleChanged.connect(self.slot_canvasScaleChanged) self.scene.scaleChanged.connect(self.slot_canvasScaleChanged)
self.scene.sceneGroupMoved.connect(self.slot_canvasItemMoved)
self.scene.pluginSelected.connect(self.slot_canvasPluginSelected) self.scene.pluginSelected.connect(self.slot_canvasPluginSelected)
self.scene.selectionChanged.connect(self.slot_canvasSelectionChanged) self.scene.selectionChanged.connect(self.slot_canvasSelectionChanged)


@@ -533,6 +532,7 @@ class HostWindow(QMainWindow):
host.PatchbayClientRemovedCallback.connect(self.slot_handlePatchbayClientRemovedCallback) host.PatchbayClientRemovedCallback.connect(self.slot_handlePatchbayClientRemovedCallback)
host.PatchbayClientRenamedCallback.connect(self.slot_handlePatchbayClientRenamedCallback) host.PatchbayClientRenamedCallback.connect(self.slot_handlePatchbayClientRenamedCallback)
host.PatchbayClientDataChangedCallback.connect(self.slot_handlePatchbayClientDataChangedCallback) host.PatchbayClientDataChangedCallback.connect(self.slot_handlePatchbayClientDataChangedCallback)
host.PatchbayClientPositionChangedCallback.connect(self.slot_handlePatchbayClientPositionChangedCallback)
host.PatchbayPortAddedCallback.connect(self.slot_handlePatchbayPortAddedCallback) host.PatchbayPortAddedCallback.connect(self.slot_handlePatchbayPortAddedCallback)
host.PatchbayPortRemovedCallback.connect(self.slot_handlePatchbayPortRemovedCallback) host.PatchbayPortRemovedCallback.connect(self.slot_handlePatchbayPortRemovedCallback)
host.PatchbayPortChangedCallback.connect(self.slot_handlePatchbayPortChangedCallback) host.PatchbayPortChangedCallback.connect(self.slot_handlePatchbayPortChangedCallback)
@@ -713,9 +713,6 @@ class HostWindow(QMainWindow):
# -------------------------------------------------------------------------------------------------------- # --------------------------------------------------------------------------------------------------------
# Files # Files


def makeExtraFilename(self):
return self.fProjectFilename.rsplit(".",1)[0]+".json"

def loadProjectNow(self): def loadProjectNow(self):
if not self.fProjectFilename: if not self.fProjectFilename:
return qCritical("ERROR: loading project without filename set") return qCritical("ERROR: loading project without filename set")
@@ -748,14 +745,6 @@ class HostWindow(QMainWindow):
QMessageBox.Ok, QMessageBox.Ok) QMessageBox.Ok, QMessageBox.Ok)
return return


if not self.fWithCanvas:
return

with open(self.makeExtraFilename(), 'w') as fh:
json.dump({
'canvas': patchcanvas.saveGroupPositions(),
}, fh)

def projectLoadingStarted(self): def projectLoadingStarted(self):
self.ui.rack.setEnabled(False) self.ui.rack.setEnabled(False)
self.ui.graphicsView.setEnabled(False) self.ui.graphicsView.setEnabled(False)
@@ -769,7 +758,7 @@ class HostWindow(QMainWindow):


QTimer.singleShot(1000, self.slot_canvasRefresh) QTimer.singleShot(1000, self.slot_canvasRefresh)


extrafile = self.makeExtraFilename()
extrafile = self.fProjectFilename.rsplit(".",1)[0]+".json"
if not os.path.exists(extrafile): if not os.path.exists(extrafile):
return return


@@ -1431,7 +1420,7 @@ class HostWindow(QMainWindow):
pFeatures.group_rename = False pFeatures.group_rename = False
pFeatures.port_info = False pFeatures.port_info = False
pFeatures.port_rename = False pFeatures.port_rename = False
pFeatures.handle_group_pos = True
pFeatures.handle_group_pos = False


patchcanvas.setOptions(pOptions) patchcanvas.setOptions(pOptions)
patchcanvas.setFeatures(pFeatures) patchcanvas.setFeatures(pFeatures)
@@ -1567,10 +1556,6 @@ class HostWindow(QMainWindow):
# -------------------------------------------------------------------------------------------------------- # --------------------------------------------------------------------------------------------------------
# Canvas (canvas callbacks) # Canvas (canvas callbacks)


@pyqtSlot(int, int, QPointF)
def slot_canvasItemMoved(self, group_id, split_mode, pos):
self.updateMiniCanvasLater()

@pyqtSlot() @pyqtSlot()
def slot_canvasSelectionChanged(self): def slot_canvasSelectionChanged(self):
self.updateMiniCanvasLater() self.updateMiniCanvasLater()
@@ -1668,6 +1653,10 @@ class HostWindow(QMainWindow):


patchcanvas.setGroupAsPlugin(clientId, pluginId, hasCustomUI, hasInlineDisplay) patchcanvas.setGroupAsPlugin(clientId, pluginId, hasCustomUI, hasInlineDisplay)


@pyqtSlot(int, int, int, int, int)
def slot_handlePatchbayClientPositionChangedCallback(self, clientId, x1, y1, x2, y2):
patchcanvas.setGroupPosFull(clientId, x1, y1, x2, y2)

@pyqtSlot(int, int, int, int, str) @pyqtSlot(int, int, int, int, str)
def slot_handlePatchbayPortAddedCallback(self, clientId, portId, portFlags, portGroupId, portName): def slot_handlePatchbayPortAddedCallback(self, clientId, portId, portFlags, portGroupId, portName):
if portFlags & PATCHBAY_PORT_IS_INPUT: if portFlags & PATCHBAY_PORT_IS_INPUT:
@@ -2720,6 +2709,9 @@ class HostWindow(QMainWindow):
def canvasCallback(action, value1, value2, valueStr): def canvasCallback(action, value1, value2, valueStr):
host = gCarla.gui.host host = gCarla.gui.host


if gCarla.gui.fCustomStopAction == HostWindow.CUSTOM_ACTION_APP_CLOSE:
return

if action == patchcanvas.ACTION_GROUP_INFO: if action == patchcanvas.ACTION_GROUP_INFO:
pass pass


@@ -2736,6 +2728,14 @@ def canvasCallback(action, value1, value2, valueStr):
patchcanvas.joinGroup(groupId) patchcanvas.joinGroup(groupId)
gCarla.gui.updateMiniCanvasLater() gCarla.gui.updateMiniCanvasLater()


elif action == patchcanvas.ACTION_GROUP_POSITION:
if gCarla.gui.fIsProjectLoading or not host.is_engine_running():
return
groupId = value1
x1, y1, x2, y2 = tuple(int(i) for i in valueStr.split(":"))
host.patchbay_set_group_pos(gCarla.gui.fExternalPatchbay, groupId, x1, y1, x2, y2)
gCarla.gui.updateMiniCanvasLater()

elif action == patchcanvas.ACTION_PORT_INFO: elif action == patchcanvas.ACTION_PORT_INFO:
pass pass


@@ -2743,7 +2743,7 @@ def canvasCallback(action, value1, value2, valueStr):
pass pass


elif action == patchcanvas.ACTION_PORTS_CONNECT: elif action == patchcanvas.ACTION_PORTS_CONNECT:
gOut, pOut, gIn, pIn = [int(i) for i in valueStr.split(":")]
gOut, pOut, gIn, pIn = tuple(int(i) for i in valueStr.split(":"))


if not host.patchbay_connect(gCarla.gui.fExternalPatchbay, gOut, pOut, gIn, pIn): if not host.patchbay_connect(gCarla.gui.fExternalPatchbay, gOut, pOut, gIn, pIn):
print("Connection failed:", host.get_last_error()) print("Connection failed:", host.get_last_error())
@@ -2875,6 +2875,8 @@ def engineCallback(host, action, pluginId, value1, value2, value3, valuef, value
host.PatchbayClientRenamedCallback.emit(pluginId, valueStr) host.PatchbayClientRenamedCallback.emit(pluginId, valueStr)
elif action == ENGINE_CALLBACK_PATCHBAY_CLIENT_DATA_CHANGED: elif action == ENGINE_CALLBACK_PATCHBAY_CLIENT_DATA_CHANGED:
host.PatchbayClientDataChangedCallback.emit(pluginId, value1, value2) host.PatchbayClientDataChangedCallback.emit(pluginId, value1, value2)
elif action == ENGINE_CALLBACK_PATCHBAY_CLIENT_POSITION_CHANGED:
host.PatchbayClientPositionChangedCallback.emit(pluginId, value1, value2, value3, int(round(valuef)))
elif action == ENGINE_CALLBACK_PATCHBAY_PORT_ADDED: elif action == ENGINE_CALLBACK_PATCHBAY_PORT_ADDED:
host.PatchbayPortAddedCallback.emit(pluginId, value1, value2, value3, valueStr) host.PatchbayPortAddedCallback.emit(pluginId, value1, value2, value3, valueStr)
elif action == ENGINE_CALLBACK_PATCHBAY_PORT_REMOVED: elif action == ENGINE_CALLBACK_PATCHBAY_PORT_REMOVED:


+ 14
- 12
source/frontend/patchcanvas/__init__.py View File

@@ -49,18 +49,19 @@ ACTION_GROUP_INFO = 0 # group_id, N, N
ACTION_GROUP_RENAME = 1 # group_id, N, N ACTION_GROUP_RENAME = 1 # group_id, N, N
ACTION_GROUP_SPLIT = 2 # group_id, N, N ACTION_GROUP_SPLIT = 2 # group_id, N, N
ACTION_GROUP_JOIN = 3 # group_id, N, N ACTION_GROUP_JOIN = 3 # group_id, N, N
ACTION_PORT_INFO = 4 # group_id, port_id, N
ACTION_PORT_RENAME = 5 # group_id, port_id, N
ACTION_PORTS_CONNECT = 6 # N, N, "outG:outP:inG:inP"
ACTION_PORTS_DISCONNECT = 7 # conn_id, N, N
ACTION_PLUGIN_CLONE = 8 # plugin_id, N, N
ACTION_PLUGIN_EDIT = 9 # plugin_id, N, N
ACTION_PLUGIN_RENAME = 10 # plugin_id, N, N
ACTION_PLUGIN_REPLACE = 11 # plugin_id, N, N
ACTION_PLUGIN_REMOVE = 12 # plugin_id, N, N
ACTION_PLUGIN_SHOW_UI = 13 # plugin_id, N, N
ACTION_BG_RIGHT_CLICK = 14 # N, N, N
ACTION_INLINE_DISPLAY = 15 # plugin_id, N, N
ACTION_GROUP_POSITION = 4 # group_id, N, N, "x1:y1:x2:y2"
ACTION_PORT_INFO = 5 # group_id, port_id, N
ACTION_PORT_RENAME = 6 # group_id, port_id, N
ACTION_PORTS_CONNECT = 7 # N, N, "outG:outP:inG:inP"
ACTION_PORTS_DISCONNECT = 8 # conn_id, N, N
ACTION_PLUGIN_CLONE = 9 # plugin_id, N, N
ACTION_PLUGIN_EDIT = 10 # plugin_id, N, N
ACTION_PLUGIN_RENAME = 11 # plugin_id, N, N
ACTION_PLUGIN_REPLACE = 12 # plugin_id, N, N
ACTION_PLUGIN_REMOVE = 13 # plugin_id, N, N
ACTION_PLUGIN_SHOW_UI = 14 # plugin_id, N, N
ACTION_BG_RIGHT_CLICK = 15 # N, N, N
ACTION_INLINE_DISPLAY = 16 # plugin_id, N, N


# Icon # Icon
ICON_APPLICATION = 0 ICON_APPLICATION = 0
@@ -134,6 +135,7 @@ class Canvas(object):
self.connection_list = [] self.connection_list = []
self.animation_list = [] self.animation_list = []
self.group_plugin_map = {} self.group_plugin_map = {}
self.old_group_pos = {}


self.callback = self.callback self.callback = self.callback
self.debug = False self.debug = False


+ 57
- 25
source/frontend/patchcanvas/canvasbox.py View File

@@ -22,9 +22,9 @@
from sip import voidptr from sip import voidptr
from struct import pack from struct import pack


from PyQt5.QtCore import qCritical, Qt, QPointF, QRectF, QTimer
from PyQt5.QtCore import pyqtSignal, pyqtSlot, qCritical, Qt, QPointF, QRectF, QTimer
from PyQt5.QtGui import QCursor, QFont, QFontMetrics, QImage, QLinearGradient, QPainter, QPen from PyQt5.QtGui import QCursor, QFont, QFontMetrics, QImage, QLinearGradient, QPainter, QPen
from PyQt5.QtWidgets import QGraphicsItem, QMenu
from PyQt5.QtWidgets import QGraphicsItem, QGraphicsObject, QMenu


# ------------------------------------------------------------------------------------------------------------ # ------------------------------------------------------------------------------------------------------------
# Imports (Custom) # Imports (Custom)
@@ -75,13 +75,17 @@ class cb_line_t(object):


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


class CanvasBox(QGraphicsItem):
class CanvasBox(QGraphicsObject):
# signals
positionChanged = pyqtSignal(int, bool, int, int)

# enums
INLINE_DISPLAY_DISABLED = 0 INLINE_DISPLAY_DISABLED = 0
INLINE_DISPLAY_ENABLED = 1 INLINE_DISPLAY_ENABLED = 1
INLINE_DISPLAY_CACHED = 2 INLINE_DISPLAY_CACHED = 2


def __init__(self, group_id, group_name, icon, parent=None): def __init__(self, group_id, group_name, icon, parent=None):
QGraphicsItem.__init__(self)
QGraphicsObject.__init__(self)
self.setParentItem(parent) self.setParentItem(parent)


# Save Variables, useful for later # Save Variables, useful for later
@@ -109,6 +113,7 @@ class CanvasBox(QGraphicsItem):
self.m_inline_data = None self.m_inline_data = None
self.m_inline_image = None self.m_inline_image = None
self.m_inline_scaling = 1.0 self.m_inline_scaling = 1.0
self.m_will_signal_pos_change = False


self.m_port_list_ids = [] self.m_port_list_ids = []
self.m_connection_lines = [] self.m_connection_lines = []
@@ -153,6 +158,10 @@ class CanvasBox(QGraphicsItem):


self.updatePositions() self.updatePositions()


self.visibleChanged.connect(self.slot_signalPositionChangedLater)
self.xChanged.connect(self.slot_signalPositionChangedLater)
self.yChanged.connect(self.slot_signalPositionChangedLater)

canvas.scene.addItem(self) canvas.scene.addItem(self)
QTimer.singleShot(0, self.fixPos) QTimer.singleShot(0, self.fixPos)


@@ -270,21 +279,31 @@ class CanvasBox(QGraphicsItem):
return return
qCritical("PatchCanvas::CanvasBox.removeLineFromGroup(%i) - unable to find line to remove" % connection_id) qCritical("PatchCanvas::CanvasBox.removeLineFromGroup(%i) - unable to find line to remove" % connection_id)


def checkItemPos(self):
if not canvas.size_rect.isNull():
pos = self.scenePos()
if not (canvas.size_rect.contains(pos) and
canvas.size_rect.contains(pos + QPointF(self.p_width, self.p_height))):
if pos.x() < canvas.size_rect.x():
self.setPos(canvas.size_rect.x(), pos.y())
elif pos.x() + self.p_width > canvas.size_rect.width():
self.setPos(canvas.size_rect.width() - self.p_width, pos.y())

pos = self.scenePos()
if pos.y() < canvas.size_rect.y():
self.setPos(pos.x(), canvas.size_rect.y())
elif pos.y() + self.p_height > canvas.size_rect.height():
self.setPos(pos.x(), canvas.size_rect.height() - self.p_height)
def checkItemPos(self, blockSignals):
if canvas.size_rect.isNull():
return

pos = self.scenePos()
if (canvas.size_rect.contains(pos) and
canvas.size_rect.contains(pos + QPointF(self.p_width, self.p_height))):
return

if blockSignals:
self.blockSignals(True)

if pos.x() < canvas.size_rect.x():
self.setPos(canvas.size_rect.x(), pos.y())
elif pos.x() + self.p_width > canvas.size_rect.width():
self.setPos(canvas.size_rect.width() - self.p_width, pos.y())

pos = self.scenePos()
if pos.y() < canvas.size_rect.y():
self.setPos(pos.x(), canvas.size_rect.y())
elif pos.y() + self.p_height > canvas.size_rect.height():
self.setPos(pos.x(), canvas.size_rect.height() - self.p_height)

if blockSignals:
self.blockSignals(False)


def removeIconFromScene(self): def removeIconFromScene(self):
if self.icon_svg is None: if self.icon_svg is None:
@@ -389,6 +408,17 @@ class CanvasBox(QGraphicsItem):


connection.widget.setZValue(z_value) connection.widget.setZValue(z_value)


def triggerSignalPositionChanged(self):
self.positionChanged.emit(self.m_group_id, self.m_splitted, self.x(), self.y())
self.m_will_signal_pos_change = False

@pyqtSlot()
def slot_signalPositionChangedLater(self):
if self.m_will_signal_pos_change:
return
self.m_will_signal_pos_change = True
QTimer.singleShot(0, self.triggerSignalPositionChanged)

def type(self): def type(self):
return CanvasBoxType return CanvasBoxType


@@ -570,14 +600,14 @@ class CanvasBox(QGraphicsItem):
event.accept() event.accept()
canvas.callback(ACTION_PLUGIN_REMOVE, self.m_plugin_id, 0, "") canvas.callback(ACTION_PLUGIN_REMOVE, self.m_plugin_id, 0, "")
return return
QGraphicsItem.keyPressEvent(self, event)
QGraphicsObject.keyPressEvent(self, event)


def hoverEnterEvent(self, event): def hoverEnterEvent(self, event):
if options.auto_select_items: if options.auto_select_items:
if len(canvas.scene.selectedItems()) > 0: if len(canvas.scene.selectedItems()) > 0:
canvas.scene.clearSelection() canvas.scene.clearSelection()
self.setSelected(True) self.setSelected(True)
QGraphicsItem.hoverEnterEvent(self, event)
QGraphicsObject.hoverEnterEvent(self, event)


def mouseDoubleClickEvent(self, event): def mouseDoubleClickEvent(self, event):
if self.m_plugin_id >= 0: if self.m_plugin_id >= 0:
@@ -585,7 +615,7 @@ class CanvasBox(QGraphicsItem):
canvas.callback(ACTION_PLUGIN_SHOW_UI if self.m_plugin_ui else ACTION_PLUGIN_EDIT, self.m_plugin_id, 0, "") canvas.callback(ACTION_PLUGIN_SHOW_UI if self.m_plugin_ui else ACTION_PLUGIN_EDIT, self.m_plugin_id, 0, "")
return return


QGraphicsItem.mouseDoubleClickEvent(self, event)
QGraphicsObject.mouseDoubleClickEvent(self, event)


def mousePressEvent(self, event): def mousePressEvent(self, event):
canvas.last_z_value += 1 canvas.last_z_value += 1
@@ -612,7 +642,7 @@ class CanvasBox(QGraphicsItem):
else: else:
self.m_mouse_down = False self.m_mouse_down = False


QGraphicsItem.mousePressEvent(self, event)
QGraphicsObject.mousePressEvent(self, event)


def mouseMoveEvent(self, event): def mouseMoveEvent(self, event):
if self.m_mouse_down: if self.m_mouse_down:
@@ -620,7 +650,7 @@ class CanvasBox(QGraphicsItem):
self.setCursor(QCursor(Qt.SizeAllCursor)) self.setCursor(QCursor(Qt.SizeAllCursor))
self.m_cursor_moving = True self.m_cursor_moving = True
self.repaintLines() self.repaintLines()
QGraphicsItem.mouseMoveEvent(self, event)
QGraphicsObject.mouseMoveEvent(self, event)


def mouseReleaseEvent(self, event): def mouseReleaseEvent(self, event):
if self.m_cursor_moving: if self.m_cursor_moving:
@@ -628,11 +658,13 @@ class CanvasBox(QGraphicsItem):
QTimer.singleShot(0, self.fixPos) QTimer.singleShot(0, self.fixPos)
self.m_mouse_down = False self.m_mouse_down = False
self.m_cursor_moving = False self.m_cursor_moving = False
QGraphicsItem.mouseReleaseEvent(self, event)
QGraphicsObject.mouseReleaseEvent(self, event)


def fixPos(self): def fixPos(self):
self.blockSignals(True)
self.setX(round(self.x())) self.setX(round(self.x()))
self.setY(round(self.y())) self.setY(round(self.y()))
self.blockSignals(False)


def boundingRect(self): def boundingRect(self):
return QRectF(0, 0, self.p_width, self.p_height) return QRectF(0, 0, self.p_width, self.p_height)


+ 63
- 2
source/frontend/patchcanvas/patchcanvas.py View File

@@ -138,6 +138,36 @@ class CanvasObject(QObject):


CanvasCallback(ACTION_PORTS_DISCONNECT, connectionId, 0, "") CanvasCallback(ACTION_PORTS_DISCONNECT, connectionId, 0, "")


@pyqtSlot(int, bool, int, int)
def boxPositionChanged(self, groupId, split, x, y):
x2 = y2 = 0

if split:
for group in canvas.group_list:
if group.group_id == groupId:
if group.split:
pos = group.widgets[1].pos()
x2 = pos.x()
y2 = pos.y()
break

valueStr = "%i:%i:%i:%i" % (x, y, x2, y2)
CanvasCallback(ACTION_GROUP_POSITION, groupId, 0, valueStr)

@pyqtSlot(int, bool, int, int)
def sboxPositionChanged(self, groupId, split, x2, y2):
x = y = 0

for group in canvas.group_list:
if group.group_id == groupId:
pos = group.widgets[0].pos()
x = pos.x()
y = pos.y()
break

valueStr = "%i:%i:%i:%i" % (x, y, x2, y2)
CanvasCallback(ACTION_GROUP_POSITION, groupId, 0, valueStr)

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


def getStoredCanvasPosition(key, fallback_pos): def getStoredCanvasPosition(key, fallback_pos):
@@ -201,11 +231,17 @@ def clear():
if canvas.debug: if canvas.debug:
print("PatchCanvas::clear()") print("PatchCanvas::clear()")


group_pos = {}
group_list_ids = [] group_list_ids = []
port_list_ids = [] port_list_ids = []
connection_list_ids = [] connection_list_ids = []


for group in canvas.group_list: for group in canvas.group_list:
group_pos[group.group_name] = (
group.split,
group.widgets[0].pos(),
group.widgets[1].pos() if group.split else None,
)
group_list_ids.append(group.group_id) group_list_ids.append(group.group_id)


for port in canvas.port_list: for port in canvas.port_list:
@@ -230,6 +266,7 @@ def clear():
canvas.port_list = [] canvas.port_list = []
canvas.connection_list = [] canvas.connection_list = []
canvas.group_plugin_map = {} canvas.group_plugin_map = {}
canvas.old_group_pos = group_pos


canvas.scene.clearSelection() canvas.scene.clearSelection()


@@ -278,6 +315,8 @@ def addGroup(group_id, group_name, split=SPLIT_UNDEF, icon=ICON_APPLICATION):
group_id, group_name.encode(), split2str(split), icon2str(icon))) group_id, group_name.encode(), split2str(split), icon2str(icon)))
return return


old_matching_group = canvas.old_group_pos.pop(group_name, None)

if split == SPLIT_UNDEF: if split == SPLIT_UNDEF:
isHardware = bool(icon == ICON_HARDWARE) isHardware = bool(icon == ICON_HARDWARE)


@@ -285,8 +324,11 @@ def addGroup(group_id, group_name, split=SPLIT_UNDEF, icon=ICON_APPLICATION):
split = getStoredCanvasSplit(group_name, SPLIT_YES if isHardware else split) split = getStoredCanvasSplit(group_name, SPLIT_YES if isHardware else split)
elif isHardware: elif isHardware:
split = SPLIT_YES split = SPLIT_YES
elif old_matching_group is not None and old_matching_group[0]:
split = SPLIT_YES


group_box = CanvasBox(group_id, group_name, icon) group_box = CanvasBox(group_id, group_name, icon)
group_box.positionChanged.connect(canvas.qobject.boxPositionChanged)


group_dict = group_dict_t() group_dict = group_dict_t()
group_dict.group_id = group_id group_dict.group_id = group_id
@@ -301,20 +343,29 @@ def addGroup(group_id, group_name, split=SPLIT_UNDEF, icon=ICON_APPLICATION):
if split == SPLIT_YES: if split == SPLIT_YES:
group_box.setSplit(True, PORT_MODE_OUTPUT) group_box.setSplit(True, PORT_MODE_OUTPUT)


group_box.blockSignals(True)
if features.handle_group_pos: if features.handle_group_pos:
group_box.setPos(getStoredCanvasPosition(group_name + "_OUTPUT", CanvasGetNewGroupPos(False))) group_box.setPos(getStoredCanvasPosition(group_name + "_OUTPUT", CanvasGetNewGroupPos(False)))
elif old_matching_group is not None:
group_box.setPos(old_matching_group[1])
else: else:
group_box.setPos(CanvasGetNewGroupPos(False)) group_box.setPos(CanvasGetNewGroupPos(False))
group_box.blockSignals(False)


group_sbox = CanvasBox(group_id, group_name, icon) group_sbox = CanvasBox(group_id, group_name, icon)
group_sbox.setSplit(True, PORT_MODE_INPUT) group_sbox.setSplit(True, PORT_MODE_INPUT)
group_sbox.positionChanged.connect(canvas.qobject.sboxPositionChanged)


group_dict.widgets[1] = group_sbox group_dict.widgets[1] = group_sbox


group_sbox.blockSignals(True)
if features.handle_group_pos: if features.handle_group_pos:
group_sbox.setPos(getStoredCanvasPosition(group_name + "_INPUT", CanvasGetNewGroupPos(True))) group_sbox.setPos(getStoredCanvasPosition(group_name + "_INPUT", CanvasGetNewGroupPos(True)))
elif old_matching_group is not None and old_matching_group[0]:
group_sbox.setPos(old_matching_group[2])
else: else:
group_sbox.setPos(CanvasGetNewGroupPos(True)) group_sbox.setPos(CanvasGetNewGroupPos(True))
group_sbox.blockSignals(False)


canvas.last_z_value += 1 canvas.last_z_value += 1
group_sbox.setZValue(canvas.last_z_value) group_sbox.setZValue(canvas.last_z_value)
@@ -322,19 +373,21 @@ def addGroup(group_id, group_name, split=SPLIT_UNDEF, icon=ICON_APPLICATION):
if options.eyecandy == EYECANDY_FULL and not options.auto_hide_groups: if options.eyecandy == EYECANDY_FULL and not options.auto_hide_groups:
CanvasItemFX(group_sbox, True, False) CanvasItemFX(group_sbox, True, False)


group_sbox.checkItemPos()
group_sbox.checkItemPos(True)


else: else:
group_box.setSplit(False) group_box.setSplit(False)


if features.handle_group_pos: if features.handle_group_pos:
group_box.setPos(getStoredCanvasPosition(group_name, CanvasGetNewGroupPos(False))) group_box.setPos(getStoredCanvasPosition(group_name, CanvasGetNewGroupPos(False)))
elif old_matching_group is not None:
group_box.setPos(old_matching_group[1])
else: else:
# Special ladish fake-split groups # Special ladish fake-split groups
horizontal = bool(icon == ICON_HARDWARE or icon == ICON_LADISH_ROOM) horizontal = bool(icon == ICON_HARDWARE or icon == ICON_LADISH_ROOM)
group_box.setPos(CanvasGetNewGroupPos(horizontal)) group_box.setPos(CanvasGetNewGroupPos(horizontal))


group_box.checkItemPos()
group_box.checkItemPos(True)


canvas.last_z_value += 1 canvas.last_z_value += 1
group_box.setZValue(canvas.last_z_value) group_box.setZValue(canvas.last_z_value)
@@ -630,10 +683,14 @@ def restoreGroupPositions(dataList):
if group is None: if group is None:
continue continue


group.widgets[0].blockSignals(True)
group.widgets[0].setPos(data['pos1x'], data['pos1y']) group.widgets[0].setPos(data['pos1x'], data['pos1y'])
group.widgets[0].blockSignals(False)


if group.split and group.widgets[1]: if group.split and group.widgets[1]:
group.widgets[1].blockSignals(True)
group.widgets[1].setPos(data['pos2x'], data['pos2y']) group.widgets[1].setPos(data['pos2x'], data['pos2y'])
group.widgets[1].blockSignals(False)


def setGroupPos(group_id, group_pos_x, group_pos_y): def setGroupPos(group_id, group_pos_x, group_pos_y):
setGroupPosFull(group_id, group_pos_x, group_pos_y, group_pos_x, group_pos_y) setGroupPosFull(group_id, group_pos_x, group_pos_y, group_pos_x, group_pos_y)
@@ -645,10 +702,14 @@ def setGroupPosFull(group_id, group_pos_x_o, group_pos_y_o, group_pos_x_i, group


for group in canvas.group_list: for group in canvas.group_list:
if group.group_id == group_id: if group.group_id == group_id:
group.widgets[0].blockSignals(True)
group.widgets[0].setPos(group_pos_x_o, group_pos_y_o) group.widgets[0].setPos(group_pos_x_o, group_pos_y_o)
group.widgets[0].blockSignals(False)


if group.split and group.widgets[1]: if group.split and group.widgets[1]:
group.widgets[1].blockSignals(True)
group.widgets[1].setPos(group_pos_x_i, group_pos_y_i) group.widgets[1].setPos(group_pos_x_i, group_pos_y_i)
group.widgets[1].blockSignals(False)


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


+ 1
- 3
source/frontend/patchcanvas/scene.py View File

@@ -58,7 +58,6 @@ class RubberbandRect(QGraphicsRectItem):


class PatchScene(QGraphicsScene): class PatchScene(QGraphicsScene):
scaleChanged = pyqtSignal(float) scaleChanged = pyqtSignal(float)
sceneGroupMoved = pyqtSignal(int, int, QPointF)
pluginSelected = pyqtSignal(list) pluginSelected = pyqtSignal(list)


def __init__(self, parent, view): def __init__(self, parent, view):
@@ -367,8 +366,7 @@ class PatchScene(QGraphicsScene):
items_list = self.selectedItems() items_list = self.selectedItems()
for item in items_list: for item in items_list:
if item and item.isVisible() and item.type() == CanvasBoxType: if item and item.isVisible() and item.type() == CanvasBoxType:
item.checkItemPos()
self.sceneGroupMoved.emit(item.getGroupId(), item.getSplittedMode(), item.scenePos())
item.checkItemPos(False)


if len(items_list) > 1: if len(items_list) > 1:
canvas.scene.update() canvas.scene.update()


+ 2
- 0
source/utils/CarlaBackendUtils.hpp View File

@@ -315,6 +315,8 @@ const char* EngineCallbackOpcode2Str(const EngineCallbackOpcode opcode) noexcept
return "ENGINE_CALLBACK_PATCHBAY_PORT_GROUP_CHANGED"; return "ENGINE_CALLBACK_PATCHBAY_PORT_GROUP_CHANGED";
case ENGINE_CALLBACK_PARAMETER_MAPPED_RANGE_CHANGED: case ENGINE_CALLBACK_PARAMETER_MAPPED_RANGE_CHANGED:
return "ENGINE_CALLBACK_PARAMETER_MAPPED_RANGE_CHANGED"; return "ENGINE_CALLBACK_PARAMETER_MAPPED_RANGE_CHANGED";
case ENGINE_CALLBACK_PATCHBAY_CLIENT_POSITION_CHANGED:
return "ENGINE_CALLBACK_PATCHBAY_CLIENT_POSITION_CHANGED";
} }


carla_stderr("CarlaBackend::EngineCallbackOpcode2Str(%i) - invalid opcode", opcode); carla_stderr("CarlaBackend::EngineCallbackOpcode2Str(%i) - invalid opcode", opcode);


+ 21
- 17
source/utils/CarlaPatchbayUtils.hpp View File

@@ -1,6 +1,6 @@
/* /*
* Carla patchbay utils * Carla patchbay utils
* Copyright (C) 2011-2017 Filipe Coelho <falktx@falktx.com>
* Copyright (C) 2011-2020 Filipe Coelho <falktx@falktx.com>
* *
* This program is free software; you can redistribute it and/or * This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as * modify it under the terms of the GNU General Public License as
@@ -27,7 +27,7 @@


struct GroupNameToId { struct GroupNameToId {
uint group; uint group;
char name[STR_MAX+1]; // globally unique
char name[STR_MAX]; // globally unique


void clear() noexcept void clear() noexcept
{ {
@@ -43,15 +43,15 @@ struct GroupNameToId {


void rename(const char n[]) noexcept void rename(const char n[]) noexcept
{ {
std::strncpy(name, n, STR_MAX);
name[STR_MAX] = '\0';
std::strncpy(name, n, STR_MAX-1);
name[STR_MAX-1] = '\0';
} }


bool operator==(const GroupNameToId& groupNameToId) const noexcept bool operator==(const GroupNameToId& groupNameToId) const noexcept
{ {
if (groupNameToId.group != group) if (groupNameToId.group != group)
return false; return false;
if (std::strncmp(groupNameToId.name, name, STR_MAX) != 0)
if (std::strncmp(groupNameToId.name, name, STR_MAX-1) != 0)
return false; return false;
return true; return true;
} }
@@ -65,10 +65,12 @@ struct GroupNameToId {
struct PatchbayGroupList { struct PatchbayGroupList {
uint lastId; uint lastId;
LinkedList<GroupNameToId> list; LinkedList<GroupNameToId> list;
CarlaMutex mutex;


PatchbayGroupList() noexcept PatchbayGroupList() noexcept
: lastId(0), : lastId(0),
list() {}
list(),
mutex() {}


void clear() noexcept void clear() noexcept
{ {
@@ -87,8 +89,8 @@ struct PatchbayGroupList {
struct PortNameToId { struct PortNameToId {
uint group; uint group;
uint port; uint port;
char name[STR_MAX+1]; // locally unique (within the same group)
char fullName[STR_MAX+1]; // globally unique
char name[STR_MAX]; // locally unique (within the same group)
char fullName[STR_MAX]; // globally unique


void clear() noexcept void clear() noexcept
{ {
@@ -107,26 +109,26 @@ struct PortNameToId {


void setFullName(const char fn[]) noexcept void setFullName(const char fn[]) noexcept
{ {
std::strncpy(fullName, fn, STR_MAX);
fullName[STR_MAX] = '\0';
std::strncpy(fullName, fn, STR_MAX-1);
fullName[STR_MAX-1] = '\0';
} }


void rename(const char n[], const char fn[]) noexcept void rename(const char n[], const char fn[]) noexcept
{ {
std::strncpy(name, n, STR_MAX);
name[STR_MAX] = '\0';
std::strncpy(name, n, STR_MAX-1);
name[STR_MAX-1] = '\0';


std::strncpy(fullName, fn, STR_MAX);
fullName[STR_MAX] = '\0';
std::strncpy(fullName, fn, STR_MAX-1);
fullName[STR_MAX-1] = '\0';
} }


bool operator==(const PortNameToId& portNameToId) noexcept bool operator==(const PortNameToId& portNameToId) noexcept
{ {
if (portNameToId.group != group || portNameToId.port != port) if (portNameToId.group != group || portNameToId.port != port)
return false; return false;
if (std::strncmp(portNameToId.name, name, STR_MAX) != 0)
if (std::strncmp(portNameToId.name, name, STR_MAX-1) != 0)
return false; return false;
if (std::strncmp(portNameToId.fullName, fullName, STR_MAX) != 0)
if (std::strncmp(portNameToId.fullName, fullName, STR_MAX-1) != 0)
return false; return false;
return true; return true;
} }
@@ -140,10 +142,12 @@ struct PortNameToId {
struct PatchbayPortList { struct PatchbayPortList {
uint lastId; uint lastId;
LinkedList<PortNameToId> list; LinkedList<PortNameToId> list;
CarlaMutex mutex;


PatchbayPortList() noexcept PatchbayPortList() noexcept
: lastId(0), : lastId(0),
list() {}
list(),
mutex() {}


void clear() noexcept void clear() noexcept
{ {


Loading…
Cancel
Save