/*============================================================================= CAMIDIEndpointMenu2.mm $Log: CAMIDIEndpointMenu2.mm,v $ Revision 1.1 2004/10/04 23:11:11 dwyatt initial checkin created 10/3/04, Doug Wyatt Copyright (c) 2004 Apple Computer, Inc. All Rights Reserved $NoKeywords: $ =============================================================================*/ #import "CAMIDIEndpointMenu2.h" #include class MIDIEndpointInfoMgr { public: struct EndpointInfo { MIDIUniqueID mUniqueID; NSString * mName; MIDIEndpointRef mEndpoint; }; typedef std::vector EndpointInfoList; MIDIEndpointInfoMgr() { UpdateFromCurrentState(); } void Clear() { EndpointInfoList::iterator eit; for (eit = mSources.begin(); eit != mSources.end(); ++eit) [(*eit).mName release]; for (eit = mDestinations.begin(); eit != mDestinations.end(); ++eit) [(*eit).mName release]; mSources.clear(); mDestinations.clear(); } void UpdateFromCurrentState() { Clear(); int i, n; MIDIEndpointRef e; n = MIDIGetNumberOfDestinations(); for (i = 0; i < n; ++i) { e = MIDIGetDestination(i); if (e == NULL) continue; EndpointInfo ei; ei.mEndpoint = e; if (MIDIObjectGetIntegerProperty(e, kMIDIPropertyUniqueID, &ei.mUniqueID)) continue; if (MIDIObjectGetStringProperty(e, kMIDIPropertyDisplayName, (CFStringRef *)&ei.mName)) continue; mDestinations.push_back(ei); } n = MIDIGetNumberOfSources(); for (i = 0; i < n; ++i) { e = MIDIGetSource(i); if (e == NULL) continue; EndpointInfo ei; ei.mEndpoint = e; if (MIDIObjectGetIntegerProperty(e, kMIDIPropertyUniqueID, &ei.mUniqueID)) continue; if (MIDIObjectGetStringProperty(e, kMIDIPropertyDisplayName, (CFStringRef *)&ei.mName)) continue; mSources.push_back(ei); } } EndpointInfoList & Sources() { return mSources; } EndpointInfoList & Destinations() { return mDestinations; } private: EndpointInfoList mSources; EndpointInfoList mDestinations; }; static MIDIEndpointInfoMgr *gMIDIEndpoints = NULL; static NSMutableSet * gInstances = NULL; static MIDIClientRef gClient = NULL; // CoreMIDI callback for when endpoints change -- rebuilds all menu instances static void NotifyProc(const MIDINotification *message, void *refCon) { if (message->messageID == kMIDIMsgSetupChanged) { gMIDIEndpoints->UpdateFromCurrentState(); NSEnumerator *e = [gInstances objectEnumerator]; CAMIDIEndpointMenu *menu; while ((menu = [e nextObject]) != nil) [menu rebuildMenu]; } } @implementation CAMIDIEndpointMenu - (void)_init { if (gInstances == NULL) gInstances = [[NSMutableSet alloc] init]; [gInstances addObject: self]; mInited = YES; mType = -1; mOptions = 0; mSelectedUniqueID = 0; } - (id)initWithFrame: (NSRect)frame { self = [super initWithFrame: frame]; if (self) [self _init]; return self; } - (void)dealloc { [gInstances removeObject: self]; if ([gInstances count] == 0) { delete gMIDIEndpoints; gMIDIEndpoints = NULL; [gInstances release]; gInstances = nil; if (gClient) { MIDIClientDispose(gClient); gClient = NULL; } } [super dealloc]; } - (void)buildMenu: (int)type opts: (int)opts { if (!mInited) [self _init]; if (gClient == NULL) MIDIClientCreate(CFSTR(""), NotifyProc, NULL, &gClient); if (gMIDIEndpoints == NULL) gMIDIEndpoints = new MIDIEndpointInfoMgr; mType = type; mOptions = opts; [self rebuildMenu]; } - (void)syncSelectedName { } static NSString *UniqueTitle(NSString *name, NSMutableDictionary *previousTitles) { NSString *newItemTitle = name; int suffix = 0; while (true) { if ([previousTitles objectForKey: newItemTitle] == nil) break; if (suffix == 0) suffix = 2; else ++suffix; newItemTitle = [NSString stringWithFormat: @"%@ #%d", name, suffix]; } [previousTitles setObject: newItemTitle forKey: newItemTitle]; return newItemTitle; } - (void)rebuildMenu { int itemsToKeep = (mOptions & kMIDIEndpointMenuOpt_CanSelectNone) ? 1 : 0; while ([self numberOfItems] > itemsToKeep) [self removeItemAtIndex: itemsToKeep]; MIDIEndpointInfoMgr::EndpointInfoList &eil = (mType == kMIDIEndpointMenuSources) ? gMIDIEndpoints->Sources() : gMIDIEndpoints->Destinations(); NSMutableDictionary *previousTitles = [[NSMutableDictionary alloc] init]; int n = eil.size(); bool foundSelection = false; for (int i = 0; i < n; ++i) { MIDIEndpointInfoMgr::EndpointInfo *ei = &eil[i]; NSString *name = ei->mName; NSString *newItemTitle = UniqueTitle(name, previousTitles); // see if that collides with any previous item -- base class requires unique titles [self addItemWithTitle: newItemTitle]; // cast from CFString if (ei->mUniqueID == mSelectedUniqueID) { [self selectItemAtIndex: itemsToKeep + i]; [self syncSelectedName]; foundSelection = true; } } if (!foundSelection) [self selectItemAtIndex: 0]; [previousTitles release]; } - (MIDIEndpointRef)selectedEndpoint { int itemsToIgnore = (mOptions & kMIDIEndpointMenuOpt_CanSelectNone) ? 1 : 0; int i = [self indexOfSelectedItem]; if (i >= itemsToIgnore) { MIDIEndpointInfoMgr::EndpointInfoList &eil = (mType == kMIDIEndpointMenuSources) ? gMIDIEndpoints->Sources() : gMIDIEndpoints->Destinations(); MIDIEndpointInfoMgr::EndpointInfo *ei = &eil[i - itemsToIgnore]; mSelectedUniqueID = ei->mUniqueID; [self syncSelectedName]; return ei->mEndpoint; } return NULL; } - (MIDIUniqueID)selectedUniqueID { [self syncSelectedName]; int itemsToIgnore = (mOptions & kMIDIEndpointMenuOpt_CanSelectNone) ? 1 : 0; int i = [self indexOfSelectedItem]; MIDIEndpointInfoMgr::EndpointInfoList &eil = (mType == kMIDIEndpointMenuSources) ? gMIDIEndpoints->Sources() : gMIDIEndpoints->Destinations(); MIDIUniqueID uid = (i >= itemsToIgnore) ? eil[i - itemsToIgnore].mUniqueID : kMIDIInvalidUniqueID; mSelectedUniqueID = uid; return uid; } - (BOOL)selectUniqueID: (MIDIUniqueID)uniqueID { mSelectedUniqueID = uniqueID; int itemsToIgnore = (mOptions & kMIDIEndpointMenuOpt_CanSelectNone) ? 1 : 0; if (uniqueID == kMIDIInvalidUniqueID && itemsToIgnore == 1) { [self selectItemAtIndex: 0]; [self syncSelectedName]; return YES; } MIDIEndpointInfoMgr::EndpointInfoList &eil = (mType == kMIDIEndpointMenuSources) ? gMIDIEndpoints->Sources() : gMIDIEndpoints->Destinations(); int n = eil.size(); for (int i = 0; i < n; ++i) { MIDIEndpointInfoMgr::EndpointInfo *ei = &eil[i]; if (ei->mUniqueID == uniqueID) { [self selectItemAtIndex: itemsToIgnore + i]; [self syncSelectedName]; return YES; } } return NO; } @end