Search
lxdream.org :: lxdream/src/cocoaui/cocoa_ctrl.m
lxdream 0.9.1
released Jun 29
Download Now
filename src/cocoaui/cocoa_ctrl.m
changeset 1043:ec716f2b8ffb
prev1041:5fcc39857c5c
next1058:37f6fdc738e7
author nkeynes
date Sat Jun 27 09:55:00 2009 +0000 (13 years ago)
permissions -rw-r--r--
last change Ensure each VMU is only attached once
Handle removal of sub-devices when detaching parent device
view annotate diff log raw
     1 /**
     2  * $Id$
     3  *
     4  * Construct and manage the controller configuration pane
     5  *
     6  * Copyright (c) 2008 Nathan Keynes.
     7  *
     8  * This program is free software; you can redistribute it and/or modify
     9  * it under the terms of the GNU General Public License as published by
    10  * the Free Software Foundation; either version 2 of the License, or
    11  * (at your option) any later version.
    12  *
    13  * This program is distributed in the hope that it will be useful,
    14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
    15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    16  * GNU General Public License for more details.
    17  */
    19 #include "cocoaui.h"
    20 #include "config.h"
    21 #include "lxpaths.h"
    22 #include "display.h"
    23 #include "maple/maple.h"
    24 #include "vmu/vmulist.h"
    26 #include <glib/gstrfuncs.h>
    28 #define FIRST_SECONDARY_DEVICE MAPLE_PORTS
    30 #define FIRST_VMU_TAG 0x1000
    31 #define LOAD_VMU_TAG -1
    32 #define CREATE_VMU_TAG -2
    34 #define KEYBINDING_SIZE 110
    36 static void cocoa_config_keysym_hook(void *data, const gchar *keysym);
    37 static gboolean cocoa_config_vmulist_hook(vmulist_change_type_t type, int idx, void *data);
    39 @interface KeyBindingEditor (Private)
    40 - (void)updateKeysym: (const gchar *)sym;
    41 @end
    43 @implementation KeyBindingEditor
    44 - (id)init
    45 {
    46     self = [super init];
    47     isPrimed = NO;
    48     lastValue = nil;
    49     [self setFieldEditor: YES];
    50     [self setEditable: FALSE];
    51     return self;
    52 }
    53 - (void)dealloc
    54 {
    55     if( lastValue != nil ) {
    56         [lastValue release];
    57         lastValue = nil;
    58     }
    59     [super dealloc];
    60 }
    61 - (void)setPrimed: (BOOL)primed
    62 {
    63     if( primed != isPrimed ) {
    64         isPrimed = primed;
    65         if( primed ) {
    66             lastValue = [[NSString stringWithString: [self string]] retain];
    67             [self setString: @"<press key>"];
    68             input_set_keysym_hook(cocoa_config_keysym_hook, self);            
    69         } else {
    70             [lastValue release];
    71             lastValue = nil;
    72             input_set_keysym_hook(NULL,NULL);
    73         }
    74     }
    75 }
    76 - (void)resignFirstResponder
    77 {
    78     if( isPrimed ) {
    79         [self setString: lastValue];
    80         [self setPrimed: NO];
    81     }
    82     [super resignFirstResponder];
    83 }
    84 - (void)fireBindingChanged
    85 {
    86     id delegate = [self delegate];
    87     if( delegate != nil && [delegate respondsToSelector:@selector(textDidChange:)] ) {
    88         [delegate textDidChange: [NSNotification notificationWithName: NSTextDidChangeNotification object: self]];
    89     }
    90 }
    92 - (void)updateKeysym: (const gchar *)sym
    93 {
    94     if( sym != NULL ) {
    95         [self setString: [NSString stringWithCString: sym]];
    96         [self setPrimed: NO];
    97         [self fireBindingChanged];
    98     }
    99 }
   100 - (void)updateMousesym: (int)button
   101 {
   102     gchar *keysym = input_keycode_to_keysym( &system_mouse_driver, (button+1) );
   103     if( keysym != NULL ) {
   104         [self updateKeysym: keysym ];
   105         g_free(keysym);
   106     }
   107 }
   108 - (void)keyPressed: (int)keycode
   109 {
   110     gchar *keysym = input_keycode_to_keysym(NULL, keycode);
   111     if( keysym != NULL ) {
   112         [self updateKeysym: keysym];
   113         g_free(keysym);
   114     }
   115 }
   116 - (void)insertText:(id)string
   117 {
   118     // Do nothing
   119 }
   120 - (void)mouseDown: (NSEvent *)event
   121 {
   122     if( isPrimed ) {
   123         [self updateMousesym: 0];
   124     } else {
   125         [self setPrimed: YES];
   126         [super mouseDown: event];
   127     }
   128 }
   129 - (void)rightMouseDown: (NSEvent *)event
   130 {
   131     if( isPrimed ) {
   132         [self updateMousesym: 1];
   133     }
   134 }
   135 - (void)otherMouseDown: (NSEvent *)event
   136 {
   137     if( isPrimed ) {
   138         [self updateMousesym: [event buttonNumber]];
   139     }
   140 }
   141 - (void)keyDown: (NSEvent *) event
   142 {
   143     NSString *chars = [event characters];
   144     if( isPrimed ) {
   145         if( chars != NULL && [chars length] == 1 && [chars characterAtIndex: 0] == 27 ) {
   146             // Escape char = abort change
   147             [self setString: lastValue];
   148             [self setPrimed: NO];
   149         } else {
   150             [self keyPressed: ([event keyCode]+1)];
   151         }
   152     } else {
   153         if( chars != NULL && [chars length] == 1 ) {
   154             int ch = [chars characterAtIndex: 0];
   155             switch( ch ) {
   156             case 0x7F:
   157                 [self setString: @""]; 
   158                 [self fireBindingChanged];
   159                 break;
   160             case '\r':
   161                 [self setPrimed: YES];
   162                 break;
   163             default:
   164                 [super keyDown: event];
   165                 break;
   166             }
   167         } else {
   168             [super keyDown: event];
   169         }
   170     }
   171 }
   172 - (void)flagsChanged: (NSEvent *) event
   173 {
   174     if( isPrimed ) {
   175         [self keyPressed: ([event keyCode]+1)];
   176     }
   177     [super flagsChanged: event];
   178 }
   179 @end
   181 static void cocoa_config_keysym_hook(void *data, const gchar *keysym)
   182 {
   183     KeyBindingEditor *editor = (KeyBindingEditor *)data;
   184     [editor updateKeysym: keysym];
   185 }
   188 @implementation KeyBindingField
   189 @end
   191 /*************************** Key-binding sub-view ***********************/
   193 #define MAX_KEY_BINDINGS 32
   195 @interface ControllerKeyBindingView : NSView
   196 {
   197     maple_device_t device;
   198     NSTextField *field[MAX_KEY_BINDINGS][2];
   199 }
   200 - (id)initWithFrame: (NSRect)frameRect;
   201 - (void)setDevice: (maple_device_t)device;
   202 @end
   204 @implementation ControllerKeyBindingView
   205 - (id)initWithFrame: (NSRect)frameRect
   206 {
   207     if( [super initWithFrame: frameRect] == nil ) {
   208         return nil;
   209     } else {
   210         device = NULL;
   211         return self;
   212     }
   213 }
   214 - (BOOL)isFlipped
   215 {
   216     return YES;
   217 }
   218 - (void)removeSubviews
   219 {
   220     [[self subviews] makeObjectsPerformSelector: @selector(removeFromSuperview)];
   221 }
   222 - (void)controlTextDidChange: (NSNotification *)notify
   223 {
   224     const gchar *p = NULL;
   225     int binding = [[notify object] tag];
   226     NSString *val1 = [field[binding][0] stringValue];
   227     if( field[binding][1] == NULL ) {
   228         p = [val1 UTF8String];
   229     } else {
   230         NSString *val2 = [field[binding][1] stringValue];
   231         char buf[ [val1 length] + [val2 length] + 2 ];
   233         if( [val1 length] == 0 ) {
   234             if( [val2 length] != 0 ) {
   235                 p = [val2 UTF8String];
   236             }
   237         } else if( [val2 length] == 0 ) {
   238             p = [val1 UTF8String];
   239         } else {
   240             sprintf( buf, "%s,%s", [val1 UTF8String], [val2 UTF8String] );
   241             p = buf;
   242         }
   243     }
   244     maple_set_device_config_value( device, binding, p ); 
   245     lxdream_save_config();
   246 }
   247 - (void)setDevice: (maple_device_t)newDevice
   248 {
   249     device = newDevice;
   250     [self removeSubviews];
   251     if( device != NULL && !MAPLE_IS_VMU(device) ) {
   252         lxdream_config_entry_t config = maple_get_device_config(device);
   253         if( config != NULL ) {
   254             int count, i, y, x;
   256             for( count=0; config[count].key != NULL; count++ );
   257             x = TEXT_GAP;
   258             NSSize size = NSMakeSize(85+KEYBINDING_SIZE*2+TEXT_GAP*4, count*(TEXT_HEIGHT+TEXT_GAP)+TEXT_GAP);
   259             [self setFrameSize: size];
   260             [self scrollRectToVisible: NSMakeRect(0,0,1,1)]; 
   261             y = TEXT_GAP;
   262             for( i=0; config[i].key != NULL; i++ ) {
   263                 /* Add label */
   264                 NSRect frame = NSMakeRect(x, y + 2, 85, LABEL_HEIGHT);
   265                 NSTextField *label = cocoa_gui_add_label(self, NS_(config[i].label), frame);
   266                 [label setAlignment: NSRightTextAlignment];
   268                 switch(config[i].type) {
   269                 case CONFIG_TYPE_KEY:
   270                     frame = NSMakeRect( x + 85 + TEXT_GAP, y, KEYBINDING_SIZE, TEXT_HEIGHT);
   271                     field[i][0] = [[KeyBindingField alloc] initWithFrame: frame];
   272                     [field[i][0] setAutoresizingMask: (NSViewMinYMargin|NSViewMaxXMargin)];
   273                     [field[i][0] setTag: i];
   274                     [field[i][0] setDelegate: self];
   275                     [self addSubview: field[i][0]];
   277                     frame = NSMakeRect( x + 85 + KEYBINDING_SIZE + (TEXT_GAP*2), y, KEYBINDING_SIZE, TEXT_HEIGHT);
   278                     field[i][1] = [[KeyBindingField alloc] initWithFrame: frame];
   279                     [field[i][1] setAutoresizingMask: (NSViewMinYMargin|NSViewMaxXMargin)];
   280                     [field[i][1] setTag: i];
   281                     [field[i][1] setDelegate: self];
   282                     [self addSubview: field[i][1]];
   284                     if( config[i].value != NULL ) {
   285                         gchar **parts = g_strsplit(config[i].value,",",3);
   286                         if( parts[0] != NULL ) {
   287                             [field[i][0] setStringValue: [NSString stringWithCString: parts[0]]];
   288                             if( parts[1] != NULL ) {
   289                                 [field[i][1] setStringValue: [NSString stringWithCString: parts[1]]];
   290                             }
   291                         }
   292                         g_strfreev(parts);
   293                     }
   294                     break;
   295                 case CONFIG_TYPE_FILE:
   296                 case CONFIG_TYPE_PATH:
   297                     frame = NSMakeRect( x + 85 + TEXT_GAP, y, KEYBINDING_SIZE*2+TEXT_GAP, TEXT_HEIGHT);
   298                     field[i][0] = [[NSTextField alloc] initWithFrame: frame];
   299                     [field[i][0] setAutoresizingMask: (NSViewMinYMargin|NSViewMaxXMargin)];
   300                     [field[i][0] setTag: i];
   301                     [field[i][0] setDelegate: self];
   302                     [self addSubview: field[i][0]];
   303                     if( config[i].value != NULL ) {
   304                         [field[i][0] setStringValue: [NSString stringWithCString: config[i].value]];
   305                     }
   306                     field[i][1] = NULL;
   307                 } 
   308                 y += (TEXT_HEIGHT + TEXT_GAP);
   309             }
   310         } else {
   311             [self setFrameSize: NSMakeSize(100,TEXT_HEIGHT+TEXT_GAP) ];
   312         }
   313     } else {
   314         [self setFrameSize: NSMakeSize(100,TEXT_HEIGHT+TEXT_GAP) ];
   315     }
   316 }
   317 @end
   319 /*************************** Top-level controller pane ***********************/
   320 static NSButton *addRadioButton( int port, int sub, int x, int y, id parent )
   321 {
   322     char buf[16];
   324     if( sub == 0 ) {
   325         snprintf( buf, sizeof(buf), _("Port %c."), 'A'+port );
   326     } else {
   327         snprintf( buf, sizeof(buf), _("VMU %d."), sub );
   328     }
   330     NSButton *radio = [[NSButton alloc] initWithFrame: NSMakeRect( x, y, 60, TEXT_HEIGHT )];
   331     [radio setTitle: [NSString stringWithUTF8String: buf]];
   332     [radio setTag: MAPLE_DEVID(port,sub) ];
   333     [radio setButtonType: NSRadioButton];
   334     [radio setAlignment: NSRightTextAlignment];
   335     [radio setTarget: parent];
   336     [radio setAction: @selector(radioChanged:)];
   337     [radio setAutoresizingMask: (NSViewMinYMargin|NSViewMaxXMargin)];
   338     [parent addSubview: radio];
   339     return radio;
   340 }
   342 static void setDevicePopupSelection( NSPopUpButton *popup, maple_device_t device )
   343 {
   344     if( device == NULL ) {
   345         [popup selectItemAtIndex: 0];
   346     } else if( MAPLE_IS_VMU(device) ) {
   347         int idx = vmulist_get_index_by_filename( MAPLE_VMU_NAME(device) );
   348         if( idx == -1 ) {
   349             [popup selectItemAtIndex: 0];
   350         } else {
   351             [popup selectItemWithTag: FIRST_VMU_TAG + idx];
   352         }
   353     } else {
   354         const struct maple_device_class **devices = maple_get_device_classes();
   355         int i;
   356         for( i=0; devices[i] != NULL; i++ ) {
   357             if( devices[i] == device->device_class ) {
   358                 [popup selectItemWithTag: i+1];
   359                 return;
   360             }
   361         }
   362         // Should never get here, but if so...
   363         [popup selectItemAtIndex: 0];
   364     }
   365 }
   367 static void buildDevicePopupMenu( NSPopUpButton *popup, maple_device_t device, BOOL primary )
   368 {
   369     int j;
   370     const struct maple_device_class **devices = maple_get_device_classes();
   372     [popup removeAllItems];
   373     [popup addItemWithTitle: NS_("<empty>")];
   374     [[popup itemAtIndex: 0] setTag: 0];
   375     for( j=0; devices[j] != NULL; j++ ) {
   376         int isPrimaryDevice = devices[j]->flags & MAPLE_TYPE_PRIMARY;
   377         if( primary ? isPrimaryDevice : (!isPrimaryDevice && !MAPLE_IS_VMU_CLASS(devices[j])) ) {
   378             [popup addItemWithTitle: [NSString stringWithUTF8String: devices[j]->name]];
   379             if( device != NULL && device->device_class == devices[j] ) {
   380                 [popup selectItemAtIndex: ([popup numberOfItems]-1)];
   381             }
   382             [[popup itemAtIndex: ([popup numberOfItems]-1)] setTag: (j+1)];
   383         }
   384     }
   386     if( !primary ) {
   387         BOOL vmu_selected = NO;
   388         const char *vmu_name;
   389         if( device != NULL && MAPLE_IS_VMU(device) ) {
   390             vmu_selected = YES;
   391             vmu_name = MAPLE_VMU_NAME(device);
   392         }
   393         if( [popup numberOfItems] > 0 ) {
   394             [[popup menu] addItem: [NSMenuItem separatorItem]];
   395         }
   397         unsigned int vmu_count = vmulist_get_size();
   398         for( j=0; j<vmu_count; j++ ) {
   399             const char *name = vmulist_get_name(j);
   400             [popup addItemWithTitle: [NSString stringWithUTF8String: name]];
   401             if( vmu_selected && strcmp(vmu_name, vmulist_get_filename(j)) == 0 ) {
   402                 [popup selectItemAtIndex: ([popup numberOfItems]-1)];
   403             }
   404             [[popup itemAtIndex: ([popup numberOfItems]-1)] setTag: FIRST_VMU_TAG + j];
   405         }
   407         [popup addItemWithTitle: NS_("Load VMU...")];
   408         [[popup itemAtIndex: ([popup numberOfItems]-1)] setTag: LOAD_VMU_TAG];
   409         [popup addItemWithTitle: NS_("Create VMU...")];
   410         [[popup itemAtIndex: ([popup numberOfItems]-1)] setTag: CREATE_VMU_TAG];
   411     }
   413 }
   415 static NSPopUpButton *addDevicePopup( int port, int sub, int x, int y, maple_device_t device, BOOL primary, id parent )
   416 {
   417     NSPopUpButton *popup = [[NSPopUpButton alloc] initWithFrame: NSMakeRect(x,y,150,TEXT_HEIGHT) 
   418                                                   pullsDown: NO];
   419     [popup setAutoresizingMask: (NSViewMinYMargin|NSViewMaxXMargin)];
   420     buildDevicePopupMenu(popup,device,primary);
   422     [popup setTarget: parent];
   423     [popup setAction: @selector(deviceChanged:)];
   424     [popup setTag: MAPLE_DEVID(port,sub) ];
   425     [parent addSubview: popup];    
   426     return popup;
   427 }
   429 @interface VMULoadValidator : NSObject
   430 {
   431 }
   432 - (BOOL)panel:(id) sender isValidFilename: (NSString *)filename;
   433 @end
   435 @implementation VMULoadValidator 
   436 - (BOOL)panel:(id) sender isValidFilename: (NSString *)filename
   437 {
   438     const char *c_fn = [filename UTF8String];
   439     vmu_volume_t vol = vmu_volume_load( c_fn );
   440     if( vol != NULL ) {
   441         vmulist_add_vmu(c_fn, vol);
   442         return YES;
   443     } else {
   444         ERROR( "Unable to load VMU file (not a valid VMU)" );
   445         return NO;
   446     }
   447 }
   449 @end
   451 @interface VMUCreateValidator : NSObject
   452 {
   453 }
   454 - (BOOL)panel:(id) sender isValidFilename: (NSString *)filename;
   455 @end
   457 @implementation VMUCreateValidator 
   458 - (BOOL)panel:(id) sender isValidFilename: (NSString *)filename
   459 {
   460     const char *vmu_filename = [filename UTF8String];
   461     int idx = vmulist_create_vmu(vmu_filename, FALSE);
   462     if( idx == -1 ) {
   463         ERROR( "Unable to create file: %s\n", strerror(errno) );
   464         return NO;
   465     } else {
   466         return YES;
   467     }
   468 }
   469 @end
   472 @interface LxdreamPrefsControllerPane: LxdreamPrefsPane
   473 {
   474     struct maple_device *save_controller[MAPLE_MAX_DEVICES];
   475     NSButton *radio[MAPLE_MAX_DEVICES];
   476     NSPopUpButton *popup[MAPLE_MAX_DEVICES];
   477     ControllerKeyBindingView *key_bindings;
   478 }
   479 + (LxdreamPrefsControllerPane *)new;
   480 - (void)vmulistChanged: (id)sender;
   481 @end
   483 static gboolean cocoa_config_vmulist_hook(vmulist_change_type_t type, int idx, void *data)
   484 {
   485     LxdreamPrefsControllerPane *pane = (LxdreamPrefsControllerPane *)data;
   486     [pane vmulistChanged: nil];
   487     return TRUE;
   488 }
   490 @implementation LxdreamPrefsControllerPane
   491 + (LxdreamPrefsControllerPane *)new
   492 {
   493     return [[LxdreamPrefsControllerPane alloc] initWithFrame: NSMakeRect(0,0,600,400)];
   494 }
   495 - (id)initWithFrame: (NSRect)frameRect
   496 {
   497     if( [super initWithFrame: frameRect title: NS_("Controllers")] == nil ) {
   498         return nil;
   499     } else {
   500         int i,j;
   501         int y = [self contentHeight] - TEXT_HEIGHT - TEXT_GAP;
   503         memset( radio, 0, sizeof(radio) );
   504         memset( save_controller, 0, sizeof(save_controller) );
   505         NSBox *rule = [[NSBox alloc] initWithFrame: 
   506                 NSMakeRect(210+(TEXT_GAP*3), 1, 1, [self contentHeight] + TEXT_GAP - 2)];
   507         [rule setAutoresizingMask: (NSViewMaxXMargin|NSViewHeightSizable)];
   508         [rule setBoxType: NSBoxSeparator];
   509         [self addSubview: rule];
   511         NSRect bindingFrame = NSMakeRect(210+(TEXT_GAP*4), 0,
   512                    frameRect.size.width - (210+(TEXT_GAP*4)), [self contentHeight] + TEXT_GAP );
   513         NSScrollView *scrollView = [[NSScrollView alloc] initWithFrame: bindingFrame];
   514         key_bindings = [[ControllerKeyBindingView alloc] initWithFrame: bindingFrame ];
   515         [scrollView setAutoresizingMask: (NSViewWidthSizable|NSViewHeightSizable)];
   516         [scrollView setDocumentView: key_bindings];
   517         [scrollView setDrawsBackground: NO];
   518         [scrollView setHasVerticalScroller: YES];
   519         [scrollView setAutohidesScrollers: YES];
   521         [self addSubview: scrollView];
   522         [key_bindings setDevice: maple_get_device(0,0)];
   524         for( i=0; i<MAPLE_PORTS; i++ ) {
   525             maple_device_t device = maple_get_device(i,0);
   527             radio[i] = addRadioButton(i,0,TEXT_GAP,y,self);
   528             popup[i] = addDevicePopup(i,0,60 + (TEXT_GAP*2),y,device, YES,self);
   529             y -= (TEXT_HEIGHT+TEXT_GAP);
   531             int j,max = device == NULL ? 0 : MAPLE_SLOTS(device->device_class);
   532             for( j=1; j<=MAPLE_USER_SLOTS; j++ ) {
   533                 radio[MAPLE_DEVID(i,j)] = addRadioButton(i, j, TEXT_GAP*2, y, self);
   534                 popup[MAPLE_DEVID(i,j)] = addDevicePopup(i, j, 60 + TEXT_GAP*2, y, maple_get_device(i,j), NO, self);
   535                 y -= (TEXT_HEIGHT+TEXT_GAP);
   536                 if( j > max ) {
   537                     [radio[MAPLE_DEVID(i,j)] setEnabled: NO];
   538                     [popup[MAPLE_DEVID(i,j)] setEnabled: NO];
   539                 }
   540             }
   541         }
   543         [radio[0] setState: NSOnState];
   545         register_vmulist_change_hook(cocoa_config_vmulist_hook, self);
   546         return self;
   547     }
   548 }
   549 - (void)dealloc
   550 {
   551     unregister_vmulist_change_hook(cocoa_config_vmulist_hook,self);
   552     [super dealloc];
   553 }
   554 - (void)vmulistChanged: (id)sender
   555 {
   556     int i;
   557     for( i=FIRST_SECONDARY_DEVICE; i<MAPLE_MAX_DEVICES; i++ ) {
   558         if( popup[i] != NULL ) {
   559             buildDevicePopupMenu(popup[i], maple_get_device(MAPLE_DEVID_PORT(i), MAPLE_DEVID_SLOT(i)), NO );
   560         }
   561     }
   562 }
   563 - (void)radioChanged: (id)sender
   564 {
   565     int tag = [sender tag];
   566     int i;
   567     for( i=0; i<MAPLE_MAX_DEVICES; i++ ) {
   568         if( i != tag && radio[i] != NULL ) {
   569             [radio[i] setState: NSOffState];
   570         }
   571     }
   572     [key_bindings setDevice: maple_get_device(MAPLE_DEVID_PORT(tag),MAPLE_DEVID_SLOT(tag))];
   573 }
   574 - (void)deviceChanged: (id)sender
   575 {
   576     int tag = [sender tag];
   577     int port = MAPLE_DEVID_PORT(tag);
   578     int slot = MAPLE_DEVID_SLOT(tag);
   579     int new_device_idx = [[sender selectedItem] tag], i; 
   580     maple_device_class_t new_device_class = NULL;
   581     const gchar *vmu_filename = NULL;
   583     for( i=0; i<MAPLE_MAX_DEVICES; i++ ) {
   584         if( radio[i] != NULL ) {
   585             if( i == tag ) {
   586                 [radio[i] setState: NSOnState];
   587             } else {
   588                 [radio[i] setState: NSOffState];
   589             }
   590         }
   591     }
   593     maple_device_t current = maple_get_device(port,slot);
   594     maple_device_t new_device = NULL;
   595     if( new_device_idx == LOAD_VMU_TAG ) {
   596         NSArray *array = [NSArray arrayWithObjects: @"vmu", nil];
   597         NSOpenPanel *panel = [NSOpenPanel openPanel];
   598         VMULoadValidator *valid = [[VMULoadValidator alloc] autorelease];
   599         [panel setDelegate: valid];
   600         NSInteger result = [panel runModalForDirectory: [NSString stringWithUTF8String: get_gui_path(CONFIG_VMU_PATH)]
   601                file: nil types: array];
   602         if( result == NSOKButton ) {
   603             vmu_filename = [[panel filename] UTF8String];
   604             int idx = vmulist_get_index_by_filename(vmu_filename);
   605             [sender selectItemWithTag: (FIRST_VMU_TAG+idx)];
   606             new_device_class = &vmu_class;
   607             set_gui_path(CONFIG_VMU_PATH, [[panel directory] UTF8String]);
   608         } else {
   609             /* Cancelled - restore previous value */
   610             setDevicePopupSelection( sender, current );
   611             return;
   612         }
   613     } else if( new_device_idx == CREATE_VMU_TAG ) {
   614         NSSavePanel *panel = [NSSavePanel savePanel];
   615         [panel setTitle: NS_("Create VMU")];
   616         [panel setCanCreateDirectories: YES];
   617         [panel setRequiredFileType: @"vmu"];
   618         VMUCreateValidator *valid = [[VMUCreateValidator alloc] autorelease];
   619         [panel setDelegate: valid];
   620         NSInteger result = [panel runModalForDirectory: [NSString stringWithUTF8String: get_gui_path(CONFIG_VMU_PATH)]
   621                file: nil];
   622         if( result == NSFileHandlingPanelOKButton ) {
   623             /* Validator has already created the file by now */
   624             vmu_filename = [[panel filename] UTF8String];
   625             int idx = vmulist_get_index_by_filename(vmu_filename);
   626             [sender selectItemWithTag: (FIRST_VMU_TAG+idx)];
   627             new_device_class = &vmu_class;
   628             set_gui_path(CONFIG_VMU_PATH, [[panel directory] UTF8String]);
   629         } else {
   630             setDevicePopupSelection( sender, current );
   631             return;
   632         }
   633     } else if( new_device_idx >= FIRST_VMU_TAG ) {
   634         vmu_filename = vmulist_get_filename( new_device_idx - FIRST_VMU_TAG );
   635         new_device_class = &vmu_class;
   636     } else if( new_device_idx > 0) {
   637         new_device_class = maple_get_device_classes()[new_device_idx-1];
   638     }
   640     if( current == NULL ? new_device_class == NULL : 
   641         (current->device_class == new_device_class && 
   642                 (!MAPLE_IS_VMU(current) || MAPLE_VMU_HAS_NAME(current, vmu_filename))) ) {
   643         // No change
   644         [key_bindings setDevice: current];
   645         return;
   646     }
   647     if( current != NULL && current->device_class == &controller_class ) {
   648         save_controller[tag] = current->clone(current);
   649     }
   650     if( new_device_class == NULL ) {
   651         maple_detach_device(port,slot);
   652         if( slot == 0 ) {
   653             /* If we detached the top-level dev, any children are automatically detached */
   654             for( i=1; i<=MAPLE_USER_SLOTS; i++ ) {
   655                 [popup[MAPLE_DEVID(port,i)] selectItemWithTag: 0];
   656             }
   657         }
   658     } else {
   659         if( new_device_class == &controller_class && save_controller[tag] != NULL ) {
   660             new_device = save_controller[tag];
   661             save_controller[tag] = NULL;
   662         } else {
   663             new_device = maple_new_device( new_device_class->name );
   664         }
   665         if( MAPLE_IS_VMU(new_device) ) {
   666             /* Remove the VMU from any other attachment point */
   667             for( i=0; i<MAPLE_MAX_DEVICES; i++ ) {
   668                 maple_device_t dev = maple_get_device(MAPLE_DEVID_PORT(i),MAPLE_DEVID_SLOT(i));
   669                 if( dev != NULL && MAPLE_IS_VMU(dev) && MAPLE_VMU_HAS_NAME(dev,vmu_filename) ) {
   670                     maple_detach_device(MAPLE_DEVID_PORT(i),MAPLE_DEVID_SLOT(i));
   671                     [popup[i] selectItemWithTag: 0];
   672                 }
   673             }
   674             MAPLE_SET_VMU_NAME(new_device,vmu_filename);
   675         }
   676         maple_attach_device(new_device,port,slot);
   677     }
   678     [key_bindings setDevice: maple_get_device(port,slot)];
   680     if( slot == 0 ) { /* Change primary */
   681         int max = new_device_class == NULL ? 0 : MAPLE_SLOTS(new_device_class);
   682         for( i=1; i<=MAPLE_USER_SLOTS; i++ ) {
   683             if( i <= max ) {
   684                 [radio[MAPLE_DEVID(port,i)] setEnabled: YES];
   685                 [popup[MAPLE_DEVID(port,i)] setEnabled: YES];
   686             } else {
   687                 [radio[MAPLE_DEVID(port,i)] setEnabled: NO];
   688                 [popup[MAPLE_DEVID(port,i)] setEnabled: NO];
   689             }                
   690         }
   691     }
   692     lxdream_save_config();
   693 }
   694 @end
   696 NSView *cocoa_gui_create_prefs_controller_pane()
   697 {
   698     return [LxdreamPrefsControllerPane new];
   699 }
.