src/video/cocoa/cocoa_v.mm
branchnoai
changeset 9722 ebf0ece7d8f6
child 9723 eee46cb39750
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/video/cocoa/cocoa_v.mm	Fri Nov 23 16:59:30 2007 +0000
@@ -0,0 +1,421 @@
+/* $Id$ */
+
+/******************************************************************************
+ *                             Cocoa video driver                             *
+ * Known things left to do:                                                   *
+ *  Nothing at the moment.                                                    *
+ ******************************************************************************/
+
+#ifdef WITH_COCOA
+
+#include <AvailabilityMacros.h>
+
+#import <Cocoa/Cocoa.h>
+#import <sys/time.h> /* gettimeofday */
+#import <sys/param.h> /* for MAXPATHLEN */
+#import <unistd.h>
+
+/**
+ * Important notice regarding all modifications!!!!!!!
+ * There are certain limitations because the file is objective C++.
+ * gdb has limitations.
+ * C++ and objective C code can't be joined in all cases (classes stuff).
+ * Read http://developer.apple.com/releasenotes/Cocoa/Objective-C++.html for more information.
+ */
+
+
+/* Portions of CPS.h */
+struct CPSProcessSerNum {
+	UInt32 lo;
+	UInt32 hi;
+};
+
+extern "C" OSErr CPSGetCurrentProcess(CPSProcessSerNum* psn);
+extern "C" OSErr CPSEnableForegroundOperation(CPSProcessSerNum* psn, UInt32 _arg2, UInt32 _arg3, UInt32 _arg4, UInt32 _arg5);
+extern "C" OSErr CPSSetFrontProcess(CPSProcessSerNum* psn);
+
+/* Disables a warning. This is needed since the method exists but has been dropped from the header, supposedly as of 10.4. */
+#if (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_4)
+@interface NSApplication(NSAppleMenu)
+- (void)setAppleMenu:(NSMenu *)menu;
+@end
+#endif
+
+
+/* Defined in stdbool.h */
+#ifndef __cplusplus
+# ifndef __BEOS__
+#  undef bool
+#  undef false
+#  undef true
+# endif
+#endif
+
+
+#include "../../stdafx.h"
+#include "../../openttd.h"
+#include "../../debug.h"
+#include "../../macros.h"
+#include "../../os/macosx/splash.h"
+#include "../../variables.h"
+#include "../../gfx.h"
+#include "cocoa_v.h"
+#include "cocoa_keys.h"
+#include "../../blitter/factory.hpp"
+#include "../../fileio.h"
+
+#undef Point
+#undef Rect
+
+
+@interface OTTDMain : NSObject
+@end
+
+
+static NSAutoreleasePool *_ottd_autorelease_pool;
+static OTTDMain *_ottd_main;
+static bool _cocoa_video_started = false;
+static bool _cocoa_video_dialog = false;
+
+CocoaSubdriver* _cocoa_subdriver = NULL;
+
+
+
+/* The main class of the application, the application's delegate */
+@implementation OTTDMain
+/* Called when the internal event loop has just started running */
+- (void) applicationDidFinishLaunching: (NSNotification*) note
+{
+	/* Hand off to main application code */
+	QZ_GameLoop();
+
+	/* We're done, thank you for playing */
+	[ NSApp stop:_ottd_main ];
+}
+
+/* Display the in game quit confirmation dialog */
+- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication*) sender
+{
+
+	HandleExitGameRequest();
+
+	return NSTerminateCancel; // NSTerminateLater ?
+}
+@end
+
+static void setApplicationMenu()
+{
+	/* warning: this code is very odd */
+	NSMenu *appleMenu;
+	NSMenuItem *menuItem;
+	NSString *title;
+	NSString *appName;
+
+	appName = @"OTTD";
+	appleMenu = [[NSMenu alloc] initWithTitle:appName];
+
+	/* Add menu items */
+	title = [@"About " stringByAppendingString:appName];
+	[appleMenu addItemWithTitle:title action:@selector(orderFrontStandardAboutPanel:) keyEquivalent:@""];
+
+	[appleMenu addItem:[NSMenuItem separatorItem]];
+
+	title = [@"Hide " stringByAppendingString:appName];
+	[appleMenu addItemWithTitle:title action:@selector(hide:) keyEquivalent:@"h"];
+
+	menuItem = (NSMenuItem*)[appleMenu addItemWithTitle:@"Hide Others" action:@selector(hideOtherApplications:) keyEquivalent:@"h"];
+	[menuItem setKeyEquivalentModifierMask:(NSAlternateKeyMask|NSCommandKeyMask)];
+
+	[appleMenu addItemWithTitle:@"Show All" action:@selector(unhideAllApplications:) keyEquivalent:@""];
+
+	[appleMenu addItem:[NSMenuItem separatorItem]];
+
+	title = [@"Quit " stringByAppendingString:appName];
+	[appleMenu addItemWithTitle:title action:@selector(terminate:) keyEquivalent:@"q"];
+
+
+	/* Put menu into the menubar */
+	menuItem = [[NSMenuItem alloc] initWithTitle:@"" action:nil keyEquivalent:@""];
+	[menuItem setSubmenu:appleMenu];
+	[[NSApp mainMenu] addItem:menuItem];
+
+	/* Tell the application object that this is now the application menu */
+	[NSApp setAppleMenu:appleMenu];
+
+	/* Finally give up our references to the objects */
+	[appleMenu release];
+	[menuItem release];
+}
+
+/* Create a window menu */
+static void setupWindowMenu()
+{
+	NSMenu* windowMenu;
+	NSMenuItem* windowMenuItem;
+	NSMenuItem* menuItem;
+
+	windowMenu = [[NSMenu alloc] initWithTitle:@"Window"];
+
+	/* "Minimize" item */
+	menuItem = [[NSMenuItem alloc] initWithTitle:@"Minimize" action:@selector(performMiniaturize:) keyEquivalent:@"m"];
+	[windowMenu addItem:menuItem];
+	[menuItem release];
+
+	/* Put menu into the menubar */
+	windowMenuItem = [[NSMenuItem alloc] initWithTitle:@"Window" action:nil keyEquivalent:@""];
+	[windowMenuItem setSubmenu:windowMenu];
+	[[NSApp mainMenu] addItem:windowMenuItem];
+
+	/* Tell the application object that this is now the window menu */
+	[NSApp setWindowsMenu:windowMenu];
+
+	/* Finally give up our references to the objects */
+	[windowMenu release];
+	[windowMenuItem release];
+}
+
+static void setupApplication()
+{
+	CPSProcessSerNum PSN;
+
+	/* Ensure the application object is initialised */
+	[NSApplication sharedApplication];
+
+	/* Tell the dock about us */
+	if (!CPSGetCurrentProcess(&PSN) &&
+			!CPSEnableForegroundOperation(&PSN, 0x03, 0x3C, 0x2C, 0x1103) &&
+			!CPSSetFrontProcess(&PSN)) {
+		[NSApplication sharedApplication];
+	}
+
+	/* Set up the menubar */
+	[NSApp setMainMenu:[[NSMenu alloc] init]];
+	setApplicationMenu();
+	setupWindowMenu();
+
+	/* Create OTTDMain and make it the app delegate */
+	_ottd_main = [[OTTDMain alloc] init];
+	[NSApp setDelegate:_ottd_main];
+}
+
+
+static void QZ_UpdateVideoModes()
+{
+	uint i, count;
+	OTTDPoint modes[32];
+
+	assert(_cocoa_subdriver != NULL);
+
+	count = _cocoa_subdriver->ListModes(modes, lengthof(modes));
+
+	for (i = 0; i < count; i++) {
+		_resolutions[i][0] = modes[i].x;
+		_resolutions[i][1] = modes[i].y;
+	}
+
+	_num_resolutions = count;
+}
+
+
+void QZ_GameSizeChanged()
+{
+	if (_cocoa_subdriver == NULL) return;
+
+	/* Tell the game that the resolution has changed */
+	_screen.width = _cocoa_subdriver->GetWidth();
+	_screen.height = _cocoa_subdriver->GetHeight();
+	_screen.pitch = _cocoa_subdriver->GetWidth();
+	_fullscreen = _cocoa_subdriver->IsFullscreen();
+
+	GameSizeChanged();
+}
+
+
+static CocoaSubdriver *QZ_CreateWindowSubdriver(int width, int height, int bpp)
+{
+	long sysVersion;
+
+	if (Gestalt(gestaltSystemVersion, &sysVersion) == noErr && sysVersion >= 0x1040) {
+		return QZ_CreateWindowQuartzSubdriver(width, height, bpp);
+	}
+
+	return QZ_CreateWindowQuickdrawSubdriver(width, height, bpp);
+}
+
+
+static CocoaSubdriver *QZ_CreateSubdriver(int width, int height, int bpp, bool fullscreen, bool fallback)
+{
+	CocoaSubdriver *ret;
+
+	ret = fullscreen ? QZ_CreateFullscreenSubdriver(width, height, bpp) : QZ_CreateWindowSubdriver(width, height, bpp);
+	if (ret != NULL) return ret;
+
+	if (!fallback) return NULL;
+
+	/* Try again in 640x480 windowed */
+	DEBUG(driver, 0, "Setting video mode failed, falling back to 640x480 windowed mode.");
+	ret = QZ_CreateWindowSubdriver(640, 480, bpp);
+	if (ret != NULL) return ret;
+
+#ifdef _DEBUG
+	/* Try fullscreen too when in debug mode */
+	DEBUG(driver, 0, "Setting video mode failed, falling back to 640x480 fullscreen mode.");
+	ret = QZ_CreateFullscreenSubdriver(640, 480, bpp);
+	if (ret != NULL) return ret;
+#endif
+
+	return NULL;
+}
+
+
+static FVideoDriver_Cocoa iFVideoDriver_Cocoa;
+
+void VideoDriver_Cocoa::Stop()
+{
+	if (!_cocoa_video_started) return;
+
+	delete _cocoa_subdriver;
+	_cocoa_subdriver = NULL;
+
+	[_ottd_main release];
+
+	_cocoa_video_started = false;
+}
+
+const char *VideoDriver_Cocoa::Start(const char * const *parm)
+{
+	int width, height, bpp;
+
+	if (_cocoa_video_started) return "Already started";
+	_cocoa_video_started = true;
+
+	setupApplication();
+
+	/* Don't create a window or enter fullscreen if we're just going to show a dialog. */
+	if (_cocoa_video_dialog) return NULL;
+
+	width = _cur_resolution[0];
+	height = _cur_resolution[1];
+	bpp = BlitterFactoryBase::GetCurrentBlitter()->GetScreenDepth();
+
+	_cocoa_subdriver = QZ_CreateSubdriver(width, height, bpp, _fullscreen, true);
+	if (_cocoa_subdriver == NULL) {
+		Stop();
+		return "Could not create subdriver";
+	}
+
+	QZ_GameSizeChanged();
+
+	QZ_UpdateVideoModes();
+
+	return NULL;
+}
+
+void VideoDriver_Cocoa::MakeDirty(int left, int top, int width, int height)
+{
+	assert(_cocoa_subdriver != NULL);
+
+	_cocoa_subdriver->MakeDirty(left, top, width, height);
+}
+
+void VideoDriver_Cocoa::MainLoop()
+{
+	/* Start the main event loop */
+	[NSApp run];
+}
+
+bool VideoDriver_Cocoa::ChangeResolution(int w, int h)
+{
+	bool ret;
+
+	assert(_cocoa_subdriver != NULL);
+
+	ret = _cocoa_subdriver->ChangeResolution(w, h);
+
+	QZ_GameSizeChanged();
+
+	QZ_UpdateVideoModes();
+
+	return ret;
+}
+
+void VideoDriver_Cocoa::ToggleFullscreen(bool full_screen)
+{
+	bool oldfs;
+
+	assert(_cocoa_subdriver != NULL);
+
+	oldfs = _cocoa_subdriver->IsFullscreen();
+	if (full_screen != oldfs) {
+		int width  = _cocoa_subdriver->GetWidth();
+		int height = _cocoa_subdriver->GetHeight();
+		int bpp    = BlitterFactoryBase::GetCurrentBlitter()->GetScreenDepth();
+
+		delete _cocoa_subdriver;
+		_cocoa_subdriver = NULL;
+
+		_cocoa_subdriver = QZ_CreateSubdriver(width, height, bpp, full_screen, false);
+		if (_cocoa_subdriver == NULL) {
+			_cocoa_subdriver = QZ_CreateSubdriver(width, height, bpp, oldfs, true);
+			if (_cocoa_subdriver == NULL) error("Cocoa: Failed to create subdriver");
+		}
+	}
+
+	QZ_GameSizeChanged();
+
+	QZ_UpdateVideoModes();
+}
+
+
+/* This is needed since sometimes assert is called before the videodriver is initialized */
+void CocoaDialog(const char* title, const char* message, const char* buttonLabel)
+{
+	bool wasstarted;
+
+	_cocoa_video_dialog = true;
+
+	wasstarted = _cocoa_video_started;
+	if (_video_driver == NULL) {
+		setupApplication(); // Setup application before showing dialog
+	} else if (!_cocoa_video_started && _video_driver->Start(NULL) != NULL) {
+		fprintf(stderr, "%s: %s\n", title, message);
+		return;
+	}
+
+	NSRunAlertPanel([NSString stringWithCString: title], [NSString stringWithCString: message], [NSString stringWithCString: buttonLabel], nil, nil);
+
+	if (!wasstarted && _video_driver != NULL) _video_driver->Stop();
+
+	_cocoa_video_dialog = false;
+}
+
+/* This is needed since OS X application bundles do not have a
+ * current directory and the data files are 'somewhere' in the bundle */
+void cocoaSetApplicationBundleDir()
+{
+	char tmp[MAXPATHLEN];
+	CFURLRef url = CFBundleCopyResourcesDirectoryURL(CFBundleGetMainBundle());
+	if (CFURLGetFileSystemRepresentation(url, true, (unsigned char*)tmp, MAXPATHLEN)) {
+		AppendPathSeparator(tmp, lengthof(tmp));
+		_searchpaths[SP_APPLICATION_BUNDLE_DIR] = strdup(tmp);
+	} else {
+		_searchpaths[SP_APPLICATION_BUNDLE_DIR] = NULL;
+	}
+
+	CFRelease(url);
+}
+
+/* These are called from main() to prevent a _NSAutoreleaseNoPool error when
+ * exiting before the cocoa video driver has been loaded
+ */
+void cocoaSetupAutoreleasePool()
+{
+	_ottd_autorelease_pool = [[NSAutoreleasePool alloc] init];
+}
+
+void cocoaReleaseAutoreleasePool()
+{
+	[_ottd_autorelease_pool release];
+}
+
+#endif /* WITH_COCOA */