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 #include <AvailabilityMacros.h> |
|
12 #if (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_4) |
|
13 /* On 10.4, we get tons of warnings that the QuickDraw functions are deprecated. |
|
14 * We know that. Don't keep bugging us about that. */ |
|
15 # undef AVAILABLE_MAC_OS_X_VERSION_10_0_AND_LATER_BUT_DEPRECATED_IN_MAC_OS_X_VERSION_10_4 |
|
16 # 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 |
|
17 #endif |
|
18 |
|
19 #import <Cocoa/Cocoa.h> |
|
20 #import <sys/time.h> /* gettimeofday */ |
|
21 #import <sys/param.h> /* for MAXPATHLEN */ |
|
22 #import <unistd.h> |
|
23 |
|
24 /** |
|
25 * Important notice regarding all modifications!!!!!!! |
|
26 * There are certain limitations because the file is objective C++. |
|
27 * gdb has limitations. |
|
28 * C++ and objective C code can't be joined in all cases (classes stuff). |
|
29 * Read http://developer.apple.com/releasenotes/Cocoa/Objective-C++.html for more information. |
|
30 */ |
|
31 |
|
32 |
|
33 /* Portions of CPS.h */ |
|
34 struct CPSProcessSerNum { |
|
35 UInt32 lo; |
|
36 UInt32 hi; |
|
37 }; |
|
38 |
|
39 extern "C" OSErr CPSGetCurrentProcess(CPSProcessSerNum* psn); |
|
40 extern "C" OSErr CPSEnableForegroundOperation(CPSProcessSerNum* psn, UInt32 _arg2, UInt32 _arg3, UInt32 _arg4, UInt32 _arg5); |
|
41 extern "C" OSErr CPSSetFrontProcess(CPSProcessSerNum* psn); |
|
42 |
|
43 /* From Menus.h (according to Xcode Developer Documentation) */ |
|
44 extern "C" void ShowMenuBar(); |
|
45 extern "C" void HideMenuBar(); |
|
46 |
|
47 /* Disables a warning. This is needed since the method exists but has been dropped from the header, supposedly as of 10.4. */ |
|
48 #if (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_4) |
|
49 @interface NSApplication(NSAppleMenu) |
|
50 - (void)setAppleMenu:(NSMenu *)menu; |
|
51 @end |
|
52 #endif |
|
53 |
|
54 |
|
55 /* Defined in stdbool.h */ |
|
56 #ifndef __cplusplus |
|
57 # ifndef __BEOS__ |
|
58 # undef bool |
|
59 # undef false |
|
60 # undef true |
|
61 # endif |
|
62 #endif |
|
63 |
|
64 |
|
65 #include "../stdafx.h" |
|
66 #include "../openttd.h" |
|
67 #include "../debug.h" |
|
68 #include "../macros.h" |
|
69 #include "../os/macosx/splash.h" |
|
70 #include "../variables.h" |
|
71 #include "../gfx.h" |
|
72 #include "cocoa_v.h" |
|
73 #include "cocoa_keys.h" |
|
74 #include "../blitter/factory.hpp" |
|
75 #include "../fileio.h" |
|
76 |
|
77 #undef Point |
|
78 #undef Rect |
|
79 |
|
80 /* Right Mouse Button Emulation enum */ |
|
81 enum { |
|
82 RMBE_COMMAND, |
|
83 RMBE_CONTROL, |
|
84 RMBE_OFF, |
|
85 }; |
|
86 |
|
87 |
|
88 /* Subclass of NSWindow to fix genie effect and support resize events */ |
|
89 @interface OTTD_QuartzWindow : NSWindow |
|
90 - (void)miniaturize:(id)sender; |
|
91 - (void)display; |
|
92 - (void)setFrame:(NSRect)frameRect display:(BOOL)flag; |
|
93 - (void)appDidHide:(NSNotification*)note; |
|
94 - (void)appWillUnhide:(NSNotification*)note; |
|
95 - (void)appDidUnhide:(NSNotification*)note; |
|
96 - (id)initWithContentRect:(NSRect)contentRect styleMask:(unsigned int)styleMask backing:(NSBackingStoreType)backingType defer:(BOOL)flag; |
|
97 @end |
|
98 |
|
99 /* Delegate for our NSWindow to send ask for quit on close */ |
|
100 @interface OTTD_QuartzWindowDelegate : NSObject |
|
101 - (BOOL)windowShouldClose:(id)sender; |
|
102 @end |
|
103 |
|
104 @interface OTTDMain : NSObject |
|
105 @end |
|
106 |
|
107 |
|
108 /* Structure for rez switch gamma fades |
|
109 * We can hide the monitor flicker by setting the gamma tables to 0 |
|
110 */ |
|
111 #define QZ_GAMMA_TABLE_SIZE 256 |
|
112 |
|
113 struct OTTD_QuartzGammaTable { |
|
114 CGGammaValue red[QZ_GAMMA_TABLE_SIZE]; |
|
115 CGGammaValue green[QZ_GAMMA_TABLE_SIZE]; |
|
116 CGGammaValue blue[QZ_GAMMA_TABLE_SIZE]; |
|
117 }; |
|
118 |
|
119 /* Add methods to get at private members of NSScreen. |
|
120 * Since there is a bug in Apple's screen switching code that does not update |
|
121 * this variable when switching to fullscreen, we'll set it manually (but only |
|
122 * for the main screen). |
|
123 */ |
|
124 @interface NSScreen (NSScreenAccess) |
|
125 - (void) setFrame:(NSRect)frame; |
|
126 @end |
|
127 |
|
128 @implementation NSScreen (NSScreenAccess) |
|
129 - (void) setFrame:(NSRect)frame; |
|
130 { |
|
131 _frame = frame; |
|
132 } |
|
133 @end |
|
134 |
|
135 |
|
136 static void QZ_Draw(); |
|
137 static void QZ_UnsetVideoMode(); |
|
138 static void QZ_UpdatePalette(uint start, uint count); |
|
139 static void QZ_WarpCursor(int x, int y); |
|
140 static void QZ_ShowMouse(); |
|
141 static void QZ_HideMouse(); |
|
142 |
|
143 |
|
144 static NSAutoreleasePool *_ottd_autorelease_pool; |
|
145 static OTTDMain *_ottd_main; |
|
146 |
|
147 |
|
148 static struct CocoaVideoData { |
|
149 bool isset; |
|
150 bool issetting; |
|
151 |
|
152 CGDirectDisplayID display_id; /* 0 == main display (only support single display) */ |
|
153 CFDictionaryRef mode; /* current mode of the display */ |
|
154 CFDictionaryRef save_mode; /* original mode of the display */ |
|
155 CFArrayRef mode_list; /* list of available fullscreen modes */ |
|
156 CGDirectPaletteRef palette; /* palette of an 8-bit display */ |
|
157 |
|
158 uint32 blitter_bpp; |
|
159 |
|
160 uint32 device_width; |
|
161 uint32 device_height; |
|
162 uint32 device_bpp; |
|
163 |
|
164 void *realpixels; |
|
165 void *pixels; |
|
166 uint32 width; |
|
167 uint32 height; |
|
168 uint32 pitch; |
|
169 bool fullscreen; |
|
170 |
|
171 unsigned int current_mods; |
|
172 bool tab_is_down; |
|
173 bool emulating_right_button; |
|
174 |
|
175 bool cursor_visible; |
|
176 bool active; |
|
177 |
|
178 #ifdef _DEBUG |
|
179 uint32 tEvent; |
|
180 #endif |
|
181 |
|
182 OTTD_QuartzWindow *window; |
|
183 NSQuickDrawView *qdview; |
|
184 |
|
185 #define MAX_DIRTY_RECTS 100 |
|
186 Rect dirty_rects[MAX_DIRTY_RECTS]; |
|
187 int num_dirty_rects; |
|
188 |
|
189 uint16 palette16[256]; |
|
190 uint32 palette32[256]; |
|
191 } _cocoa_video_data; |
|
192 |
|
193 static bool _cocoa_video_started = false; |
|
194 static bool _cocoa_video_dialog = false; |
|
195 |
|
196 |
|
197 |
|
198 |
|
199 /****************************************************************************** |
|
200 * Game loop and accessories * |
|
201 ******************************************************************************/ |
|
202 |
|
203 static uint32 GetTick() |
|
204 { |
|
205 struct timeval tim; |
|
206 |
|
207 gettimeofday(&tim, NULL); |
|
208 return tim.tv_usec / 1000 + tim.tv_sec * 1000; |
|
209 } |
|
210 |
|
211 static void QZ_CheckPaletteAnim() |
|
212 { |
|
213 if (_pal_count_dirty != 0) { |
|
214 Blitter *blitter = BlitterFactoryBase::GetCurrentBlitter(); |
|
215 |
|
216 switch (blitter->UsePaletteAnimation()) { |
|
217 case Blitter::PALETTE_ANIMATION_VIDEO_BACKEND: |
|
218 QZ_UpdatePalette(_pal_first_dirty, _pal_count_dirty); |
|
219 break; |
|
220 |
|
221 case Blitter::PALETTE_ANIMATION_BLITTER: |
|
222 blitter->PaletteAnimate(_pal_first_dirty, _pal_count_dirty); |
|
223 break; |
|
224 |
|
225 case Blitter::PALETTE_ANIMATION_NONE: |
|
226 break; |
|
227 |
|
228 default: |
|
229 NOT_REACHED(); |
|
230 } |
|
231 _pal_count_dirty = 0; |
|
232 } |
|
233 } |
|
234 |
|
235 |
|
236 |
|
237 struct VkMapping { |
|
238 unsigned short vk_from; |
|
239 byte map_to; |
|
240 }; |
|
241 |
|
242 #define AS(x, z) {x, z} |
|
243 |
|
244 static const VkMapping _vk_mapping[] = { |
|
245 AS(QZ_BACKQUOTE, WKC_BACKQUOTE), // key left of '1' |
|
246 AS(QZ_BACKQUOTE2, WKC_BACKQUOTE), // some keyboards have it on another scancode |
|
247 |
|
248 /* Pageup stuff + up/down */ |
|
249 AS(QZ_PAGEUP, WKC_PAGEUP), |
|
250 AS(QZ_PAGEDOWN, WKC_PAGEDOWN), |
|
251 |
|
252 AS(QZ_UP, WKC_UP), |
|
253 AS(QZ_DOWN, WKC_DOWN), |
|
254 AS(QZ_LEFT, WKC_LEFT), |
|
255 AS(QZ_RIGHT, WKC_RIGHT), |
|
256 |
|
257 AS(QZ_HOME, WKC_HOME), |
|
258 AS(QZ_END, WKC_END), |
|
259 |
|
260 AS(QZ_INSERT, WKC_INSERT), |
|
261 AS(QZ_DELETE, WKC_DELETE), |
|
262 |
|
263 /* Letters. QZ_[a-z] is not in numerical order so we can't use AM(...) */ |
|
264 AS(QZ_a, 'A'), |
|
265 AS(QZ_b, 'B'), |
|
266 AS(QZ_c, 'C'), |
|
267 AS(QZ_d, 'D'), |
|
268 AS(QZ_e, 'E'), |
|
269 AS(QZ_f, 'F'), |
|
270 AS(QZ_g, 'G'), |
|
271 AS(QZ_h, 'H'), |
|
272 AS(QZ_i, 'I'), |
|
273 AS(QZ_j, 'J'), |
|
274 AS(QZ_k, 'K'), |
|
275 AS(QZ_l, 'L'), |
|
276 AS(QZ_m, 'M'), |
|
277 AS(QZ_n, 'N'), |
|
278 AS(QZ_o, 'O'), |
|
279 AS(QZ_p, 'P'), |
|
280 AS(QZ_q, 'Q'), |
|
281 AS(QZ_r, 'R'), |
|
282 AS(QZ_s, 'S'), |
|
283 AS(QZ_t, 'T'), |
|
284 AS(QZ_u, 'U'), |
|
285 AS(QZ_v, 'V'), |
|
286 AS(QZ_w, 'W'), |
|
287 AS(QZ_x, 'X'), |
|
288 AS(QZ_y, 'Y'), |
|
289 AS(QZ_z, 'Z'), |
|
290 /* Same thing for digits */ |
|
291 AS(QZ_0, '0'), |
|
292 AS(QZ_1, '1'), |
|
293 AS(QZ_2, '2'), |
|
294 AS(QZ_3, '3'), |
|
295 AS(QZ_4, '4'), |
|
296 AS(QZ_5, '5'), |
|
297 AS(QZ_6, '6'), |
|
298 AS(QZ_7, '7'), |
|
299 AS(QZ_8, '8'), |
|
300 AS(QZ_9, '9'), |
|
301 |
|
302 AS(QZ_ESCAPE, WKC_ESC), |
|
303 AS(QZ_PAUSE, WKC_PAUSE), |
|
304 AS(QZ_BACKSPACE, WKC_BACKSPACE), |
|
305 |
|
306 AS(QZ_SPACE, WKC_SPACE), |
|
307 AS(QZ_RETURN, WKC_RETURN), |
|
308 AS(QZ_TAB, WKC_TAB), |
|
309 |
|
310 /* Function keys */ |
|
311 AS(QZ_F1, WKC_F1), |
|
312 AS(QZ_F2, WKC_F2), |
|
313 AS(QZ_F3, WKC_F3), |
|
314 AS(QZ_F4, WKC_F4), |
|
315 AS(QZ_F5, WKC_F5), |
|
316 AS(QZ_F6, WKC_F6), |
|
317 AS(QZ_F7, WKC_F7), |
|
318 AS(QZ_F8, WKC_F8), |
|
319 AS(QZ_F9, WKC_F9), |
|
320 AS(QZ_F10, WKC_F10), |
|
321 AS(QZ_F11, WKC_F11), |
|
322 AS(QZ_F12, WKC_F12), |
|
323 |
|
324 /* Numeric part */ |
|
325 AS(QZ_KP0, WKC_NUM_0), |
|
326 AS(QZ_KP1, WKC_NUM_1), |
|
327 AS(QZ_KP2, WKC_NUM_2), |
|
328 AS(QZ_KP3, WKC_NUM_3), |
|
329 AS(QZ_KP4, WKC_NUM_4), |
|
330 AS(QZ_KP5, WKC_NUM_5), |
|
331 AS(QZ_KP6, WKC_NUM_6), |
|
332 AS(QZ_KP7, WKC_NUM_7), |
|
333 AS(QZ_KP8, WKC_NUM_8), |
|
334 AS(QZ_KP9, WKC_NUM_9), |
|
335 AS(QZ_KP_DIVIDE, WKC_NUM_DIV), |
|
336 AS(QZ_KP_MULTIPLY, WKC_NUM_MUL), |
|
337 AS(QZ_KP_MINUS, WKC_NUM_MINUS), |
|
338 AS(QZ_KP_PLUS, WKC_NUM_PLUS), |
|
339 AS(QZ_KP_ENTER, WKC_NUM_ENTER), |
|
340 AS(QZ_KP_PERIOD, WKC_NUM_DECIMAL), |
|
341 |
|
342 /* Other non-letter keys */ |
|
343 AS(QZ_SLASH, WKC_SLASH), |
|
344 AS(QZ_SEMICOLON, WKC_SEMICOLON), |
|
345 AS(QZ_EQUALS, WKC_EQUALS), |
|
346 AS(QZ_LEFTBRACKET, WKC_L_BRACKET), |
|
347 AS(QZ_BACKSLASH, WKC_BACKSLASH), |
|
348 AS(QZ_RIGHTBRACKET, WKC_R_BRACKET), |
|
349 |
|
350 AS(QZ_QUOTE, WKC_SINGLEQUOTE), |
|
351 AS(QZ_COMMA, WKC_COMMA), |
|
352 AS(QZ_MINUS, WKC_MINUS), |
|
353 AS(QZ_PERIOD, WKC_PERIOD) |
|
354 }; |
|
355 |
|
356 |
|
357 static uint32 QZ_MapKey(unsigned short sym) |
|
358 { |
|
359 const VkMapping *map; |
|
360 uint32 key = 0; |
|
361 |
|
362 for (map = _vk_mapping; map != endof(_vk_mapping); ++map) { |
|
363 if (sym == map->vk_from) { |
|
364 key = map->map_to; |
|
365 break; |
|
366 } |
|
367 } |
|
368 |
|
369 if (_cocoa_video_data.current_mods & NSShiftKeyMask) key |= WKC_SHIFT; |
|
370 if (_cocoa_video_data.current_mods & NSControlKeyMask) key |= (_patches.right_mouse_btn_emulation != RMBE_CONTROL ? WKC_CTRL : WKC_META); |
|
371 if (_cocoa_video_data.current_mods & NSAlternateKeyMask) key |= WKC_ALT; |
|
372 if (_cocoa_video_data.current_mods & NSCommandKeyMask) key |= (_patches.right_mouse_btn_emulation != RMBE_CONTROL ? WKC_META : WKC_CTRL); |
|
373 |
|
374 return key << 16; |
|
375 } |
|
376 |
|
377 static void QZ_KeyEvent(unsigned short keycode, unsigned short unicode, BOOL down) |
|
378 { |
|
379 switch (keycode) { |
|
380 case QZ_UP: SB(_dirkeys, 1, 1, down); break; |
|
381 case QZ_DOWN: SB(_dirkeys, 3, 1, down); break; |
|
382 case QZ_LEFT: SB(_dirkeys, 0, 1, down); break; |
|
383 case QZ_RIGHT: SB(_dirkeys, 2, 1, down); break; |
|
384 |
|
385 case QZ_TAB: _cocoa_video_data.tab_is_down = down; break; |
|
386 |
|
387 case QZ_RETURN: |
|
388 case QZ_f: |
|
389 if (down && (_cocoa_video_data.current_mods & NSCommandKeyMask)) { |
|
390 _video_driver->ToggleFullscreen(!_fullscreen); |
|
391 } |
|
392 break; |
|
393 } |
|
394 |
|
395 if (down) { |
|
396 uint32 pressed_key = QZ_MapKey(keycode) | unicode; |
|
397 HandleKeypress(pressed_key); |
|
398 DEBUG(driver, 2, "cocoa_v: QZ_KeyEvent: %x (%x), down, mapping: %x", keycode, unicode, pressed_key); |
|
399 } else { |
|
400 DEBUG(driver, 2, "cocoa_v: QZ_KeyEvent: %x (%x), up", keycode, unicode); |
|
401 } |
|
402 } |
|
403 |
|
404 static void QZ_DoUnsidedModifiers(unsigned int newMods) |
|
405 { |
|
406 const int mapping[] = { QZ_CAPSLOCK, QZ_LSHIFT, QZ_LCTRL, QZ_LALT, QZ_LMETA }; |
|
407 |
|
408 int i; |
|
409 unsigned int bit; |
|
410 |
|
411 if (_cocoa_video_data.current_mods == newMods) return; |
|
412 |
|
413 /* Iterate through the bits, testing each against the current modifiers */ |
|
414 for (i = 0, bit = NSAlphaShiftKeyMask; bit <= NSCommandKeyMask; bit <<= 1, ++i) { |
|
415 unsigned int currentMask, newMask; |
|
416 |
|
417 currentMask = _cocoa_video_data.current_mods & bit; |
|
418 newMask = newMods & bit; |
|
419 |
|
420 if (currentMask && currentMask != newMask) { /* modifier up event */ |
|
421 /* If this was Caps Lock, we need some additional voodoo to make SDL happy (is this needed in ottd?) */ |
|
422 if (bit == NSAlphaShiftKeyMask) QZ_KeyEvent(mapping[i], 0, YES); |
|
423 QZ_KeyEvent(mapping[i], 0, NO); |
|
424 } else if (newMask && currentMask != newMask) { /* modifier down event */ |
|
425 QZ_KeyEvent(mapping[i], 0, YES); |
|
426 /* If this was Caps Lock, we need some additional voodoo to make SDL happy (is this needed in ottd?) */ |
|
427 if (bit == NSAlphaShiftKeyMask) QZ_KeyEvent(mapping[i], 0, NO); |
|
428 } |
|
429 } |
|
430 |
|
431 _cocoa_video_data.current_mods = newMods; |
|
432 } |
|
433 |
|
434 static void QZ_MouseMovedEvent(int x, int y) |
|
435 { |
|
436 if (_cursor.fix_at) { |
|
437 int dx = x - _cursor.pos.x; |
|
438 int dy = y - _cursor.pos.y; |
|
439 |
|
440 if (dx != 0 || dy != 0) { |
|
441 _cursor.delta.x += dx; |
|
442 _cursor.delta.y += dy; |
|
443 |
|
444 QZ_WarpCursor(_cursor.pos.x, _cursor.pos.y); |
|
445 } |
|
446 } else { |
|
447 _cursor.delta.x = x - _cursor.pos.x; |
|
448 _cursor.delta.y = y - _cursor.pos.y; |
|
449 _cursor.pos.x = x; |
|
450 _cursor.pos.y = y; |
|
451 _cursor.dirty = true; |
|
452 } |
|
453 HandleMouseEvents(); |
|
454 } |
|
455 |
|
456 |
|
457 static void QZ_MouseButtonEvent(int button, BOOL down) |
|
458 { |
|
459 switch (button) { |
|
460 case 0: |
|
461 if (down) { |
|
462 _left_button_down = true; |
|
463 } else { |
|
464 _left_button_down = false; |
|
465 _left_button_clicked = false; |
|
466 } |
|
467 HandleMouseEvents(); |
|
468 break; |
|
469 |
|
470 case 1: |
|
471 if (down) { |
|
472 _right_button_down = true; |
|
473 _right_button_clicked = true; |
|
474 } else { |
|
475 _right_button_down = false; |
|
476 } |
|
477 HandleMouseEvents(); |
|
478 break; |
|
479 } |
|
480 } |
|
481 |
|
482 |
|
483 static inline NSPoint QZ_GetMouseLocation(NSEvent *event) |
|
484 { |
|
485 NSPoint pt; |
|
486 |
|
487 if (_cocoa_video_data.fullscreen) { |
|
488 pt = [ NSEvent mouseLocation ]; |
|
489 pt.y = _cocoa_video_data.height - pt.y; |
|
490 } else { |
|
491 pt = [event locationInWindow]; |
|
492 pt = [_cocoa_video_data.qdview convertPoint:pt fromView:nil]; |
|
493 } |
|
494 |
|
495 return pt; |
|
496 } |
|
497 |
|
498 static bool QZ_MouseIsInsideView(NSPoint *pt) |
|
499 { |
|
500 if (_cocoa_video_data.fullscreen) { |
|
501 return pt->x >= 0 && pt->y >= 0 && pt->x < _cocoa_video_data.width && pt->y < _cocoa_video_data.height; |
|
502 } else { |
|
503 return [ _cocoa_video_data.qdview mouse:*pt inRect:[ _cocoa_video_data.qdview bounds ] ]; |
|
504 } |
|
505 } |
|
506 |
|
507 |
|
508 static bool QZ_PollEvent() |
|
509 { |
|
510 NSEvent *event; |
|
511 NSPoint pt; |
|
512 NSString *chars; |
|
513 #ifdef _DEBUG |
|
514 uint32 et0, et; |
|
515 #endif |
|
516 |
|
517 #ifdef _DEBUG |
|
518 et0 = GetTick(); |
|
519 #endif |
|
520 event = [ NSApp nextEventMatchingMask:NSAnyEventMask |
|
521 untilDate: [ NSDate distantPast ] |
|
522 inMode: NSDefaultRunLoopMode dequeue:YES ]; |
|
523 #ifdef _DEBUG |
|
524 et = GetTick(); |
|
525 _cocoa_video_data.tEvent+= et - et0; |
|
526 #endif |
|
527 |
|
528 if (event == nil) return false; |
|
529 if (!_cocoa_video_data.active) { |
|
530 QZ_ShowMouse(); |
|
531 [NSApp sendEvent:event]; |
|
532 return true; |
|
533 } |
|
534 |
|
535 QZ_DoUnsidedModifiers( [ event modifierFlags ] ); |
|
536 |
|
537 switch ([event type]) { |
|
538 case NSMouseMoved: |
|
539 case NSOtherMouseDragged: |
|
540 case NSRightMouseDragged: |
|
541 case NSLeftMouseDragged: |
|
542 pt = QZ_GetMouseLocation(event); |
|
543 if (!QZ_MouseIsInsideView(&pt) && |
|
544 !_cocoa_video_data.emulating_right_button) { |
|
545 QZ_ShowMouse(); |
|
546 [NSApp sendEvent:event]; |
|
547 break; |
|
548 } |
|
549 |
|
550 QZ_HideMouse(); |
|
551 QZ_MouseMovedEvent((int)pt.x, (int)pt.y); |
|
552 break; |
|
553 |
|
554 case NSLeftMouseDown: |
|
555 { |
|
556 uint32 keymask = 0; |
|
557 if (_patches.right_mouse_btn_emulation == RMBE_COMMAND) keymask |= NSCommandKeyMask; |
|
558 if (_patches.right_mouse_btn_emulation == RMBE_CONTROL) keymask |= NSControlKeyMask; |
|
559 |
|
560 pt = QZ_GetMouseLocation(event); |
|
561 |
|
562 if (!([ event modifierFlags ] & keymask) || |
|
563 !QZ_MouseIsInsideView(&pt)) { |
|
564 [NSApp sendEvent:event]; |
|
565 } |
|
566 |
|
567 if (!QZ_MouseIsInsideView(&pt)) { |
|
568 QZ_ShowMouse(); |
|
569 break; |
|
570 } |
|
571 |
|
572 QZ_HideMouse(); |
|
573 QZ_MouseMovedEvent((int)pt.x, (int)pt.y); |
|
574 |
|
575 /* Right mouse button emulation */ |
|
576 if ([ event modifierFlags ] & keymask) { |
|
577 _cocoa_video_data.emulating_right_button = true; |
|
578 QZ_MouseButtonEvent(1, YES); |
|
579 } else { |
|
580 QZ_MouseButtonEvent(0, YES); |
|
581 } |
|
582 break; |
|
583 } |
|
584 case NSLeftMouseUp: |
|
585 [NSApp sendEvent:event]; |
|
586 |
|
587 pt = QZ_GetMouseLocation(event); |
|
588 if (!QZ_MouseIsInsideView(&pt)) { |
|
589 QZ_ShowMouse(); |
|
590 break; |
|
591 } |
|
592 |
|
593 QZ_HideMouse(); |
|
594 QZ_MouseMovedEvent((int)pt.x, (int)pt.y); |
|
595 |
|
596 /* Right mouse button emulation */ |
|
597 if (_cocoa_video_data.emulating_right_button) { |
|
598 _cocoa_video_data.emulating_right_button = false; |
|
599 QZ_MouseButtonEvent(1, NO); |
|
600 } else { |
|
601 QZ_MouseButtonEvent(0, NO); |
|
602 } |
|
603 break; |
|
604 |
|
605 case NSRightMouseDown: |
|
606 pt = QZ_GetMouseLocation(event); |
|
607 if (!QZ_MouseIsInsideView(&pt)) { |
|
608 QZ_ShowMouse(); |
|
609 [NSApp sendEvent:event]; |
|
610 break; |
|
611 } |
|
612 |
|
613 QZ_HideMouse(); |
|
614 QZ_MouseMovedEvent((int)pt.x, (int)pt.y); |
|
615 QZ_MouseButtonEvent(1, YES); |
|
616 break; |
|
617 |
|
618 case NSRightMouseUp: |
|
619 pt = QZ_GetMouseLocation(event); |
|
620 if (!QZ_MouseIsInsideView(&pt)) { |
|
621 QZ_ShowMouse(); |
|
622 [NSApp sendEvent:event]; |
|
623 break; |
|
624 } |
|
625 |
|
626 QZ_HideMouse(); |
|
627 QZ_MouseMovedEvent((int)pt.x, (int)pt.y); |
|
628 QZ_MouseButtonEvent(1, NO); |
|
629 break; |
|
630 |
|
631 #if 0 |
|
632 /* This is not needed since openttd currently only use two buttons */ |
|
633 case NSOtherMouseDown: |
|
634 pt = QZ_GetMouseLocation(event); |
|
635 if (!QZ_MouseIsInsideView(&pt)) { |
|
636 QZ_ShowMouse(); |
|
637 [NSApp sendEvent:event]; |
|
638 break; |
|
639 } |
|
640 |
|
641 QZ_HideMouse(); |
|
642 QZ_MouseMovedEvent((int)pt.x, (int)pt.y); |
|
643 QZ_MouseButtonEvent([ event buttonNumber ], YES); |
|
644 break; |
|
645 |
|
646 case NSOtherMouseUp: |
|
647 pt = QZ_GetMouseLocation(event); |
|
648 if (!QZ_MouseIsInsideView(&pt)) { |
|
649 QZ_ShowMouse(); |
|
650 [NSApp sendEvent:event]; |
|
651 break; |
|
652 } |
|
653 |
|
654 QZ_HideMouse(); |
|
655 QZ_MouseMovedEvent((int)pt.x, (int)pt.y); |
|
656 QZ_MouseButtonEvent([ event buttonNumber ], NO); |
|
657 break; |
|
658 #endif |
|
659 |
|
660 case NSKeyDown: |
|
661 /* Quit, hide and minimize */ |
|
662 switch ([event keyCode]) { |
|
663 case QZ_q: |
|
664 case QZ_h: |
|
665 case QZ_m: |
|
666 if ([ event modifierFlags ] & NSCommandKeyMask) { |
|
667 [NSApp sendEvent:event]; |
|
668 } |
|
669 break; |
|
670 } |
|
671 |
|
672 chars = [ event characters ]; |
|
673 QZ_KeyEvent([event keyCode], [ chars length ] ? [ chars characterAtIndex:0 ] : 0, YES); |
|
674 break; |
|
675 |
|
676 case NSKeyUp: |
|
677 /* Quit, hide and minimize */ |
|
678 switch ([event keyCode]) { |
|
679 case QZ_q: |
|
680 case QZ_h: |
|
681 case QZ_m: |
|
682 if ([ event modifierFlags ] & NSCommandKeyMask) { |
|
683 [NSApp sendEvent:event]; |
|
684 } |
|
685 break; |
|
686 } |
|
687 |
|
688 chars = [ event characters ]; |
|
689 QZ_KeyEvent([event keyCode], [ chars length ] ? [ chars characterAtIndex:0 ] : 0, NO); |
|
690 break; |
|
691 |
|
692 case NSScrollWheel: |
|
693 if ([ event deltaY ] > 0.0) { /* Scroll up */ |
|
694 _cursor.wheel--; |
|
695 } else if ([ event deltaY ] < 0.0) { /* Scroll down */ |
|
696 _cursor.wheel++; |
|
697 } /* else: deltaY was 0.0 and we don't want to do anything */ |
|
698 |
|
699 /* Set the scroll count for scrollwheel scrolling */ |
|
700 _cursor.h_wheel -= (int)([ event deltaX ]* 5 * _patches.scrollwheel_multiplier); |
|
701 _cursor.v_wheel -= (int)([ event deltaY ]* 5 * _patches.scrollwheel_multiplier); |
|
702 break; |
|
703 |
|
704 default: |
|
705 [NSApp sendEvent:event]; |
|
706 } |
|
707 |
|
708 return true; |
|
709 } |
|
710 |
|
711 |
|
712 static void QZ_GameLoop() |
|
713 { |
|
714 uint32 cur_ticks = GetTick(); |
|
715 uint32 last_cur_ticks = cur_ticks; |
|
716 uint32 next_tick = cur_ticks + 30; |
|
717 uint32 pal_tick = 0; |
|
718 #ifdef _DEBUG |
|
719 uint32 et0, et, st0, st; |
|
720 #endif |
|
721 int i; |
|
722 |
|
723 #ifdef _DEBUG |
|
724 et0 = GetTick(); |
|
725 st = 0; |
|
726 #endif |
|
727 |
|
728 _screen.dst_ptr = _cocoa_video_data.pixels; |
|
729 DisplaySplashImage(); |
|
730 QZ_CheckPaletteAnim(); |
|
731 QZ_Draw(); |
|
732 CSleep(1); |
|
733 |
|
734 for (i = 0; i < 2; i++) GameLoop(); |
|
735 |
|
736 _screen.dst_ptr = _cocoa_video_data.pixels; |
|
737 UpdateWindows(); |
|
738 QZ_CheckPaletteAnim(); |
|
739 QZ_Draw(); |
|
740 CSleep(1); |
|
741 |
|
742 for (;;) { |
|
743 uint32 prev_cur_ticks = cur_ticks; // to check for wrapping |
|
744 InteractiveRandom(); // randomness |
|
745 |
|
746 while (QZ_PollEvent()) {} |
|
747 |
|
748 if (_exit_game) break; |
|
749 |
|
750 #if defined(_DEBUG) |
|
751 if (_cocoa_video_data.current_mods & NSShiftKeyMask) |
|
752 #else |
|
753 if (_cocoa_video_data.tab_is_down) |
|
754 #endif |
|
755 { |
|
756 if (!_networking && _game_mode != GM_MENU) _fast_forward |= 2; |
|
757 } else if (_fast_forward & 2) { |
|
758 _fast_forward = 0; |
|
759 } |
|
760 |
|
761 cur_ticks = GetTick(); |
|
762 if (cur_ticks >= next_tick || (_fast_forward && !_pause_game) || cur_ticks < prev_cur_ticks) { |
|
763 _realtime_tick += cur_ticks - last_cur_ticks; |
|
764 last_cur_ticks = cur_ticks; |
|
765 next_tick = cur_ticks + 30; |
|
766 |
|
767 _ctrl_pressed = !!(_cocoa_video_data.current_mods & ( _patches.right_mouse_btn_emulation != RMBE_CONTROL ? NSControlKeyMask : NSCommandKeyMask)); |
|
768 _shift_pressed = !!(_cocoa_video_data.current_mods & NSShiftKeyMask); |
|
769 |
|
770 GameLoop(); |
|
771 |
|
772 _screen.dst_ptr = _cocoa_video_data.pixels; |
|
773 UpdateWindows(); |
|
774 if (++pal_tick > 4) { |
|
775 QZ_CheckPaletteAnim(); |
|
776 pal_tick = 1; |
|
777 } |
|
778 QZ_Draw(); |
|
779 } else { |
|
780 #ifdef _DEBUG |
|
781 st0 = GetTick(); |
|
782 #endif |
|
783 CSleep(1); |
|
784 #ifdef _DEBUG |
|
785 st += GetTick() - st0; |
|
786 #endif |
|
787 _screen.dst_ptr = _cocoa_video_data.pixels; |
|
788 DrawChatMessage(); |
|
789 DrawMouseCursor(); |
|
790 QZ_Draw(); |
|
791 } |
|
792 } |
|
793 |
|
794 #ifdef _DEBUG |
|
795 et = GetTick(); |
|
796 |
|
797 DEBUG(driver, 1, "cocoa_v: nextEventMatchingMask took %i ms total", _cocoa_video_data.tEvent); |
|
798 DEBUG(driver, 1, "cocoa_v: game loop took %i ms total (%i ms without sleep)", et - et0, et - et0 - st); |
|
799 DEBUG(driver, 1, "cocoa_v: (nextEventMatchingMask total)/(game loop total) is %f%%", (double)_cocoa_video_data.tEvent / (double)(et - et0) * 100); |
|
800 DEBUG(driver, 1, "cocoa_v: (nextEventMatchingMask total)/(game loop without sleep total) is %f%%", (double)_cocoa_video_data.tEvent / (double)(et - et0 - st) * 100); |
|
801 #endif |
|
802 } |
|
803 |
|
804 |
|
805 /****************************************************************************** |
|
806 * Windowed mode * |
|
807 ******************************************************************************/ |
|
808 |
|
809 /* This function makes the *game region* of the window 100% opaque. |
|
810 * The genie effect uses the alpha component. Otherwise, |
|
811 * it doesn't seem to matter what value it has. |
|
812 */ |
|
813 static void QZ_SetPortAlphaOpaque() |
|
814 { |
|
815 if (_cocoa_video_data.device_bpp == 32) { |
|
816 uint32* pixels = (uint32*)_cocoa_video_data.realpixels; |
|
817 uint32 rowPixels = _cocoa_video_data.pitch / 4; |
|
818 uint32 i; |
|
819 uint32 j; |
|
820 |
|
821 for (i = 0; i < _cocoa_video_data.height; i++) |
|
822 for (j = 0; j < _cocoa_video_data.width; j++) { |
|
823 pixels[i * rowPixels + j] |= 0xFF000000; |
|
824 } |
|
825 } |
|
826 } |
|
827 |
|
828 |
|
829 @implementation OTTD_QuartzWindow |
|
830 |
|
831 /* we override these methods to fix the miniaturize animation/dock icon bug */ |
|
832 - (void)miniaturize:(id)sender |
|
833 { |
|
834 /* make the alpha channel opaque so anim won't have holes in it */ |
|
835 QZ_SetPortAlphaOpaque (); |
|
836 |
|
837 /* window is hidden now */ |
|
838 _cocoa_video_data.active = false; |
|
839 |
|
840 QZ_ShowMouse(); |
|
841 |
|
842 [ super miniaturize:sender ]; |
|
843 } |
|
844 |
|
845 - (void)display |
|
846 { |
|
847 /* This method fires just before the window deminaturizes from the Dock. |
|
848 * We'll save the current visible surface, let the window manager redraw any |
|
849 * UI elements, and restore the surface. This way, no expose event |
|
850 * is required, and the deminiaturize works perfectly. |
|
851 */ |
|
852 |
|
853 QZ_SetPortAlphaOpaque(); |
|
854 |
|
855 /* save current visible surface */ |
|
856 [ self cacheImageInRect:[ _cocoa_video_data.qdview frame ] ]; |
|
857 |
|
858 /* let the window manager redraw controls, border, etc */ |
|
859 [ super display ]; |
|
860 |
|
861 /* restore visible surface */ |
|
862 [ self restoreCachedImage ]; |
|
863 |
|
864 /* window is visible again */ |
|
865 _cocoa_video_data.active = true; |
|
866 } |
|
867 |
|
868 - (void)setFrame:(NSRect)frameRect display:(BOOL)flag |
|
869 { |
|
870 NSRect newViewFrame; |
|
871 CGrafPtr thePort; |
|
872 |
|
873 [ super setFrame:frameRect display:flag ]; |
|
874 |
|
875 /* Don't do anything if the window is currently beign created */ |
|
876 if (_cocoa_video_data.issetting) return; |
|
877 |
|
878 if (_cocoa_video_data.window == nil) return; |
|
879 |
|
880 newViewFrame = [ _cocoa_video_data.qdview frame ]; |
|
881 |
|
882 /* Update the pixels and pitch */ |
|
883 thePort = (OpaqueGrafPtr*) [ _cocoa_video_data.qdview qdPort ]; |
|
884 LockPortBits(thePort); |
|
885 |
|
886 _cocoa_video_data.realpixels = GetPixBaseAddr(GetPortPixMap(thePort)); |
|
887 _cocoa_video_data.pitch = GetPixRowBytes(GetPortPixMap(thePort)); |
|
888 |
|
889 /* _cocoa_video_data.realpixels now points to the window's pixels |
|
890 * We want it to point to the *view's* pixels |
|
891 */ |
|
892 { |
|
893 int vOffset = [ _cocoa_video_data.window frame ].size.height - newViewFrame.size.height - newViewFrame.origin.y; |
|
894 int hOffset = newViewFrame.origin.x; |
|
895 |
|
896 _cocoa_video_data.realpixels = (uint8*)_cocoa_video_data.realpixels + (vOffset * _cocoa_video_data.pitch) + hOffset * (_cocoa_video_data.device_bpp / 8); |
|
897 } |
|
898 |
|
899 UnlockPortBits(thePort); |
|
900 |
|
901 /* Allocate new buffer */ |
|
902 free(_cocoa_video_data.pixels); |
|
903 _cocoa_video_data.pixels = malloc(newViewFrame.size.width * newViewFrame.size.height * _cocoa_video_data.blitter_bpp / 8); |
|
904 assert(_cocoa_video_data.pixels != NULL); |
|
905 |
|
906 |
|
907 /* Tell the game that the resolution changed */ |
|
908 _cocoa_video_data.width = newViewFrame.size.width; |
|
909 _cocoa_video_data.height = newViewFrame.size.height; |
|
910 |
|
911 _screen.width = _cocoa_video_data.width; |
|
912 _screen.height = _cocoa_video_data.height; |
|
913 _screen.pitch = _cocoa_video_data.width; |
|
914 |
|
915 GameSizeChanged(); |
|
916 |
|
917 /* Redraw screen */ |
|
918 _cocoa_video_data.num_dirty_rects = MAX_DIRTY_RECTS; |
|
919 } |
|
920 |
|
921 - (void)appDidHide:(NSNotification*)note |
|
922 { |
|
923 _cocoa_video_data.active = false; |
|
924 } |
|
925 |
|
926 |
|
927 - (void)appWillUnhide:(NSNotification*)note |
|
928 { |
|
929 QZ_SetPortAlphaOpaque (); |
|
930 |
|
931 /* save current visible surface */ |
|
932 [ self cacheImageInRect:[ _cocoa_video_data.qdview frame ] ]; |
|
933 } |
|
934 |
|
935 - (void)appDidUnhide:(NSNotification*)note |
|
936 { |
|
937 /* restore cached image, since it may not be current, post expose event too */ |
|
938 [ self restoreCachedImage ]; |
|
939 |
|
940 _cocoa_video_data.active = true; |
|
941 } |
|
942 |
|
943 |
|
944 - (id)initWithContentRect:(NSRect)contentRect styleMask:(unsigned int)styleMask backing:(NSBackingStoreType)backingType defer:(BOOL)flag |
|
945 { |
|
946 /* Make our window subclass receive these application notifications */ |
|
947 [ [ NSNotificationCenter defaultCenter ] addObserver:self |
|
948 selector:@selector(appDidHide:) name:NSApplicationDidHideNotification object:NSApp ]; |
|
949 |
|
950 [ [ NSNotificationCenter defaultCenter ] addObserver:self |
|
951 selector:@selector(appDidUnhide:) name:NSApplicationDidUnhideNotification object:NSApp ]; |
|
952 |
|
953 [ [ NSNotificationCenter defaultCenter ] addObserver:self |
|
954 selector:@selector(appWillUnhide:) name:NSApplicationWillUnhideNotification object:NSApp ]; |
|
955 |
|
956 return [ super initWithContentRect:contentRect styleMask:styleMask backing:backingType defer:flag ]; |
|
957 } |
|
958 |
|
959 @end |
|
960 |
|
961 @implementation OTTD_QuartzWindowDelegate |
|
962 - (BOOL)windowShouldClose:(id)sender |
|
963 { |
|
964 HandleExitGameRequest(); |
|
965 |
|
966 return NO; |
|
967 } |
|
968 |
|
969 - (void)windowDidBecomeKey:(NSNotification*)aNotification |
|
970 { |
|
971 _cocoa_video_data.active = true; |
|
972 } |
|
973 |
|
974 - (void)windowDidResignKey:(NSNotification*)aNotification |
|
975 { |
|
976 _cocoa_video_data.active = false; |
|
977 } |
|
978 |
|
979 - (void)windowDidBecomeMain:(NSNotification*)aNotification |
|
980 { |
|
981 _cocoa_video_data.active = true; |
|
982 } |
|
983 |
|
984 - (void)windowDidResignMain:(NSNotification*)aNotification |
|
985 { |
|
986 _cocoa_video_data.active = false; |
|
987 } |
|
988 |
|
989 @end |
|
990 |
|
991 |
|
992 static void QZ_UpdateWindowPalette(uint start, uint count) |
|
993 { |
|
994 uint i; |
|
995 |
|
996 switch (_cocoa_video_data.device_bpp) { |
|
997 case 32: |
|
998 for (i = start; i < start + count; i++) { |
|
999 uint32 clr32 = 0xff000000; |
|
1000 clr32 |= (uint32)_cur_palette[i].r << 16; |
|
1001 clr32 |= (uint32)_cur_palette[i].g << 8; |
|
1002 clr32 |= (uint32)_cur_palette[i].b; |
|
1003 _cocoa_video_data.palette32[i] = clr32; |
|
1004 } |
|
1005 break; |
|
1006 case 16: |
|
1007 for (i = start; i < start + count; i++) { |
|
1008 uint16 clr16 = 0x0000; |
|
1009 clr16 |= (uint16)((_cur_palette[i].r >> 3) & 0x1f) << 10; |
|
1010 clr16 |= (uint16)((_cur_palette[i].g >> 3) & 0x1f) << 5; |
|
1011 clr16 |= (uint16)((_cur_palette[i].b >> 3) & 0x1f); |
|
1012 _cocoa_video_data.palette16[i] = clr16; |
|
1013 } |
|
1014 break; |
|
1015 } |
|
1016 |
|
1017 _cocoa_video_data.num_dirty_rects = MAX_DIRTY_RECTS; |
|
1018 } |
|
1019 |
|
1020 static inline void QZ_WindowBlitIndexedPixelsToView32(uint left, uint top, uint right, uint bottom) |
|
1021 { |
|
1022 uint32* dst = (uint32*)_cocoa_video_data.realpixels; |
|
1023 uint width = _cocoa_video_data.width; |
|
1024 uint pitch = _cocoa_video_data.pitch / 4; |
|
1025 uint x; |
|
1026 uint y; |
|
1027 |
|
1028 switch (_cocoa_video_data.blitter_bpp) { |
|
1029 case 8: { |
|
1030 const uint32* pal = _cocoa_video_data.palette32; |
|
1031 const uint8* src = (uint8*) _cocoa_video_data.pixels; |
|
1032 |
|
1033 for (y = top; y < bottom; y++) { |
|
1034 for (x = left; x < right; x++) { |
|
1035 dst[y * pitch + x] = pal[src[y * width + x]]; |
|
1036 } |
|
1037 } |
|
1038 } |
|
1039 break; |
|
1040 case 32: { |
|
1041 const uint32* src = (uint32*) _cocoa_video_data.pixels; |
|
1042 |
|
1043 dst += top * pitch + left; |
|
1044 src += top * width + left; |
|
1045 |
|
1046 for (y = top; y < bottom; y++, dst+= pitch, src+= width) |
|
1047 memcpy(dst, src, (right - left) * 4); |
|
1048 } |
|
1049 break; |
|
1050 } |
|
1051 } |
|
1052 |
|
1053 /** |
|
1054 * This function copies pixels from the screen buffer in 16bpp windowed mode. It assumes |
|
1055 * that the blitter is 8bpp since the driver only supports 8 and 32 bpp blitters, and we |
|
1056 * don't allow a blitter with a higer bpp than the display in windowed mode. |
|
1057 * |
|
1058 * @param left The x coord for the left edge of the box to blit. |
|
1059 * @param top The y coord for the top edge of the box to blit. |
|
1060 * @param right The x coord for the right edge of the box to blit. |
|
1061 * @param bottom The y coord for the bottom edge of the box to blit. |
|
1062 */ |
|
1063 static inline void QZ_WindowBlitIndexedPixelsToView16(uint left, uint top, uint right, uint bottom) |
|
1064 { |
|
1065 const uint16* pal = _cocoa_video_data.palette16; |
|
1066 const uint8* src = (uint8*) _cocoa_video_data.pixels; |
|
1067 uint16* dst = (uint16*)_cocoa_video_data.realpixels; |
|
1068 uint width = _cocoa_video_data.width; |
|
1069 uint pitch = _cocoa_video_data.pitch / 2; |
|
1070 uint x; |
|
1071 uint y; |
|
1072 |
|
1073 for (y = top; y < bottom; y++) { |
|
1074 for (x = left; x < right; x++) { |
|
1075 dst[y * pitch + x] = pal[src[y * width + x]]; |
|
1076 } |
|
1077 } |
|
1078 } |
|
1079 |
|
1080 static inline void QZ_WindowBlitIndexedPixelsToView(int left, int top, int right, int bottom) |
|
1081 { |
|
1082 switch (_cocoa_video_data.device_bpp) { |
|
1083 case 32: QZ_WindowBlitIndexedPixelsToView32(left, top, right, bottom); break; |
|
1084 case 16: QZ_WindowBlitIndexedPixelsToView16(left, top, right, bottom); break; |
|
1085 } |
|
1086 } |
|
1087 |
|
1088 static bool _resize_icon[] = { |
|
1089 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, |
|
1090 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, |
|
1091 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, |
|
1092 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, |
|
1093 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, |
|
1094 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, |
|
1095 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, |
|
1096 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, |
|
1097 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, |
|
1098 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, |
|
1099 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, |
|
1100 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, |
|
1101 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, |
|
1102 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, |
|
1103 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, |
|
1104 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0 |
|
1105 }; |
|
1106 |
|
1107 static void QZ_DrawResizeIcon() |
|
1108 { |
|
1109 int xoff = _cocoa_video_data.width - 16; |
|
1110 int yoff = _cocoa_video_data.height - 16; |
|
1111 int x; |
|
1112 int y; |
|
1113 |
|
1114 for (y = 0; y < 16; y++) { |
|
1115 uint16* trg16 = (uint16*)_cocoa_video_data.realpixels + (yoff + y) * _cocoa_video_data.pitch / 2 + xoff; |
|
1116 uint32* trg32 = (uint32*)_cocoa_video_data.realpixels + (yoff + y) * _cocoa_video_data.pitch / 4 + xoff; |
|
1117 |
|
1118 for (x = 0; x < 16; x++, trg16++, trg32++) { |
|
1119 if (!_resize_icon[y * 16 + x]) continue; |
|
1120 |
|
1121 switch (_cocoa_video_data.device_bpp) { |
|
1122 case 32: *trg32 = 0xff000000; break; |
|
1123 case 16: *trg16 = 0x0000; break; |
|
1124 } |
|
1125 } |
|
1126 } |
|
1127 } |
|
1128 |
|
1129 static void QZ_DrawWindow() |
|
1130 { |
|
1131 int i; |
|
1132 RgnHandle dirty, temp; |
|
1133 |
|
1134 /* Check if we need to do anything */ |
|
1135 if (_cocoa_video_data.num_dirty_rects == 0 || |
|
1136 [ _cocoa_video_data.window isMiniaturized ]) { |
|
1137 return; |
|
1138 } |
|
1139 |
|
1140 if (_cocoa_video_data.num_dirty_rects >= MAX_DIRTY_RECTS) { |
|
1141 _cocoa_video_data.num_dirty_rects = 1; |
|
1142 _cocoa_video_data.dirty_rects[0].left = 0; |
|
1143 _cocoa_video_data.dirty_rects[0].top = 0; |
|
1144 _cocoa_video_data.dirty_rects[0].right = _cocoa_video_data.width; |
|
1145 _cocoa_video_data.dirty_rects[0].bottom = _cocoa_video_data.height; |
|
1146 } |
|
1147 |
|
1148 dirty = NewRgn(); |
|
1149 temp = NewRgn(); |
|
1150 |
|
1151 SetEmptyRgn(dirty); |
|
1152 |
|
1153 /* Build the region of dirty rectangles */ |
|
1154 for (i = 0; i < _cocoa_video_data.num_dirty_rects; i++) { |
|
1155 QZ_WindowBlitIndexedPixelsToView( |
|
1156 _cocoa_video_data.dirty_rects[i].left, |
|
1157 _cocoa_video_data.dirty_rects[i].top, |
|
1158 _cocoa_video_data.dirty_rects[i].right, |
|
1159 _cocoa_video_data.dirty_rects[i].bottom |
|
1160 ); |
|
1161 |
|
1162 MacSetRectRgn( |
|
1163 temp, |
|
1164 _cocoa_video_data.dirty_rects[i].left, |
|
1165 _cocoa_video_data.dirty_rects[i].top, |
|
1166 _cocoa_video_data.dirty_rects[i].right, |
|
1167 _cocoa_video_data.dirty_rects[i].bottom |
|
1168 ); |
|
1169 MacUnionRgn(dirty, temp, dirty); |
|
1170 } |
|
1171 |
|
1172 QZ_DrawResizeIcon(); |
|
1173 |
|
1174 /* Flush the dirty region */ |
|
1175 QDFlushPortBuffer( (OpaqueGrafPtr*) [ _cocoa_video_data.qdview qdPort ], dirty); |
|
1176 DisposeRgn(dirty); |
|
1177 DisposeRgn(temp); |
|
1178 |
|
1179 _cocoa_video_data.num_dirty_rects = 0; |
|
1180 } |
|
1181 |
|
1182 |
|
1183 extern const char _openttd_revision[]; |
|
1184 |
|
1185 static const char* QZ_SetVideoWindowed(uint width, uint height) |
|
1186 { |
|
1187 char caption[50]; |
|
1188 NSString *nsscaption; |
|
1189 unsigned int style; |
|
1190 NSRect contentRect; |
|
1191 BOOL isCustom = NO; |
|
1192 |
|
1193 if (_cocoa_video_data.blitter_bpp > _cocoa_video_data.device_bpp) { |
|
1194 error("Cocoa: Cannot use a blitter with a higer screen depth than the display when running in windowed mode."); |
|
1195 } |
|
1196 |
|
1197 if (width > _cocoa_video_data.device_width) |
|
1198 width = _cocoa_video_data.device_width; |
|
1199 if (height > _cocoa_video_data.device_height) |
|
1200 height = _cocoa_video_data.device_height; |
|
1201 |
|
1202 _cocoa_video_data.width = width; |
|
1203 _cocoa_video_data.height = height; |
|
1204 |
|
1205 contentRect = NSMakeRect(0, 0, width, height); |
|
1206 |
|
1207 /* Check if we should completely destroy the previous mode |
|
1208 * - If it is fullscreen |
|
1209 */ |
|
1210 if (_cocoa_video_data.isset && _cocoa_video_data.fullscreen) |
|
1211 QZ_UnsetVideoMode(); |
|
1212 |
|
1213 /* Check if we should recreate the window */ |
|
1214 if (_cocoa_video_data.window == nil) { |
|
1215 /* Set the window style */ |
|
1216 style = NSTitledWindowMask; |
|
1217 style |= (NSMiniaturizableWindowMask | NSClosableWindowMask); |
|
1218 style |= NSResizableWindowMask; |
|
1219 |
|
1220 /* Manually create a window, avoids having a nib file resource */ |
|
1221 _cocoa_video_data.window = [ [ OTTD_QuartzWindow alloc ] |
|
1222 initWithContentRect:contentRect |
|
1223 styleMask:style |
|
1224 backing:NSBackingStoreBuffered |
|
1225 defer:NO ]; |
|
1226 |
|
1227 if (_cocoa_video_data.window == nil) |
|
1228 return "Could not create the Cocoa window"; |
|
1229 |
|
1230 snprintf(caption, sizeof(caption), "OpenTTD %s", _openttd_revision); |
|
1231 nsscaption = [ [ NSString alloc ] initWithCString:caption ]; |
|
1232 [ _cocoa_video_data.window setTitle:nsscaption ]; |
|
1233 [ _cocoa_video_data.window setMiniwindowTitle:nsscaption ]; |
|
1234 [ nsscaption release ]; |
|
1235 |
|
1236 [ _cocoa_video_data.window setAcceptsMouseMovedEvents:YES ]; |
|
1237 [ _cocoa_video_data.window setViewsNeedDisplay:NO ]; |
|
1238 |
|
1239 [ _cocoa_video_data.window setDelegate: [ [ [ OTTD_QuartzWindowDelegate alloc ] init ] autorelease ] ]; |
|
1240 } else { |
|
1241 /* We already have a window, just change its size */ |
|
1242 if (!isCustom) { |
|
1243 [ _cocoa_video_data.window setContentSize:contentRect.size ]; |
|
1244 // Ensure frame height - title bar height >= view height |
|
1245 contentRect.size.height = Clamp(height, 0, [ _cocoa_video_data.window frame ].size.height - 22 /* 22 is the height of title bar of window*/); |
|
1246 height = contentRect.size.height; |
|
1247 [ _cocoa_video_data.qdview setFrameSize:contentRect.size ]; |
|
1248 } |
|
1249 } |
|
1250 |
|
1251 // Update again |
|
1252 _cocoa_video_data.width = width; |
|
1253 _cocoa_video_data.height = height; |
|
1254 |
|
1255 [ _cocoa_video_data.window center ]; |
|
1256 |
|
1257 /* Only recreate the view if it doesn't already exist */ |
|
1258 if (_cocoa_video_data.qdview == nil) { |
|
1259 _cocoa_video_data.qdview = [ [ NSQuickDrawView alloc ] initWithFrame:contentRect ]; |
|
1260 [ _cocoa_video_data.qdview setAutoresizingMask: NSViewWidthSizable | NSViewHeightSizable ]; |
|
1261 [ [ _cocoa_video_data.window contentView ] addSubview:_cocoa_video_data.qdview ]; |
|
1262 [ _cocoa_video_data.qdview release ]; |
|
1263 [ _cocoa_video_data.window makeKeyAndOrderFront:nil ]; |
|
1264 } |
|
1265 |
|
1266 CGrafPtr thePort = (OpaqueGrafPtr*) [ _cocoa_video_data.qdview qdPort ]; |
|
1267 |
|
1268 LockPortBits(thePort); |
|
1269 _cocoa_video_data.realpixels = GetPixBaseAddr(GetPortPixMap(thePort)); |
|
1270 _cocoa_video_data.pitch = GetPixRowBytes(GetPortPixMap(thePort)); |
|
1271 UnlockPortBits(thePort); |
|
1272 |
|
1273 /* _cocoa_video_data.realpixels now points to the window's pixels |
|
1274 * We want it to point to the *view's* pixels |
|
1275 */ |
|
1276 { |
|
1277 int vOffset = [ _cocoa_video_data.window frame ].size.height - [ _cocoa_video_data.qdview frame ].size.height - [ _cocoa_video_data.qdview frame ].origin.y; |
|
1278 int hOffset = [ _cocoa_video_data.qdview frame ].origin.x; |
|
1279 |
|
1280 _cocoa_video_data.realpixels = (uint8*)_cocoa_video_data.realpixels + (vOffset * _cocoa_video_data.pitch) + hOffset * (_cocoa_video_data.device_bpp / 8); |
|
1281 } |
|
1282 |
|
1283 free(_cocoa_video_data.pixels); |
|
1284 _cocoa_video_data.pixels = malloc(width * height * _cocoa_video_data.blitter_bpp / 8); |
|
1285 if (_cocoa_video_data.pixels == NULL) return "Failed to allocate 8-bit buffer"; |
|
1286 |
|
1287 _cocoa_video_data.fullscreen = false; |
|
1288 |
|
1289 return NULL; |
|
1290 } |
|
1291 |
|
1292 |
|
1293 /****************************************************************************** |
|
1294 * Fullscreen mode * |
|
1295 ******************************************************************************/ |
|
1296 |
|
1297 /* Gamma functions to try to hide the flash from a rez switch |
|
1298 * Fade the display from normal to black |
|
1299 * Save gamma tables for fade back to normal |
|
1300 */ |
|
1301 static uint32 QZ_FadeGammaOut(OTTD_QuartzGammaTable* table) |
|
1302 { |
|
1303 CGGammaValue redTable[QZ_GAMMA_TABLE_SIZE]; |
|
1304 CGGammaValue greenTable[QZ_GAMMA_TABLE_SIZE]; |
|
1305 CGGammaValue blueTable[QZ_GAMMA_TABLE_SIZE]; |
|
1306 float percent; |
|
1307 int j; |
|
1308 unsigned int actual; |
|
1309 |
|
1310 if (CGGetDisplayTransferByTable( |
|
1311 _cocoa_video_data.display_id, QZ_GAMMA_TABLE_SIZE, |
|
1312 table->red, table->green, table->blue, &actual |
|
1313 ) != CGDisplayNoErr || |
|
1314 actual != QZ_GAMMA_TABLE_SIZE) { |
|
1315 return 1; |
|
1316 } |
|
1317 |
|
1318 memcpy(redTable, table->red, sizeof(redTable)); |
|
1319 memcpy(greenTable, table->green, sizeof(greenTable)); |
|
1320 memcpy(blueTable, table->blue, sizeof(greenTable)); |
|
1321 |
|
1322 for (percent = 1.0; percent >= 0.0; percent -= 0.01) { |
|
1323 for (j = 0; j < QZ_GAMMA_TABLE_SIZE; j++) { |
|
1324 redTable[j] = redTable[j] * percent; |
|
1325 greenTable[j] = greenTable[j] * percent; |
|
1326 blueTable[j] = blueTable[j] * percent; |
|
1327 } |
|
1328 |
|
1329 if (CGSetDisplayTransferByTable( |
|
1330 _cocoa_video_data.display_id, QZ_GAMMA_TABLE_SIZE, |
|
1331 redTable, greenTable, blueTable |
|
1332 ) != CGDisplayNoErr) { |
|
1333 CGDisplayRestoreColorSyncSettings(); |
|
1334 return 1; |
|
1335 } |
|
1336 |
|
1337 CSleep(10); |
|
1338 } |
|
1339 |
|
1340 return 0; |
|
1341 } |
|
1342 |
|
1343 /* Fade the display from black to normal |
|
1344 * Restore previously saved gamma values |
|
1345 */ |
|
1346 static uint32 QZ_FadeGammaIn(const OTTD_QuartzGammaTable* table) |
|
1347 { |
|
1348 CGGammaValue redTable[QZ_GAMMA_TABLE_SIZE]; |
|
1349 CGGammaValue greenTable[QZ_GAMMA_TABLE_SIZE]; |
|
1350 CGGammaValue blueTable[QZ_GAMMA_TABLE_SIZE]; |
|
1351 float percent; |
|
1352 int j; |
|
1353 |
|
1354 memset(redTable, 0, sizeof(redTable)); |
|
1355 memset(greenTable, 0, sizeof(greenTable)); |
|
1356 memset(blueTable, 0, sizeof(greenTable)); |
|
1357 |
|
1358 for (percent = 0.0; percent <= 1.0; percent += 0.01) { |
|
1359 for (j = 0; j < QZ_GAMMA_TABLE_SIZE; j++) { |
|
1360 redTable[j] = table->red[j] * percent; |
|
1361 greenTable[j] = table->green[j] * percent; |
|
1362 blueTable[j] = table->blue[j] * percent; |
|
1363 } |
|
1364 |
|
1365 if (CGSetDisplayTransferByTable( |
|
1366 _cocoa_video_data.display_id, QZ_GAMMA_TABLE_SIZE, |
|
1367 redTable, greenTable, blueTable |
|
1368 ) != CGDisplayNoErr) { |
|
1369 CGDisplayRestoreColorSyncSettings(); |
|
1370 return 1; |
|
1371 } |
|
1372 |
|
1373 CSleep(10); |
|
1374 } |
|
1375 |
|
1376 return 0; |
|
1377 } |
|
1378 |
|
1379 static const char* QZ_SetVideoFullScreen(int width, int height) |
|
1380 { |
|
1381 const char* errstr = "QZ_SetVideoFullScreen error"; |
|
1382 int exact_match; |
|
1383 CFNumberRef number; |
|
1384 int bpp; |
|
1385 int gamma_error; |
|
1386 OTTD_QuartzGammaTable gamma_table; |
|
1387 NSRect screen_rect; |
|
1388 CGError error; |
|
1389 NSPoint pt; |
|
1390 |
|
1391 /* Destroy any previous mode */ |
|
1392 if (_cocoa_video_data.isset) QZ_UnsetVideoMode(); |
|
1393 |
|
1394 /* See if requested mode exists */ |
|
1395 _cocoa_video_data.mode = CGDisplayBestModeForParameters(_cocoa_video_data.display_id, _cocoa_video_data.blitter_bpp, width, height, &exact_match); |
|
1396 |
|
1397 /* If the mode wasn't an exact match, check if it has the right bpp, and update width and height */ |
|
1398 if (!exact_match) { |
|
1399 number = (const __CFNumber*) CFDictionaryGetValue(_cocoa_video_data.mode, kCGDisplayBitsPerPixel); |
|
1400 CFNumberGetValue(number, kCFNumberSInt32Type, &bpp); |
|
1401 if ((uint) bpp != _cocoa_video_data.blitter_bpp) { |
|
1402 errstr = "Failed to find display resolution"; |
|
1403 goto ERR_NO_MATCH; |
|
1404 } |
|
1405 |
|
1406 number = (const __CFNumber*)CFDictionaryGetValue(_cocoa_video_data.mode, kCGDisplayWidth); |
|
1407 CFNumberGetValue(number, kCFNumberSInt32Type, &width); |
|
1408 |
|
1409 number = (const __CFNumber*)CFDictionaryGetValue(_cocoa_video_data.mode, kCGDisplayHeight); |
|
1410 CFNumberGetValue(number, kCFNumberSInt32Type, &height); |
|
1411 } |
|
1412 |
|
1413 /* Fade display to zero gamma */ |
|
1414 gamma_error = QZ_FadeGammaOut(&gamma_table); |
|
1415 |
|
1416 /* Put up the blanking window (a window above all other windows) */ |
|
1417 error = CGDisplayCapture(_cocoa_video_data.display_id); |
|
1418 |
|
1419 if (CGDisplayNoErr != error) { |
|
1420 errstr = "Failed capturing display"; |
|
1421 goto ERR_NO_CAPTURE; |
|
1422 } |
|
1423 |
|
1424 /* Do the physical switch */ |
|
1425 if (CGDisplaySwitchToMode(_cocoa_video_data.display_id, _cocoa_video_data.mode) != CGDisplayNoErr) { |
|
1426 errstr = "Failed switching display resolution"; |
|
1427 goto ERR_NO_SWITCH; |
|
1428 } |
|
1429 |
|
1430 _cocoa_video_data.realpixels = CGDisplayBaseAddress(_cocoa_video_data.display_id); |
|
1431 _cocoa_video_data.pitch = CGDisplayBytesPerRow(_cocoa_video_data.display_id); |
|
1432 |
|
1433 _cocoa_video_data.width = CGDisplayPixelsWide(_cocoa_video_data.display_id); |
|
1434 _cocoa_video_data.height = CGDisplayPixelsHigh(_cocoa_video_data.display_id); |
|
1435 _cocoa_video_data.fullscreen = true; |
|
1436 |
|
1437 /* Setup double-buffer emulation */ |
|
1438 _cocoa_video_data.pixels = malloc(width * height * _cocoa_video_data.blitter_bpp / 8); |
|
1439 if (_cocoa_video_data.pixels == NULL) { |
|
1440 errstr = "Failed to allocate memory for double buffering"; |
|
1441 goto ERR_DOUBLEBUF; |
|
1442 } |
|
1443 |
|
1444 if (_cocoa_video_data.blitter_bpp == 8 && !CGDisplayCanSetPalette(_cocoa_video_data.display_id)) { |
|
1445 errstr = "Not an indexed display mode."; |
|
1446 goto ERR_NOT_INDEXED; |
|
1447 } |
|
1448 |
|
1449 /* If we don't hide menu bar, it will get events and interrupt the program */ |
|
1450 HideMenuBar(); |
|
1451 |
|
1452 /* Fade the display to original gamma */ |
|
1453 if (!gamma_error) QZ_FadeGammaIn(&gamma_table); |
|
1454 |
|
1455 /* There is a bug in Cocoa where NSScreen doesn't synchronize |
|
1456 * with CGDirectDisplay, so the main screen's frame is wrong. |
|
1457 * As a result, coordinate translation produces incorrect results. |
|
1458 * We can hack around this bug by setting the screen rect ourselves. |
|
1459 * This hack should be removed if/when the bug is fixed. |
|
1460 */ |
|
1461 screen_rect = NSMakeRect(0, 0, width, height); |
|
1462 [ [ NSScreen mainScreen ] setFrame:screen_rect ]; |
|
1463 |
|
1464 /* we're fullscreen, so flag all input states... */ |
|
1465 _cocoa_video_data.active = true; |
|
1466 |
|
1467 |
|
1468 pt = [ NSEvent mouseLocation ]; |
|
1469 pt.y = CGDisplayPixelsHigh(_cocoa_video_data.display_id) - pt.y; |
|
1470 if (QZ_MouseIsInsideView(&pt)) QZ_HideMouse(); |
|
1471 |
|
1472 return NULL; |
|
1473 |
|
1474 /* Since the blanking window covers *all* windows (even force quit) correct recovery is crucial */ |
|
1475 ERR_NOT_INDEXED: |
|
1476 free(_cocoa_video_data.pixels); |
|
1477 _cocoa_video_data.pixels = NULL; |
|
1478 ERR_DOUBLEBUF: |
|
1479 CGDisplaySwitchToMode(_cocoa_video_data.display_id, _cocoa_video_data.save_mode); |
|
1480 ERR_NO_SWITCH: |
|
1481 CGReleaseAllDisplays(); |
|
1482 ERR_NO_CAPTURE: |
|
1483 if (!gamma_error) QZ_FadeGammaIn(&gamma_table); |
|
1484 ERR_NO_MATCH: |
|
1485 return errstr; |
|
1486 } |
|
1487 |
|
1488 |
|
1489 static void QZ_UpdateFullscreenPalette(uint first_color, uint num_colors) |
|
1490 { |
|
1491 CGTableCount index; |
|
1492 CGDeviceColor color; |
|
1493 |
|
1494 for (index = first_color; index < first_color+num_colors; index++) { |
|
1495 /* Clamp colors between 0.0 and 1.0 */ |
|
1496 color.red = _cur_palette[index].r / 255.0; |
|
1497 color.blue = _cur_palette[index].b / 255.0; |
|
1498 color.green = _cur_palette[index].g / 255.0; |
|
1499 |
|
1500 CGPaletteSetColorAtIndex(_cocoa_video_data.palette, color, index); |
|
1501 } |
|
1502 |
|
1503 CGDisplaySetPalette(_cocoa_video_data.display_id, _cocoa_video_data.palette); |
|
1504 } |
|
1505 |
|
1506 /* Wait for the VBL to occur (estimated since we don't have a hardware interrupt) */ |
|
1507 static void QZ_WaitForVerticalBlank() |
|
1508 { |
|
1509 /* The VBL delay is based on Ian Ollmann's RezLib <iano@cco.caltech.edu> */ |
|
1510 double refreshRate; |
|
1511 double linesPerSecond; |
|
1512 double target; |
|
1513 double position; |
|
1514 double adjustment; |
|
1515 CFNumberRef refreshRateCFNumber; |
|
1516 |
|
1517 refreshRateCFNumber = (const __CFNumber*)CFDictionaryGetValue(_cocoa_video_data.mode, kCGDisplayRefreshRate); |
|
1518 if (refreshRateCFNumber == NULL) return; |
|
1519 |
|
1520 if (CFNumberGetValue(refreshRateCFNumber, kCFNumberDoubleType, &refreshRate) == 0) |
|
1521 return; |
|
1522 |
|
1523 if (refreshRate == 0) return; |
|
1524 |
|
1525 linesPerSecond = refreshRate * _cocoa_video_data.height; |
|
1526 target = _cocoa_video_data.height; |
|
1527 |
|
1528 /* Figure out the first delay so we start off about right */ |
|
1529 position = CGDisplayBeamPosition(_cocoa_video_data.display_id); |
|
1530 if (position > target) position = 0; |
|
1531 |
|
1532 adjustment = (target - position) / linesPerSecond; |
|
1533 |
|
1534 CSleep((uint32)(adjustment * 1000)); |
|
1535 } |
|
1536 |
|
1537 |
|
1538 static void QZ_DrawScreen() |
|
1539 { |
|
1540 const uint8* src = (uint8*) _cocoa_video_data.pixels; |
|
1541 uint8* dst = (uint8*)_cocoa_video_data.realpixels; |
|
1542 uint pitch = _cocoa_video_data.pitch; |
|
1543 uint width = _cocoa_video_data.width; |
|
1544 uint num_dirty = _cocoa_video_data.num_dirty_rects; |
|
1545 uint bytesperpixel = _cocoa_video_data.blitter_bpp / 8; |
|
1546 uint i; |
|
1547 |
|
1548 /* Check if we need to do anything */ |
|
1549 if (num_dirty == 0) return; |
|
1550 |
|
1551 if (num_dirty >= MAX_DIRTY_RECTS) { |
|
1552 num_dirty = 1; |
|
1553 _cocoa_video_data.dirty_rects[0].left = 0; |
|
1554 _cocoa_video_data.dirty_rects[0].top = 0; |
|
1555 _cocoa_video_data.dirty_rects[0].right = _cocoa_video_data.width; |
|
1556 _cocoa_video_data.dirty_rects[0].bottom = _cocoa_video_data.height; |
|
1557 } |
|
1558 |
|
1559 QZ_WaitForVerticalBlank(); |
|
1560 /* Build the region of dirty rectangles */ |
|
1561 for (i = 0; i < num_dirty; i++) { |
|
1562 uint y = _cocoa_video_data.dirty_rects[i].top; |
|
1563 uint left = _cocoa_video_data.dirty_rects[i].left; |
|
1564 uint length = _cocoa_video_data.dirty_rects[i].right - left; |
|
1565 uint bottom = _cocoa_video_data.dirty_rects[i].bottom; |
|
1566 |
|
1567 for (; y < bottom; y++) { |
|
1568 memcpy(dst + y * pitch + left * bytesperpixel, src + y * width * bytesperpixel + left * bytesperpixel, length * bytesperpixel); |
|
1569 } |
|
1570 } |
|
1571 |
|
1572 _cocoa_video_data.num_dirty_rects = 0; |
|
1573 } |
|
1574 |
|
1575 |
|
1576 static int QZ_ListFullscreenModes(OTTDPoint* mode_list, int max_modes) |
|
1577 { |
|
1578 CFIndex num_modes; |
|
1579 CFIndex i; |
|
1580 int list_size = 0; |
|
1581 |
|
1582 num_modes = CFArrayGetCount(_cocoa_video_data.mode_list); |
|
1583 |
|
1584 /* Build list of modes with the requested bpp */ |
|
1585 for (i = 0; i < num_modes && list_size < max_modes; i++) { |
|
1586 CFDictionaryRef onemode; |
|
1587 CFNumberRef number; |
|
1588 int bpp; |
|
1589 int intvalue; |
|
1590 bool hasMode; |
|
1591 uint16 width, height; |
|
1592 |
|
1593 onemode = (const __CFDictionary*)CFArrayGetValueAtIndex(_cocoa_video_data.mode_list, i); |
|
1594 number = (const __CFNumber*)CFDictionaryGetValue(onemode, kCGDisplayBitsPerPixel); |
|
1595 CFNumberGetValue (number, kCFNumberSInt32Type, &bpp); |
|
1596 |
|
1597 if ((uint) bpp != _cocoa_video_data.blitter_bpp) continue; |
|
1598 |
|
1599 number = (const __CFNumber*)CFDictionaryGetValue(onemode, kCGDisplayWidth); |
|
1600 CFNumberGetValue(number, kCFNumberSInt32Type, &intvalue); |
|
1601 width = (uint16)intvalue; |
|
1602 |
|
1603 number = (const __CFNumber*)CFDictionaryGetValue(onemode, kCGDisplayHeight); |
|
1604 CFNumberGetValue(number, kCFNumberSInt32Type, &intvalue); |
|
1605 height = (uint16)intvalue; |
|
1606 |
|
1607 /* Check if mode is already in the list */ |
|
1608 { |
|
1609 int i; |
|
1610 hasMode = false; |
|
1611 for (i = 0; i < list_size; i++) { |
|
1612 if (mode_list[i].x == width && mode_list[i].y == height) { |
|
1613 hasMode = true; |
|
1614 break; |
|
1615 } |
|
1616 } |
|
1617 } |
|
1618 |
|
1619 if (hasMode) continue; |
|
1620 |
|
1621 /* Add mode to the list */ |
|
1622 mode_list[list_size].x = width; |
|
1623 mode_list[list_size].y = height; |
|
1624 list_size++; |
|
1625 } |
|
1626 |
|
1627 /* Sort list smallest to largest */ |
|
1628 { |
|
1629 int i, j; |
|
1630 for (i = 0; i < list_size; i++) { |
|
1631 for (j = 0; j < list_size-1; j++) { |
|
1632 if (mode_list[j].x > mode_list[j + 1].x || ( |
|
1633 mode_list[j].x == mode_list[j + 1].x && |
|
1634 mode_list[j].y > mode_list[j + 1].y |
|
1635 )) { |
|
1636 uint tmpw = mode_list[j].x; |
|
1637 uint tmph = mode_list[j].y; |
|
1638 |
|
1639 mode_list[j].x = mode_list[j + 1].x; |
|
1640 mode_list[j].y = mode_list[j + 1].y; |
|
1641 |
|
1642 mode_list[j + 1].x = tmpw; |
|
1643 mode_list[j + 1].y = tmph; |
|
1644 } |
|
1645 } |
|
1646 } |
|
1647 } |
|
1648 |
|
1649 return list_size; |
|
1650 } |
|
1651 |
|
1652 |
|
1653 /****************************************************************************** |
|
1654 * Windowed and fullscreen common code * |
|
1655 ******************************************************************************/ |
|
1656 |
|
1657 static void QZ_UpdatePalette(uint start, uint count) |
|
1658 { |
|
1659 if (_cocoa_video_data.fullscreen) { |
|
1660 QZ_UpdateFullscreenPalette(start, count); |
|
1661 } else { |
|
1662 QZ_UpdateWindowPalette(start, count); |
|
1663 } |
|
1664 } |
|
1665 |
|
1666 static void QZ_InitPalette() |
|
1667 { |
|
1668 QZ_UpdatePalette(0, 256); |
|
1669 } |
|
1670 |
|
1671 static void QZ_Draw() |
|
1672 { |
|
1673 if (_cocoa_video_data.fullscreen) { |
|
1674 QZ_DrawScreen(); |
|
1675 } else { |
|
1676 QZ_DrawWindow(); |
|
1677 } |
|
1678 } |
|
1679 |
|
1680 |
|
1681 static const OTTDPoint _default_resolutions[] = { |
|
1682 { 640, 480}, |
|
1683 { 800, 600}, |
|
1684 {1024, 768}, |
|
1685 {1152, 864}, |
|
1686 {1280, 800}, |
|
1687 {1280, 960}, |
|
1688 {1280, 1024}, |
|
1689 {1400, 1050}, |
|
1690 {1600, 1200}, |
|
1691 {1680, 1050}, |
|
1692 {1920, 1200} |
|
1693 }; |
|
1694 |
|
1695 static void QZ_UpdateVideoModes() |
|
1696 { |
|
1697 uint i, j, count; |
|
1698 OTTDPoint modes[32]; |
|
1699 const OTTDPoint *current_modes; |
|
1700 |
|
1701 if (_cocoa_video_data.fullscreen) { |
|
1702 count = QZ_ListFullscreenModes(modes, 32); |
|
1703 current_modes = modes; |
|
1704 } else { |
|
1705 count = lengthof(_default_resolutions); |
|
1706 current_modes = _default_resolutions; |
|
1707 } |
|
1708 |
|
1709 for (i = 0, j = 0; j < lengthof(_resolutions) && i < count; i++) { |
|
1710 if (_cocoa_video_data.fullscreen || ( |
|
1711 (uint)current_modes[i].x < _cocoa_video_data.device_width && |
|
1712 (uint)current_modes[i].y < _cocoa_video_data.device_height) |
|
1713 ) { |
|
1714 _resolutions[j][0] = current_modes[i].x; |
|
1715 _resolutions[j][1] = current_modes[i].y; |
|
1716 j++; |
|
1717 } |
|
1718 } |
|
1719 |
|
1720 _num_resolutions = j; |
|
1721 } |
|
1722 |
|
1723 static void QZ_UnsetVideoMode() |
|
1724 { |
|
1725 if (_cocoa_video_data.fullscreen) { |
|
1726 /* Release fullscreen resources */ |
|
1727 OTTD_QuartzGammaTable gamma_table; |
|
1728 int gamma_error; |
|
1729 NSRect screen_rect; |
|
1730 |
|
1731 gamma_error = QZ_FadeGammaOut(&gamma_table); |
|
1732 |
|
1733 /* Restore original screen resolution/bpp */ |
|
1734 CGDisplaySwitchToMode(_cocoa_video_data.display_id, _cocoa_video_data.save_mode); |
|
1735 CGReleaseAllDisplays(); |
|
1736 ShowMenuBar(); |
|
1737 /* Reset the main screen's rectangle |
|
1738 * See comment in QZ_SetVideoFullscreen for why we do this |
|
1739 */ |
|
1740 screen_rect = NSMakeRect(0,0,_cocoa_video_data.device_width,_cocoa_video_data.device_height); |
|
1741 [ [ NSScreen mainScreen ] setFrame:screen_rect ]; |
|
1742 |
|
1743 if (!gamma_error) QZ_FadeGammaIn(&gamma_table); |
|
1744 } else { |
|
1745 /* Release window mode resources */ |
|
1746 [ _cocoa_video_data.window close ]; |
|
1747 _cocoa_video_data.window = nil; |
|
1748 _cocoa_video_data.qdview = nil; |
|
1749 } |
|
1750 |
|
1751 free(_cocoa_video_data.pixels); |
|
1752 _cocoa_video_data.pixels = NULL; |
|
1753 |
|
1754 /* Signal successful teardown */ |
|
1755 _cocoa_video_data.isset = false; |
|
1756 |
|
1757 QZ_ShowMouse(); |
|
1758 } |
|
1759 |
|
1760 |
|
1761 static const char* QZ_SetVideoMode(uint width, uint height, bool fullscreen) |
|
1762 { |
|
1763 const char *ret; |
|
1764 |
|
1765 _cocoa_video_data.issetting = true; |
|
1766 if (fullscreen) { |
|
1767 /* Setup full screen video */ |
|
1768 ret = QZ_SetVideoFullScreen(width, height); |
|
1769 } else { |
|
1770 /* Setup windowed video */ |
|
1771 ret = QZ_SetVideoWindowed(width, height); |
|
1772 } |
|
1773 _cocoa_video_data.issetting = false; |
|
1774 if (ret != NULL) return ret; |
|
1775 |
|
1776 /* Signal successful completion (used internally) */ |
|
1777 _cocoa_video_data.isset = true; |
|
1778 |
|
1779 /* Tell the game that the resolution has changed */ |
|
1780 _screen.width = _cocoa_video_data.width; |
|
1781 _screen.height = _cocoa_video_data.height; |
|
1782 _screen.pitch = _cocoa_video_data.width; |
|
1783 |
|
1784 QZ_UpdateVideoModes(); |
|
1785 GameSizeChanged(); |
|
1786 |
|
1787 QZ_InitPalette(); |
|
1788 |
|
1789 return NULL; |
|
1790 } |
|
1791 |
|
1792 static const char* QZ_SetVideoModeAndRestoreOnFailure(uint width, uint height, bool fullscreen) |
|
1793 { |
|
1794 bool wasset = _cocoa_video_data.isset; |
|
1795 uint32 oldwidth = _cocoa_video_data.width; |
|
1796 uint32 oldheight = _cocoa_video_data.height; |
|
1797 bool oldfullscreen = _cocoa_video_data.fullscreen; |
|
1798 const char *ret; |
|
1799 |
|
1800 ret = QZ_SetVideoMode(width, height, fullscreen); |
|
1801 if (ret != NULL && wasset) QZ_SetVideoMode(oldwidth, oldheight, oldfullscreen); |
|
1802 |
|
1803 return ret; |
|
1804 } |
|
1805 |
|
1806 static void QZ_VideoInit() |
|
1807 { |
|
1808 |
|
1809 memset(&_cocoa_video_data, 0, sizeof(_cocoa_video_data)); |
|
1810 |
|
1811 _cocoa_video_data.blitter_bpp = BlitterFactoryBase::GetCurrentBlitter()->GetScreenDepth(); |
|
1812 DEBUG(driver, 1, "Cocoa: Blitter bpp %d", _cocoa_video_data.blitter_bpp); |
|
1813 |
|
1814 if (_cocoa_video_data.blitter_bpp == 0) error("Can't use a blitter that blits 0 bpp for normal visuals"); |
|
1815 |
|
1816 |
|
1817 if (_cocoa_video_data.blitter_bpp != 8 && _cocoa_video_data.blitter_bpp != 32) { |
|
1818 error("Cocoa: This video driver only supports 8 and 32 bpp blitters."); |
|
1819 } |
|
1820 |
|
1821 /* Initialize the video settings; this data persists between mode switches */ |
|
1822 _cocoa_video_data.display_id = kCGDirectMainDisplay; |
|
1823 _cocoa_video_data.save_mode = CGDisplayCurrentMode(_cocoa_video_data.display_id); |
|
1824 _cocoa_video_data.mode_list = CGDisplayAvailableModes(_cocoa_video_data.display_id); |
|
1825 _cocoa_video_data.palette = CGPaletteCreateDefaultColorPalette(); |
|
1826 |
|
1827 /* Gather some information that is useful to know about the display */ |
|
1828 /* Maybe this should be moved to QZ_SetVideoMode, in case this is changed after startup */ |
|
1829 CFNumberGetValue( |
|
1830 (const __CFNumber*)CFDictionaryGetValue(_cocoa_video_data.save_mode, kCGDisplayBitsPerPixel), |
|
1831 kCFNumberSInt32Type, &_cocoa_video_data.device_bpp |
|
1832 ); |
|
1833 |
|
1834 CFNumberGetValue( |
|
1835 (const __CFNumber*)CFDictionaryGetValue(_cocoa_video_data.save_mode, kCGDisplayWidth), |
|
1836 kCFNumberSInt32Type, &_cocoa_video_data.device_width |
|
1837 ); |
|
1838 |
|
1839 CFNumberGetValue( |
|
1840 (const __CFNumber*)CFDictionaryGetValue(_cocoa_video_data.save_mode, kCGDisplayHeight), |
|
1841 kCFNumberSInt32Type, &_cocoa_video_data.device_height |
|
1842 ); |
|
1843 |
|
1844 _cocoa_video_data.cursor_visible = true; |
|
1845 |
|
1846 /* register for sleep notifications so wake from sleep generates SDL_VIDEOEXPOSE */ |
|
1847 // QZ_RegisterForSleepNotifications(); |
|
1848 } |
|
1849 |
|
1850 |
|
1851 /* Convert local coordinate to window server (CoreGraphics) coordinate */ |
|
1852 static CGPoint QZ_PrivateLocalToCG(NSPoint* p) |
|
1853 { |
|
1854 CGPoint cgp; |
|
1855 |
|
1856 if (!_cocoa_video_data.fullscreen) { |
|
1857 *p = [ _cocoa_video_data.qdview convertPoint:*p toView: nil ]; |
|
1858 *p = [ _cocoa_video_data.window convertBaseToScreen:*p ]; |
|
1859 p->y = _cocoa_video_data.device_height - p->y; |
|
1860 } |
|
1861 |
|
1862 cgp.x = p->x; |
|
1863 cgp.y = p->y; |
|
1864 |
|
1865 return cgp; |
|
1866 } |
|
1867 |
|
1868 static void QZ_WarpCursor(int x, int y) |
|
1869 { |
|
1870 NSPoint p; |
|
1871 CGPoint cgp; |
|
1872 |
|
1873 /* Only allow warping when in foreground */ |
|
1874 if (![ NSApp isActive ]) return; |
|
1875 |
|
1876 p = NSMakePoint(x, y); |
|
1877 cgp = QZ_PrivateLocalToCG(&p); |
|
1878 |
|
1879 /* this is the magic call that fixes cursor "freezing" after warp */ |
|
1880 CGSetLocalEventsSuppressionInterval(0.0); |
|
1881 /* Do the actual warp */ |
|
1882 CGWarpMouseCursorPosition(cgp); |
|
1883 |
|
1884 /* Generate the mouse moved event */ |
|
1885 } |
|
1886 |
|
1887 static void QZ_ShowMouse() |
|
1888 { |
|
1889 if (!_cocoa_video_data.cursor_visible) { |
|
1890 [ NSCursor unhide ]; |
|
1891 _cocoa_video_data.cursor_visible = true; |
|
1892 |
|
1893 // Hide the openttd cursor when leaving the window |
|
1894 if (_cocoa_video_data.isset) |
|
1895 UndrawMouseCursor(); |
|
1896 _cursor.in_window = false; |
|
1897 } |
|
1898 } |
|
1899 |
|
1900 static void QZ_HideMouse() |
|
1901 { |
|
1902 if (_cocoa_video_data.cursor_visible) { |
|
1903 #ifndef _DEBUG |
|
1904 [ NSCursor hide ]; |
|
1905 #endif |
|
1906 _cocoa_video_data.cursor_visible = false; |
|
1907 |
|
1908 // Show the openttd cursor again |
|
1909 _cursor.in_window = true; |
|
1910 } |
|
1911 } |
|
1912 |
|
1913 |
|
1914 /****************************************************************************** |
|
1915 * OS X application creation * |
|
1916 ******************************************************************************/ |
|
1917 |
|
1918 /* The main class of the application, the application's delegate */ |
|
1919 @implementation OTTDMain |
|
1920 /* Called when the internal event loop has just started running */ |
|
1921 - (void) applicationDidFinishLaunching: (NSNotification*) note |
|
1922 { |
|
1923 /* Hand off to main application code */ |
|
1924 QZ_GameLoop(); |
|
1925 |
|
1926 /* We're done, thank you for playing */ |
|
1927 [ NSApp stop:_ottd_main ]; |
|
1928 } |
|
1929 |
|
1930 /* Display the in game quit confirmation dialog */ |
|
1931 - (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication*) sender |
|
1932 { |
|
1933 |
|
1934 HandleExitGameRequest(); |
|
1935 |
|
1936 return NSTerminateCancel; // NSTerminateLater ? |
|
1937 } |
|
1938 @end |
|
1939 |
|
1940 static void setApplicationMenu() |
|
1941 { |
|
1942 /* warning: this code is very odd */ |
|
1943 NSMenu *appleMenu; |
|
1944 NSMenuItem *menuItem; |
|
1945 NSString *title; |
|
1946 NSString *appName; |
|
1947 |
|
1948 appName = @"OTTD"; |
|
1949 appleMenu = [[NSMenu alloc] initWithTitle:appName]; |
|
1950 |
|
1951 /* Add menu items */ |
|
1952 title = [@"About " stringByAppendingString:appName]; |
|
1953 [appleMenu addItemWithTitle:title action:@selector(orderFrontStandardAboutPanel:) keyEquivalent:@""]; |
|
1954 |
|
1955 [appleMenu addItem:[NSMenuItem separatorItem]]; |
|
1956 |
|
1957 title = [@"Hide " stringByAppendingString:appName]; |
|
1958 [appleMenu addItemWithTitle:title action:@selector(hide:) keyEquivalent:@"h"]; |
|
1959 |
|
1960 menuItem = (NSMenuItem*)[appleMenu addItemWithTitle:@"Hide Others" action:@selector(hideOtherApplications:) keyEquivalent:@"h"]; |
|
1961 [menuItem setKeyEquivalentModifierMask:(NSAlternateKeyMask|NSCommandKeyMask)]; |
|
1962 |
|
1963 [appleMenu addItemWithTitle:@"Show All" action:@selector(unhideAllApplications:) keyEquivalent:@""]; |
|
1964 |
|
1965 [appleMenu addItem:[NSMenuItem separatorItem]]; |
|
1966 |
|
1967 title = [@"Quit " stringByAppendingString:appName]; |
|
1968 [appleMenu addItemWithTitle:title action:@selector(terminate:) keyEquivalent:@"q"]; |
|
1969 |
|
1970 |
|
1971 /* Put menu into the menubar */ |
|
1972 menuItem = [[NSMenuItem alloc] initWithTitle:@"" action:nil keyEquivalent:@""]; |
|
1973 [menuItem setSubmenu:appleMenu]; |
|
1974 [[NSApp mainMenu] addItem:menuItem]; |
|
1975 |
|
1976 /* Tell the application object that this is now the application menu */ |
|
1977 [NSApp setAppleMenu:appleMenu]; |
|
1978 |
|
1979 /* Finally give up our references to the objects */ |
|
1980 [appleMenu release]; |
|
1981 [menuItem release]; |
|
1982 } |
|
1983 |
|
1984 /* Create a window menu */ |
|
1985 static void setupWindowMenu() |
|
1986 { |
|
1987 NSMenu* windowMenu; |
|
1988 NSMenuItem* windowMenuItem; |
|
1989 NSMenuItem* menuItem; |
|
1990 |
|
1991 windowMenu = [[NSMenu alloc] initWithTitle:@"Window"]; |
|
1992 |
|
1993 /* "Minimize" item */ |
|
1994 menuItem = [[NSMenuItem alloc] initWithTitle:@"Minimize" action:@selector(performMiniaturize:) keyEquivalent:@"m"]; |
|
1995 [windowMenu addItem:menuItem]; |
|
1996 [menuItem release]; |
|
1997 |
|
1998 /* Put menu into the menubar */ |
|
1999 windowMenuItem = [[NSMenuItem alloc] initWithTitle:@"Window" action:nil keyEquivalent:@""]; |
|
2000 [windowMenuItem setSubmenu:windowMenu]; |
|
2001 [[NSApp mainMenu] addItem:windowMenuItem]; |
|
2002 |
|
2003 /* Tell the application object that this is now the window menu */ |
|
2004 [NSApp setWindowsMenu:windowMenu]; |
|
2005 |
|
2006 /* Finally give up our references to the objects */ |
|
2007 [windowMenu release]; |
|
2008 [windowMenuItem release]; |
|
2009 } |
|
2010 |
|
2011 static void setupApplication() |
|
2012 { |
|
2013 CPSProcessSerNum PSN; |
|
2014 |
|
2015 /* Ensure the application object is initialised */ |
|
2016 [NSApplication sharedApplication]; |
|
2017 |
|
2018 /* Tell the dock about us */ |
|
2019 if (!CPSGetCurrentProcess(&PSN) && |
|
2020 !CPSEnableForegroundOperation(&PSN, 0x03, 0x3C, 0x2C, 0x1103) && |
|
2021 !CPSSetFrontProcess(&PSN)) { |
|
2022 [NSApplication sharedApplication]; |
|
2023 } |
|
2024 |
|
2025 /* Set up the menubar */ |
|
2026 [NSApp setMainMenu:[[NSMenu alloc] init]]; |
|
2027 setApplicationMenu(); |
|
2028 setupWindowMenu(); |
|
2029 |
|
2030 /* Create OTTDMain and make it the app delegate */ |
|
2031 _ottd_main = [[OTTDMain alloc] init]; |
|
2032 [NSApp setDelegate:_ottd_main]; |
|
2033 } |
|
2034 |
|
2035 |
|
2036 /****************************************************************************** |
|
2037 * Video driver interface * |
|
2038 ******************************************************************************/ |
|
2039 |
|
2040 static FVideoDriver_Cocoa iFVideoDriver_Cocoa; |
|
2041 |
|
2042 void VideoDriver_Cocoa::Stop() |
|
2043 { |
|
2044 if (!_cocoa_video_started) return; |
|
2045 |
|
2046 if (_cocoa_video_data.isset) QZ_UnsetVideoMode(); |
|
2047 |
|
2048 [_ottd_main release]; |
|
2049 |
|
2050 _cocoa_video_started = false; |
|
2051 } |
|
2052 |
|
2053 const char *VideoDriver_Cocoa::Start(const char * const *parm) |
|
2054 { |
|
2055 const char *ret; |
|
2056 |
|
2057 if (_cocoa_video_started) return "Already started"; |
|
2058 _cocoa_video_started = true; |
|
2059 |
|
2060 memset(&_cocoa_video_data, 0, sizeof(_cocoa_video_data)); |
|
2061 |
|
2062 setupApplication(); |
|
2063 |
|
2064 /* Don't create a window or enter fullscreen if we're just going to show a dialog. */ |
|
2065 if (_cocoa_video_dialog) return NULL; |
|
2066 |
|
2067 QZ_VideoInit(); |
|
2068 |
|
2069 ret = QZ_SetVideoMode(_cur_resolution[0], _cur_resolution[1], _fullscreen); |
|
2070 if (ret != NULL) _video_driver->Stop(); |
|
2071 |
|
2072 return ret; |
|
2073 } |
|
2074 |
|
2075 void VideoDriver_Cocoa::MakeDirty(int left, int top, int width, int height) |
|
2076 { |
|
2077 if (_cocoa_video_data.num_dirty_rects < MAX_DIRTY_RECTS) { |
|
2078 _cocoa_video_data.dirty_rects[_cocoa_video_data.num_dirty_rects].left = left; |
|
2079 _cocoa_video_data.dirty_rects[_cocoa_video_data.num_dirty_rects].top = top; |
|
2080 _cocoa_video_data.dirty_rects[_cocoa_video_data.num_dirty_rects].right = left + width; |
|
2081 _cocoa_video_data.dirty_rects[_cocoa_video_data.num_dirty_rects].bottom = top + height; |
|
2082 } |
|
2083 _cocoa_video_data.num_dirty_rects++; |
|
2084 } |
|
2085 |
|
2086 void VideoDriver_Cocoa::MainLoop() |
|
2087 { |
|
2088 /* Start the main event loop */ |
|
2089 [NSApp run]; |
|
2090 } |
|
2091 |
|
2092 bool VideoDriver_Cocoa::ChangeResolution(int w, int h) |
|
2093 { |
|
2094 const char *ret = QZ_SetVideoModeAndRestoreOnFailure((uint)w, (uint)h, _cocoa_video_data.fullscreen); |
|
2095 if (ret != NULL) { |
|
2096 DEBUG(driver, 0, "cocoa_v: VideoDriver_Cocoa::ChangeResolution failed with message: %s", ret); |
|
2097 } |
|
2098 |
|
2099 return ret == NULL; |
|
2100 } |
|
2101 |
|
2102 void VideoDriver_Cocoa::ToggleFullscreen(bool full_screen) |
|
2103 { |
|
2104 const char *ret = QZ_SetVideoModeAndRestoreOnFailure(_cocoa_video_data.width, _cocoa_video_data.height, full_screen); |
|
2105 if (ret != NULL) { |
|
2106 DEBUG(driver, 0, "cocoa_v: VideoDriver_Cocoa::ToggleFullscreen failed with message: %s", ret); |
|
2107 } |
|
2108 |
|
2109 _fullscreen = _cocoa_video_data.fullscreen; |
|
2110 } |
|
2111 |
|
2112 |
|
2113 /* This is needed since sometimes assert is called before the videodriver is initialized */ |
|
2114 void CocoaDialog(const char* title, const char* message, const char* buttonLabel) |
|
2115 { |
|
2116 bool wasstarted; |
|
2117 |
|
2118 _cocoa_video_dialog = true; |
|
2119 |
|
2120 wasstarted = _cocoa_video_started; |
|
2121 if (_video_driver == NULL) { |
|
2122 setupApplication(); // Setup application before showing dialog |
|
2123 } else if (!_cocoa_video_started && _video_driver->Start(NULL) != NULL) { |
|
2124 fprintf(stderr, "%s: %s\n", title, message); |
|
2125 return; |
|
2126 } |
|
2127 |
|
2128 NSRunAlertPanel([NSString stringWithCString: title], [NSString stringWithCString: message], [NSString stringWithCString: buttonLabel], nil, nil); |
|
2129 |
|
2130 if (!wasstarted && _video_driver != NULL) _video_driver->Stop(); |
|
2131 |
|
2132 _cocoa_video_dialog = false; |
|
2133 } |
|
2134 |
|
2135 /* This is needed since OS X application bundles do not have a |
|
2136 * current directory and the data files are 'somewhere' in the bundle */ |
|
2137 void cocoaSetApplicationBundleDir() |
|
2138 { |
|
2139 char tmp[MAXPATHLEN]; |
|
2140 CFURLRef url = CFBundleCopyResourcesDirectoryURL(CFBundleGetMainBundle()); |
|
2141 if (CFURLGetFileSystemRepresentation(url, true, (unsigned char*)tmp, MAXPATHLEN)) { |
|
2142 AppendPathSeparator(tmp, lengthof(tmp)); |
|
2143 _searchpaths[SP_APPLICATION_BUNDLE_DIR] = strdup(tmp); |
|
2144 } else { |
|
2145 _searchpaths[SP_APPLICATION_BUNDLE_DIR] = NULL; |
|
2146 } |
|
2147 |
|
2148 CFRelease(url); |
|
2149 } |
|
2150 |
|
2151 /* These are called from main() to prevent a _NSAutoreleaseNoPool error when |
|
2152 * exiting before the cocoa video driver has been loaded |
|
2153 */ |
|
2154 void cocoaSetupAutoreleasePool() |
|
2155 { |
|
2156 _ottd_autorelease_pool = [[NSAutoreleasePool alloc] init]; |
|
2157 } |
|
2158 |
|
2159 void cocoaReleaseAutoreleasePool() |
|
2160 { |
|
2161 [_ottd_autorelease_pool release]; |
|
2162 } |
|
2163 |
|
2164 #endif /* WITH_COCOA */ |
|