nkeynes@681: /** nkeynes@681: * $Id$ nkeynes@681: * nkeynes@681: * Core Cocoa-based user interface nkeynes@681: * nkeynes@725: * Copyright (c) 2008 Nathan Keynes. nkeynes@681: * nkeynes@681: * This program is free software; you can redistribute it and/or modify nkeynes@681: * it under the terms of the GNU General Public License as published by nkeynes@681: * the Free Software Foundation; either version 2 of the License, or nkeynes@681: * (at your option) any later version. nkeynes@681: * nkeynes@681: * This program is distributed in the hope that it will be useful, nkeynes@681: * but WITHOUT ANY WARRANTY; without even the implied warranty of nkeynes@681: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the nkeynes@681: * GNU General Public License for more details. nkeynes@681: */ nkeynes@681: nkeynes@681: #include nkeynes@681: #include nkeynes@681: #include nkeynes@685: #include nkeynes@681: #include nkeynes@681: #include "lxdream.h" nkeynes@718: #include "dream.h" nkeynes@681: #include "dreamcast.h" nkeynes@718: #include "config.h" nkeynes@718: #include "display.h" nkeynes@681: #include "gui.h" nkeynes@681: #include "cocoaui/cocoaui.h" nkeynes@681: nkeynes@681: void cocoa_gui_update( void ); nkeynes@681: void cocoa_gui_start( void ); nkeynes@681: void cocoa_gui_stop( void ); nkeynes@681: void cocoa_gui_run_later( void ); nkeynes@681: uint32_t cocoa_gui_run_slice( uint32_t nanosecs ); nkeynes@681: nkeynes@681: struct dreamcast_module cocoa_gui_module = { "gui", NULL, nkeynes@736: cocoa_gui_update, nkeynes@736: cocoa_gui_start, nkeynes@736: cocoa_gui_run_slice, nkeynes@736: cocoa_gui_stop, nkeynes@736: NULL, NULL }; nkeynes@681: nkeynes@681: /** nkeynes@681: * Count of running nanoseconds - used to cut back on the GUI runtime nkeynes@681: */ nkeynes@681: static uint32_t cocoa_gui_nanos = 0; nkeynes@681: static uint32_t cocoa_gui_ticks = 0; nkeynes@681: static struct timeval cocoa_gui_lasttv; nkeynes@681: static BOOL cocoa_gui_autorun = NO; nkeynes@693: static BOOL cocoa_gui_is_running = NO; nkeynes@681: nkeynes@681: @interface NSApplication (PrivateAdditions) nkeynes@681: - (void) setAppleMenu:(NSMenu *)aMenu; nkeynes@681: @end nkeynes@681: nkeynes@681: static void cocoa_gui_create_menu(void) nkeynes@681: { nkeynes@681: NSMenu *appleMenu, *services; nkeynes@681: NSMenuItem *menuItem; nkeynes@681: NSString *title; nkeynes@681: NSString *appName; nkeynes@736: nkeynes@681: appName = @"Lxdream"; nkeynes@681: appleMenu = [[NSMenu alloc] initWithTitle:@""]; nkeynes@681: nkeynes@681: /* Add menu items */ nkeynes@681: title = [@"About " stringByAppendingString:appName]; nkeynes@725: [appleMenu addItemWithTitle:title action:@selector(about_action:) keyEquivalent:@""]; nkeynes@738: nkeynes@765: [appleMenu addItem:[NSMenuItem separatorItem]]; nkeynes@765: [appleMenu addItemWithTitle: NS_("Preferences...") action:@selector(preferences_action:) keyEquivalent:@","]; nkeynes@736: nkeynes@725: // Services Menu nkeynes@681: [appleMenu addItem:[NSMenuItem separatorItem]]; nkeynes@681: services = [[[NSMenu alloc] init] autorelease]; nkeynes@681: [appleMenu addItemWithTitle:@"Services" action:nil keyEquivalent:@""]; nkeynes@681: [appleMenu setSubmenu: services forItem: [appleMenu itemWithTitle: @"Services"]]; nkeynes@681: nkeynes@681: // Hide AppName nkeynes@681: title = [@"Hide " stringByAppendingString:appName]; nkeynes@681: [appleMenu addItemWithTitle:title action:@selector(hide:) keyEquivalent:@"h"]; nkeynes@681: nkeynes@681: // Hide Others nkeynes@681: menuItem = (NSMenuItem *)[appleMenu addItemWithTitle:@"Hide Others" nkeynes@736: action:@selector(hideOtherApplications:) nkeynes@736: keyEquivalent:@"h"]; nkeynes@681: [menuItem setKeyEquivalentModifierMask:(NSAlternateKeyMask|NSCommandKeyMask)]; nkeynes@681: nkeynes@681: // Show All nkeynes@681: [appleMenu addItemWithTitle:@"Show All" action:@selector(unhideAllApplications:) keyEquivalent:@""]; nkeynes@681: [appleMenu addItem:[NSMenuItem separatorItem]]; nkeynes@681: nkeynes@681: // Quit AppName nkeynes@681: title = [@"Quit " stringByAppendingString:appName]; nkeynes@681: [appleMenu addItemWithTitle:title action:@selector(terminate:) keyEquivalent:@"q"]; nkeynes@681: nkeynes@681: /* Put menu into the menubar */ nkeynes@681: menuItem = [[NSMenuItem alloc] initWithTitle:@"" action:nil keyEquivalent:@""]; nkeynes@681: [menuItem setSubmenu: appleMenu]; nkeynes@681: NSMenu *menu = [NSMenu new]; nkeynes@681: [menu addItem: menuItem]; nkeynes@736: nkeynes@691: NSMenu *gdromMenu = cocoa_gdrom_menu_new(); nkeynes@736: nkeynes@681: NSMenu *fileMenu = [[NSMenu alloc] initWithTitle: NS_("File")]; nkeynes@681: [fileMenu addItemWithTitle: NS_("Load Binary") action: @selector(load_binary_action:) keyEquivalent: @"b"]; nkeynes@691: [[fileMenu addItemWithTitle: NS_("GD-Rom") action: nil keyEquivalent: @""] nkeynes@691: setSubmenu: gdromMenu]; nkeynes@681: [fileMenu addItem: [NSMenuItem separatorItem]]; nkeynes@681: [[fileMenu addItemWithTitle: NS_("Reset") action: @selector(reset_action:) keyEquivalent: @"r"] nkeynes@681: setKeyEquivalentModifierMask:(NSAlternateKeyMask|NSCommandKeyMask)]; nkeynes@681: [fileMenu addItemWithTitle: NS_("Pause") action: @selector(pause_action:) keyEquivalent: @"p"]; nkeynes@681: [fileMenu addItemWithTitle: NS_("Resume") action: @selector(run_action:) keyEquivalent: @"r"]; nkeynes@681: [fileMenu addItem: [NSMenuItem separatorItem]]; nkeynes@681: [fileMenu addItemWithTitle: NS_("Load State") action: @selector(load_action:) keyEquivalent: @"o"]; nkeynes@681: [fileMenu addItemWithTitle: NS_("Save State") action: @selector(save_action:) keyEquivalent: @"s"]; nkeynes@736: nkeynes@681: menuItem = [[NSMenuItem alloc] initWithTitle:NS_("File") action: nil keyEquivalent: @""]; nkeynes@681: [menuItem setSubmenu: fileMenu]; nkeynes@681: [menu addItem: menuItem]; nkeynes@736: nkeynes@681: /* Tell the application object that this is now the application menu */ nkeynes@681: [NSApp setMainMenu: menu]; nkeynes@681: [NSApp setAppleMenu: appleMenu]; nkeynes@681: [NSApp setServicesMenu: services]; nkeynes@681: nkeynes@681: /* Finally give up our references to the objects */ nkeynes@681: [appleMenu release]; nkeynes@681: [menuItem release]; nkeynes@681: [menu release]; nkeynes@681: } nkeynes@681: nkeynes@681: @interface LxdreamDelegate : NSObject nkeynes@681: @end nkeynes@681: nkeynes@681: @implementation LxdreamDelegate nkeynes@681: - (void)windowWillClose: (NSNotification *)notice nkeynes@681: { nkeynes@681: dreamcast_shutdown(); nkeynes@681: exit(0); nkeynes@681: } nkeynes@681: - (void)windowDidBecomeMain: (NSNotification *)notice nkeynes@681: { nkeynes@681: if( cocoa_gui_autorun ) { nkeynes@681: cocoa_gui_autorun = NO; nkeynes@681: cocoa_gui_run_later(); nkeynes@681: } nkeynes@681: } nkeynes@725: - (void) about_action: (id)sender nkeynes@725: { nkeynes@725: NSArray *keys = [NSArray arrayWithObjects: @"Version", @"Copyright", nil]; nkeynes@738: NSArray *values = [NSArray arrayWithObjects: NS_(lxdream_full_version), NS_(lxdream_copyright), nil]; nkeynes@725: nkeynes@725: NSDictionary *options= [NSDictionary dictionaryWithObjects: values forKeys: keys]; nkeynes@725: nkeynes@725: [NSApp orderFrontStandardAboutPanelWithOptions: options]; nkeynes@725: } nkeynes@725: - (void) preferences_action: (id)sender nkeynes@725: { nkeynes@765: cocoa_gui_show_preferences(); nkeynes@725: } nkeynes@681: - (void) load_action: (id)sender nkeynes@681: { nkeynes@681: NSOpenPanel *panel = [NSOpenPanel openPanel]; nkeynes@718: const gchar *dir = lxdream_get_config_value(CONFIG_SAVE_PATH); nkeynes@718: NSString *path = (dir == NULL ? NSHomeDirectory() : [NSString stringWithCString: dir]); nkeynes@681: NSArray *fileTypes = [NSArray arrayWithObject: @"dst"]; nkeynes@718: int result = [panel runModalForDirectory: path file: nil types: fileTypes]; nkeynes@681: if( result == NSOKButton && [[panel filenames] count] > 0 ) { nkeynes@681: NSString *filename = [[panel filenames] objectAtIndex: 0]; nkeynes@681: dreamcast_load_state( [filename UTF8String] ); nkeynes@681: } nkeynes@681: } nkeynes@681: - (void) save_action: (id)sender nkeynes@681: { nkeynes@681: NSSavePanel *panel = [NSSavePanel savePanel]; nkeynes@718: const gchar *dir = lxdream_get_config_value(CONFIG_SAVE_PATH); nkeynes@718: NSString *path = (dir == NULL ? NSHomeDirectory() : [NSString stringWithCString: dir]); nkeynes@681: [panel setRequiredFileType: @"dst"]; nkeynes@718: int result = [panel runModalForDirectory: path file:@""]; nkeynes@681: if( result == NSOKButton ) { nkeynes@681: NSString *filename = [panel filename]; nkeynes@681: dreamcast_save_state( [filename UTF8String] ); nkeynes@681: } nkeynes@681: } nkeynes@681: - (void) load_binary_action: (id)sender nkeynes@681: { nkeynes@681: NSOpenPanel *panel = [NSOpenPanel openPanel]; nkeynes@718: const gchar *dir = lxdream_get_config_value(CONFIG_DEFAULT_PATH); nkeynes@718: NSString *path = (dir == NULL ? NSHomeDirectory() : [NSString stringWithCString: dir]); nkeynes@718: int result = [panel runModalForDirectory: path file: nil types: nil]; nkeynes@681: if( result == NSOKButton && [[panel filenames] count] > 0 ) { nkeynes@681: NSString *filename = [[panel filenames] objectAtIndex: 0]; nkeynes@681: file_load_magic( [filename UTF8String] ); nkeynes@681: } nkeynes@681: } nkeynes@681: - (void) mount_action: (id)sender nkeynes@681: { nkeynes@681: NSOpenPanel *panel = [NSOpenPanel openPanel]; nkeynes@718: const gchar *dir = lxdream_get_config_value(CONFIG_DEFAULT_PATH); nkeynes@718: NSString *path = (dir == NULL ? NSHomeDirectory() : [NSString stringWithCString: dir]); nkeynes@718: int result = [panel runModalForDirectory: path file: nil types: nil]; nkeynes@681: if( result == NSOKButton && [[panel filenames] count] > 0 ) { nkeynes@681: NSString *filename = [[panel filenames] objectAtIndex: 0]; nkeynes@681: gdrom_mount_image( [filename UTF8String] ); nkeynes@681: } nkeynes@681: } nkeynes@681: - (void) pause_action: (id)sender nkeynes@681: { nkeynes@681: dreamcast_stop(); nkeynes@681: } nkeynes@681: nkeynes@681: - (void) reset_action: (id)sender nkeynes@681: { nkeynes@681: dreamcast_reset(); nkeynes@681: } nkeynes@681: - (void) run_action: (id)sender nkeynes@681: { nkeynes@681: cocoa_gui_run_later(); nkeynes@681: } nkeynes@681: - (void) run_immediate nkeynes@681: { nkeynes@681: dreamcast_run(); nkeynes@681: } nkeynes@691: - (void) gdrom_list_action: (id)sender nkeynes@691: { nkeynes@691: gdrom_list_set_selection( [sender tag] ); nkeynes@691: } nkeynes@681: @end nkeynes@681: nkeynes@681: nkeynes@681: gboolean gui_parse_cmdline( int *argc, char **argv[] ) nkeynes@681: { nkeynes@685: /* If started from the finder, the first (and only) arg will look something like nkeynes@736: * -psn_0_... - we want to remove this so that lxdream doesn't try to process it nkeynes@736: * normally nkeynes@736: */ nkeynes@685: if( *argc == 2 && strncmp((*argv)[1], "-psn_", 5) == 0 ) { nkeynes@685: *argc = 1; nkeynes@685: } nkeynes@685: return TRUE; nkeynes@681: } nkeynes@681: nkeynes@681: gboolean gui_init( gboolean withDebug ) nkeynes@681: { nkeynes@681: dreamcast_register_module( &cocoa_gui_module ); nkeynes@736: nkeynes@681: NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; nkeynes@681: [NSApplication sharedApplication]; nkeynes@736: nkeynes@681: LxdreamDelegate *delegate = [[LxdreamDelegate alloc] init]; nkeynes@681: [NSApp setDelegate: delegate]; nkeynes@686: NSString *iconFile = [[NSBundle mainBundle] pathForResource:@"dcemu" ofType:@"gif"]; nkeynes@686: NSImage *iconImage = [[NSImage alloc] initWithContentsOfFile: iconFile]; nkeynes@725: [iconImage setName: @"NSApplicationIcon"]; nkeynes@686: [NSApp setApplicationIconImage: iconImage]; nkeynes@681: cocoa_gui_create_menu(); nkeynes@681: NSWindow *window = cocoa_gui_create_main_window(); nkeynes@681: [window makeKeyAndOrderFront: nil]; nkeynes@681: [NSApp activateIgnoringOtherApps: YES]; nkeynes@736: nkeynes@681: [pool release]; nkeynes@681: } nkeynes@681: nkeynes@681: void gui_main_loop( gboolean run ) nkeynes@681: { nkeynes@681: if( run ) { nkeynes@681: cocoa_gui_autorun = YES; nkeynes@681: } nkeynes@693: cocoa_gui_is_running = YES; nkeynes@736: [NSApp run]; nkeynes@736: cocoa_gui_is_running = NO; nkeynes@681: } nkeynes@681: nkeynes@681: void gui_update_state(void) nkeynes@681: { nkeynes@681: cocoa_gui_update(); nkeynes@681: } nkeynes@681: nkeynes@681: gboolean gui_error_dialog( const char *msg, ... ) nkeynes@681: { nkeynes@693: if( cocoa_gui_is_running ) { nkeynes@693: NSString *error_string; nkeynes@693: nkeynes@693: va_list args; nkeynes@693: va_start(args, msg); nkeynes@693: error_string = [[NSString alloc] initWithFormat: [NSString stringWithCString: msg] arguments: args]; nkeynes@693: NSRunAlertPanel(@"Error in lxdream", error_string, nil, nil, nil); nkeynes@693: va_end(args); nkeynes@693: return TRUE; nkeynes@693: } else { nkeynes@693: return FALSE; nkeynes@693: } nkeynes@681: } nkeynes@681: nkeynes@681: void gui_update_io_activity( io_activity_type io, gboolean active ) nkeynes@681: { nkeynes@681: nkeynes@681: } nkeynes@681: nkeynes@681: nkeynes@681: uint32_t cocoa_gui_run_slice( uint32_t nanosecs ) nkeynes@681: { nkeynes@681: NSEvent *event; nkeynes@681: NSAutoreleasePool *pool; nkeynes@681: nkeynes@681: cocoa_gui_nanos += nanosecs; nkeynes@681: if( cocoa_gui_nanos > GUI_TICK_PERIOD ) { /* 10 ms */ nkeynes@681: cocoa_gui_nanos -= GUI_TICK_PERIOD; nkeynes@681: cocoa_gui_ticks ++; nkeynes@681: uint32_t current_period = cocoa_gui_ticks * GUI_TICK_PERIOD; nkeynes@681: nkeynes@681: // Run the event loop nkeynes@681: pool = [NSAutoreleasePool new]; nkeynes@681: while( (event = [NSApp nextEventMatchingMask: NSAnyEventMask untilDate: nil nkeynes@681: inMode: NSDefaultRunLoopMode dequeue: YES]) != nil ) { nkeynes@681: [NSApp sendEvent: event]; nkeynes@681: } nkeynes@681: [pool release]; nkeynes@681: nkeynes@681: struct timeval tv; nkeynes@681: gettimeofday(&tv,NULL); nkeynes@681: uint32_t ns = ((tv.tv_sec - cocoa_gui_lasttv.tv_sec) * 1000000000) + nkeynes@681: (tv.tv_usec - cocoa_gui_lasttv.tv_usec)*1000; nkeynes@681: if( (ns * 1.05) < current_period ) { nkeynes@681: // We've gotten ahead - sleep for a little bit nkeynes@681: struct timespec tv; nkeynes@681: tv.tv_sec = 0; nkeynes@681: tv.tv_nsec = current_period - ns; nkeynes@681: nanosleep(&tv, &tv); nkeynes@681: } nkeynes@681: nkeynes@681: /* Update the display every 10 ticks (ie 10 times a second) and nkeynes@681: * save the current tv value */ nkeynes@681: if( cocoa_gui_ticks > 10 ) { nkeynes@681: gchar buf[32]; nkeynes@681: cocoa_gui_ticks -= 10; nkeynes@681: nkeynes@681: double speed = (float)( (double)current_period * 100.0 / ns ); nkeynes@681: cocoa_gui_lasttv.tv_sec = tv.tv_sec; nkeynes@681: cocoa_gui_lasttv.tv_usec = tv.tv_usec; nkeynes@681: snprintf( buf, 32, _("Running (%2.4f%%)"), speed ); nkeynes@681: [((LxdreamMainWindow *)[NSApp mainWindow]) setStatusText: buf]; nkeynes@681: nkeynes@681: } nkeynes@681: } nkeynes@681: return nanosecs; nkeynes@681: } nkeynes@681: nkeynes@681: void cocoa_gui_update( void ) nkeynes@681: { nkeynes@736: nkeynes@681: } nkeynes@681: nkeynes@681: void cocoa_gui_start( void ) nkeynes@681: { nkeynes@681: LxdreamMainWindow *win = (LxdreamMainWindow *)[NSApp mainWindow]; nkeynes@681: [win setRunning: YES]; nkeynes@681: cocoa_gui_nanos = 0; nkeynes@681: gettimeofday(&cocoa_gui_lasttv,NULL); nkeynes@681: } nkeynes@681: nkeynes@681: void cocoa_gui_stop( void ) nkeynes@681: { nkeynes@681: LxdreamMainWindow *win = (LxdreamMainWindow *)[NSApp mainWindow]; nkeynes@681: [win setRunning: NO]; nkeynes@681: } nkeynes@681: nkeynes@681: /** nkeynes@681: * Queue a dreamcast_run() to execute after the currently event(s) nkeynes@681: */ nkeynes@681: void cocoa_gui_run_later( void ) nkeynes@681: { nkeynes@681: [[NSRunLoop currentRunLoop] performSelector: @selector(run_immediate) nkeynes@736: target: [NSApp delegate] argument: nil order: 1 nkeynes@736: modes: [NSArray arrayWithObject: NSDefaultRunLoopMode] ]; nkeynes@718: } nkeynes@718: nkeynes@718: NSImage *NSImage_new_from_framebuffer( frame_buffer_t buffer ) nkeynes@718: { nkeynes@718: NSBitmapImageRep *rep = nkeynes@718: [[NSBitmapImageRep alloc] initWithBitmapDataPlanes: &buffer->data nkeynes@718: pixelsWide: buffer->width pixelsHigh: buffer->height nkeynes@736: bitsPerSample: 8 samplesPerPixel: 3 nkeynes@736: hasAlpha: NO isPlanar: NO nkeynes@736: colorSpaceName: NSDeviceRGBColorSpace bitmapFormat: 0 nkeynes@736: bytesPerRow: buffer->rowstride bitsPerPixel: 24]; nkeynes@736: nkeynes@718: NSImage *image = [[NSImage alloc] initWithSize: NSMakeSize(0.0,0.0)]; nkeynes@718: [image addRepresentation: rep]; nkeynes@718: return image; nkeynes@718: }