diff --git a/src/bootstrap_gui.cpp b/src/bootstrap_gui.cpp index c71ef2e1cc..c123a650c6 100644 --- a/src/bootstrap_gui.cpp +++ b/src/bootstrap_gui.cpp @@ -218,7 +218,7 @@ bool HandleBootstrap() if (BlitterFactoryBase::GetCurrentBlitter()->GetScreenDepth() == 0) goto failure; /* If there is no network or no freetype, then there is nothing we can do. Go straight to failure. */ -#if defined(ENABLE_NETWORK) && defined(WITH_FREETYPE) && !defined(__APPLE__) && (defined(WITH_FONTCONFIG) || defined(WIN32)) +#if defined(ENABLE_NETWORK) && defined(WITH_FREETYPE) && (defined(WITH_FONTCONFIG) || defined(WIN32) || defined(__APPLE__)) if (!_network_available) goto failure; /* First tell the game we're bootstrapping. */ diff --git a/src/fontdetection.cpp b/src/fontdetection.cpp index 943254ab37..2a847933f0 100644 --- a/src/fontdetection.cpp +++ b/src/fontdetection.cpp @@ -486,16 +486,28 @@ bool SetFallbackFont(FreeTypeSettings *settings, const char *language_isocode, i for (CFIndex i = 0; descs != NULL && i < CFArrayGetCount(descs); i++) { CTFontDescriptorRef font = (CTFontDescriptorRef)CFArrayGetValueAtIndex(descs, i); + /* Get font traits. */ + CFDictionaryRef traits = (CFDictionaryRef)CTFontDescriptorCopyAttribute(font, kCTFontTraitsAttribute); + CTFontSymbolicTraits symbolic_traits; + CFNumberGetValue((CFNumberRef)CFDictionaryGetValue(traits, kCTFontSymbolicTrait), kCFNumberIntType, &symbolic_traits); + CFRelease(traits); + + /* Skip symbol fonts and vertical fonts. */ + if ((symbolic_traits & kCTFontClassMaskTrait) == (CTFontStylisticClass)kCTFontSymbolicClass || (symbolic_traits & kCTFontVerticalTrait)) continue; + /* Skip bold fonts (especially Arial Bold, which looks worse than regular Arial). */ + if (symbolic_traits & kCTFontBoldTrait) continue; + /* Select monospaced fonts if asked for. */ + if (((symbolic_traits & kCTFontMonoSpaceTrait) == kCTFontMonoSpaceTrait) != callback->Monospace()) continue; + /* Get font name. */ char name[128]; CFStringRef font_name = (CFStringRef)CTFontDescriptorCopyAttribute(font, kCTFontDisplayNameAttribute); CFStringGetCString(font_name, name, lengthof(name), kCFStringEncodingUTF8); CFRelease(font_name); - /* Skip some inappropriate or ugly looking fonts that have better alternatives. */ - if (strncmp(name, "Courier", 7) == 0 || strncmp(name, "Apple Symbols", 13) == 0 || - strncmp(name, ".Aqua", 5) == 0 || strncmp(name, "LastResort", 10) == 0 || - strncmp(name, "GB18030 Bitmap", 14) == 0) continue; + /* There are some special fonts starting with an '.' and the last + * resort font that aren't usable. Skip them. */ + if (name[0] == '.' || strncmp(name, "LastResort", 10) == 0) continue; /* Save result. */ callback->SetFontNames(settings, name); @@ -520,15 +532,18 @@ bool SetFallbackFont(FreeTypeSettings *settings, const char *language_isocode, i CFStringRef font_name; ATSFontGetName(font, kATSOptionFlagsDefault, &font_name); CFStringGetCString(font_name, name, lengthof(name), kCFStringEncodingUTF8); + + bool monospace = IsMonospaceFont(font_name); CFRelease(font_name); + /* Select monospaced fonts if asked for. */ + if (monospace != callback->Monospace()) continue; + /* We only want the base font and not bold or italic variants. */ if (strstr(name, "Italic") != NULL || strstr(name, "Bold")) continue; /* Skip some inappropriate or ugly looking fonts that have better alternatives. */ - if (strncmp(name, "Courier", 7) == 0 || strncmp(name, "Apple Symbols", 13) == 0 || - strncmp(name, ".Aqua", 5) == 0 || strncmp(name, "LastResort", 10) == 0 || - strncmp(name, "GB18030 Bitmap", 14) == 0) continue; + if (name[0] == '.' || strncmp(name, "Apple Symbols", 13) == 0 || strncmp(name, "LastResort", 10) == 0) continue; /* Save result. */ callback->SetFontNames(settings, name); diff --git a/src/os/macosx/macos.h b/src/os/macosx/macos.h index 16f34a3dc7..1e6729f121 100644 --- a/src/os/macosx/macos.h +++ b/src/os/macosx/macos.h @@ -36,4 +36,6 @@ static inline bool MacOSVersionIsAtLeast(long major, long minor, long bugfix) return true; } +bool IsMonospaceFont(CFStringRef name); + #endif /* MACOS_H */ diff --git a/src/os/macosx/macos.mm b/src/os/macosx/macos.mm index 38ddae8ee9..1b1a1d5a7a 100644 --- a/src/os/macosx/macos.mm +++ b/src/os/macosx/macos.mm @@ -193,3 +193,15 @@ uint GetCPUCoreCount() return count; } + +/** + * Check if a font is a monospace font. + * @param name Name of the font. + * @return True if the font is a monospace font. + */ +bool IsMonospaceFont(CFStringRef name) +{ + NSFont *font = [ NSFont fontWithName:(NSString *)name size:0.0f ]; + + return font != NULL ? [ font isFixedPitch ] : false; +} diff --git a/src/video/cocoa/cocoa_v.mm b/src/video/cocoa/cocoa_v.mm index f316a9bb58..8e7b7824ec 100644 --- a/src/video/cocoa/cocoa_v.mm +++ b/src/video/cocoa/cocoa_v.mm @@ -56,22 +56,50 @@ static bool _cocoa_video_dialog = false; CocoaSubdriver *_cocoa_subdriver = NULL; +static const NSString *OTTDMainLaunchGameEngine = @"ottdmain_launch_game_engine"; /** * The main class of the application, the application's delegate. */ @implementation OTTDMain +/** + * Stop the game engine. Must be called on main thread. + */ +- (void)stopEngine +{ + [ NSApp stop:self ]; + + /* Send an empty event to return from the run loop. Without that, application is stuck waiting for an event. */ + NSEvent *event = [ NSEvent otherEventWithType:NSApplicationDefined location:NSMakePoint(0, 0) modifierFlags:0 timestamp:0.0 windowNumber:0 context:nil subtype:0 data1:0 data2:0 ]; + [ NSApp postEvent:event atStart:YES ]; +} + +/** + * Start the game loop. + */ +- (void)launchGameEngine: (NSNotification*) note +{ + /* Setup cursor for the current _game_mode. */ + [ _cocoa_subdriver->cocoaview resetCursorRects ]; + + /* Hand off to main application code. */ + QZ_GameLoop(); + + /* We are done, thank you for playing. */ + [ self performSelectorOnMainThread:@selector(stopEngine) withObject:nil waitUntilDone:FALSE ]; +} + /** * Called when the internal event loop has just started running. */ - (void) applicationDidFinishLaunching: (NSNotification*) note { - /* Hand off to main application code */ - QZ_GameLoop(); + /* Add a notification observer so we can restart the game loop later on if necessary. */ + [ [ NSNotificationCenter defaultCenter ] addObserver:self selector:@selector(launchGameEngine:) name:OTTDMainLaunchGameEngine object:nil ]; - /* We're done, thank you for playing */ - [ NSApp stop:_ottd_main ]; + /* Start game loop. */ + [ [ NSNotificationCenter defaultCenter ] postNotificationName:OTTDMainLaunchGameEngine object:nil ]; } /** @@ -79,11 +107,18 @@ CocoaSubdriver *_cocoa_subdriver = NULL; */ - (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication*) sender { - HandleExitGameRequest(); return NSTerminateCancel; // NSTerminateLater ? } + +/** + * Remove ourself as a notification observer. + */ +- (void)unregisterObserver +{ + [ [ NSNotificationCenter defaultCenter ] removeObserver:self ]; +} @end /** @@ -326,6 +361,8 @@ void VideoDriver_Cocoa::Stop() { if (!_cocoa_video_started) return; + [ _ottd_main unregisterObserver ]; + delete _cocoa_subdriver; _cocoa_subdriver = NULL; @@ -385,7 +422,11 @@ void VideoDriver_Cocoa::MakeDirty(int left, int top, int width, int height) */ void VideoDriver_Cocoa::MainLoop() { - /* Start the main event loop */ + /* Restart game loop if it was already running (e.g. after bootstrapping), + * otherwise this call is a no-op. */ + [ [ NSNotificationCenter defaultCenter ] postNotificationName:OTTDMainLaunchGameEngine object:nil ]; + + /* Start the main event loop. */ [ NSApp run ]; } @@ -608,18 +649,12 @@ void cocoaReleaseAutoreleasePool() - (void)appWillUnhide:(NSNotification*)note { driver->SetPortAlphaOpaque (); - - /* save current visible surface */ - [ self cacheImageInRect:[ driver->cocoaview frame ] ]; } /** * Unhide and restore display plane and re-activate driver */ - (void)appDidUnhide:(NSNotification*)note { - /* restore cached image, since it may not be current, post expose event too */ - [ self restoreCachedImage ]; - driver->active = true; } /** @@ -708,7 +743,7 @@ void cocoaReleaseAutoreleasePool() [ super resetCursorRects ]; [ self clearTrackingRect ]; [ self setTrackingRect ]; - [ self addCursorRect:[ self bounds ] cursor:[ NSCursor clearCocoaCursor ] ]; + [ self addCursorRect:[ self bounds ] cursor:(_game_mode == GM_BOOTSTRAP ? [ NSCursor arrowCursor ] : [ NSCursor clearCocoaCursor ]) ]; } /** * Prepare for moving the application window diff --git a/src/video/win32_v.cpp b/src/video/win32_v.cpp index 922f8ba898..814fdc4b94 100644 --- a/src/video/win32_v.cpp +++ b/src/video/win32_v.cpp @@ -21,9 +21,15 @@ #include "../texteff.hpp" #include "../thread/thread.h" #include "../progress.h" +#include "../window_func.h" #include "win32_v.h" #include +/* Missing define in MinGW headers. */ +#ifndef MAPVK_VK_TO_CHAR +#define MAPVK_VK_TO_CHAR (2) +#endif + static struct { HWND main_wnd; HBITMAP dib_sect; @@ -432,6 +438,20 @@ static void PaintWindowThread(void *) _draw_thread->Exit(); } +/** Forward key presses to the window system. */ +static LRESULT HandleCharMsg(uint keycode, uint charcode) +{ +#if !defined(UNICODE) + wchar_t w; + int len = MultiByteToWideChar(_codepage, 0, (char*)&charcode, 1, &w, 1); + charcode = len == 1 ? w : 0; +#endif /* UNICODE */ + + HandleKeypress(GB(charcode, 0, 16) | (keycode << 16)); + + return 0; +} + static LRESULT CALLBACK WndProcGdi(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { static uint32 keycode = 0; @@ -592,31 +612,50 @@ static LRESULT CALLBACK WndProcGdi(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lP return 0; } -#if !defined(UNICODE) - wchar_t w; - int len = MultiByteToWideChar(_codepage, 0, (char*)&charcode, 1, &w, 1); - charcode = len == 1 ? w : 0; -#endif /* UNICODE */ + /* IMEs and other input methods sometimes send a WM_CHAR without a WM_KEYDOWN, + * clear the keycode so a previous WM_KEYDOWN doesn't become 'stuck'. */ + uint cur_keycode = keycode; + keycode = 0; - /* No matter the keyboard layout, we will map the '~' to the console */ - scancode = scancode == 41 ? (int)WKC_BACKQUOTE : keycode; - HandleKeypress(GB(charcode, 0, 16) | (scancode << 16)); - return 0; + return HandleCharMsg(cur_keycode, charcode); } case WM_KEYDOWN: { - keycode = MapWindowsKey(wParam); + /* No matter the keyboard layout, we will map the '~' to the console. */ + uint scancode = GB(lParam, 16, 8); + keycode = scancode == 41 ? WKC_BACKQUOTE : MapWindowsKey(wParam); /* Silently drop all messages handled by WM_CHAR. */ MSG msg; if (PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE)) { - if (msg.message == WM_CHAR && GB(lParam, 16, 8) == GB(msg.lParam, 16, 8)) { + if ((msg.message == WM_CHAR || msg.message == WM_DEADCHAR) && GB(lParam, 16, 8) == GB(msg.lParam, 16, 8)) { return 0; } } - HandleKeypress(0 | (keycode << 16)); - return 0; + uint charcode = MapVirtualKey(wParam, MAPVK_VK_TO_CHAR); + + /* No character translation? */ + if (charcode == 0) { + HandleKeypress(0 | (keycode << 16)); + return 0; + } + + /* Is the console key a dead key? If yes, ignore the first key down event. */ + if (HasBit(charcode, 31) && !console) { + if (scancode == 41) { + console = true; + return 0; + } + } + console = false; + + /* IMEs and other input methods sometimes send a WM_CHAR without a WM_KEYDOWN, + * clear the keycode so a previous WM_KEYDOWN doesn't become 'stuck'. */ + uint cur_keycode = keycode; + keycode = 0; + + return HandleCharMsg(cur_keycode, LOWORD(charcode)); } case WM_SYSKEYDOWN: // user presses F10 or Alt, both activating the title-menu @@ -979,7 +1018,8 @@ void VideoDriver_Win32::MainLoop() while (PeekMessage(&mesg, NULL, 0, 0, PM_REMOVE)) { InteractiveRandom(); // randomness - TranslateMessage(&mesg); + /* Convert key messages to char messages if we want text input. */ + if (EditBoxInGlobalFocus()) TranslateMessage(&mesg); DispatchMessage(&mesg); } if (_exit_game) return; diff --git a/src/window.cpp b/src/window.cpp index d223f5cfd6..77d4859900 100644 --- a/src/window.cpp +++ b/src/window.cpp @@ -277,7 +277,7 @@ void SetFocusedWindow(Window *w) * has a edit box as focused widget, or if a console is focused. * @return returns true if an edit box is in global focus or if the focused window is a console, else false */ -static bool EditBoxInGlobalFocus() +bool EditBoxInGlobalFocus() { if (_focused_window == NULL) return false; diff --git a/src/window_func.h b/src/window_func.h index 704885b9a8..ee494592be 100644 --- a/src/window_func.h +++ b/src/window_func.h @@ -52,4 +52,6 @@ void SetWindowClassesDirty(WindowClass cls); void DeleteWindowById(WindowClass cls, WindowNumber number, bool force = true); void DeleteWindowByClass(WindowClass cls); +bool EditBoxInGlobalFocus(); + #endif /* WINDOW_FUNC_H */