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