diff options
-rw-r--r-- | source.list | 5 | ||||
-rw-r--r-- | src/video/cocoa/cocoa_keys.h (renamed from src/video/cocoa_keys.h) | 0 | ||||
-rw-r--r-- | src/video/cocoa/cocoa_v.h | 71 | ||||
-rw-r--r-- | src/video/cocoa/cocoa_v.mm | 416 | ||||
-rw-r--r-- | src/video/cocoa/event.mm | 700 | ||||
-rw-r--r-- | src/video/cocoa/fullscreen.mm | 617 | ||||
-rw-r--r-- | src/video/cocoa/wnd_quickdraw.mm | 816 | ||||
-rw-r--r-- | src/video/cocoa_v.h | 31 | ||||
-rw-r--r-- | src/video/cocoa_v.mm | 2164 |
9 files changed, 2624 insertions, 2196 deletions
diff --git a/source.list b/source.list index 67851e9d2..8742f5ca9 100644 --- a/source.list +++ b/source.list @@ -479,7 +479,10 @@ sound/null_s.cpp #end #if COCOA - video/cocoa_v.mm + video/cocoa/cocoa_v.mm + video/cocoa/event.mm + video/cocoa/fullscreen.mm + video/cocoa/wnd_quickdraw.mm sound/cocoa_s.cpp os/macosx/splash.cpp #end diff --git a/src/video/cocoa_keys.h b/src/video/cocoa/cocoa_keys.h index 1e69c1425..1e69c1425 100644 --- a/src/video/cocoa_keys.h +++ b/src/video/cocoa/cocoa_keys.h diff --git a/src/video/cocoa/cocoa_v.h b/src/video/cocoa/cocoa_v.h new file mode 100644 index 000000000..fb53fb2ee --- /dev/null +++ b/src/video/cocoa/cocoa_v.h @@ -0,0 +1,71 @@ +/* $Id$ */ + +#ifndef VIDEO_COCOA_H +#define VIDEO_COCOA_H + +#include "../video_driver.hpp" + +class VideoDriver_Cocoa: public VideoDriver { +public: + /* virtual */ const char *Start(const char * const *param); + + /* virtual */ void Stop(); + + /* virtual */ void MakeDirty(int left, int top, int width, int height); + + /* virtual */ void MainLoop(); + + /* virtual */ bool ChangeResolution(int w, int h); + + /* virtual */ void ToggleFullscreen(bool fullscreen); +}; + +class FVideoDriver_Cocoa: public VideoDriverFactory<FVideoDriver_Cocoa> { +public: + static const int priority = 10; + /* virtual */ const char *GetName() { return "cocoa"; } + /* virtual */ const char *GetDescription() { return "Cocoa Video Driver"; } + /* virtual */ Driver *CreateInstance() { return new VideoDriver_Cocoa(); } +}; + + + +class CocoaSubdriver { +public: + virtual ~CocoaSubdriver() {} + + virtual void Draw() = 0; + virtual void MakeDirty(int left, int top, int width, int height) = 0; + virtual void UpdatePalette(uint first_color, uint num_colors) = 0; + + virtual uint ListModes(OTTDPoint* modes, uint max_modes) = 0; + + virtual bool ChangeResolution(int w, int h) = 0; + + virtual bool IsFullscreen() = 0; + virtual int GetWidth() = 0; + virtual int GetHeight() = 0; + virtual void *GetPixelBuffer() = 0; + + /* Convert local coordinate to window server (CoreGraphics) coordinate */ + virtual CGPoint PrivateLocalToCG(NSPoint* p) = 0; + + virtual NSPoint GetMouseLocation(NSEvent *event) = 0; + virtual bool MouseIsInsideView(NSPoint *pt) = 0; + + virtual bool IsActive() = 0; +}; + +extern CocoaSubdriver* _cocoa_subdriver; + +CocoaSubdriver *QZ_CreateFullscreenSubdriver(int width, int height, int bpp); +CocoaSubdriver *QZ_CreateWindowQuickdrawSubdriver(int width, int height, int bpp); + +void QZ_GameSizeChanged(); + +void QZ_GameLoop(); + +void QZ_ShowMouse(); +void QZ_HideMouse(); + +#endif /* VIDEO_COCOA_H */ diff --git a/src/video/cocoa/cocoa_v.mm b/src/video/cocoa/cocoa_v.mm new file mode 100644 index 000000000..7e09b3d7d --- /dev/null +++ b/src/video/cocoa/cocoa_v.mm @@ -0,0 +1,416 @@ +/* $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) +{ + // For now there is only the quickdraw window mode subdriver, but a pure quartz one should be added. + 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 */ diff --git a/src/video/cocoa/event.mm b/src/video/cocoa/event.mm new file mode 100644 index 000000000..9151f4002 --- /dev/null +++ b/src/video/cocoa/event.mm @@ -0,0 +1,700 @@ +/* $Id$ */ + +/****************************************************************************** + * Cocoa video driver * + * Known things left to do: * + * Nothing at the moment. * + ******************************************************************************/ + +#ifdef WITH_COCOA + +#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. + */ + + +/* 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 + +/* Right Mouse Button Emulation enum */ +enum { + RMBE_COMMAND, + RMBE_CONTROL, + RMBE_OFF, +}; + + +static bool _show_mouse = true; +static unsigned int _current_mods; +static bool _tab_is_down; +static bool _emulating_right_button; +#ifdef _DEBUG +static uint32 _tEvent; +#endif + + +static uint32 GetTick() +{ + struct timeval tim; + + gettimeofday(&tim, NULL); + return tim.tv_usec / 1000 + tim.tv_sec * 1000; +} + + +void QZ_ShowMouse() +{ + if (!_show_mouse) { + [ NSCursor unhide ]; + _show_mouse = true; + + // Hide the openttd cursor when leaving the window + if (_cocoa_subdriver != NULL) + UndrawMouseCursor(); + _cursor.in_window = false; + } +} + +void QZ_HideMouse() +{ + if (_show_mouse) { + /* + * Don't hide the cursor when compiling in debug mode. + * Note: Not hiding the cursor will cause artefacts around it in 8bpp fullscreen mode. + */ +#ifndef _DEBUG + [ NSCursor hide ]; +#endif + _show_mouse = false; + + // Show the openttd cursor again + _cursor.in_window = true; + } +} + +static void QZ_WarpCursor(int x, int y) +{ + NSPoint p; + CGPoint cgp; + + assert(_cocoa_subdriver); + + /* Only allow warping when in foreground */ + if (![ NSApp isActive ]) return; + + p = NSMakePoint(x, y); + cgp = _cocoa_subdriver->PrivateLocalToCG(&p); + + /* this is the magic call that fixes cursor "freezing" after warp */ + CGSetLocalEventsSuppressionInterval(0.0); + /* Do the actual warp */ + CGWarpMouseCursorPosition(cgp); + + /* Generate the mouse moved event */ +} + + +static void QZ_CheckPaletteAnim() +{ + if (_pal_count_dirty != 0) { + Blitter *blitter = BlitterFactoryBase::GetCurrentBlitter(); + + switch (blitter->UsePaletteAnimation()) { + case Blitter::PALETTE_ANIMATION_VIDEO_BACKEND: + _cocoa_subdriver->UpdatePalette(_pal_first_dirty, _pal_count_dirty); + break; + + case Blitter::PALETTE_ANIMATION_BLITTER: + blitter->PaletteAnimate(_pal_first_dirty, _pal_count_dirty); + break; + + case Blitter::PALETTE_ANIMATION_NONE: + break; + + default: + NOT_REACHED(); + } + _pal_count_dirty = 0; + } +} + + + +struct VkMapping { + unsigned short vk_from; + byte map_to; +}; + +#define AS(x, z) {x, z} + +static const VkMapping _vk_mapping[] = { + AS(QZ_BACKQUOTE, WKC_BACKQUOTE), // key left of '1' + AS(QZ_BACKQUOTE2, WKC_BACKQUOTE), // some keyboards have it on another scancode + + /* Pageup stuff + up/down */ + AS(QZ_PAGEUP, WKC_PAGEUP), + AS(QZ_PAGEDOWN, WKC_PAGEDOWN), + + AS(QZ_UP, WKC_UP), + AS(QZ_DOWN, WKC_DOWN), + AS(QZ_LEFT, WKC_LEFT), + AS(QZ_RIGHT, WKC_RIGHT), + + AS(QZ_HOME, WKC_HOME), + AS(QZ_END, WKC_END), + + AS(QZ_INSERT, WKC_INSERT), + AS(QZ_DELETE, WKC_DELETE), + + /* Letters. QZ_[a-z] is not in numerical order so we can't use AM(...) */ + AS(QZ_a, 'A'), + AS(QZ_b, 'B'), + AS(QZ_c, 'C'), + AS(QZ_d, 'D'), + AS(QZ_e, 'E'), + AS(QZ_f, 'F'), + AS(QZ_g, 'G'), + AS(QZ_h, 'H'), + AS(QZ_i, 'I'), + AS(QZ_j, 'J'), + AS(QZ_k, 'K'), + AS(QZ_l, 'L'), + AS(QZ_m, 'M'), + AS(QZ_n, 'N'), + AS(QZ_o, 'O'), + AS(QZ_p, 'P'), + AS(QZ_q, 'Q'), + AS(QZ_r, 'R'), + AS(QZ_s, 'S'), + AS(QZ_t, 'T'), + AS(QZ_u, 'U'), + AS(QZ_v, 'V'), + AS(QZ_w, 'W'), + AS(QZ_x, 'X'), + AS(QZ_y, 'Y'), + AS(QZ_z, 'Z'), + /* Same thing for digits */ + AS(QZ_0, '0'), + AS(QZ_1, '1'), + AS(QZ_2, '2'), + AS(QZ_3, '3'), + AS(QZ_4, '4'), + AS(QZ_5, '5'), + AS(QZ_6, '6'), + AS(QZ_7, '7'), + AS(QZ_8, '8'), + AS(QZ_9, '9'), + + AS(QZ_ESCAPE, WKC_ESC), + AS(QZ_PAUSE, WKC_PAUSE), + AS(QZ_BACKSPACE, WKC_BACKSPACE), + + AS(QZ_SPACE, WKC_SPACE), + AS(QZ_RETURN, WKC_RETURN), + AS(QZ_TAB, WKC_TAB), + + /* Function keys */ + AS(QZ_F1, WKC_F1), + AS(QZ_F2, WKC_F2), + AS(QZ_F3, WKC_F3), + AS(QZ_F4, WKC_F4), + AS(QZ_F5, WKC_F5), + AS(QZ_F6, WKC_F6), + AS(QZ_F7, WKC_F7), + AS(QZ_F8, WKC_F8), + AS(QZ_F9, WKC_F9), + AS(QZ_F10, WKC_F10), + AS(QZ_F11, WKC_F11), + AS(QZ_F12, WKC_F12), + + /* Numeric part */ + AS(QZ_KP0, WKC_NUM_0), + AS(QZ_KP1, WKC_NUM_1), + AS(QZ_KP2, WKC_NUM_2), + AS(QZ_KP3, WKC_NUM_3), + AS(QZ_KP4, WKC_NUM_4), + AS(QZ_KP5, WKC_NUM_5), + AS(QZ_KP6, WKC_NUM_6), + AS(QZ_KP7, WKC_NUM_7), + AS(QZ_KP8, WKC_NUM_8), + AS(QZ_KP9, WKC_NUM_9), + AS(QZ_KP_DIVIDE, WKC_NUM_DIV), + AS(QZ_KP_MULTIPLY, WKC_NUM_MUL), + AS(QZ_KP_MINUS, WKC_NUM_MINUS), + AS(QZ_KP_PLUS, WKC_NUM_PLUS), + AS(QZ_KP_ENTER, WKC_NUM_ENTER), + AS(QZ_KP_PERIOD, WKC_NUM_DECIMAL), + + /* Other non-letter keys */ + AS(QZ_SLASH, WKC_SLASH), + AS(QZ_SEMICOLON, WKC_SEMICOLON), + AS(QZ_EQUALS, WKC_EQUALS), + AS(QZ_LEFTBRACKET, WKC_L_BRACKET), + AS(QZ_BACKSLASH, WKC_BACKSLASH), + AS(QZ_RIGHTBRACKET, WKC_R_BRACKET), + + AS(QZ_QUOTE, WKC_SINGLEQUOTE), + AS(QZ_COMMA, WKC_COMMA), + AS(QZ_MINUS, WKC_MINUS), + AS(QZ_PERIOD, WKC_PERIOD) +}; + + +static uint32 QZ_MapKey(unsigned short sym) +{ + const VkMapping *map; + uint32 key = 0; + + for (map = _vk_mapping; map != endof(_vk_mapping); ++map) { + if (sym == map->vk_from) { + key = map->map_to; + break; + } + } + + if (_current_mods & NSShiftKeyMask) key |= WKC_SHIFT; + if (_current_mods & NSControlKeyMask) key |= (_patches.right_mouse_btn_emulation != RMBE_CONTROL ? WKC_CTRL : WKC_META); + if (_current_mods & NSAlternateKeyMask) key |= WKC_ALT; + if (_current_mods & NSCommandKeyMask) key |= (_patches.right_mouse_btn_emulation != RMBE_CONTROL ? WKC_META : WKC_CTRL); + + return key << 16; +} + +static void QZ_KeyEvent(unsigned short keycode, unsigned short unicode, BOOL down) +{ + switch (keycode) { + case QZ_UP: SB(_dirkeys, 1, 1, down); break; + case QZ_DOWN: SB(_dirkeys, 3, 1, down); break; + case QZ_LEFT: SB(_dirkeys, 0, 1, down); break; + case QZ_RIGHT: SB(_dirkeys, 2, 1, down); break; + + case QZ_TAB: _tab_is_down = down; break; + + case QZ_RETURN: + case QZ_f: + if (down && (_current_mods & NSCommandKeyMask)) { + _video_driver->ToggleFullscreen(!_fullscreen); + } + break; + } + + if (down) { + uint32 pressed_key = QZ_MapKey(keycode) | unicode; + HandleKeypress(pressed_key); + DEBUG(driver, 2, "cocoa_v: QZ_KeyEvent: %x (%x), down, mapping: %x", keycode, unicode, pressed_key); + } else { + DEBUG(driver, 2, "cocoa_v: QZ_KeyEvent: %x (%x), up", keycode, unicode); + } +} + +static void QZ_DoUnsidedModifiers(unsigned int newMods) +{ + const int mapping[] = { QZ_CAPSLOCK, QZ_LSHIFT, QZ_LCTRL, QZ_LALT, QZ_LMETA }; + + int i; + unsigned int bit; + + if (_current_mods == newMods) return; + + /* Iterate through the bits, testing each against the current modifiers */ + for (i = 0, bit = NSAlphaShiftKeyMask; bit <= NSCommandKeyMask; bit <<= 1, ++i) { + unsigned int currentMask, newMask; + + currentMask = _current_mods & bit; + newMask = newMods & bit; + + if (currentMask && currentMask != newMask) { /* modifier up event */ + /* If this was Caps Lock, we need some additional voodoo to make SDL happy (is this needed in ottd?) */ + if (bit == NSAlphaShiftKeyMask) QZ_KeyEvent(mapping[i], 0, YES); + QZ_KeyEvent(mapping[i], 0, NO); + } else if (newMask && currentMask != newMask) { /* modifier down event */ + QZ_KeyEvent(mapping[i], 0, YES); + /* If this was Caps Lock, we need some additional voodoo to make SDL happy (is this needed in ottd?) */ + if (bit == NSAlphaShiftKeyMask) QZ_KeyEvent(mapping[i], 0, NO); + } + } + + _current_mods = newMods; +} + +static void QZ_MouseMovedEvent(int x, int y) +{ + if (_cursor.fix_at) { + int dx = x - _cursor.pos.x; + int dy = y - _cursor.pos.y; + + if (dx != 0 || dy != 0) { + _cursor.delta.x += dx; + _cursor.delta.y += dy; + + QZ_WarpCursor(_cursor.pos.x, _cursor.pos.y); + } + } else { + _cursor.delta.x = x - _cursor.pos.x; + _cursor.delta.y = y - _cursor.pos.y; + _cursor.pos.x = x; + _cursor.pos.y = y; + _cursor.dirty = true; + } + HandleMouseEvents(); +} + + +static void QZ_MouseButtonEvent(int button, BOOL down) +{ + switch (button) { + case 0: + if (down) { + _left_button_down = true; + } else { + _left_button_down = false; + _left_button_clicked = false; + } + HandleMouseEvents(); + break; + + case 1: + if (down) { + _right_button_down = true; + _right_button_clicked = true; + } else { + _right_button_down = false; + } + HandleMouseEvents(); + break; + } +} + + + + +static bool QZ_PollEvent() +{ + NSEvent *event; + NSPoint pt; + NSString *chars; +#ifdef _DEBUG + uint32 et0, et; +#endif + + assert(_cocoa_subdriver != NULL); + +#ifdef _DEBUG + et0 = GetTick(); +#endif + event = [ NSApp nextEventMatchingMask:NSAnyEventMask + untilDate: [ NSDate distantPast ] + inMode: NSDefaultRunLoopMode dequeue:YES ]; +#ifdef _DEBUG + et = GetTick(); + _tEvent+= et - et0; +#endif + + if (event == nil) return false; + if (!_cocoa_subdriver->IsActive()) { + QZ_ShowMouse(); + [NSApp sendEvent:event]; + return true; + } + + QZ_DoUnsidedModifiers( [ event modifierFlags ] ); + + switch ([event type]) { + case NSMouseMoved: + case NSOtherMouseDragged: + case NSRightMouseDragged: + case NSLeftMouseDragged: + pt = _cocoa_subdriver->GetMouseLocation(event); + if (!_cocoa_subdriver->MouseIsInsideView(&pt) && + !_emulating_right_button) { + QZ_ShowMouse(); + [NSApp sendEvent:event]; + break; + } + + QZ_HideMouse(); + QZ_MouseMovedEvent((int)pt.x, (int)pt.y); + break; + + case NSLeftMouseDown: + { + uint32 keymask = 0; + if (_patches.right_mouse_btn_emulation == RMBE_COMMAND) keymask |= NSCommandKeyMask; + if (_patches.right_mouse_btn_emulation == RMBE_CONTROL) keymask |= NSControlKeyMask; + + pt = _cocoa_subdriver->GetMouseLocation(event); + + if (!([ event modifierFlags ] & keymask) || + !_cocoa_subdriver->MouseIsInsideView(&pt)) { + [NSApp sendEvent:event]; + } + + if (!_cocoa_subdriver->MouseIsInsideView(&pt)) { + QZ_ShowMouse(); + break; + } + + QZ_HideMouse(); + QZ_MouseMovedEvent((int)pt.x, (int)pt.y); + + /* Right mouse button emulation */ + if ([ event modifierFlags ] & keymask) { + _emulating_right_button = true; + QZ_MouseButtonEvent(1, YES); + } else { + QZ_MouseButtonEvent(0, YES); + } + break; + } + case NSLeftMouseUp: + [NSApp sendEvent:event]; + + pt = _cocoa_subdriver->GetMouseLocation(event); + if (!_cocoa_subdriver->MouseIsInsideView(&pt)) { + QZ_ShowMouse(); + break; + } + + QZ_HideMouse(); + QZ_MouseMovedEvent((int)pt.x, (int)pt.y); + + /* Right mouse button emulation */ + if (_emulating_right_button) { + _emulating_right_button = false; + QZ_MouseButtonEvent(1, NO); + } else { + QZ_MouseButtonEvent(0, NO); + } + break; + + case NSRightMouseDown: + pt = _cocoa_subdriver->GetMouseLocation(event); + if (!_cocoa_subdriver->MouseIsInsideView(&pt)) { + QZ_ShowMouse(); + [NSApp sendEvent:event]; + break; + } + + QZ_HideMouse(); + QZ_MouseMovedEvent((int)pt.x, (int)pt.y); + QZ_MouseButtonEvent(1, YES); + break; + + case NSRightMouseUp: + pt = _cocoa_subdriver->GetMouseLocation(event); + if (!_cocoa_subdriver->MouseIsInsideView(&pt)) { + QZ_ShowMouse(); + [NSApp sendEvent:event]; + break; + } + + QZ_HideMouse(); + QZ_MouseMovedEvent((int)pt.x, (int)pt.y); + QZ_MouseButtonEvent(1, NO); + break; + +#if 0 + /* This is not needed since openttd currently only use two buttons */ + case NSOtherMouseDown: + pt = QZ_GetMouseLocation(event); + if (!QZ_MouseIsInsideView(&pt)) { + QZ_ShowMouse(); + [NSApp sendEvent:event]; + break; + } + + QZ_HideMouse(); + QZ_MouseMovedEvent((int)pt.x, (int)pt.y); + QZ_MouseButtonEvent([ event buttonNumber ], YES); + break; + + case NSOtherMouseUp: + pt = QZ_GetMouseLocation(event); + if (!QZ_MouseIsInsideView(&pt)) { + QZ_ShowMouse(); + [NSApp sendEvent:event]; + break; + } + + QZ_HideMouse(); + QZ_MouseMovedEvent((int)pt.x, (int)pt.y); + QZ_MouseButtonEvent([ event buttonNumber ], NO); + break; +#endif + + case NSKeyDown: + /* Quit, hide and minimize */ + switch ([event keyCode]) { + case QZ_q: + case QZ_h: + case QZ_m: + if ([ event modifierFlags ] & NSCommandKeyMask) { + [NSApp sendEvent:event]; + } + break; + } + + chars = [ event characters ]; + QZ_KeyEvent([event keyCode], [ chars length ] ? [ chars characterAtIndex:0 ] : 0, YES); + break; + + case NSKeyUp: + /* Quit, hide and minimize */ + switch ([event keyCode]) { + case QZ_q: + case QZ_h: + case QZ_m: + if ([ event modifierFlags ] & NSCommandKeyMask) { + [NSApp sendEvent:event]; + } + break; + } + + chars = [ event characters ]; + QZ_KeyEvent([event keyCode], [ chars length ] ? [ chars characterAtIndex:0 ] : 0, NO); + break; + + case NSScrollWheel: + if ([ event deltaY ] > 0.0) { /* Scroll up */ + _cursor.wheel--; + } else if ([ event deltaY ] < 0.0) { /* Scroll down */ + _cursor.wheel++; + } /* else: deltaY was 0.0 and we don't want to do anything */ + + /* Set the scroll count for scrollwheel scrolling */ + _cursor.h_wheel -= (int)([ event deltaX ]* 5 * _patches.scrollwheel_multiplier); + _cursor.v_wheel -= (int)([ event deltaY ]* 5 * _patches.scrollwheel_multiplier); + break; + + default: + [NSApp sendEvent:event]; + } + + return true; +} + + +void QZ_GameLoop() +{ + uint32 cur_ticks = GetTick(); + uint32 last_cur_ticks = cur_ticks; + uint32 next_tick = cur_ticks + 30; + uint32 pal_tick = 0; +#ifdef _DEBUG + uint32 et0, et, st0, st; +#endif + int i; + +#ifdef _DEBUG + et0 = GetTick(); + st = 0; +#endif + + _screen.dst_ptr = _cocoa_subdriver->GetPixelBuffer(); + DisplaySplashImage(); + QZ_CheckPaletteAnim(); + _cocoa_subdriver->Draw(); + CSleep(1); + + for (i = 0; i < 2; i++) GameLoop(); + + _screen.dst_ptr = _cocoa_subdriver->GetPixelBuffer(); + UpdateWindows(); + QZ_CheckPaletteAnim(); + _cocoa_subdriver->Draw(); + CSleep(1); + + for (;;) { + uint32 prev_cur_ticks = cur_ticks; // to check for wrapping + InteractiveRandom(); // randomness + + while (QZ_PollEvent()) {} + + if (_exit_game) break; + +#if defined(_DEBUG) + if (_current_mods & NSShiftKeyMask) +#else + if (_tab_is_down) +#endif + { + if (!_networking && _game_mode != GM_MENU) _fast_forward |= 2; + } else if (_fast_forward & 2) { + _fast_forward = 0; + } + + cur_ticks = GetTick(); + if (cur_ticks >= next_tick || (_fast_forward && !_pause_game) || cur_ticks < prev_cur_ticks) { + _realtime_tick += cur_ticks - last_cur_ticks; + last_cur_ticks = cur_ticks; + next_tick = cur_ticks + 30; + + _ctrl_pressed = !!(_current_mods & ( _patches.right_mouse_btn_emulation != RMBE_CONTROL ? NSControlKeyMask : NSCommandKeyMask)); + _shift_pressed = !!(_current_mods & NSShiftKeyMask); + + GameLoop(); + + _screen.dst_ptr = _cocoa_subdriver->GetPixelBuffer(); + UpdateWindows(); + if (++pal_tick > 4) { + QZ_CheckPaletteAnim(); + pal_tick = 1; + } + _cocoa_subdriver->Draw(); + } else { +#ifdef _DEBUG + st0 = GetTick(); +#endif + CSleep(1); +#ifdef _DEBUG + st += GetTick() - st0; +#endif + _screen.dst_ptr = _cocoa_subdriver->GetPixelBuffer(); + DrawChatMessage(); + DrawMouseCursor(); + _cocoa_subdriver->Draw(); + } + } + +#ifdef _DEBUG + et = GetTick(); + + DEBUG(driver, 1, "cocoa_v: nextEventMatchingMask took %i ms total", _tEvent); + DEBUG(driver, 1, "cocoa_v: game loop took %i ms total (%i ms without sleep)", et - et0, et - et0 - st); + DEBUG(driver, 1, "cocoa_v: (nextEventMatchingMask total)/(game loop total) is %f%%", (double)_tEvent / (double)(et - et0) * 100); + DEBUG(driver, 1, "cocoa_v: (nextEventMatchingMask total)/(game loop without sleep total) is %f%%", (double)_tEvent / (double)(et - et0 - st) * 100); +#endif +} + +#endif /* WITH_COCOA */ diff --git a/src/video/cocoa/fullscreen.mm b/src/video/cocoa/fullscreen.mm new file mode 100644 index 000000000..c60e0c58a --- /dev/null +++ b/src/video/cocoa/fullscreen.mm @@ -0,0 +1,617 @@ +/* $Id$ */ + +/****************************************************************************** + * Cocoa video driver * + * Known things left to do: * + * Scale© the old pixel buffer to the new one when switching resolution. * + ******************************************************************************/ + +#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. + */ + + +/* From Menus.h (according to Xcode Developer Documentation) */ +extern "C" void ShowMenuBar(); +extern "C" void HideMenuBar(); + +/* 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 + + +/* Structure for rez switch gamma fades + * We can hide the monitor flicker by setting the gamma tables to 0 + */ +#define QZ_GAMMA_TABLE_SIZE 256 + +struct OTTD_QuartzGammaTable { + CGGammaValue red[QZ_GAMMA_TABLE_SIZE]; + CGGammaValue green[QZ_GAMMA_TABLE_SIZE]; + CGGammaValue blue[QZ_GAMMA_TABLE_SIZE]; +}; + +/* Add methods to get at private members of NSScreen. + * Since there is a bug in Apple's screen switching code that does not update + * this variable when switching to fullscreen, we'll set it manually (but only + * for the main screen). + */ +@interface NSScreen (NSScreenAccess) + - (void) setFrame:(NSRect)frame; +@end + +@implementation NSScreen (NSScreenAccess) +- (void) setFrame:(NSRect)frame; +{ + _frame = frame; +} +@end + + +class FullscreenSubdriver: public CocoaSubdriver { + int display_width; + int display_height; + int display_depth; + int screen_pitch; + void* screen_buffer; + void* pixel_buffer; + + CGDirectDisplayID display_id; /* 0 == main display (only support single display) */ + CFDictionaryRef cur_mode; /* current mode of the display */ + CFDictionaryRef save_mode; /* original mode of the display */ + CGDirectPaletteRef palette; /* palette of an 8-bit display */ + + #define MAX_DIRTY_RECTS 100 + Rect dirty_rects[MAX_DIRTY_RECTS]; + int num_dirty_rects; + + + /* Gamma functions to try to hide the flash from a rez switch + * Fade the display from normal to black + * Save gamma tables for fade back to normal + */ + uint32 FadeGammaOut(OTTD_QuartzGammaTable* table) + { + CGGammaValue redTable[QZ_GAMMA_TABLE_SIZE]; + CGGammaValue greenTable[QZ_GAMMA_TABLE_SIZE]; + CGGammaValue blueTable[QZ_GAMMA_TABLE_SIZE]; + float percent; + int j; + unsigned int actual; + + if (CGGetDisplayTransferByTable( + display_id, QZ_GAMMA_TABLE_SIZE, + table->red, table->green, table->blue, &actual + ) != CGDisplayNoErr || + actual != QZ_GAMMA_TABLE_SIZE) { + return 1; + } + + memcpy(redTable, table->red, sizeof(redTable)); + memcpy(greenTable, table->green, sizeof(greenTable)); + memcpy(blueTable, table->blue, sizeof(greenTable)); + + for (percent = 1.0; percent >= 0.0; percent -= 0.01) { + for (j = 0; j < QZ_GAMMA_TABLE_SIZE; j++) { + redTable[j] = redTable[j] * percent; + greenTable[j] = greenTable[j] * percent; + blueTable[j] = blueTable[j] * percent; + } + + if (CGSetDisplayTransferByTable( + display_id, QZ_GAMMA_TABLE_SIZE, + redTable, greenTable, blueTable + ) != CGDisplayNoErr) { + CGDisplayRestoreColorSyncSettings(); + return 1; + } + + CSleep(10); + } + + return 0; + } + + /* Fade the display from black to normal + * Restore previously saved gamma values + */ + uint32 FadeGammaIn(const OTTD_QuartzGammaTable* table) + { + CGGammaValue redTable[QZ_GAMMA_TABLE_SIZE]; + CGGammaValue greenTable[QZ_GAMMA_TABLE_SIZE]; + CGGammaValue blueTable[QZ_GAMMA_TABLE_SIZE]; + float percent; + int j; + + memset(redTable, 0, sizeof(redTable)); + memset(greenTable, 0, sizeof(greenTable)); + memset(blueTable, 0, sizeof(greenTable)); + + for (percent = 0.0; percent <= 1.0; percent += 0.01) { + for (j = 0; j < QZ_GAMMA_TABLE_SIZE; j++) { + redTable[j] = table->red[j] * percent; + greenTable[j] = table->green[j] * percent; + blueTable[j] = table->blue[j] * percent; + } + + if (CGSetDisplayTransferByTable( + display_id, QZ_GAMMA_TABLE_SIZE, + redTable, greenTable, blueTable + ) != CGDisplayNoErr) { + CGDisplayRestoreColorSyncSettings(); + return 1; + } + + CSleep(10); + } + + return 0; + } + + /* Wait for the VBL to occur (estimated since we don't have a hardware interrupt) */ + void WaitForVerticalBlank() + { + /* The VBL delay is based on Ian Ollmann's RezLib <iano@cco.caltech.edu> */ + double refreshRate; + double linesPerSecond; + double target; + double position; + double adjustment; + CFNumberRef refreshRateCFNumber; + + refreshRateCFNumber = (const __CFNumber*)CFDictionaryGetValue(cur_mode, kCGDisplayRefreshRate); + if (refreshRateCFNumber == NULL) return; + + if (CFNumberGetValue(refreshRateCFNumber, kCFNumberDoubleType, &refreshRate) == 0) + return; + + if (refreshRate == 0) return; + + linesPerSecond = refreshRate * display_height; + target = display_height; + + /* Figure out the first delay so we start off about right */ + position = CGDisplayBeamPosition(display_id); + if (position > target) position = 0; + + adjustment = (target - position) / linesPerSecond; + + CSleep((uint32)(adjustment * 1000)); + } + + + bool SetVideoMode(int w, int h) + { + int exact_match; + CFNumberRef number; + int bpp; + int gamma_error; + OTTD_QuartzGammaTable gamma_table; + NSRect screen_rect; + CGError error; + NSPoint pt; + + /* Destroy any previous mode */ + if (pixel_buffer != NULL) { + free(pixel_buffer); + pixel_buffer = NULL; + } + + /* See if requested mode exists */ + cur_mode = CGDisplayBestModeForParameters(display_id, display_depth, w, h, &exact_match); + + /* If the mode wasn't an exact match, check if it has the right bpp, and update width and height */ + if (!exact_match) { + number = (const __CFNumber*) CFDictionaryGetValue(cur_mode, kCGDisplayBitsPerPixel); + CFNumberGetValue(number, kCFNumberSInt32Type, &bpp); + if (bpp != display_depth) { + DEBUG(driver, 0, "Failed to find display resolution"); + goto ERR_NO_MATCH; + } + + number = (const __CFNumber*)CFDictionaryGetValue(cur_mode, kCGDisplayWidth); + CFNumberGetValue(number, kCFNumberSInt32Type, &w); + + number = (const __CFNumber*)CFDictionaryGetValue(cur_mode, kCGDisplayHeight); + CFNumberGetValue(number, kCFNumberSInt32Type, &h); + } + + /* Fade display to zero gamma */ + gamma_error = FadeGammaOut(&gamma_table); + + /* Put up the blanking window (a window above all other windows) */ + error = CGDisplayCapture(display_id); + + if (CGDisplayNoErr != error) { + DEBUG(driver, 0, "Failed capturing display"); + goto ERR_NO_CAPTURE; + } + + /* Do the physical switch */ + if (CGDisplaySwitchToMode(display_id, cur_mode) != CGDisplayNoErr) { + DEBUG(driver, 0, "Failed switching display resolution"); + goto ERR_NO_SWITCH; + } + + screen_buffer = CGDisplayBaseAddress(display_id); + screen_pitch = CGDisplayBytesPerRow(display_id); + + display_width = CGDisplayPixelsWide(display_id); + display_height = CGDisplayPixelsHigh(display_id); + + /* Setup double-buffer emulation */ + pixel_buffer = malloc(display_width * display_height * display_depth / 8); + if (pixel_buffer == NULL) { + DEBUG(driver, 0, "Failed to allocate memory for double buffering"); + goto ERR_DOUBLEBUF; + } + + if (display_depth == 8 && !CGDisplayCanSetPalette(display_id)) { + DEBUG(driver, 0, "Not an indexed display mode."); + goto ERR_NOT_INDEXED; + } + + /* If we don't hide menu bar, it will get events and interrupt the program */ + HideMenuBar(); + + /* Fade the display to original gamma */ + if (!gamma_error) FadeGammaIn(&gamma_table); + + /* There is a bug in Cocoa where NSScreen doesn't synchronize + * with CGDirectDisplay, so the main screen's frame is wrong. + * As a result, coordinate translation produces incorrect results. + * We can hack around this bug by setting the screen rect ourselves. + * This hack should be removed if/when the bug is fixed. + */ + screen_rect = NSMakeRect(0, 0, display_width, display_height); + [ [ NSScreen mainScreen ] setFrame:screen_rect ]; + + + pt = [ NSEvent mouseLocation ]; + pt.y = display_height - pt.y; + if (MouseIsInsideView(&pt)) QZ_HideMouse(); + + UpdatePalette(0, 256); + + return true; + + /* Since the blanking window covers *all* windows (even force quit) correct recovery is crucial */ +ERR_NOT_INDEXED: + free(pixel_buffer); + pixel_buffer = NULL; +ERR_DOUBLEBUF: + CGDisplaySwitchToMode(display_id, save_mode); +ERR_NO_SWITCH: + CGReleaseAllDisplays(); +ERR_NO_CAPTURE: + if (!gamma_error) FadeGammaIn(&gamma_table); +ERR_NO_MATCH: + display_width = 0; + display_height = 0; + + return false; + } + + void RestoreVideoMode() + { + /* Release fullscreen resources */ + OTTD_QuartzGammaTable gamma_table; + int gamma_error; + NSRect screen_rect; + + gamma_error = FadeGammaOut(&gamma_table); + + /* Restore original screen resolution/bpp */ + CGDisplaySwitchToMode(display_id, save_mode); + CGReleaseAllDisplays(); + ShowMenuBar(); + /* Reset the main screen's rectangle + * See comment in SetVideoMode for why we do this + */ + screen_rect = NSMakeRect(0, 0, CGDisplayPixelsWide(display_id), CGDisplayPixelsHigh(display_id)); + [ [ NSScreen mainScreen ] setFrame:screen_rect ]; + + QZ_ShowMouse(); + + /* Destroy the pixel buffer */ + if (pixel_buffer != NULL) { + free(pixel_buffer); + pixel_buffer = NULL; + } + + if (!gamma_error) FadeGammaIn(&gamma_table); + + display_width = 0; + display_height = 0; + } + +public: + FullscreenSubdriver(int bpp) + { + if (bpp != 8 && bpp != 32) { + error("Cocoa: This video driver only supports 8 and 32 bpp blitters."); + } + + /* Initialize the video settings; this data persists between mode switches */ + display_id = kCGDirectMainDisplay; + save_mode = CGDisplayCurrentMode(display_id); + + if (bpp == 8) palette = CGPaletteCreateDefaultColorPalette(); + + display_width = 0; + display_height = 0; + display_depth = bpp; + pixel_buffer = NULL; + + num_dirty_rects = MAX_DIRTY_RECTS; + } + + virtual ~FullscreenSubdriver() + { + RestoreVideoMode(); + } + + virtual void Draw() + { + const uint8* src = (uint8*) pixel_buffer; + uint8* dst = (uint8*) screen_buffer; + uint pitch = screen_pitch; + uint width = display_width; + uint num_dirty = num_dirty_rects; + uint bytesperpixel = display_depth / 8; + uint i; + + /* Check if we need to do anything */ + if (num_dirty == 0) return; + + if (num_dirty >= MAX_DIRTY_RECTS) { + num_dirty = 1; + dirty_rects[0].left = 0; + dirty_rects[0].top = 0; + dirty_rects[0].right = display_width; + dirty_rects[0].bottom = display_height; + } + + WaitForVerticalBlank(); + /* Build the region of dirty rectangles */ + for (i = 0; i < num_dirty; i++) { + uint y = dirty_rects[i].top; + uint left = dirty_rects[i].left; + uint length = dirty_rects[i].right - left; + uint bottom = dirty_rects[i].bottom; + + for (; y < bottom; y++) { + memcpy(dst + y * pitch + left * bytesperpixel, src + y * width * bytesperpixel + left * bytesperpixel, length * bytesperpixel); + } + } + + num_dirty_rects = 0; + } + + virtual void MakeDirty(int left, int top, int width, int height) + { + if (num_dirty_rects < MAX_DIRTY_RECTS) { + dirty_rects[num_dirty_rects].left = left; + dirty_rects[num_dirty_rects].top = top; + dirty_rects[num_dirty_rects].right = left + width; + dirty_rects[num_dirty_rects].bottom = top + height; + } + num_dirty_rects++; + } + + virtual void UpdatePalette(uint first_color, uint num_colors) + { + CGTableCount index; + CGDeviceColor color; + + if (display_depth != 8) + return; + + for (index = first_color; index < first_color+num_colors; index++) { + /* Clamp colors between 0.0 and 1.0 */ + color.red = _cur_palette[index].r / 255.0; + color.blue = _cur_palette[index].b / 255.0; + color.green = _cur_palette[index].g / 255.0; + + CGPaletteSetColorAtIndex(palette, color, index); + } + + CGDisplaySetPalette(display_id, palette); + } + + virtual uint ListModes(OTTDPoint* modes, uint max_modes) + { + CFArrayRef mode_list; + CFIndex num_modes; + CFIndex i; + uint count = 0; + + mode_list = CGDisplayAvailableModes(display_id); + num_modes = CFArrayGetCount(mode_list); + + /* Build list of modes with the requested bpp */ + for (i = 0; i < num_modes && count < max_modes; i++) { + CFDictionaryRef onemode; + CFNumberRef number; + int bpp; + int intvalue; + bool hasMode; + uint16 width, height; + + onemode = (const __CFDictionary*)CFArrayGetValueAtIndex(mode_list, i); + number = (const __CFNumber*)CFDictionaryGetValue(onemode, kCGDisplayBitsPerPixel); + CFNumberGetValue (number, kCFNumberSInt32Type, &bpp); + + if (bpp != display_depth) continue; + + number = (const __CFNumber*)CFDictionaryGetValue(onemode, kCGDisplayWidth); + CFNumberGetValue(number, kCFNumberSInt32Type, &intvalue); + width = (uint16)intvalue; + + number = (const __CFNumber*)CFDictionaryGetValue(onemode, kCGDisplayHeight); + CFNumberGetValue(number, kCFNumberSInt32Type, &intvalue); + height = (uint16)intvalue; + + /* Check if mode is already in the list */ + { + uint i; + hasMode = false; + for (i = 0; i < count; i++) { + if (modes[i].x == width && modes[i].y == height) { + hasMode = true; + break; + } + } + } + + if (hasMode) continue; + + /* Add mode to the list */ + modes[count].x = width; + modes[count].y = height; + count++; + } + + /* Sort list smallest to largest */ + { + uint i, j; + for (i = 0; i < count; i++) { + for (j = 0; j < count-1; j++) { + if (modes[j].x > modes[j + 1].x || ( + modes[j].x == modes[j + 1].x && + modes[j].y > modes[j + 1].y + )) { + uint tmpw = modes[j].x; + uint tmph = modes[j].y; + + modes[j].x = modes[j + 1].x; + modes[j].y = modes[j + 1].y; + + modes[j + 1].x = tmpw; + modes[j + 1].y = tmph; + } + } + } + } + + return count; + } + + virtual bool ChangeResolution(int w, int h) + { + int old_width = display_width; + int old_height = display_height; + + if (SetVideoMode(w, h)) + return true; + + if (old_width != 0 && old_height != 0) + SetVideoMode(old_width, old_height); + + return false; + } + + virtual bool IsFullscreen() + { + return true; + } + + virtual int GetWidth() + { + return display_width; + } + + virtual int GetHeight() + { + return display_height; + } + + virtual void *GetPixelBuffer() + { + return pixel_buffer; + } + + /* + Convert local coordinate to window server (CoreGraphics) coordinate. + In fullscreen mode this just means copying the coords. + */ + virtual CGPoint PrivateLocalToCG(NSPoint* p) + { + CGPoint cgp; + + cgp.x = p->x; + cgp.y = p->y; + + return cgp; + } + + virtual NSPoint GetMouseLocation(NSEvent *event) + { + NSPoint pt; + + pt = [ NSEvent mouseLocation ]; + pt.y = display_height - pt.y; + + return pt; + } + + virtual bool MouseIsInsideView(NSPoint *pt) + { + return pt->x >= 0 && pt->y >= 0 && pt->x < display_width && pt->y < display_height; + } + + virtual bool IsActive() + { + return true; + } +}; + +CocoaSubdriver *QZ_CreateFullscreenSubdriver(int width, int height, int bpp) +{ + FullscreenSubdriver *ret; + + ret = new FullscreenSubdriver(bpp); + + if (!ret->ChangeResolution(width, height)) { + delete ret; + return NULL; + } + + return ret; +} + +#endif /* WITH_COCOA */ diff --git a/src/video/cocoa/wnd_quickdraw.mm b/src/video/cocoa/wnd_quickdraw.mm new file mode 100644 index 000000000..592c41d6e --- /dev/null +++ b/src/video/cocoa/wnd_quickdraw.mm @@ -0,0 +1,816 @@ +/* $Id$ */ + +/****************************************************************************** + * Cocoa video driver * + * Known things left to do: * + * List available resolutions. * + ******************************************************************************/ + +#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. + */ + + +/* 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 + + +class WindowQuickdrawSubdriver; + + +/* Subclass of NSWindow to fix genie effect and support resize events */ +@interface OTTD_QuartzWindow : NSWindow { + WindowQuickdrawSubdriver *driver; +} + +- (void)setDriver:(WindowQuickdrawSubdriver*)drv; + +- (void)miniaturize:(id)sender; +- (void)display; +- (void)setFrame:(NSRect)frameRect display:(BOOL)flag; +- (void)appDidHide:(NSNotification*)note; +- (void)appWillUnhide:(NSNotification*)note; +- (void)appDidUnhide:(NSNotification*)note; +- (id)initWithContentRect:(NSRect)contentRect styleMask:(unsigned int)styleMask backing:(NSBackingStoreType)backingType defer:(BOOL)flag; +@end + +/* Delegate for our NSWindow to send ask for quit on close */ +@interface OTTD_QuartzWindowDelegate : NSObject{ + WindowQuickdrawSubdriver *driver; +} + +- (void)setDriver:(WindowQuickdrawSubdriver*)drv; + +- (BOOL)windowShouldClose:(id)sender; +@end + +class WindowQuickdrawSubdriver: public CocoaSubdriver { + int device_width; + int device_height; + int device_depth; + + int window_width; + int window_height; + int window_pitch; + + int buffer_depth; + + void *pixel_buffer; + void *window_buffer; + + OTTD_QuartzWindow *window; + + #define MAX_DIRTY_RECTS 100 + Rect dirty_rects[MAX_DIRTY_RECTS]; + int num_dirty_rects; + + uint16 palette16[256]; + uint32 palette32[256]; + +public: + bool active; + bool setup; + + NSQuickDrawView *qdview; + +private: + void GetDeviceInfo(); + + bool SetVideoMode(int width, int height); + + /** + * This function copies 32bpp pixels from the screen buffer in 16bpp windowed mode. + * + * @param left The x coord for the left edge of the box to blit. + * @param top The y coord for the top edge of the box to blit. + * @param right The x coord for the right edge of the box to blit. + * @param bottom The y coord for the bottom edge of the box to blit. + */ + void Blit32ToView32(int left, int top, int right, int bottom); + + /** + * This function copies 8bpp pixels from the screen buffer in 32bpp windowed mode. + * + * @param left The x coord for the left edge of the box to blit. + * @param top The y coord for the top edge of the box to blit. + * @param right The x coord for the right edge of the box to blit. + * @param bottom The y coord for the bottom edge of the box to blit. + */ + void BlitIndexedToView32(int left, int top, int right, int bottom); + + /** + * This function copies 8bpp pixels from the screen buffer in 16bpp windowed mode. + * + * @param left The x coord for the left edge of the box to blit. + * @param top The y coord for the top edge of the box to blit. + * @param right The x coord for the right edge of the box to blit. + * @param bottom The y coord for the bottom edge of the box to blit. + */ + void BlitIndexedToView16(int left, int top, int right, int bottom); + + inline void BlitToView(int left, int top, int right, int bottom); + void DrawResizeIcon(); + + +public: + WindowQuickdrawSubdriver(int bpp); + virtual ~WindowQuickdrawSubdriver(); + + virtual void Draw(); + virtual void MakeDirty(int left, int top, int width, int height); + virtual void UpdatePalette(uint first_color, uint num_colors); + + virtual uint ListModes(OTTDPoint* modes, uint max_modes); + + virtual bool ChangeResolution(int w, int h); + + virtual bool IsFullscreen() { return false; } + + virtual int GetWidth() { return window_width; } + virtual int GetHeight() { return window_height; } + virtual void *GetPixelBuffer() { return pixel_buffer; } + + /* Convert local coordinate to window server (CoreGraphics) coordinate */ + virtual CGPoint PrivateLocalToCG(NSPoint* p); + + virtual NSPoint GetMouseLocation(NSEvent *event); + virtual bool MouseIsInsideView(NSPoint *pt); + + virtual bool IsActive() { return active; } + + + void SetPortAlphaOpaque(); + bool WindowResized(); +}; + + +@implementation OTTD_QuartzWindow + +- (void)setDriver:(WindowQuickdrawSubdriver*)drv +{ + driver = drv; +} + + +/* we override these methods to fix the miniaturize animation/dock icon bug */ +- (void)miniaturize:(id)sender +{ + /* make the alpha channel opaque so anim won't have holes in it */ + driver->SetPortAlphaOpaque (); + + /* window is hidden now */ + driver->active = false; + + QZ_ShowMouse(); + + [ super miniaturize:sender ]; +} + +- (void)display +{ + /* This method fires just before the window deminaturizes from the Dock. + * We'll save the current visible surface, let the window manager redraw any + * UI elements, and restore the surface. This way, no expose event + * is required, and the deminiaturize works perfectly. + */ + + driver->SetPortAlphaOpaque(); + + /* save current visible surface */ + [ self cacheImageInRect:[ driver->qdview frame ] ]; + + /* let the window manager redraw controls, border, etc */ + [ super display ]; + + /* restore visible surface */ + [ self restoreCachedImage ]; + + /* window is visible again */ + driver->active = true; +} + +- (void)setFrame:(NSRect)frameRect display:(BOOL)flag +{ + [ super setFrame:frameRect display:flag ]; + + /* Don't do anything if the window is currently being created */ + if (driver->setup) return; + + if (!driver->WindowResized()) + error("Cocoa: Failed to resize window."); +} + +- (void)appDidHide:(NSNotification*)note +{ + driver->active = false; +} + + +- (void)appWillUnhide:(NSNotification*)note +{ + driver->SetPortAlphaOpaque (); + + /* save current visible surface */ + [ self cacheImageInRect:[ driver->qdview frame ] ]; +} + +- (void)appDidUnhide:(NSNotification*)note +{ + /* restore cached image, since it may not be current, post expose event too */ + [ self restoreCachedImage ]; + + driver->active = true; +} + + +- (id)initWithContentRect:(NSRect)contentRect styleMask:(unsigned int)styleMask backing:(NSBackingStoreType)backingType defer:(BOOL)flag +{ + /* Make our window subclass receive these application notifications */ + [ [ NSNotificationCenter defaultCenter ] addObserver:self + selector:@selector(appDidHide:) name:NSApplicationDidHideNotification object:NSApp ]; + + [ [ NSNotificationCenter defaultCenter ] addObserver:self + selector:@selector(appDidUnhide:) name:NSApplicationDidUnhideNotification object:NSApp ]; + + [ [ NSNotificationCenter defaultCenter ] addObserver:self + selector:@selector(appWillUnhide:) name:NSApplicationWillUnhideNotification object:NSApp ]; + + return [ super initWithContentRect:contentRect styleMask:styleMask backing:backingType defer:flag ]; +} + +@end + +@implementation OTTD_QuartzWindowDelegate +- (void)setDriver:(WindowQuickdrawSubdriver*)drv +{ + driver = drv; +} + +- (BOOL)windowShouldClose:(id)sender +{ + HandleExitGameRequest(); + + return NO; +} + +- (void)windowDidBecomeKey:(NSNotification*)aNotification +{ + driver->active = true; +} + +- (void)windowDidResignKey:(NSNotification*)aNotification +{ + driver->active = false; +} + +- (void)windowDidBecomeMain:(NSNotification*)aNotification +{ + driver->active = true; +} + +- (void)windowDidResignMain:(NSNotification*)aNotification +{ + driver->active = false; +} + +@end + + +static const int _resize_icon_width = 16; +static const int _resize_icon_height = 16; + +static bool _resize_icon[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, + 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, + 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, + 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, + 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, + 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, + 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, + 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0 +}; + + + +extern const char _openttd_revision[]; + + +void WindowQuickdrawSubdriver::GetDeviceInfo() +{ + CFDictionaryRef cur_mode; + + /* Initialize the video settings; this data persists between mode switches */ + cur_mode = CGDisplayCurrentMode(kCGDirectMainDisplay); + + /* Gather some information that is useful to know about the display */ + CFNumberGetValue( + (const __CFNumber*)CFDictionaryGetValue(cur_mode, kCGDisplayBitsPerPixel), + kCFNumberSInt32Type, &device_depth + ); + + CFNumberGetValue( + (const __CFNumber*)CFDictionaryGetValue(cur_mode, kCGDisplayWidth), + kCFNumberSInt32Type, &device_width + ); + + CFNumberGetValue( + (const __CFNumber*)CFDictionaryGetValue(cur_mode, kCGDisplayHeight), + kCFNumberSInt32Type, &device_height + ); +} + +bool WindowQuickdrawSubdriver::SetVideoMode(int width, int height) +{ + char caption[50]; + NSString *nsscaption; + unsigned int style; + NSRect contentRect; + BOOL isCustom = NO; + bool ret; + + setup = true; + + GetDeviceInfo(); + + if (buffer_depth > device_depth) { + DEBUG(driver, 0, "Cannot use a blitter with a higer screen depth than the display when running in windowed mode."); + setup = false; + return false; + } + + if (width > device_width) + width = device_width; + if (height > device_height) + height = device_height; + + contentRect = NSMakeRect(0, 0, width, height); + + /* Check if we should recreate the window */ + if (window == nil) { + OTTD_QuartzWindowDelegate *delegate; + + /* Set the window style */ + style = NSTitledWindowMask; + style |= (NSMiniaturizableWindowMask | NSClosableWindowMask); + style |= NSResizableWindowMask; + + /* Manually create a window, avoids having a nib file resource */ + window = [ [ OTTD_QuartzWindow alloc ] + initWithContentRect:contentRect + styleMask:style + backing:NSBackingStoreBuffered + defer:NO ]; + + if (window == nil) { + DEBUG(driver, 0, "Could not create the Cocoa window."); + setup = false; + return false; + } + + [ window setDriver:this ]; + + snprintf(caption, sizeof(caption), "OpenTTD %s", _openttd_revision); + nsscaption = [ [ NSString alloc ] initWithCString:caption ]; + [ window setTitle:nsscaption ]; + [ window setMiniwindowTitle:nsscaption ]; + [ nsscaption release ]; + + [ window setAcceptsMouseMovedEvents:YES ]; + [ window setViewsNeedDisplay:NO ]; + + delegate = [ [ OTTD_QuartzWindowDelegate alloc ] init ]; + [ delegate setDriver:this ]; + [ window setDelegate: [ delegate autorelease ] ]; + } else { + /* We already have a window, just change its size */ + if (!isCustom) { + [ window setContentSize:contentRect.size ]; + // Ensure frame height - title bar height >= view height + contentRect.size.height = Clamp(height, 0, [ window frame ].size.height - 22 /* 22 is the height of title bar of window*/); + height = contentRect.size.height; + [ qdview setFrameSize:contentRect.size ]; + } + } + + // Update again + window_width = width; + window_height = height; + + [ window center ]; + + /* Only recreate the view if it doesn't already exist */ + if (qdview == nil) { + qdview = [ [ NSQuickDrawView alloc ] initWithFrame:contentRect ]; + if (qdview == nil) { + DEBUG(driver, 0, "Could not create the Quickdraw view."); + setup = false; + return false; + } + + [ qdview setAutoresizingMask: NSViewWidthSizable | NSViewHeightSizable ]; + [ [ window contentView ] addSubview:qdview ]; + [ qdview release ]; + [ window makeKeyAndOrderFront:nil ]; + } + + ret = WindowResized(); + + UpdatePalette(0, 256); + + setup = false; + + return ret; +} + +void WindowQuickdrawSubdriver::Blit32ToView32(int left, int top, int right, int bottom) +{ + const uint32* src = (uint32*)pixel_buffer; + uint32* dst = (uint32*)window_buffer; + uint width = window_width; + uint pitch = window_pitch / 4; + int y; + + + dst += top * pitch + left; + src += top * width + left; + + for (y = top; y < bottom; y++, dst+= pitch, src+= width) { + memcpy(dst, src, (right - left) * 4); + } +} + +void WindowQuickdrawSubdriver::BlitIndexedToView32(int left, int top, int right, int bottom) +{ + const uint32* pal = palette32; + const uint8* src = (uint8*)pixel_buffer; + uint32* dst = (uint32*)window_buffer; + uint width = window_width; + uint pitch = window_pitch / 4; + int x; + int y; + + for (y = top; y < bottom; y++) { + for (x = left; x < right; x++) { + dst[y * pitch + x] = pal[src[y * width + x]]; + } + } +} + +void WindowQuickdrawSubdriver::BlitIndexedToView16(int left, int top, int right, int bottom) +{ + const uint16* pal = palette16; + const uint8* src = (uint8*)pixel_buffer; + uint16* dst = (uint16*)window_buffer; + uint width = window_width; + uint pitch = window_pitch / 2; + int x; + int y; + + for (y = top; y < bottom; y++) { + for (x = left; x < right; x++) { + dst[y * pitch + x] = pal[src[y * width + x]]; + } + } +} + + +inline void WindowQuickdrawSubdriver::BlitToView(int left, int top, int right, int bottom) +{ + switch (device_depth) { + case 32: + switch(buffer_depth) { + case 32: + Blit32ToView32(left, top, right, bottom); + break; + case 8: + BlitIndexedToView32(left, top, right, bottom); + break; + } + break; + case 16: + BlitIndexedToView16(left, top, right, bottom); + break; + } +} + +void WindowQuickdrawSubdriver::DrawResizeIcon() +{ + int xoff = window_width - _resize_icon_width; + int yoff = window_height - _resize_icon_height; + int x; + int y; + + switch (device_depth) { + case 32: + for (y = 0; y < _resize_icon_height; y++) { + uint32* trg = (uint32*)window_buffer + (yoff + y) * window_pitch / 4 + xoff; + + for (x = 0; x < _resize_icon_width; x++, trg++) { + if (_resize_icon[y * _resize_icon_width + x]) *trg = 0xff000000; + } + } + break; + case 16: + for (y = 0; y < _resize_icon_height; y++) { + uint16* trg = (uint16*)window_buffer + (yoff + y) * window_pitch / 2 + xoff; + + for (x = 0; x < _resize_icon_width; x++, trg++) { + if (_resize_icon[y * _resize_icon_width + x]) *trg = 0x0000; + } + } + break; + } +} + + +WindowQuickdrawSubdriver::WindowQuickdrawSubdriver(int bpp) +{ + window_width = 0; + window_height = 0; + buffer_depth = bpp; + pixel_buffer = NULL; + active = false; + setup = false; + + window = nil; + qdview = nil; + + num_dirty_rects = MAX_DIRTY_RECTS; +} + +WindowQuickdrawSubdriver::~WindowQuickdrawSubdriver() +{ + QZ_ShowMouse(); + + /* Release window mode resources */ + if (window != nil) [ window close ]; + + free(pixel_buffer); +} + +void WindowQuickdrawSubdriver::Draw() +{ + int i; + RgnHandle dirty, temp; + + /* Check if we need to do anything */ + if (num_dirty_rects == 0 || + [ window isMiniaturized ]) { + return; + } + + if (num_dirty_rects >= MAX_DIRTY_RECTS) { + num_dirty_rects = 1; + dirty_rects[0].left = 0; + dirty_rects[0].top = 0; + dirty_rects[0].right = window_width; + dirty_rects[0].bottom = window_height; + } + + dirty = NewRgn(); + temp = NewRgn(); + + SetEmptyRgn(dirty); + + /* Build the region of dirty rectangles */ + for (i = 0; i < num_dirty_rects; i++) { + BlitToView( + dirty_rects[i].left, + dirty_rects[i].top, + dirty_rects[i].right, + dirty_rects[i].bottom + ); + + MacSetRectRgn( + temp, + dirty_rects[i].left, + dirty_rects[i].top, + dirty_rects[i].right, + dirty_rects[i].bottom + ); + MacUnionRgn(dirty, temp, dirty); + } + + DrawResizeIcon(); + + /* Flush the dirty region */ + QDFlushPortBuffer( (OpaqueGrafPtr*) [ qdview qdPort ], dirty); + DisposeRgn(dirty); + DisposeRgn(temp); + + num_dirty_rects = 0; +} + +void WindowQuickdrawSubdriver::MakeDirty(int left, int top, int width, int height) +{ + if (num_dirty_rects < MAX_DIRTY_RECTS) { + dirty_rects[num_dirty_rects].left = left; + dirty_rects[num_dirty_rects].top = top; + dirty_rects[num_dirty_rects].right = left + width; + dirty_rects[num_dirty_rects].bottom = top + height; + } + num_dirty_rects++; +} + +void WindowQuickdrawSubdriver::UpdatePalette(uint first_color, uint num_colors) +{ + uint i; + + if (buffer_depth != 8) + return; + + switch (device_depth) { + case 32: + for (i = first_color; i < first_color + num_colors; i++) { + uint32 clr32 = 0xff000000; + clr32 |= (uint32)_cur_palette[i].r << 16; + clr32 |= (uint32)_cur_palette[i].g << 8; + clr32 |= (uint32)_cur_palette[i].b; + palette32[i] = clr32; + } + break; + case 16: + for (i = first_color; i < first_color + num_colors; i++) { + uint16 clr16 = 0x0000; + clr16 |= (uint16)((_cur_palette[i].r >> 3) & 0x1f) << 10; + clr16 |= (uint16)((_cur_palette[i].g >> 3) & 0x1f) << 5; + clr16 |= (uint16)((_cur_palette[i].b >> 3) & 0x1f); + palette16[i] = clr16; + } + break; + } + + num_dirty_rects = MAX_DIRTY_RECTS; +} + +uint WindowQuickdrawSubdriver::ListModes(OTTDPoint* modes, uint max_modes) +{ + if (max_modes == 0) return 0; + + modes[0].x = window_width; + modes[0].y = window_height; + + return 1; +} + +bool WindowQuickdrawSubdriver::ChangeResolution(int w, int h) +{ + int old_width = window_width; + int old_height = window_height; + + if (SetVideoMode(w, h)) + return true; + + if (old_width != 0 && old_height != 0) + SetVideoMode(old_width, old_height); + + return false; +} + +/* Convert local coordinate to window server (CoreGraphics) coordinate */ +CGPoint WindowQuickdrawSubdriver::PrivateLocalToCG(NSPoint* p) +{ + CGPoint cgp; + + *p = [ qdview convertPoint:*p toView: nil ]; + *p = [ window convertBaseToScreen:*p ]; + p->y = window_height - p->y; + + cgp.x = p->x; + cgp.y = p->y; + + return cgp; +} + +NSPoint WindowQuickdrawSubdriver::GetMouseLocation(NSEvent *event) +{ + NSPoint pt; + + pt = [ event locationInWindow ]; + pt = [ qdview convertPoint:pt fromView:nil ]; + + return pt; +} + +bool WindowQuickdrawSubdriver::MouseIsInsideView(NSPoint *pt) +{ + return [ qdview mouse:*pt inRect:[ qdview bounds ] ]; +} + + +/* This function makes the *game region* of the window 100% opaque. + * The genie effect uses the alpha component. Otherwise, + * it doesn't seem to matter what value it has. + */ +void WindowQuickdrawSubdriver::SetPortAlphaOpaque() +{ + if (device_depth != 32) + return; + + uint32* pixels = (uint32*)window_buffer; + uint32 pitch = window_pitch / 4; + int x, y; + + for (y = 0; y < window_height; y++) + for (x = 0; x < window_width; x++) { + pixels[y * pitch + x] |= 0xFF000000; + } +} + +bool WindowQuickdrawSubdriver::WindowResized() +{ + if (window == nil || qdview == nil) return true; + + NSRect newframe = [ qdview frame ]; + CGrafPtr thePort = (OpaqueGrafPtr*) [ qdview qdPort ]; + int voff, hoff; + + LockPortBits(thePort); + window_buffer = GetPixBaseAddr(GetPortPixMap(thePort)); + window_pitch = GetPixRowBytes(GetPortPixMap(thePort)); + UnlockPortBits(thePort); + + /* _cocoa_video_data.realpixels now points to the window's pixels + * We want it to point to the *view's* pixels + */ + voff = [ window frame ].size.height - newframe.size.height - newframe.origin.y; + hoff = [ qdview frame ].origin.x; + window_buffer = (uint8*)window_buffer + (voff * window_pitch) + hoff * (device_depth / 8); + + window_width = newframe.size.width; + window_height = newframe.size.height; + + free(pixel_buffer); + pixel_buffer = malloc(window_width * window_height * buffer_depth / 8); + if (pixel_buffer == NULL) { + DEBUG(driver, 0, "Failed to allocate pixel buffer"); + return false; + } + + QZ_GameSizeChanged(); + + /* Redraw screen */ + num_dirty_rects = MAX_DIRTY_RECTS; + + return true; +} + + +CocoaSubdriver *QZ_CreateWindowQuickdrawSubdriver(int width, int height, int bpp) +{ + WindowQuickdrawSubdriver *ret; + + ret = new WindowQuickdrawSubdriver(bpp); + + if (!ret->ChangeResolution(width, height)) { + delete ret; + return NULL; + } + + return ret; +} + +#endif /* WITH_COCOA */ diff --git a/src/video/cocoa_v.h b/src/video/cocoa_v.h deleted file mode 100644 index bb980ad91..000000000 --- a/src/video/cocoa_v.h +++ /dev/null @@ -1,31 +0,0 @@ -/* $Id$ */ - -#ifndef VIDEO_COCOA_H -#define VIDEO_COCOA_H - -#include "video_driver.hpp" - -class VideoDriver_Cocoa: public VideoDriver { -public: - /* virtual */ const char *Start(const char * const *param); - - /* virtual */ void Stop(); - - /* virtual */ void MakeDirty(int left, int top, int width, int height); - - /* virtual */ void MainLoop(); - - /* virtual */ bool ChangeResolution(int w, int h); - - /* virtual */ void ToggleFullscreen(bool fullscreen); -}; - -class FVideoDriver_Cocoa: public VideoDriverFactory<FVideoDriver_Cocoa> { -public: - static const int priority = 10; - /* virtual */ const char *GetName() { return "cocoa"; } - /* virtual */ const char *GetDescription() { return "Cocoa Video Driver"; } - /* virtual */ Driver *CreateInstance() { return new VideoDriver_Cocoa(); } -}; - -#endif /* VIDEO_COCOA_H */ diff --git a/src/video/cocoa_v.mm b/src/video/cocoa_v.mm deleted file mode 100644 index cf76ccd94..000000000 --- a/src/video/cocoa_v.mm +++ /dev/null @@ -1,2164 +0,0 @@ -/* $Id$ */ - -/****************************************************************************** - * Cocoa video driver * - * Known things left to do: * - * Nothing at the moment. * - ******************************************************************************/ - -#ifdef WITH_COCOA - -#include <AvailabilityMacros.h> -#if (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_4) -/* On 10.4, we get tons of warnings that the QuickDraw functions are deprecated. - * We know that. Don't keep bugging us about that. */ -# undef AVAILABLE_MAC_OS_X_VERSION_10_0_AND_LATER_BUT_DEPRECATED_IN_MAC_OS_X_VERSION_10_4 -# define AVAILABLE_MAC_OS_X_VERSION_10_0_AND_LATER_BUT_DEPRECATED_IN_MAC_OS_X_VERSION_10_4 AVAILABLE_MAC_OS_X_VERSION_10_0_AND_LATER -#endif - -#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); - -/* From Menus.h (according to Xcode Developer Documentation) */ -extern "C" void ShowMenuBar(); -extern "C" void HideMenuBar(); - -/* 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 - -/* Right Mouse Button Emulation enum */ -enum { - RMBE_COMMAND, - RMBE_CONTROL, - RMBE_OFF, -}; - - -/* Subclass of NSWindow to fix genie effect and support resize events */ -@interface OTTD_QuartzWindow : NSWindow -- (void)miniaturize:(id)sender; -- (void)display; -- (void)setFrame:(NSRect)frameRect display:(BOOL)flag; -- (void)appDidHide:(NSNotification*)note; -- (void)appWillUnhide:(NSNotification*)note; -- (void)appDidUnhide:(NSNotification*)note; -- (id)initWithContentRect:(NSRect)contentRect styleMask:(unsigned int)styleMask backing:(NSBackingStoreType)backingType defer:(BOOL)flag; -@end - -/* Delegate for our NSWindow to send ask for quit on close */ -@interface OTTD_QuartzWindowDelegate : NSObject -- (BOOL)windowShouldClose:(id)sender; -@end - -@interface OTTDMain : NSObject -@end - - -/* Structure for rez switch gamma fades - * We can hide the monitor flicker by setting the gamma tables to 0 - */ -#define QZ_GAMMA_TABLE_SIZE 256 - -struct OTTD_QuartzGammaTable { - CGGammaValue red[QZ_GAMMA_TABLE_SIZE]; - CGGammaValue green[QZ_GAMMA_TABLE_SIZE]; - CGGammaValue blue[QZ_GAMMA_TABLE_SIZE]; -}; - -/* Add methods to get at private members of NSScreen. - * Since there is a bug in Apple's screen switching code that does not update - * this variable when switching to fullscreen, we'll set it manually (but only - * for the main screen). - */ -@interface NSScreen (NSScreenAccess) - - (void) setFrame:(NSRect)frame; -@end - -@implementation NSScreen (NSScreenAccess) -- (void) setFrame:(NSRect)frame; -{ - _frame = frame; -} -@end - - -static void QZ_Draw(); -static void QZ_UnsetVideoMode(); -static void QZ_UpdatePalette(uint start, uint count); -static void QZ_WarpCursor(int x, int y); -static void QZ_ShowMouse(); -static void QZ_HideMouse(); - - -static NSAutoreleasePool *_ottd_autorelease_pool; -static OTTDMain *_ottd_main; - - -static struct CocoaVideoData { - bool isset; - bool issetting; - - CGDirectDisplayID display_id; /* 0 == main display (only support single display) */ - CFDictionaryRef mode; /* current mode of the display */ - CFDictionaryRef save_mode; /* original mode of the display */ - CFArrayRef mode_list; /* list of available fullscreen modes */ - CGDirectPaletteRef palette; /* palette of an 8-bit display */ - - uint32 blitter_bpp; - - uint32 device_width; - uint32 device_height; - uint32 device_bpp; - - void *realpixels; - void *pixels; - uint32 width; - uint32 height; - uint32 pitch; - bool fullscreen; - - unsigned int current_mods; - bool tab_is_down; - bool emulating_right_button; - - bool cursor_visible; - bool active; - -#ifdef _DEBUG - uint32 tEvent; -#endif - - OTTD_QuartzWindow *window; - NSQuickDrawView *qdview; - -#define MAX_DIRTY_RECTS 100 - Rect dirty_rects[MAX_DIRTY_RECTS]; - int num_dirty_rects; - - uint16 palette16[256]; - uint32 palette32[256]; -} _cocoa_video_data; - -static bool _cocoa_video_started = false; -static bool _cocoa_video_dialog = false; - - - - -/****************************************************************************** - * Game loop and accessories * - ******************************************************************************/ - -static uint32 GetTick() -{ - struct timeval tim; - - gettimeofday(&tim, NULL); - return tim.tv_usec / 1000 + tim.tv_sec * 1000; -} - -static void QZ_CheckPaletteAnim() -{ - if (_pal_count_dirty != 0) { - Blitter *blitter = BlitterFactoryBase::GetCurrentBlitter(); - - switch (blitter->UsePaletteAnimation()) { - case Blitter::PALETTE_ANIMATION_VIDEO_BACKEND: - QZ_UpdatePalette(_pal_first_dirty, _pal_count_dirty); - break; - - case Blitter::PALETTE_ANIMATION_BLITTER: - blitter->PaletteAnimate(_pal_first_dirty, _pal_count_dirty); - break; - - case Blitter::PALETTE_ANIMATION_NONE: - break; - - default: - NOT_REACHED(); - } - _pal_count_dirty = 0; - } -} - - - -struct VkMapping { - unsigned short vk_from; - byte map_to; -}; - -#define AS(x, z) {x, z} - -static const VkMapping _vk_mapping[] = { - AS(QZ_BACKQUOTE, WKC_BACKQUOTE), // key left of '1' - AS(QZ_BACKQUOTE2, WKC_BACKQUOTE), // some keyboards have it on another scancode - - /* Pageup stuff + up/down */ - AS(QZ_PAGEUP, WKC_PAGEUP), - AS(QZ_PAGEDOWN, WKC_PAGEDOWN), - - AS(QZ_UP, WKC_UP), - AS(QZ_DOWN, WKC_DOWN), - AS(QZ_LEFT, WKC_LEFT), - AS(QZ_RIGHT, WKC_RIGHT), - - AS(QZ_HOME, WKC_HOME), - AS(QZ_END, WKC_END), - - AS(QZ_INSERT, WKC_INSERT), - AS(QZ_DELETE, WKC_DELETE), - - /* Letters. QZ_[a-z] is not in numerical order so we can't use AM(...) */ - AS(QZ_a, 'A'), - AS(QZ_b, 'B'), - AS(QZ_c, 'C'), - AS(QZ_d, 'D'), - AS(QZ_e, 'E'), - AS(QZ_f, 'F'), - AS(QZ_g, 'G'), - AS(QZ_h, 'H'), - AS(QZ_i, 'I'), - AS(QZ_j, 'J'), - AS(QZ_k, 'K'), - AS(QZ_l, 'L'), - AS(QZ_m, 'M'), - AS(QZ_n, 'N'), - AS(QZ_o, 'O'), - AS(QZ_p, 'P'), - AS(QZ_q, 'Q'), - AS(QZ_r, 'R'), - AS(QZ_s, 'S'), - AS(QZ_t, 'T'), - AS(QZ_u, 'U'), - AS(QZ_v, 'V'), - AS(QZ_w, 'W'), - AS(QZ_x, 'X'), - AS(QZ_y, 'Y'), - AS(QZ_z, 'Z'), - /* Same thing for digits */ - AS(QZ_0, '0'), - AS(QZ_1, '1'), - AS(QZ_2, '2'), - AS(QZ_3, '3'), - AS(QZ_4, '4'), - AS(QZ_5, '5'), - AS(QZ_6, '6'), - AS(QZ_7, '7'), - AS(QZ_8, '8'), - AS(QZ_9, '9'), - - AS(QZ_ESCAPE, WKC_ESC), - AS(QZ_PAUSE, WKC_PAUSE), - AS(QZ_BACKSPACE, WKC_BACKSPACE), - - AS(QZ_SPACE, WKC_SPACE), - AS(QZ_RETURN, WKC_RETURN), - AS(QZ_TAB, WKC_TAB), - - /* Function keys */ - AS(QZ_F1, WKC_F1), - AS(QZ_F2, WKC_F2), - AS(QZ_F3, WKC_F3), - AS(QZ_F4, WKC_F4), - AS(QZ_F5, WKC_F5), - AS(QZ_F6, WKC_F6), - AS(QZ_F7, WKC_F7), - AS(QZ_F8, WKC_F8), - AS(QZ_F9, WKC_F9), - AS(QZ_F10, WKC_F10), - AS(QZ_F11, WKC_F11), - AS(QZ_F12, WKC_F12), - - /* Numeric part */ - AS(QZ_KP0, WKC_NUM_0), - AS(QZ_KP1, WKC_NUM_1), - AS(QZ_KP2, WKC_NUM_2), - AS(QZ_KP3, WKC_NUM_3), - AS(QZ_KP4, WKC_NUM_4), - AS(QZ_KP5, WKC_NUM_5), - AS(QZ_KP6, WKC_NUM_6), - AS(QZ_KP7, WKC_NUM_7), - AS(QZ_KP8, WKC_NUM_8), - AS(QZ_KP9, WKC_NUM_9), - AS(QZ_KP_DIVIDE, WKC_NUM_DIV), - AS(QZ_KP_MULTIPLY, WKC_NUM_MUL), - AS(QZ_KP_MINUS, WKC_NUM_MINUS), - AS(QZ_KP_PLUS, WKC_NUM_PLUS), - AS(QZ_KP_ENTER, WKC_NUM_ENTER), - AS(QZ_KP_PERIOD, WKC_NUM_DECIMAL), - - /* Other non-letter keys */ - AS(QZ_SLASH, WKC_SLASH), - AS(QZ_SEMICOLON, WKC_SEMICOLON), - AS(QZ_EQUALS, WKC_EQUALS), - AS(QZ_LEFTBRACKET, WKC_L_BRACKET), - AS(QZ_BACKSLASH, WKC_BACKSLASH), - AS(QZ_RIGHTBRACKET, WKC_R_BRACKET), - - AS(QZ_QUOTE, WKC_SINGLEQUOTE), - AS(QZ_COMMA, WKC_COMMA), - AS(QZ_MINUS, WKC_MINUS), - AS(QZ_PERIOD, WKC_PERIOD) -}; - - -static uint32 QZ_MapKey(unsigned short sym) -{ - const VkMapping *map; - uint32 key = 0; - - for (map = _vk_mapping; map != endof(_vk_mapping); ++map) { - if (sym == map->vk_from) { - key = map->map_to; - break; - } - } - - if (_cocoa_video_data.current_mods & NSShiftKeyMask) key |= WKC_SHIFT; - if (_cocoa_video_data.current_mods & NSControlKeyMask) key |= (_patches.right_mouse_btn_emulation != RMBE_CONTROL ? WKC_CTRL : WKC_META); - if (_cocoa_video_data.current_mods & NSAlternateKeyMask) key |= WKC_ALT; - if (_cocoa_video_data.current_mods & NSCommandKeyMask) key |= (_patches.right_mouse_btn_emulation != RMBE_CONTROL ? WKC_META : WKC_CTRL); - - return key << 16; -} - -static void QZ_KeyEvent(unsigned short keycode, unsigned short unicode, BOOL down) -{ - switch (keycode) { - case QZ_UP: SB(_dirkeys, 1, 1, down); break; - case QZ_DOWN: SB(_dirkeys, 3, 1, down); break; - case QZ_LEFT: SB(_dirkeys, 0, 1, down); break; - case QZ_RIGHT: SB(_dirkeys, 2, 1, down); break; - - case QZ_TAB: _cocoa_video_data.tab_is_down = down; break; - - case QZ_RETURN: - case QZ_f: - if (down && (_cocoa_video_data.current_mods & NSCommandKeyMask)) { - _video_driver->ToggleFullscreen(!_fullscreen); - } - break; - } - - if (down) { - uint32 pressed_key = QZ_MapKey(keycode) | unicode; - HandleKeypress(pressed_key); - DEBUG(driver, 2, "cocoa_v: QZ_KeyEvent: %x (%x), down, mapping: %x", keycode, unicode, pressed_key); - } else { - DEBUG(driver, 2, "cocoa_v: QZ_KeyEvent: %x (%x), up", keycode, unicode); - } -} - -static void QZ_DoUnsidedModifiers(unsigned int newMods) -{ - const int mapping[] = { QZ_CAPSLOCK, QZ_LSHIFT, QZ_LCTRL, QZ_LALT, QZ_LMETA }; - - int i; - unsigned int bit; - - if (_cocoa_video_data.current_mods == newMods) return; - - /* Iterate through the bits, testing each against the current modifiers */ - for (i = 0, bit = NSAlphaShiftKeyMask; bit <= NSCommandKeyMask; bit <<= 1, ++i) { - unsigned int currentMask, newMask; - - currentMask = _cocoa_video_data.current_mods & bit; - newMask = newMods & bit; - - if (currentMask && currentMask != newMask) { /* modifier up event */ - /* If this was Caps Lock, we need some additional voodoo to make SDL happy (is this needed in ottd?) */ - if (bit == NSAlphaShiftKeyMask) QZ_KeyEvent(mapping[i], 0, YES); - QZ_KeyEvent(mapping[i], 0, NO); - } else if (newMask && currentMask != newMask) { /* modifier down event */ - QZ_KeyEvent(mapping[i], 0, YES); - /* If this was Caps Lock, we need some additional voodoo to make SDL happy (is this needed in ottd?) */ - if (bit == NSAlphaShiftKeyMask) QZ_KeyEvent(mapping[i], 0, NO); - } - } - - _cocoa_video_data.current_mods = newMods; -} - -static void QZ_MouseMovedEvent(int x, int y) -{ - if (_cursor.fix_at) { - int dx = x - _cursor.pos.x; - int dy = y - _cursor.pos.y; - - if (dx != 0 || dy != 0) { - _cursor.delta.x += dx; - _cursor.delta.y += dy; - - QZ_WarpCursor(_cursor.pos.x, _cursor.pos.y); - } - } else { - _cursor.delta.x = x - _cursor.pos.x; - _cursor.delta.y = y - _cursor.pos.y; - _cursor.pos.x = x; - _cursor.pos.y = y; - _cursor.dirty = true; - } - HandleMouseEvents(); -} - - -static void QZ_MouseButtonEvent(int button, BOOL down) -{ - switch (button) { - case 0: - if (down) { - _left_button_down = true; - } else { - _left_button_down = false; - _left_button_clicked = false; - } - HandleMouseEvents(); - break; - - case 1: - if (down) { - _right_button_down = true; - _right_button_clicked = true; - } else { - _right_button_down = false; - } - HandleMouseEvents(); - break; - } -} - - -static inline NSPoint QZ_GetMouseLocation(NSEvent *event) -{ - NSPoint pt; - - if (_cocoa_video_data.fullscreen) { - pt = [ NSEvent mouseLocation ]; - pt.y = _cocoa_video_data.height - pt.y; - } else { - pt = [event locationInWindow]; - pt = [_cocoa_video_data.qdview convertPoint:pt fromView:nil]; - } - - return pt; -} - -static bool QZ_MouseIsInsideView(NSPoint *pt) -{ - if (_cocoa_video_data.fullscreen) { - return pt->x >= 0 && pt->y >= 0 && pt->x < _cocoa_video_data.width && pt->y < _cocoa_video_data.height; - } else { - return [ _cocoa_video_data.qdview mouse:*pt inRect:[ _cocoa_video_data.qdview bounds ] ]; - } -} - - -static bool QZ_PollEvent() -{ - NSEvent *event; - NSPoint pt; - NSString *chars; -#ifdef _DEBUG - uint32 et0, et; -#endif - -#ifdef _DEBUG - et0 = GetTick(); -#endif - event = [ NSApp nextEventMatchingMask:NSAnyEventMask - untilDate: [ NSDate distantPast ] - inMode: NSDefaultRunLoopMode dequeue:YES ]; -#ifdef _DEBUG - et = GetTick(); - _cocoa_video_data.tEvent+= et - et0; -#endif - - if (event == nil) return false; - if (!_cocoa_video_data.active) { - QZ_ShowMouse(); - [NSApp sendEvent:event]; - return true; - } - - QZ_DoUnsidedModifiers( [ event modifierFlags ] ); - - switch ([event type]) { - case NSMouseMoved: - case NSOtherMouseDragged: - case NSRightMouseDragged: - case NSLeftMouseDragged: - pt = QZ_GetMouseLocation(event); - if (!QZ_MouseIsInsideView(&pt) && - !_cocoa_video_data.emulating_right_button) { - QZ_ShowMouse(); - [NSApp sendEvent:event]; - break; - } - - QZ_HideMouse(); - QZ_MouseMovedEvent((int)pt.x, (int)pt.y); - break; - - case NSLeftMouseDown: - { - uint32 keymask = 0; - if (_patches.right_mouse_btn_emulation == RMBE_COMMAND) keymask |= NSCommandKeyMask; - if (_patches.right_mouse_btn_emulation == RMBE_CONTROL) keymask |= NSControlKeyMask; - - pt = QZ_GetMouseLocation(event); - - if (!([ event modifierFlags ] & keymask) || - !QZ_MouseIsInsideView(&pt)) { - [NSApp sendEvent:event]; - } - - if (!QZ_MouseIsInsideView(&pt)) { - QZ_ShowMouse(); - break; - } - - QZ_HideMouse(); - QZ_MouseMovedEvent((int)pt.x, (int)pt.y); - - /* Right mouse button emulation */ - if ([ event modifierFlags ] & keymask) { - _cocoa_video_data.emulating_right_button = true; - QZ_MouseButtonEvent(1, YES); - } else { - QZ_MouseButtonEvent(0, YES); - } - break; - } - case NSLeftMouseUp: - [NSApp sendEvent:event]; - - pt = QZ_GetMouseLocation(event); - if (!QZ_MouseIsInsideView(&pt)) { - QZ_ShowMouse(); - break; - } - - QZ_HideMouse(); - QZ_MouseMovedEvent((int)pt.x, (int)pt.y); - - /* Right mouse button emulation */ - if (_cocoa_video_data.emulating_right_button) { - _cocoa_video_data.emulating_right_button = false; - QZ_MouseButtonEvent(1, NO); - } else { - QZ_MouseButtonEvent(0, NO); - } - break; - - case NSRightMouseDown: - pt = QZ_GetMouseLocation(event); - if (!QZ_MouseIsInsideView(&pt)) { - QZ_ShowMouse(); - [NSApp sendEvent:event]; - break; - } - - QZ_HideMouse(); - QZ_MouseMovedEvent((int)pt.x, (int)pt.y); - QZ_MouseButtonEvent(1, YES); - break; - - case NSRightMouseUp: - pt = QZ_GetMouseLocation(event); - if (!QZ_MouseIsInsideView(&pt)) { - QZ_ShowMouse(); - [NSApp sendEvent:event]; - break; - } - - QZ_HideMouse(); - QZ_MouseMovedEvent((int)pt.x, (int)pt.y); - QZ_MouseButtonEvent(1, NO); - break; - -#if 0 - /* This is not needed since openttd currently only use two buttons */ - case NSOtherMouseDown: - pt = QZ_GetMouseLocation(event); - if (!QZ_MouseIsInsideView(&pt)) { - QZ_ShowMouse(); - [NSApp sendEvent:event]; - break; - } - - QZ_HideMouse(); - QZ_MouseMovedEvent((int)pt.x, (int)pt.y); - QZ_MouseButtonEvent([ event buttonNumber ], YES); - break; - - case NSOtherMouseUp: - pt = QZ_GetMouseLocation(event); - if (!QZ_MouseIsInsideView(&pt)) { - QZ_ShowMouse(); - [NSApp sendEvent:event]; - break; - } - - QZ_HideMouse(); - QZ_MouseMovedEvent((int)pt.x, (int)pt.y); - QZ_MouseButtonEvent([ event buttonNumber ], NO); - break; -#endif - - case NSKeyDown: - /* Quit, hide and minimize */ - switch ([event keyCode]) { - case QZ_q: - case QZ_h: - case QZ_m: - if ([ event modifierFlags ] & NSCommandKeyMask) { - [NSApp sendEvent:event]; - } - break; - } - - chars = [ event characters ]; - QZ_KeyEvent([event keyCode], [ chars length ] ? [ chars characterAtIndex:0 ] : 0, YES); - break; - - case NSKeyUp: - /* Quit, hide and minimize */ - switch ([event keyCode]) { - case QZ_q: - case QZ_h: - case QZ_m: - if ([ event modifierFlags ] & NSCommandKeyMask) { - [NSApp sendEvent:event]; - } - break; - } - - chars = [ event characters ]; - QZ_KeyEvent([event keyCode], [ chars length ] ? [ chars characterAtIndex:0 ] : 0, NO); - break; - - case NSScrollWheel: - if ([ event deltaY ] > 0.0) { /* Scroll up */ - _cursor.wheel--; - } else if ([ event deltaY ] < 0.0) { /* Scroll down */ - _cursor.wheel++; - } /* else: deltaY was 0.0 and we don't want to do anything */ - - /* Set the scroll count for scrollwheel scrolling */ - _cursor.h_wheel -= (int)([ event deltaX ]* 5 * _patches.scrollwheel_multiplier); - _cursor.v_wheel -= (int)([ event deltaY ]* 5 * _patches.scrollwheel_multiplier); - break; - - default: - [NSApp sendEvent:event]; - } - - return true; -} - - -static void QZ_GameLoop() -{ - uint32 cur_ticks = GetTick(); - uint32 last_cur_ticks = cur_ticks; - uint32 next_tick = cur_ticks + 30; - uint32 pal_tick = 0; -#ifdef _DEBUG - uint32 et0, et, st0, st; -#endif - int i; - -#ifdef _DEBUG - et0 = GetTick(); - st = 0; -#endif - - _screen.dst_ptr = _cocoa_video_data.pixels; - DisplaySplashImage(); - QZ_CheckPaletteAnim(); - QZ_Draw(); - CSleep(1); - - for (i = 0; i < 2; i++) GameLoop(); - - _screen.dst_ptr = _cocoa_video_data.pixels; - UpdateWindows(); - QZ_CheckPaletteAnim(); - QZ_Draw(); - CSleep(1); - - for (;;) { - uint32 prev_cur_ticks = cur_ticks; // to check for wrapping - InteractiveRandom(); // randomness - - while (QZ_PollEvent()) {} - - if (_exit_game) break; - -#if defined(_DEBUG) - if (_cocoa_video_data.current_mods & NSShiftKeyMask) -#else - if (_cocoa_video_data.tab_is_down) -#endif - { - if (!_networking && _game_mode != GM_MENU) _fast_forward |= 2; - } else if (_fast_forward & 2) { - _fast_forward = 0; - } - - cur_ticks = GetTick(); - if (cur_ticks >= next_tick || (_fast_forward && !_pause_game) || cur_ticks < prev_cur_ticks) { - _realtime_tick += cur_ticks - last_cur_ticks; - last_cur_ticks = cur_ticks; - next_tick = cur_ticks + 30; - - _ctrl_pressed = !!(_cocoa_video_data.current_mods & ( _patches.right_mouse_btn_emulation != RMBE_CONTROL ? NSControlKeyMask : NSCommandKeyMask)); - _shift_pressed = !!(_cocoa_video_data.current_mods & NSShiftKeyMask); - - GameLoop(); - - _screen.dst_ptr = _cocoa_video_data.pixels; - UpdateWindows(); - if (++pal_tick > 4) { - QZ_CheckPaletteAnim(); - pal_tick = 1; - } - QZ_Draw(); - } else { -#ifdef _DEBUG - st0 = GetTick(); -#endif - CSleep(1); -#ifdef _DEBUG - st += GetTick() - st0; -#endif - _screen.dst_ptr = _cocoa_video_data.pixels; - DrawChatMessage(); - DrawMouseCursor(); - QZ_Draw(); - } - } - -#ifdef _DEBUG - et = GetTick(); - - DEBUG(driver, 1, "cocoa_v: nextEventMatchingMask took %i ms total", _cocoa_video_data.tEvent); - DEBUG(driver, 1, "cocoa_v: game loop took %i ms total (%i ms without sleep)", et - et0, et - et0 - st); - DEBUG(driver, 1, "cocoa_v: (nextEventMatchingMask total)/(game loop total) is %f%%", (double)_cocoa_video_data.tEvent / (double)(et - et0) * 100); - DEBUG(driver, 1, "cocoa_v: (nextEventMatchingMask total)/(game loop without sleep total) is %f%%", (double)_cocoa_video_data.tEvent / (double)(et - et0 - st) * 100); -#endif -} - - -/****************************************************************************** - * Windowed mode * - ******************************************************************************/ - -/* This function makes the *game region* of the window 100% opaque. - * The genie effect uses the alpha component. Otherwise, - * it doesn't seem to matter what value it has. - */ -static void QZ_SetPortAlphaOpaque() -{ - if (_cocoa_video_data.device_bpp == 32) { - uint32* pixels = (uint32*)_cocoa_video_data.realpixels; - uint32 rowPixels = _cocoa_video_data.pitch / 4; - uint32 i; - uint32 j; - - for (i = 0; i < _cocoa_video_data.height; i++) - for (j = 0; j < _cocoa_video_data.width; j++) { - pixels[i * rowPixels + j] |= 0xFF000000; - } - } -} - - -@implementation OTTD_QuartzWindow - -/* we override these methods to fix the miniaturize animation/dock icon bug */ -- (void)miniaturize:(id)sender -{ - /* make the alpha channel opaque so anim won't have holes in it */ - QZ_SetPortAlphaOpaque (); - - /* window is hidden now */ - _cocoa_video_data.active = false; - - QZ_ShowMouse(); - - [ super miniaturize:sender ]; -} - -- (void)display -{ - /* This method fires just before the window deminaturizes from the Dock. - * We'll save the current visible surface, let the window manager redraw any - * UI elements, and restore the surface. This way, no expose event - * is required, and the deminiaturize works perfectly. - */ - - QZ_SetPortAlphaOpaque(); - - /* save current visible surface */ - [ self cacheImageInRect:[ _cocoa_video_data.qdview frame ] ]; - - /* let the window manager redraw controls, border, etc */ - [ super display ]; - - /* restore visible surface */ - [ self restoreCachedImage ]; - - /* window is visible again */ - _cocoa_video_data.active = true; -} - -- (void)setFrame:(NSRect)frameRect display:(BOOL)flag -{ - NSRect newViewFrame; - CGrafPtr thePort; - - [ super setFrame:frameRect display:flag ]; - - /* Don't do anything if the window is currently beign created */ - if (_cocoa_video_data.issetting) return; - - if (_cocoa_video_data.window == nil) return; - - newViewFrame = [ _cocoa_video_data.qdview frame ]; - - /* Update the pixels and pitch */ - thePort = (OpaqueGrafPtr*) [ _cocoa_video_data.qdview qdPort ]; - LockPortBits(thePort); - - _cocoa_video_data.realpixels = GetPixBaseAddr(GetPortPixMap(thePort)); - _cocoa_video_data.pitch = GetPixRowBytes(GetPortPixMap(thePort)); - - /* _cocoa_video_data.realpixels now points to the window's pixels - * We want it to point to the *view's* pixels - */ - { - int vOffset = [ _cocoa_video_data.window frame ].size.height - newViewFrame.size.height - newViewFrame.origin.y; - int hOffset = newViewFrame.origin.x; - - _cocoa_video_data.realpixels = (uint8*)_cocoa_video_data.realpixels + (vOffset * _cocoa_video_data.pitch) + hOffset * (_cocoa_video_data.device_bpp / 8); - } - - UnlockPortBits(thePort); - - /* Allocate new buffer */ - free(_cocoa_video_data.pixels); - _cocoa_video_data.pixels = malloc(newViewFrame.size.width * newViewFrame.size.height * _cocoa_video_data.blitter_bpp / 8); - assert(_cocoa_video_data.pixels != NULL); - - - /* Tell the game that the resolution changed */ - _cocoa_video_data.width = newViewFrame.size.width; - _cocoa_video_data.height = newViewFrame.size.height; - - _screen.width = _cocoa_video_data.width; - _screen.height = _cocoa_video_data.height; - _screen.pitch = _cocoa_video_data.width; - - GameSizeChanged(); - - /* Redraw screen */ - _cocoa_video_data.num_dirty_rects = MAX_DIRTY_RECTS; -} - -- (void)appDidHide:(NSNotification*)note -{ - _cocoa_video_data.active = false; -} - - -- (void)appWillUnhide:(NSNotification*)note -{ - QZ_SetPortAlphaOpaque (); - - /* save current visible surface */ - [ self cacheImageInRect:[ _cocoa_video_data.qdview frame ] ]; -} - -- (void)appDidUnhide:(NSNotification*)note -{ - /* restore cached image, since it may not be current, post expose event too */ - [ self restoreCachedImage ]; - - _cocoa_video_data.active = true; -} - - -- (id)initWithContentRect:(NSRect)contentRect styleMask:(unsigned int)styleMask backing:(NSBackingStoreType)backingType defer:(BOOL)flag -{ - /* Make our window subclass receive these application notifications */ - [ [ NSNotificationCenter defaultCenter ] addObserver:self - selector:@selector(appDidHide:) name:NSApplicationDidHideNotification object:NSApp ]; - - [ [ NSNotificationCenter defaultCenter ] addObserver:self - selector:@selector(appDidUnhide:) name:NSApplicationDidUnhideNotification object:NSApp ]; - - [ [ NSNotificationCenter defaultCenter ] addObserver:self - selector:@selector(appWillUnhide:) name:NSApplicationWillUnhideNotification object:NSApp ]; - - return [ super initWithContentRect:contentRect styleMask:styleMask backing:backingType defer:flag ]; -} - -@end - -@implementation OTTD_QuartzWindowDelegate -- (BOOL)windowShouldClose:(id)sender -{ - HandleExitGameRequest(); - - return NO; -} - -- (void)windowDidBecomeKey:(NSNotification*)aNotification -{ - _cocoa_video_data.active = true; -} - -- (void)windowDidResignKey:(NSNotification*)aNotification -{ - _cocoa_video_data.active = false; -} - -- (void)windowDidBecomeMain:(NSNotification*)aNotification -{ - _cocoa_video_data.active = true; -} - -- (void)windowDidResignMain:(NSNotification*)aNotification -{ - _cocoa_video_data.active = false; -} - -@end - - -static void QZ_UpdateWindowPalette(uint start, uint count) -{ - uint i; - - switch (_cocoa_video_data.device_bpp) { - case 32: - for (i = start; i < start + count; i++) { - uint32 clr32 = 0xff000000; - clr32 |= (uint32)_cur_palette[i].r << 16; - clr32 |= (uint32)_cur_palette[i].g << 8; - clr32 |= (uint32)_cur_palette[i].b; - _cocoa_video_data.palette32[i] = clr32; - } - break; - case 16: - for (i = start; i < start + count; i++) { - uint16 clr16 = 0x0000; - clr16 |= (uint16)((_cur_palette[i].r >> 3) & 0x1f) << 10; - clr16 |= (uint16)((_cur_palette[i].g >> 3) & 0x1f) << 5; - clr16 |= (uint16)((_cur_palette[i].b >> 3) & 0x1f); - _cocoa_video_data.palette16[i] = clr16; - } - break; - } - - _cocoa_video_data.num_dirty_rects = MAX_DIRTY_RECTS; -} - -static inline void QZ_WindowBlitIndexedPixelsToView32(uint left, uint top, uint right, uint bottom) -{ - uint32* dst = (uint32*)_cocoa_video_data.realpixels; - uint width = _cocoa_video_data.width; - uint pitch = _cocoa_video_data.pitch / 4; - uint x; - uint y; - - switch (_cocoa_video_data.blitter_bpp) { - case 8: { - const uint32* pal = _cocoa_video_data.palette32; - const uint8* src = (uint8*) _cocoa_video_data.pixels; - - for (y = top; y < bottom; y++) { - for (x = left; x < right; x++) { - dst[y * pitch + x] = pal[src[y * width + x]]; - } - } - } - break; - case 32: { - const uint32* src = (uint32*) _cocoa_video_data.pixels; - - dst += top * pitch + left; - src += top * width + left; - - for (y = top; y < bottom; y++, dst+= pitch, src+= width) - memcpy(dst, src, (right - left) * 4); - } - break; - } -} - -/** - * This function copies pixels from the screen buffer in 16bpp windowed mode. It assumes - * that the blitter is 8bpp since the driver only supports 8 and 32 bpp blitters, and we - * don't allow a blitter with a higer bpp than the display in windowed mode. - * - * @param left The x coord for the left edge of the box to blit. - * @param top The y coord for the top edge of the box to blit. - * @param right The x coord for the right edge of the box to blit. - * @param bottom The y coord for the bottom edge of the box to blit. - */ -static inline void QZ_WindowBlitIndexedPixelsToView16(uint left, uint top, uint right, uint bottom) -{ - const uint16* pal = _cocoa_video_data.palette16; - const uint8* src = (uint8*) _cocoa_video_data.pixels; - uint16* dst = (uint16*)_cocoa_video_data.realpixels; - uint width = _cocoa_video_data.width; - uint pitch = _cocoa_video_data.pitch / 2; - uint x; - uint y; - - for (y = top; y < bottom; y++) { - for (x = left; x < right; x++) { - dst[y * pitch + x] = pal[src[y * width + x]]; - } - } -} - -static inline void QZ_WindowBlitIndexedPixelsToView(int left, int top, int right, int bottom) -{ - switch (_cocoa_video_data.device_bpp) { - case 32: QZ_WindowBlitIndexedPixelsToView32(left, top, right, bottom); break; - case 16: QZ_WindowBlitIndexedPixelsToView16(left, top, right, bottom); break; - } -} - -static bool _resize_icon[] = { - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, - 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, - 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, - 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, - 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, - 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, - 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, - 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0 -}; - -static void QZ_DrawResizeIcon() -{ - int xoff = _cocoa_video_data.width - 16; - int yoff = _cocoa_video_data.height - 16; - int x; - int y; - - for (y = 0; y < 16; y++) { - uint16* trg16 = (uint16*)_cocoa_video_data.realpixels + (yoff + y) * _cocoa_video_data.pitch / 2 + xoff; - uint32* trg32 = (uint32*)_cocoa_video_data.realpixels + (yoff + y) * _cocoa_video_data.pitch / 4 + xoff; - - for (x = 0; x < 16; x++, trg16++, trg32++) { - if (!_resize_icon[y * 16 + x]) continue; - - switch (_cocoa_video_data.device_bpp) { - case 32: *trg32 = 0xff000000; break; - case 16: *trg16 = 0x0000; break; - } - } - } -} - -static void QZ_DrawWindow() -{ - int i; - RgnHandle dirty, temp; - - /* Check if we need to do anything */ - if (_cocoa_video_data.num_dirty_rects == 0 || - [ _cocoa_video_data.window isMiniaturized ]) { - return; - } - - if (_cocoa_video_data.num_dirty_rects >= MAX_DIRTY_RECTS) { - _cocoa_video_data.num_dirty_rects = 1; - _cocoa_video_data.dirty_rects[0].left = 0; - _cocoa_video_data.dirty_rects[0].top = 0; - _cocoa_video_data.dirty_rects[0].right = _cocoa_video_data.width; - _cocoa_video_data.dirty_rects[0].bottom = _cocoa_video_data.height; - } - - dirty = NewRgn(); - temp = NewRgn(); - - SetEmptyRgn(dirty); - - /* Build the region of dirty rectangles */ - for (i = 0; i < _cocoa_video_data.num_dirty_rects; i++) { - QZ_WindowBlitIndexedPixelsToView( - _cocoa_video_data.dirty_rects[i].left, - _cocoa_video_data.dirty_rects[i].top, - _cocoa_video_data.dirty_rects[i].right, - _cocoa_video_data.dirty_rects[i].bottom - ); - - MacSetRectRgn( - temp, - _cocoa_video_data.dirty_rects[i].left, - _cocoa_video_data.dirty_rects[i].top, - _cocoa_video_data.dirty_rects[i].right, - _cocoa_video_data.dirty_rects[i].bottom - ); - MacUnionRgn(dirty, temp, dirty); - } - - QZ_DrawResizeIcon(); - - /* Flush the dirty region */ - QDFlushPortBuffer( (OpaqueGrafPtr*) [ _cocoa_video_data.qdview qdPort ], dirty); - DisposeRgn(dirty); - DisposeRgn(temp); - - _cocoa_video_data.num_dirty_rects = 0; -} - - -extern const char _openttd_revision[]; - -static const char* QZ_SetVideoWindowed(uint width, uint height) -{ - char caption[50]; - NSString *nsscaption; - unsigned int style; - NSRect contentRect; - BOOL isCustom = NO; - - if (_cocoa_video_data.blitter_bpp > _cocoa_video_data.device_bpp) { - error("Cocoa: Cannot use a blitter with a higer screen depth than the display when running in windowed mode."); - } - - if (width > _cocoa_video_data.device_width) - width = _cocoa_video_data.device_width; - if (height > _cocoa_video_data.device_height) - height = _cocoa_video_data.device_height; - - _cocoa_video_data.width = width; - _cocoa_video_data.height = height; - - contentRect = NSMakeRect(0, 0, width, height); - - /* Check if we should completely destroy the previous mode - * - If it is fullscreen - */ - if (_cocoa_video_data.isset && _cocoa_video_data.fullscreen) - QZ_UnsetVideoMode(); - - /* Check if we should recreate the window */ - if (_cocoa_video_data.window == nil) { - /* Set the window style */ - style = NSTitledWindowMask; - style |= (NSMiniaturizableWindowMask | NSClosableWindowMask); - style |= NSResizableWindowMask; - - /* Manually create a window, avoids having a nib file resource */ - _cocoa_video_data.window = [ [ OTTD_QuartzWindow alloc ] - initWithContentRect:contentRect - styleMask:style - backing:NSBackingStoreBuffered - defer:NO ]; - - if (_cocoa_video_data.window == nil) - return "Could not create the Cocoa window"; - - snprintf(caption, sizeof(caption), "OpenTTD %s", _openttd_revision); - nsscaption = [ [ NSString alloc ] initWithCString:caption ]; - [ _cocoa_video_data.window setTitle:nsscaption ]; - [ _cocoa_video_data.window setMiniwindowTitle:nsscaption ]; - [ nsscaption release ]; - - [ _cocoa_video_data.window setAcceptsMouseMovedEvents:YES ]; - [ _cocoa_video_data.window setViewsNeedDisplay:NO ]; - - [ _cocoa_video_data.window setDelegate: [ [ [ OTTD_QuartzWindowDelegate alloc ] init ] autorelease ] ]; - } else { - /* We already have a window, just change its size */ - if (!isCustom) { - [ _cocoa_video_data.window setContentSize:contentRect.size ]; - // Ensure frame height - title bar height >= view height - contentRect.size.height = Clamp(height, 0, [ _cocoa_video_data.window frame ].size.height - 22 /* 22 is the height of title bar of window*/); - height = contentRect.size.height; - [ _cocoa_video_data.qdview setFrameSize:contentRect.size ]; - } - } - - // Update again - _cocoa_video_data.width = width; - _cocoa_video_data.height = height; - - [ _cocoa_video_data.window center ]; - - /* Only recreate the view if it doesn't already exist */ - if (_cocoa_video_data.qdview == nil) { - _cocoa_video_data.qdview = [ [ NSQuickDrawView alloc ] initWithFrame:contentRect ]; - [ _cocoa_video_data.qdview setAutoresizingMask: NSViewWidthSizable | NSViewHeightSizable ]; - [ [ _cocoa_video_data.window contentView ] addSubview:_cocoa_video_data.qdview ]; - [ _cocoa_video_data.qdview release ]; - [ _cocoa_video_data.window makeKeyAndOrderFront:nil ]; - } - - CGrafPtr thePort = (OpaqueGrafPtr*) [ _cocoa_video_data.qdview qdPort ]; - - LockPortBits(thePort); - _cocoa_video_data.realpixels = GetPixBaseAddr(GetPortPixMap(thePort)); - _cocoa_video_data.pitch = GetPixRowBytes(GetPortPixMap(thePort)); - UnlockPortBits(thePort); - - /* _cocoa_video_data.realpixels now points to the window's pixels - * We want it to point to the *view's* pixels - */ - { - int vOffset = [ _cocoa_video_data.window frame ].size.height - [ _cocoa_video_data.qdview frame ].size.height - [ _cocoa_video_data.qdview frame ].origin.y; - int hOffset = [ _cocoa_video_data.qdview frame ].origin.x; - - _cocoa_video_data.realpixels = (uint8*)_cocoa_video_data.realpixels + (vOffset * _cocoa_video_data.pitch) + hOffset * (_cocoa_video_data.device_bpp / 8); - } - - free(_cocoa_video_data.pixels); - _cocoa_video_data.pixels = malloc(width * height * _cocoa_video_data.blitter_bpp / 8); - if (_cocoa_video_data.pixels == NULL) return "Failed to allocate 8-bit buffer"; - - _cocoa_video_data.fullscreen = false; - - return NULL; -} - - -/****************************************************************************** - * Fullscreen mode * - ******************************************************************************/ - -/* Gamma functions to try to hide the flash from a rez switch - * Fade the display from normal to black - * Save gamma tables for fade back to normal - */ -static uint32 QZ_FadeGammaOut(OTTD_QuartzGammaTable* table) -{ - CGGammaValue redTable[QZ_GAMMA_TABLE_SIZE]; - CGGammaValue greenTable[QZ_GAMMA_TABLE_SIZE]; - CGGammaValue blueTable[QZ_GAMMA_TABLE_SIZE]; - float percent; - int j; - unsigned int actual; - - if (CGGetDisplayTransferByTable( - _cocoa_video_data.display_id, QZ_GAMMA_TABLE_SIZE, - table->red, table->green, table->blue, &actual - ) != CGDisplayNoErr || - actual != QZ_GAMMA_TABLE_SIZE) { - return 1; - } - - memcpy(redTable, table->red, sizeof(redTable)); - memcpy(greenTable, table->green, sizeof(greenTable)); - memcpy(blueTable, table->blue, sizeof(greenTable)); - - for (percent = 1.0; percent >= 0.0; percent -= 0.01) { - for (j = 0; j < QZ_GAMMA_TABLE_SIZE; j++) { - redTable[j] = redTable[j] * percent; - greenTable[j] = greenTable[j] * percent; - blueTable[j] = blueTable[j] * percent; - } - - if (CGSetDisplayTransferByTable( - _cocoa_video_data.display_id, QZ_GAMMA_TABLE_SIZE, - redTable, greenTable, blueTable - ) != CGDisplayNoErr) { - CGDisplayRestoreColorSyncSettings(); - return 1; - } - - CSleep(10); - } - - return 0; -} - -/* Fade the display from black to normal - * Restore previously saved gamma values - */ -static uint32 QZ_FadeGammaIn(const OTTD_QuartzGammaTable* table) -{ - CGGammaValue redTable[QZ_GAMMA_TABLE_SIZE]; - CGGammaValue greenTable[QZ_GAMMA_TABLE_SIZE]; - CGGammaValue blueTable[QZ_GAMMA_TABLE_SIZE]; - float percent; - int j; - - memset(redTable, 0, sizeof(redTable)); - memset(greenTable, 0, sizeof(greenTable)); - memset(blueTable, 0, sizeof(greenTable)); - - for (percent = 0.0; percent <= 1.0; percent += 0.01) { - for (j = 0; j < QZ_GAMMA_TABLE_SIZE; j++) { - redTable[j] = table->red[j] * percent; - greenTable[j] = table->green[j] * percent; - blueTable[j] = table->blue[j] * percent; - } - - if (CGSetDisplayTransferByTable( - _cocoa_video_data.display_id, QZ_GAMMA_TABLE_SIZE, - redTable, greenTable, blueTable - ) != CGDisplayNoErr) { - CGDisplayRestoreColorSyncSettings(); - return 1; - } - - CSleep(10); - } - - return 0; -} - -static const char* QZ_SetVideoFullScreen(int width, int height) -{ - const char* errstr = "QZ_SetVideoFullScreen error"; - int exact_match; - CFNumberRef number; - int bpp; - int gamma_error; - OTTD_QuartzGammaTable gamma_table; - NSRect screen_rect; - CGError error; - NSPoint pt; - - /* Destroy any previous mode */ - if (_cocoa_video_data.isset) QZ_UnsetVideoMode(); - - /* See if requested mode exists */ - _cocoa_video_data.mode = CGDisplayBestModeForParameters(_cocoa_video_data.display_id, _cocoa_video_data.blitter_bpp, width, height, &exact_match); - - /* If the mode wasn't an exact match, check if it has the right bpp, and update width and height */ - if (!exact_match) { - number = (const __CFNumber*) CFDictionaryGetValue(_cocoa_video_data.mode, kCGDisplayBitsPerPixel); - CFNumberGetValue(number, kCFNumberSInt32Type, &bpp); - if ((uint) bpp != _cocoa_video_data.blitter_bpp) { - errstr = "Failed to find display resolution"; - goto ERR_NO_MATCH; - } - - number = (const __CFNumber*)CFDictionaryGetValue(_cocoa_video_data.mode, kCGDisplayWidth); - CFNumberGetValue(number, kCFNumberSInt32Type, &width); - - number = (const __CFNumber*)CFDictionaryGetValue(_cocoa_video_data.mode, kCGDisplayHeight); - CFNumberGetValue(number, kCFNumberSInt32Type, &height); - } - - /* Fade display to zero gamma */ - gamma_error = QZ_FadeGammaOut(&gamma_table); - - /* Put up the blanking window (a window above all other windows) */ - error = CGDisplayCapture(_cocoa_video_data.display_id); - - if (CGDisplayNoErr != error) { - errstr = "Failed capturing display"; - goto ERR_NO_CAPTURE; - } - - /* Do the physical switch */ - if (CGDisplaySwitchToMode(_cocoa_video_data.display_id, _cocoa_video_data.mode) != CGDisplayNoErr) { - errstr = "Failed switching display resolution"; - goto ERR_NO_SWITCH; - } - - _cocoa_video_data.realpixels = CGDisplayBaseAddress(_cocoa_video_data.display_id); - _cocoa_video_data.pitch = CGDisplayBytesPerRow(_cocoa_video_data.display_id); - - _cocoa_video_data.width = CGDisplayPixelsWide(_cocoa_video_data.display_id); - _cocoa_video_data.height = CGDisplayPixelsHigh(_cocoa_video_data.display_id); - _cocoa_video_data.fullscreen = true; - - /* Setup double-buffer emulation */ - _cocoa_video_data.pixels = malloc(width * height * _cocoa_video_data.blitter_bpp / 8); - if (_cocoa_video_data.pixels == NULL) { - errstr = "Failed to allocate memory for double buffering"; - goto ERR_DOUBLEBUF; - } - - if (_cocoa_video_data.blitter_bpp == 8 && !CGDisplayCanSetPalette(_cocoa_video_data.display_id)) { - errstr = "Not an indexed display mode."; - goto ERR_NOT_INDEXED; - } - - /* If we don't hide menu bar, it will get events and interrupt the program */ - HideMenuBar(); - - /* Fade the display to original gamma */ - if (!gamma_error) QZ_FadeGammaIn(&gamma_table); - - /* There is a bug in Cocoa where NSScreen doesn't synchronize - * with CGDirectDisplay, so the main screen's frame is wrong. - * As a result, coordinate translation produces incorrect results. - * We can hack around this bug by setting the screen rect ourselves. - * This hack should be removed if/when the bug is fixed. - */ - screen_rect = NSMakeRect(0, 0, width, height); - [ [ NSScreen mainScreen ] setFrame:screen_rect ]; - - /* we're fullscreen, so flag all input states... */ - _cocoa_video_data.active = true; - - - pt = [ NSEvent mouseLocation ]; - pt.y = CGDisplayPixelsHigh(_cocoa_video_data.display_id) - pt.y; - if (QZ_MouseIsInsideView(&pt)) QZ_HideMouse(); - - return NULL; - -/* Since the blanking window covers *all* windows (even force quit) correct recovery is crucial */ -ERR_NOT_INDEXED: - free(_cocoa_video_data.pixels); - _cocoa_video_data.pixels = NULL; -ERR_DOUBLEBUF: - CGDisplaySwitchToMode(_cocoa_video_data.display_id, _cocoa_video_data.save_mode); -ERR_NO_SWITCH: - CGReleaseAllDisplays(); -ERR_NO_CAPTURE: - if (!gamma_error) QZ_FadeGammaIn(&gamma_table); -ERR_NO_MATCH: - return errstr; -} - - -static void QZ_UpdateFullscreenPalette(uint first_color, uint num_colors) -{ - CGTableCount index; - CGDeviceColor color; - - for (index = first_color; index < first_color+num_colors; index++) { - /* Clamp colors between 0.0 and 1.0 */ - color.red = _cur_palette[index].r / 255.0; - color.blue = _cur_palette[index].b / 255.0; - color.green = _cur_palette[index].g / 255.0; - - CGPaletteSetColorAtIndex(_cocoa_video_data.palette, color, index); - } - - CGDisplaySetPalette(_cocoa_video_data.display_id, _cocoa_video_data.palette); -} - -/* Wait for the VBL to occur (estimated since we don't have a hardware interrupt) */ -static void QZ_WaitForVerticalBlank() -{ - /* The VBL delay is based on Ian Ollmann's RezLib <iano@cco.caltech.edu> */ - double refreshRate; - double linesPerSecond; - double target; - double position; - double adjustment; - CFNumberRef refreshRateCFNumber; - - refreshRateCFNumber = (const __CFNumber*)CFDictionaryGetValue(_cocoa_video_data.mode, kCGDisplayRefreshRate); - if (refreshRateCFNumber == NULL) return; - - if (CFNumberGetValue(refreshRateCFNumber, kCFNumberDoubleType, &refreshRate) == 0) - return; - - if (refreshRate == 0) return; - - linesPerSecond = refreshRate * _cocoa_video_data.height; - target = _cocoa_video_data.height; - - /* Figure out the first delay so we start off about right */ - position = CGDisplayBeamPosition(_cocoa_video_data.display_id); - if (position > target) position = 0; - - adjustment = (target - position) / linesPerSecond; - - CSleep((uint32)(adjustment * 1000)); -} - - -static void QZ_DrawScreen() -{ - const uint8* src = (uint8*) _cocoa_video_data.pixels; - uint8* dst = (uint8*)_cocoa_video_data.realpixels; - uint pitch = _cocoa_video_data.pitch; - uint width = _cocoa_video_data.width; - uint num_dirty = _cocoa_video_data.num_dirty_rects; - uint bytesperpixel = _cocoa_video_data.blitter_bpp / 8; - uint i; - - /* Check if we need to do anything */ - if (num_dirty == 0) return; - - if (num_dirty >= MAX_DIRTY_RECTS) { - num_dirty = 1; - _cocoa_video_data.dirty_rects[0].left = 0; - _cocoa_video_data.dirty_rects[0].top = 0; - _cocoa_video_data.dirty_rects[0].right = _cocoa_video_data.width; - _cocoa_video_data.dirty_rects[0].bottom = _cocoa_video_data.height; - } - - QZ_WaitForVerticalBlank(); - /* Build the region of dirty rectangles */ - for (i = 0; i < num_dirty; i++) { - uint y = _cocoa_video_data.dirty_rects[i].top; - uint left = _cocoa_video_data.dirty_rects[i].left; - uint length = _cocoa_video_data.dirty_rects[i].right - left; - uint bottom = _cocoa_video_data.dirty_rects[i].bottom; - - for (; y < bottom; y++) { - memcpy(dst + y * pitch + left * bytesperpixel, src + y * width * bytesperpixel + left * bytesperpixel, length * bytesperpixel); - } - } - - _cocoa_video_data.num_dirty_rects = 0; -} - - -static int QZ_ListFullscreenModes(OTTDPoint* mode_list, int max_modes) -{ - CFIndex num_modes; - CFIndex i; - int list_size = 0; - - num_modes = CFArrayGetCount(_cocoa_video_data.mode_list); - - /* Build list of modes with the requested bpp */ - for (i = 0; i < num_modes && list_size < max_modes; i++) { - CFDictionaryRef onemode; - CFNumberRef number; - int bpp; - int intvalue; - bool hasMode; - uint16 width, height; - - onemode = (const __CFDictionary*)CFArrayGetValueAtIndex(_cocoa_video_data.mode_list, i); - number = (const __CFNumber*)CFDictionaryGetValue(onemode, kCGDisplayBitsPerPixel); - CFNumberGetValue (number, kCFNumberSInt32Type, &bpp); - - if ((uint) bpp != _cocoa_video_data.blitter_bpp) continue; - - number = (const __CFNumber*)CFDictionaryGetValue(onemode, kCGDisplayWidth); - CFNumberGetValue(number, kCFNumberSInt32Type, &intvalue); - width = (uint16)intvalue; - - number = (const __CFNumber*)CFDictionaryGetValue(onemode, kCGDisplayHeight); - CFNumberGetValue(number, kCFNumberSInt32Type, &intvalue); - height = (uint16)intvalue; - - /* Check if mode is already in the list */ - { - int i; - hasMode = false; - for (i = 0; i < list_size; i++) { - if (mode_list[i].x == width && mode_list[i].y == height) { - hasMode = true; - break; - } - } - } - - if (hasMode) continue; - - /* Add mode to the list */ - mode_list[list_size].x = width; - mode_list[list_size].y = height; - list_size++; - } - - /* Sort list smallest to largest */ - { - int i, j; - for (i = 0; i < list_size; i++) { - for (j = 0; j < list_size-1; j++) { - if (mode_list[j].x > mode_list[j + 1].x || ( - mode_list[j].x == mode_list[j + 1].x && - mode_list[j].y > mode_list[j + 1].y - )) { - uint tmpw = mode_list[j].x; - uint tmph = mode_list[j].y; - - mode_list[j].x = mode_list[j + 1].x; - mode_list[j].y = mode_list[j + 1].y; - - mode_list[j + 1].x = tmpw; - mode_list[j + 1].y = tmph; - } - } - } - } - - return list_size; -} - - -/****************************************************************************** - * Windowed and fullscreen common code * - ******************************************************************************/ - -static void QZ_UpdatePalette(uint start, uint count) -{ - if (_cocoa_video_data.fullscreen) { - QZ_UpdateFullscreenPalette(start, count); - } else { - QZ_UpdateWindowPalette(start, count); - } -} - -static void QZ_InitPalette() -{ - QZ_UpdatePalette(0, 256); -} - -static void QZ_Draw() -{ - if (_cocoa_video_data.fullscreen) { - QZ_DrawScreen(); - } else { - QZ_DrawWindow(); - } -} - - -static const OTTDPoint _default_resolutions[] = { - { 640, 480}, - { 800, 600}, - {1024, 768}, - {1152, 864}, - {1280, 800}, - {1280, 960}, - {1280, 1024}, - {1400, 1050}, - {1600, 1200}, - {1680, 1050}, - {1920, 1200} -}; - -static void QZ_UpdateVideoModes() -{ - uint i, j, count; - OTTDPoint modes[32]; - const OTTDPoint *current_modes; - - if (_cocoa_video_data.fullscreen) { - count = QZ_ListFullscreenModes(modes, 32); - current_modes = modes; - } else { - count = lengthof(_default_resolutions); - current_modes = _default_resolutions; - } - - for (i = 0, j = 0; j < lengthof(_resolutions) && i < count; i++) { - if (_cocoa_video_data.fullscreen || ( - (uint)current_modes[i].x < _cocoa_video_data.device_width && - (uint)current_modes[i].y < _cocoa_video_data.device_height) - ) { - _resolutions[j][0] = current_modes[i].x; - _resolutions[j][1] = current_modes[i].y; - j++; - } - } - - _num_resolutions = j; -} - -static void QZ_UnsetVideoMode() -{ - if (_cocoa_video_data.fullscreen) { - /* Release fullscreen resources */ - OTTD_QuartzGammaTable gamma_table; - int gamma_error; - NSRect screen_rect; - - gamma_error = QZ_FadeGammaOut(&gamma_table); - - /* Restore original screen resolution/bpp */ - CGDisplaySwitchToMode(_cocoa_video_data.display_id, _cocoa_video_data.save_mode); - CGReleaseAllDisplays(); - ShowMenuBar(); - /* Reset the main screen's rectangle - * See comment in QZ_SetVideoFullscreen for why we do this - */ - screen_rect = NSMakeRect(0,0,_cocoa_video_data.device_width,_cocoa_video_data.device_height); - [ [ NSScreen mainScreen ] setFrame:screen_rect ]; - - if (!gamma_error) QZ_FadeGammaIn(&gamma_table); - } else { - /* Release window mode resources */ - [ _cocoa_video_data.window close ]; - _cocoa_video_data.window = nil; - _cocoa_video_data.qdview = nil; - } - - free(_cocoa_video_data.pixels); - _cocoa_video_data.pixels = NULL; - - /* Signal successful teardown */ - _cocoa_video_data.isset = false; - - QZ_ShowMouse(); -} - - -static const char* QZ_SetVideoMode(uint width, uint height, bool fullscreen) -{ - const char *ret; - - _cocoa_video_data.issetting = true; - if (fullscreen) { - /* Setup full screen video */ - ret = QZ_SetVideoFullScreen(width, height); - } else { - /* Setup windowed video */ - ret = QZ_SetVideoWindowed(width, height); - } - _cocoa_video_data.issetting = false; - if (ret != NULL) return ret; - - /* Signal successful completion (used internally) */ - _cocoa_video_data.isset = true; - - /* Tell the game that the resolution has changed */ - _screen.width = _cocoa_video_data.width; - _screen.height = _cocoa_video_data.height; - _screen.pitch = _cocoa_video_data.width; - - QZ_UpdateVideoModes(); - GameSizeChanged(); - - QZ_InitPalette(); - - return NULL; -} - -static const char* QZ_SetVideoModeAndRestoreOnFailure(uint width, uint height, bool fullscreen) -{ - bool wasset = _cocoa_video_data.isset; - uint32 oldwidth = _cocoa_video_data.width; - uint32 oldheight = _cocoa_video_data.height; - bool oldfullscreen = _cocoa_video_data.fullscreen; - const char *ret; - - ret = QZ_SetVideoMode(width, height, fullscreen); - if (ret != NULL && wasset) QZ_SetVideoMode(oldwidth, oldheight, oldfullscreen); - - return ret; -} - -static void QZ_VideoInit() -{ - - memset(&_cocoa_video_data, 0, sizeof(_cocoa_video_data)); - - _cocoa_video_data.blitter_bpp = BlitterFactoryBase::GetCurrentBlitter()->GetScreenDepth(); - DEBUG(driver, 1, "Cocoa: Blitter bpp %d", _cocoa_video_data.blitter_bpp); - - if (_cocoa_video_data.blitter_bpp == 0) error("Can't use a blitter that blits 0 bpp for normal visuals"); - - - if (_cocoa_video_data.blitter_bpp != 8 && _cocoa_video_data.blitter_bpp != 32) { - error("Cocoa: This video driver only supports 8 and 32 bpp blitters."); - } - - /* Initialize the video settings; this data persists between mode switches */ - _cocoa_video_data.display_id = kCGDirectMainDisplay; - _cocoa_video_data.save_mode = CGDisplayCurrentMode(_cocoa_video_data.display_id); - _cocoa_video_data.mode_list = CGDisplayAvailableModes(_cocoa_video_data.display_id); - _cocoa_video_data.palette = CGPaletteCreateDefaultColorPalette(); - - /* Gather some information that is useful to know about the display */ - /* Maybe this should be moved to QZ_SetVideoMode, in case this is changed after startup */ - CFNumberGetValue( - (const __CFNumber*)CFDictionaryGetValue(_cocoa_video_data.save_mode, kCGDisplayBitsPerPixel), - kCFNumberSInt32Type, &_cocoa_video_data.device_bpp - ); - - CFNumberGetValue( - (const __CFNumber*)CFDictionaryGetValue(_cocoa_video_data.save_mode, kCGDisplayWidth), - kCFNumberSInt32Type, &_cocoa_video_data.device_width - ); - - CFNumberGetValue( - (const __CFNumber*)CFDictionaryGetValue(_cocoa_video_data.save_mode, kCGDisplayHeight), - kCFNumberSInt32Type, &_cocoa_video_data.device_height - ); - - _cocoa_video_data.cursor_visible = true; - - /* register for sleep notifications so wake from sleep generates SDL_VIDEOEXPOSE */ -// QZ_RegisterForSleepNotifications(); -} - - -/* Convert local coordinate to window server (CoreGraphics) coordinate */ -static CGPoint QZ_PrivateLocalToCG(NSPoint* p) -{ - CGPoint cgp; - - if (!_cocoa_video_data.fullscreen) { - *p = [ _cocoa_video_data.qdview convertPoint:*p toView: nil ]; - *p = [ _cocoa_video_data.window convertBaseToScreen:*p ]; - p->y = _cocoa_video_data.device_height - p->y; - } - - cgp.x = p->x; - cgp.y = p->y; - - return cgp; -} - -static void QZ_WarpCursor(int x, int y) -{ - NSPoint p; - CGPoint cgp; - - /* Only allow warping when in foreground */ - if (![ NSApp isActive ]) return; - - p = NSMakePoint(x, y); - cgp = QZ_PrivateLocalToCG(&p); - - /* this is the magic call that fixes cursor "freezing" after warp */ - CGSetLocalEventsSuppressionInterval(0.0); - /* Do the actual warp */ - CGWarpMouseCursorPosition(cgp); - - /* Generate the mouse moved event */ -} - -static void QZ_ShowMouse() -{ - if (!_cocoa_video_data.cursor_visible) { - [ NSCursor unhide ]; - _cocoa_video_data.cursor_visible = true; - - // Hide the openttd cursor when leaving the window - if (_cocoa_video_data.isset) - UndrawMouseCursor(); - _cursor.in_window = false; - } -} - -static void QZ_HideMouse() -{ - if (_cocoa_video_data.cursor_visible) { -#ifndef _DEBUG - [ NSCursor hide ]; -#endif - _cocoa_video_data.cursor_visible = false; - - // Show the openttd cursor again - _cursor.in_window = true; - } -} - - -/****************************************************************************** - * OS X application creation * - ******************************************************************************/ - -/* 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]; -} - - -/****************************************************************************** - * Video driver interface * - ******************************************************************************/ - -static FVideoDriver_Cocoa iFVideoDriver_Cocoa; - -void VideoDriver_Cocoa::Stop() -{ - if (!_cocoa_video_started) return; - - if (_cocoa_video_data.isset) QZ_UnsetVideoMode(); - - [_ottd_main release]; - - _cocoa_video_started = false; -} - -const char *VideoDriver_Cocoa::Start(const char * const *parm) -{ - const char *ret; - - if (_cocoa_video_started) return "Already started"; - _cocoa_video_started = true; - - memset(&_cocoa_video_data, 0, sizeof(_cocoa_video_data)); - - setupApplication(); - - /* Don't create a window or enter fullscreen if we're just going to show a dialog. */ - if (_cocoa_video_dialog) return NULL; - - QZ_VideoInit(); - - ret = QZ_SetVideoMode(_cur_resolution[0], _cur_resolution[1], _fullscreen); - if (ret != NULL) _video_driver->Stop(); - - return ret; -} - -void VideoDriver_Cocoa::MakeDirty(int left, int top, int width, int height) -{ - if (_cocoa_video_data.num_dirty_rects < MAX_DIRTY_RECTS) { - _cocoa_video_data.dirty_rects[_cocoa_video_data.num_dirty_rects].left = left; - _cocoa_video_data.dirty_rects[_cocoa_video_data.num_dirty_rects].top = top; - _cocoa_video_data.dirty_rects[_cocoa_video_data.num_dirty_rects].right = left + width; - _cocoa_video_data.dirty_rects[_cocoa_video_data.num_dirty_rects].bottom = top + height; - } - _cocoa_video_data.num_dirty_rects++; -} - -void VideoDriver_Cocoa::MainLoop() -{ - /* Start the main event loop */ - [NSApp run]; -} - -bool VideoDriver_Cocoa::ChangeResolution(int w, int h) -{ - const char *ret = QZ_SetVideoModeAndRestoreOnFailure((uint)w, (uint)h, _cocoa_video_data.fullscreen); - if (ret != NULL) { - DEBUG(driver, 0, "cocoa_v: VideoDriver_Cocoa::ChangeResolution failed with message: %s", ret); - } - - return ret == NULL; -} - -void VideoDriver_Cocoa::ToggleFullscreen(bool full_screen) -{ - const char *ret = QZ_SetVideoModeAndRestoreOnFailure(_cocoa_video_data.width, _cocoa_video_data.height, full_screen); - if (ret != NULL) { - DEBUG(driver, 0, "cocoa_v: VideoDriver_Cocoa::ToggleFullscreen failed with message: %s", ret); - } - - _fullscreen = _cocoa_video_data.fullscreen; -} - - -/* 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 */ |