/* * Copyright (c) 1999-2000, Eric Moon. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions, and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions, and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * 3. The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ // RouteAppNodeManager.cpp #include "RouteAppNodeManager.h" #include "MediaIcon.h" #include "NodeGroup.h" #include "NodeRef.h" #include "Connection.h" #include "route_app_io.h" #include "ConnectionIO.h" #include "DormantNodeIO.h" #include "LiveNodeIO.h" #include "MediaFormatIO.h" #include "MessageIO.h" #include "NodeSetIOContext.h" #include "StringContent.h" #include "MediaString.h" #include #include #include #include #include #include #include #include #include #include "set_tools.h" // Locale Kit #include #undef B_TRANSLATION_CONTEXT #define B_TRANSLATION_CONTEXT "CortexRouteApp" using namespace std; __USE_CORTEX_NAMESPACE #define D_METHOD(x) //PRINT (x) #define D_HOOK(x) //PRINT (x) #define D_SETTINGS(x) //PRINT (x) // -------------------------------------------------------- // // *** ctor/dtor // -------------------------------------------------------- // RouteAppNodeManager::~RouteAppNodeManager() { _freeIcons(); } RouteAppNodeManager::RouteAppNodeManager( bool useAddOnHost) : NodeManager(useAddOnHost), m_nextGroupNumber(1) { // pre-cache icons? +++++ } // -------------------------------------------------------- // // *** group management // -------------------------------------------------------- // // -------------------------------------------------------- // // *** icon management // -------------------------------------------------------- // // fetch cached icon for the given live node; the MediaIcon // instance is guaranteed to last as long as this object. // Returns 0 if the node doesn't exist. const MediaIcon* RouteAppNodeManager::mediaIconFor( media_node_id nodeID, icon_size iconSize) { BAutolock _l(this); uint64 key = _makeIconKey(nodeID, iconSize); icon_map::const_iterator it = m_iconMap.find(key); if(it != m_iconMap.end()) { // already cached return (*it).second; } // look up live_node_info NodeRef* ref; status_t err = getNodeRef(nodeID, &ref); if(err < B_OK) return 0; return mediaIconFor(ref->nodeInfo(), iconSize); } const MediaIcon* RouteAppNodeManager::mediaIconFor( live_node_info nodeInfo, icon_size iconSize) { uint64 key = _makeIconKey(nodeInfo.node.node, iconSize); icon_map::const_iterator it = m_iconMap.find(key); if(it != m_iconMap.end()) { // already cached return (*it).second; } // create & cache icon MediaIcon* icon = new MediaIcon( nodeInfo, iconSize); m_iconMap.insert( icon_map::value_type(key, icon)); return icon; } // -------------------------------------------------------- // // *** error handling // -------------------------------------------------------- // status_t RouteAppNodeManager::setLogTarget( const BMessenger& target) { BAutolock _l(this); if(!target.IsValid()) return B_BAD_VALUE; m_logTarget = target; return B_OK; } // -------------------------------------------------------- // // NodeManager hook implementations // -------------------------------------------------------- // void RouteAppNodeManager::nodeCreated( NodeRef* ref) { // prepare the log message BMessage logMsg(M_LOG); BString title = B_TRANSLATE("Node '%name%' created"); title.ReplaceFirst("%name%", ref->name()); logMsg.AddString("title", title); // create a default group for the node // [em 8feb00] NodeGroup* g = createGroup(ref->name()); if(ref->kind() & B_TIME_SOURCE) { // notify observers BMessage m(M_TIME_SOURCE_CREATED); m.AddInt32("nodeID", ref->id()); notify(&m); } // adopt node's time source if it's not the system clock (the default) // [em 20mar00] media_node systemClock; status_t err = roster->GetSystemTimeSource(&systemClock); if(err == B_OK) { BTimeSource* ts = roster->MakeTimeSourceFor(ref->node()); if (ts == NULL) return; if(ts->Node() != systemClock) { g->setTimeSource(ts->Node()); logMsg.AddString("line", "Synced to system clock"); } ts->Release(); } g->addNode(ref); m_logTarget.SendMessage(&logMsg); } void RouteAppNodeManager::nodeDeleted( const NodeRef* ref) { // prepare the log message BMessage logMsg(M_LOG); BString title = B_TRANSLATE("Node '%name%' released"); title.ReplaceFirst("%name%", ref->name()); logMsg.AddString("title", title); if(ref->kind() & B_TIME_SOURCE) { // notify observers BMessage m(M_TIME_SOURCE_DELETED); m.AddInt32("nodeID", ref->id()); notify(&m); } m_logTarget.SendMessage(&logMsg); } void RouteAppNodeManager::connectionMade( Connection* connection) { D_HOOK(( "@ RouteAppNodeManager::connectionMade()\n")); status_t err; // prepare the log message BMessage logMsg(M_LOG); BString title = B_TRANSLATE("Connection made"); if (strcmp(connection->outputName(), connection->inputName()) == 0) { title = B_TRANSLATE("Connection '%name%' made"); title.ReplaceFirst("%name%", connection->outputName()); } logMsg.AddString("title", title); if(!(connection->flags() & Connection::INTERNAL)) // don't react to connection Cortex didn't make return; // create or merge groups NodeRef *producer, *consumer; err = getNodeRef(connection->sourceNode(), &producer); if(err < B_OK) { D_HOOK(( "!!! RouteAppNodeManager::connectionMade():\n" " sourceNode (%ld) not found\n", connection->sourceNode())); return; } err = getNodeRef(connection->destinationNode(), &consumer); if(err < B_OK) { D_HOOK(( "!!! RouteAppNodeManager::connectionMade():\n" " destinationNode (%ld) not found\n", connection->destinationNode())); return; } // add node names to log messages BString line = B_TRANSLATE("Between:"); logMsg.AddString("line", line); line = " "; line += B_TRANSLATE("%producer% and %consumer%"); line.ReplaceFirst("%producer%", producer->name()); line.ReplaceFirst("%consumer%", consumer->name()); logMsg.AddString("line", line); // add format to log message line = B_TRANSLATE("Negotiated format:"); logMsg.AddString("line", line); line = " "; line << MediaString::getStringFor(connection->format(), false); logMsg.AddString("line", line); NodeGroup *group = 0; BString groupName = B_TRANSLATE("Untitled group"); groupName += " "; if(_canGroup(producer) && _canGroup(consumer)) { if (producer->group() && consumer->group() && !(producer->group()->groupFlags() & NodeGroup::GROUP_LOCKED) && !(consumer->group()->groupFlags() & NodeGroup::GROUP_LOCKED)) { // merge into consumers group group = consumer->group(); mergeGroups(producer->group(), group); } else if (producer->group() && !(producer->group()->groupFlags() & NodeGroup::GROUP_LOCKED)) { // add consumer to producers group group = producer->group(); group->addNode(consumer); } else if (consumer->group() && !(consumer->group()->groupFlags() & NodeGroup::GROUP_LOCKED)) { // add producer to consumers group group = consumer->group(); group->addNode(producer); } else { // make new group for both groupName << m_nextGroupNumber++; group = createGroup(groupName.String()); group->addNode(producer); group->addNode(consumer); } } else if(_canGroup(producer) && !producer->group()) { // make new group for producer groupName << m_nextGroupNumber++; group = createGroup(groupName.String()); group->addNode(producer); } else if(_canGroup(consumer) && !consumer->group()) { // make new group for consumer groupName << m_nextGroupNumber++; group = createGroup(groupName.String()); group->addNode(consumer); } m_logTarget.SendMessage(&logMsg); } void RouteAppNodeManager::connectionBroken( const Connection* connection) { D_HOOK(( "@ RouteAppNodeManager::connectionBroken()\n")); // prepare the log message BMessage logMsg(M_LOG); BString title = B_TRANSLATE("Connection broken"); if (strcmp(connection->outputName(), connection->inputName()) == 0) { title = B_TRANSLATE("Connection '%name%' broken"); title.ReplaceFirst("%name%", connection->outputName()); } logMsg.AddString("title", title); if(!(connection->flags() & Connection::INTERNAL)) // don't react to connection Cortex didn't make return; status_t err; // if the source and destination nodes belong to the same group, // and if no direct or indirect connection remains between the // source and destination nodes, split groups NodeRef *producer, *consumer; err = getNodeRef(connection->sourceNode(), &producer); if(err < B_OK) { D_HOOK(( "!!! RouteAppNodeManager::connectionMade():\n" " sourceNode (%ld) not found\n", connection->sourceNode())); return; } err = getNodeRef(connection->destinationNode(), &consumer); if(err < B_OK) { D_HOOK(( "!!! RouteAppNodeManager::connectionMade():\n" " destinationNode (%ld) not found\n", connection->destinationNode())); return; } // add node names to log messages BString line = B_TRANSLATE("Between:"); logMsg.AddString("line", line); line = " "; line += B_TRANSLATE("%producer% and %consumer%"); line.ReplaceFirst("%producer%", producer->name()); line.ReplaceFirst("%consumer%", consumer->name()); logMsg.AddString("line", line); if( producer->group() && producer->group() == consumer->group() && !findRoute(producer->id(), consumer->id())) { NodeGroup *newGroup; splitGroup(producer, consumer, &newGroup); } m_logTarget.SendMessage(&logMsg); } void RouteAppNodeManager::connectionFailed( const media_output & output, const media_input & input, const media_format & format, status_t error) { D_HOOK(( "@ RouteAppNodeManager::connectionFailed()\n")); status_t err; // prepare the log message BMessage logMsg(M_LOG); BString title = B_TRANSLATE("Connection failed"); logMsg.AddString("title", title); logMsg.AddInt32("error", error); NodeRef *producer, *consumer; err = getNodeRef(output.node.node, &producer); if(err < B_OK) { return; } err = getNodeRef(input.node.node, &consumer); if(err < B_OK) { return; } // add node names to log messages BString line = B_TRANSLATE("Between:"); logMsg.AddString("line", line); line = " "; line += B_TRANSLATE("%producer% and %consumer%"); line.ReplaceFirst("%producer%", producer->name()); line.ReplaceFirst("%consumer%", consumer->name()); logMsg.AddString("line", line); // add format to log message line = B_TRANSLATE("Tried format:"); logMsg.AddString("line", line); line = " "; line << MediaString::getStringFor(format, true); logMsg.AddString("line", line); // and send it m_logTarget.SendMessage(&logMsg); } // -------------------------------------------------------- // // *** IPersistent // -------------------------------------------------------- // void RouteAppNodeManager::xmlExportBegin( ExportContext& context) const { status_t err; try { NodeSetIOContext& set = dynamic_cast(context); context.beginElement(_NODE_SET_ELEMENT); // validate the node set for(int n = set.countNodes()-1; n >= 0; --n) { media_node_id id = set.nodeAt(n); ASSERT(id != media_node::null.node); // fetch node NodeRef* ref; err = getNodeRef(id, &ref); if(err < B_OK) { D_SETTINGS(( "! RVNM::xmlExportBegin(): node %ld doesn't exist\n", id)); set.removeNodeAt(n); continue; } // skip unless internal if(!ref->isInternal()) { D_SETTINGS(( "! RVNM::xmlExportBegin(): node %ld not internal; skipping.\n", id)); set.removeNodeAt(n); continue; } } } catch(bad_cast& e) { context.reportError("RouteAppNodeManager: expected a NodeSetIOContext\n"); } } void RouteAppNodeManager::xmlExportAttributes( ExportContext& context) const {} void RouteAppNodeManager::xmlExportContent( ExportContext& context) const { status_t err; try { NodeSetIOContext& nodeSet = dynamic_cast(context); context.beginContent(); // write nodes; enumerate connections typedef map connection_map; connection_map connections; int count = nodeSet.countNodes(); for(int n = 0; n < count; ++n) { media_node_id id = nodeSet.nodeAt(n); ASSERT(id != media_node::null.node); // fetch node NodeRef* ref; err = getNodeRef(id, &ref); if(err < B_OK) { D_SETTINGS(( "! RouteAppNodeManager::xmlExportContent():\n" " getNodeRef(%ld) failed: '%s'\n", id, strerror(err))); continue; } // fetch connections vector conSet; ref->getInputConnections(conSet); ref->getOutputConnections(conSet); for(uint32 c = 0; c < conSet.size(); ++c) // non-unique connections will be rejected: connections.insert( connection_map::value_type(conSet[c].id(), conSet[c])); // create an IO object for the node & write it DormantNodeIO io(ref, nodeSet.keyAt(n)); if(context.writeObject(&io) < B_OK) // abort return; } // write connections for(connection_map::const_iterator it = connections.begin(); it != connections.end(); ++it) { ConnectionIO io( &(*it).second, this, &nodeSet); if(context.writeObject(&io) < B_OK) // abort return; } // +++++ write groups // write UI state { BMessage m; nodeSet.exportUIState(&m); context.beginElement(_UI_STATE_ELEMENT); context.beginContent(); MessageIO io(&m); context.writeObject(&io); context.endElement(); } } catch(bad_cast& e) { context.reportError("RouteAppNodeManager: expected a NodeSetIOContext\n"); } } void RouteAppNodeManager::xmlExportEnd( ExportContext& context) const { context.endElement(); } // -------------------------------------------------------- // // IMPORT // -------------------------------------------------------- // void RouteAppNodeManager::xmlImportBegin( ImportContext& context) { } void RouteAppNodeManager::xmlImportAttribute( const char* key, const char* value, ImportContext& context) {} void RouteAppNodeManager::xmlImportContent( const char* data, uint32 length, ImportContext& context) {} void RouteAppNodeManager::xmlImportChild( IPersistent* child, ImportContext& context) { status_t err; if(!strcmp(context.element(), _DORMANT_NODE_ELEMENT)) { DormantNodeIO* io = dynamic_cast(child); ASSERT(io); NodeRef* newRef; err = io->instantiate(this, &newRef); if(err == B_OK) { // created node; add an entry to the set stored in the // ImportContext for later reference try { NodeSetIOContext& set = dynamic_cast(context); set.addNode(newRef->id(), io->nodeKey()); } catch(bad_cast& e) { context.reportError("RouteAppNodeManager: expected a NodeSetIOContext\n"); } } else { D_SETTINGS(( "!!! RouteAppNodeManager::xmlImportChild():\n" " DormantNodeIO::instantiate() failed:\n" " '%s'\n", strerror(err))); } } else if(!strcmp(context.element(), _CONNECTION_ELEMENT)) { ConnectionIO* io = dynamic_cast(child); ASSERT(io); // instantiate the connection Connection con; err = io->instantiate( this, dynamic_cast(&context), &con); if(err < B_OK) { D_SETTINGS(( "!!! ConnectionIO::instantiate() failed:\n" " '%s'\n", strerror(err))); } // +++++ group magic? } else if(!strcmp(context.element(), _NODE_GROUP_ELEMENT)) { // +++++ } else if( context.parentElement() && !strcmp(context.parentElement(), _UI_STATE_ELEMENT)) { // expect a nested message MessageIO* io = dynamic_cast(child); if(!io) { BString err; err << "RouteAppNodeManager: unexpected child '" << context.element() << "'\n"; context.reportError(err.String()); } // hand it off via the extended context object try { NodeSetIOContext& set = dynamic_cast(context); set.importUIState(io->message()); } catch(bad_cast& e) { context.reportError("RouteAppNodeManager: expected a NodeSetIOContext\n"); } } } void RouteAppNodeManager::xmlImportComplete( ImportContext& context) { } // -------------------------------------------------------- // /*static*/ void RouteAppNodeManager::AddTo( XML::DocumentType* docType) { // set up the document type MessageIO::AddTo(docType); MediaFormatIO::AddTo(docType); ConnectionIO::AddTo(docType); DormantNodeIO::AddTo(docType); LiveNodeIO::AddTo(docType); _add_string_elements(docType); } // -------------------------------------------------------- // // implementation // -------------------------------------------------------- // uint64 RouteAppNodeManager::_makeIconKey( media_node_id nodeID, icon_size iconSize) { return ((uint64)nodeID) << 32 | iconSize; } void RouteAppNodeManager::_readIconKey( uint64 key, media_node_id& nodeID, icon_size& iconSize) { nodeID = key >> 32; iconSize = icon_size(key & 0xffffffff); } void RouteAppNodeManager::_freeIcons() { ptr_map_delete( m_iconMap.begin(), m_iconMap.end()); } bool RouteAppNodeManager::_canGroup(NodeRef* ref) const { // sanity check & easy cases ASSERT(ref); if(ref->isInternal()) return true; // bar 'touchy' system nodes if(ref == audioMixerNode() || ref == audioOutputNode()) return false; return true; } // END -- RouteAppNodeManager.cpp --