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 4 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 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;



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

@@ -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.


+ 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);

/*!
* 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.


+ 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);
}

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


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

@@ -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 <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";
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 <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";
@@ -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)


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

@@ -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<float>(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<float>(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<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)
@@ -1845,7 +1910,7 @@ void PatchbayGraph::replacePlugin(CarlaPlugin* const oldPlugin, CarlaPlugin* con
node->properties.set("isPlugin", true);
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)
@@ -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)
{
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; 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
{
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)


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

@@ -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,


+ 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);
} 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;


+ 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
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])



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

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


+ 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.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:


+ 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_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


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

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


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

@@ -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


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

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


+ 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";
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);


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

@@ -1,6 +1,6 @@
/*
* 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
* 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<GroupNameToId> 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<PortNameToId> list;
CarlaMutex mutex;

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

void clear() noexcept
{


Loading…
Cancel
Save