diff --git a/source/backend/CarlaBackend.h b/source/backend/CarlaBackend.h
index 95642c416..c8ccb46cb 100644
--- a/source/backend/CarlaBackend.h
+++ b/source/backend/CarlaBackend.h
@@ -1141,7 +1141,17 @@ typedef enum {
* @a value1 Parameter index
* @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;
diff --git a/source/backend/CarlaEngine.hpp b/source/backend/CarlaEngine.hpp
index c1fdfc279..66f0f9a87 100644
--- a/source/backend/CarlaEngine.hpp
+++ b/source/backend/CarlaEngine.hpp
@@ -1168,6 +1168,12 @@ public:
*/
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.
*/
@@ -1348,10 +1354,11 @@ protected:
* Virtual functions for handling patchbay state.
* 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 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.
diff --git a/source/backend/CarlaHost.h b/source/backend/CarlaHost.h
index d268b830e..156f7cbaa 100644
--- a/source/backend/CarlaHost.h
+++ b/source/backend/CarlaHost.h
@@ -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);
+/*!
+ * 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.
* @param external Wherever to show external/hardware ports instead of internal ones.
diff --git a/source/backend/CarlaStandalone.cpp b/source/backend/CarlaStandalone.cpp
index 16f92a633..d93a6e715 100644
--- a/source/backend/CarlaStandalone.cpp
+++ b/source/backend/CarlaStandalone.cpp
@@ -970,6 +970,16 @@ bool carla_patchbay_disconnect(CarlaHostHandle handle, bool external, uint conne
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)
{
CARLA_SAFE_ASSERT_WITH_LAST_ERROR_RETURN(handle->engine != nullptr, "Engine is not initialized", false);
diff --git a/source/backend/engine/CarlaEngine.cpp b/source/backend/engine/CarlaEngine.cpp
index 1c20cf1e9..2704ada2f 100644
--- a/source/backend/engine/CarlaEngine.cpp
+++ b/source/backend/engine/CarlaEngine.cpp
@@ -2198,67 +2198,149 @@ void CarlaEngine::saveProjectInternal(water::MemoryOutputStream& outStream) cons
// save internal connections
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);
outPatchbay << "\n \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 << " \n";
- outPatchbay << " \n";
- outPatchbay << " " << xmlSafeString(connTarget, true) << "\n";
- outPatchbay << " \n";
+ outPatchbay << " \n";
+ outPatchbay << " \n";
+ outPatchbay << " " << xmlSafeString(connTarget, true) << "\n";
+ outPatchbay << " \n";
+ }
+ }
+
+ if (patchbayPos != nullptr && posCount != 0)
+ {
+ outPatchbay << " \n";
+
+ for (uint i=0; i\n";
+ outPatchbay << " " << xmlSafeString(ppos.name, true) << "\n";
+ outPatchbay << " \n";
+
+ if (ppos.dealloc)
+ delete[] ppos.name;
+ }
+
+ outPatchbay << " \n";
}
outPatchbay << " \n";
outStream << outPatchbay;
+
+ delete[] patchbayPos;
}
+
}
// if we're running inside some session-manager (and using JACK), let them handle the connections
- bool saveExternalConnections;
+ bool saveExternalConnections, saveExternalPositions = true;
/**/ if (isPlugin)
+ {
saveExternalConnections = false;
+ saveExternalPositions = false;
+ }
else if (std::strcmp(getCurrentDriverName(), "JACK") != 0)
+ {
saveExternalConnections = true;
+ }
else if (std::getenv("CARLA_DONT_MANAGE_CONNECTIONS") != nullptr)
+ {
saveExternalConnections = false;
+ }
else if (std::getenv("LADISH_APP_NAME") != nullptr)
+ {
saveExternalConnections = false;
+ }
else if (std::getenv("NSM_URL") != nullptr)
+ {
saveExternalConnections = false;
+ }
else
+ {
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);
outPatchbay << "\n \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 << " \n";
- outPatchbay << " \n";
- outPatchbay << " " << xmlSafeString(connTarget, true) << "\n";
- outPatchbay << " \n";
+ outPatchbay << " \n";
+ outPatchbay << " \n";
+ outPatchbay << " " << xmlSafeString(connTarget, true) << "\n";
+ outPatchbay << " \n";
+ }
+ }
+
+ if (patchbayPos != nullptr && posCount != 0)
+ {
+ outPatchbay << " \n";
+
+ for (uint i=0; i\n";
+ outPatchbay << " " << xmlSafeString(ppos.name, true) << "\n";
+ outPatchbay << " \n";
+
+ if (ppos.dealloc)
+ delete[] ppos.name;
+ }
+
+ outPatchbay << " \n";
}
outPatchbay << " \n";
@@ -2843,8 +2925,9 @@ bool CarlaEngine::loadProjectInternal(water::XmlDocument& xmlDoc)
// plus external connections too
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())
{
@@ -2857,7 +2940,11 @@ bool CarlaEngine::loadProjectInternal(water::XmlDocument& xmlDoc)
continue;
}
// or load external patchbay connections
- else if (tagName != "ExternalPatchbay")
+ else if (tagName == "ExternalPatchbay")
+ {
+ loadingAsExternal = true;
+ }
+ else
{
continue;
}
@@ -2891,6 +2978,82 @@ bool CarlaEngine::loadProjectInternal(water::XmlDocument& xmlDoc)
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
if (pData->options.resetXruns)
diff --git a/source/backend/engine/CarlaEngineGraph.cpp b/source/backend/engine/CarlaEngineGraph.cpp
index 1d198e8e4..29280d0c5 100644
--- a/source/backend/engine/CarlaEngineGraph.cpp
+++ b/source/backend/engine/CarlaEngineGraph.cpp
@@ -134,8 +134,12 @@ ExternalGraph::ExternalGraph(CarlaEngine* const engine) noexcept
: connections(),
audioPorts(),
midiPorts(),
+ positions(),
retCon(),
- kEngine(engine) {}
+ kEngine(engine)
+{
+ carla_zeroStruct(positions);
+}
void ExternalGraph::clear() noexcept
{
@@ -313,11 +317,24 @@ bool ExternalGraph::disconnect(const bool sendHost, const bool sendOSC,
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(y2),
+ nullptr);
+}
+
void ExternalGraph::refresh(const bool sendHost, const bool sendOSC, const char* const deviceName)
{
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
{
@@ -591,6 +608,39 @@ const char* const* ExternalGraph::getConnections() const noexcept
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
{
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
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(node != nullptr,);
CARLA_SAFE_ASSERT_RETURN(proc != nullptr,);
+ const uint groupId = node->nodeId;
+
engine->callback(sendHost, sendOSC,
ENGINE_CALLBACK_PATCHBAY_CLIENT_ADDED,
groupId,
- clientId >= 0 ? PATCHBAY_ICON_PLUGIN : PATCHBAY_ICON_HARDWARE,
- clientId,
+ pluginId >= 0 ? PATCHBAY_ICON_PLUGIN : PATCHBAY_ICON_HARDWARE,
+ pluginId,
0, 0.0f,
proc->getName().toRawUTF8());
@@ -1308,6 +1361,18 @@ void addNodeToPatchbay(const bool sendHost, const bool sendOSC, CarlaEngine* con
0, 0.0f,
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(node->properties.getWithDefault("y2", 0)),
+ nullptr);
+ }
}
static inline
@@ -1813,7 +1878,7 @@ void PatchbayGraph::addPlugin(CarlaPlugin* const plugin)
node->properties.set("isPlugin", true);
node->properties.set("pluginId", static_cast(plugin->getId()));
- addNodeToPatchbay(sendHost, sendOSC, kEngine, node->nodeId, static_cast(plugin->getId()), instance);
+ addNodeToPatchbay(sendHost, sendOSC, kEngine, node, static_cast(plugin->getId()), instance);
}
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("pluginId", static_cast(newPlugin->getId()));
- addNodeToPatchbay(sendHost, sendOSC, kEngine, node->nodeId, static_cast(newPlugin->getId()), instance);
+ addNodeToPatchbay(sendHost, sendOSC, kEngine, node, static_cast(newPlugin->getId()), instance);
}
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(y2),
+ nullptr);
+}
+
void PatchbayGraph::refresh(const bool sendHost, const bool sendOSC, const bool external, const char* const deviceName)
{
if (external)
@@ -2111,13 +2196,13 @@ void PatchbayGraph::refresh(const bool sendHost, const bool sendOSC, const bool
AudioProcessor* const proc(node->getProcessor());
CARLA_SAFE_ASSERT_CONTINUE(proc != nullptr);
- int clientId = -1;
+ int pluginId = -1;
// plugin node
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];
@@ -2212,6 +2297,115 @@ const char* const* PatchbayGraph::getConnections(const bool external) const
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; igetName();
+ 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(); igetProcessor());
+ 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
{
if (external)
@@ -2607,8 +2801,6 @@ bool CarlaEngine::patchbayConnect(const bool external,
return graph->connect(external, groupA, portA, groupB, portB);
}
-
- return false;
}
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 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)
@@ -2687,6 +2899,22 @@ const char* const* CarlaEngine::getPatchbayConnections(const bool external) cons
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,
const char* const sourcePort,
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)
diff --git a/source/backend/engine/CarlaEngineGraph.hpp b/source/backend/engine/CarlaEngineGraph.hpp
index 14436e7c2..33688fadc 100644
--- a/source/backend/engine/CarlaEngineGraph.hpp
+++ b/source/backend/engine/CarlaEngineGraph.hpp
@@ -33,6 +33,13 @@ using water::MidiBuffer;
CARLA_BACKEND_START_NAMESPACE
+// -----------------------------------------------------------------------
+
+struct PatchbayPosition {
+ bool active;
+ int x1, y1, x2, y2;
+};
+
// -----------------------------------------------------------------------
// External Graph stuff
@@ -80,6 +87,7 @@ struct ExternalGraphPorts {
struct ExternalGraph {
PatchbayConnectionList connections;
ExternalGraphPorts audioPorts, midiPorts;
+ PatchbayPosition positions[kExternalGraphGroupMax];
mutable CharStringListPtr retCon;
ExternalGraph(CarlaEngine* engine) noexcept;
@@ -89,10 +97,13 @@ struct ExternalGraph {
uint groupA, uint portA, uint groupB, uint portB) noexcept;
bool disconnect(bool sendHost, bool sendOSC,
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,
const char* deviceName);
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;
CarlaEngine* const kEngine;
@@ -190,9 +201,12 @@ public:
bool connect(bool external, uint groupA, uint portA, uint groupB, uint portB);
bool disconnect(bool external, uint connectionId);
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);
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;
void process(CarlaEngine::ProtectedData* data,
diff --git a/source/backend/engine/CarlaEngineJack.cpp b/source/backend/engine/CarlaEngineJack.cpp
index a492a1527..d034e602c 100644
--- a/source/backend/engine/CarlaEngineJack.cpp
+++ b/source/backend/engine/CarlaEngineJack.cpp
@@ -56,6 +56,7 @@
#define URI_CANVAS_ICON "http://kxstudio.sf.net/ns/canvas/icon"
#define URI_MAIN_CLIENT_NAME "https://kx.studio/ns/carla/main-client-name"
+#define URI_POSITION "https://kx.studio/ns/carla/position"
#define URI_PLUGIN_ICON "https://kx.studio/ns/carla/plugin-icon"
#define URI_PLUGIN_ID "https://kx.studio/ns/carla/plugin-id"
@@ -111,8 +112,11 @@ struct CarlaJackPortHints {
// -----------------------------------------------------------------------
// Fallback data
-static const EngineEvent kFallbackJackEngineEvent = { kEngineEventTypeNull, 0, 0, {{ kEngineControlEventTypeNull, 0, 0.0f }} };
-//static CarlaEngineEventCV kFallbackEngineEventCV = { nullptr, (uint32_t)-1, 0.0f };
+static const GroupNameToId kGroupNameToIdFallback = { 0, { '\0' } };
+static const PortNameToId kPortNameToIdFallback = { 0, 0, { '\0' }, { '\0' } };
+static /* */ PortNameToId kPortNameToIdFallbackNC = { 0, 0, { '\0' }, { '\0' } };
+static const ConnectionToId kConnectionToIdFallback = { 0, 0, 0, 0, 0 };
+static const EngineEvent kFallbackJackEngineEvent = { kEngineEventTypeNull, 0, 0, {{ kEngineControlEventTypeNull, 0, 0.0f }} };
// -----------------------------------------------------------------------
// Carla Engine Port removal helper
@@ -1896,46 +1900,70 @@ public:
stopThread(-1);
- const uint groupId(fUsedGroups.getGroupId(plugin->getName()));
+ LinkedList ports;
+ LinkedList conns;
- if (groupId > 0)
{
- for (LinkedList::Itenerator it = fUsedPorts.list.begin2(); it.valid(); it.next())
+ const CarlaMutexLocker cml1(fUsedGroups.mutex);
+
+ if (const uint groupId = fUsedGroups.getGroupId(plugin->getName()))
{
- for (LinkedList::Itenerator it2 = fUsedConnections.list.begin2(); it2.valid(); it2.next())
+ const CarlaMutexLocker cml2(fUsedPorts.mutex);
+
+ for (LinkedList::Itenerator it = fUsedPorts.list.begin2(); it.valid(); it.next())
{
- static ConnectionToId connectionFallback = { 0, 0, 0, 0, 0 };
+ const PortNameToId& portNameToId(it.getValue(kPortNameToIdFallback));
+ CARLA_SAFE_ASSERT_CONTINUE(portNameToId.group != 0);
+
+ if (portNameToId.group != groupId)
+ continue;
- ConnectionToId& connectionToId = it2.getValue(connectionFallback);
+ ports.append(portNameToId);
+ fUsedPorts.list.remove(it);
+ }
+
+ const CarlaMutexLocker cml3(fUsedConnections.mutex);
+
+ for (LinkedList::Itenerator it = fUsedConnections.list.begin2(); it.valid(); it.next())
+ {
+ const ConnectionToId& connectionToId = it.getValue(kConnectionToIdFallback);
CARLA_SAFE_ASSERT_CONTINUE(connectionToId.id != 0);
if (connectionToId.groupA != groupId && connectionToId.groupB != groupId)
continue;
- callback(fExternalPatchbayHost, fExternalPatchbayOsc,
- ENGINE_CALLBACK_PATCHBAY_CONNECTION_REMOVED,
- connectionToId.id,
- 0, 0, 0, 0.0f, nullptr);
- fUsedConnections.list.remove(it2);
+ conns.append(connectionToId);
+ fUsedConnections.list.remove(it);
}
+ }
+ }
- static PortNameToId portNameFallback = { 0, 0, { '\0' }, { '\0' } };
-
- PortNameToId& portNameToId(it.getValue(portNameFallback));
- CARLA_SAFE_ASSERT_CONTINUE(portNameToId.group != 0);
+ for (LinkedList::Itenerator it = conns.begin2(); it.valid(); it.next())
+ {
+ const ConnectionToId& connectionToId = it.getValue(kConnectionToIdFallback);
+ CARLA_SAFE_ASSERT_CONTINUE(connectionToId.id != 0);
- if (portNameToId.group != groupId)
- continue;
+ callback(fExternalPatchbayHost, fExternalPatchbayOsc,
+ ENGINE_CALLBACK_PATCHBAY_CONNECTION_REMOVED,
+ connectionToId.id,
+ 0, 0, 0, 0.0f, nullptr);
+ }
- callback(fExternalPatchbayHost, fExternalPatchbayOsc,
- ENGINE_CALLBACK_PATCHBAY_PORT_REMOVED,
- portNameToId.group,
- static_cast(portNameToId.port),
- 0, 0, 0.0f, nullptr);
- fUsedPorts.list.remove(it);
- }
+ for (LinkedList::Itenerator it = ports.begin2(); it.valid(); it.next())
+ {
+ const PortNameToId& portNameToId(it.getValue(kPortNameToIdFallback));
+ CARLA_SAFE_ASSERT_CONTINUE(portNameToId.group != 0);
+
+ callback(fExternalPatchbayHost, fExternalPatchbayOsc,
+ ENGINE_CALLBACK_PATCHBAY_PORT_REMOVED,
+ portNameToId.group,
+ static_cast(portNameToId.port),
+ 0, 0, 0.0f, nullptr);
}
+ ports.clear();
+ conns.clear();
+
startThread();
}
else
@@ -1971,6 +1999,8 @@ public:
if (pData->options.processMode == ENGINE_PROCESS_MODE_PATCHBAY && ! external)
return CarlaEngine::patchbayConnect(false, groupA, portA, groupB, portB);
+ const CarlaMutexLocker cml(fUsedPorts.mutex);
+
const char* const fullPortNameA = fUsedPorts.getFullPortName(groupA, portA);
CARLA_SAFE_ASSERT_RETURN(fullPortNameA != nullptr && fullPortNameA[0] != '\0', false);
@@ -2000,7 +2030,7 @@ public:
for (LinkedList::Itenerator it = fUsedConnections.list.begin2(); it.valid(); it.next())
{
- connectionToId = it.getValue(connectionToId);
+ connectionToId = it.getValue(kConnectionToIdFallback);
CARLA_SAFE_ASSERT_CONTINUE(connectionToId.id != 0);
if (connectionToId.id == connectionId)
@@ -2014,6 +2044,8 @@ public:
return false;
}
+ const CarlaMutexLocker cml(fUsedPorts.mutex);
+
const char* const fullPortNameA = fUsedPorts.getFullPortName(connectionToId.groupA, connectionToId.portA);
CARLA_SAFE_ASSERT_RETURN(fullPortNameA != nullptr && fullPortNameA[0] != '\0', false);
@@ -2029,6 +2061,36 @@ public:
return true;
}
+ bool 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) override
+ {
+ CARLA_SAFE_ASSERT_RETURN(fClient != nullptr, false);
+
+ if (pData->options.processMode == ENGINE_PROCESS_MODE_PATCHBAY && ! external)
+ return CarlaEngine::patchbaySetGroupPos(sendHost, sendOSC, false, groupId, x1, y1, x2, y2);
+
+ const char* groupName;
+
+ {
+ const CarlaMutexLocker cml(fUsedPorts.mutex);
+
+ groupName = fUsedGroups.getGroupName(groupId);
+ CARLA_SAFE_ASSERT_RETURN(groupName != nullptr && groupName[0] != '\0', false);
+ }
+
+ const char* const uuidstr = jackbridge_get_uuid_for_client_name(fClient, groupName);
+ CARLA_SAFE_ASSERT_RETURN(uuidstr != nullptr && uuidstr[0] != '\0', false);
+
+ jack_uuid_t uuid;
+ CARLA_SAFE_ASSERT_RETURN(jackbridge_uuid_parse(uuidstr, &uuid), false);
+
+ char valueStr[STR_MAX];
+ std::snprintf(valueStr, 63, "%i:%i:%i:%i", x1, y1, x2, y2);
+ valueStr[STR_MAX-1] = '\0';
+
+ return jackbridge_set_property(fClient, uuid, URI_POSITION, valueStr, URI_TYPE_STRING);
+ }
+
bool patchbayRefresh(const bool sendHost, const bool sendOSC, const bool external) override
{
CARLA_SAFE_ASSERT_RETURN(fClient != nullptr, false);
@@ -2051,9 +2113,15 @@ public:
return CarlaEngine::patchbayRefresh(sendHost, sendOSC, false);
}
- fUsedGroups.clear();
- fUsedPorts.clear();
- fUsedConnections.clear();
+ {
+ const CarlaMutexLocker cml1(fUsedGroups.mutex);
+ const CarlaMutexLocker cml2(fUsedPorts.mutex);
+ const CarlaMutexLocker cml3(fUsedConnections.mutex);
+
+ fUsedGroups.clear();
+ fUsedPorts.clear();
+ fUsedConnections.clear();
+ }
initJackPatchbay(sendHost, sendOSC, jackbridge_get_client_name(fClient));
return true;
@@ -2179,7 +2247,81 @@ public:
return fRetConns;
}
- void restorePatchbayConnection(const bool external, const char* const connSource, const char* const connTarget) override
+ const PatchbayPosition* getPatchbayPositions(const bool external, uint& count) const override
+ {
+ CARLA_SAFE_ASSERT_RETURN(fClient != nullptr, nullptr);
+ carla_debug("CarlaEngineJack::getPatchbayPositions(%s)", bool2str(external));
+
+ if (pData->options.processMode == ENGINE_PROCESS_MODE_PATCHBAY && ! external)
+ return CarlaEngine::getPatchbayPositions(external, count);
+
+ if (const std::size_t maxCount = fUsedGroups.list.count())
+ {
+ PatchbayPosition* ret;
+
+ try {
+ ret = new CarlaEngine::PatchbayPosition[maxCount];
+ } CARLA_SAFE_EXCEPTION_RETURN("new CarlaEngine::PatchbayPosition", nullptr);
+
+ count = 0;
+
+ GroupNameToId groupNameToId;
+
+ const CarlaMutexLocker cml1(fUsedGroups.mutex);
+
+ for (LinkedList::Itenerator it = fUsedGroups.list.begin2(); it.valid(); it.next())
+ {
+ groupNameToId = it.getValue(kGroupNameToIdFallback);
+ CARLA_SAFE_ASSERT_CONTINUE(groupNameToId.group != 0);
+
+ const char* const uuidstr = jackbridge_get_uuid_for_client_name(fClient, groupNameToId.name);
+ CARLA_SAFE_ASSERT_CONTINUE(uuidstr != nullptr && uuidstr[0] != '\0');
+
+ jack_uuid_t uuid;
+ CARLA_SAFE_ASSERT_CONTINUE(jackbridge_uuid_parse(uuidstr, &uuid));
+
+ char* value = nullptr;
+ char* type = nullptr;
+
+ if (jackbridge_get_property(uuid, URI_POSITION, &value, &type)
+ && value != nullptr
+ && type != nullptr
+ && std::strcmp(type, URI_TYPE_STRING) == 0)
+ {
+ CarlaEngine::PatchbayPosition& ppos(ret[count++]);
+
+ ppos.name = carla_strdup_safe(groupNameToId.name);
+ ppos.dealloc = true;
+
+ if (char* sep1 = std::strstr(value, ":"))
+ {
+ *sep1++ = '\0';
+ ppos.x1 = std::atoi(value);
+
+ if (char* sep2 = std::strstr(sep1, ":"))
+ {
+ *sep2++ = '\0';
+ ppos.y1 = std::atoi(sep1);
+
+ if (char* sep3 = std::strstr(sep2, ":"))
+ {
+ *sep3++ = '\0';
+ ppos.x2 = std::atoi(sep2);
+ ppos.y2 = std::atoi(sep3);
+ }
+ }
+ }
+ }
+ }
+
+ return ret;
+ }
+
+ return nullptr;
+ }
+
+ void restorePatchbayConnection(const bool external,
+ const char* const connSource, const char* const connTarget) override
{
CARLA_SAFE_ASSERT_RETURN(fClient != nullptr,);
CARLA_SAFE_ASSERT_RETURN(connSource != nullptr && connSource[0] != '\0',);
@@ -2199,6 +2341,51 @@ public:
jackbridge_connect(fClient, connSource, connTarget);
}
}
+
+ void restorePatchbayGroupPosition(const bool external, const PatchbayPosition& ppos) override
+ {
+ CARLA_SAFE_ASSERT_RETURN(fClient != nullptr,);
+ carla_debug("CarlaEngineJack::restorePatchbayGroupPosition(%s, ...)", bool2str(external));
+
+ if (pData->options.processMode == ENGINE_PROCESS_MODE_PATCHBAY && ! external)
+ return CarlaEngine::restorePatchbayGroupPosition(external, ppos);
+
+ uint groupId = 0;
+
+ // it might take a bit to receive jack client registration callback, so we ease things a bit
+ for (int i=10; --i >=0;)
+ {
+ {
+ const CarlaMutexLocker cml1(fUsedGroups.mutex);
+ groupId = fUsedGroups.getGroupId(ppos.name);
+ }
+
+ if (groupId != 0)
+ break;
+
+ carla_msleep(200);
+ callback(true, true, ENGINE_CALLBACK_IDLE, 0, 0, 0, 0, 0.0f, nullptr);
+ }
+
+ CARLA_SAFE_ASSERT_RETURN(groupId != 0,);
+
+ const char* const uuidstr = jackbridge_get_uuid_for_client_name(fClient, ppos.name);
+ CARLA_SAFE_ASSERT_RETURN(uuidstr != nullptr && uuidstr[0] != '\0',);
+
+ jack_uuid_t uuid;
+ CARLA_SAFE_ASSERT_RETURN(jackbridge_uuid_parse(uuidstr, &uuid),);
+
+ char valueStr[STR_MAX];
+ std::snprintf(valueStr, 63, "%i:%i:%i:%i", ppos.x1, ppos.y1, ppos.x2, ppos.y2);
+ valueStr[STR_MAX-1] = '\0';
+
+ jackbridge_set_property(fClient, uuid, URI_POSITION, valueStr, URI_TYPE_STRING);
+
+ callback(true, true,
+ ENGINE_CALLBACK_PATCHBAY_CLIENT_POSITION_CHANGED,
+ groupId, ppos.x1, ppos.y1, ppos.x2, static_cast(ppos.y2),
+ nullptr);
+ }
#endif
// -------------------------------------------------------------------
@@ -2539,6 +2726,7 @@ protected:
callback(fExternalPatchbayHost, fExternalPatchbayOsc,
ENGINE_CALLBACK_PATCHBAY_CLIENT_REMOVED, groupNameToId.group, 0, 0, 0, 0.0f, nullptr);
+ const CarlaMutexLocker cml(fUsedGroups.mutex);
fUsedGroups.list.removeOne(groupNameToId);
}
@@ -2553,39 +2741,84 @@ protected:
if (! fExternalPatchbayHost) return;
#endif
- bool found;
+ bool groupFound;
CarlaString groupName(portName);
- groupName.truncate(groupName.rfind(shortPortName, &found)-1);
+ groupName.truncate(groupName.rfind(shortPortName, &groupFound)-1);
+ CARLA_SAFE_ASSERT_RETURN(groupFound,);
- CARLA_SAFE_ASSERT_RETURN(found,);
-
- uint groupId(fUsedGroups.getGroupId(groupName));
+ groupFound = false;
+ GroupToIdData groupData;
+ PortToIdData portData;
- if (groupId == 0)
{
- groupId = ++fUsedGroups.lastId;
+ const CarlaMutexLocker cml1(fUsedGroups.mutex);
- GroupNameToId groupNameToId;
- groupNameToId.setData(groupId, groupName);
+ groupData.id = fUsedGroups.getGroupId(groupName);
+
+ if (groupData.id == 0)
+ {
+ groupData.id = ++fUsedGroups.lastId;
+
+ GroupNameToId groupNameToId;
+ groupNameToId.setData(groupData.id, groupName);
+
+ int pluginId = -1;
+ PatchbayIcon icon = jackPortHints.isHardware ? PATCHBAY_ICON_HARDWARE : PATCHBAY_ICON_APPLICATION;
- int pluginId = -1;
- PatchbayIcon icon = jackPortHints.isHardware ? PATCHBAY_ICON_HARDWARE : PATCHBAY_ICON_APPLICATION;
+ findPluginIdAndIcon(groupName, pluginId, icon);
- findPluginIdAndIcon(groupName, pluginId, icon);
+ fUsedGroups.list.append(groupNameToId);
+ groupFound = true;
+ groupData.icon = icon;
+ groupData.pluginId = pluginId;
+ std::strncpy(groupData.strVal, groupName, STR_MAX-1);
+ groupData.strVal[STR_MAX-1] = '\0';
+ }
+
+ uint canvasPortFlags = 0x0;
+ canvasPortFlags |= jackPortHints.isInput ? PATCHBAY_PORT_IS_INPUT : 0x0;
+
+ /**/ if (jackPortHints.isCV)
+ canvasPortFlags |= PATCHBAY_PORT_TYPE_CV;
+ else if (jackPortHints.isOSC)
+ canvasPortFlags |= PATCHBAY_PORT_TYPE_OSC;
+ else if (jackPortHints.isAudio)
+ canvasPortFlags |= PATCHBAY_PORT_TYPE_AUDIO;
+ else if (jackPortHints.isMIDI)
+ canvasPortFlags |= PATCHBAY_PORT_TYPE_MIDI;
+
+ const CarlaMutexLocker cml2(fUsedPorts.mutex);
+
+ portData.group = groupData.id;
+ portData.port = ++fUsedPorts.lastId;
+ portData.flags = canvasPortFlags;
+ std::strncpy(portData.strVal, shortPortName, STR_MAX-1);
+ portData.strVal[STR_MAX-1] = '\0';
+
+ PortNameToId portNameToId;
+ portNameToId.setData(portData.group, portData.port, shortPortName, portName);
+ fUsedPorts.list.append(portNameToId);
+ }
+
+ if (groupFound)
+ {
callback(fExternalPatchbayHost, fExternalPatchbayOsc,
ENGINE_CALLBACK_PATCHBAY_CLIENT_ADDED,
- groupNameToId.group,
- icon,
- pluginId,
+ groupData.id,
+ groupData.icon,
+ groupData.pluginId,
0, 0.0f,
- groupNameToId.name);
-
- fUsedGroups.list.append(groupNameToId);
+ groupData.strVal);
}
- addPatchbayJackPort(fExternalPatchbayHost, fExternalPatchbayOsc,
- groupId, jackPortHints, shortPortName, portName);
+ callback(fExternalPatchbayHost, fExternalPatchbayOsc,
+ ENGINE_CALLBACK_PATCHBAY_PORT_ADDED,
+ portData.group,
+ static_cast(portData.port),
+ static_cast(portData.flags),
+ 0, 0.0f,
+ portData.strVal);
}
void handleJackPortUnregistrationCallback(const char* const portName)
@@ -2597,18 +2830,28 @@ protected:
if (! fExternalPatchbayHost) return;
#endif
- const PortNameToId& portNameToId(fUsedPorts.getPortNameToId(portName));
+ uint groupId, portId;
+
+ {
+ const CarlaMutexLocker cml(fUsedPorts.mutex);
- /* NOTE: Due to JACK2 async behaviour the port we get here might be the same of a previous rename-plugin request.
- See the comment on CarlaEngineJack::renamePlugin() for more information. */
- if (portNameToId.group <= 0 || portNameToId.port <= 0) return;
+ const PortNameToId& portNameToId(fUsedPorts.getPortNameToId(portName));
+
+ /* NOTE: Due to JACK2 async behaviour the port we get here might be the same of a previous rename-plugin request.
+ See the comment on CarlaEngineJack::renamePlugin() for more information. */
+ if (portNameToId.group <= 0 || portNameToId.port <= 0) return;
+
+ groupId = portNameToId.group;
+ portId = portNameToId.port;
+
+ fUsedPorts.list.removeOne(portNameToId);
+ }
callback(fExternalPatchbayHost, fExternalPatchbayOsc,
ENGINE_CALLBACK_PATCHBAY_PORT_REMOVED,
- portNameToId.group,
- static_cast(portNameToId.port),
+ groupId,
+ static_cast(portId),
0, 0, 0.0f, nullptr);
- fUsedPorts.list.removeOne(portNameToId);
}
void handleJackPortConnectCallback(const char* const portNameA, const char* const portNameB)
@@ -2620,27 +2863,42 @@ protected:
if (! fExternalPatchbayHost) return;
#endif
- const PortNameToId& portNameToIdA(fUsedPorts.getPortNameToId(portNameA));
- const PortNameToId& portNameToIdB(fUsedPorts.getPortNameToId(portNameB));
+ char strBuf[STR_MAX];
+ uint connectionId;
+
+ {
+ const CarlaMutexLocker cml1(fUsedPorts.mutex);
+
+ const PortNameToId& portNameToIdA(fUsedPorts.getPortNameToId(portNameA));
+ const PortNameToId& portNameToIdB(fUsedPorts.getPortNameToId(portNameB));
+
+ /* NOTE: Due to JACK2 async behaviour the port we get here might be the same of a previous rename-plugin request.
+ See the comment on CarlaEngineJack::renamePlugin() for more information. */
+ if (portNameToIdA.group <= 0 || portNameToIdA.port <= 0) return;
+ if (portNameToIdB.group <= 0 || portNameToIdB.port <= 0) return;
- /* NOTE: Due to JACK2 async behaviour the port we get here might be the same of a previous rename-plugin request.
- See the comment on CarlaEngineJack::renamePlugin() for more information. */
- if (portNameToIdA.group <= 0 || portNameToIdA.port <= 0) return;
- if (portNameToIdB.group <= 0 || portNameToIdB.port <= 0) return;
+ const CarlaMutexLocker cml2(fUsedConnections.mutex);
- char strBuf[STR_MAX+1];
- std::snprintf(strBuf, STR_MAX, "%i:%i:%i:%i", portNameToIdA.group, portNameToIdA.port, portNameToIdB.group, portNameToIdB.port);
- strBuf[STR_MAX] = '\0';
+ std::snprintf(strBuf, STR_MAX-1, "%i:%i:%i:%i",
+ portNameToIdA.group, portNameToIdA.port,
+ portNameToIdB.group, portNameToIdB.port);
+ strBuf[STR_MAX-1] = '\0';
- ConnectionToId connectionToId;
- connectionToId.setData(++fUsedConnections.lastId, portNameToIdA.group, portNameToIdA.port, portNameToIdB.group, portNameToIdB.port);
+ connectionId = ++fUsedConnections.lastId;
+
+ ConnectionToId connectionToId;
+ connectionToId.setData(connectionId,
+ portNameToIdA.group, portNameToIdA.port,
+ portNameToIdB.group, portNameToIdB.port);
+
+ fUsedConnections.list.append(connectionToId);
+ }
callback(fExternalPatchbayHost, fExternalPatchbayOsc,
ENGINE_CALLBACK_PATCHBAY_CONNECTION_ADDED,
- connectionToId.id,
+ connectionId,
0, 0, 0, 0.0f,
strBuf);
- fUsedConnections.list.append(connectionToId);
}
void handleJackPortDisconnectCallback(const char* const portNameA, const char* const portNameB)
@@ -2652,39 +2910,40 @@ protected:
if (! fExternalPatchbayHost) return;
#endif
- const PortNameToId& portNameToIdA(fUsedPorts.getPortNameToId(portNameA));
- const PortNameToId& portNameToIdB(fUsedPorts.getPortNameToId(portNameB));
+ uint connectionId = 0;
+
+ {
+ const CarlaMutexLocker cml1(fUsedPorts.mutex);
- /* NOTE: Due to JACK2 async behaviour the port we get here might be the same of a previous rename-plugin request.
- See the comment on CarlaEngineJack::renamePlugin() for more information. */
- if (portNameToIdA.group <= 0 || portNameToIdA.port <= 0) return;
- if (portNameToIdB.group <= 0 || portNameToIdB.port <= 0) return;
+ const PortNameToId& portNameToIdA(fUsedPorts.getPortNameToId(portNameA));
+ const PortNameToId& portNameToIdB(fUsedPorts.getPortNameToId(portNameB));
- ConnectionToId connectionToId = { 0, 0, 0, 0, 0 };
- bool found = false;
+ /* NOTE: Due to JACK2 async behaviour the port we get here might be the same of a previous rename-plugin request.
+ See the comment on CarlaEngineJack::renamePlugin() for more information. */
+ if (portNameToIdA.group <= 0 || portNameToIdA.port <= 0) return;
+ if (portNameToIdB.group <= 0 || portNameToIdB.port <= 0) return;
- {
- const CarlaMutexLocker cml(fUsedConnections.mutex);
+ const CarlaMutexLocker cml2(fUsedConnections.mutex);
for (LinkedList::Itenerator it = fUsedConnections.list.begin2(); it.valid(); it.next())
{
- connectionToId = it.getValue(connectionToId);
+ const ConnectionToId& connectionToId = it.getValue(kConnectionToIdFallback);
CARLA_SAFE_ASSERT_CONTINUE(connectionToId.id != 0);
if (connectionToId.groupA == portNameToIdA.group && connectionToId.portA == portNameToIdA.port &&
connectionToId.groupB == portNameToIdB.group && connectionToId.portB == portNameToIdB.port)
{
- found = true;
+ connectionId = connectionToId.id;
fUsedConnections.list.remove(it);
break;
}
}
}
- if (found) {
+ if (connectionId != 0) {
callback(fExternalPatchbayHost, fExternalPatchbayOsc,
ENGINE_CALLBACK_PATCHBAY_CONNECTION_REMOVED,
- connectionToId.id,
+ connectionId,
0, 0, 0, 0.0f, nullptr);
}
}
@@ -2706,33 +2965,49 @@ protected:
bool found;
CarlaString groupName(newFullName);
groupName.truncate(groupName.rfind(newShortName, &found)-1);
-
CARLA_SAFE_ASSERT_RETURN(found,);
- const uint groupId(fUsedGroups.getGroupId(groupName));
- CARLA_SAFE_ASSERT_RETURN(groupId > 0,);
+ uint groupId, portId;
+ char portName[STR_MAX];
+ found = false;
- for (LinkedList::Itenerator it = fUsedPorts.list.begin2(); it.valid(); it.next())
{
- static PortNameToId portNameFallback = { 0, 0, { '\0' }, { '\0' } };
+ const CarlaMutexLocker cml1(fUsedGroups.mutex);
- PortNameToId& portNameToId(it.getValue(portNameFallback));
- CARLA_SAFE_ASSERT_CONTINUE(portNameToId.group != 0);
+ groupId = fUsedGroups.getGroupId(groupName);
+ CARLA_SAFE_ASSERT_RETURN(groupId != 0,);
- if (std::strncmp(portNameToId.fullName, oldFullName, STR_MAX) == 0)
+ const CarlaMutexLocker cml2(fUsedPorts.mutex);
+
+ for (LinkedList::Itenerator it = fUsedPorts.list.begin2(); it.valid(); it.next())
{
- CARLA_SAFE_ASSERT_CONTINUE(portNameToId.group == groupId);
+ PortNameToId& portNameToId(it.getValue(kPortNameToIdFallbackNC));
+ CARLA_SAFE_ASSERT_CONTINUE(portNameToId.group != 0);
- portNameToId.rename(newShortName, newFullName);
- callback(fExternalPatchbayHost, fExternalPatchbayOsc,
- ENGINE_CALLBACK_PATCHBAY_PORT_CHANGED,
- portNameToId.group,
- static_cast(portNameToId.port),
- 0, 0, 0.0f,
- portNameToId.name);
- break;
+ if (std::strncmp(portNameToId.fullName, oldFullName, STR_MAX) == 0)
+ {
+ CARLA_SAFE_ASSERT_CONTINUE(portNameToId.group == groupId);
+
+ found = true;
+ portId = portNameToId.port;
+ std::strncpy(portName, newShortName, STR_MAX-1);
+ portName[STR_MAX-1] = '\0';
+
+ portNameToId.rename(newShortName, newFullName);
+ break;
+ }
}
}
+
+ if (found)
+ {
+ callback(fExternalPatchbayHost, fExternalPatchbayOsc,
+ ENGINE_CALLBACK_PATCHBAY_PORT_CHANGED,
+ groupId,
+ static_cast(portId),
+ 0, 0, 0.0f,
+ portName);
+ }
}
#endif
@@ -2895,170 +3170,278 @@ private:
}
}
+ // handy stuff only needed for initJackPatchbay
+ struct GroupToIdData {
+ uint id;
+ PatchbayIcon icon;
+ int pluginId;
+ char strVal[STR_MAX];
+ };
+ struct PortToIdData {
+ uint group;
+ uint port;
+ uint flags;
+ char strVal[STR_MAX];
+ };
+ struct ConnectionToIdData {
+ uint id;
+ char strVal[STR_MAX];
+ };
+
void initJackPatchbay(const bool sendHost, const bool sendOSC, const char* const ourName)
{
CARLA_SAFE_ASSERT_RETURN(pData->options.processMode != ENGINE_PROCESS_MODE_PATCHBAY ||
(fExternalPatchbayHost && sendHost) || (fExternalPatchbayOsc && sendOSC),);
CARLA_SAFE_ASSERT_RETURN(ourName != nullptr && ourName[0] != '\0',);
+ uint id, carlaId;
CarlaStringList parsedGroups;
+ LinkedList groupCallbackData;
+ LinkedList portsCallbackData;
+ LinkedList connCallbackData;
- // add our client first
{
- parsedGroups.append(ourName);
+ const CarlaMutexLocker cml1(fUsedGroups.mutex);
+ const CarlaMutexLocker cml2(fUsedPorts.mutex);
+ const CarlaMutexLocker cml3(fUsedConnections.mutex);
- GroupNameToId groupNameToId;
- groupNameToId.setData(++fUsedGroups.lastId, ourName);
-
- callback(sendHost, sendOSC,
- ENGINE_CALLBACK_PATCHBAY_CLIENT_ADDED,
- groupNameToId.group,
- PATCHBAY_ICON_CARLA,
- MAIN_CARLA_PLUGIN_ID,
- 0, 0.0f,
- groupNameToId.name);
- fUsedGroups.list.append(groupNameToId);
- }
+ // add our client first
+ {
+ carlaId = ++fUsedGroups.lastId;
+ parsedGroups.append(ourName);
- // query all jack ports
- {
- const char** const ports = jackbridge_get_ports(fClient, nullptr, nullptr, 0);
- CARLA_SAFE_ASSERT_RETURN(ports != nullptr,);
+ GroupNameToId groupNameToId;
+ groupNameToId.setData(carlaId, ourName);
+ fUsedGroups.list.append(groupNameToId);
+ }
- for (int i=0; ports[i] != nullptr; ++i)
+ // query all jack ports
{
- const char* const fullPortName(ports[i]);
- CARLA_SAFE_ASSERT_CONTINUE(fullPortName != nullptr && fullPortName[0] != '\0');
+ const char** const ports = jackbridge_get_ports(fClient, nullptr, nullptr, 0);
+ CARLA_SAFE_ASSERT_RETURN(ports != nullptr,);
- const jack_port_t* const jackPort(jackbridge_port_by_name(fClient, fullPortName));
- CARLA_SAFE_ASSERT_CONTINUE(jackPort != nullptr);
+ for (int i=0; ports[i] != nullptr; ++i)
+ {
+ const char* const fullPortName(ports[i]);
+ CARLA_SAFE_ASSERT_CONTINUE(fullPortName != nullptr && fullPortName[0] != '\0');
- const char* const shortPortName(jackbridge_port_short_name(jackPort));
- CARLA_SAFE_ASSERT_CONTINUE(shortPortName != nullptr && shortPortName[0] != '\0');
+ const jack_port_t* const jackPort(jackbridge_port_by_name(fClient, fullPortName));
+ CARLA_SAFE_ASSERT_CONTINUE(jackPort != nullptr);
- const CarlaJackPortHints jackPortHints(CarlaJackPortHints::fromPort(jackPort));
+ const char* const shortPortName(jackbridge_port_short_name(jackPort));
+ CARLA_SAFE_ASSERT_CONTINUE(shortPortName != nullptr && shortPortName[0] != '\0');
- uint groupId = 0;
+ const CarlaJackPortHints jackPortHints(CarlaJackPortHints::fromPort(jackPort));
- bool found;
- CarlaString groupName(fullPortName);
- groupName.truncate(groupName.rfind(shortPortName, &found)-1);
+ uint groupId = 0;
- CARLA_SAFE_ASSERT_CONTINUE(found);
+ bool found;
+ CarlaString groupName(fullPortName);
+ groupName.truncate(groupName.rfind(shortPortName, &found)-1);
- if (parsedGroups.contains(groupName))
- {
- groupId = fUsedGroups.getGroupId(groupName);
- CARLA_SAFE_ASSERT_CONTINUE(groupId > 0);
+ CARLA_SAFE_ASSERT_CONTINUE(found);
+
+ if (parsedGroups.contains(groupName))
+ {
+ groupId = fUsedGroups.getGroupId(groupName);
+ CARLA_SAFE_ASSERT_CONTINUE(groupId > 0);
+ }
+ else
+ {
+ groupId = ++fUsedGroups.lastId;
+ parsedGroups.append(groupName);
+
+ GroupNameToId groupNameToId;
+ groupNameToId.setData(groupId, groupName);
+
+ int pluginId = -1;
+ PatchbayIcon icon = jackPortHints.isHardware ? PATCHBAY_ICON_HARDWARE : PATCHBAY_ICON_APPLICATION;
+
+ findPluginIdAndIcon(groupName, pluginId, icon);
+
+ fUsedGroups.list.append(groupNameToId);
+
+ GroupToIdData groupData;
+ groupData.id = groupId;
+ groupData.icon = icon;
+ groupData.pluginId = pluginId;
+ std::strncpy(groupData.strVal, groupName, STR_MAX-1);
+ groupData.strVal[STR_MAX-1] = '\0';
+ groupCallbackData.append(groupData);
+ }
+
+ uint canvasPortFlags = 0x0;
+ canvasPortFlags |= jackPortHints.isInput ? PATCHBAY_PORT_IS_INPUT : 0x0;
+
+ /**/ if (jackPortHints.isCV)
+ canvasPortFlags |= PATCHBAY_PORT_TYPE_CV;
+ else if (jackPortHints.isOSC)
+ canvasPortFlags |= PATCHBAY_PORT_TYPE_OSC;
+ else if (jackPortHints.isAudio)
+ canvasPortFlags |= PATCHBAY_PORT_TYPE_AUDIO;
+ else if (jackPortHints.isMIDI)
+ canvasPortFlags |= PATCHBAY_PORT_TYPE_MIDI;
+
+ id = ++fUsedPorts.lastId;
+
+ PortNameToId portNameToId;
+ portNameToId.setData(groupId, id, shortPortName, fullPortName);
+ fUsedPorts.list.append(portNameToId);
+
+ PortToIdData portData;
+ portData.group = groupId;
+ portData.port = id;
+ portData.flags = canvasPortFlags;
+ std::strncpy(portData.strVal, shortPortName, STR_MAX-1);
+ portData.strVal[STR_MAX-1] = '\0';
+ portsCallbackData.append(portData);
}
- else
+
+ jackbridge_free(ports);
+ }
+
+ // query connections, after all ports are in place
+ if (const char** const ports = jackbridge_get_ports(fClient, nullptr, nullptr, JackPortIsOutput))
+ {
+ for (int i=0; ports[i] != nullptr; ++i)
{
- groupId = ++fUsedGroups.lastId;
- parsedGroups.append(groupName);
+ const char* const fullPortName(ports[i]);
+ CARLA_SAFE_ASSERT_CONTINUE(fullPortName != nullptr && fullPortName[0] != '\0');
- GroupNameToId groupNameToId;
- groupNameToId.setData(groupId, groupName);
+ const jack_port_t* const jackPort(jackbridge_port_by_name(fClient, fullPortName));
+ CARLA_SAFE_ASSERT_CONTINUE(jackPort != nullptr);
- int pluginId = -1;
- PatchbayIcon icon = jackPortHints.isHardware ? PATCHBAY_ICON_HARDWARE : PATCHBAY_ICON_APPLICATION;
+ const PortNameToId& thisPort(fUsedPorts.getPortNameToId(fullPortName));
- findPluginIdAndIcon(groupName, pluginId, icon);
+ CARLA_SAFE_ASSERT_CONTINUE(thisPort.group > 0);
+ CARLA_SAFE_ASSERT_CONTINUE(thisPort.port > 0);
- callback(sendHost, sendOSC,
- ENGINE_CALLBACK_PATCHBAY_CLIENT_ADDED,
- groupNameToId.group,
- icon,
- pluginId,
- 0, 0.0f,
- groupNameToId.name);
- fUsedGroups.list.append(groupNameToId);
+ if (const char** const connections = jackbridge_port_get_all_connections(fClient, jackPort))
+ {
+ for (int j=0; connections[j] != nullptr; ++j)
+ {
+ const char* const connection(connections[j]);
+ CARLA_SAFE_ASSERT_CONTINUE(connection != nullptr && connection[0] != '\0');
+
+ const PortNameToId& targetPort(fUsedPorts.getPortNameToId(connection));
+
+ CARLA_SAFE_ASSERT_CONTINUE(targetPort.group > 0);
+ CARLA_SAFE_ASSERT_CONTINUE(targetPort.port > 0);
+
+ id = ++fUsedConnections.lastId;
+
+ ConnectionToId connectionToId;
+ connectionToId.setData(id, thisPort.group, thisPort.port, targetPort.group, targetPort.port);
+ fUsedConnections.list.append(connectionToId);
+
+ ConnectionToIdData connData;
+ connData.id = id;
+ std::snprintf(connData.strVal, STR_MAX-1, "%i:%i:%i:%i",
+ thisPort.group, thisPort.port, targetPort.group, targetPort.port);
+ connData.strVal[STR_MAX-1] = '\0';
+ connCallbackData.append(connData);
+ }
+
+ jackbridge_free(connections);
+ }
}
- addPatchbayJackPort(sendHost, sendOSC, groupId, jackPortHints, shortPortName, fullPortName);
+ jackbridge_free(ports);
}
-
- jackbridge_free(ports);
}
- // query connections, after all ports are in place
- if (const char** const ports = jackbridge_get_ports(fClient, nullptr, nullptr, JackPortIsOutput))
- {
- char strBuf[STR_MAX+1];
-
- for (int i=0; ports[i] != nullptr; ++i)
- {
- const char* const fullPortName(ports[i]);
- CARLA_SAFE_ASSERT_CONTINUE(fullPortName != nullptr && fullPortName[0] != '\0');
+ const GroupToIdData groupFallback = { 0, PATCHBAY_ICON_PLUGIN, -1, { '\0' } };
+ const PortToIdData portFallback = { 0, 0, 0, { '\0' } };
+ const ConnectionToIdData connFallback = { 0, { '\0' } };
- const jack_port_t* const jackPort(jackbridge_port_by_name(fClient, fullPortName));
- CARLA_SAFE_ASSERT_CONTINUE(jackPort != nullptr);
+ callback(sendHost, sendOSC,
+ ENGINE_CALLBACK_PATCHBAY_CLIENT_ADDED,
+ carlaId,
+ PATCHBAY_ICON_CARLA,
+ MAIN_CARLA_PLUGIN_ID,
+ 0, 0.0f,
+ ourName);
- const PortNameToId& thisPort(fUsedPorts.getPortNameToId(fullPortName));
+ for (LinkedList::Itenerator it = groupCallbackData.begin2(); it.valid(); it.next())
+ {
+ const GroupToIdData& group(it.getValue(groupFallback));
- CARLA_SAFE_ASSERT_CONTINUE(thisPort.group > 0);
- CARLA_SAFE_ASSERT_CONTINUE(thisPort.port > 0);
+ callback(sendHost, sendOSC,
+ ENGINE_CALLBACK_PATCHBAY_CLIENT_ADDED,
+ group.id,
+ group.icon,
+ group.pluginId,
+ 0, 0.0f,
+ group.strVal);
- if (const char** const connections = jackbridge_port_get_all_connections(fClient, jackPort))
- {
- for (int j=0; connections[j] != nullptr; ++j)
- {
- const char* const connection(connections[j]);
- CARLA_SAFE_ASSERT_CONTINUE(connection != nullptr && connection[0] != '\0');
+ const char* const uuidstr = jackbridge_get_uuid_for_client_name(fClient, group.strVal);
+ CARLA_SAFE_ASSERT_RETURN(uuidstr != nullptr && uuidstr[0] != '\0',);
- const PortNameToId& targetPort(fUsedPorts.getPortNameToId(connection));
+ jack_uuid_t uuid;
+ CARLA_SAFE_ASSERT_RETURN(jackbridge_uuid_parse(uuidstr, &uuid),);
- CARLA_SAFE_ASSERT_CONTINUE(targetPort.group > 0);
- CARLA_SAFE_ASSERT_CONTINUE(targetPort.port > 0);
+ char* value = nullptr;
+ char* type = nullptr;
- std::snprintf(strBuf, STR_MAX, "%i:%i:%i:%i", thisPort.group, thisPort.port, targetPort.group, targetPort.port);
- strBuf[STR_MAX] = '\0';
+ if (jackbridge_get_property(uuid, URI_POSITION, &value, &type)
+ && value != nullptr
+ && type != nullptr
+ && std::strcmp(type, URI_TYPE_STRING) == 0)
+ {
+ if (char* sep1 = std::strstr(value, ":"))
+ {
+ int x1, y1 = 0, x2 = 0, y2 = 0;
+ *sep1++ = '\0';
+ x1 = std::atoi(value);
- ConnectionToId connectionToId;
- connectionToId.setData(++fUsedConnections.lastId, thisPort.group, thisPort.port, targetPort.group, targetPort.port);
+ if (char* sep2 = std::strstr(sep1, ":"))
+ {
+ *sep2++ = '\0';
+ y1 = std::atoi(sep1);
- callback(sendHost, sendOSC,
- ENGINE_CALLBACK_PATCHBAY_CONNECTION_ADDED,
- connectionToId.id,
- 0, 0, 0, 0.0f,
- strBuf);
- fUsedConnections.list.append(connectionToId);
+ if (char* sep3 = std::strstr(sep2, ":"))
+ {
+ *sep3++ = '\0';
+ x2 = std::atoi(sep2);
+ y2 = std::atoi(sep3);
+ }
}
- jackbridge_free(connections);
+ callback(sendHost, sendOSC,
+ ENGINE_CALLBACK_PATCHBAY_CLIENT_POSITION_CHANGED,
+ group.id, x1, y1, x2, static_cast(y2),
+ nullptr);
}
}
-
- jackbridge_free(ports);
}
- }
- void addPatchbayJackPort(const bool sendHost, const bool sendOSC,
- const uint groupId, const CarlaJackPortHints& jackPortHints,
- const char* const shortPortName, const char* const fullPortName)
- {
- uint canvasPortFlags = 0x0;
- canvasPortFlags |= jackPortHints.isInput ? PATCHBAY_PORT_IS_INPUT : 0x0;
+ for (LinkedList::Itenerator it = portsCallbackData.begin2(); it.valid(); it.next())
+ {
+ const PortToIdData& port(it.getValue(portFallback));
- /**/ if (jackPortHints.isCV)
- canvasPortFlags |= PATCHBAY_PORT_TYPE_CV;
- else if (jackPortHints.isOSC)
- canvasPortFlags |= PATCHBAY_PORT_TYPE_OSC;
- else if (jackPortHints.isAudio)
- canvasPortFlags |= PATCHBAY_PORT_TYPE_AUDIO;
- else if (jackPortHints.isMIDI)
- canvasPortFlags |= PATCHBAY_PORT_TYPE_MIDI;
+ callback(sendHost, sendOSC,
+ ENGINE_CALLBACK_PATCHBAY_PORT_ADDED,
+ port.group,
+ static_cast(port.port),
+ static_cast(port.flags),
+ 0, 0.0f,
+ port.strVal);
+ }
- PortNameToId portNameToId;
- portNameToId.setData(groupId, ++fUsedPorts.lastId, shortPortName, fullPortName);
+ for (LinkedList::Itenerator it = connCallbackData.begin2(); it.valid(); it.next())
+ {
+ const ConnectionToIdData& conn(it.getValue(connFallback));
- callback(sendHost, sendOSC,
- ENGINE_CALLBACK_PATCHBAY_PORT_ADDED,
- portNameToId.group,
- static_cast(portNameToId.port),
- static_cast(canvasPortFlags),
- 0, 0.0f,
- portNameToId.name);
- fUsedPorts.list.append(portNameToId);
+ callback(sendHost, sendOSC,
+ ENGINE_CALLBACK_PATCHBAY_CONNECTION_ADDED,
+ conn.id,
+ 0, 0, 0, 0.0f,
+ conn.strVal);
+ }
+
+ groupCallbackData.clear();
+ portsCallbackData.clear();
+ connCallbackData.clear();
}
#endif
diff --git a/source/backend/engine/CarlaEngineNative.cpp b/source/backend/engine/CarlaEngineNative.cpp
index 19e82c922..91209d4fd 100644
--- a/source/backend/engine/CarlaEngineNative.cpp
+++ b/source/backend/engine/CarlaEngineNative.cpp
@@ -1816,6 +1816,22 @@ bool CarlaEngineNativeUI::msgReceived(const char* const msg) noexcept
ok = fEngine->patchbayDisconnect(external, connectionId);
} 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)
{
bool external;
diff --git a/source/frontend/carla_backend.py b/source/frontend/carla_backend.py
index 8f41f3016..efcebcdf1 100644
--- a/source/frontend/carla_backend.py
+++ b/source/frontend/carla_backend.py
@@ -818,6 +818,14 @@ ENGINE_CALLBACK_PATCHBAY_PORT_GROUP_CHANGED = 45
# @a valueStr New mapped range as "%f:%f" syntax
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 opcodes.
@@ -1678,6 +1686,14 @@ class CarlaHostMeta(object):
def patchbay_disconnect(self, external, connectionId):
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.
# @param external Wherever to show external/hardware ports instead of internal ones.
# Only valid in patchbay engine mode, other modes will ignore this.
@@ -2306,6 +2322,9 @@ class CarlaHostNull(CarlaHostMeta):
def patchbay_disconnect(self, external, connectionId):
return False
+ def patchbay_set_group_pos(self, external, groupId, x1, y1, x2, y2):
+ return False
+
def patchbay_refresh(self, external):
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.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.restype = c_bool
@@ -2933,6 +2955,9 @@ class CarlaHostDLL(CarlaHostMeta):
def patchbay_disconnect(self, 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):
return bool(self.lib.carla_patchbay_refresh(self.handle, external))
@@ -3325,6 +3350,9 @@ class CarlaHostPlugin(CarlaHostMeta):
def patchbay_disconnect(self, 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):
return self.sendMsgAndSetError(["patchbay_refresh", external])
diff --git a/source/frontend/carla_backend_qt.py b/source/frontend/carla_backend_qt.py
index 008521cea..152b29fa8 100644
--- a/source/frontend/carla_backend_qt.py
+++ b/source/frontend/carla_backend_qt.py
@@ -56,6 +56,7 @@ class CarlaHostSignals(QObject):
PatchbayClientRemovedCallback = pyqtSignal(int)
PatchbayClientRenamedCallback = pyqtSignal(int, str)
PatchbayClientDataChangedCallback = pyqtSignal(int, int, int)
+ PatchbayClientPositionChangedCallback = pyqtSignal(int, int, int, int, int)
PatchbayPortAddedCallback = pyqtSignal(int, int, int, int, str)
PatchbayPortRemovedCallback = pyqtSignal(int, int)
PatchbayPortChangedCallback = pyqtSignal(int, int, int, int, str)
diff --git a/source/frontend/carla_host.py b/source/frontend/carla_host.py
index 2c3c17e02..eab6b2238 100644
--- a/source/frontend/carla_host.py
+++ b/source/frontend/carla_host.py
@@ -505,7 +505,6 @@ class HostWindow(QMainWindow):
self.ui.graphicsView.verticalScrollBar().valueChanged.connect(self.slot_verticalScrollBarChanged)
self.ui.miniCanvasPreview.miniCanvasMoved.connect(self.slot_miniCanvasMoved)
self.scene.scaleChanged.connect(self.slot_canvasScaleChanged)
- self.scene.sceneGroupMoved.connect(self.slot_canvasItemMoved)
self.scene.pluginSelected.connect(self.slot_canvasPluginSelected)
self.scene.selectionChanged.connect(self.slot_canvasSelectionChanged)
@@ -533,6 +532,7 @@ class HostWindow(QMainWindow):
host.PatchbayClientRemovedCallback.connect(self.slot_handlePatchbayClientRemovedCallback)
host.PatchbayClientRenamedCallback.connect(self.slot_handlePatchbayClientRenamedCallback)
host.PatchbayClientDataChangedCallback.connect(self.slot_handlePatchbayClientDataChangedCallback)
+ host.PatchbayClientPositionChangedCallback.connect(self.slot_handlePatchbayClientPositionChangedCallback)
host.PatchbayPortAddedCallback.connect(self.slot_handlePatchbayPortAddedCallback)
host.PatchbayPortRemovedCallback.connect(self.slot_handlePatchbayPortRemovedCallback)
host.PatchbayPortChangedCallback.connect(self.slot_handlePatchbayPortChangedCallback)
@@ -713,9 +713,6 @@ class HostWindow(QMainWindow):
# --------------------------------------------------------------------------------------------------------
# Files
- def makeExtraFilename(self):
- return self.fProjectFilename.rsplit(".",1)[0]+".json"
-
def loadProjectNow(self):
if not self.fProjectFilename:
return qCritical("ERROR: loading project without filename set")
@@ -748,14 +745,6 @@ class HostWindow(QMainWindow):
QMessageBox.Ok, QMessageBox.Ok)
return
- if not self.fWithCanvas:
- return
-
- with open(self.makeExtraFilename(), 'w') as fh:
- json.dump({
- 'canvas': patchcanvas.saveGroupPositions(),
- }, fh)
-
def projectLoadingStarted(self):
self.ui.rack.setEnabled(False)
self.ui.graphicsView.setEnabled(False)
@@ -769,7 +758,7 @@ class HostWindow(QMainWindow):
QTimer.singleShot(1000, self.slot_canvasRefresh)
- extrafile = self.makeExtraFilename()
+ extrafile = self.fProjectFilename.rsplit(".",1)[0]+".json"
if not os.path.exists(extrafile):
return
@@ -1431,7 +1420,7 @@ class HostWindow(QMainWindow):
pFeatures.group_rename = False
pFeatures.port_info = False
pFeatures.port_rename = False
- pFeatures.handle_group_pos = True
+ pFeatures.handle_group_pos = False
patchcanvas.setOptions(pOptions)
patchcanvas.setFeatures(pFeatures)
@@ -1567,10 +1556,6 @@ class HostWindow(QMainWindow):
# --------------------------------------------------------------------------------------------------------
# Canvas (canvas callbacks)
- @pyqtSlot(int, int, QPointF)
- def slot_canvasItemMoved(self, group_id, split_mode, pos):
- self.updateMiniCanvasLater()
-
@pyqtSlot()
def slot_canvasSelectionChanged(self):
self.updateMiniCanvasLater()
@@ -1668,6 +1653,10 @@ class HostWindow(QMainWindow):
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)
def slot_handlePatchbayPortAddedCallback(self, clientId, portId, portFlags, portGroupId, portName):
if portFlags & PATCHBAY_PORT_IS_INPUT:
@@ -2720,6 +2709,9 @@ class HostWindow(QMainWindow):
def canvasCallback(action, value1, value2, valueStr):
host = gCarla.gui.host
+ if gCarla.gui.fCustomStopAction == HostWindow.CUSTOM_ACTION_APP_CLOSE:
+ return
+
if action == patchcanvas.ACTION_GROUP_INFO:
pass
@@ -2736,6 +2728,14 @@ def canvasCallback(action, value1, value2, valueStr):
patchcanvas.joinGroup(groupId)
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:
pass
@@ -2743,7 +2743,7 @@ def canvasCallback(action, value1, value2, valueStr):
pass
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):
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)
elif action == ENGINE_CALLBACK_PATCHBAY_CLIENT_DATA_CHANGED:
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:
host.PatchbayPortAddedCallback.emit(pluginId, value1, value2, value3, valueStr)
elif action == ENGINE_CALLBACK_PATCHBAY_PORT_REMOVED:
diff --git a/source/frontend/patchcanvas/__init__.py b/source/frontend/patchcanvas/__init__.py
index c7d90f6dc..cada20c53 100644
--- a/source/frontend/patchcanvas/__init__.py
+++ b/source/frontend/patchcanvas/__init__.py
@@ -49,18 +49,19 @@ ACTION_GROUP_INFO = 0 # group_id, N, N
ACTION_GROUP_RENAME = 1 # group_id, N, N
ACTION_GROUP_SPLIT = 2 # group_id, N, N
ACTION_GROUP_JOIN = 3 # group_id, N, N
-ACTION_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_APPLICATION = 0
@@ -134,6 +135,7 @@ class Canvas(object):
self.connection_list = []
self.animation_list = []
self.group_plugin_map = {}
+ self.old_group_pos = {}
self.callback = self.callback
self.debug = False
diff --git a/source/frontend/patchcanvas/canvasbox.py b/source/frontend/patchcanvas/canvasbox.py
index 4cabace00..2b731288f 100644
--- a/source/frontend/patchcanvas/canvasbox.py
+++ b/source/frontend/patchcanvas/canvasbox.py
@@ -22,9 +22,9 @@
from sip import voidptr
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.QtWidgets import QGraphicsItem, QMenu
+from PyQt5.QtWidgets import QGraphicsItem, QGraphicsObject, QMenu
# ------------------------------------------------------------------------------------------------------------
# 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_ENABLED = 1
INLINE_DISPLAY_CACHED = 2
def __init__(self, group_id, group_name, icon, parent=None):
- QGraphicsItem.__init__(self)
+ QGraphicsObject.__init__(self)
self.setParentItem(parent)
# Save Variables, useful for later
@@ -109,6 +113,7 @@ class CanvasBox(QGraphicsItem):
self.m_inline_data = None
self.m_inline_image = None
self.m_inline_scaling = 1.0
+ self.m_will_signal_pos_change = False
self.m_port_list_ids = []
self.m_connection_lines = []
@@ -153,6 +158,10 @@ class CanvasBox(QGraphicsItem):
self.updatePositions()
+ self.visibleChanged.connect(self.slot_signalPositionChangedLater)
+ self.xChanged.connect(self.slot_signalPositionChangedLater)
+ self.yChanged.connect(self.slot_signalPositionChangedLater)
+
canvas.scene.addItem(self)
QTimer.singleShot(0, self.fixPos)
@@ -270,21 +279,31 @@ class CanvasBox(QGraphicsItem):
return
qCritical("PatchCanvas::CanvasBox.removeLineFromGroup(%i) - unable to find line to remove" % connection_id)
- def checkItemPos(self):
- if not canvas.size_rect.isNull():
- pos = self.scenePos()
- if not (canvas.size_rect.contains(pos) and
- canvas.size_rect.contains(pos + QPointF(self.p_width, self.p_height))):
- if pos.x() < canvas.size_rect.x():
- self.setPos(canvas.size_rect.x(), pos.y())
- elif pos.x() + self.p_width > canvas.size_rect.width():
- self.setPos(canvas.size_rect.width() - self.p_width, pos.y())
-
- pos = self.scenePos()
- if pos.y() < canvas.size_rect.y():
- self.setPos(pos.x(), canvas.size_rect.y())
- elif pos.y() + self.p_height > canvas.size_rect.height():
- self.setPos(pos.x(), canvas.size_rect.height() - self.p_height)
+ def 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):
if self.icon_svg is None:
@@ -389,6 +408,17 @@ class CanvasBox(QGraphicsItem):
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):
return CanvasBoxType
@@ -570,14 +600,14 @@ class CanvasBox(QGraphicsItem):
event.accept()
canvas.callback(ACTION_PLUGIN_REMOVE, self.m_plugin_id, 0, "")
return
- QGraphicsItem.keyPressEvent(self, event)
+ QGraphicsObject.keyPressEvent(self, event)
def hoverEnterEvent(self, event):
if options.auto_select_items:
if len(canvas.scene.selectedItems()) > 0:
canvas.scene.clearSelection()
self.setSelected(True)
- QGraphicsItem.hoverEnterEvent(self, event)
+ QGraphicsObject.hoverEnterEvent(self, event)
def mouseDoubleClickEvent(self, event):
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, "")
return
- QGraphicsItem.mouseDoubleClickEvent(self, event)
+ QGraphicsObject.mouseDoubleClickEvent(self, event)
def mousePressEvent(self, event):
canvas.last_z_value += 1
@@ -612,7 +642,7 @@ class CanvasBox(QGraphicsItem):
else:
self.m_mouse_down = False
- QGraphicsItem.mousePressEvent(self, event)
+ QGraphicsObject.mousePressEvent(self, event)
def mouseMoveEvent(self, event):
if self.m_mouse_down:
@@ -620,7 +650,7 @@ class CanvasBox(QGraphicsItem):
self.setCursor(QCursor(Qt.SizeAllCursor))
self.m_cursor_moving = True
self.repaintLines()
- QGraphicsItem.mouseMoveEvent(self, event)
+ QGraphicsObject.mouseMoveEvent(self, event)
def mouseReleaseEvent(self, event):
if self.m_cursor_moving:
@@ -628,11 +658,13 @@ class CanvasBox(QGraphicsItem):
QTimer.singleShot(0, self.fixPos)
self.m_mouse_down = False
self.m_cursor_moving = False
- QGraphicsItem.mouseReleaseEvent(self, event)
+ QGraphicsObject.mouseReleaseEvent(self, event)
def fixPos(self):
+ self.blockSignals(True)
self.setX(round(self.x()))
self.setY(round(self.y()))
+ self.blockSignals(False)
def boundingRect(self):
return QRectF(0, 0, self.p_width, self.p_height)
diff --git a/source/frontend/patchcanvas/patchcanvas.py b/source/frontend/patchcanvas/patchcanvas.py
index 04cab3b6f..435aafdf0 100644
--- a/source/frontend/patchcanvas/patchcanvas.py
+++ b/source/frontend/patchcanvas/patchcanvas.py
@@ -138,6 +138,36 @@ class CanvasObject(QObject):
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):
@@ -201,11 +231,17 @@ def clear():
if canvas.debug:
print("PatchCanvas::clear()")
+ group_pos = {}
group_list_ids = []
port_list_ids = []
connection_list_ids = []
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)
for port in canvas.port_list:
@@ -230,6 +266,7 @@ def clear():
canvas.port_list = []
canvas.connection_list = []
canvas.group_plugin_map = {}
+ canvas.old_group_pos = group_pos
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)))
return
+ old_matching_group = canvas.old_group_pos.pop(group_name, None)
+
if split == SPLIT_UNDEF:
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)
elif isHardware:
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.positionChanged.connect(canvas.qobject.boxPositionChanged)
group_dict = group_dict_t()
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:
group_box.setSplit(True, PORT_MODE_OUTPUT)
+ group_box.blockSignals(True)
if features.handle_group_pos:
group_box.setPos(getStoredCanvasPosition(group_name + "_OUTPUT", CanvasGetNewGroupPos(False)))
+ elif old_matching_group is not None:
+ group_box.setPos(old_matching_group[1])
else:
group_box.setPos(CanvasGetNewGroupPos(False))
+ group_box.blockSignals(False)
group_sbox = CanvasBox(group_id, group_name, icon)
group_sbox.setSplit(True, PORT_MODE_INPUT)
+ group_sbox.positionChanged.connect(canvas.qobject.sboxPositionChanged)
group_dict.widgets[1] = group_sbox
+ group_sbox.blockSignals(True)
if features.handle_group_pos:
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:
group_sbox.setPos(CanvasGetNewGroupPos(True))
+ group_sbox.blockSignals(False)
canvas.last_z_value += 1
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:
CanvasItemFX(group_sbox, True, False)
- group_sbox.checkItemPos()
+ group_sbox.checkItemPos(True)
else:
group_box.setSplit(False)
if features.handle_group_pos:
group_box.setPos(getStoredCanvasPosition(group_name, CanvasGetNewGroupPos(False)))
+ elif old_matching_group is not None:
+ group_box.setPos(old_matching_group[1])
else:
# Special ladish fake-split groups
horizontal = bool(icon == ICON_HARDWARE or icon == ICON_LADISH_ROOM)
group_box.setPos(CanvasGetNewGroupPos(horizontal))
- group_box.checkItemPos()
+ group_box.checkItemPos(True)
canvas.last_z_value += 1
group_box.setZValue(canvas.last_z_value)
@@ -630,10 +683,14 @@ def restoreGroupPositions(dataList):
if group is None:
continue
+ group.widgets[0].blockSignals(True)
group.widgets[0].setPos(data['pos1x'], data['pos1y'])
+ group.widgets[0].blockSignals(False)
if group.split and group.widgets[1]:
+ group.widgets[1].blockSignals(True)
group.widgets[1].setPos(data['pos2x'], data['pos2y'])
+ group.widgets[1].blockSignals(False)
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)
@@ -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:
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].blockSignals(False)
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].blockSignals(False)
QTimer.singleShot(0, canvas.scene.update)
return
diff --git a/source/frontend/patchcanvas/scene.py b/source/frontend/patchcanvas/scene.py
index 19f716743..99f26df12 100644
--- a/source/frontend/patchcanvas/scene.py
+++ b/source/frontend/patchcanvas/scene.py
@@ -58,7 +58,6 @@ class RubberbandRect(QGraphicsRectItem):
class PatchScene(QGraphicsScene):
scaleChanged = pyqtSignal(float)
- sceneGroupMoved = pyqtSignal(int, int, QPointF)
pluginSelected = pyqtSignal(list)
def __init__(self, parent, view):
@@ -367,8 +366,7 @@ class PatchScene(QGraphicsScene):
items_list = self.selectedItems()
for item in items_list:
if item and item.isVisible() and item.type() == CanvasBoxType:
- item.checkItemPos()
- self.sceneGroupMoved.emit(item.getGroupId(), item.getSplittedMode(), item.scenePos())
+ item.checkItemPos(False)
if len(items_list) > 1:
canvas.scene.update()
diff --git a/source/utils/CarlaBackendUtils.hpp b/source/utils/CarlaBackendUtils.hpp
index d37711154..2feaee2e2 100644
--- a/source/utils/CarlaBackendUtils.hpp
+++ b/source/utils/CarlaBackendUtils.hpp
@@ -315,6 +315,8 @@ const char* EngineCallbackOpcode2Str(const EngineCallbackOpcode opcode) noexcept
return "ENGINE_CALLBACK_PATCHBAY_PORT_GROUP_CHANGED";
case 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);
diff --git a/source/utils/CarlaPatchbayUtils.hpp b/source/utils/CarlaPatchbayUtils.hpp
index e4b5d1a82..3c0880541 100644
--- a/source/utils/CarlaPatchbayUtils.hpp
+++ b/source/utils/CarlaPatchbayUtils.hpp
@@ -1,6 +1,6 @@
/*
* Carla patchbay utils
- * Copyright (C) 2011-2017 Filipe Coelho
+ * Copyright (C) 2011-2020 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
@@ -27,7 +27,7 @@
struct GroupNameToId {
uint group;
- char name[STR_MAX+1]; // globally unique
+ char name[STR_MAX]; // globally unique
void clear() noexcept
{
@@ -43,15 +43,15 @@ struct GroupNameToId {
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
{
if (groupNameToId.group != group)
return false;
- if (std::strncmp(groupNameToId.name, name, STR_MAX) != 0)
+ if (std::strncmp(groupNameToId.name, name, STR_MAX-1) != 0)
return false;
return true;
}
@@ -65,10 +65,12 @@ struct GroupNameToId {
struct PatchbayGroupList {
uint lastId;
LinkedList list;
+ CarlaMutex mutex;
PatchbayGroupList() noexcept
: lastId(0),
- list() {}
+ list(),
+ mutex() {}
void clear() noexcept
{
@@ -87,8 +89,8 @@ struct PatchbayGroupList {
struct PortNameToId {
uint group;
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
{
@@ -107,26 +109,26 @@ struct PortNameToId {
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
{
- 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
{
if (portNameToId.group != group || portNameToId.port != port)
return false;
- if (std::strncmp(portNameToId.name, name, STR_MAX) != 0)
+ if (std::strncmp(portNameToId.name, name, STR_MAX-1) != 0)
return false;
- if (std::strncmp(portNameToId.fullName, fullName, STR_MAX) != 0)
+ if (std::strncmp(portNameToId.fullName, fullName, STR_MAX-1) != 0)
return false;
return true;
}
@@ -140,10 +142,12 @@ struct PortNameToId {
struct PatchbayPortList {
uint lastId;
LinkedList list;
+ CarlaMutex mutex;
PatchbayPortList() noexcept
: lastId(0),
- list() {}
+ list(),
+ mutex() {}
void clear() noexcept
{