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 1034:7044e01148f0
prev964:f2f3c7612d06
next1036:af7b0c5905dd
author nkeynes
date Wed Jun 24 02:41:12 2009 +0000 (13 years ago)
permissions -rw-r--r--
last change Add initial VMU support
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 "display.h"
    22 #include "maple/maple.h"
    23 #include "vmu/vmulist.h"
    25 #include <glib/gstrfuncs.h>
    27 #define FIRST_SECONDARY_DEVICE MAPLE_PORTS
    29 #define FIRST_VMU_TAG 0x1000
    30 #define LOAD_VMU_TAG -1
    31 #define CREATE_VMU_TAG -2
    33 #define KEYBINDING_SIZE 110
    35 static void cocoa_config_keysym_hook(void *data, const gchar *keysym);
    36 static gboolean cocoa_config_vmulist_hook(vmulist_change_type_t type, int idx, void *data);
    38 @interface KeyBindingEditor (Private)
    39 - (void)updateKeysym: (const gchar *)sym;
    40 @end
    42 @implementation KeyBindingEditor
    43 - (id)init
    44 {
    45     self = [super init];
    46     isPrimed = NO;
    47     lastValue = nil;
    48     [self setFieldEditor: YES];
    49     [self setEditable: FALSE];
    50     return self;
    51 }
    52 - (void)dealloc
    53 {
    54     if( lastValue != nil ) {
    55         [lastValue release];
    56         lastValue = nil;
    57     }
    58     [super dealloc];
    59 }
    60 - (void)setPrimed: (BOOL)primed
    61 {
    62     if( primed != isPrimed ) {
    63         isPrimed = primed;
    64         if( primed ) {
    65             lastValue = [[NSString stringWithString: [self string]] retain];
    66             [self setString: @"<press key>"];
    67             input_set_keysym_hook(cocoa_config_keysym_hook, self);            
    68         } else {
    69             [lastValue release];
    70             lastValue = nil;
    71             input_set_keysym_hook(NULL,NULL);
    72         }
    73     }
    74 }
    75 - (void)resignFirstResponder
    76 {
    77     if( isPrimed ) {
    78         [self setString: lastValue];
    79         [self setPrimed: NO];
    80     }
    81     [super resignFirstResponder];
    82 }
    83 - (void)fireBindingChanged
    84 {
    85     id delegate = [self delegate];
    86     if( delegate != nil && [delegate respondsToSelector:@selector(textDidChange:)] ) {
    87         [delegate textDidChange: [NSNotification notificationWithName: NSTextDidChangeNotification object: self]];
    88     }
    89 }
    91 - (void)updateKeysym: (const gchar *)sym
    92 {
    93     if( sym != NULL ) {
    94         [self setString: [NSString stringWithCString: sym]];
    95         [self setPrimed: NO];
    96         [self fireBindingChanged];
    97     }
    98 }
    99 - (void)updateMousesym: (int)button
   100 {
   101     gchar *keysym = input_keycode_to_keysym( &system_mouse_driver, (button+1) );
   102     if( keysym != NULL ) {
   103         [self updateKeysym: keysym ];
   104         g_free(keysym);
   105     }
   106 }
   107 - (void)keyPressed: (int)keycode
   108 {
   109     gchar *keysym = input_keycode_to_keysym(NULL, keycode);
   110     if( keysym != NULL ) {
   111         [self updateKeysym: keysym];
   112         g_free(keysym);
   113     }
   114 }
   115 - (void)insertText:(id)string
   116 {
   117     // Do nothing
   118 }
   119 - (void)mouseDown: (NSEvent *)event
   120 {
   121     if( isPrimed ) {
   122         [self updateMousesym: 0];
   123     } else {
   124         [self setPrimed: YES];
   125         [super mouseDown: event];
   126     }
   127 }
   128 - (void)rightMouseDown: (NSEvent *)event
   129 {
   130     if( isPrimed ) {
   131         [self updateMousesym: 1];
   132     }
   133 }
   134 - (void)otherMouseDown: (NSEvent *)event
   135 {
   136     if( isPrimed ) {
   137         [self updateMousesym: [event buttonNumber]];
   138     }
   139 }
   140 - (void)keyDown: (NSEvent *) event
   141 {
   142     NSString *chars = [event characters];
   143     if( isPrimed ) {
   144         if( chars != NULL && [chars length] == 1 && [chars characterAtIndex: 0] == 27 ) {
   145             // Escape char = abort change
   146             [self setString: lastValue];
   147             [self setPrimed: NO];
   148         } else {
   149             [self keyPressed: ([event keyCode]+1)];
   150         }
   151     } else {
   152         if( chars != NULL && [chars length] == 1 ) {
   153             int ch = [chars characterAtIndex: 0];
   154             switch( ch ) {
   155             case 0x7F:
   156                 [self setString: @""]; 
   157                 [self fireBindingChanged];
   158                 break;
   159             case '\r':
   160                 [self setPrimed: YES];
   161                 break;
   162             default:
   163                 [super keyDown: event];
   164                 break;
   165             }
   166         } else {
   167             [super keyDown: event];
   168         }
   169     }
   170 }
   171 - (void)flagsChanged: (NSEvent *) event
   172 {
   173     if( isPrimed ) {
   174         [self keyPressed: ([event keyCode]+1)];
   175     }
   176     [super flagsChanged: event];
   177 }
   178 @end
   180 static void cocoa_config_keysym_hook(void *data, const gchar *keysym)
   181 {
   182     KeyBindingEditor *editor = (KeyBindingEditor *)data;
   183     [editor updateKeysym: keysym];
   184 }
   187 @implementation KeyBindingField
   188 @end
   190 /*************************** Key-binding sub-view ***********************/
   192 #define MAX_KEY_BINDINGS 32
   194 @interface ControllerKeyBindingView : NSView
   195 {
   196     maple_device_t device;
   197     NSTextField *field[MAX_KEY_BINDINGS][2];
   198 }
   199 - (id)initWithFrame: (NSRect)frameRect;
   200 - (void)setDevice: (maple_device_t)device;
   201 @end
   203 @implementation ControllerKeyBindingView
   204 - (id)initWithFrame: (NSRect)frameRect
   205 {
   206     if( [super initWithFrame: frameRect] == nil ) {
   207         return nil;
   208     } else {
   209         device = NULL;
   210         return self;
   211     }
   212 }
   213 - (BOOL)isFlipped
   214 {
   215     return YES;
   216 }
   217 - (void)removeSubviews
   218 {
   219     [[self subviews] makeObjectsPerformSelector: @selector(removeFromSuperview)];
   220 }
   221 - (void)controlTextDidChange: (NSNotification *)notify
   222 {
   223     const gchar *p = NULL;
   224     int binding = [[notify object] tag];
   225     NSString *val1 = [field[binding][0] stringValue];
   226     if( field[binding][1] == NULL ) {
   227         p = [val1 UTF8String];
   228     } else {
   229         NSString *val2 = [field[binding][1] stringValue];
   230         char buf[ [val1 length] + [val2 length] + 2 ];
   232         if( [val1 length] == 0 ) {
   233             if( [val2 length] != 0 ) {
   234                 p = [val2 UTF8String];
   235             }
   236         } else if( [val2 length] == 0 ) {
   237             p = [val1 UTF8String];
   238         } else {
   239             sprintf( buf, "%s,%s", [val1 UTF8String], [val2 UTF8String] );
   240             p = buf;
   241         }
   242     }
   243     maple_set_device_config_value( device, binding, p ); 
   244     lxdream_save_config();
   245 }
   246 - (void)setDevice: (maple_device_t)newDevice
   247 {
   248     device = newDevice;
   249     [self removeSubviews];
   250     if( device != NULL && !MAPLE_IS_VMU(device) ) {
   251         lxdream_config_entry_t config = maple_get_device_config(device);
   252         if( config != NULL ) {
   253             int count, i, y, x;
   255             for( count=0; config[count].key != NULL; count++ );
   256             x = TEXT_GAP;
   257             NSSize size = NSMakeSize(85+KEYBINDING_SIZE*2+TEXT_GAP*4, count*(TEXT_HEIGHT+TEXT_GAP)+TEXT_GAP);
   258             [self setFrameSize: size];
   259             [self scrollRectToVisible: NSMakeRect(0,0,1,1)]; 
   260             y = TEXT_GAP;
   261             for( i=0; config[i].key != NULL; i++ ) {
   262                 /* Add label */
   263                 NSRect frame = NSMakeRect(x, y + 2, 85, LABEL_HEIGHT);
   264                 NSTextField *label = cocoa_gui_add_label(self, NS_(config[i].label), frame);
   265                 [label setAlignment: NSRightTextAlignment];
   267                 switch(config[i].type) {
   268                 case CONFIG_TYPE_KEY:
   269                     frame = NSMakeRect( x + 85 + TEXT_GAP, y, KEYBINDING_SIZE, TEXT_HEIGHT);
   270                     field[i][0] = [[KeyBindingField alloc] initWithFrame: frame];
   271                     [field[i][0] setAutoresizingMask: (NSViewMinYMargin|NSViewMaxXMargin)];
   272                     [field[i][0] setTag: i];
   273                     [field[i][0] setDelegate: self];
   274                     [self addSubview: field[i][0]];
   276                     frame = NSMakeRect( x + 85 + KEYBINDING_SIZE + (TEXT_GAP*2), y, KEYBINDING_SIZE, TEXT_HEIGHT);
   277                     field[i][1] = [[KeyBindingField alloc] initWithFrame: frame];
   278                     [field[i][1] setAutoresizingMask: (NSViewMinYMargin|NSViewMaxXMargin)];
   279                     [field[i][1] setTag: i];
   280                     [field[i][1] setDelegate: self];
   281                     [self addSubview: field[i][1]];
   283                     if( config[i].value != NULL ) {
   284                         gchar **parts = g_strsplit(config[i].value,",",3);
   285                         if( parts[0] != NULL ) {
   286                             [field[i][0] setStringValue: [NSString stringWithCString: parts[0]]];
   287                             if( parts[1] != NULL ) {
   288                                 [field[i][1] setStringValue: [NSString stringWithCString: parts[1]]];
   289                             }
   290                         }
   291                         g_strfreev(parts);
   292                     }
   293                     break;
   294                 case CONFIG_TYPE_FILE:
   295                 case CONFIG_TYPE_PATH:
   296                     frame = NSMakeRect( x + 85 + TEXT_GAP, y, KEYBINDING_SIZE*2+TEXT_GAP, TEXT_HEIGHT);
   297                     field[i][0] = [[NSTextField alloc] initWithFrame: frame];
   298                     [field[i][0] setAutoresizingMask: (NSViewMinYMargin|NSViewMaxXMargin)];
   299                     [field[i][0] setTag: i];
   300                     [field[i][0] setDelegate: self];
   301                     [self addSubview: field[i][0]];
   302                     if( config[i].value != NULL ) {
   303                         [field[i][0] setStringValue: [NSString stringWithCString: config[i].value]];
   304                     }
   305                     field[i][1] = NULL;
   306                 } 
   307                 y += (TEXT_HEIGHT + TEXT_GAP);
   308             }
   309         } else {
   310             [self setFrameSize: NSMakeSize(100,TEXT_HEIGHT+TEXT_GAP) ];
   311         }
   312     } else {
   313         [self setFrameSize: NSMakeSize(100,TEXT_HEIGHT+TEXT_GAP) ];
   314     }
   315 }
   316 @end
   318 /*************************** Top-level controller pane ***********************/
   319 static NSButton *addRadioButton( int port, int sub, int x, int y, id parent )
   320 {
   321     char buf[16];
   323     if( sub == 0 ) {
   324         snprintf( buf, sizeof(buf), _("Port %c."), 'A'+port );
   325     } else {
   326         snprintf( buf, sizeof(buf), _("VMU %d."), sub );
   327     }
   329     NSButton *radio = [[NSButton alloc] initWithFrame: NSMakeRect( x, y, 60, TEXT_HEIGHT )];
   330     [radio setTitle: [NSString stringWithUTF8String: buf]];
   331     [radio setTag: MAPLE_DEVID(port,sub) ];
   332     [radio setButtonType: NSRadioButton];
   333     [radio setAlignment: NSRightTextAlignment];
   334     [radio setTarget: parent];
   335     [radio setAction: @selector(radioChanged:)];
   336     [radio setAutoresizingMask: (NSViewMinYMargin|NSViewMaxXMargin)];
   337     [parent addSubview: radio];
   338     return radio;
   339 }
   341 static void setDevicePopupSelection( NSPopUpButton *popup, maple_device_t device )
   342 {
   343     if( device == NULL ) {
   344         [popup selectItemAtIndex: 0];
   345     } else if( MAPLE_IS_VMU(device) ) {
   346         int idx = vmulist_get_index_by_filename( MAPLE_VMU_NAME(device) );
   347         if( idx == -1 ) {
   348             [popup selectItemAtIndex: 0];
   349         } else {
   350             [popup selectItemWithTag: FIRST_VMU_TAG + idx];
   351         }
   352     } else {
   353         const struct maple_device_class **devices = maple_get_device_classes();
   354         int i;
   355         for( i=0; devices[i] != NULL; i++ ) {
   356             if( devices[i] == device->device_class ) {
   357                 [popup selectItemWithTag: i+1];
   358                 return;
   359             }
   360         }
   361         // Should never get here, but if so...
   362         [popup selectItemAtIndex: 0];
   363     }
   364 }
   366 static void buildDevicePopupMenu( NSPopUpButton *popup, maple_device_t device, BOOL primary )
   367 {
   368     int j;
   369     const struct maple_device_class **devices = maple_get_device_classes();
   371     [popup removeAllItems];
   372     [popup addItemWithTitle: NS_("<empty>")];
   373     [[popup itemAtIndex: 0] setTag: 0];
   374     for( j=0; devices[j] != NULL; j++ ) {
   375         int isPrimaryDevice = devices[j]->flags & MAPLE_TYPE_PRIMARY;
   376         if( primary ? isPrimaryDevice : (!isPrimaryDevice && !MAPLE_IS_VMU_CLASS(devices[j])) ) {
   377             [popup addItemWithTitle: [NSString stringWithUTF8String: devices[j]->name]];
   378             if( device != NULL && device->device_class == devices[j] ) {
   379                 [popup selectItemAtIndex: ([popup numberOfItems]-1)];
   380             }
   381             [[popup itemAtIndex: ([popup numberOfItems]-1)] setTag: (j+1)];
   382         }
   383     }
   385     if( !primary ) {
   386         BOOL vmu_selected = NO;
   387         const char *vmu_name;
   388         if( device != NULL && MAPLE_IS_VMU(device) ) {
   389             vmu_selected = YES;
   390             vmu_name = MAPLE_VMU_NAME(device);
   391         }
   392         if( [popup numberOfItems] > 0 ) {
   393             [[popup menu] addItem: [NSMenuItem separatorItem]];
   394         }
   396         unsigned int vmu_count = vmulist_get_size();
   397         for( j=0; j<vmu_count; j++ ) {
   398             const char *name = vmulist_get_name(j);
   399             [popup addItemWithTitle: [NSString stringWithUTF8String: name]];
   400             if( vmu_selected && strcmp(vmu_name, vmulist_get_filename(j)) == 0 ) {
   401                 [popup selectItemAtIndex: ([popup numberOfItems]-1)];
   402             }
   403             [[popup itemAtIndex: ([popup numberOfItems]-1)] setTag: FIRST_VMU_TAG + j];
   404         }
   406         [popup addItemWithTitle: NS_("Load VMU...")];
   407         [[popup itemAtIndex: ([popup numberOfItems]-1)] setTag: LOAD_VMU_TAG];
   408         [popup addItemWithTitle: NS_("Create VMU...")];
   409         [[popup itemAtIndex: ([popup numberOfItems]-1)] setTag: CREATE_VMU_TAG];
   410     }
   412 }
   414 static NSPopUpButton *addDevicePopup( int port, int sub, int x, int y, maple_device_t device, BOOL primary, id parent )
   415 {
   416     NSPopUpButton *popup = [[NSPopUpButton alloc] initWithFrame: NSMakeRect(x,y,150,TEXT_HEIGHT) 
   417                                                   pullsDown: NO];
   418     [popup setAutoresizingMask: (NSViewMinYMargin|NSViewMaxXMargin)];
   419     buildDevicePopupMenu(popup,device,primary);
   421     [popup setTarget: parent];
   422     [popup setAction: @selector(deviceChanged:)];
   423     [popup setTag: MAPLE_DEVID(port,sub) ];
   424     [parent addSubview: popup];    
   425     return popup;
   426 }
   428 @interface VMULoadValidator : NSObject
   429 {
   430 }
   431 - (BOOL)panel:(id) sender isValidFilename: (NSString *)filename;
   432 @end
   434 @implementation VMULoadValidator 
   435 - (BOOL)panel:(id) sender isValidFilename: (NSString *)filename
   436 {
   437     const char *c_fn = [filename UTF8String];
   438     vmu_volume_t vol = vmu_volume_load( c_fn );
   439     if( vol != NULL ) {
   440         vmulist_add_vmu(c_fn, vol);
   441         return YES;
   442     } else {
   443         ERROR( "Unable to load VMU file (not a valid VMU)" );
   444         return NO;
   445     }
   446 }
   448 @end
   450 @interface VMUCreateValidator : NSObject
   451 {
   452 }
   453 - (BOOL)panel:(id) sender isValidFilename: (NSString *)filename;
   454 @end
   456 @implementation VMUCreateValidator 
   457 - (BOOL)panel:(id) sender isValidFilename: (NSString *)filename
   458 {
   459     const char *vmu_filename = [filename UTF8String];
   460     int idx = vmulist_create_vmu(vmu_filename, FALSE);
   461     if( idx == -1 ) {
   462         ERROR( "Unable to create file: %s\n", strerror(errno) );
   463         return NO;
   464     } else {
   465         return YES;
   466     }
   467 }
   468 @end
   471 @interface LxdreamPrefsControllerPane: LxdreamPrefsPane
   472 {
   473     struct maple_device *save_controller[MAPLE_MAX_DEVICES];
   474     NSButton *radio[MAPLE_MAX_DEVICES];
   475     NSPopUpButton *popup[MAPLE_MAX_DEVICES];
   476     ControllerKeyBindingView *key_bindings;
   477 }
   478 + (LxdreamPrefsControllerPane *)new;
   479 - (void)vmulistChanged: (id)sender;
   480 @end
   482 static gboolean cocoa_config_vmulist_hook(vmulist_change_type_t type, int idx, void *data)
   483 {
   484     LxdreamPrefsControllerPane *pane = (LxdreamPrefsControllerPane *)data;
   485     [pane vmulistChanged: nil];
   486     return TRUE;
   487 }
   489 @implementation LxdreamPrefsControllerPane
   490 + (LxdreamPrefsControllerPane *)new
   491 {
   492     return [[LxdreamPrefsControllerPane alloc] initWithFrame: NSMakeRect(0,0,600,400)];
   493 }
   494 - (id)initWithFrame: (NSRect)frameRect
   495 {
   496     if( [super initWithFrame: frameRect title: NS_("Controllers")] == nil ) {
   497         return nil;
   498     } else {
   499         int i,j;
   500         int y = [self contentHeight] - TEXT_HEIGHT - TEXT_GAP;
   502         memset( radio, 0, sizeof(radio) );
   503         memset( save_controller, 0, sizeof(save_controller) );
   504         NSBox *rule = [[NSBox alloc] initWithFrame: 
   505                 NSMakeRect(210+(TEXT_GAP*3), 1, 1, [self contentHeight] + TEXT_GAP - 2)];
   506         [rule setAutoresizingMask: (NSViewMaxXMargin|NSViewHeightSizable)];
   507         [rule setBoxType: NSBoxSeparator];
   508         [self addSubview: rule];
   510         NSRect bindingFrame = NSMakeRect(210+(TEXT_GAP*4), 0,
   511                    frameRect.size.width - (210+(TEXT_GAP*4)), [self contentHeight] + TEXT_GAP );
   512         NSScrollView *scrollView = [[NSScrollView alloc] initWithFrame: bindingFrame];
   513         key_bindings = [[ControllerKeyBindingView alloc] initWithFrame: bindingFrame ];
   514         [scrollView setAutoresizingMask: (NSViewWidthSizable|NSViewHeightSizable)];
   515         [scrollView setDocumentView: key_bindings];
   516         [scrollView setDrawsBackground: NO];
   517         [scrollView setHasVerticalScroller: YES];
   518         [scrollView setAutohidesScrollers: YES];
   520         [self addSubview: scrollView];
   521         [key_bindings setDevice: maple_get_device(0,0)];
   523         for( i=0; i<MAPLE_PORTS; i++ ) {
   524             maple_device_t device = maple_get_device(i,0);
   526             radio[i] = addRadioButton(i,0,TEXT_GAP,y,self);
   527             popup[i] = addDevicePopup(i,0,60 + (TEXT_GAP*2),y,device, YES,self);
   528             y -= (TEXT_HEIGHT+TEXT_GAP);
   530             int j,max = device == NULL ? 0 : MAPLE_SLOTS(device->device_class);
   531             for( j=1; j<=MAPLE_USER_SLOTS; j++ ) {
   532                 radio[MAPLE_DEVID(i,j)] = addRadioButton(i, j, TEXT_GAP*2, y, self);
   533                 popup[MAPLE_DEVID(i,j)] = addDevicePopup(i, j, 60 + TEXT_GAP*2, y, maple_get_device(i,j), NO, self);
   534                 y -= (TEXT_HEIGHT+TEXT_GAP);
   535                 if( j > max ) {
   536                     [radio[MAPLE_DEVID(i,j)] setEnabled: NO];
   537                     [popup[MAPLE_DEVID(i,j)] setEnabled: NO];
   538                 }
   539             }
   540         }
   542         [radio[0] setState: NSOnState];
   544         register_vmulist_change_hook(cocoa_config_vmulist_hook, self);
   545         return self;
   546     }
   547 }
   548 - (void)dealloc
   549 {
   550     unregister_vmulist_change_hook(cocoa_config_vmulist_hook,self);
   551     [super dealloc];
   552 }
   553 - (void)vmulistChanged: (id)sender
   554 {
   555     int i;
   556     for( i=FIRST_SECONDARY_DEVICE; i<MAPLE_MAX_DEVICES; i++ ) {
   557         if( popup[i] != NULL ) {
   558             buildDevicePopupMenu(popup[i], maple_get_device(MAPLE_DEVID_PORT(i), MAPLE_DEVID_SLOT(i)), NO );
   559         }
   560     }
   561 }
   562 - (void)radioChanged: (id)sender
   563 {
   564     int tag = [sender tag];
   565     int i;
   566     for( i=0; i<MAPLE_MAX_DEVICES; i++ ) {
   567         if( i != tag && radio[i] != NULL ) {
   568             [radio[i] setState: NSOffState];
   569         }
   570     }
   571     [key_bindings setDevice: maple_get_device(MAPLE_DEVID_PORT(tag),MAPLE_DEVID_SLOT(tag))];
   572 }
   573 - (void)deviceChanged: (id)sender
   574 {
   575     int tag = [sender tag];
   576     int port = MAPLE_DEVID_PORT(tag);
   577     int slot = MAPLE_DEVID_SLOT(tag);
   578     int new_device_idx = [[sender selectedItem] tag], i; 
   579     maple_device_class_t new_device_class = NULL;
   580     const gchar *vmu_filename = NULL;
   582     for( i=0; i<MAPLE_MAX_DEVICES; i++ ) {
   583         if( radio[i] != NULL ) {
   584             if( i == tag ) {
   585                 [radio[i] setState: NSOnState];
   586             } else {
   587                 [radio[i] setState: NSOffState];
   588             }
   589         }
   590     }
   592     maple_device_t current = maple_get_device(port,slot);
   593     maple_device_t new_device = NULL;
   594     if( new_device_idx == LOAD_VMU_TAG ) {
   595         NSArray *array = [NSArray arrayWithObjects: @"vmu", nil];
   596         NSOpenPanel *panel = [NSOpenPanel openPanel];
   597         VMULoadValidator *valid = [[VMULoadValidator alloc] autorelease];
   598         [panel setDelegate: valid];
   599         NSInteger result = [panel runModalForDirectory: [NSString stringWithUTF8String: lxdream_get_config_value(CONFIG_VMU_PATH)]
   600                file: nil types: array];
   601         if( result == NSOKButton ) {
   602             vmu_filename = [[panel filename] UTF8String];
   603             int idx = vmulist_get_index_by_filename(vmu_filename);
   604             [sender selectItemWithTag: (FIRST_VMU_TAG+idx)];
   605             new_device_class = &vmu_class;
   606         } else {
   607             /* Cancelled - restore previous value */
   608             setDevicePopupSelection( sender, current );
   609             return;
   610         }
   611     } else if( new_device_idx == CREATE_VMU_TAG ) {
   612         NSSavePanel *panel = [NSSavePanel savePanel];
   613         [panel setTitle: NS_("Create VMU")];
   614         [panel setCanCreateDirectories: YES];
   615         [panel setRequiredFileType: @"vmu"];
   616         VMUCreateValidator *valid = [[VMUCreateValidator alloc] autorelease];
   617         [panel setDelegate: valid];
   618         NSInteger result = [panel runModalForDirectory: [NSString stringWithUTF8String: lxdream_get_config_value(CONFIG_VMU_PATH)]
   619                file: nil];
   620         if( result == NSFileHandlingPanelOKButton ) {
   621             /* Validator has already created the file by now */
   622             vmu_filename = [[panel filename] UTF8String];
   623             int idx = vmulist_get_index_by_filename(vmu_filename);
   624             [sender selectItemWithTag: (FIRST_VMU_TAG+idx)];
   625             new_device_class = &vmu_class;
   626         } else {
   627             setDevicePopupSelection( sender, current );
   628             return;
   629         }
   630     } else if( new_device_idx >= FIRST_VMU_TAG ) {
   631         vmu_filename = vmulist_get_filename( new_device_idx - FIRST_VMU_TAG );
   632         new_device_class = &vmu_class;
   633     } else if( new_device_idx > 0) {
   634         new_device_class = maple_get_device_classes()[new_device_idx-1];
   635     }
   637     if( current == NULL ? new_device_class == NULL : 
   638         (current->device_class == new_device_class && 
   639                 (!MAPLE_IS_VMU(current) || MAPLE_VMU_HAS_NAME(current, vmu_filename))) ) {
   640         // No change
   641         [key_bindings setDevice: current];
   642         return;
   643     }
   644     if( current != NULL && current->device_class == &controller_class ) {
   645         save_controller[tag] = current->clone(current);
   646     }
   647     if( new_device_class == NULL ) {
   648         maple_detach_device(port,slot);
   649     } else {
   650         if( new_device_class == &controller_class && save_controller[tag] != NULL ) {
   651             new_device = save_controller[tag];
   652             save_controller[tag] = NULL;
   653         } else {
   654             new_device = maple_new_device( new_device_class->name );
   655         }
   656         if( MAPLE_IS_VMU(new_device) ) {
   657             MAPLE_SET_VMU_NAME(new_device,vmu_filename);
   658         }
   659         maple_attach_device(new_device,port,slot);
   660     }
   661     [key_bindings setDevice: maple_get_device(port,slot)];
   663     if( slot == 0 ) { /* Change primary */
   664         int max = new_device_class == NULL ? 0 : MAPLE_SLOTS(new_device_class);
   665         for( i=1; i<=MAPLE_USER_SLOTS; i++ ) {
   666             if( i <= max ) {
   667                 [radio[MAPLE_DEVID(port,i)] setEnabled: YES];
   668                 [popup[MAPLE_DEVID(port,i)] setEnabled: YES];
   669             } else {
   670                 [radio[MAPLE_DEVID(port,i)] setEnabled: NO];
   671                 [popup[MAPLE_DEVID(port,i)] setEnabled: NO];
   672             }                
   673         }
   674     }
   675     lxdream_save_config();
   676 }
   677 @end
   679 NSView *cocoa_gui_create_prefs_controller_pane()
   680 {
   681     return [LxdreamPrefsControllerPane new];
   682 }
.