From 1030dc837b10a03a02a85d5504cbeec168ce49e2 Mon Sep 17 00:00:00 2001 From: Bernie Innocenti Date: Mon, 03 May 2010 21:53:47 +0000 Subject: Import XaoS r489 (trunk after version 3.5) --- (limited to 'src/ui/ui-drv/cocoa/AppController.m') diff --git a/src/ui/ui-drv/cocoa/AppController.m b/src/ui/ui-drv/cocoa/AppController.m new file mode 100644 index 0000000..6f03b97 --- /dev/null +++ b/src/ui/ui-drv/cocoa/AppController.m @@ -0,0 +1,542 @@ +/* + * XaoS, a fast portable realtime fractal zoomer + * Copyright (C) 1996 by + * + * Jan Hubicka (hubicka@paru.cas.cz) + * Thomas Marsh (tmarsh@austin.ibm.com) + * + * Cocoa Driver by J.B. Langston III (jb-langston@austin.rr.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ +#import "AppController.h" +#import "CustomDialog.h" +#import "VideatorProxy.h" +#import "ui.h" + +#ifdef HAVE_GETTEXT +#include +#include +#define _(string) gettext(string) +#else +#define _(string) (string) +#endif + +/* + * This category overrides standard NSWindow behavior which prevents a window + * from receiving keyboard events unless it has a titlebar. Without this, the + * keyboard doesn't work in full screen mode. + */ +@implementation NSWindow (CanBecomeKeyWindowOverride) +- (BOOL)canBecomeKeyWindow { + return YES; +} +@end + + +AppController *controller; + +@implementation AppController + + +#pragma mark Initialization +- (id)init { + self = [super init]; + if (self) { + applicationIsLaunched = NO; + [self initLocale]; + } + return self; +} + +#pragma mark Accessors + +- (FractalView *)view { + return view; +} + +#pragma mark Driver Initialization + +- (void)initLocale { + /* + * The LANG environment variables used by gettext to determine the locale + * are not normally set on Mac OS X, so we use the Cocoa API to retrieve + * the list of preferred languages and set the LANG variable accordingly. + */ + + NSString *myLocalePath = [[[NSBundle mainBundle] resourcePath] + stringByAppendingPathComponent:@"locale"]; + +#ifdef USE_LOCALEPATH + /* + * This is a global variable defined in ui.h, which the main function uses + * to locate the locale files when USE_LOCALEPATH is defined. If it is + * undefined, the main function will use the hard coded locale path instead. + */ + localepath = (char *)[myLocalePath UTF8String]; +#endif + + /* + * Each of the locales we support is stored in its own subdirectory in the + * Resources/locale directory. The name of the directory corresponds to the + * ISO code for the locale. Therefore, a list of the files in this + * directory conveniently serves as a list of supported locales. + */ + NSMutableArray *supportedLanguages = [[[NSFileManager defaultManager] + directoryContentsAtPath:myLocalePath] + mutableCopy]; + + /* English is supported by default, so there isn't a locale directory for + * it. But in order to match it with the user's preferred languages, it + * still has to be in the array of supported languages. + */ + [supportedLanguages addObject:@"en"]; + + /* + * The AppleLanguages user default returns an array of languages sorted + * according to the User's settings in the International Preference Panel. + * We find the best match between the supported and preferred locales + * and set the LANG variable to that. + */ + NSUserDefaults * defaults = [NSUserDefaults standardUserDefaults]; + NSArray *preferredLanguages = [defaults objectForKey:@"AppleLanguages"]; + NSString *lang = [preferredLanguages firstObjectCommonWithArray:supportedLanguages]; + if (lang) setenv("LANG", [lang UTF8String], /*overwrite? */ 0); + + [supportedLanguages release]; +} + +- (int)initDriver:(struct ui_driver *)driver + fullscreen:(BOOL)fullscreen { + /* + * Calculate the pixel size in cm. userSpaceScaleFactor returns: + * pixels per per point + * pixels per inch = pixels per point * 72.0 + * inches per pixel = 1 / pixels per inch + * cm per pixel = inches per pixel * 2.54 + */ + CGDirectDisplayID displayID = (CGDirectDisplayID)[[[[NSScreen mainScreen] deviceDescription] objectForKey:@"NSScreenNumber"] intValue]; + CGSize displaySize = CGDisplayScreenSize(displayID); + NSSize displayResolution = [[NSScreen mainScreen] frame].size; + driver->width = (displaySize.width/displayResolution.width)/10; + driver->height = (displaySize.height/displayResolution.height)/10; + + if (fullscreen) { + /* + * SetSystemUIMode is the easiest way to make a full screen application. + * It's Carbon, but it should be 64-bit safe. kUIModeAllHidden hides + * the dock and menuBar and kUIOptionAutoShowMenuBar causes the menubar + * to automatically slide in when the user moves the mouse to the top + * edge of the screen. + */ + SetSystemUIMode(kUIModeAllHidden, kUIOptionAutoShowMenuBar); + window = [[NSWindow alloc] initWithContentRect:[[NSScreen mainScreen] frame] + styleMask:NSBorderlessWindowMask + backing:NSBackingStoreBuffered + defer:YES]; + } else { + window = [[NSWindow alloc] initWithContentRect:NSMakeRect(50, 50, 640, 480) + styleMask:(NSTitledWindowMask | NSClosableWindowMask | NSMiniaturizableWindowMask | NSResizableWindowMask) + backing:NSBackingStoreBuffered + defer:YES]; + [window setFrameAutosaveName:@"XaoSWindow"]; + } + + view = [[FractalView alloc] initWithFrame:[[window contentView] frame]]; + [view setAutoresizingMask:(NSViewWidthSizable | NSViewHeightSizable)]; + [[window contentView] addSubview:view]; + [window makeFirstResponder:view]; + [window setDelegate:self]; + [window setTitle:@"XaoS"]; + [window makeKeyAndOrderFront:self]; + [NSApp setDelegate:self]; + + /* + * These tasks should only be done once, when the application first launches + * but for various reasons, they can't be done until after the main run + * loop has started. That's why we put them in the driver init code. + */ + if (!applicationIsLaunched) { + [self localizeApplicationMenu]; + [NSApp finishLaunching]; + applicationIsLaunched = YES; + } + + return 1; // 1 for success; 0 for failure +} + +- (void)uninitDriver { + SetSystemUIMode(kUIModeNormal, 0); + [view release]; + [window release]; +} + +#pragma mark Menus + +- (void)localizeApplicationMenu { + /* + * Internationalize XaoS application menu. We do this via code instead + * of within the nib because this allows all i18n to be cross-platform and + * self-contained within the po file instead of spread across many places. + */ + NSMenu *appMenu = [[[NSApp mainMenu] itemAtIndex:0] submenu]; + + [[appMenu itemWithTitle:@"About XaoS"] + setTitle:[NSString stringWithUTF8String:_("About XaoS")]]; + + [[appMenu itemWithTitle:@"Services"] + setTitle:[NSString stringWithUTF8String:_("Services")]]; + + [[appMenu itemWithTitle:@"Hide XaoS"] + setTitle:[NSString stringWithUTF8String:_("Hide XaoS")]]; + + [[appMenu itemWithTitle:@"Hide Others"] + setTitle:[NSString stringWithUTF8String:_("Hide Others")]]; + + [[appMenu itemWithTitle:@"Show All"] + setTitle:[NSString stringWithUTF8String:_("Show All")]]; + + [[appMenu itemWithTitle:@"Quit XaoS"] + setTitle:[NSString stringWithUTF8String:_("Quit XaoS")]]; +} + +- (void)performMenuAction:(NSMenuItem *)sender { + /* + * Find the XaoS menu item associated with the sending Cocoa menu item + * then invoke the callback to perform that action. + */ + NSString *name = [sender representedObject]; + CONST menuitem *item = menu_findcommand([name UTF8String]); + + ui_menuactivate(item, NULL); +} + +- (NSString *)keyEquivalentForName:(NSString *)name { + // If you want more command-keys, just add them here based on their name: + if ([name isEqualToString:@"undo"]) return @"z"; + if ([name isEqualToString:@"redo"]) return @"Z"; + if ([name isEqualToString:@"loadpos"]) return @"o"; + if ([name isEqualToString:@"savepos"]) return @"s"; + return @""; +} + +- (void)buildMenuWithContext:(struct uih_context *)context + name:(CONST char *)name { + NSMenu *menu = [NSApp mainMenu]; + while ([menu numberOfItems] > 1) + [menu removeItemAtIndex:1]; + [self buildMenuWithContext:context name:name parent:menu]; +} + +- (void)buildMenuWithContext:(struct uih_context *)context + name:(CONST char *)menuName + parent:(NSMenu *)parentMenu { + [self buildMenuWithContext:context + name:menuName + parent:parentMenu + isNumbered:NO]; +} + +- (void)buildMenuWithContext:(struct uih_context *)context + name:(CONST char *)menuName + parent:(NSMenu *)parentMenu + isNumbered:(BOOL)isNumbered { + int i, n; + CONST menuitem *item; + for (i=0,n=1; (item = menu_item(menuName, i)) != NULL; i++) { + if (item->type == MENU_SEPARATOR) { + [parentMenu addItem:[NSMenuItem separatorItem]]; + } else { + NSString *menuTitle = [NSString stringWithUTF8String:item->name]; + + /* + * Add elipses to menu items that open dialogs in order to conform + * with the Apple Human Interface Guidelines. + */ + if (item->type == MENU_CUSTOMDIALOG || item->type == MENU_DIALOG) + menuTitle = [menuTitle stringByAppendingString:@"..."]; + + NSString *menuShortName = [NSString stringWithUTF8String:item->shortname]; + NSString *keyEquiv = [self keyEquivalentForName:menuShortName]; + + /* + * Add classic XaoS key accelerator to name in parenthesis, unless + * this is the main menu. This allows both Mac-style and Xaos-style + * key equivalents to co-exist. + */ + if (item->key && parentMenu != [NSApp mainMenu]) + menuTitle = [NSString stringWithFormat:@"%@ (%s)", menuTitle, item->key]; + + NSMenuItem *newItem = [[NSMenuItem allocWithZone:[NSMenu menuZone]] initWithTitle:menuTitle action:nil keyEquivalent:keyEquiv]; + + /* + * If this is a numbered pop-up menu, override the default key + * accelerator with a number or letter based on the position in + * the menu. + */ + if (isNumbered && item->type != MENU_SUBMENU) { + if (n < 10) + keyEquiv = [NSString stringWithFormat:@"%d", n]; + else if (n == 10) + keyEquiv = @"0"; + else if (n < 36) + keyEquiv = [NSString stringWithFormat:@"%c", 'a' + n - 11]; + + [newItem setKeyEquivalent:keyEquiv]; + [newItem setKeyEquivalentModifierMask:0]; + n++; + } + + if (item->type == MENU_SUBMENU) { + /* Recursively build submenus */ + NSMenu *newMenu = [[NSMenu allocWithZone:[NSMenu menuZone]] initWithTitle:menuTitle]; + [newMenu setDelegate:self]; + [newItem setSubmenu:newMenu]; + [self buildMenuWithContext:context name:item->shortname parent:newMenu]; + + /* Conditionally add special items to certain menus */ + + /* + * These items are necessary to provide the expected keyboard + * equivalents for cut & paste operations in custom dialogs + * and to conform to the human interface guidelines + */ + if ([menuShortName isEqualToString:@"edit"]) { + [newMenu addItem:[NSMenuItem separatorItem]]; + [newMenu addItemWithTitle:[NSString stringWithUTF8String:_("Cut")] + action:@selector(cut:) keyEquivalent:@"x"]; + + [newMenu addItemWithTitle:[NSString stringWithUTF8String:_("Copy")] + action:@selector(copy:) keyEquivalent:@"c"]; + + [newMenu addItemWithTitle:[NSString stringWithUTF8String:_("Paste")] + action:@selector(paste:) keyEquivalent:@"v"]; + + [newMenu addItemWithTitle:[NSString stringWithUTF8String:_("Delete")] + action:@selector(delete:) keyEquivalent:@""]; + + [newMenu addItemWithTitle:[NSString stringWithUTF8String:_("Select All")] + action:@selector(selectAll:) keyEquivalent:@"a"]; + } + + /* + * These items in the window menu are necessary to provide expected + * keyboard equivalents for menu operations such as minimizing and + * to conform with the human interface guidelnes. + */ + if ([menuShortName isEqualToString:@"window"]) { + [newMenu addItemWithTitle:[NSString stringWithUTF8String:_("Minimize")] + action:@selector(performMiniaturize:) keyEquivalent:@"m"]; + + [newMenu addItemWithTitle:[NSString stringWithUTF8String:_("Zoom")] + action:@selector(performZoom:) keyEquivalent:@""]; + + [newMenu addItem:[NSMenuItem separatorItem]]; + + [newMenu addItemWithTitle:[NSString stringWithUTF8String:_("Bring All to Front")] + action:@selector(arrangeInFront:) keyEquivalent:@""]; + } + + /* + * The close menu item in the File menu is necessary to provide + * the expected Command-W close window keyboard equivalent and + * to conform with the human interface guidelines. + */ + if ([menuShortName isEqualToString:@"file"]) { + int i = [newMenu indexOfItemWithRepresentedObject:@"savepos"]; + [newMenu insertItemWithTitle:[NSString stringWithUTF8String:_("Close")] + action:@selector(performClose:) + keyEquivalent:@"w" + atIndex:i]; + [newMenu insertItem:[NSMenuItem separatorItem] atIndex:i]; + } + + /* + * Add Videator Output menu item in the UI menu just below + * VJ Mode. This will toggle sending video feed to Videator. + */ +#ifdef VIDEATOR_SUPPORT + if ([menuShortName isEqualToString:@"ui"]) { + int i = [newMenu indexOfItemWithRepresentedObject:@"inhibittextoutput"]+1; + NSMenuItem *item = [newMenu insertItemWithTitle:[NSString stringWithUTF8String:_("Videator Output")] + action:@selector(toggleVideator:) + keyEquivalent:@"" + atIndex:i]; + [item setTarget:[view videatorProxy]]; + [item setRepresentedObject:@"videator"]; + } +#endif + + [newMenu release]; + } else { + /* + * Set action for leaf menu items to generic callback function + * and save the short name as the item's represented object. When + * the callback is activated, it will find the XaoS menu item + * to activate based on the represented object. + */ + [newItem setTarget:self]; + [newItem setAction:@selector(performMenuAction:)]; + [newItem setRepresentedObject:menuShortName]; + if (item->flags & (MENUFLAG_RADIO | MENUFLAG_CHECKBOX) && menu_enabled (item, context)) + [newItem setState:NSOnState]; + } + + [parentMenu addItem:newItem]; + [newItem release]; + } + } +} + +- (void)menuNeedsUpdate:(NSMenu *)menu { + CONST struct menuitem *xaosItem; + NSMenuItem *menuItem; + NSEnumerator *itemEnumerator = [[menu itemArray] objectEnumerator]; + while (menuItem = [itemEnumerator nextObject]) { + if ([menuItem representedObject]) { + xaosItem = menu_findcommand([[menuItem representedObject] UTF8String]); + if (xaosItem) + [menuItem setState:(menu_enabled(xaosItem, globaluih) ? NSOnState : NSOffState)]; +#ifdef VIDEATOR_SUPPORT + else if ([[menuItem representedObject] isEqualToString:@"videator"]) + [menuItem setState:([[view videatorProxy] videatorEnabled] ? NSOnState : NSOffState)]; +#endif + } + } +} + +- (void)showPopUpMenuWithContext:(struct uih_context *)context + name:(CONST char *)name { + NSMenu *popUpMenu = [[NSMenu alloc] initWithTitle:@"Popup Menu"]; + NSPopUpButtonCell *popUpButtonCell = [[NSPopUpButtonCell alloc] initTextCell:@"" pullsDown:NO]; + NSRect frame = {{0.0, 0.0}, {0.0, 0.0}}; + frame.origin = [window mouseLocationOutsideOfEventStream]; + [self buildMenuWithContext:context name:name parent:popUpMenu isNumbered:YES]; + int state = [[popUpMenu itemAtIndex:0] state]; + [popUpButtonCell setMenu:popUpMenu]; + [[popUpMenu itemAtIndex:0] setState:state]; + [popUpButtonCell performClickWithFrame:frame inView:view]; + [popUpButtonCell release]; + [popUpMenu release]; +} + +#pragma mark Dialogs + +- (void)showDialogWithContext:(struct uih_context *)context + name:(CONST char *)name { + CONST menuitem *item = menu_findcommand (name); + if (!item) return; + + CONST menudialog *dialog = menu_getdialog (context, item); + if (!dialog) return; + + int nitems; + for (nitems = 0; dialog[nitems].question; nitems++); + + if (nitems == 1 && (dialog[0].type == DIALOG_IFILE || dialog[0].type == DIALOG_OFILE)) { + NSString *extension = [[NSString stringWithUTF8String:dialog[0].defstr] pathExtension]; + + NSString *fileName = nil; + switch(dialog[0].type) { + case DIALOG_IFILE: + { + NSOpenPanel *oPanel = [NSOpenPanel openPanel]; + + int result = [oPanel runModalForDirectory:nil + file:nil + types:[NSArray arrayWithObject:extension]]; + + if (result == NSOKButton) + fileName = [oPanel filename]; + break; + } + case DIALOG_OFILE: + { + NSSavePanel *sPanel = [NSSavePanel savePanel]; + [sPanel setRequiredFileType:extension]; + + int result = [sPanel runModalForDirectory:nil file:@"untitled"]; + + if (result == NSOKButton) + fileName = [sPanel filename]; + break; + } + } + + [window makeKeyAndOrderFront:self]; + + if (fileName) { + dialogparam *param = malloc (sizeof (dialogparam)); + param->dstring = strdup([fileName UTF8String]); + ui_menuactivate (item, param); + } + + } else { + CustomDialog *customDialog = [[CustomDialog alloc] initWithContext:context + menuItem:item + dialog:dialog]; + [NSApp beginSheet:customDialog + modalForWindow:window + modalDelegate:nil + didEndSelector:nil + contextInfo:nil]; + [NSApp runModalForWindow:customDialog]; + [NSApp endSheet:customDialog]; + [customDialog orderOut:self]; + [window makeKeyAndOrderFront:self]; + + if ([customDialog params]) + ui_menuactivate(item, [customDialog params]); + + [customDialog release]; + } +} + +#pragma mark Help + +- (void)showHelpWithContext:(struct uih_context *)context + name:(CONST char *)name { + NSString *anchor = [NSString stringWithUTF8String:name]; + [[NSHelpManager sharedHelpManager] openHelpAnchor:anchor inBook:@"XaoS Help"]; +} + +#pragma mark Window Delegates + +- (void)windowWillClose:(NSNotification *)notification { + [NSApp terminate:self]; +} + +- (void)windowDidResize:(NSNotification *)notification { + // Handle maximize/zoom, but ignore live resizing + if (![view inLiveResize]) + ui_resize(); +} + +#pragma mark Application Delegates + +- (BOOL)application:(NSApplication *)theApplication + openFile:(NSString *)filename { + if ([[filename pathExtension] isEqualToString:@"xpf"]) { + uih_loadfile(globaluih, [filename UTF8String]); + return YES; + } else if ([[filename pathExtension] isEqualToString:@"xaf"]) { + uih_playfile(globaluih, [filename UTF8String]); + return YES; + } else { + return NO; + } +} + +@end \ No newline at end of file -- cgit v0.9.1