nkeynes@964: /** nkeynes@964: * $Id$ nkeynes@964: * nkeynes@964: * Construct and manage the controller configuration pane nkeynes@964: * nkeynes@964: * Copyright (c) 2008 Nathan Keynes. nkeynes@964: * nkeynes@964: * This program is free software; you can redistribute it and/or modify nkeynes@964: * it under the terms of the GNU General Public License as published by nkeynes@964: * the Free Software Foundation; either version 2 of the License, or nkeynes@964: * (at your option) any later version. nkeynes@964: * nkeynes@964: * This program is distributed in the hope that it will be useful, nkeynes@964: * but WITHOUT ANY WARRANTY; without even the implied warranty of nkeynes@964: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the nkeynes@964: * GNU General Public License for more details. nkeynes@964: */ nkeynes@964: nkeynes@964: #include "cocoaui.h" nkeynes@964: #include "config.h" nkeynes@964: #include "display.h" nkeynes@964: #include "maple/maple.h" nkeynes@1034: #include "vmu/vmulist.h" nkeynes@964: nkeynes@964: #include nkeynes@964: nkeynes@1034: #define FIRST_SECONDARY_DEVICE MAPLE_PORTS nkeynes@1034: nkeynes@1034: #define FIRST_VMU_TAG 0x1000 nkeynes@1034: #define LOAD_VMU_TAG -1 nkeynes@1034: #define CREATE_VMU_TAG -2 nkeynes@964: nkeynes@964: #define KEYBINDING_SIZE 110 nkeynes@964: nkeynes@964: static void cocoa_config_keysym_hook(void *data, const gchar *keysym); nkeynes@1034: static gboolean cocoa_config_vmulist_hook(vmulist_change_type_t type, int idx, void *data); nkeynes@964: nkeynes@964: @interface KeyBindingEditor (Private) nkeynes@964: - (void)updateKeysym: (const gchar *)sym; nkeynes@964: @end nkeynes@964: nkeynes@964: @implementation KeyBindingEditor nkeynes@964: - (id)init nkeynes@964: { nkeynes@964: self = [super init]; nkeynes@964: isPrimed = NO; nkeynes@964: lastValue = nil; nkeynes@964: [self setFieldEditor: YES]; nkeynes@964: [self setEditable: FALSE]; nkeynes@964: return self; nkeynes@964: } nkeynes@964: - (void)dealloc nkeynes@964: { nkeynes@964: if( lastValue != nil ) { nkeynes@964: [lastValue release]; nkeynes@964: lastValue = nil; nkeynes@964: } nkeynes@964: [super dealloc]; nkeynes@964: } nkeynes@964: - (void)setPrimed: (BOOL)primed nkeynes@964: { nkeynes@964: if( primed != isPrimed ) { nkeynes@964: isPrimed = primed; nkeynes@964: if( primed ) { nkeynes@964: lastValue = [[NSString stringWithString: [self string]] retain]; nkeynes@964: [self setString: @""]; nkeynes@964: input_set_keysym_hook(cocoa_config_keysym_hook, self); nkeynes@964: } else { nkeynes@964: [lastValue release]; nkeynes@964: lastValue = nil; nkeynes@964: input_set_keysym_hook(NULL,NULL); nkeynes@964: } nkeynes@964: } nkeynes@964: } nkeynes@964: - (void)resignFirstResponder nkeynes@964: { nkeynes@964: if( isPrimed ) { nkeynes@964: [self setString: lastValue]; nkeynes@964: [self setPrimed: NO]; nkeynes@964: } nkeynes@964: [super resignFirstResponder]; nkeynes@964: } nkeynes@964: - (void)fireBindingChanged nkeynes@964: { nkeynes@964: id delegate = [self delegate]; nkeynes@964: if( delegate != nil && [delegate respondsToSelector:@selector(textDidChange:)] ) { nkeynes@964: [delegate textDidChange: [NSNotification notificationWithName: NSTextDidChangeNotification object: self]]; nkeynes@964: } nkeynes@964: } nkeynes@964: nkeynes@964: - (void)updateKeysym: (const gchar *)sym nkeynes@964: { nkeynes@964: if( sym != NULL ) { nkeynes@964: [self setString: [NSString stringWithCString: sym]]; nkeynes@964: [self setPrimed: NO]; nkeynes@964: [self fireBindingChanged]; nkeynes@964: } nkeynes@964: } nkeynes@964: - (void)updateMousesym: (int)button nkeynes@964: { nkeynes@964: gchar *keysym = input_keycode_to_keysym( &system_mouse_driver, (button+1) ); nkeynes@964: if( keysym != NULL ) { nkeynes@964: [self updateKeysym: keysym ]; nkeynes@964: g_free(keysym); nkeynes@964: } nkeynes@964: } nkeynes@964: - (void)keyPressed: (int)keycode nkeynes@964: { nkeynes@964: gchar *keysym = input_keycode_to_keysym(NULL, keycode); nkeynes@964: if( keysym != NULL ) { nkeynes@964: [self updateKeysym: keysym]; nkeynes@964: g_free(keysym); nkeynes@964: } nkeynes@964: } nkeynes@964: - (void)insertText:(id)string nkeynes@964: { nkeynes@964: // Do nothing nkeynes@964: } nkeynes@964: - (void)mouseDown: (NSEvent *)event nkeynes@964: { nkeynes@964: if( isPrimed ) { nkeynes@964: [self updateMousesym: 0]; nkeynes@964: } else { nkeynes@964: [self setPrimed: YES]; nkeynes@964: [super mouseDown: event]; nkeynes@964: } nkeynes@964: } nkeynes@964: - (void)rightMouseDown: (NSEvent *)event nkeynes@964: { nkeynes@964: if( isPrimed ) { nkeynes@964: [self updateMousesym: 1]; nkeynes@964: } nkeynes@964: } nkeynes@964: - (void)otherMouseDown: (NSEvent *)event nkeynes@964: { nkeynes@964: if( isPrimed ) { nkeynes@964: [self updateMousesym: [event buttonNumber]]; nkeynes@964: } nkeynes@964: } nkeynes@964: - (void)keyDown: (NSEvent *) event nkeynes@964: { nkeynes@964: NSString *chars = [event characters]; nkeynes@964: if( isPrimed ) { nkeynes@964: if( chars != NULL && [chars length] == 1 && [chars characterAtIndex: 0] == 27 ) { nkeynes@964: // Escape char = abort change nkeynes@964: [self setString: lastValue]; nkeynes@964: [self setPrimed: NO]; nkeynes@964: } else { nkeynes@964: [self keyPressed: ([event keyCode]+1)]; nkeynes@964: } nkeynes@964: } else { nkeynes@964: if( chars != NULL && [chars length] == 1 ) { nkeynes@964: int ch = [chars characterAtIndex: 0]; nkeynes@964: switch( ch ) { nkeynes@964: case 0x7F: nkeynes@964: [self setString: @""]; nkeynes@964: [self fireBindingChanged]; nkeynes@964: break; nkeynes@964: case '\r': nkeynes@964: [self setPrimed: YES]; nkeynes@964: break; nkeynes@964: default: nkeynes@964: [super keyDown: event]; nkeynes@964: break; nkeynes@964: } nkeynes@964: } else { nkeynes@964: [super keyDown: event]; nkeynes@964: } nkeynes@964: } nkeynes@964: } nkeynes@964: - (void)flagsChanged: (NSEvent *) event nkeynes@964: { nkeynes@964: if( isPrimed ) { nkeynes@964: [self keyPressed: ([event keyCode]+1)]; nkeynes@964: } nkeynes@964: [super flagsChanged: event]; nkeynes@964: } nkeynes@964: @end nkeynes@964: nkeynes@964: static void cocoa_config_keysym_hook(void *data, const gchar *keysym) nkeynes@964: { nkeynes@964: KeyBindingEditor *editor = (KeyBindingEditor *)data; nkeynes@964: [editor updateKeysym: keysym]; nkeynes@964: } nkeynes@964: nkeynes@964: nkeynes@964: @implementation KeyBindingField nkeynes@964: @end nkeynes@964: nkeynes@964: /*************************** Key-binding sub-view ***********************/ nkeynes@964: nkeynes@964: #define MAX_KEY_BINDINGS 32 nkeynes@964: nkeynes@964: @interface ControllerKeyBindingView : NSView nkeynes@964: { nkeynes@964: maple_device_t device; nkeynes@1034: NSTextField *field[MAX_KEY_BINDINGS][2]; nkeynes@964: } nkeynes@964: - (id)initWithFrame: (NSRect)frameRect; nkeynes@964: - (void)setDevice: (maple_device_t)device; nkeynes@964: @end nkeynes@964: nkeynes@964: @implementation ControllerKeyBindingView nkeynes@964: - (id)initWithFrame: (NSRect)frameRect nkeynes@964: { nkeynes@964: if( [super initWithFrame: frameRect] == nil ) { nkeynes@964: return nil; nkeynes@964: } else { nkeynes@964: device = NULL; nkeynes@964: return self; nkeynes@964: } nkeynes@964: } nkeynes@964: - (BOOL)isFlipped nkeynes@964: { nkeynes@964: return YES; nkeynes@964: } nkeynes@964: - (void)removeSubviews nkeynes@964: { nkeynes@964: [[self subviews] makeObjectsPerformSelector: @selector(removeFromSuperview)]; nkeynes@964: } nkeynes@964: - (void)controlTextDidChange: (NSNotification *)notify nkeynes@964: { nkeynes@1034: const gchar *p = NULL; nkeynes@964: int binding = [[notify object] tag]; nkeynes@964: NSString *val1 = [field[binding][0] stringValue]; nkeynes@1034: if( field[binding][1] == NULL ) { nkeynes@964: p = [val1 UTF8String]; nkeynes@964: } else { nkeynes@1034: NSString *val2 = [field[binding][1] stringValue]; nkeynes@1034: char buf[ [val1 length] + [val2 length] + 2 ]; nkeynes@1034: nkeynes@1034: if( [val1 length] == 0 ) { nkeynes@1034: if( [val2 length] != 0 ) { nkeynes@1034: p = [val2 UTF8String]; nkeynes@1034: } nkeynes@1034: } else if( [val2 length] == 0 ) { nkeynes@1034: p = [val1 UTF8String]; nkeynes@1034: } else { nkeynes@1034: sprintf( buf, "%s,%s", [val1 UTF8String], [val2 UTF8String] ); nkeynes@1034: p = buf; nkeynes@1034: } nkeynes@964: } nkeynes@964: maple_set_device_config_value( device, binding, p ); nkeynes@964: lxdream_save_config(); nkeynes@964: } nkeynes@964: - (void)setDevice: (maple_device_t)newDevice nkeynes@964: { nkeynes@964: device = newDevice; nkeynes@964: [self removeSubviews]; nkeynes@1034: if( device != NULL && !MAPLE_IS_VMU(device) ) { nkeynes@964: lxdream_config_entry_t config = maple_get_device_config(device); nkeynes@964: if( config != NULL ) { nkeynes@964: int count, i, y, x; nkeynes@964: nkeynes@964: for( count=0; config[count].key != NULL; count++ ); nkeynes@964: x = TEXT_GAP; nkeynes@964: NSSize size = NSMakeSize(85+KEYBINDING_SIZE*2+TEXT_GAP*4, count*(TEXT_HEIGHT+TEXT_GAP)+TEXT_GAP); nkeynes@964: [self setFrameSize: size]; nkeynes@964: [self scrollRectToVisible: NSMakeRect(0,0,1,1)]; nkeynes@964: y = TEXT_GAP; nkeynes@964: for( i=0; config[i].key != NULL; i++ ) { nkeynes@1034: /* Add label */ nkeynes@964: NSRect frame = NSMakeRect(x, y + 2, 85, LABEL_HEIGHT); nkeynes@964: NSTextField *label = cocoa_gui_add_label(self, NS_(config[i].label), frame); nkeynes@964: [label setAlignment: NSRightTextAlignment]; nkeynes@1034: nkeynes@1034: switch(config[i].type) { nkeynes@1034: case CONFIG_TYPE_KEY: nkeynes@1034: frame = NSMakeRect( x + 85 + TEXT_GAP, y, KEYBINDING_SIZE, TEXT_HEIGHT); nkeynes@1034: field[i][0] = [[KeyBindingField alloc] initWithFrame: frame]; nkeynes@1034: [field[i][0] setAutoresizingMask: (NSViewMinYMargin|NSViewMaxXMargin)]; nkeynes@1034: [field[i][0] setTag: i]; nkeynes@1034: [field[i][0] setDelegate: self]; nkeynes@1034: [self addSubview: field[i][0]]; nkeynes@964: nkeynes@1034: frame = NSMakeRect( x + 85 + KEYBINDING_SIZE + (TEXT_GAP*2), y, KEYBINDING_SIZE, TEXT_HEIGHT); nkeynes@1034: field[i][1] = [[KeyBindingField alloc] initWithFrame: frame]; nkeynes@1034: [field[i][1] setAutoresizingMask: (NSViewMinYMargin|NSViewMaxXMargin)]; nkeynes@1034: [field[i][1] setTag: i]; nkeynes@1034: [field[i][1] setDelegate: self]; nkeynes@1034: [self addSubview: field[i][1]]; nkeynes@964: nkeynes@1034: if( config[i].value != NULL ) { nkeynes@1034: gchar **parts = g_strsplit(config[i].value,",",3); nkeynes@1034: if( parts[0] != NULL ) { nkeynes@1034: [field[i][0] setStringValue: [NSString stringWithCString: parts[0]]]; nkeynes@1034: if( parts[1] != NULL ) { nkeynes@1034: [field[i][1] setStringValue: [NSString stringWithCString: parts[1]]]; nkeynes@1034: } nkeynes@964: } nkeynes@1034: g_strfreev(parts); nkeynes@964: } nkeynes@1034: break; nkeynes@1034: case CONFIG_TYPE_FILE: nkeynes@1034: case CONFIG_TYPE_PATH: nkeynes@1034: frame = NSMakeRect( x + 85 + TEXT_GAP, y, KEYBINDING_SIZE*2+TEXT_GAP, TEXT_HEIGHT); nkeynes@1034: field[i][0] = [[NSTextField alloc] initWithFrame: frame]; nkeynes@1034: [field[i][0] setAutoresizingMask: (NSViewMinYMargin|NSViewMaxXMargin)]; nkeynes@1034: [field[i][0] setTag: i]; nkeynes@1034: [field[i][0] setDelegate: self]; nkeynes@1034: [self addSubview: field[i][0]]; nkeynes@1034: if( config[i].value != NULL ) { nkeynes@1034: [field[i][0] setStringValue: [NSString stringWithCString: config[i].value]]; nkeynes@1034: } nkeynes@1034: field[i][1] = NULL; nkeynes@1034: } nkeynes@964: y += (TEXT_HEIGHT + TEXT_GAP); nkeynes@964: } nkeynes@964: } else { nkeynes@964: [self setFrameSize: NSMakeSize(100,TEXT_HEIGHT+TEXT_GAP) ]; nkeynes@964: } nkeynes@964: } else { nkeynes@964: [self setFrameSize: NSMakeSize(100,TEXT_HEIGHT+TEXT_GAP) ]; nkeynes@964: } nkeynes@964: } nkeynes@964: @end nkeynes@964: nkeynes@964: /*************************** Top-level controller pane ***********************/ nkeynes@1034: static NSButton *addRadioButton( int port, int sub, int x, int y, id parent ) nkeynes@1034: { nkeynes@1034: char buf[16]; nkeynes@1034: nkeynes@1034: if( sub == 0 ) { nkeynes@1034: snprintf( buf, sizeof(buf), _("Port %c."), 'A'+port ); nkeynes@1034: } else { nkeynes@1034: snprintf( buf, sizeof(buf), _("VMU %d."), sub ); nkeynes@1034: } nkeynes@1034: nkeynes@1034: NSButton *radio = [[NSButton alloc] initWithFrame: NSMakeRect( x, y, 60, TEXT_HEIGHT )]; nkeynes@1034: [radio setTitle: [NSString stringWithUTF8String: buf]]; nkeynes@1034: [radio setTag: MAPLE_DEVID(port,sub) ]; nkeynes@1034: [radio setButtonType: NSRadioButton]; nkeynes@1034: [radio setAlignment: NSRightTextAlignment]; nkeynes@1034: [radio setTarget: parent]; nkeynes@1034: [radio setAction: @selector(radioChanged:)]; nkeynes@1034: [radio setAutoresizingMask: (NSViewMinYMargin|NSViewMaxXMargin)]; nkeynes@1034: [parent addSubview: radio]; nkeynes@1034: return radio; nkeynes@1034: } nkeynes@1034: nkeynes@1034: static void setDevicePopupSelection( NSPopUpButton *popup, maple_device_t device ) nkeynes@1034: { nkeynes@1034: if( device == NULL ) { nkeynes@1034: [popup selectItemAtIndex: 0]; nkeynes@1034: } else if( MAPLE_IS_VMU(device) ) { nkeynes@1034: int idx = vmulist_get_index_by_filename( MAPLE_VMU_NAME(device) ); nkeynes@1034: if( idx == -1 ) { nkeynes@1034: [popup selectItemAtIndex: 0]; nkeynes@1034: } else { nkeynes@1034: [popup selectItemWithTag: FIRST_VMU_TAG + idx]; nkeynes@1034: } nkeynes@1034: } else { nkeynes@1034: const struct maple_device_class **devices = maple_get_device_classes(); nkeynes@1034: int i; nkeynes@1034: for( i=0; devices[i] != NULL; i++ ) { nkeynes@1034: if( devices[i] == device->device_class ) { nkeynes@1034: [popup selectItemWithTag: i+1]; nkeynes@1034: return; nkeynes@1034: } nkeynes@1034: } nkeynes@1034: // Should never get here, but if so... nkeynes@1034: [popup selectItemAtIndex: 0]; nkeynes@1034: } nkeynes@1034: } nkeynes@1034: nkeynes@1034: static void buildDevicePopupMenu( NSPopUpButton *popup, maple_device_t device, BOOL primary ) nkeynes@1034: { nkeynes@1034: int j; nkeynes@1034: const struct maple_device_class **devices = maple_get_device_classes(); nkeynes@1034: nkeynes@1034: [popup removeAllItems]; nkeynes@1034: [popup addItemWithTitle: NS_("")]; nkeynes@1034: [[popup itemAtIndex: 0] setTag: 0]; nkeynes@1034: for( j=0; devices[j] != NULL; j++ ) { nkeynes@1034: int isPrimaryDevice = devices[j]->flags & MAPLE_TYPE_PRIMARY; nkeynes@1034: if( primary ? isPrimaryDevice : (!isPrimaryDevice && !MAPLE_IS_VMU_CLASS(devices[j])) ) { nkeynes@1034: [popup addItemWithTitle: [NSString stringWithUTF8String: devices[j]->name]]; nkeynes@1034: if( device != NULL && device->device_class == devices[j] ) { nkeynes@1034: [popup selectItemAtIndex: ([popup numberOfItems]-1)]; nkeynes@1034: } nkeynes@1034: [[popup itemAtIndex: ([popup numberOfItems]-1)] setTag: (j+1)]; nkeynes@1034: } nkeynes@1034: } nkeynes@1034: nkeynes@1034: if( !primary ) { nkeynes@1034: BOOL vmu_selected = NO; nkeynes@1034: const char *vmu_name; nkeynes@1034: if( device != NULL && MAPLE_IS_VMU(device) ) { nkeynes@1034: vmu_selected = YES; nkeynes@1034: vmu_name = MAPLE_VMU_NAME(device); nkeynes@1034: } nkeynes@1034: if( [popup numberOfItems] > 0 ) { nkeynes@1034: [[popup menu] addItem: [NSMenuItem separatorItem]]; nkeynes@1034: } nkeynes@1034: nkeynes@1034: unsigned int vmu_count = vmulist_get_size(); nkeynes@1034: for( j=0; jdevice_class); nkeynes@1034: for( j=1; j<=MAPLE_USER_SLOTS; j++ ) { nkeynes@1034: radio[MAPLE_DEVID(i,j)] = addRadioButton(i, j, TEXT_GAP*2, y, self); nkeynes@1034: popup[MAPLE_DEVID(i,j)] = addDevicePopup(i, j, 60 + TEXT_GAP*2, y, maple_get_device(i,j), NO, self); nkeynes@1034: y -= (TEXT_HEIGHT+TEXT_GAP); nkeynes@1034: if( j > max ) { nkeynes@1034: [radio[MAPLE_DEVID(i,j)] setEnabled: NO]; nkeynes@1034: [popup[MAPLE_DEVID(i,j)] setEnabled: NO]; nkeynes@964: } nkeynes@964: } nkeynes@964: } nkeynes@964: nkeynes@964: [radio[0] setState: NSOnState]; nkeynes@1034: nkeynes@1034: register_vmulist_change_hook(cocoa_config_vmulist_hook, self); nkeynes@964: return self; nkeynes@964: } nkeynes@964: } nkeynes@1034: - (void)dealloc nkeynes@1034: { nkeynes@1034: unregister_vmulist_change_hook(cocoa_config_vmulist_hook,self); nkeynes@1034: [super dealloc]; nkeynes@1034: } nkeynes@1034: - (void)vmulistChanged: (id)sender nkeynes@1034: { nkeynes@1034: int i; nkeynes@1034: for( i=FIRST_SECONDARY_DEVICE; i= FIRST_VMU_TAG ) { nkeynes@1034: vmu_filename = vmulist_get_filename( new_device_idx - FIRST_VMU_TAG ); nkeynes@1034: new_device_class = &vmu_class; nkeynes@1034: } else if( new_device_idx > 0) { nkeynes@1034: new_device_class = maple_get_device_classes()[new_device_idx-1]; nkeynes@964: } nkeynes@1034: nkeynes@1034: if( current == NULL ? new_device_class == NULL : nkeynes@1034: (current->device_class == new_device_class && nkeynes@1034: (!MAPLE_IS_VMU(current) || MAPLE_VMU_HAS_NAME(current, vmu_filename))) ) { nkeynes@964: // No change nkeynes@964: [key_bindings setDevice: current]; nkeynes@964: return; nkeynes@964: } nkeynes@964: if( current != NULL && current->device_class == &controller_class ) { nkeynes@1034: save_controller[tag] = current->clone(current); nkeynes@964: } nkeynes@964: if( new_device_class == NULL ) { nkeynes@1034: maple_detach_device(port,slot); nkeynes@964: } else { nkeynes@1034: if( new_device_class == &controller_class && save_controller[tag] != NULL ) { nkeynes@1034: new_device = save_controller[tag]; nkeynes@1034: save_controller[tag] = NULL; nkeynes@964: } else { nkeynes@964: new_device = maple_new_device( new_device_class->name ); nkeynes@964: } nkeynes@1034: if( MAPLE_IS_VMU(new_device) ) { nkeynes@1034: MAPLE_SET_VMU_NAME(new_device,vmu_filename); nkeynes@1034: } nkeynes@1034: maple_attach_device(new_device,port,slot); nkeynes@964: } nkeynes@1034: [key_bindings setDevice: maple_get_device(port,slot)]; nkeynes@1034: nkeynes@1034: if( slot == 0 ) { /* Change primary */ nkeynes@1034: int max = new_device_class == NULL ? 0 : MAPLE_SLOTS(new_device_class); nkeynes@1034: for( i=1; i<=MAPLE_USER_SLOTS; i++ ) { nkeynes@1034: if( i <= max ) { nkeynes@1034: [radio[MAPLE_DEVID(port,i)] setEnabled: YES]; nkeynes@1034: [popup[MAPLE_DEVID(port,i)] setEnabled: YES]; nkeynes@1034: } else { nkeynes@1034: [radio[MAPLE_DEVID(port,i)] setEnabled: NO]; nkeynes@1034: [popup[MAPLE_DEVID(port,i)] setEnabled: NO]; nkeynes@1034: } nkeynes@1034: } nkeynes@1034: } nkeynes@964: lxdream_save_config(); nkeynes@964: } nkeynes@964: @end nkeynes@964: nkeynes@964: NSView *cocoa_gui_create_prefs_controller_pane() nkeynes@964: { nkeynes@964: return [LxdreamPrefsControllerPane new]; nkeynes@964: }