src/video/cocoa/event.mm
changeset 8435 38a1ab65d6e4
parent 8423 8453e9a0f0b5
child 8448 a59e637a8eb3
equal deleted inserted replaced
8434:6cce8c248f24 8435:38a1ab65d6e4
       
     1 /* $Id$ */
       
     2 
       
     3 /******************************************************************************
       
     4  *                             Cocoa video driver                             *
       
     5  * Known things left to do:                                                   *
       
     6  *  Nothing at the moment.                                                    *
       
     7  ******************************************************************************/
       
     8 
       
     9 #ifdef WITH_COCOA
       
    10 
       
    11 #import <Cocoa/Cocoa.h>
       
    12 #import <sys/time.h> /* gettimeofday */
       
    13 #import <sys/param.h> /* for MAXPATHLEN */
       
    14 #import <unistd.h>
       
    15 
       
    16 /**
       
    17  * Important notice regarding all modifications!!!!!!!
       
    18  * There are certain limitations because the file is objective C++.
       
    19  * gdb has limitations.
       
    20  * C++ and objective C code can't be joined in all cases (classes stuff).
       
    21  * Read http://developer.apple.com/releasenotes/Cocoa/Objective-C++.html for more information.
       
    22  */
       
    23 
       
    24 
       
    25 /* Defined in stdbool.h */
       
    26 #ifndef __cplusplus
       
    27 # ifndef __BEOS__
       
    28 #  undef bool
       
    29 #  undef false
       
    30 #  undef true
       
    31 # endif
       
    32 #endif
       
    33 
       
    34 
       
    35 #include "../../stdafx.h"
       
    36 #include "../../openttd.h"
       
    37 #include "../../debug.h"
       
    38 #include "../../macros.h"
       
    39 #include "../../os/macosx/splash.h"
       
    40 #include "../../variables.h"
       
    41 #include "../../gfx.h"
       
    42 #include "cocoa_v.h"
       
    43 #include "cocoa_keys.h"
       
    44 #include "../../blitter/factory.hpp"
       
    45 #include "../../fileio.h"
       
    46 
       
    47 #undef Point
       
    48 #undef Rect
       
    49 
       
    50 /* Right Mouse Button Emulation enum */
       
    51 enum {
       
    52 	RMBE_COMMAND,
       
    53 	RMBE_CONTROL,
       
    54 	RMBE_OFF,
       
    55 };
       
    56 
       
    57 
       
    58 static bool _show_mouse = true;
       
    59 static unsigned int _current_mods;
       
    60 static bool _tab_is_down;
       
    61 static bool _emulating_right_button;
       
    62 #ifdef _DEBUG
       
    63 static uint32 _tEvent;
       
    64 #endif
       
    65 
       
    66 
       
    67 static uint32 GetTick()
       
    68 {
       
    69 	struct timeval tim;
       
    70 
       
    71 	gettimeofday(&tim, NULL);
       
    72 	return tim.tv_usec / 1000 + tim.tv_sec * 1000;
       
    73 }
       
    74 
       
    75 
       
    76 void QZ_ShowMouse()
       
    77 {
       
    78 	if (!_show_mouse) {
       
    79 		[ NSCursor unhide ];
       
    80 		_show_mouse = true;
       
    81 
       
    82 		// Hide the openttd cursor when leaving the window
       
    83 		if (_cocoa_subdriver != NULL)
       
    84 			UndrawMouseCursor();
       
    85 		_cursor.in_window = false;
       
    86 	}
       
    87 }
       
    88 
       
    89 void QZ_HideMouse()
       
    90 {
       
    91 	if (_show_mouse) {
       
    92 		/*
       
    93 		 * Don't hide the cursor when compiling in debug mode.
       
    94 		 * Note: Not hiding the cursor will cause artefacts around it in 8bpp fullscreen mode.
       
    95 		 */
       
    96 #ifndef _DEBUG
       
    97 		[ NSCursor hide ];
       
    98 #endif
       
    99 		_show_mouse = false;
       
   100 
       
   101 		// Show the openttd cursor again
       
   102 		_cursor.in_window = true;
       
   103 	}
       
   104 }
       
   105 
       
   106 static void QZ_WarpCursor(int x, int y)
       
   107 {
       
   108 	NSPoint p;
       
   109 	CGPoint cgp;
       
   110 
       
   111 	assert(_cocoa_subdriver);
       
   112 
       
   113 	/* Only allow warping when in foreground */
       
   114 	if (![ NSApp isActive ]) return;
       
   115 
       
   116 	p = NSMakePoint(x, y);
       
   117 	cgp = _cocoa_subdriver->PrivateLocalToCG(&p);
       
   118 
       
   119 	/* this is the magic call that fixes cursor "freezing" after warp */
       
   120 	CGSetLocalEventsSuppressionInterval(0.0);
       
   121 	/* Do the actual warp */
       
   122 	CGWarpMouseCursorPosition(cgp);
       
   123 
       
   124 	/* Generate the mouse moved event */
       
   125 }
       
   126 
       
   127 
       
   128 static void QZ_CheckPaletteAnim()
       
   129 {
       
   130 	if (_pal_count_dirty != 0) {
       
   131 		Blitter *blitter = BlitterFactoryBase::GetCurrentBlitter();
       
   132 
       
   133 		switch (blitter->UsePaletteAnimation()) {
       
   134 			case Blitter::PALETTE_ANIMATION_VIDEO_BACKEND:
       
   135 				_cocoa_subdriver->UpdatePalette(_pal_first_dirty, _pal_count_dirty);
       
   136 				break;
       
   137 
       
   138 			case Blitter::PALETTE_ANIMATION_BLITTER:
       
   139 				blitter->PaletteAnimate(_pal_first_dirty, _pal_count_dirty);
       
   140 				break;
       
   141 
       
   142 			case Blitter::PALETTE_ANIMATION_NONE:
       
   143 				break;
       
   144 
       
   145 			default:
       
   146 				NOT_REACHED();
       
   147 		}
       
   148 		_pal_count_dirty = 0;
       
   149 	}
       
   150 }
       
   151 
       
   152 
       
   153 
       
   154 struct VkMapping {
       
   155 	unsigned short vk_from;
       
   156 	byte map_to;
       
   157 };
       
   158 
       
   159 #define AS(x, z) {x, z}
       
   160 
       
   161 static const VkMapping _vk_mapping[] = {
       
   162 	AS(QZ_BACKQUOTE,  WKC_BACKQUOTE), // key left of '1'
       
   163 	AS(QZ_BACKQUOTE2, WKC_BACKQUOTE), // some keyboards have it on another scancode
       
   164 
       
   165 	/* Pageup stuff + up/down */
       
   166 	AS(QZ_PAGEUP,   WKC_PAGEUP),
       
   167 	AS(QZ_PAGEDOWN, WKC_PAGEDOWN),
       
   168 
       
   169 	AS(QZ_UP,    WKC_UP),
       
   170 	AS(QZ_DOWN,  WKC_DOWN),
       
   171 	AS(QZ_LEFT,  WKC_LEFT),
       
   172 	AS(QZ_RIGHT, WKC_RIGHT),
       
   173 
       
   174 	AS(QZ_HOME, WKC_HOME),
       
   175 	AS(QZ_END,  WKC_END),
       
   176 
       
   177 	AS(QZ_INSERT, WKC_INSERT),
       
   178 	AS(QZ_DELETE, WKC_DELETE),
       
   179 
       
   180 	/* Letters. QZ_[a-z] is not in numerical order so we can't use AM(...) */
       
   181 	AS(QZ_a, 'A'),
       
   182 	AS(QZ_b, 'B'),
       
   183 	AS(QZ_c, 'C'),
       
   184 	AS(QZ_d, 'D'),
       
   185 	AS(QZ_e, 'E'),
       
   186 	AS(QZ_f, 'F'),
       
   187 	AS(QZ_g, 'G'),
       
   188 	AS(QZ_h, 'H'),
       
   189 	AS(QZ_i, 'I'),
       
   190 	AS(QZ_j, 'J'),
       
   191 	AS(QZ_k, 'K'),
       
   192 	AS(QZ_l, 'L'),
       
   193 	AS(QZ_m, 'M'),
       
   194 	AS(QZ_n, 'N'),
       
   195 	AS(QZ_o, 'O'),
       
   196 	AS(QZ_p, 'P'),
       
   197 	AS(QZ_q, 'Q'),
       
   198 	AS(QZ_r, 'R'),
       
   199 	AS(QZ_s, 'S'),
       
   200 	AS(QZ_t, 'T'),
       
   201 	AS(QZ_u, 'U'),
       
   202 	AS(QZ_v, 'V'),
       
   203 	AS(QZ_w, 'W'),
       
   204 	AS(QZ_x, 'X'),
       
   205 	AS(QZ_y, 'Y'),
       
   206 	AS(QZ_z, 'Z'),
       
   207 	/* Same thing for digits */
       
   208 	AS(QZ_0, '0'),
       
   209 	AS(QZ_1, '1'),
       
   210 	AS(QZ_2, '2'),
       
   211 	AS(QZ_3, '3'),
       
   212 	AS(QZ_4, '4'),
       
   213 	AS(QZ_5, '5'),
       
   214 	AS(QZ_6, '6'),
       
   215 	AS(QZ_7, '7'),
       
   216 	AS(QZ_8, '8'),
       
   217 	AS(QZ_9, '9'),
       
   218 
       
   219 	AS(QZ_ESCAPE,    WKC_ESC),
       
   220 	AS(QZ_PAUSE,     WKC_PAUSE),
       
   221 	AS(QZ_BACKSPACE, WKC_BACKSPACE),
       
   222 
       
   223 	AS(QZ_SPACE,  WKC_SPACE),
       
   224 	AS(QZ_RETURN, WKC_RETURN),
       
   225 	AS(QZ_TAB,    WKC_TAB),
       
   226 
       
   227 	/* Function keys */
       
   228 	AS(QZ_F1,  WKC_F1),
       
   229 	AS(QZ_F2,  WKC_F2),
       
   230 	AS(QZ_F3,  WKC_F3),
       
   231 	AS(QZ_F4,  WKC_F4),
       
   232 	AS(QZ_F5,  WKC_F5),
       
   233 	AS(QZ_F6,  WKC_F6),
       
   234 	AS(QZ_F7,  WKC_F7),
       
   235 	AS(QZ_F8,  WKC_F8),
       
   236 	AS(QZ_F9,  WKC_F9),
       
   237 	AS(QZ_F10, WKC_F10),
       
   238 	AS(QZ_F11, WKC_F11),
       
   239 	AS(QZ_F12, WKC_F12),
       
   240 
       
   241 	/* Numeric part */
       
   242 	AS(QZ_KP0,         WKC_NUM_0),
       
   243 	AS(QZ_KP1,         WKC_NUM_1),
       
   244 	AS(QZ_KP2,         WKC_NUM_2),
       
   245 	AS(QZ_KP3,         WKC_NUM_3),
       
   246 	AS(QZ_KP4,         WKC_NUM_4),
       
   247 	AS(QZ_KP5,         WKC_NUM_5),
       
   248 	AS(QZ_KP6,         WKC_NUM_6),
       
   249 	AS(QZ_KP7,         WKC_NUM_7),
       
   250 	AS(QZ_KP8,         WKC_NUM_8),
       
   251 	AS(QZ_KP9,         WKC_NUM_9),
       
   252 	AS(QZ_KP_DIVIDE,   WKC_NUM_DIV),
       
   253 	AS(QZ_KP_MULTIPLY, WKC_NUM_MUL),
       
   254 	AS(QZ_KP_MINUS,    WKC_NUM_MINUS),
       
   255 	AS(QZ_KP_PLUS,     WKC_NUM_PLUS),
       
   256 	AS(QZ_KP_ENTER,    WKC_NUM_ENTER),
       
   257 	AS(QZ_KP_PERIOD,   WKC_NUM_DECIMAL),
       
   258 
       
   259 	/* Other non-letter keys */
       
   260 	AS(QZ_SLASH,        WKC_SLASH),
       
   261 	AS(QZ_SEMICOLON,    WKC_SEMICOLON),
       
   262 	AS(QZ_EQUALS,       WKC_EQUALS),
       
   263 	AS(QZ_LEFTBRACKET,  WKC_L_BRACKET),
       
   264 	AS(QZ_BACKSLASH,    WKC_BACKSLASH),
       
   265 	AS(QZ_RIGHTBRACKET, WKC_R_BRACKET),
       
   266 
       
   267 	AS(QZ_QUOTE,   WKC_SINGLEQUOTE),
       
   268 	AS(QZ_COMMA,   WKC_COMMA),
       
   269 	AS(QZ_MINUS,   WKC_MINUS),
       
   270 	AS(QZ_PERIOD,  WKC_PERIOD)
       
   271 };
       
   272 
       
   273 
       
   274 static uint32 QZ_MapKey(unsigned short sym)
       
   275 {
       
   276 	const VkMapping *map;
       
   277 	uint32 key = 0;
       
   278 
       
   279 	for (map = _vk_mapping; map != endof(_vk_mapping); ++map) {
       
   280 		if (sym == map->vk_from) {
       
   281 			key = map->map_to;
       
   282 			break;
       
   283 		}
       
   284 	}
       
   285 
       
   286 	if (_current_mods & NSShiftKeyMask)     key |= WKC_SHIFT;
       
   287 	if (_current_mods & NSControlKeyMask)   key |= (_patches.right_mouse_btn_emulation != RMBE_CONTROL ? WKC_CTRL : WKC_META);
       
   288 	if (_current_mods & NSAlternateKeyMask) key |= WKC_ALT;
       
   289 	if (_current_mods & NSCommandKeyMask)   key |= (_patches.right_mouse_btn_emulation != RMBE_CONTROL ? WKC_META : WKC_CTRL);
       
   290 
       
   291 	return key << 16;
       
   292 }
       
   293 
       
   294 static void QZ_KeyEvent(unsigned short keycode, unsigned short unicode, BOOL down)
       
   295 {
       
   296 	switch (keycode) {
       
   297 		case QZ_UP:    SB(_dirkeys, 1, 1, down); break;
       
   298 		case QZ_DOWN:  SB(_dirkeys, 3, 1, down); break;
       
   299 		case QZ_LEFT:  SB(_dirkeys, 0, 1, down); break;
       
   300 		case QZ_RIGHT: SB(_dirkeys, 2, 1, down); break;
       
   301 
       
   302 		case QZ_TAB: _tab_is_down = down; break;
       
   303 
       
   304 		case QZ_RETURN:
       
   305 		case QZ_f:
       
   306 			if (down && (_current_mods & NSCommandKeyMask)) {
       
   307 				_video_driver->ToggleFullscreen(!_fullscreen);
       
   308 			}
       
   309 			break;
       
   310 	}
       
   311 
       
   312 	if (down) {
       
   313 		uint32 pressed_key = QZ_MapKey(keycode) | unicode;
       
   314 		HandleKeypress(pressed_key);
       
   315 		DEBUG(driver, 2, "cocoa_v: QZ_KeyEvent: %x (%x), down, mapping: %x", keycode, unicode, pressed_key);
       
   316 	} else {
       
   317 		DEBUG(driver, 2, "cocoa_v: QZ_KeyEvent: %x (%x), up", keycode, unicode);
       
   318 	}
       
   319 }
       
   320 
       
   321 static void QZ_DoUnsidedModifiers(unsigned int newMods)
       
   322 {
       
   323 	const int mapping[] = { QZ_CAPSLOCK, QZ_LSHIFT, QZ_LCTRL, QZ_LALT, QZ_LMETA };
       
   324 
       
   325 	int i;
       
   326 	unsigned int bit;
       
   327 
       
   328 	if (_current_mods == newMods) return;
       
   329 
       
   330 	/* Iterate through the bits, testing each against the current modifiers */
       
   331 	for (i = 0, bit = NSAlphaShiftKeyMask; bit <= NSCommandKeyMask; bit <<= 1, ++i) {
       
   332 		unsigned int currentMask, newMask;
       
   333 
       
   334 		currentMask = _current_mods & bit;
       
   335 		newMask     = newMods & bit;
       
   336 
       
   337 		if (currentMask && currentMask != newMask) { /* modifier up event */
       
   338 			/* If this was Caps Lock, we need some additional voodoo to make SDL happy (is this needed in ottd?) */
       
   339 			if (bit == NSAlphaShiftKeyMask) QZ_KeyEvent(mapping[i], 0, YES);
       
   340 			QZ_KeyEvent(mapping[i], 0, NO);
       
   341 		} else if (newMask && currentMask != newMask) { /* modifier down event */
       
   342 			QZ_KeyEvent(mapping[i], 0, YES);
       
   343 			/* If this was Caps Lock, we need some additional voodoo to make SDL happy (is this needed in ottd?) */
       
   344 			if (bit == NSAlphaShiftKeyMask) QZ_KeyEvent(mapping[i], 0, NO);
       
   345 		}
       
   346 	}
       
   347 
       
   348 	_current_mods = newMods;
       
   349 }
       
   350 
       
   351 static void QZ_MouseMovedEvent(int x, int y)
       
   352 {
       
   353 	if (_cursor.fix_at) {
       
   354 		int dx = x - _cursor.pos.x;
       
   355 		int dy = y - _cursor.pos.y;
       
   356 
       
   357 		if (dx != 0 || dy != 0) {
       
   358 			_cursor.delta.x += dx;
       
   359 			_cursor.delta.y += dy;
       
   360 
       
   361 			QZ_WarpCursor(_cursor.pos.x, _cursor.pos.y);
       
   362 		}
       
   363 	} else {
       
   364 		_cursor.delta.x = x - _cursor.pos.x;
       
   365 		_cursor.delta.y = y - _cursor.pos.y;
       
   366 		_cursor.pos.x = x;
       
   367 		_cursor.pos.y = y;
       
   368 		_cursor.dirty = true;
       
   369 	}
       
   370 	HandleMouseEvents();
       
   371 }
       
   372 
       
   373 
       
   374 static void QZ_MouseButtonEvent(int button, BOOL down)
       
   375 {
       
   376 	switch (button) {
       
   377 		case 0:
       
   378 			if (down) {
       
   379 				_left_button_down = true;
       
   380 			} else {
       
   381 				_left_button_down = false;
       
   382 				_left_button_clicked = false;
       
   383 			}
       
   384 			HandleMouseEvents();
       
   385 			break;
       
   386 
       
   387 		case 1:
       
   388 			if (down) {
       
   389 				_right_button_down = true;
       
   390 				_right_button_clicked = true;
       
   391 			} else {
       
   392 				_right_button_down = false;
       
   393 			}
       
   394 			HandleMouseEvents();
       
   395 			break;
       
   396 	}
       
   397 }
       
   398 
       
   399 
       
   400 
       
   401 
       
   402 static bool QZ_PollEvent()
       
   403 {
       
   404 	NSEvent *event;
       
   405 	NSPoint pt;
       
   406 	NSString *chars;
       
   407 #ifdef _DEBUG
       
   408 	uint32 et0, et;
       
   409 #endif
       
   410 
       
   411 	assert(_cocoa_subdriver != NULL);
       
   412 
       
   413 #ifdef _DEBUG
       
   414 	et0 = GetTick();
       
   415 #endif
       
   416 	event = [ NSApp nextEventMatchingMask:NSAnyEventMask
       
   417 			untilDate: [ NSDate distantPast ]
       
   418 			inMode: NSDefaultRunLoopMode dequeue:YES ];
       
   419 #ifdef _DEBUG
       
   420 	et = GetTick();
       
   421 	_tEvent+= et - et0;
       
   422 #endif
       
   423 
       
   424 	if (event == nil) return false;
       
   425 	if (!_cocoa_subdriver->IsActive()) {
       
   426 		QZ_ShowMouse();
       
   427 		[NSApp sendEvent:event];
       
   428 		return true;
       
   429 	}
       
   430 
       
   431 	QZ_DoUnsidedModifiers( [ event modifierFlags ] );
       
   432 
       
   433 	switch ([event type]) {
       
   434 		case NSMouseMoved:
       
   435 		case NSOtherMouseDragged:
       
   436 		case NSRightMouseDragged:
       
   437 		case NSLeftMouseDragged:
       
   438 			pt = _cocoa_subdriver->GetMouseLocation(event);
       
   439 			if (!_cocoa_subdriver->MouseIsInsideView(&pt) &&
       
   440 					!_emulating_right_button) {
       
   441 				QZ_ShowMouse();
       
   442 				[NSApp sendEvent:event];
       
   443 				break;
       
   444 			}
       
   445 
       
   446 			QZ_HideMouse();
       
   447 			QZ_MouseMovedEvent((int)pt.x, (int)pt.y);
       
   448 			break;
       
   449 
       
   450 		case NSLeftMouseDown:
       
   451 		{
       
   452 			uint32 keymask = 0;
       
   453 			if (_patches.right_mouse_btn_emulation == RMBE_COMMAND) keymask |= NSCommandKeyMask;
       
   454 			if (_patches.right_mouse_btn_emulation == RMBE_CONTROL) keymask |= NSControlKeyMask;
       
   455 
       
   456 			pt = _cocoa_subdriver->GetMouseLocation(event);
       
   457 
       
   458 			if (!([ event modifierFlags ] & keymask) ||
       
   459 					!_cocoa_subdriver->MouseIsInsideView(&pt)) {
       
   460 				[NSApp sendEvent:event];
       
   461 			}
       
   462 
       
   463 			if (!_cocoa_subdriver->MouseIsInsideView(&pt)) {
       
   464 				QZ_ShowMouse();
       
   465 				break;
       
   466 			}
       
   467 
       
   468 			QZ_HideMouse();
       
   469 			QZ_MouseMovedEvent((int)pt.x, (int)pt.y);
       
   470 
       
   471 			/* Right mouse button emulation */
       
   472 			if ([ event modifierFlags ] & keymask) {
       
   473 				_emulating_right_button = true;
       
   474 				QZ_MouseButtonEvent(1, YES);
       
   475 			} else {
       
   476 				QZ_MouseButtonEvent(0, YES);
       
   477 			}
       
   478 			break;
       
   479 		}
       
   480 		case NSLeftMouseUp:
       
   481 			[NSApp sendEvent:event];
       
   482 
       
   483 			pt = _cocoa_subdriver->GetMouseLocation(event);
       
   484 			if (!_cocoa_subdriver->MouseIsInsideView(&pt)) {
       
   485 				QZ_ShowMouse();
       
   486 				break;
       
   487 			}
       
   488 
       
   489 			QZ_HideMouse();
       
   490 			QZ_MouseMovedEvent((int)pt.x, (int)pt.y);
       
   491 
       
   492 			/* Right mouse button emulation */
       
   493 			if (_emulating_right_button) {
       
   494 				_emulating_right_button = false;
       
   495 				QZ_MouseButtonEvent(1, NO);
       
   496 			} else {
       
   497 				QZ_MouseButtonEvent(0, NO);
       
   498 			}
       
   499 			break;
       
   500 
       
   501 		case NSRightMouseDown:
       
   502 			pt = _cocoa_subdriver->GetMouseLocation(event);
       
   503 			if (!_cocoa_subdriver->MouseIsInsideView(&pt)) {
       
   504 				QZ_ShowMouse();
       
   505 				[NSApp sendEvent:event];
       
   506 				break;
       
   507 			}
       
   508 
       
   509 			QZ_HideMouse();
       
   510 			QZ_MouseMovedEvent((int)pt.x, (int)pt.y);
       
   511 			QZ_MouseButtonEvent(1, YES);
       
   512 			break;
       
   513 
       
   514 		case NSRightMouseUp:
       
   515 			pt = _cocoa_subdriver->GetMouseLocation(event);
       
   516 			if (!_cocoa_subdriver->MouseIsInsideView(&pt)) {
       
   517 				QZ_ShowMouse();
       
   518 				[NSApp sendEvent:event];
       
   519 				break;
       
   520 			}
       
   521 
       
   522 			QZ_HideMouse();
       
   523 			QZ_MouseMovedEvent((int)pt.x, (int)pt.y);
       
   524 			QZ_MouseButtonEvent(1, NO);
       
   525 			break;
       
   526 
       
   527 #if 0
       
   528 		/* This is not needed since openttd currently only use two buttons */
       
   529 		case NSOtherMouseDown:
       
   530 			pt = QZ_GetMouseLocation(event);
       
   531 			if (!QZ_MouseIsInsideView(&pt)) {
       
   532 				QZ_ShowMouse();
       
   533 				[NSApp sendEvent:event];
       
   534 				break;
       
   535 			}
       
   536 
       
   537 			QZ_HideMouse();
       
   538 			QZ_MouseMovedEvent((int)pt.x, (int)pt.y);
       
   539 			QZ_MouseButtonEvent([ event buttonNumber ], YES);
       
   540 			break;
       
   541 
       
   542 		case NSOtherMouseUp:
       
   543 			pt = QZ_GetMouseLocation(event);
       
   544 			if (!QZ_MouseIsInsideView(&pt)) {
       
   545 				QZ_ShowMouse();
       
   546 				[NSApp sendEvent:event];
       
   547 				break;
       
   548 			}
       
   549 
       
   550 			QZ_HideMouse();
       
   551 			QZ_MouseMovedEvent((int)pt.x, (int)pt.y);
       
   552 			QZ_MouseButtonEvent([ event buttonNumber ], NO);
       
   553 			break;
       
   554 #endif
       
   555 
       
   556 		case NSKeyDown:
       
   557 			/* Quit, hide and minimize */
       
   558 			switch ([event keyCode]) {
       
   559 				case QZ_q:
       
   560 				case QZ_h:
       
   561 				case QZ_m:
       
   562 					if ([ event modifierFlags ] & NSCommandKeyMask) {
       
   563 						[NSApp sendEvent:event];
       
   564 					}
       
   565 					break;
       
   566 			}
       
   567 
       
   568 			chars = [ event characters ];
       
   569 			QZ_KeyEvent([event keyCode], [ chars length ] ? [ chars characterAtIndex:0 ] : 0, YES);
       
   570 			break;
       
   571 
       
   572 		case NSKeyUp:
       
   573 			/* Quit, hide and minimize */
       
   574 			switch ([event keyCode]) {
       
   575 				case QZ_q:
       
   576 				case QZ_h:
       
   577 				case QZ_m:
       
   578 					if ([ event modifierFlags ] & NSCommandKeyMask) {
       
   579 						[NSApp sendEvent:event];
       
   580 					}
       
   581 					break;
       
   582 			}
       
   583 
       
   584 			chars = [ event characters ];
       
   585 			QZ_KeyEvent([event keyCode], [ chars length ] ? [ chars characterAtIndex:0 ] : 0, NO);
       
   586 			break;
       
   587 
       
   588 		case NSScrollWheel:
       
   589 			if ([ event deltaY ] > 0.0) { /* Scroll up */
       
   590 				_cursor.wheel--;
       
   591 			} else if ([ event deltaY ] < 0.0) { /* Scroll down */
       
   592 				_cursor.wheel++;
       
   593 			} /* else: deltaY was 0.0 and we don't want to do anything */
       
   594 
       
   595 			/* Set the scroll count for scrollwheel scrolling */
       
   596 			_cursor.h_wheel -= (int)([ event deltaX ]* 5 * _patches.scrollwheel_multiplier);
       
   597 			_cursor.v_wheel -= (int)([ event deltaY ]* 5 * _patches.scrollwheel_multiplier);
       
   598 			break;
       
   599 
       
   600 		default:
       
   601 			[NSApp sendEvent:event];
       
   602 	}
       
   603 
       
   604 	return true;
       
   605 }
       
   606 
       
   607 
       
   608 void QZ_GameLoop()
       
   609 {
       
   610 	uint32 cur_ticks = GetTick();
       
   611 	uint32 last_cur_ticks = cur_ticks;
       
   612 	uint32 next_tick = cur_ticks + 30;
       
   613 	uint32 pal_tick = 0;
       
   614 #ifdef _DEBUG
       
   615 	uint32 et0, et, st0, st;
       
   616 #endif
       
   617 	int i;
       
   618 
       
   619 #ifdef _DEBUG
       
   620 	et0 = GetTick();
       
   621 	st = 0;
       
   622 #endif
       
   623 
       
   624 	_screen.dst_ptr = _cocoa_subdriver->GetPixelBuffer();
       
   625 	DisplaySplashImage();
       
   626 	QZ_CheckPaletteAnim();
       
   627 	_cocoa_subdriver->Draw();
       
   628 	CSleep(1);
       
   629 
       
   630 	for (i = 0; i < 2; i++) GameLoop();
       
   631 
       
   632 	_screen.dst_ptr = _cocoa_subdriver->GetPixelBuffer();
       
   633 	UpdateWindows();
       
   634 	QZ_CheckPaletteAnim();
       
   635 	_cocoa_subdriver->Draw();
       
   636 	CSleep(1);
       
   637 
       
   638 	for (;;) {
       
   639 		uint32 prev_cur_ticks = cur_ticks; // to check for wrapping
       
   640 		InteractiveRandom(); // randomness
       
   641 
       
   642 		while (QZ_PollEvent()) {}
       
   643 
       
   644 		if (_exit_game) break;
       
   645 
       
   646 #if defined(_DEBUG)
       
   647 		if (_current_mods & NSShiftKeyMask)
       
   648 #else
       
   649 		if (_tab_is_down)
       
   650 #endif
       
   651 		{
       
   652 			if (!_networking && _game_mode != GM_MENU) _fast_forward |= 2;
       
   653 		} else if (_fast_forward & 2) {
       
   654 			_fast_forward = 0;
       
   655 		}
       
   656 
       
   657 		cur_ticks = GetTick();
       
   658 		if (cur_ticks >= next_tick || (_fast_forward && !_pause_game) || cur_ticks < prev_cur_ticks) {
       
   659 			_realtime_tick += cur_ticks - last_cur_ticks;
       
   660 			last_cur_ticks = cur_ticks;
       
   661 			next_tick = cur_ticks + 30;
       
   662 
       
   663 			_ctrl_pressed = !!(_current_mods & ( _patches.right_mouse_btn_emulation != RMBE_CONTROL ? NSControlKeyMask : NSCommandKeyMask));
       
   664 			_shift_pressed = !!(_current_mods & NSShiftKeyMask);
       
   665 
       
   666 			GameLoop();
       
   667 
       
   668 			_screen.dst_ptr = _cocoa_subdriver->GetPixelBuffer();
       
   669 			UpdateWindows();
       
   670 			if (++pal_tick > 4) {
       
   671 				QZ_CheckPaletteAnim();
       
   672 				pal_tick = 1;
       
   673 			}
       
   674 			_cocoa_subdriver->Draw();
       
   675 		} else {
       
   676 #ifdef _DEBUG
       
   677 			st0 = GetTick();
       
   678 #endif
       
   679 			CSleep(1);
       
   680 #ifdef _DEBUG
       
   681 			st += GetTick() - st0;
       
   682 #endif
       
   683 			_screen.dst_ptr = _cocoa_subdriver->GetPixelBuffer();
       
   684 			DrawChatMessage();
       
   685 			DrawMouseCursor();
       
   686 			_cocoa_subdriver->Draw();
       
   687 		}
       
   688 	}
       
   689 
       
   690 #ifdef _DEBUG
       
   691 	et = GetTick();
       
   692 
       
   693 	DEBUG(driver, 1, "cocoa_v: nextEventMatchingMask took %i ms total", _tEvent);
       
   694 	DEBUG(driver, 1, "cocoa_v: game loop took %i ms total (%i ms without sleep)", et - et0, et - et0 - st);
       
   695 	DEBUG(driver, 1, "cocoa_v: (nextEventMatchingMask total)/(game loop total) is %f%%", (double)_tEvent / (double)(et - et0) * 100);
       
   696 	DEBUG(driver, 1, "cocoa_v: (nextEventMatchingMask total)/(game loop without sleep total) is %f%%", (double)_tEvent / (double)(et - et0 - st) * 100);
       
   697 #endif
       
   698 }
       
   699 
       
   700 #endif /* WITH_COCOA */