/*============================================================================= CAMIDIEndpointMenu.mm $Log: CAMIDIEndpointMenu.mm,v $ Revision 1.4 2004/09/04 21:22:00 dwyatt add accessor to get the CAMIDIEndpoints being used Revision 1.3 2004/08/07 14:30:19 dwyatt remove possibly problematic code Revision 1.2 2004/08/02 18:46:06 dwyatt fix selection of item by uniqueID Revision 1.1 2004/06/23 19:07:12 dwyatt initial checkin created Mon Apr 19 2004, Doug Wyatt Copyright (c) 2003 Apple Computer, Inc. All Rights Reserved $NoKeywords: $ =============================================================================*/ #import "CAMIDIEndpointMenu.h" #include "CAMIDIEndpoints.h" static CAMIDIEndpoints *gMIDIEndpoints = NULL; static NSMutableSet * gInstances = NULL; static MIDIClientRef gClient = NULL; #define mEndpointInfo ((CAMIDIEndpoints::EndpointInfoList *)_endpointInfo) // 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 { mEndpointInfo = NULL; if (gInstances == NULL) gInstances = [[NSMutableSet alloc] init]; [gInstances addObject: self]; mInited = YES; mType = -1; mOptions = 0; mSelection = 0; mSelectionName = nil; } - (id)initWithFrame: (NSRect)frame { self = [super initWithFrame: frame]; if (self) [self _init]; return self; } - (void)dealloc { delete mEndpointInfo; [gInstances removeObject: self]; [mSelectionName release]; [super dealloc]; } - (void)buildMenu: (int)type opts: (int)opts { if (!mInited) [self _init]; if (gClient == NULL) MIDIClientCreate(CFSTR(""), NotifyProc, NULL, &gClient); mType = type; mOptions = opts; [self rebuildMenu]; } - (void)syncSelectedName { [mSelectionName release]; [(mSelectionName = [self titleOfSelectedItem]) retain]; } 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]; delete mEndpointInfo; mEndpointInfo = NULL; if (gMIDIEndpoints == NULL) gMIDIEndpoints = new CAMIDIEndpoints; switch (mType) { case kMIDIEndpointMenuSources: mEndpointInfo = gMIDIEndpoints->GetSources(mOptions); break; case kMIDIEndpointMenuDestinations: default: // better than failing I suppose mEndpointInfo = gMIDIEndpoints->GetDestinations(mOptions); break; case kMIDIEndpointMenuPairs: mEndpointInfo = gMIDIEndpoints->GetEndpointPairs(mOptions); break; } NSMutableDictionary *previousTitles = [[NSMutableDictionary alloc] init]; int n = mEndpointInfo->size(); bool foundSelection = false; for (int i = 0; i < n; ++i) { CAMIDIEndpoints::EndpointInfo *ei = (*mEndpointInfo)[i]; NSString *name = (NSString *)ei->mDisplayName; 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 == mSelection) { [self selectItemAtIndex: itemsToKeep + i]; [self syncSelectedName]; foundSelection = true; } } if (!foundSelection) { /*if (mSelectionName != nil) { #warning "all the code following indexOfSelectedItem calls will crash in the presence of this!" // add the previously selected item, disabled, at the bottom NSString *newItemTitle = UniqueTitle(mSelectionName, previousTitles); [self addItemWithTitle: newItemTitle]; id item = [self itemAtIndex: itemsToKeep + n]; [item setEnabled: NO]; [self selectItemAtIndex: itemsToKeep + n]; } else*/ { [self selectItemAtIndex: 0]; } } [previousTitles release]; } - (MIDIEndpointRef)selectedEndpoint { int itemsToIgnore = (mOptions & kMIDIEndpointMenuOpt_CanSelectNone) ? 1 : 0; int i = [self indexOfSelectedItem]; if (i >= itemsToIgnore) { CAMIDIEndpoints::EndpointInfo *ei = (*mEndpointInfo)[i - itemsToIgnore]; mSelection = ei->mUniqueID; [self syncSelectedName]; if (mType == kMIDIEndpointMenuSources) return ei->mSourceEndpoint; else return ei->mDestinationEndpoint; } return NULL; } - (void)selectedEndpointPair: (MIDIEndpointRef *)outSource dest: (MIDIEndpointRef *)outDest { int itemsToIgnore = (mOptions & kMIDIEndpointMenuOpt_CanSelectNone) ? 1 : 0; int i = [self indexOfSelectedItem]; if (i >= itemsToIgnore) { CAMIDIEndpoints::EndpointInfo *ei = (*mEndpointInfo)[i - itemsToIgnore]; mSelection = ei->mUniqueID; [self syncSelectedName]; *outSource = ei->mSourceEndpoint; *outDest = ei->mDestinationEndpoint; return; } *outSource = NULL; *outDest = NULL; } - (MIDIUniqueID)selectedUniqueID { [self syncSelectedName]; int itemsToIgnore = (mOptions & kMIDIEndpointMenuOpt_CanSelectNone) ? 1 : 0; int i = [self indexOfSelectedItem]; MIDIUniqueID uid = (i >= itemsToIgnore) ? (*mEndpointInfo)[i - itemsToIgnore]->mUniqueID : kMIDIInvalidUniqueID; mSelection = uid; return uid; } - (BOOL)selectUniqueID: (MIDIUniqueID)uniqueID { mSelection = uniqueID; int itemsToIgnore = (mOptions & kMIDIEndpointMenuOpt_CanSelectNone) ? 1 : 0; if (uniqueID == kMIDIInvalidUniqueID && itemsToIgnore == 1) { [self selectItemAtIndex: 0]; [self syncSelectedName]; return YES; } int n = mEndpointInfo->size(); for (int i = 0; i < n; ++i) { CAMIDIEndpoints::EndpointInfo *ei = (*mEndpointInfo)[i]; if (ei->mUniqueID == uniqueID) { [self selectItemAtIndex: itemsToIgnore + i]; [self syncSelectedName]; return YES; } } return NO; } + (CAMIDIEndpoints *)getEndpoints { if (gMIDIEndpoints == NULL) gMIDIEndpoints = new CAMIDIEndpoints; return gMIDIEndpoints; } @end