Signed-off-by: falkTX <falktx@falktx.com>tags/v2.2.0-RC1
@@ -1141,7 +1141,17 @@ typedef enum { | |||||
* @a value1 Parameter index | * @a value1 Parameter index | ||||
* @a valueStr New mapped range as "%f:%f" syntax | * @a valueStr New mapped range as "%f:%f" syntax | ||||
*/ | */ | ||||
ENGINE_CALLBACK_PARAMETER_MAPPED_RANGE_CHANGED = 46 | |||||
ENGINE_CALLBACK_PARAMETER_MAPPED_RANGE_CHANGED = 46, | |||||
/*! | |||||
* A patchbay client position has changed. | |||||
* @a pluginId Client Id | |||||
* @a value1 X position 1 | |||||
* @a value2 Y position 1 | |||||
* @a value3 X position 2 | |||||
* @a valuef Y position 2 | |||||
*/ | |||||
ENGINE_CALLBACK_PATCHBAY_CLIENT_POSITION_CHANGED = 47, | |||||
} EngineCallbackOpcode; | } EngineCallbackOpcode; | ||||
@@ -1168,6 +1168,12 @@ public: | |||||
*/ | */ | ||||
virtual bool patchbayDisconnect(bool external, uint connectionId); | virtual bool patchbayDisconnect(bool external, uint connectionId); | ||||
/*! | |||||
* Set the position of a group. | |||||
*/ | |||||
virtual bool patchbaySetGroupPos(bool sendHost, bool sendOSC, bool external, | |||||
uint groupId, int x1, int y1, int x2, int y2); | |||||
/*! | /*! | ||||
* Force the engine to resend all patchbay clients, ports and connections again. | * Force the engine to resend all patchbay clients, ports and connections again. | ||||
*/ | */ | ||||
@@ -1348,10 +1354,11 @@ protected: | |||||
* Virtual functions for handling patchbay state. | * Virtual functions for handling patchbay state. | ||||
* Do not free returned data. | * Do not free returned data. | ||||
*/ | */ | ||||
struct PatchbayPosition { const char* name; int x1, y1, x2, y2; bool dealloc; }; | |||||
virtual const char* const* getPatchbayConnections(bool external) const; | virtual const char* const* getPatchbayConnections(bool external) const; | ||||
virtual void restorePatchbayConnection(bool external, | |||||
const char* sourcePort, | |||||
const char* targetPort); | |||||
virtual const PatchbayPosition* getPatchbayPositions(bool external, uint& count) const; | |||||
virtual void restorePatchbayConnection(bool external, const char* sourcePort, const char* targetPort); | |||||
virtual void restorePatchbayGroupPosition(bool external, const PatchbayPosition& ppos); | |||||
/*! | /*! | ||||
* Virtual functions for handling external graph ports. | * Virtual functions for handling external graph ports. | ||||
@@ -550,6 +550,15 @@ CARLA_EXPORT bool carla_patchbay_connect(CarlaHostHandle handle, bool external, | |||||
*/ | */ | ||||
CARLA_EXPORT bool carla_patchbay_disconnect(CarlaHostHandle handle, bool external, uint connectionId); | CARLA_EXPORT bool carla_patchbay_disconnect(CarlaHostHandle handle, bool external, uint connectionId); | ||||
/*! | |||||
* Set the position of a group. | |||||
* This is purely cached and saved in the project file, Carla backend does nothing with the value. | |||||
* When loading a project, callbacks are used to inform of the previously saved positions. | |||||
* @see ENGINE_CALLBACK_PATCHBAY_CLIENT_POSITION_CHANGED | |||||
*/ | |||||
CARLA_EXPORT bool carla_patchbay_set_group_pos(CarlaHostHandle handle, bool external, | |||||
uint groupId, int x1, int y1, int x2, int y2); | |||||
/*! | /*! | ||||
* Force the engine to resend all patchbay clients, ports and connections again. | * Force the engine to resend all patchbay clients, ports and connections again. | ||||
* @param external Wherever to show external/hardware ports instead of internal ones. | * @param external Wherever to show external/hardware ports instead of internal ones. | ||||
@@ -970,6 +970,16 @@ bool carla_patchbay_disconnect(CarlaHostHandle handle, bool external, uint conne | |||||
return handle->engine->patchbayDisconnect(external, connectionId); | return handle->engine->patchbayDisconnect(external, connectionId); | ||||
} | } | ||||
bool carla_patchbay_set_group_pos(CarlaHostHandle handle, bool external, uint groupId, int x1, int y1, int x2, int y2) | |||||
{ | |||||
CARLA_SAFE_ASSERT_WITH_LAST_ERROR_RETURN(handle->engine != nullptr, "Engine is not initialized", false); | |||||
carla_debug("carla_patchbay_set_group_pos(%p, %s, %u, %i, %i, %i, %i)", | |||||
handle, bool2str(external), groupId, x1, y1, x2, y2); | |||||
return handle->engine->patchbaySetGroupPos(true, false, external, groupId, x1, y1, x2, y2); | |||||
} | |||||
bool carla_patchbay_refresh(CarlaHostHandle handle, bool external) | bool carla_patchbay_refresh(CarlaHostHandle handle, bool external) | ||||
{ | { | ||||
CARLA_SAFE_ASSERT_WITH_LAST_ERROR_RETURN(handle->engine != nullptr, "Engine is not initialized", false); | CARLA_SAFE_ASSERT_WITH_LAST_ERROR_RETURN(handle->engine != nullptr, "Engine is not initialized", false); | ||||
@@ -2198,67 +2198,149 @@ void CarlaEngine::saveProjectInternal(water::MemoryOutputStream& outStream) cons | |||||
// save internal connections | // save internal connections | ||||
if (pData->options.processMode == ENGINE_PROCESS_MODE_PATCHBAY) | if (pData->options.processMode == ENGINE_PROCESS_MODE_PATCHBAY) | ||||
{ | { | ||||
if (const char* const* const patchbayConns = getPatchbayConnections(false)) | |||||
uint posCount = 0; | |||||
const char* const* const patchbayConns = getPatchbayConnections(false); | |||||
const PatchbayPosition* const patchbayPos = getPatchbayPositions(false, posCount); | |||||
if (patchbayConns != nullptr || patchbayPos != nullptr) | |||||
{ | { | ||||
MemoryOutputStream outPatchbay(2048); | MemoryOutputStream outPatchbay(2048); | ||||
outPatchbay << "\n <Patchbay>\n"; | outPatchbay << "\n <Patchbay>\n"; | ||||
for (int i=0; patchbayConns[i] != nullptr && patchbayConns[i+1] != nullptr; ++i, ++i ) | |||||
if (patchbayConns != nullptr) | |||||
{ | { | ||||
const char* const connSource(patchbayConns[i]); | |||||
const char* const connTarget(patchbayConns[i+1]); | |||||
for (int i=0; patchbayConns[i] != nullptr && patchbayConns[i+1] != nullptr; ++i, ++i) | |||||
{ | |||||
const char* const connSource(patchbayConns[i]); | |||||
const char* const connTarget(patchbayConns[i+1]); | |||||
CARLA_SAFE_ASSERT_CONTINUE(connSource != nullptr && connSource[0] != '\0'); | |||||
CARLA_SAFE_ASSERT_CONTINUE(connTarget != nullptr && connTarget[0] != '\0'); | |||||
CARLA_SAFE_ASSERT_CONTINUE(connSource != nullptr && connSource[0] != '\0'); | |||||
CARLA_SAFE_ASSERT_CONTINUE(connTarget != nullptr && connTarget[0] != '\0'); | |||||
outPatchbay << " <Connection>\n"; | |||||
outPatchbay << " <Source>" << xmlSafeString(connSource, true) << "</Source>\n"; | |||||
outPatchbay << " <Target>" << xmlSafeString(connTarget, true) << "</Target>\n"; | |||||
outPatchbay << " </Connection>\n"; | |||||
outPatchbay << " <Connection>\n"; | |||||
outPatchbay << " <Source>" << xmlSafeString(connSource, true) << "</Source>\n"; | |||||
outPatchbay << " <Target>" << xmlSafeString(connTarget, true) << "</Target>\n"; | |||||
outPatchbay << " </Connection>\n"; | |||||
} | |||||
} | |||||
if (patchbayPos != nullptr && posCount != 0) | |||||
{ | |||||
outPatchbay << " <Positions>\n"; | |||||
for (uint i=0; i<posCount; ++i) | |||||
{ | |||||
const PatchbayPosition& ppos(patchbayPos[i]); | |||||
CARLA_SAFE_ASSERT_CONTINUE(ppos.name != nullptr && ppos.name[0] != '\0'); | |||||
outPatchbay << " <Position x1=\"" << ppos.x1 << "\" y1=\"" << ppos.y1; | |||||
if (ppos.x2 != 0 || ppos.y2 != 0) | |||||
outPatchbay << "\" x2=\"" << ppos.x2 << "\" y2=\"" << ppos.y2; | |||||
outPatchbay << "\">\n"; | |||||
outPatchbay << " <Name>" << xmlSafeString(ppos.name, true) << "</Name>\n"; | |||||
outPatchbay << " </Position>\n"; | |||||
if (ppos.dealloc) | |||||
delete[] ppos.name; | |||||
} | |||||
outPatchbay << " </Positions>\n"; | |||||
} | } | ||||
outPatchbay << " </Patchbay>\n"; | outPatchbay << " </Patchbay>\n"; | ||||
outStream << outPatchbay; | outStream << outPatchbay; | ||||
delete[] patchbayPos; | |||||
} | } | ||||
} | } | ||||
// if we're running inside some session-manager (and using JACK), let them handle the connections | // if we're running inside some session-manager (and using JACK), let them handle the connections | ||||
bool saveExternalConnections; | |||||
bool saveExternalConnections, saveExternalPositions = true; | |||||
/**/ if (isPlugin) | /**/ if (isPlugin) | ||||
{ | |||||
saveExternalConnections = false; | saveExternalConnections = false; | ||||
saveExternalPositions = false; | |||||
} | |||||
else if (std::strcmp(getCurrentDriverName(), "JACK") != 0) | else if (std::strcmp(getCurrentDriverName(), "JACK") != 0) | ||||
{ | |||||
saveExternalConnections = true; | saveExternalConnections = true; | ||||
} | |||||
else if (std::getenv("CARLA_DONT_MANAGE_CONNECTIONS") != nullptr) | else if (std::getenv("CARLA_DONT_MANAGE_CONNECTIONS") != nullptr) | ||||
{ | |||||
saveExternalConnections = false; | saveExternalConnections = false; | ||||
} | |||||
else if (std::getenv("LADISH_APP_NAME") != nullptr) | else if (std::getenv("LADISH_APP_NAME") != nullptr) | ||||
{ | |||||
saveExternalConnections = false; | saveExternalConnections = false; | ||||
} | |||||
else if (std::getenv("NSM_URL") != nullptr) | else if (std::getenv("NSM_URL") != nullptr) | ||||
{ | |||||
saveExternalConnections = false; | saveExternalConnections = false; | ||||
} | |||||
else | else | ||||
{ | |||||
saveExternalConnections = true; | saveExternalConnections = true; | ||||
} | |||||
if (saveExternalConnections) | |||||
if (saveExternalConnections || saveExternalPositions) | |||||
{ | { | ||||
if (const char* const* const patchbayConns = getPatchbayConnections(true)) | |||||
uint posCount = 0; | |||||
const char* const* const patchbayConns = saveExternalConnections | |||||
? getPatchbayConnections(true) | |||||
: nullptr; | |||||
const PatchbayPosition* const patchbayPos = saveExternalPositions | |||||
? getPatchbayPositions(true, posCount) | |||||
: nullptr; | |||||
if (patchbayConns != nullptr || patchbayPos != nullptr) | |||||
{ | { | ||||
MemoryOutputStream outPatchbay(2048); | MemoryOutputStream outPatchbay(2048); | ||||
outPatchbay << "\n <ExternalPatchbay>\n"; | outPatchbay << "\n <ExternalPatchbay>\n"; | ||||
for (int i=0; patchbayConns[i] != nullptr && patchbayConns[i+1] != nullptr; ++i, ++i ) | |||||
if (patchbayConns != nullptr) | |||||
{ | { | ||||
const char* const connSource(patchbayConns[i]); | |||||
const char* const connTarget(patchbayConns[i+1]); | |||||
for (int i=0; patchbayConns[i] != nullptr && patchbayConns[i+1] != nullptr; ++i, ++i ) | |||||
{ | |||||
const char* const connSource(patchbayConns[i]); | |||||
const char* const connTarget(patchbayConns[i+1]); | |||||
CARLA_SAFE_ASSERT_CONTINUE(connSource != nullptr && connSource[0] != '\0'); | |||||
CARLA_SAFE_ASSERT_CONTINUE(connTarget != nullptr && connTarget[0] != '\0'); | |||||
CARLA_SAFE_ASSERT_CONTINUE(connSource != nullptr && connSource[0] != '\0'); | |||||
CARLA_SAFE_ASSERT_CONTINUE(connTarget != nullptr && connTarget[0] != '\0'); | |||||
outPatchbay << " <Connection>\n"; | |||||
outPatchbay << " <Source>" << xmlSafeString(connSource, true) << "</Source>\n"; | |||||
outPatchbay << " <Target>" << xmlSafeString(connTarget, true) << "</Target>\n"; | |||||
outPatchbay << " </Connection>\n"; | |||||
outPatchbay << " <Connection>\n"; | |||||
outPatchbay << " <Source>" << xmlSafeString(connSource, true) << "</Source>\n"; | |||||
outPatchbay << " <Target>" << xmlSafeString(connTarget, true) << "</Target>\n"; | |||||
outPatchbay << " </Connection>\n"; | |||||
} | |||||
} | |||||
if (patchbayPos != nullptr && posCount != 0) | |||||
{ | |||||
outPatchbay << " <Positions>\n"; | |||||
for (uint i=0; i<posCount; ++i) | |||||
{ | |||||
const PatchbayPosition& ppos(patchbayPos[i]); | |||||
CARLA_SAFE_ASSERT_CONTINUE(ppos.name != nullptr && ppos.name[0] != '\0'); | |||||
outPatchbay << " <Position x1=\"" << ppos.x1 << "\" y1=\"" << ppos.y1; | |||||
if (ppos.x2 != 0 || ppos.y2 != 0) | |||||
outPatchbay << "\" x2=\"" << ppos.x2 << "\" y2=\"" << ppos.y2; | |||||
outPatchbay << "\">\n"; | |||||
outPatchbay << " <Name>" << xmlSafeString(ppos.name, true) << "</Name>\n"; | |||||
outPatchbay << " </Position>\n"; | |||||
if (ppos.dealloc) | |||||
delete[] ppos.name; | |||||
} | |||||
outPatchbay << " </Positions>\n"; | |||||
} | } | ||||
outPatchbay << " </ExternalPatchbay>\n"; | outPatchbay << " </ExternalPatchbay>\n"; | ||||
@@ -2843,8 +2925,9 @@ bool CarlaEngine::loadProjectInternal(water::XmlDocument& xmlDoc) | |||||
// plus external connections too | // plus external connections too | ||||
if (loadExternalConnections) | if (loadExternalConnections) | ||||
{ | { | ||||
const bool loadingAsExternal = pData->options.processMode == ENGINE_PROCESS_MODE_PATCHBAY && | |||||
hasInternalConnections; | |||||
bool loadingAsExternal = hasInternalConnections && | |||||
(pData->options.processMode == ENGINE_PROCESS_MODE_CONTINUOUS_RACK || | |||||
pData->options.processMode == ENGINE_PROCESS_MODE_PATCHBAY); | |||||
for (XmlElement* elem = xmlElement->getFirstChildElement(); elem != nullptr; elem = elem->getNextElement()) | for (XmlElement* elem = xmlElement->getFirstChildElement(); elem != nullptr; elem = elem->getNextElement()) | ||||
{ | { | ||||
@@ -2857,7 +2940,11 @@ bool CarlaEngine::loadProjectInternal(water::XmlDocument& xmlDoc) | |||||
continue; | continue; | ||||
} | } | ||||
// or load external patchbay connections | // or load external patchbay connections | ||||
else if (tagName != "ExternalPatchbay") | |||||
else if (tagName == "ExternalPatchbay") | |||||
{ | |||||
loadingAsExternal = true; | |||||
} | |||||
else | |||||
{ | { | ||||
continue; | continue; | ||||
} | } | ||||
@@ -2891,6 +2978,82 @@ bool CarlaEngine::loadProjectInternal(water::XmlDocument& xmlDoc) | |||||
break; | break; | ||||
} | } | ||||
} | } | ||||
// finally, we handle positions | |||||
if (XmlElement* const elemPatchbay = xmlElement->getChildByName("Patchbay")) | |||||
{ | |||||
if (XmlElement* const elemPositions = elemPatchbay->getChildByName("Positions")) | |||||
{ | |||||
String name; | |||||
PatchbayPosition ppos = { nullptr, 0, 0, 0, 0, false }; | |||||
for (XmlElement* patchElem = elemPositions->getFirstChildElement(); patchElem != nullptr; patchElem = patchElem->getNextElement()) | |||||
{ | |||||
const String& patchTag(patchElem->getTagName()); | |||||
if (patchTag != "Position") | |||||
continue; | |||||
XmlElement* const patchName = patchElem->getChildByName("Name"); | |||||
CARLA_SAFE_ASSERT_CONTINUE(patchName != nullptr); | |||||
const String nameText(patchName->getAllSubText().trim()); | |||||
name = xmlSafeString(nameText, false); | |||||
ppos.name = name.toRawUTF8(); | |||||
ppos.x1 = patchElem->getIntAttribute("x1"); | |||||
ppos.y1 = patchElem->getIntAttribute("y1"); | |||||
ppos.x2 = patchElem->getIntAttribute("x2"); | |||||
ppos.y2 = patchElem->getIntAttribute("y2"); | |||||
if (name.isNotEmpty()) | |||||
restorePatchbayGroupPosition(false, ppos); | |||||
} | |||||
callback(true, true, ENGINE_CALLBACK_IDLE, 0, 0, 0, 0, 0.0f, nullptr); | |||||
if (pData->aboutToClose) | |||||
return true; | |||||
if (pData->actionCanceled) | |||||
{ | |||||
setLastError("Project load canceled"); | |||||
return false; | |||||
} | |||||
} | |||||
} | |||||
if (XmlElement* const elemPatchbay = xmlElement->getChildByName("ExternalPatchbay")) | |||||
{ | |||||
if (XmlElement* const elemPositions = elemPatchbay->getChildByName("Positions")) | |||||
{ | |||||
String name; | |||||
PatchbayPosition ppos = { nullptr, 0, 0, 0, 0, false }; | |||||
for (XmlElement* patchElem = elemPositions->getFirstChildElement(); patchElem != nullptr; patchElem = patchElem->getNextElement()) | |||||
{ | |||||
const String& patchTag(patchElem->getTagName()); | |||||
if (patchTag != "Position") | |||||
continue; | |||||
XmlElement* const patchName = patchElem->getChildByName("Name"); | |||||
CARLA_SAFE_ASSERT_CONTINUE(patchName != nullptr); | |||||
const String nameText(patchName->getAllSubText().trim()); | |||||
name = xmlSafeString(nameText, false); | |||||
ppos.name = name.toRawUTF8(); | |||||
ppos.x1 = patchElem->getIntAttribute("x1"); | |||||
ppos.y1 = patchElem->getIntAttribute("y1"); | |||||
ppos.x2 = patchElem->getIntAttribute("x2"); | |||||
ppos.y2 = patchElem->getIntAttribute("y2"); | |||||
if (name.isNotEmpty()) | |||||
restorePatchbayGroupPosition(true, ppos); | |||||
} | |||||
} | |||||
} | |||||
#endif | #endif | ||||
if (pData->options.resetXruns) | if (pData->options.resetXruns) | ||||
@@ -134,8 +134,12 @@ ExternalGraph::ExternalGraph(CarlaEngine* const engine) noexcept | |||||
: connections(), | : connections(), | ||||
audioPorts(), | audioPorts(), | ||||
midiPorts(), | midiPorts(), | ||||
positions(), | |||||
retCon(), | retCon(), | ||||
kEngine(engine) {} | |||||
kEngine(engine) | |||||
{ | |||||
carla_zeroStruct(positions); | |||||
} | |||||
void ExternalGraph::clear() noexcept | void ExternalGraph::clear() noexcept | ||||
{ | { | ||||
@@ -313,11 +317,24 @@ bool ExternalGraph::disconnect(const bool sendHost, const bool sendOSC, | |||||
return false; | return false; | ||||
} | } | ||||
void ExternalGraph::setGroupPos(const bool sendHost, const bool sendOSC, | |||||
const uint groupId, const int x1, const int y1, const int x2, const int y2) | |||||
{ | |||||
CARLA_SAFE_ASSERT_UINT_RETURN(groupId >= kExternalGraphGroupCarla && groupId < kExternalGraphGroupMax, groupId,); | |||||
positions[groupId] = { true, x1, y1, x2, y2 }; | |||||
kEngine->callback(sendHost, sendOSC, | |||||
ENGINE_CALLBACK_PATCHBAY_CLIENT_POSITION_CHANGED, | |||||
groupId, x1, y1, x2, static_cast<float>(y2), | |||||
nullptr); | |||||
} | |||||
void ExternalGraph::refresh(const bool sendHost, const bool sendOSC, const char* const deviceName) | void ExternalGraph::refresh(const bool sendHost, const bool sendOSC, const char* const deviceName) | ||||
{ | { | ||||
CARLA_SAFE_ASSERT_RETURN(deviceName != nullptr,); | CARLA_SAFE_ASSERT_RETURN(deviceName != nullptr,); | ||||
const bool isRack(kEngine->getOptions().processMode == ENGINE_PROCESS_MODE_CONTINUOUS_RACK); | |||||
const bool isRack = kEngine->getOptions().processMode == ENGINE_PROCESS_MODE_CONTINUOUS_RACK; | |||||
// Main | // Main | ||||
{ | { | ||||
@@ -591,6 +608,39 @@ const char* const* ExternalGraph::getConnections() const noexcept | |||||
return retCon; | return retCon; | ||||
} | } | ||||
bool ExternalGraph::getGroupFromName(const char* groupName, uint& groupId) const noexcept | |||||
{ | |||||
CARLA_SAFE_ASSERT_RETURN(groupName != nullptr && groupName[0] != '\0', false); | |||||
if (std::strcmp(groupName, "Carla") == 0) | |||||
{ | |||||
groupId = kExternalGraphGroupCarla; | |||||
return true; | |||||
} | |||||
if (std::strcmp(groupName, "AudioIn") == 0) | |||||
{ | |||||
groupId = kExternalGraphGroupAudioIn; | |||||
return true; | |||||
} | |||||
if (std::strcmp(groupName, "AudioOut") == 0) | |||||
{ | |||||
groupId = kExternalGraphGroupAudioOut; | |||||
return true; | |||||
} | |||||
if (std::strcmp(groupName, "MidiIn") == 0) | |||||
{ | |||||
groupId = kExternalGraphGroupMidiIn; | |||||
return true; | |||||
} | |||||
if (std::strcmp(groupName, "MidiOut") == 0) | |||||
{ | |||||
groupId = kExternalGraphGroupMidiOut; | |||||
return true; | |||||
} | |||||
return false; | |||||
} | |||||
bool ExternalGraph::getGroupAndPortIdFromFullName(const char* const fullPortName, uint& groupId, uint& portId) const noexcept | bool ExternalGraph::getGroupAndPortIdFromFullName(const char* const fullPortName, uint& groupId, uint& portId) const noexcept | ||||
{ | { | ||||
CARLA_SAFE_ASSERT_RETURN(fullPortName != nullptr && fullPortName[0] != '\0', false); | CARLA_SAFE_ASSERT_RETURN(fullPortName != nullptr && fullPortName[0] != '\0', false); | ||||
@@ -1230,16 +1280,19 @@ const String getProcessorFullPortName(AudioProcessor* const proc, const uint32_t | |||||
static inline | static inline | ||||
void addNodeToPatchbay(const bool sendHost, const bool sendOSC, CarlaEngine* const engine, | void addNodeToPatchbay(const bool sendHost, const bool sendOSC, CarlaEngine* const engine, | ||||
const uint32_t groupId, const int clientId, const AudioProcessor* const proc) | |||||
AudioProcessorGraph::Node* const node, const int pluginId, const AudioProcessor* const proc) | |||||
{ | { | ||||
CARLA_SAFE_ASSERT_RETURN(engine != nullptr,); | CARLA_SAFE_ASSERT_RETURN(engine != nullptr,); | ||||
CARLA_SAFE_ASSERT_RETURN(node != nullptr,); | |||||
CARLA_SAFE_ASSERT_RETURN(proc != nullptr,); | CARLA_SAFE_ASSERT_RETURN(proc != nullptr,); | ||||
const uint groupId = node->nodeId; | |||||
engine->callback(sendHost, sendOSC, | engine->callback(sendHost, sendOSC, | ||||
ENGINE_CALLBACK_PATCHBAY_CLIENT_ADDED, | ENGINE_CALLBACK_PATCHBAY_CLIENT_ADDED, | ||||
groupId, | groupId, | ||||
clientId >= 0 ? PATCHBAY_ICON_PLUGIN : PATCHBAY_ICON_HARDWARE, | |||||
clientId, | |||||
pluginId >= 0 ? PATCHBAY_ICON_PLUGIN : PATCHBAY_ICON_HARDWARE, | |||||
pluginId, | |||||
0, 0.0f, | 0, 0.0f, | ||||
proc->getName().toRawUTF8()); | proc->getName().toRawUTF8()); | ||||
@@ -1308,6 +1361,18 @@ void addNodeToPatchbay(const bool sendHost, const bool sendOSC, CarlaEngine* con | |||||
0, 0.0f, | 0, 0.0f, | ||||
proc->getOutputChannelName(AudioProcessor::ChannelTypeMIDI, i).toRawUTF8()); | proc->getOutputChannelName(AudioProcessor::ChannelTypeMIDI, i).toRawUTF8()); | ||||
} | } | ||||
if (node->properties.contains("x1")) | |||||
{ | |||||
engine->callback(sendHost, sendOSC, | |||||
ENGINE_CALLBACK_PATCHBAY_CLIENT_POSITION_CHANGED, | |||||
groupId, | |||||
node->properties.getWithDefault("x1", 0), | |||||
node->properties.getWithDefault("y1", 0), | |||||
node->properties.getWithDefault("x2", 0), | |||||
static_cast<float>(node->properties.getWithDefault("y2", 0)), | |||||
nullptr); | |||||
} | |||||
} | } | ||||
static inline | static inline | ||||
@@ -1813,7 +1878,7 @@ void PatchbayGraph::addPlugin(CarlaPlugin* const plugin) | |||||
node->properties.set("isPlugin", true); | node->properties.set("isPlugin", true); | ||||
node->properties.set("pluginId", static_cast<int>(plugin->getId())); | node->properties.set("pluginId", static_cast<int>(plugin->getId())); | ||||
addNodeToPatchbay(sendHost, sendOSC, kEngine, node->nodeId, static_cast<int>(plugin->getId()), instance); | |||||
addNodeToPatchbay(sendHost, sendOSC, kEngine, node, static_cast<int>(plugin->getId()), instance); | |||||
} | } | ||||
void PatchbayGraph::replacePlugin(CarlaPlugin* const oldPlugin, CarlaPlugin* const newPlugin) | void PatchbayGraph::replacePlugin(CarlaPlugin* const oldPlugin, CarlaPlugin* const newPlugin) | ||||
@@ -1845,7 +1910,7 @@ void PatchbayGraph::replacePlugin(CarlaPlugin* const oldPlugin, CarlaPlugin* con | |||||
node->properties.set("isPlugin", true); | node->properties.set("isPlugin", true); | ||||
node->properties.set("pluginId", static_cast<int>(newPlugin->getId())); | node->properties.set("pluginId", static_cast<int>(newPlugin->getId())); | ||||
addNodeToPatchbay(sendHost, sendOSC, kEngine, node->nodeId, static_cast<int>(newPlugin->getId()), instance); | |||||
addNodeToPatchbay(sendHost, sendOSC, kEngine, node, static_cast<int>(newPlugin->getId()), instance); | |||||
} | } | ||||
void PatchbayGraph::renamePlugin(CarlaPlugin* const plugin, const char* const newName) | void PatchbayGraph::renamePlugin(CarlaPlugin* const plugin, const char* const newName) | ||||
@@ -2093,6 +2158,26 @@ void PatchbayGraph::disconnectInternalGroup(const uint groupId) noexcept | |||||
} | } | ||||
} | } | ||||
void PatchbayGraph::setGroupPos(const bool sendHost, const bool sendOSC, const bool external, | |||||
uint groupId, int x1, int y1, int x2, int y2) | |||||
{ | |||||
if (external) | |||||
return extGraph.setGroupPos(sendHost, sendOSC, groupId, x1, y1, x2, y2); | |||||
AudioProcessorGraph::Node* const node(graph.getNodeForId(groupId)); | |||||
CARLA_SAFE_ASSERT_RETURN(node != nullptr,); | |||||
node->properties.set("x1", x1); | |||||
node->properties.set("y1", y1); | |||||
node->properties.set("x2", x2); | |||||
node->properties.set("y2", y2); | |||||
kEngine->callback(sendHost, sendOSC, | |||||
ENGINE_CALLBACK_PATCHBAY_CLIENT_POSITION_CHANGED, | |||||
groupId, x1, y1, x2, static_cast<float>(y2), | |||||
nullptr); | |||||
} | |||||
void PatchbayGraph::refresh(const bool sendHost, const bool sendOSC, const bool external, const char* const deviceName) | void PatchbayGraph::refresh(const bool sendHost, const bool sendOSC, const bool external, const char* const deviceName) | ||||
{ | { | ||||
if (external) | if (external) | ||||
@@ -2111,13 +2196,13 @@ void PatchbayGraph::refresh(const bool sendHost, const bool sendOSC, const bool | |||||
AudioProcessor* const proc(node->getProcessor()); | AudioProcessor* const proc(node->getProcessor()); | ||||
CARLA_SAFE_ASSERT_CONTINUE(proc != nullptr); | CARLA_SAFE_ASSERT_CONTINUE(proc != nullptr); | ||||
int clientId = -1; | |||||
int pluginId = -1; | |||||
// plugin node | // plugin node | ||||
if (node->properties.getWithDefault("isPlugin", false) == water::var(true)) | if (node->properties.getWithDefault("isPlugin", false) == water::var(true)) | ||||
clientId = node->properties.getWithDefault("pluginId", -1); | |||||
pluginId = node->properties.getWithDefault("pluginId", -1); | |||||
addNodeToPatchbay(sendHost, sendOSC, kEngine, node->nodeId, clientId, proc); | |||||
addNodeToPatchbay(sendHost, sendOSC, kEngine, node, pluginId, proc); | |||||
} | } | ||||
char strBuf[STR_MAX+1]; | char strBuf[STR_MAX+1]; | ||||
@@ -2212,6 +2297,115 @@ const char* const* PatchbayGraph::getConnections(const bool external) const | |||||
return retCon; | return retCon; | ||||
} | } | ||||
const CarlaEngine::PatchbayPosition* PatchbayGraph::getPositions(bool external, uint& count) const | |||||
{ | |||||
CarlaEngine::PatchbayPosition* ret; | |||||
if (external) | |||||
{ | |||||
try { | |||||
ret = new CarlaEngine::PatchbayPosition[kExternalGraphGroupMax]; | |||||
} CARLA_SAFE_EXCEPTION_RETURN("new CarlaEngine::PatchbayPosition", nullptr); | |||||
count = 0; | |||||
for (uint i=kExternalGraphGroupCarla; i<kExternalGraphGroupMax; ++i) | |||||
{ | |||||
const PatchbayPosition& eppos(extGraph.positions[i]); | |||||
if (! eppos.active) | |||||
continue; | |||||
CarlaEngine::PatchbayPosition& ppos(ret[count++]); | |||||
switch (i) | |||||
{ | |||||
case kExternalGraphGroupCarla: | |||||
ppos.name = kEngine->getName(); | |||||
break; | |||||
case kExternalGraphGroupAudioIn: | |||||
ppos.name = "Capture"; | |||||
break; | |||||
case kExternalGraphGroupAudioOut: | |||||
ppos.name = "Playback"; | |||||
break; | |||||
case kExternalGraphGroupMidiIn: | |||||
ppos.name = "Readable MIDI ports"; | |||||
break; | |||||
case kExternalGraphGroupMidiOut: | |||||
ppos.name = "Writable MIDI ports"; | |||||
break; | |||||
} | |||||
ppos.dealloc = false; | |||||
ppos.x1 = eppos.x1; | |||||
ppos.y1 = eppos.y1; | |||||
ppos.x2 = eppos.x2; | |||||
ppos.y2 = eppos.y2; | |||||
} | |||||
return ret; | |||||
} | |||||
else | |||||
{ | |||||
const int numNodes = graph.getNumNodes(); | |||||
CARLA_SAFE_ASSERT_RETURN(numNodes > 0, nullptr); | |||||
try { | |||||
ret = new CarlaEngine::PatchbayPosition[numNodes]; | |||||
} CARLA_SAFE_EXCEPTION_RETURN("new CarlaEngine::PatchbayPosition", nullptr); | |||||
count = 0; | |||||
for (int i=numNodes; --i >= 0;) | |||||
{ | |||||
AudioProcessorGraph::Node* const node(graph.getNode(i)); | |||||
CARLA_SAFE_ASSERT_CONTINUE(node != nullptr); | |||||
if (! node->properties.contains("x1")) | |||||
continue; | |||||
AudioProcessor* const proc(node->getProcessor()); | |||||
CARLA_SAFE_ASSERT_CONTINUE(proc != nullptr); | |||||
CarlaEngine::PatchbayPosition& ppos(ret[count++]); | |||||
ppos.name = carla_strdup(proc->getName().toRawUTF8()); | |||||
ppos.dealloc = true; | |||||
ppos.x1 = node->properties.getWithDefault("x1", 0); | |||||
ppos.y1 = node->properties.getWithDefault("y1", 0); | |||||
ppos.x2 = node->properties.getWithDefault("x2", 0); | |||||
ppos.y2 = node->properties.getWithDefault("y2", 0); | |||||
} | |||||
return ret; | |||||
} | |||||
} | |||||
bool PatchbayGraph::getGroupFromName(bool external, const char* groupName, uint& groupId) const | |||||
{ | |||||
if (external) | |||||
return extGraph.getGroupFromName(groupName, groupId); | |||||
for (int i=0, count=graph.getNumNodes(); i<count; ++i) | |||||
{ | |||||
AudioProcessorGraph::Node* const node(graph.getNode(i)); | |||||
CARLA_SAFE_ASSERT_CONTINUE(node != nullptr); | |||||
AudioProcessor* const proc(node->getProcessor()); | |||||
CARLA_SAFE_ASSERT_CONTINUE(proc != nullptr); | |||||
if (proc->getName() != groupName) | |||||
continue; | |||||
groupId = node->nodeId; | |||||
return true; | |||||
} | |||||
return false; | |||||
} | |||||
bool PatchbayGraph::getGroupAndPortIdFromFullName(const bool external, const char* const fullPortName, uint& groupId, uint& portId) const | bool PatchbayGraph::getGroupAndPortIdFromFullName(const bool external, const char* const fullPortName, uint& groupId, uint& portId) const | ||||
{ | { | ||||
if (external) | if (external) | ||||
@@ -2607,8 +2801,6 @@ bool CarlaEngine::patchbayConnect(const bool external, | |||||
return graph->connect(external, groupA, portA, groupB, portB); | return graph->connect(external, groupA, portA, groupB, portB); | ||||
} | } | ||||
return false; | |||||
} | } | ||||
bool CarlaEngine::patchbayDisconnect(const bool external, const uint connectionId) | bool CarlaEngine::patchbayDisconnect(const bool external, const uint connectionId) | ||||
@@ -2631,8 +2823,28 @@ bool CarlaEngine::patchbayDisconnect(const bool external, const uint connectionI | |||||
return graph->disconnect(external, connectionId); | return graph->disconnect(external, connectionId); | ||||
} | } | ||||
} | |||||
return false; | |||||
bool CarlaEngine::patchbaySetGroupPos(const bool sendHost, const bool sendOSC, const bool external, | |||||
const uint groupId, const int x1, const int y1, const int x2, const int y2) | |||||
{ | |||||
CARLA_SAFE_ASSERT_RETURN(pData->options.processMode == ENGINE_PROCESS_MODE_CONTINUOUS_RACK || pData->options.processMode == ENGINE_PROCESS_MODE_PATCHBAY, false); | |||||
CARLA_SAFE_ASSERT_RETURN(pData->graph.isReady(), false); | |||||
carla_debug("CarlaEngine::patchbaySetGroupPos(%u, %i, %i, %i, %i)", groupId, x1, y1, x2, y2); | |||||
if (pData->options.processMode == ENGINE_PROCESS_MODE_CONTINUOUS_RACK) | |||||
{ | |||||
// we don't bother to save position in this case, there is only midi in/out | |||||
return true; | |||||
} | |||||
else | |||||
{ | |||||
PatchbayGraph* const graph = pData->graph.getPatchbayGraph(); | |||||
CARLA_SAFE_ASSERT_RETURN(graph != nullptr, false); | |||||
graph->setGroupPos(sendHost, sendOSC, external, groupId, x1, y1, x2, y2); | |||||
return true; | |||||
} | |||||
} | } | ||||
bool CarlaEngine::patchbayRefresh(const bool sendHost, const bool sendOSC, const bool external) | bool CarlaEngine::patchbayRefresh(const bool sendHost, const bool sendOSC, const bool external) | ||||
@@ -2687,6 +2899,22 @@ const char* const* CarlaEngine::getPatchbayConnections(const bool external) cons | |||||
return nullptr; | return nullptr; | ||||
} | } | ||||
const CarlaEngine::PatchbayPosition* CarlaEngine::getPatchbayPositions(bool external, uint& count) const | |||||
{ | |||||
CARLA_SAFE_ASSERT_RETURN(pData->graph.isReady(), nullptr); | |||||
carla_debug("CarlaEngine::getPatchbayPositions(%s)", bool2str(external)); | |||||
if (pData->options.processMode == ENGINE_PROCESS_MODE_PATCHBAY) | |||||
{ | |||||
PatchbayGraph* const graph = pData->graph.getPatchbayGraph(); | |||||
CARLA_SAFE_ASSERT_RETURN(graph != nullptr, nullptr); | |||||
return graph->getPositions(external, count); | |||||
} | |||||
return nullptr; | |||||
} | |||||
void CarlaEngine::restorePatchbayConnection(const bool external, | void CarlaEngine::restorePatchbayConnection(const bool external, | ||||
const char* const sourcePort, | const char* const sourcePort, | ||||
const char* const targetPort) | const char* const targetPort) | ||||
@@ -2726,6 +2954,23 @@ void CarlaEngine::restorePatchbayConnection(const bool external, | |||||
} | } | ||||
} | } | ||||
void CarlaEngine::restorePatchbayGroupPosition(const bool external, const PatchbayPosition& ppos) | |||||
{ | |||||
CARLA_SAFE_ASSERT_RETURN(pData->graph.isReady(),); | |||||
CARLA_SAFE_ASSERT_RETURN(ppos.name != nullptr && ppos.name[0] != '\0',); | |||||
if (pData->options.processMode == ENGINE_PROCESS_MODE_PATCHBAY) | |||||
{ | |||||
PatchbayGraph* const graph = pData->graph.getPatchbayGraph(); | |||||
CARLA_SAFE_ASSERT_RETURN(graph != nullptr,); | |||||
uint groupId; | |||||
CARLA_SAFE_ASSERT_RETURN(graph->getGroupFromName(external, ppos.name, groupId),); | |||||
graph->setGroupPos(true, true, external, groupId, ppos.x1, ppos.y1, ppos.x2, ppos.y2); | |||||
} | |||||
} | |||||
// ----------------------------------------------------------------------- | // ----------------------------------------------------------------------- | ||||
bool CarlaEngine::connectExternalGraphPort(const uint connectionType, const uint portId, const char* const portName) | bool CarlaEngine::connectExternalGraphPort(const uint connectionType, const uint portId, const char* const portName) | ||||
@@ -33,6 +33,13 @@ using water::MidiBuffer; | |||||
CARLA_BACKEND_START_NAMESPACE | CARLA_BACKEND_START_NAMESPACE | ||||
// ----------------------------------------------------------------------- | |||||
struct PatchbayPosition { | |||||
bool active; | |||||
int x1, y1, x2, y2; | |||||
}; | |||||
// ----------------------------------------------------------------------- | // ----------------------------------------------------------------------- | ||||
// External Graph stuff | // External Graph stuff | ||||
@@ -80,6 +87,7 @@ struct ExternalGraphPorts { | |||||
struct ExternalGraph { | struct ExternalGraph { | ||||
PatchbayConnectionList connections; | PatchbayConnectionList connections; | ||||
ExternalGraphPorts audioPorts, midiPorts; | ExternalGraphPorts audioPorts, midiPorts; | ||||
PatchbayPosition positions[kExternalGraphGroupMax]; | |||||
mutable CharStringListPtr retCon; | mutable CharStringListPtr retCon; | ||||
ExternalGraph(CarlaEngine* engine) noexcept; | ExternalGraph(CarlaEngine* engine) noexcept; | ||||
@@ -89,10 +97,13 @@ struct ExternalGraph { | |||||
uint groupA, uint portA, uint groupB, uint portB) noexcept; | uint groupA, uint portA, uint groupB, uint portB) noexcept; | ||||
bool disconnect(bool sendHost, bool sendOSC, | bool disconnect(bool sendHost, bool sendOSC, | ||||
uint connectionId) noexcept; | uint connectionId) noexcept; | ||||
void setGroupPos(bool sendHost, bool sendOSC, | |||||
uint groupId, int x1, int y1, int x2, int y2); | |||||
void refresh(bool sendHost, bool sendOSC, | void refresh(bool sendHost, bool sendOSC, | ||||
const char* deviceName); | const char* deviceName); | ||||
const char* const* getConnections() const noexcept; | const char* const* getConnections() const noexcept; | ||||
bool getGroupFromName(const char* groupName, uint& groupId) const noexcept; | |||||
bool getGroupAndPortIdFromFullName(const char* fullPortName, uint& groupId, uint& portId) const noexcept; | bool getGroupAndPortIdFromFullName(const char* fullPortName, uint& groupId, uint& portId) const noexcept; | ||||
CarlaEngine* const kEngine; | CarlaEngine* const kEngine; | ||||
@@ -190,9 +201,12 @@ public: | |||||
bool connect(bool external, uint groupA, uint portA, uint groupB, uint portB); | bool connect(bool external, uint groupA, uint portA, uint groupB, uint portB); | ||||
bool disconnect(bool external, uint connectionId); | bool disconnect(bool external, uint connectionId); | ||||
void disconnectInternalGroup(uint groupId) noexcept; | void disconnectInternalGroup(uint groupId) noexcept; | ||||
void setGroupPos(bool sendHost, bool sendOsc, bool external, uint groupId, int x1, int y1, int x2, int y2); | |||||
void refresh(bool sendHost, bool sendOsc, bool external, const char* deviceName); | void refresh(bool sendHost, bool sendOsc, bool external, const char* deviceName); | ||||
const char* const* getConnections(bool external) const; | const char* const* getConnections(bool external) const; | ||||
const CarlaEngine::PatchbayPosition* getPositions(bool external, uint& count) const; | |||||
bool getGroupFromName(bool external, const char* groupName, uint& groupId) const; | |||||
bool getGroupAndPortIdFromFullName(bool external, const char* fullPortName, uint& groupId, uint& portId) const; | bool getGroupAndPortIdFromFullName(bool external, const char* fullPortName, uint& groupId, uint& portId) const; | ||||
void process(CarlaEngine::ProtectedData* data, | void process(CarlaEngine::ProtectedData* data, | ||||
@@ -1816,6 +1816,22 @@ bool CarlaEngineNativeUI::msgReceived(const char* const msg) noexcept | |||||
ok = fEngine->patchbayDisconnect(external, connectionId); | ok = fEngine->patchbayDisconnect(external, connectionId); | ||||
} CARLA_SAFE_EXCEPTION("patchbayDisconnect"); | } CARLA_SAFE_EXCEPTION("patchbayDisconnect"); | ||||
} | } | ||||
else if (std::strcmp(msg, "patchbay_set_group_pos") == 0) | |||||
{ | |||||
bool external; | |||||
uint32_t groupId; | |||||
int x1, y1, x2, y2; | |||||
CARLA_SAFE_ASSERT_RETURN(readNextLineAsBool(external), true); | |||||
CARLA_SAFE_ASSERT_RETURN(readNextLineAsUInt(groupId), true); | |||||
CARLA_SAFE_ASSERT_RETURN(readNextLineAsInt(x1), true); | |||||
CARLA_SAFE_ASSERT_RETURN(readNextLineAsInt(y1), true); | |||||
CARLA_SAFE_ASSERT_RETURN(readNextLineAsInt(x2), true); | |||||
CARLA_SAFE_ASSERT_RETURN(readNextLineAsInt(y2), true); | |||||
try { | |||||
ok = fEngine->patchbaySetGroupPos(true, false, external, groupId, x1, y1, x2, y2); | |||||
} CARLA_SAFE_EXCEPTION("patchbaySetGroupPos"); | |||||
} | |||||
else if (std::strcmp(msg, "patchbay_refresh") == 0) | else if (std::strcmp(msg, "patchbay_refresh") == 0) | ||||
{ | { | ||||
bool external; | bool external; | ||||
@@ -818,6 +818,14 @@ ENGINE_CALLBACK_PATCHBAY_PORT_GROUP_CHANGED = 45 | |||||
# @a valueStr New mapped range as "%f:%f" syntax | # @a valueStr New mapped range as "%f:%f" syntax | ||||
ENGINE_CALLBACK_PARAMETER_MAPPED_RANGE_CHANGED = 46 | ENGINE_CALLBACK_PARAMETER_MAPPED_RANGE_CHANGED = 46 | ||||
# A patchbay client position has changed. | |||||
# @a pluginId Client Id | |||||
# @a value1 X position 1 | |||||
# @a value2 Y position 1 | |||||
# @a value3 X position 2 | |||||
# @a valuef Y position 2 | |||||
ENGINE_CALLBACK_PATCHBAY_CLIENT_POSITION_CHANGED = 47 | |||||
# ------------------------------------------------------------------------------------------------------------ | # ------------------------------------------------------------------------------------------------------------ | ||||
# NSM Callback Opcode | # NSM Callback Opcode | ||||
# NSM callback opcodes. | # NSM callback opcodes. | ||||
@@ -1678,6 +1686,14 @@ class CarlaHostMeta(object): | |||||
def patchbay_disconnect(self, external, connectionId): | def patchbay_disconnect(self, external, connectionId): | ||||
raise NotImplementedError | raise NotImplementedError | ||||
# Set the position of a group. | |||||
# This is purely cached and saved in the project file, Carla backend does nothing with the value. | |||||
# When loading a project, callbacks are used to inform of the previously saved positions. | |||||
# @see ENGINE_CALLBACK_PATCHBAY_CLIENT_POSITION_CHANGED | |||||
@abstractmethod | |||||
def patchbay_set_group_pos(self, external, groupId, x1, y1, x2, y2): | |||||
raise NotImplementedError | |||||
# Force the engine to resend all patchbay clients, ports and connections again. | # Force the engine to resend all patchbay clients, ports and connections again. | ||||
# @param external Wherever to show external/hardware ports instead of internal ones. | # @param external Wherever to show external/hardware ports instead of internal ones. | ||||
# Only valid in patchbay engine mode, other modes will ignore this. | # Only valid in patchbay engine mode, other modes will ignore this. | ||||
@@ -2306,6 +2322,9 @@ class CarlaHostNull(CarlaHostMeta): | |||||
def patchbay_disconnect(self, external, connectionId): | def patchbay_disconnect(self, external, connectionId): | ||||
return False | return False | ||||
def patchbay_set_group_pos(self, external, groupId, x1, y1, x2, y2): | |||||
return False | |||||
def patchbay_refresh(self, external): | def patchbay_refresh(self, external): | ||||
return False | return False | ||||
@@ -2624,6 +2643,9 @@ class CarlaHostDLL(CarlaHostMeta): | |||||
self.lib.carla_patchbay_disconnect.argtypes = (c_void_p, c_bool, c_uint) | self.lib.carla_patchbay_disconnect.argtypes = (c_void_p, c_bool, c_uint) | ||||
self.lib.carla_patchbay_disconnect.restype = c_bool | self.lib.carla_patchbay_disconnect.restype = c_bool | ||||
self.lib.carla_patchbay_set_group_pos.argtypes = (c_void_p, c_bool, c_uint, c_int, c_int, c_int, c_int) | |||||
self.lib.carla_patchbay_set_group_pos.restype = c_bool | |||||
self.lib.carla_patchbay_refresh.argtypes = (c_void_p, c_bool) | self.lib.carla_patchbay_refresh.argtypes = (c_void_p, c_bool) | ||||
self.lib.carla_patchbay_refresh.restype = c_bool | self.lib.carla_patchbay_refresh.restype = c_bool | ||||
@@ -2933,6 +2955,9 @@ class CarlaHostDLL(CarlaHostMeta): | |||||
def patchbay_disconnect(self, external, connectionId): | def patchbay_disconnect(self, external, connectionId): | ||||
return bool(self.lib.carla_patchbay_disconnect(self.handle, external, connectionId)) | return bool(self.lib.carla_patchbay_disconnect(self.handle, external, connectionId)) | ||||
def patchbay_set_group_pos(self, external, groupId, x1, y1, x2, y2): | |||||
return bool(self.lib.carla_patchbay_set_group_pos(self.handle, external, groupId, x1, y1, x2, y2)) | |||||
def patchbay_refresh(self, external): | def patchbay_refresh(self, external): | ||||
return bool(self.lib.carla_patchbay_refresh(self.handle, external)) | return bool(self.lib.carla_patchbay_refresh(self.handle, external)) | ||||
@@ -3325,6 +3350,9 @@ class CarlaHostPlugin(CarlaHostMeta): | |||||
def patchbay_disconnect(self, external, connectionId): | def patchbay_disconnect(self, external, connectionId): | ||||
return self.sendMsgAndSetError(["patchbay_disconnect", external, connectionId]) | return self.sendMsgAndSetError(["patchbay_disconnect", external, connectionId]) | ||||
def patchbay_set_group_pos(self, external, groupId, x1, y1, x2, y2): | |||||
return self.sendMsgAndSetError(["patchbay_set_group_pos", external, groupId, x1, y1, x2, y2]) | |||||
def patchbay_refresh(self, external): | def patchbay_refresh(self, external): | ||||
return self.sendMsgAndSetError(["patchbay_refresh", external]) | return self.sendMsgAndSetError(["patchbay_refresh", external]) | ||||
@@ -56,6 +56,7 @@ class CarlaHostSignals(QObject): | |||||
PatchbayClientRemovedCallback = pyqtSignal(int) | PatchbayClientRemovedCallback = pyqtSignal(int) | ||||
PatchbayClientRenamedCallback = pyqtSignal(int, str) | PatchbayClientRenamedCallback = pyqtSignal(int, str) | ||||
PatchbayClientDataChangedCallback = pyqtSignal(int, int, int) | PatchbayClientDataChangedCallback = pyqtSignal(int, int, int) | ||||
PatchbayClientPositionChangedCallback = pyqtSignal(int, int, int, int, int) | |||||
PatchbayPortAddedCallback = pyqtSignal(int, int, int, int, str) | PatchbayPortAddedCallback = pyqtSignal(int, int, int, int, str) | ||||
PatchbayPortRemovedCallback = pyqtSignal(int, int) | PatchbayPortRemovedCallback = pyqtSignal(int, int) | ||||
PatchbayPortChangedCallback = pyqtSignal(int, int, int, int, str) | PatchbayPortChangedCallback = pyqtSignal(int, int, int, int, str) | ||||
@@ -505,7 +505,6 @@ class HostWindow(QMainWindow): | |||||
self.ui.graphicsView.verticalScrollBar().valueChanged.connect(self.slot_verticalScrollBarChanged) | self.ui.graphicsView.verticalScrollBar().valueChanged.connect(self.slot_verticalScrollBarChanged) | ||||
self.ui.miniCanvasPreview.miniCanvasMoved.connect(self.slot_miniCanvasMoved) | self.ui.miniCanvasPreview.miniCanvasMoved.connect(self.slot_miniCanvasMoved) | ||||
self.scene.scaleChanged.connect(self.slot_canvasScaleChanged) | self.scene.scaleChanged.connect(self.slot_canvasScaleChanged) | ||||
self.scene.sceneGroupMoved.connect(self.slot_canvasItemMoved) | |||||
self.scene.pluginSelected.connect(self.slot_canvasPluginSelected) | self.scene.pluginSelected.connect(self.slot_canvasPluginSelected) | ||||
self.scene.selectionChanged.connect(self.slot_canvasSelectionChanged) | self.scene.selectionChanged.connect(self.slot_canvasSelectionChanged) | ||||
@@ -533,6 +532,7 @@ class HostWindow(QMainWindow): | |||||
host.PatchbayClientRemovedCallback.connect(self.slot_handlePatchbayClientRemovedCallback) | host.PatchbayClientRemovedCallback.connect(self.slot_handlePatchbayClientRemovedCallback) | ||||
host.PatchbayClientRenamedCallback.connect(self.slot_handlePatchbayClientRenamedCallback) | host.PatchbayClientRenamedCallback.connect(self.slot_handlePatchbayClientRenamedCallback) | ||||
host.PatchbayClientDataChangedCallback.connect(self.slot_handlePatchbayClientDataChangedCallback) | host.PatchbayClientDataChangedCallback.connect(self.slot_handlePatchbayClientDataChangedCallback) | ||||
host.PatchbayClientPositionChangedCallback.connect(self.slot_handlePatchbayClientPositionChangedCallback) | |||||
host.PatchbayPortAddedCallback.connect(self.slot_handlePatchbayPortAddedCallback) | host.PatchbayPortAddedCallback.connect(self.slot_handlePatchbayPortAddedCallback) | ||||
host.PatchbayPortRemovedCallback.connect(self.slot_handlePatchbayPortRemovedCallback) | host.PatchbayPortRemovedCallback.connect(self.slot_handlePatchbayPortRemovedCallback) | ||||
host.PatchbayPortChangedCallback.connect(self.slot_handlePatchbayPortChangedCallback) | host.PatchbayPortChangedCallback.connect(self.slot_handlePatchbayPortChangedCallback) | ||||
@@ -713,9 +713,6 @@ class HostWindow(QMainWindow): | |||||
# -------------------------------------------------------------------------------------------------------- | # -------------------------------------------------------------------------------------------------------- | ||||
# Files | # Files | ||||
def makeExtraFilename(self): | |||||
return self.fProjectFilename.rsplit(".",1)[0]+".json" | |||||
def loadProjectNow(self): | def loadProjectNow(self): | ||||
if not self.fProjectFilename: | if not self.fProjectFilename: | ||||
return qCritical("ERROR: loading project without filename set") | return qCritical("ERROR: loading project without filename set") | ||||
@@ -748,14 +745,6 @@ class HostWindow(QMainWindow): | |||||
QMessageBox.Ok, QMessageBox.Ok) | QMessageBox.Ok, QMessageBox.Ok) | ||||
return | return | ||||
if not self.fWithCanvas: | |||||
return | |||||
with open(self.makeExtraFilename(), 'w') as fh: | |||||
json.dump({ | |||||
'canvas': patchcanvas.saveGroupPositions(), | |||||
}, fh) | |||||
def projectLoadingStarted(self): | def projectLoadingStarted(self): | ||||
self.ui.rack.setEnabled(False) | self.ui.rack.setEnabled(False) | ||||
self.ui.graphicsView.setEnabled(False) | self.ui.graphicsView.setEnabled(False) | ||||
@@ -769,7 +758,7 @@ class HostWindow(QMainWindow): | |||||
QTimer.singleShot(1000, self.slot_canvasRefresh) | QTimer.singleShot(1000, self.slot_canvasRefresh) | ||||
extrafile = self.makeExtraFilename() | |||||
extrafile = self.fProjectFilename.rsplit(".",1)[0]+".json" | |||||
if not os.path.exists(extrafile): | if not os.path.exists(extrafile): | ||||
return | return | ||||
@@ -1431,7 +1420,7 @@ class HostWindow(QMainWindow): | |||||
pFeatures.group_rename = False | pFeatures.group_rename = False | ||||
pFeatures.port_info = False | pFeatures.port_info = False | ||||
pFeatures.port_rename = False | pFeatures.port_rename = False | ||||
pFeatures.handle_group_pos = True | |||||
pFeatures.handle_group_pos = False | |||||
patchcanvas.setOptions(pOptions) | patchcanvas.setOptions(pOptions) | ||||
patchcanvas.setFeatures(pFeatures) | patchcanvas.setFeatures(pFeatures) | ||||
@@ -1567,10 +1556,6 @@ class HostWindow(QMainWindow): | |||||
# -------------------------------------------------------------------------------------------------------- | # -------------------------------------------------------------------------------------------------------- | ||||
# Canvas (canvas callbacks) | # Canvas (canvas callbacks) | ||||
@pyqtSlot(int, int, QPointF) | |||||
def slot_canvasItemMoved(self, group_id, split_mode, pos): | |||||
self.updateMiniCanvasLater() | |||||
@pyqtSlot() | @pyqtSlot() | ||||
def slot_canvasSelectionChanged(self): | def slot_canvasSelectionChanged(self): | ||||
self.updateMiniCanvasLater() | self.updateMiniCanvasLater() | ||||
@@ -1668,6 +1653,10 @@ class HostWindow(QMainWindow): | |||||
patchcanvas.setGroupAsPlugin(clientId, pluginId, hasCustomUI, hasInlineDisplay) | patchcanvas.setGroupAsPlugin(clientId, pluginId, hasCustomUI, hasInlineDisplay) | ||||
@pyqtSlot(int, int, int, int, int) | |||||
def slot_handlePatchbayClientPositionChangedCallback(self, clientId, x1, y1, x2, y2): | |||||
patchcanvas.setGroupPosFull(clientId, x1, y1, x2, y2) | |||||
@pyqtSlot(int, int, int, int, str) | @pyqtSlot(int, int, int, int, str) | ||||
def slot_handlePatchbayPortAddedCallback(self, clientId, portId, portFlags, portGroupId, portName): | def slot_handlePatchbayPortAddedCallback(self, clientId, portId, portFlags, portGroupId, portName): | ||||
if portFlags & PATCHBAY_PORT_IS_INPUT: | if portFlags & PATCHBAY_PORT_IS_INPUT: | ||||
@@ -2720,6 +2709,9 @@ class HostWindow(QMainWindow): | |||||
def canvasCallback(action, value1, value2, valueStr): | def canvasCallback(action, value1, value2, valueStr): | ||||
host = gCarla.gui.host | host = gCarla.gui.host | ||||
if gCarla.gui.fCustomStopAction == HostWindow.CUSTOM_ACTION_APP_CLOSE: | |||||
return | |||||
if action == patchcanvas.ACTION_GROUP_INFO: | if action == patchcanvas.ACTION_GROUP_INFO: | ||||
pass | pass | ||||
@@ -2736,6 +2728,14 @@ def canvasCallback(action, value1, value2, valueStr): | |||||
patchcanvas.joinGroup(groupId) | patchcanvas.joinGroup(groupId) | ||||
gCarla.gui.updateMiniCanvasLater() | gCarla.gui.updateMiniCanvasLater() | ||||
elif action == patchcanvas.ACTION_GROUP_POSITION: | |||||
if gCarla.gui.fIsProjectLoading or not host.is_engine_running(): | |||||
return | |||||
groupId = value1 | |||||
x1, y1, x2, y2 = tuple(int(i) for i in valueStr.split(":")) | |||||
host.patchbay_set_group_pos(gCarla.gui.fExternalPatchbay, groupId, x1, y1, x2, y2) | |||||
gCarla.gui.updateMiniCanvasLater() | |||||
elif action == patchcanvas.ACTION_PORT_INFO: | elif action == patchcanvas.ACTION_PORT_INFO: | ||||
pass | pass | ||||
@@ -2743,7 +2743,7 @@ def canvasCallback(action, value1, value2, valueStr): | |||||
pass | pass | ||||
elif action == patchcanvas.ACTION_PORTS_CONNECT: | elif action == patchcanvas.ACTION_PORTS_CONNECT: | ||||
gOut, pOut, gIn, pIn = [int(i) for i in valueStr.split(":")] | |||||
gOut, pOut, gIn, pIn = tuple(int(i) for i in valueStr.split(":")) | |||||
if not host.patchbay_connect(gCarla.gui.fExternalPatchbay, gOut, pOut, gIn, pIn): | if not host.patchbay_connect(gCarla.gui.fExternalPatchbay, gOut, pOut, gIn, pIn): | ||||
print("Connection failed:", host.get_last_error()) | print("Connection failed:", host.get_last_error()) | ||||
@@ -2875,6 +2875,8 @@ def engineCallback(host, action, pluginId, value1, value2, value3, valuef, value | |||||
host.PatchbayClientRenamedCallback.emit(pluginId, valueStr) | host.PatchbayClientRenamedCallback.emit(pluginId, valueStr) | ||||
elif action == ENGINE_CALLBACK_PATCHBAY_CLIENT_DATA_CHANGED: | elif action == ENGINE_CALLBACK_PATCHBAY_CLIENT_DATA_CHANGED: | ||||
host.PatchbayClientDataChangedCallback.emit(pluginId, value1, value2) | host.PatchbayClientDataChangedCallback.emit(pluginId, value1, value2) | ||||
elif action == ENGINE_CALLBACK_PATCHBAY_CLIENT_POSITION_CHANGED: | |||||
host.PatchbayClientPositionChangedCallback.emit(pluginId, value1, value2, value3, int(round(valuef))) | |||||
elif action == ENGINE_CALLBACK_PATCHBAY_PORT_ADDED: | elif action == ENGINE_CALLBACK_PATCHBAY_PORT_ADDED: | ||||
host.PatchbayPortAddedCallback.emit(pluginId, value1, value2, value3, valueStr) | host.PatchbayPortAddedCallback.emit(pluginId, value1, value2, value3, valueStr) | ||||
elif action == ENGINE_CALLBACK_PATCHBAY_PORT_REMOVED: | elif action == ENGINE_CALLBACK_PATCHBAY_PORT_REMOVED: | ||||
@@ -49,18 +49,19 @@ ACTION_GROUP_INFO = 0 # group_id, N, N | |||||
ACTION_GROUP_RENAME = 1 # group_id, N, N | ACTION_GROUP_RENAME = 1 # group_id, N, N | ||||
ACTION_GROUP_SPLIT = 2 # group_id, N, N | ACTION_GROUP_SPLIT = 2 # group_id, N, N | ||||
ACTION_GROUP_JOIN = 3 # group_id, N, N | ACTION_GROUP_JOIN = 3 # group_id, N, N | ||||
ACTION_PORT_INFO = 4 # group_id, port_id, N | |||||
ACTION_PORT_RENAME = 5 # group_id, port_id, N | |||||
ACTION_PORTS_CONNECT = 6 # N, N, "outG:outP:inG:inP" | |||||
ACTION_PORTS_DISCONNECT = 7 # conn_id, N, N | |||||
ACTION_PLUGIN_CLONE = 8 # plugin_id, N, N | |||||
ACTION_PLUGIN_EDIT = 9 # plugin_id, N, N | |||||
ACTION_PLUGIN_RENAME = 10 # plugin_id, N, N | |||||
ACTION_PLUGIN_REPLACE = 11 # plugin_id, N, N | |||||
ACTION_PLUGIN_REMOVE = 12 # plugin_id, N, N | |||||
ACTION_PLUGIN_SHOW_UI = 13 # plugin_id, N, N | |||||
ACTION_BG_RIGHT_CLICK = 14 # N, N, N | |||||
ACTION_INLINE_DISPLAY = 15 # plugin_id, N, N | |||||
ACTION_GROUP_POSITION = 4 # group_id, N, N, "x1:y1:x2:y2" | |||||
ACTION_PORT_INFO = 5 # group_id, port_id, N | |||||
ACTION_PORT_RENAME = 6 # group_id, port_id, N | |||||
ACTION_PORTS_CONNECT = 7 # N, N, "outG:outP:inG:inP" | |||||
ACTION_PORTS_DISCONNECT = 8 # conn_id, N, N | |||||
ACTION_PLUGIN_CLONE = 9 # plugin_id, N, N | |||||
ACTION_PLUGIN_EDIT = 10 # plugin_id, N, N | |||||
ACTION_PLUGIN_RENAME = 11 # plugin_id, N, N | |||||
ACTION_PLUGIN_REPLACE = 12 # plugin_id, N, N | |||||
ACTION_PLUGIN_REMOVE = 13 # plugin_id, N, N | |||||
ACTION_PLUGIN_SHOW_UI = 14 # plugin_id, N, N | |||||
ACTION_BG_RIGHT_CLICK = 15 # N, N, N | |||||
ACTION_INLINE_DISPLAY = 16 # plugin_id, N, N | |||||
# Icon | # Icon | ||||
ICON_APPLICATION = 0 | ICON_APPLICATION = 0 | ||||
@@ -134,6 +135,7 @@ class Canvas(object): | |||||
self.connection_list = [] | self.connection_list = [] | ||||
self.animation_list = [] | self.animation_list = [] | ||||
self.group_plugin_map = {} | self.group_plugin_map = {} | ||||
self.old_group_pos = {} | |||||
self.callback = self.callback | self.callback = self.callback | ||||
self.debug = False | self.debug = False | ||||
@@ -22,9 +22,9 @@ | |||||
from sip import voidptr | from sip import voidptr | ||||
from struct import pack | from struct import pack | ||||
from PyQt5.QtCore import qCritical, Qt, QPointF, QRectF, QTimer | |||||
from PyQt5.QtCore import pyqtSignal, pyqtSlot, qCritical, Qt, QPointF, QRectF, QTimer | |||||
from PyQt5.QtGui import QCursor, QFont, QFontMetrics, QImage, QLinearGradient, QPainter, QPen | from PyQt5.QtGui import QCursor, QFont, QFontMetrics, QImage, QLinearGradient, QPainter, QPen | ||||
from PyQt5.QtWidgets import QGraphicsItem, QMenu | |||||
from PyQt5.QtWidgets import QGraphicsItem, QGraphicsObject, QMenu | |||||
# ------------------------------------------------------------------------------------------------------------ | # ------------------------------------------------------------------------------------------------------------ | ||||
# Imports (Custom) | # Imports (Custom) | ||||
@@ -75,13 +75,17 @@ class cb_line_t(object): | |||||
# ------------------------------------------------------------------------------------------------------------ | # ------------------------------------------------------------------------------------------------------------ | ||||
class CanvasBox(QGraphicsItem): | |||||
class CanvasBox(QGraphicsObject): | |||||
# signals | |||||
positionChanged = pyqtSignal(int, bool, int, int) | |||||
# enums | |||||
INLINE_DISPLAY_DISABLED = 0 | INLINE_DISPLAY_DISABLED = 0 | ||||
INLINE_DISPLAY_ENABLED = 1 | INLINE_DISPLAY_ENABLED = 1 | ||||
INLINE_DISPLAY_CACHED = 2 | INLINE_DISPLAY_CACHED = 2 | ||||
def __init__(self, group_id, group_name, icon, parent=None): | def __init__(self, group_id, group_name, icon, parent=None): | ||||
QGraphicsItem.__init__(self) | |||||
QGraphicsObject.__init__(self) | |||||
self.setParentItem(parent) | self.setParentItem(parent) | ||||
# Save Variables, useful for later | # Save Variables, useful for later | ||||
@@ -109,6 +113,7 @@ class CanvasBox(QGraphicsItem): | |||||
self.m_inline_data = None | self.m_inline_data = None | ||||
self.m_inline_image = None | self.m_inline_image = None | ||||
self.m_inline_scaling = 1.0 | self.m_inline_scaling = 1.0 | ||||
self.m_will_signal_pos_change = False | |||||
self.m_port_list_ids = [] | self.m_port_list_ids = [] | ||||
self.m_connection_lines = [] | self.m_connection_lines = [] | ||||
@@ -153,6 +158,10 @@ class CanvasBox(QGraphicsItem): | |||||
self.updatePositions() | self.updatePositions() | ||||
self.visibleChanged.connect(self.slot_signalPositionChangedLater) | |||||
self.xChanged.connect(self.slot_signalPositionChangedLater) | |||||
self.yChanged.connect(self.slot_signalPositionChangedLater) | |||||
canvas.scene.addItem(self) | canvas.scene.addItem(self) | ||||
QTimer.singleShot(0, self.fixPos) | QTimer.singleShot(0, self.fixPos) | ||||
@@ -270,21 +279,31 @@ class CanvasBox(QGraphicsItem): | |||||
return | return | ||||
qCritical("PatchCanvas::CanvasBox.removeLineFromGroup(%i) - unable to find line to remove" % connection_id) | qCritical("PatchCanvas::CanvasBox.removeLineFromGroup(%i) - unable to find line to remove" % connection_id) | ||||
def checkItemPos(self): | |||||
if not canvas.size_rect.isNull(): | |||||
pos = self.scenePos() | |||||
if not (canvas.size_rect.contains(pos) and | |||||
canvas.size_rect.contains(pos + QPointF(self.p_width, self.p_height))): | |||||
if pos.x() < canvas.size_rect.x(): | |||||
self.setPos(canvas.size_rect.x(), pos.y()) | |||||
elif pos.x() + self.p_width > canvas.size_rect.width(): | |||||
self.setPos(canvas.size_rect.width() - self.p_width, pos.y()) | |||||
pos = self.scenePos() | |||||
if pos.y() < canvas.size_rect.y(): | |||||
self.setPos(pos.x(), canvas.size_rect.y()) | |||||
elif pos.y() + self.p_height > canvas.size_rect.height(): | |||||
self.setPos(pos.x(), canvas.size_rect.height() - self.p_height) | |||||
def checkItemPos(self, blockSignals): | |||||
if canvas.size_rect.isNull(): | |||||
return | |||||
pos = self.scenePos() | |||||
if (canvas.size_rect.contains(pos) and | |||||
canvas.size_rect.contains(pos + QPointF(self.p_width, self.p_height))): | |||||
return | |||||
if blockSignals: | |||||
self.blockSignals(True) | |||||
if pos.x() < canvas.size_rect.x(): | |||||
self.setPos(canvas.size_rect.x(), pos.y()) | |||||
elif pos.x() + self.p_width > canvas.size_rect.width(): | |||||
self.setPos(canvas.size_rect.width() - self.p_width, pos.y()) | |||||
pos = self.scenePos() | |||||
if pos.y() < canvas.size_rect.y(): | |||||
self.setPos(pos.x(), canvas.size_rect.y()) | |||||
elif pos.y() + self.p_height > canvas.size_rect.height(): | |||||
self.setPos(pos.x(), canvas.size_rect.height() - self.p_height) | |||||
if blockSignals: | |||||
self.blockSignals(False) | |||||
def removeIconFromScene(self): | def removeIconFromScene(self): | ||||
if self.icon_svg is None: | if self.icon_svg is None: | ||||
@@ -389,6 +408,17 @@ class CanvasBox(QGraphicsItem): | |||||
connection.widget.setZValue(z_value) | connection.widget.setZValue(z_value) | ||||
def triggerSignalPositionChanged(self): | |||||
self.positionChanged.emit(self.m_group_id, self.m_splitted, self.x(), self.y()) | |||||
self.m_will_signal_pos_change = False | |||||
@pyqtSlot() | |||||
def slot_signalPositionChangedLater(self): | |||||
if self.m_will_signal_pos_change: | |||||
return | |||||
self.m_will_signal_pos_change = True | |||||
QTimer.singleShot(0, self.triggerSignalPositionChanged) | |||||
def type(self): | def type(self): | ||||
return CanvasBoxType | return CanvasBoxType | ||||
@@ -570,14 +600,14 @@ class CanvasBox(QGraphicsItem): | |||||
event.accept() | event.accept() | ||||
canvas.callback(ACTION_PLUGIN_REMOVE, self.m_plugin_id, 0, "") | canvas.callback(ACTION_PLUGIN_REMOVE, self.m_plugin_id, 0, "") | ||||
return | return | ||||
QGraphicsItem.keyPressEvent(self, event) | |||||
QGraphicsObject.keyPressEvent(self, event) | |||||
def hoverEnterEvent(self, event): | def hoverEnterEvent(self, event): | ||||
if options.auto_select_items: | if options.auto_select_items: | ||||
if len(canvas.scene.selectedItems()) > 0: | if len(canvas.scene.selectedItems()) > 0: | ||||
canvas.scene.clearSelection() | canvas.scene.clearSelection() | ||||
self.setSelected(True) | self.setSelected(True) | ||||
QGraphicsItem.hoverEnterEvent(self, event) | |||||
QGraphicsObject.hoverEnterEvent(self, event) | |||||
def mouseDoubleClickEvent(self, event): | def mouseDoubleClickEvent(self, event): | ||||
if self.m_plugin_id >= 0: | if self.m_plugin_id >= 0: | ||||
@@ -585,7 +615,7 @@ class CanvasBox(QGraphicsItem): | |||||
canvas.callback(ACTION_PLUGIN_SHOW_UI if self.m_plugin_ui else ACTION_PLUGIN_EDIT, self.m_plugin_id, 0, "") | canvas.callback(ACTION_PLUGIN_SHOW_UI if self.m_plugin_ui else ACTION_PLUGIN_EDIT, self.m_plugin_id, 0, "") | ||||
return | return | ||||
QGraphicsItem.mouseDoubleClickEvent(self, event) | |||||
QGraphicsObject.mouseDoubleClickEvent(self, event) | |||||
def mousePressEvent(self, event): | def mousePressEvent(self, event): | ||||
canvas.last_z_value += 1 | canvas.last_z_value += 1 | ||||
@@ -612,7 +642,7 @@ class CanvasBox(QGraphicsItem): | |||||
else: | else: | ||||
self.m_mouse_down = False | self.m_mouse_down = False | ||||
QGraphicsItem.mousePressEvent(self, event) | |||||
QGraphicsObject.mousePressEvent(self, event) | |||||
def mouseMoveEvent(self, event): | def mouseMoveEvent(self, event): | ||||
if self.m_mouse_down: | if self.m_mouse_down: | ||||
@@ -620,7 +650,7 @@ class CanvasBox(QGraphicsItem): | |||||
self.setCursor(QCursor(Qt.SizeAllCursor)) | self.setCursor(QCursor(Qt.SizeAllCursor)) | ||||
self.m_cursor_moving = True | self.m_cursor_moving = True | ||||
self.repaintLines() | self.repaintLines() | ||||
QGraphicsItem.mouseMoveEvent(self, event) | |||||
QGraphicsObject.mouseMoveEvent(self, event) | |||||
def mouseReleaseEvent(self, event): | def mouseReleaseEvent(self, event): | ||||
if self.m_cursor_moving: | if self.m_cursor_moving: | ||||
@@ -628,11 +658,13 @@ class CanvasBox(QGraphicsItem): | |||||
QTimer.singleShot(0, self.fixPos) | QTimer.singleShot(0, self.fixPos) | ||||
self.m_mouse_down = False | self.m_mouse_down = False | ||||
self.m_cursor_moving = False | self.m_cursor_moving = False | ||||
QGraphicsItem.mouseReleaseEvent(self, event) | |||||
QGraphicsObject.mouseReleaseEvent(self, event) | |||||
def fixPos(self): | def fixPos(self): | ||||
self.blockSignals(True) | |||||
self.setX(round(self.x())) | self.setX(round(self.x())) | ||||
self.setY(round(self.y())) | self.setY(round(self.y())) | ||||
self.blockSignals(False) | |||||
def boundingRect(self): | def boundingRect(self): | ||||
return QRectF(0, 0, self.p_width, self.p_height) | return QRectF(0, 0, self.p_width, self.p_height) | ||||
@@ -138,6 +138,36 @@ class CanvasObject(QObject): | |||||
CanvasCallback(ACTION_PORTS_DISCONNECT, connectionId, 0, "") | CanvasCallback(ACTION_PORTS_DISCONNECT, connectionId, 0, "") | ||||
@pyqtSlot(int, bool, int, int) | |||||
def boxPositionChanged(self, groupId, split, x, y): | |||||
x2 = y2 = 0 | |||||
if split: | |||||
for group in canvas.group_list: | |||||
if group.group_id == groupId: | |||||
if group.split: | |||||
pos = group.widgets[1].pos() | |||||
x2 = pos.x() | |||||
y2 = pos.y() | |||||
break | |||||
valueStr = "%i:%i:%i:%i" % (x, y, x2, y2) | |||||
CanvasCallback(ACTION_GROUP_POSITION, groupId, 0, valueStr) | |||||
@pyqtSlot(int, bool, int, int) | |||||
def sboxPositionChanged(self, groupId, split, x2, y2): | |||||
x = y = 0 | |||||
for group in canvas.group_list: | |||||
if group.group_id == groupId: | |||||
pos = group.widgets[0].pos() | |||||
x = pos.x() | |||||
y = pos.y() | |||||
break | |||||
valueStr = "%i:%i:%i:%i" % (x, y, x2, y2) | |||||
CanvasCallback(ACTION_GROUP_POSITION, groupId, 0, valueStr) | |||||
# ------------------------------------------------------------------------------------------------------------ | # ------------------------------------------------------------------------------------------------------------ | ||||
def getStoredCanvasPosition(key, fallback_pos): | def getStoredCanvasPosition(key, fallback_pos): | ||||
@@ -201,11 +231,17 @@ def clear(): | |||||
if canvas.debug: | if canvas.debug: | ||||
print("PatchCanvas::clear()") | print("PatchCanvas::clear()") | ||||
group_pos = {} | |||||
group_list_ids = [] | group_list_ids = [] | ||||
port_list_ids = [] | port_list_ids = [] | ||||
connection_list_ids = [] | connection_list_ids = [] | ||||
for group in canvas.group_list: | for group in canvas.group_list: | ||||
group_pos[group.group_name] = ( | |||||
group.split, | |||||
group.widgets[0].pos(), | |||||
group.widgets[1].pos() if group.split else None, | |||||
) | |||||
group_list_ids.append(group.group_id) | group_list_ids.append(group.group_id) | ||||
for port in canvas.port_list: | for port in canvas.port_list: | ||||
@@ -230,6 +266,7 @@ def clear(): | |||||
canvas.port_list = [] | canvas.port_list = [] | ||||
canvas.connection_list = [] | canvas.connection_list = [] | ||||
canvas.group_plugin_map = {} | canvas.group_plugin_map = {} | ||||
canvas.old_group_pos = group_pos | |||||
canvas.scene.clearSelection() | canvas.scene.clearSelection() | ||||
@@ -278,6 +315,8 @@ def addGroup(group_id, group_name, split=SPLIT_UNDEF, icon=ICON_APPLICATION): | |||||
group_id, group_name.encode(), split2str(split), icon2str(icon))) | group_id, group_name.encode(), split2str(split), icon2str(icon))) | ||||
return | return | ||||
old_matching_group = canvas.old_group_pos.pop(group_name, None) | |||||
if split == SPLIT_UNDEF: | if split == SPLIT_UNDEF: | ||||
isHardware = bool(icon == ICON_HARDWARE) | isHardware = bool(icon == ICON_HARDWARE) | ||||
@@ -285,8 +324,11 @@ def addGroup(group_id, group_name, split=SPLIT_UNDEF, icon=ICON_APPLICATION): | |||||
split = getStoredCanvasSplit(group_name, SPLIT_YES if isHardware else split) | split = getStoredCanvasSplit(group_name, SPLIT_YES if isHardware else split) | ||||
elif isHardware: | elif isHardware: | ||||
split = SPLIT_YES | split = SPLIT_YES | ||||
elif old_matching_group is not None and old_matching_group[0]: | |||||
split = SPLIT_YES | |||||
group_box = CanvasBox(group_id, group_name, icon) | group_box = CanvasBox(group_id, group_name, icon) | ||||
group_box.positionChanged.connect(canvas.qobject.boxPositionChanged) | |||||
group_dict = group_dict_t() | group_dict = group_dict_t() | ||||
group_dict.group_id = group_id | group_dict.group_id = group_id | ||||
@@ -301,20 +343,29 @@ def addGroup(group_id, group_name, split=SPLIT_UNDEF, icon=ICON_APPLICATION): | |||||
if split == SPLIT_YES: | if split == SPLIT_YES: | ||||
group_box.setSplit(True, PORT_MODE_OUTPUT) | group_box.setSplit(True, PORT_MODE_OUTPUT) | ||||
group_box.blockSignals(True) | |||||
if features.handle_group_pos: | if features.handle_group_pos: | ||||
group_box.setPos(getStoredCanvasPosition(group_name + "_OUTPUT", CanvasGetNewGroupPos(False))) | group_box.setPos(getStoredCanvasPosition(group_name + "_OUTPUT", CanvasGetNewGroupPos(False))) | ||||
elif old_matching_group is not None: | |||||
group_box.setPos(old_matching_group[1]) | |||||
else: | else: | ||||
group_box.setPos(CanvasGetNewGroupPos(False)) | group_box.setPos(CanvasGetNewGroupPos(False)) | ||||
group_box.blockSignals(False) | |||||
group_sbox = CanvasBox(group_id, group_name, icon) | group_sbox = CanvasBox(group_id, group_name, icon) | ||||
group_sbox.setSplit(True, PORT_MODE_INPUT) | group_sbox.setSplit(True, PORT_MODE_INPUT) | ||||
group_sbox.positionChanged.connect(canvas.qobject.sboxPositionChanged) | |||||
group_dict.widgets[1] = group_sbox | group_dict.widgets[1] = group_sbox | ||||
group_sbox.blockSignals(True) | |||||
if features.handle_group_pos: | if features.handle_group_pos: | ||||
group_sbox.setPos(getStoredCanvasPosition(group_name + "_INPUT", CanvasGetNewGroupPos(True))) | group_sbox.setPos(getStoredCanvasPosition(group_name + "_INPUT", CanvasGetNewGroupPos(True))) | ||||
elif old_matching_group is not None and old_matching_group[0]: | |||||
group_sbox.setPos(old_matching_group[2]) | |||||
else: | else: | ||||
group_sbox.setPos(CanvasGetNewGroupPos(True)) | group_sbox.setPos(CanvasGetNewGroupPos(True)) | ||||
group_sbox.blockSignals(False) | |||||
canvas.last_z_value += 1 | canvas.last_z_value += 1 | ||||
group_sbox.setZValue(canvas.last_z_value) | group_sbox.setZValue(canvas.last_z_value) | ||||
@@ -322,19 +373,21 @@ def addGroup(group_id, group_name, split=SPLIT_UNDEF, icon=ICON_APPLICATION): | |||||
if options.eyecandy == EYECANDY_FULL and not options.auto_hide_groups: | if options.eyecandy == EYECANDY_FULL and not options.auto_hide_groups: | ||||
CanvasItemFX(group_sbox, True, False) | CanvasItemFX(group_sbox, True, False) | ||||
group_sbox.checkItemPos() | |||||
group_sbox.checkItemPos(True) | |||||
else: | else: | ||||
group_box.setSplit(False) | group_box.setSplit(False) | ||||
if features.handle_group_pos: | if features.handle_group_pos: | ||||
group_box.setPos(getStoredCanvasPosition(group_name, CanvasGetNewGroupPos(False))) | group_box.setPos(getStoredCanvasPosition(group_name, CanvasGetNewGroupPos(False))) | ||||
elif old_matching_group is not None: | |||||
group_box.setPos(old_matching_group[1]) | |||||
else: | else: | ||||
# Special ladish fake-split groups | # Special ladish fake-split groups | ||||
horizontal = bool(icon == ICON_HARDWARE or icon == ICON_LADISH_ROOM) | horizontal = bool(icon == ICON_HARDWARE or icon == ICON_LADISH_ROOM) | ||||
group_box.setPos(CanvasGetNewGroupPos(horizontal)) | group_box.setPos(CanvasGetNewGroupPos(horizontal)) | ||||
group_box.checkItemPos() | |||||
group_box.checkItemPos(True) | |||||
canvas.last_z_value += 1 | canvas.last_z_value += 1 | ||||
group_box.setZValue(canvas.last_z_value) | group_box.setZValue(canvas.last_z_value) | ||||
@@ -630,10 +683,14 @@ def restoreGroupPositions(dataList): | |||||
if group is None: | if group is None: | ||||
continue | continue | ||||
group.widgets[0].blockSignals(True) | |||||
group.widgets[0].setPos(data['pos1x'], data['pos1y']) | group.widgets[0].setPos(data['pos1x'], data['pos1y']) | ||||
group.widgets[0].blockSignals(False) | |||||
if group.split and group.widgets[1]: | if group.split and group.widgets[1]: | ||||
group.widgets[1].blockSignals(True) | |||||
group.widgets[1].setPos(data['pos2x'], data['pos2y']) | group.widgets[1].setPos(data['pos2x'], data['pos2y']) | ||||
group.widgets[1].blockSignals(False) | |||||
def setGroupPos(group_id, group_pos_x, group_pos_y): | def setGroupPos(group_id, group_pos_x, group_pos_y): | ||||
setGroupPosFull(group_id, group_pos_x, group_pos_y, group_pos_x, group_pos_y) | setGroupPosFull(group_id, group_pos_x, group_pos_y, group_pos_x, group_pos_y) | ||||
@@ -645,10 +702,14 @@ def setGroupPosFull(group_id, group_pos_x_o, group_pos_y_o, group_pos_x_i, group | |||||
for group in canvas.group_list: | for group in canvas.group_list: | ||||
if group.group_id == group_id: | if group.group_id == group_id: | ||||
group.widgets[0].blockSignals(True) | |||||
group.widgets[0].setPos(group_pos_x_o, group_pos_y_o) | group.widgets[0].setPos(group_pos_x_o, group_pos_y_o) | ||||
group.widgets[0].blockSignals(False) | |||||
if group.split and group.widgets[1]: | if group.split and group.widgets[1]: | ||||
group.widgets[1].blockSignals(True) | |||||
group.widgets[1].setPos(group_pos_x_i, group_pos_y_i) | group.widgets[1].setPos(group_pos_x_i, group_pos_y_i) | ||||
group.widgets[1].blockSignals(False) | |||||
QTimer.singleShot(0, canvas.scene.update) | QTimer.singleShot(0, canvas.scene.update) | ||||
return | return | ||||
@@ -58,7 +58,6 @@ class RubberbandRect(QGraphicsRectItem): | |||||
class PatchScene(QGraphicsScene): | class PatchScene(QGraphicsScene): | ||||
scaleChanged = pyqtSignal(float) | scaleChanged = pyqtSignal(float) | ||||
sceneGroupMoved = pyqtSignal(int, int, QPointF) | |||||
pluginSelected = pyqtSignal(list) | pluginSelected = pyqtSignal(list) | ||||
def __init__(self, parent, view): | def __init__(self, parent, view): | ||||
@@ -367,8 +366,7 @@ class PatchScene(QGraphicsScene): | |||||
items_list = self.selectedItems() | items_list = self.selectedItems() | ||||
for item in items_list: | for item in items_list: | ||||
if item and item.isVisible() and item.type() == CanvasBoxType: | if item and item.isVisible() and item.type() == CanvasBoxType: | ||||
item.checkItemPos() | |||||
self.sceneGroupMoved.emit(item.getGroupId(), item.getSplittedMode(), item.scenePos()) | |||||
item.checkItemPos(False) | |||||
if len(items_list) > 1: | if len(items_list) > 1: | ||||
canvas.scene.update() | canvas.scene.update() | ||||
@@ -315,6 +315,8 @@ const char* EngineCallbackOpcode2Str(const EngineCallbackOpcode opcode) noexcept | |||||
return "ENGINE_CALLBACK_PATCHBAY_PORT_GROUP_CHANGED"; | return "ENGINE_CALLBACK_PATCHBAY_PORT_GROUP_CHANGED"; | ||||
case ENGINE_CALLBACK_PARAMETER_MAPPED_RANGE_CHANGED: | case ENGINE_CALLBACK_PARAMETER_MAPPED_RANGE_CHANGED: | ||||
return "ENGINE_CALLBACK_PARAMETER_MAPPED_RANGE_CHANGED"; | return "ENGINE_CALLBACK_PARAMETER_MAPPED_RANGE_CHANGED"; | ||||
case ENGINE_CALLBACK_PATCHBAY_CLIENT_POSITION_CHANGED: | |||||
return "ENGINE_CALLBACK_PATCHBAY_CLIENT_POSITION_CHANGED"; | |||||
} | } | ||||
carla_stderr("CarlaBackend::EngineCallbackOpcode2Str(%i) - invalid opcode", opcode); | carla_stderr("CarlaBackend::EngineCallbackOpcode2Str(%i) - invalid opcode", opcode); | ||||
@@ -1,6 +1,6 @@ | |||||
/* | /* | ||||
* Carla patchbay utils | * Carla patchbay utils | ||||
* Copyright (C) 2011-2017 Filipe Coelho <falktx@falktx.com> | |||||
* Copyright (C) 2011-2020 Filipe Coelho <falktx@falktx.com> | |||||
* | * | ||||
* This program is free software; you can redistribute it and/or | * This program is free software; you can redistribute it and/or | ||||
* modify it under the terms of the GNU General Public License as | * modify it under the terms of the GNU General Public License as | ||||
@@ -27,7 +27,7 @@ | |||||
struct GroupNameToId { | struct GroupNameToId { | ||||
uint group; | uint group; | ||||
char name[STR_MAX+1]; // globally unique | |||||
char name[STR_MAX]; // globally unique | |||||
void clear() noexcept | void clear() noexcept | ||||
{ | { | ||||
@@ -43,15 +43,15 @@ struct GroupNameToId { | |||||
void rename(const char n[]) noexcept | void rename(const char n[]) noexcept | ||||
{ | { | ||||
std::strncpy(name, n, STR_MAX); | |||||
name[STR_MAX] = '\0'; | |||||
std::strncpy(name, n, STR_MAX-1); | |||||
name[STR_MAX-1] = '\0'; | |||||
} | } | ||||
bool operator==(const GroupNameToId& groupNameToId) const noexcept | bool operator==(const GroupNameToId& groupNameToId) const noexcept | ||||
{ | { | ||||
if (groupNameToId.group != group) | if (groupNameToId.group != group) | ||||
return false; | return false; | ||||
if (std::strncmp(groupNameToId.name, name, STR_MAX) != 0) | |||||
if (std::strncmp(groupNameToId.name, name, STR_MAX-1) != 0) | |||||
return false; | return false; | ||||
return true; | return true; | ||||
} | } | ||||
@@ -65,10 +65,12 @@ struct GroupNameToId { | |||||
struct PatchbayGroupList { | struct PatchbayGroupList { | ||||
uint lastId; | uint lastId; | ||||
LinkedList<GroupNameToId> list; | LinkedList<GroupNameToId> list; | ||||
CarlaMutex mutex; | |||||
PatchbayGroupList() noexcept | PatchbayGroupList() noexcept | ||||
: lastId(0), | : lastId(0), | ||||
list() {} | |||||
list(), | |||||
mutex() {} | |||||
void clear() noexcept | void clear() noexcept | ||||
{ | { | ||||
@@ -87,8 +89,8 @@ struct PatchbayGroupList { | |||||
struct PortNameToId { | struct PortNameToId { | ||||
uint group; | uint group; | ||||
uint port; | uint port; | ||||
char name[STR_MAX+1]; // locally unique (within the same group) | |||||
char fullName[STR_MAX+1]; // globally unique | |||||
char name[STR_MAX]; // locally unique (within the same group) | |||||
char fullName[STR_MAX]; // globally unique | |||||
void clear() noexcept | void clear() noexcept | ||||
{ | { | ||||
@@ -107,26 +109,26 @@ struct PortNameToId { | |||||
void setFullName(const char fn[]) noexcept | void setFullName(const char fn[]) noexcept | ||||
{ | { | ||||
std::strncpy(fullName, fn, STR_MAX); | |||||
fullName[STR_MAX] = '\0'; | |||||
std::strncpy(fullName, fn, STR_MAX-1); | |||||
fullName[STR_MAX-1] = '\0'; | |||||
} | } | ||||
void rename(const char n[], const char fn[]) noexcept | void rename(const char n[], const char fn[]) noexcept | ||||
{ | { | ||||
std::strncpy(name, n, STR_MAX); | |||||
name[STR_MAX] = '\0'; | |||||
std::strncpy(name, n, STR_MAX-1); | |||||
name[STR_MAX-1] = '\0'; | |||||
std::strncpy(fullName, fn, STR_MAX); | |||||
fullName[STR_MAX] = '\0'; | |||||
std::strncpy(fullName, fn, STR_MAX-1); | |||||
fullName[STR_MAX-1] = '\0'; | |||||
} | } | ||||
bool operator==(const PortNameToId& portNameToId) noexcept | bool operator==(const PortNameToId& portNameToId) noexcept | ||||
{ | { | ||||
if (portNameToId.group != group || portNameToId.port != port) | if (portNameToId.group != group || portNameToId.port != port) | ||||
return false; | return false; | ||||
if (std::strncmp(portNameToId.name, name, STR_MAX) != 0) | |||||
if (std::strncmp(portNameToId.name, name, STR_MAX-1) != 0) | |||||
return false; | return false; | ||||
if (std::strncmp(portNameToId.fullName, fullName, STR_MAX) != 0) | |||||
if (std::strncmp(portNameToId.fullName, fullName, STR_MAX-1) != 0) | |||||
return false; | return false; | ||||
return true; | return true; | ||||
} | } | ||||
@@ -140,10 +142,12 @@ struct PortNameToId { | |||||
struct PatchbayPortList { | struct PatchbayPortList { | ||||
uint lastId; | uint lastId; | ||||
LinkedList<PortNameToId> list; | LinkedList<PortNameToId> list; | ||||
CarlaMutex mutex; | |||||
PatchbayPortList() noexcept | PatchbayPortList() noexcept | ||||
: lastId(0), | : lastId(0), | ||||
list() {} | |||||
list(), | |||||
mutex() {} | |||||
void clear() noexcept | void clear() noexcept | ||||
{ | { | ||||