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 1072:d82e04e6d497
prev1069:7e2b65496762
next1297:7e98a164b2d9
author nkeynes
date Tue Jul 21 20:33:21 2009 +1000 (14 years ago)
permissions -rw-r--r--
last change Heavy configuration management refactor
- Configuration groups now take both an on_change event handler and a
default keybinding handler, making most keybinding tasks quite simple
- GUI configuration all merged into a unified model, drastically reducing
the amount of GUI config code.

Bonuses
- OSX now has a hotkey preference pane
- GTK keybinding editor is much more usable
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 LABEL_WIDTH 85
    37 static gboolean cocoa_config_vmulist_hook(vmulist_change_type_t type, int idx, void *data);
    38 /*************************** Top-level controller pane ***********************/
    39 static NSButton *addRadioButton( int port, int sub, int x, int y, id parent )
    40 {
    41     char buf[16];
    43     if( sub == 0 ) {
    44         snprintf( buf, sizeof(buf), _("Port %c."), 'A'+port );
    45     } else {
    46         snprintf( buf, sizeof(buf), _("VMU %d."), sub );
    47     }
    49     NSButton *radio = [[NSButton alloc] initWithFrame: NSMakeRect( x, y, 60, TEXT_HEIGHT )];
    50     [radio setTitle: [NSString stringWithUTF8String: buf]];
    51     [radio setTag: MAPLE_DEVID(port,sub) ];
    52     [radio setButtonType: NSRadioButton];
    53     [radio setAlignment: NSRightTextAlignment];
    54     [radio setTarget: parent];
    55     [radio setAction: @selector(radioChanged:)];
    56     [radio setAutoresizingMask: (NSViewMinYMargin|NSViewMaxXMargin)];
    57     [parent addSubview: radio];
    58     return radio;
    59 }
    61 static void setDevicePopupSelection( NSPopUpButton *popup, maple_device_t device )
    62 {
    63     if( device == NULL ) {
    64         [popup selectItemAtIndex: 0];
    65     } else if( MAPLE_IS_VMU(device) ) {
    66         int idx = vmulist_get_index_by_filename( MAPLE_VMU_NAME(device) );
    67         if( idx == -1 ) {
    68             [popup selectItemAtIndex: 0];
    69         } else {
    70             [popup selectItemWithTag: FIRST_VMU_TAG + idx];
    71         }
    72     } else {
    73         const struct maple_device_class **devices = maple_get_device_classes();
    74         int i;
    75         for( i=0; devices[i] != NULL; i++ ) {
    76             if( devices[i] == device->device_class ) {
    77                 [popup selectItemWithTag: i+1];
    78                 return;
    79             }
    80         }
    81         // Should never get here, but if so...
    82         [popup selectItemAtIndex: 0];
    83     }
    84 }
    86 static void buildDevicePopupMenu( NSPopUpButton *popup, maple_device_t device, BOOL primary )
    87 {
    88     int j;
    89     const struct maple_device_class **devices = maple_get_device_classes();
    91     [popup removeAllItems];
    92     [popup addItemWithTitle: NS_("<empty>")];
    93     [[popup itemAtIndex: 0] setTag: 0];
    94     for( j=0; devices[j] != NULL; j++ ) {
    95         int isPrimaryDevice = devices[j]->flags & MAPLE_TYPE_PRIMARY;
    96         if( primary ? isPrimaryDevice : (!isPrimaryDevice && !MAPLE_IS_VMU_CLASS(devices[j])) ) {
    97             [popup addItemWithTitle: [NSString stringWithUTF8String: devices[j]->name]];
    98             if( device != NULL && device->device_class == devices[j] ) {
    99                 [popup selectItemAtIndex: ([popup numberOfItems]-1)];
   100             }
   101             [[popup itemAtIndex: ([popup numberOfItems]-1)] setTag: (j+1)];
   102         }
   103     }
   105     if( !primary ) {
   106         BOOL vmu_selected = NO;
   107         const char *vmu_name = NULL;
   108         if( device != NULL && MAPLE_IS_VMU(device) ) {
   109             vmu_name = MAPLE_VMU_NAME(device);
   110             if( vmu_name == NULL ) {
   111                 device = NULL;
   112             } else {
   113                 vmu_selected = YES;
   114             }
   115         }
   116         if( [popup numberOfItems] > 0 ) {
   117             [[popup menu] addItem: [NSMenuItem separatorItem]];
   118         }
   120         unsigned int vmu_count = vmulist_get_size();
   121         for( j=0; j<vmu_count; j++ ) {
   122             const char *name = vmulist_get_name(j);
   123             [popup addItemWithTitle: [NSString stringWithUTF8String: name]];
   124             if( vmu_selected && strcmp(vmu_name, vmulist_get_filename(j)) == 0 ) {
   125                 [popup selectItemAtIndex: ([popup numberOfItems]-1)];
   126             }
   127             [[popup itemAtIndex: ([popup numberOfItems]-1)] setTag: FIRST_VMU_TAG + j];
   128         }
   130         [popup addItemWithTitle: NS_("Load VMU...")];
   131         [[popup itemAtIndex: ([popup numberOfItems]-1)] setTag: LOAD_VMU_TAG];
   132         [popup addItemWithTitle: NS_("Create VMU...")];
   133         [[popup itemAtIndex: ([popup numberOfItems]-1)] setTag: CREATE_VMU_TAG];
   134     }
   136 }
   138 static NSPopUpButton *addDevicePopup( int port, int sub, int x, int y, maple_device_t device, BOOL primary, id parent )
   139 {
   140     NSPopUpButton *popup = [[NSPopUpButton alloc] initWithFrame: NSMakeRect(x,y,150,TEXT_HEIGHT) 
   141                                                   pullsDown: NO];
   142     [popup setAutoresizingMask: (NSViewMinYMargin|NSViewMaxXMargin)];
   143     buildDevicePopupMenu(popup,device,primary);
   145     [popup setTarget: parent];
   146     [popup setAction: @selector(deviceChanged:)];
   147     [popup setTag: MAPLE_DEVID(port,sub) ];
   148     [parent addSubview: popup];    
   149     return popup;
   150 }
   152 @interface VMULoadValidator : NSObject
   153 {
   154 }
   155 - (BOOL)panel:(id) sender isValidFilename: (NSString *)filename;
   156 @end
   158 @implementation VMULoadValidator 
   159 - (BOOL)panel:(id) sender isValidFilename: (NSString *)filename
   160 {
   161     const char *c_fn = [filename UTF8String];
   162     vmu_volume_t vol = vmu_volume_load( c_fn );
   163     if( vol != NULL ) {
   164         vmulist_add_vmu(c_fn, vol);
   165         return YES;
   166     } else {
   167         ERROR( "Unable to load VMU file (not a valid VMU)" );
   168         return NO;
   169     }
   170 }
   172 @end
   174 @interface VMUCreateValidator : NSObject
   175 {
   176 }
   177 - (BOOL)panel:(id) sender isValidFilename: (NSString *)filename;
   178 @end
   180 @implementation VMUCreateValidator 
   181 - (BOOL)panel:(id) sender isValidFilename: (NSString *)filename
   182 {
   183     const char *vmu_filename = [filename UTF8String];
   184     int idx = vmulist_create_vmu(vmu_filename, FALSE);
   185     if( idx == -1 ) {
   186         ERROR( "Unable to create file: %s\n", strerror(errno) );
   187         return NO;
   188     } else {
   189         return YES;
   190     }
   191 }
   192 @end
   195 @interface LxdreamPrefsControllerPane: LxdreamPrefsPane
   196 {
   197     struct maple_device *save_controller[MAPLE_MAX_DEVICES];
   198     NSButton *radio[MAPLE_MAX_DEVICES];
   199     NSPopUpButton *popup[MAPLE_MAX_DEVICES];
   200     ConfigurationView *key_bindings;
   201 }
   202 + (LxdreamPrefsControllerPane *)new;
   203 - (void)vmulistChanged: (id)sender;
   204 @end
   206 static gboolean cocoa_config_vmulist_hook(vmulist_change_type_t type, int idx, void *data)
   207 {
   208     LxdreamPrefsControllerPane *pane = (LxdreamPrefsControllerPane *)data;
   209     [pane vmulistChanged: nil];
   210     return TRUE;
   211 }
   213 @implementation LxdreamPrefsControllerPane
   214 + (LxdreamPrefsControllerPane *)new
   215 {
   216     return [[LxdreamPrefsControllerPane alloc] initWithFrame: NSMakeRect(0,0,600,400)];
   217 }
   218 - (id)initWithFrame: (NSRect)frameRect
   219 {
   220     if( [super initWithFrame: frameRect title: NS_("Controllers")] == nil ) {
   221         return nil;
   222     } else {
   223         int i,j;
   224         int y = [self contentHeight] - TEXT_HEIGHT - TEXT_GAP;
   226         memset( radio, 0, sizeof(radio) );
   227         memset( save_controller, 0, sizeof(save_controller) );
   228         NSBox *rule = [[NSBox alloc] initWithFrame: 
   229                 NSMakeRect(210+(TEXT_GAP*3), 1, 1, [self contentHeight] + TEXT_GAP - 2)];
   230         [rule setAutoresizingMask: (NSViewMaxXMargin|NSViewHeightSizable)];
   231         [rule setBoxType: NSBoxSeparator];
   232         [self addSubview: rule];
   234         NSRect bindingFrame = NSMakeRect(210+(TEXT_GAP*4), 0,
   235                    frameRect.size.width - (210+(TEXT_GAP*4)), [self contentHeight] + TEXT_GAP );
   236         NSScrollView *scrollView = [[NSScrollView alloc] initWithFrame: bindingFrame];
   237         key_bindings = [[ConfigurationView alloc] initWithFrame: bindingFrame ];
   238         [key_bindings setLabelWidth: LABEL_WIDTH];
   239         [scrollView setAutoresizingMask: (NSViewWidthSizable|NSViewHeightSizable)];
   240         [scrollView setDocumentView: key_bindings];
   241         [scrollView setDrawsBackground: NO];
   242         [scrollView setHasVerticalScroller: YES];
   243         [scrollView setAutohidesScrollers: YES];
   245         [self addSubview: scrollView];
   246         [key_bindings setDevice: maple_get_device(0,0)];
   248         for( i=0; i<MAPLE_PORTS; i++ ) {
   249             maple_device_t device = maple_get_device(i,0);
   251             radio[i] = addRadioButton(i,0,TEXT_GAP,y,self);
   252             popup[i] = addDevicePopup(i,0,60 + (TEXT_GAP*2),y,device, YES,self);
   253             y -= (TEXT_HEIGHT+TEXT_GAP);
   255             int j,max = device == NULL ? 0 : MAPLE_SLOTS(device->device_class);
   256             for( j=1; j<=MAPLE_USER_SLOTS; j++ ) {
   257                 radio[MAPLE_DEVID(i,j)] = addRadioButton(i, j, TEXT_GAP*2, y, self);
   258                 popup[MAPLE_DEVID(i,j)] = addDevicePopup(i, j, 60 + TEXT_GAP*2, y, maple_get_device(i,j), NO, self);
   259                 y -= (TEXT_HEIGHT+TEXT_GAP);
   260                 if( j > max ) {
   261                     [radio[MAPLE_DEVID(i,j)] setEnabled: NO];
   262                     [popup[MAPLE_DEVID(i,j)] setEnabled: NO];
   263                 }
   264             }
   265         }
   267         [radio[0] setState: NSOnState];
   269         register_vmulist_change_hook(cocoa_config_vmulist_hook, self);
   270         return self;
   271     }
   272 }
   273 - (void)dealloc
   274 {
   275     unregister_vmulist_change_hook(cocoa_config_vmulist_hook,self);
   276     [super dealloc];
   277 }
   278 - (void)vmulistChanged: (id)sender
   279 {
   280     int i;
   281     for( i=FIRST_SECONDARY_DEVICE; i<MAPLE_MAX_DEVICES; i++ ) {
   282         if( popup[i] != NULL ) {
   283             buildDevicePopupMenu(popup[i], maple_get_device(MAPLE_DEVID_PORT(i), MAPLE_DEVID_SLOT(i)), NO );
   284         }
   285     }
   286 }
   287 - (void)radioChanged: (id)sender
   288 {
   289     int tag = [sender tag];
   290     int i;
   291     for( i=0; i<MAPLE_MAX_DEVICES; i++ ) {
   292         if( i != tag && radio[i] != NULL ) {
   293             [radio[i] setState: NSOffState];
   294         }
   295     }
   296     [key_bindings setDevice: maple_get_device(MAPLE_DEVID_PORT(tag),MAPLE_DEVID_SLOT(tag))];
   297 }
   298 - (void)deviceChanged: (id)sender
   299 {
   300     int tag = [sender tag];
   301     int port = MAPLE_DEVID_PORT(tag);
   302     int slot = MAPLE_DEVID_SLOT(tag);
   303     int new_device_idx = [[sender selectedItem] tag], i; 
   304     maple_device_class_t new_device_class = NULL;
   305     const gchar *vmu_filename = NULL;
   307     for( i=0; i<MAPLE_MAX_DEVICES; i++ ) {
   308         if( radio[i] != NULL ) {
   309             if( i == tag ) {
   310                 [radio[i] setState: NSOnState];
   311             } else {
   312                 [radio[i] setState: NSOffState];
   313             }
   314         }
   315     }
   317     maple_device_t current = maple_get_device(port,slot);
   318     maple_device_t new_device = NULL;
   319     if( new_device_idx == LOAD_VMU_TAG ) {
   320         NSArray *array = [NSArray arrayWithObjects: @"vmu", nil];
   321         NSOpenPanel *panel = [NSOpenPanel openPanel];
   322         VMULoadValidator *valid = [[VMULoadValidator alloc] autorelease];
   323         [panel setDelegate: valid];
   324         int result = [panel runModalForDirectory: [NSString stringWithUTF8String: get_gui_path(CONFIG_VMU_PATH)]
   325                file: nil types: array];
   326         if( result == NSOKButton ) {
   327             vmu_filename = [[panel filename] UTF8String];
   328             int idx = vmulist_get_index_by_filename(vmu_filename);
   329             [sender selectItemWithTag: (FIRST_VMU_TAG+idx)];
   330             new_device_class = &vmu_class;
   331             set_gui_path(CONFIG_VMU_PATH, [[panel directory] UTF8String]);
   332         } else {
   333             /* Cancelled - restore previous value */
   334             setDevicePopupSelection( sender, current );
   335             return;
   336         }
   337     } else if( new_device_idx == CREATE_VMU_TAG ) {
   338         NSSavePanel *panel = [NSSavePanel savePanel];
   339         [panel setTitle: NS_("Create VMU")];
   340         [panel setCanCreateDirectories: YES];
   341         [panel setRequiredFileType: @"vmu"];
   342         VMUCreateValidator *valid = [[VMUCreateValidator alloc] autorelease];
   343         [panel setDelegate: valid];
   344         int result = [panel runModalForDirectory: [NSString stringWithUTF8String: get_gui_path(CONFIG_VMU_PATH)]
   345                file: nil];
   346         if( result == NSFileHandlingPanelOKButton ) {
   347             /* Validator has already created the file by now */
   348             vmu_filename = [[panel filename] UTF8String];
   349             int idx = vmulist_get_index_by_filename(vmu_filename);
   350             [sender selectItemWithTag: (FIRST_VMU_TAG+idx)];
   351             new_device_class = &vmu_class;
   352             set_gui_path(CONFIG_VMU_PATH, [[panel directory] UTF8String]);
   353         } else {
   354             setDevicePopupSelection( sender, current );
   355             return;
   356         }
   357     } else if( new_device_idx >= FIRST_VMU_TAG ) {
   358         vmu_filename = vmulist_get_filename( new_device_idx - FIRST_VMU_TAG );
   359         new_device_class = &vmu_class;
   360     } else if( new_device_idx > 0) {
   361         new_device_class = maple_get_device_classes()[new_device_idx-1];
   362     }
   364     if( current == NULL ? new_device_class == NULL : 
   365         (current->device_class == new_device_class && 
   366                 (!MAPLE_IS_VMU(current) || MAPLE_VMU_HAS_NAME(current, vmu_filename))) ) {
   367         // No change
   368         [key_bindings setDevice: current];
   369         return;
   370     }
   371     if( current != NULL && current->device_class == &controller_class ) {
   372         save_controller[tag] = current->clone(current);
   373     }
   374     if( new_device_class == NULL ) {
   375         maple_detach_device(port,slot);
   376         if( slot == 0 ) {
   377             /* If we detached the top-level dev, any children are automatically detached */
   378             for( i=1; i<=MAPLE_USER_SLOTS; i++ ) {
   379                 [popup[MAPLE_DEVID(port,i)] selectItemWithTag: 0];
   380             }
   381         }
   382     } else {
   383         if( new_device_class == &controller_class && save_controller[tag] != NULL ) {
   384             new_device = save_controller[tag];
   385             save_controller[tag] = NULL;
   386         } else {
   387             new_device = maple_new_device( new_device_class->name );
   388         }
   389         if( MAPLE_IS_VMU(new_device) ) {
   390             /* Remove the VMU from any other attachment point */
   391             for( i=0; i<MAPLE_MAX_DEVICES; i++ ) {
   392                 maple_device_t dev = maple_get_device(MAPLE_DEVID_PORT(i),MAPLE_DEVID_SLOT(i));
   393                 if( dev != NULL && MAPLE_IS_VMU(dev) && MAPLE_VMU_HAS_NAME(dev,vmu_filename) ) {
   394                     maple_detach_device(MAPLE_DEVID_PORT(i),MAPLE_DEVID_SLOT(i));
   395                     [popup[i] selectItemWithTag: 0];
   396                 }
   397             }
   398             MAPLE_SET_VMU_NAME(new_device,vmu_filename);
   399         }
   400         maple_attach_device(new_device,port,slot);
   401     }
   402     [key_bindings setDevice: maple_get_device(port,slot)];
   404     if( slot == 0 ) { /* Change primary */
   405         int max = new_device_class == NULL ? 0 : MAPLE_SLOTS(new_device_class);
   406         for( i=1; i<=MAPLE_USER_SLOTS; i++ ) {
   407             if( i <= max ) {
   408                 [radio[MAPLE_DEVID(port,i)] setEnabled: YES];
   409                 [popup[MAPLE_DEVID(port,i)] setEnabled: YES];
   410             } else {
   411                 [radio[MAPLE_DEVID(port,i)] setEnabled: NO];
   412                 [popup[MAPLE_DEVID(port,i)] setEnabled: NO];
   413             }                
   414         }
   415     }
   416     lxdream_save_config();
   417 }
   418 @end
   420 NSView *cocoa_gui_create_prefs_controller_pane()
   421 {
   422     return [LxdreamPrefsControllerPane new];
   423 }
.