add midi input/output menu.

This commit is contained in:
Kelvin Sherlock 2021-08-07 15:12:20 -04:00
parent 026a9e7704
commit 70f4738264
7 changed files with 440 additions and 31 deletions

View File

@ -149,6 +149,10 @@
B6841BD7251EC926006A5C39 /* vmnet_helper.c in Sources */ = {isa = PBXBuildFile; fileRef = B6841BCA251EC88E006A5C39 /* vmnet_helper.c */; };
B6841BDA251ECB1C006A5C39 /* mame64 in CopyFiles */ = {isa = PBXBuildFile; fileRef = B66236B824FDA698006CABD7 /* mame64 */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
B6841BDE251ECC29006A5C39 /* vmnet.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B6841BDD251ECC29006A5C39 /* vmnet.framework */; };
B68A899026BE18E000B2C8C6 /* MidiManager.m in Sources */ = {isa = PBXBuildFile; fileRef = B68A898F26BE18E000B2C8C6 /* MidiManager.m */; };
B68A899126BE18E000B2C8C6 /* MidiManager.m in Sources */ = {isa = PBXBuildFile; fileRef = B68A898F26BE18E000B2C8C6 /* MidiManager.m */; };
B68A899426BF124B00B2C8C6 /* CoreMIDI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B68A899326BF124B00B2C8C6 /* CoreMIDI.framework */; };
B68A899526BF12A600B2C8C6 /* CoreMIDI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B68A899326BF124B00B2C8C6 /* CoreMIDI.framework */; };
B6A1A1942528EB1700DB0FD7 /* Menu.m in Sources */ = {isa = PBXBuildFile; fileRef = B6A1A1932528EB1700DB0FD7 /* Menu.m */; };
B6A1A1952528EB1700DB0FD7 /* Menu.m in Sources */ = {isa = PBXBuildFile; fileRef = B6A1A1932528EB1700DB0FD7 /* Menu.m */; };
B6B9EA662506A5550080E70D /* EjectButton.m in Sources */ = {isa = PBXBuildFile; fileRef = B6B9EA642506A5550080E70D /* EjectButton.m */; };
@ -449,6 +453,9 @@
B6841BCA251EC88E006A5C39 /* vmnet_helper.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = vmnet_helper.c; sourceTree = "<group>"; };
B6841BD0251EC913006A5C39 /* vmnet_helper */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = vmnet_helper; sourceTree = BUILT_PRODUCTS_DIR; };
B6841BDD251ECC29006A5C39 /* vmnet.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = vmnet.framework; path = System/Library/Frameworks/vmnet.framework; sourceTree = SDKROOT; };
B68A898F26BE18E000B2C8C6 /* MidiManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MidiManager.m; sourceTree = "<group>"; };
B68A899226BE320200B2C8C6 /* MidiManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MidiManager.h; sourceTree = "<group>"; };
B68A899326BF124B00B2C8C6 /* CoreMIDI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreMIDI.framework; path = System/Library/Frameworks/CoreMIDI.framework; sourceTree = SDKROOT; };
B6A1A1932528EB1700DB0FD7 /* Menu.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = Menu.m; sourceTree = "<group>"; };
B6A1A1962528EB4600DB0FD7 /* Menu.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Menu.h; sourceTree = "<group>"; };
B6B9EA642506A5550080E70D /* EjectButton.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = EjectButton.m; sourceTree = "<group>"; };
@ -518,6 +525,7 @@
files = (
B65D718625E70BD5008C5F87 /* WebKit.framework in Frameworks */,
B635C09D26784A4800B23BFD /* Sparkle.framework in Frameworks */,
B68A899426BF124B00B2C8C6 /* CoreMIDI.framework in Frameworks */,
B635C09A26784A1200B23BFD /* Sparkle.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
@ -526,6 +534,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
B68A899526BF12A600B2C8C6 /* CoreMIDI.framework in Frameworks */,
B65D718725E70BE5008C5F87 /* WebKit.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
@ -635,6 +644,7 @@
B66236B624FDA686006CABD7 /* Frameworks */ = {
isa = PBXGroup;
children = (
B68A899326BF124B00B2C8C6 /* CoreMIDI.framework */,
B635C09926784A1200B23BFD /* Sparkle.framework */,
B65D718525E70BD5008C5F87 /* WebKit.framework */,
B6841BDD251ECC29006A5C39 /* vmnet.framework */,
@ -725,6 +735,8 @@
B6B9EA642506A5550080E70D /* EjectButton.m */,
B64AF1F8250EF6A500A09B9B /* Transformers.h */,
B64AF1F9250EF6A500A09B9B /* Transformers.m */,
B68A898F26BE18E000B2C8C6 /* MidiManager.m */,
B68A899226BE320200B2C8C6 /* MidiManager.h */,
B6BA258124E99BEB005FB8FF /* Assets.xcassets */,
B64E15AF24EA365E00E8AD3D /* Resources */,
B6BA258624E99BEB005FB8FF /* Info.plist */,
@ -1205,6 +1217,7 @@
B6665C14265A0E3E00254939 /* AutocompleteControl.m in Sources */,
B64979C224EF6703008ABD20 /* MediaViewController.m in Sources */,
B60A6E1424EE0AE2004B7EEF /* FlippedView.m in Sources */,
B68A899026BE18E000B2C8C6 /* MidiManager.m in Sources */,
B6BA258024E99BE9005FB8FF /* AppDelegate.m in Sources */,
B6004DF024FB05D600D38596 /* LogWindowController.m in Sources */,
B63005332666D6940014C381 /* BookmarkManager.m in Sources */,
@ -1223,6 +1236,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
B68A899126BE18E000B2C8C6 /* MidiManager.m in Sources */,
B608E1802502FE0C00D53465 /* TransparentScroller.m in Sources */,
B64AF1FB250EF6A500A09B9B /* Transformers.m in Sources */,
B6E4B5B024FDE2670094A35C /* main.m in Sources */,

View File

@ -61,7 +61,7 @@
<outlet property="textField" destination="Pep-mX-LHY" id="hUe-cf-6vq"/>
</connections>
</tableCellView>
<tableCellView identifier="ItemView" translatesAutoresizingMaskIntoConstraints="NO" id="yGq-lc-RCM" customClass="TablePathView">
<tableCellView identifier="ItemView" translatesAutoresizingMaskIntoConstraints="NO" id="yGq-lc-RCM" customClass="PathTableCellView">
<rect key="frame" x="1" y="21" width="296" height="22"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
@ -101,7 +101,7 @@
<outlet property="pathControl" destination="f7R-TO-fmF" id="oH7-N3-JC7"/>
</connections>
</tableCellView>
<tableCellView identifier="BBItemView" id="2Nq-Xz-tkV" customClass="TablePathView">
<tableCellView identifier="BBItemView" id="2Nq-Xz-tkV" customClass="PathTableCellView">
<rect key="frame" x="1" y="46" width="296" height="22"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
@ -146,7 +146,7 @@
<outlet property="ejectButton" destination="zxn-1E-o34" id="eC6-Mf-tN6"/>
</connections>
</tableCellView>
<tableCellView identifier="OutputItemView" id="jQd-Ar-5uf" customClass="TablePathView">
<tableCellView identifier="OutputItemView" id="jQd-Ar-5uf" customClass="PathTableCellView">
<rect key="frame" x="1" y="71" width="296" height="22"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
@ -177,12 +177,12 @@
</textFieldCell>
<connections>
<action selector="textAction:" target="-2" id="N7h-6y-D8W"/>
<binding destination="jQd-Ar-5uf" name="enabled" keyPath="objectValue.valid" id="1wE-NY-Vb2"/>
<binding destination="jQd-Ar-5uf" name="value" keyPath="objectValue.string" id="kXU-qJ-HKa">
<dictionary key="options">
<string key="NSNullPlaceholder">/path/to/file</string>
</dictionary>
</binding>
<binding destination="jQd-Ar-5uf" name="enabled" keyPath="objectValue.valid" id="1wE-NY-Vb2"/>
</connections>
</textField>
</subviews>
@ -191,6 +191,57 @@
<outlet property="ejectButton" destination="vG4-PP-efF" id="X2I-2N-t5r"/>
</connections>
</tableCellView>
<tableCellView identifier="MidiItemView" id="HDi-YM-iKn" customClass="MidiTableCellView">
<rect key="frame" x="1" y="96" width="296" height="22"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<imageView horizontalHuggingPriority="251" verticalHuggingPriority="251" id="XcO-0S-W4c">
<rect key="frame" x="0.0" y="1" width="20" height="20"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<imageCell key="cell" refusesFirstResponder="YES" alignment="left" imageAlignment="left" image="drag-handle-4x10" id="4a2-So-rxD"/>
</imageView>
<button verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="F3v-8Z-LaF" customClass="EjectButton">
<rect key="frame" x="277" y="3" width="16" height="16"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMinY="YES"/>
<buttonCell key="cell" type="bevel" bezelStyle="regularSquare" image="eject-16x16" imagePosition="only" alignment="center" alternateImage="eject-hover-16x16" imageScaling="proportionallyDown" inset="2" id="b3t-Zp-2si">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<action selector="ejectAction:" target="-2" id="AvU-Sz-QKJ"/>
<binding destination="HDi-YM-iKn" name="enabled" keyPath="objectValue.occupied" id="ERy-Iz-T9Q"/>
</connections>
</button>
<popUpButton verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="L2F-mj-lkr">
<rect key="frame" x="20" y="-3" width="244" height="25"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<popUpButtonCell key="cell" type="push" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="CaJ-NG-vbR" id="Rac-za-bEQ">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
<menu key="menu" id="I55-5S-oM2">
<items>
<menuItem state="on" id="CaJ-NG-vbR">
<modifierMask key="keyEquivalentModifierMask"/>
</menuItem>
</items>
</menu>
</popUpButtonCell>
<connections>
<action selector="midiAction:" target="-2" id="LDL-4h-xUR"/>
<binding destination="HDi-YM-iKn" name="selectedValue" keyPath="objectValue.string" id="xjL-hx-VG3">
<dictionary key="options">
<string key="NSValueTransformerName">EmptyStringTransformer</string>
</dictionary>
</binding>
</connections>
</popUpButton>
</subviews>
<connections>
<outlet property="dragHandle" destination="XcO-0S-W4c" id="oKY-UF-LkW"/>
<outlet property="ejectButton" destination="F3v-8Z-LaF" id="ql8-4u-qCA"/>
<outlet property="popUpButton" destination="L2F-mj-lkr" id="Ktx-Yc-DIp"/>
</connections>
</tableCellView>
</prototypeCellViews>
</tableColumn>
</tableColumns>

View File

@ -9,26 +9,8 @@
#import "MediaViewController.h"
#import "TableCellView.h"
enum {
kIndexFloppy525 = 0,
kIndexFloppy35,
kIndexHardDrive,
kIndexCDROM,
kIndexCassette,
kIndexDiskImage,
kIndexBitBanger,
kIndexMidiIn,
kIndexMidiOut,
kIndexPicture, // computer eyes -pic, .png only.
// kIndexPrintout // -prin, .prn extension only?
kIndexLast
};
#define CATEGORY_COUNT 10
#define SIZEOF(x) (sizeof(x) / sizeof(x[0]))
static_assert(kIndexLast == CATEGORY_COUNT, "Invalid Category Count");
@protocol MediaNode
-(BOOL)isGroupItem;
@ -285,15 +267,23 @@ static_assert(kIndexLast == CATEGORY_COUNT, "Invalid Category Count");
-(NSString *)viewIdentifier {
if (_category == kIndexBitBanger) return @"BBItemView";
if (_category == kIndexMidiOut) return @"OutputItemView";
if (_category == kIndexMidiIn) return @"OutputItemView";
if (_category == kIndexMidiOut) return @"MidiItemView";
if (_category == kIndexMidiIn) return @"MidiItemView";
return @"ItemView";
}
-(void)prepareView: (TablePathView *)view {
-(void)prepareView: (MediaTableCellView *)view {
/* set the path tag = category. */
[view prepareView: _category];
#if 0
if (_category == kIndexMidiIn || _category == kIndexMidiOut || _category == kIndexBitBanger) {
return;
}
NSPathControl *pc = [view pathControl];
[pc setTag: _category + 1]; // to differentiate 0 / no path control.
#endif
}
-(CGFloat)height {
@ -437,7 +427,7 @@ x = media.name; cat = _data[index]; delta |= [cat setItemCount: x]
// So we should build a device list (and pre-populate the default one)
// another approach is a separate utility to act as a midi/serial input converter
// and midi file / serial converter so the modem/serial port could be used.
#if 0
#if 1
_(midiin, kIndexMidiIn);
_(midiout, kIndexMidiOut);
#endif
@ -782,6 +772,9 @@ static NSString *kDragType = @"private.ample.media";
-(IBAction)textAction: (id)sender {
[self rebuildArgs];
}
- (IBAction)midiAction:(id)sender {
[self rebuildArgs];
}
-(IBAction)resetMedia:(id)sender {
[self resetDiskImages];

25
Ample/MidiManager.h Normal file
View File

@ -0,0 +1,25 @@
//
// MidiManager.h
// Ample
//
// Created by Kelvin Sherlock on 8/6/2021.
// Copyright © 2021 Kelvin Sherlock. All rights reserved.
//
#ifndef MidiManager_h
#define MidiManager_h
extern NSString *kMidiSourcesChangedNotification;
extern NSString *kMidiDestinationsChangedNotification;
@interface MidiManager : NSObject
@property NSArray *sources;
@property NSArray *destinations;
+(instancetype)sharedManager;
@end
#endif /* MidiManager_h */

153
Ample/MidiManager.m Normal file
View File

@ -0,0 +1,153 @@
//
// Midi.m
// Ample
//
// Created by Kelvin Sherlock on 8/6/2021.
// Copyright © 2021 Kelvin Sherlock. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <CoreMIDI/CoreMIDI.h>
#import "MidiManager.h"
static NSArray *MidiSources(void) {
ItemCount count = MIDIGetNumberOfSources();
if (count <= 0) return @[];
NSMutableArray *rv = [NSMutableArray arrayWithCapacity: count + 1];
MIDIEndpointRef ep;
for(int i = 0; i < count; ++i) {
ep = MIDIGetSource(i);
if (!ep) continue;
// https://developer.apple.com/library/archive/qa/qa1374/_index.html
CFStringRef str = NULL;
MIDIObjectGetStringProperty(ep, kMIDIPropertyDisplayName, &str);
if (str) {
[rv addObject: (__bridge id _Nonnull)(str)];
CFRelease(str);
}
}
return rv;
}
static NSArray *MidiDestinations(void) {
ItemCount count = MIDIGetNumberOfDestinations();
if (count <= 0) return @[];
NSMutableArray *rv = [NSMutableArray arrayWithCapacity: count + 1];
MIDIEndpointRef ep;
for(int i = 0; i < count; ++i) {
ep = MIDIGetDestination(i);
if (!ep) continue;
// https://developer.apple.com/library/archive/qa/qa1374/_index.html
CFStringRef str = NULL;
MIDIObjectGetStringProperty(ep, kMIDIPropertyDisplayName, &str);
if (str) {
[rv addObject: (__bridge id _Nonnull)(str)];
CFRelease(str);
}
}
return rv;
}
NSString *kMidiSourcesChangedNotification = @"Midi Sources Changed";
NSString *kMidiDestinationsChangedNotification = @"Midi Destinations Changed";
@interface MidiManager () {
MIDIClientRef _client;
}
-(void)objectAddRemove: (const MIDIObjectAddRemoveNotification *)message;
-(void)objectPropertyChanged: (const MIDIObjectPropertyChangeNotification *)message;
@end
static MidiManager *singleton = nil;
@implementation MidiManager
-(void)awakeFromNib {
if (!singleton) singleton = self;
}
+(instancetype)sharedManager {
if (!singleton) singleton = [MidiManager new];
return singleton;
}
-(instancetype)init {
if (singleton) return singleton;
OSStatus status;
status = MIDIClientCreateWithBlock(
CFSTR("serial_midi"),
&_client,
^(const MIDINotification *message){
switch(message->messageID) {
case kMIDIMsgObjectAdded:
case kMIDIMsgObjectRemoved:
[self objectAddRemove: (const MIDIObjectAddRemoveNotification *)message];
break;
case kMIDIMsgPropertyChanged:
[self objectPropertyChanged: (const MIDIObjectPropertyChangeNotification *)message];
default:
break;
}
});
_sources = MidiSources();
_destinations = MidiDestinations();
return self;
}
-(void)objectAddRemove: (const MIDIObjectAddRemoveNotification *)message {
const MIDIObjectAddRemoveNotification *m = (const MIDIObjectAddRemoveNotification *)message;
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
if (m->childType == kMIDIObjectType_Source) {
[self setSources: MidiSources()];
[nc postNotificationName: kMidiSourcesChangedNotification object: self];
}
if (m->childType == kMIDIObjectType_Destination) {
[self setDestinations: MidiDestinations()];
[nc postNotificationName: kMidiDestinationsChangedNotification object: self];
}
}
-(void)objectPropertyChanged: (const MIDIObjectPropertyChangeNotification *)message {
const MIDIObjectPropertyChangeNotification *m = (const MIDIObjectPropertyChangeNotification *)message;
if (m->propertyName == kMIDIPropertyDisplayName) {
[self setSources: MidiSources()];
[self setDestinations: MidiDestinations()];
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
[nc postNotificationName: kMidiSourcesChangedNotification object: self];
[nc postNotificationName: kMidiDestinationsChangedNotification object: self];
}
}
-(void)dealloc {
if (_client)
MIDIClientDispose(_client);
}
@end

View File

@ -11,12 +11,40 @@
//NS_ASSUME_NONNULL_BEGIN
enum {
kIndexFloppy525 = 0,
kIndexFloppy35,
kIndexHardDrive,
kIndexCDROM,
kIndexCassette,
kIndexDiskImage,
kIndexBitBanger,
kIndexMidiIn,
kIndexMidiOut,
kIndexPicture, // computer eyes -pic, .png only.
// kIndexPrintout // -prin, .prn extension only?
kIndexLast
};
#define CATEGORY_COUNT 10
static_assert(kIndexLast == CATEGORY_COUNT, "Invalid Category Count");
@interface TablePathView : NSTableCellView
@property (weak) IBOutlet NSPathControl *pathControl;
@interface MediaTableCellView : NSTableCellView
@property (weak) IBOutlet NSButton *ejectButton;
@property (weak) IBOutlet NSImageView *dragHandle;
@property BOOL movable;
-(void)prepareView: (NSInteger)category;
@end
@interface PathTableCellView : MediaTableCellView <NSPathControlDelegate>
@property (weak) IBOutlet NSPathControl *pathControl;
@end
@interface MidiTableCellView : MediaTableCellView
@property (weak) IBOutlet NSPopUpButton *popUpButton;
@end
//NS_ASSUME_NONNULL_END

View File

@ -7,13 +7,16 @@
//
#import "TableCellView.h"
#import "MidiManager.h"
#import "Menu.h"
@implementation TablePathView {
@implementation MediaTableCellView
#if 0
{
NSTrackingRectTag _trackingRect;
}
#endif
-(void)awakeFromNib {
// need to do it here for 10.11 compatibility.
@ -31,6 +34,8 @@
}
-(void)prepareView: (NSInteger)category {
}
#if 0
-(void)awakeFromNib {
@ -65,3 +70,143 @@
@end
@implementation PathTableCellView
-(void)prepareView: (NSInteger)category {
[_pathControl setTag: category + 1];
}
- (void)pathControl:(NSPathControl *)pathControl willPopUpMenu:(NSMenu *)menu {
// if this is an output path, replace the "choose..." button with a save panel.
NSMenuItem *item = [menu itemAtIndex: 0];
if (item) {
[item setTarget: self];
[item setAction: @selector(choosePath:)];
}
}
-(IBAction)choosePath:(id)sender {
NSPathControl *pc = _pathControl;
NSURL *url = [pc URL];
NSSavePanel *p = [NSSavePanel savePanel];
if (url) {
NSFileManager *fm = [NSFileManager defaultManager];
BOOL dir = NO;
NSString *str = [NSString stringWithCString: [url fileSystemRepresentation] encoding: NSUTF8StringEncoding];
[fm fileExistsAtPath: str isDirectory: &dir];
if (!dir) {
[p setNameFieldStringValue: [str lastPathComponent]];
url = [url URLByDeletingLastPathComponent];
}
[p setDirectoryURL: url];
}
[p setExtensionHidden: NO];
[p beginWithCompletionHandler: ^(NSModalResponse response){
if (response != NSModalResponseOK) return;
NSURL *url = [p URL];
[pc setURL: url];
}];
}
@end
@interface EmptyStringTransformer : NSValueTransformer
@end
static NSString *kNone = @"—None—";
@implementation EmptyStringTransformer
+(void)load {
[self setValueTransformer: [self new] forName: @"EmptyStringTransformer"];
}
+ (Class)transformedValueClass {
return [NSString class];
}
+ (BOOL)allowsReverseTransformation {
return YES;
}
- (id)transformedValue:(id)value {
if (value == nil) return kNone;
if ([kNone isEqualToString: value]) return nil;
return value;
}
@end
@implementation MidiTableCellView {
NSInteger _category;
}
/* binding should be able to handle the menu but i couldn't make it work. */
-(void)prepareView: (NSInteger)category {
_category = category;
// 10.11 + doesn't need to remove the observer in the -dealloc
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
[nc addObserver: self selector: @selector(midiChanged:) name: category == kIndexMidiIn ? kMidiSourcesChangedNotification : kMidiDestinationsChangedNotification object: nil];
[self updateMenus: NO];
}
-(void)updateMenus: (BOOL)notification {
NSMenu *menu = [_popUpButton menu];
MidiManager *mgr = [MidiManager sharedManager];
NSArray *array = _category == kIndexMidiIn ? [mgr sources] : [mgr destinations];
[menu removeAllItems];
NSString *selected = [[_popUpButton selectedItem] representedObject];
int selectedIndex = -1;
NSMenuItem *item;
item = [[NSMenuItem alloc] initWithTitle: kNone action: NULL keyEquivalent: @""];
[item setAttributedTitle: ItalicMenuString(kNone)];
[menu addItem: item];
selectedIndex = 0;
#if 0
if (!selected || [@"" isEqualToString: selected]) {
selectedIndex = 0;
}
#endif
int ix = 1;
for (NSString *s in array) {
item = [[NSMenuItem alloc] initWithTitle: s action: NULL keyEquivalent: @""];
[item setRepresentedObject: s];
[menu addItem: item];
if ([s isEqualToString: selected]) {
selectedIndex = ix;
}
++ix;
}
// does this propogate?
[_popUpButton selectItemAtIndex: selectedIndex];
if (notification) [_popUpButton sendAction: [_popUpButton action] to: [_popUpButton target]];
}
-(void)midiChanged: (NSNotification *)notification {
[self updateMenus: YES];
}
-(void)prepareForReuse {
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
[nc removeObserver: self];
_category = 0;
[super prepareForReuse];
}
@end