PreferencesDialog.cpp 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845
  1. /*
  2. ===========================================================================
  3. Doom 3 GPL Source Code
  4. Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company.
  5. This file is part of the Doom 3 GPL Source Code (?Doom 3 Source Code?).
  6. Doom 3 Source Code is free software: you can redistribute it and/or modify
  7. it under the terms of the GNU General Public License as published by
  8. the Free Software Foundation, either version 3 of the License, or
  9. (at your option) any later version.
  10. Doom 3 Source Code is distributed in the hope that it will be useful,
  11. but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. GNU General Public License for more details.
  14. You should have received a copy of the GNU General Public License
  15. along with Doom 3 Source Code. If not, see <http://www.gnu.org/licenses/>.
  16. In addition, the Doom 3 Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 Source Code. If not, please request a copy in writing from id Software at the address below.
  17. If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
  18. ===========================================================================
  19. */
  20. #include "../../idlib/precompiled.h"
  21. #include <Carbon/Carbon.h>
  22. #include "PreferencesDialog.h"
  23. #include "PickMonitor.h"
  24. #include <list>
  25. #include <set>
  26. static idCVar r_stretched( "r_stretched", "0", CVAR_ARCHIVE | CVAR_BOOL, "Used stretched resolution" );
  27. #define kPref_PrefsDialogAlways CFSTR("PrefsDialogAlways")
  28. #define kPref_PrefsDialogOpenAL CFSTR("UseOpenAL")
  29. #ifndef kAppCreator
  30. #define kAppCreator 'DOM3' // Creator type
  31. #endif
  32. const UInt32 kRes_Stretched = (1 << 0); // set if the resolution is a stretched mode (kCGDisplayModeIsStretched)
  33. const UInt32 kRes_Safe = (1 << 1); // ¥¥¥Ê(currently unused) set if the resolution is safe (kCGDisplayModeIsSafeForHardware)
  34. // Data to be presented and edited in the prefs dialog
  35. struct PrefInfo
  36. {
  37. // prefs values
  38. GameDisplayMode prefGameDisplayMode;
  39. CGDirectDisplayID prefDisplayID;
  40. int prefWidth;
  41. int prefHeight;
  42. int prefDepth;
  43. Fixed prefFrequency;
  44. UInt32 prefResFlags;
  45. Boolean prefAlways;
  46. Boolean prefOpenAL;
  47. bool okPressed; // Set to true if the user pressed the OK button
  48. // The following are private data passed from GameDisplayPreferencesDialog() to it's command handler.
  49. WindowRef window;
  50. ControlRef fullscreenBtn;
  51. ControlRef inAWindowBtn;
  52. ControlRef resolutionPopup;
  53. ControlRef refreshRatePopup;
  54. ControlRef chooseMonitorsBtn;
  55. ControlRef alwaysBtn;
  56. ControlRef openALBtn;
  57. ValidModeCallbackProc callback; // To validate display modes
  58. bool multiMonitor; // Does user have multiple monitors
  59. std::list<Fixed> refreshRates; // List of refresh rates available for the selected monitor
  60. SInt32 freqMenuIndex;
  61. };
  62. #pragma mark -
  63. bool R_GetModeInfo( int *width, int *height, int mode );
  64. static int GetScreenIndexForDisplayID( CGDirectDisplayID inDisplayID ) {
  65. unsigned int i;
  66. OSErr err;
  67. int r_screen = -1;
  68. CGDisplayCount count;
  69. err = CGGetActiveDisplayList(0, NULL, &count);
  70. if (noErr == err) {
  71. CGDirectDisplayID displays[count];
  72. err = CGGetActiveDisplayList(count, displays, &count);
  73. if (noErr == err) {
  74. for ( i = 0; i < count; i++)
  75. if (displays[i] == inDisplayID)
  76. r_screen = i;
  77. }
  78. }
  79. return r_screen;
  80. }
  81. static CGDirectDisplayID GetDisplayIDForScreenIndex( int inScreenIndex ) {
  82. OSErr err;
  83. int r_screen = -1;
  84. CGDisplayCount count;
  85. err = CGGetActiveDisplayList(0, NULL, &count);
  86. if (noErr == err) {
  87. CGDirectDisplayID displays[count];
  88. err = CGGetActiveDisplayList(count, displays, &count);
  89. if (noErr == err) {
  90. if ( inScreenIndex >= 0 && inScreenIndex <= count )
  91. return displays[inScreenIndex];
  92. }
  93. }
  94. return (CGDirectDisplayID)r_screen;
  95. }
  96. void Sys_DoPreferences( void ) {
  97. // An NSKeyDown event is not fired if the user holds down Cmd during startup.
  98. // Cmd is treated purely as a modifier. To capture the user
  99. // holding down Cmd, you would need to override NSApplication's
  100. // keydown handler. That's overkill for a single check at
  101. // startup, use the Carbon GetKeys approach.
  102. unsigned char km[16];
  103. const int kMacKeyCodeCommand = 0x37;
  104. KeyMap *keymap = (KeyMap*)&km;
  105. GetKeys(*keymap);
  106. Boolean prefAways, keyFound, useOpenAL;
  107. prefAways = CFPreferencesGetAppBooleanValue ( kPref_PrefsDialogAlways, kCFPreferencesCurrentApplication, &keyFound );
  108. bool fAlways = prefAways && keyFound;
  109. if ( fAlways || ( km[kMacKeyCodeCommand>>3] >> ( kMacKeyCodeCommand & 7 ) ) & 1 ) {
  110. GameDisplayInfo info;
  111. info.mode = cvarSystem->GetCVarBool( "r_fullscreen" ) ? kFullScreen : kWindow;
  112. info.displayID = GetDisplayIDForScreenIndex( cvarSystem->GetCVarInteger( "r_screen" ) );
  113. int w = 800, h = 600;
  114. R_GetModeInfo( &w, &h, cvarSystem->GetCVarInteger( "r_mode" ) );
  115. info.width = w;
  116. info.height = h;
  117. info.depth = 32;
  118. info.frequency = cvarSystem->GetCVarInteger( "r_maxDisplayRefresh" );
  119. info.windowLoc.x = 0;
  120. info.windowLoc.y = 0;
  121. info.flags = 0;
  122. info.resFlags = 0;
  123. if ( r_stretched.GetBool() )
  124. info.resFlags |= kRes_Stretched;
  125. WindowRef prefWindow;
  126. if ( CreateGameDisplayPreferencesDialog( &info, &prefWindow ) == noErr ) {
  127. if ( RunGameDisplayPreferencesDialog( &info, prefWindow ) == noErr ) {
  128. cvarSystem->SetCVarBool( "r_fullscreen", info.mode == kFullScreen );
  129. int i = 0;
  130. int r_mode = -1;
  131. while ( r_mode == -1 && R_GetModeInfo( &w, &h, i ) ) {
  132. if ( w == info.width && h == info.height )
  133. r_mode = i;
  134. i++;
  135. }
  136. cvarSystem->SetCVarInteger( "r_mode", r_mode );
  137. if ( r_mode == -1 ) {
  138. cvarSystem->SetCVarInteger( "r_customWidth", info.width );
  139. cvarSystem->SetCVarInteger( "r_customHeight", info.height );
  140. }
  141. float r = (float) info.width / (float) info.height;
  142. if ( r > 1.7f )
  143. cvarSystem->SetCVarInteger( "r_aspectRatio", 1 ); // 16:9
  144. else if ( r > 1.55f )
  145. cvarSystem->SetCVarInteger( "r_aspectRatio", 2 ); // 16:10
  146. else
  147. cvarSystem->SetCVarInteger( "r_aspectRatio", 0 ); // 4:3
  148. r_stretched.SetBool( info.resFlags & kRes_Stretched );
  149. cvarSystem->SetCVarInteger( "r_screen", GetScreenIndexForDisplayID( info.displayID ) );
  150. cvarSystem->SetCVarInteger( "r_minDisplayRefresh", (int)FixedToFloat( info.frequency ) );
  151. cvarSystem->SetCVarInteger( "r_maxDisplayRefresh", (int)FixedToFloat( info.frequency ) );
  152. }
  153. else {
  154. Sys_Quit();
  155. }
  156. }
  157. }
  158. useOpenAL = CFPreferencesGetAppBooleanValue (kPref_PrefsDialogOpenAL, kCFPreferencesCurrentApplication, &keyFound);
  159. if ( keyFound && useOpenAL ) {
  160. cvarSystem->SetCVarInteger( "com_asyncSound", 1 );
  161. cvarSystem->SetCVarInteger( "s_useOpenAL", 1 );
  162. }
  163. else {
  164. cvarSystem->SetCVarInteger( "com_asyncSound", 2 );
  165. cvarSystem->SetCVarInteger( "s_useOpenAL", 0 );
  166. }
  167. }
  168. #pragma mark -
  169. #define EnablePopupMenuItem(inControl,inMenuItem) EnableMenuItem(GetControlPopupMenuRef(inControl),inMenuItem)
  170. #define DisablePopupMenuItem(inControl,inMenuItem) DisableMenuItem(GetControlPopupMenuRef(inControl),inMenuItem)
  171. #define IsPopupMenuItemEnabled(inControl,inMenuItem) IsMenuItemEnabled(GetControlPopupMenuRef(inControl),inMenuItem)
  172. // Command IDs used in the NIB file
  173. enum
  174. {
  175. kCmdFullscreen = 'Full',
  176. kCmdInAWindow = 'Wind',
  177. kCmdResolution = 'Reso',
  178. kCmdRefreshRate = 'Refr',
  179. kCmdChooseMonitors = 'Moni',
  180. };
  181. // Control IDs used in the NIB file
  182. static const ControlID kFullscreenBtn = { 'PREF', 1 };
  183. static const ControlID kInAWindowBtn = { 'PREF', 2 };
  184. static const ControlID kResolutionPopup = { 'PREF', 3 };
  185. static const ControlID kRefreshRatePopup = { 'PREF', 4 };
  186. static const ControlID kChooseMonitorsBtn = { 'PREF', 5 };
  187. static const ControlID kAlwaysBtn = { 'PREF', 6 };
  188. static const ControlID kOpenALBtn = { 'PREF', 7 };
  189. struct Res
  190. {
  191. int width;
  192. int height;
  193. int depth;
  194. UInt32 resFlags;
  195. };
  196. static bool operator< (const Res& a, const Res& b)
  197. {
  198. if (a.width == b.width)
  199. {
  200. if (a.height == b.height)
  201. {
  202. if (a.resFlags == b.resFlags)
  203. {
  204. return (a.depth < b.depth);
  205. }
  206. return (a.resFlags < b.resFlags);
  207. }
  208. return (a.height < b.height);
  209. }
  210. return (a.width < b.width);
  211. }
  212. inline Res MakeRes(int width, int height, int depth)
  213. {
  214. Res temp = { width, height, depth, 0 };
  215. return temp;
  216. }
  217. inline Res MakeRes(int width, int height, int depth, UInt32 resFlags)
  218. {
  219. Res temp = { width, height, depth, resFlags };
  220. return temp;
  221. }
  222. static bool ValidDisplayID (CGDirectDisplayID inDisplayID)
  223. {
  224. unsigned int i;
  225. CGDisplayErr err;
  226. CGDisplayCount count;
  227. err = CGGetActiveDisplayList(0, NULL, &count);
  228. if (noErr == err)
  229. {
  230. CGDirectDisplayID displays[count];
  231. err = CGGetActiveDisplayList(count, displays, &count);
  232. if (noErr == err)
  233. {
  234. for ( i = 0; i < count; i++)
  235. if (displays[i] == inDisplayID)
  236. return true;
  237. }
  238. }
  239. return false;
  240. }
  241. static int BuildResolutionList(CGDirectDisplayID inDisplayID, Res *ioList, ValidModeCallbackProc inCallback)
  242. {
  243. std::set<Res> modes;
  244. int i, total = 0;
  245. if (inDisplayID == (CGDirectDisplayID)-1) // special case, not associated with any display
  246. {
  247. Res stdModes[] = { { 640, 480 }, { 800, 600 }, { 1024, 768 }, { 1152, 768 },
  248. { 1280, 854 }, { 1280, 960 }, { 1280, 1024 }, { 1440, 900 } };
  249. total = sizeof(stdModes) / sizeof(Res);
  250. for (i = 0; i < total; i++)
  251. {
  252. if (inCallback == NULL || inCallback(inDisplayID, stdModes[i].width, stdModes[i].height, 32, 0))
  253. modes.insert( MakeRes(stdModes[i].width, stdModes[i].height, 32) );
  254. }
  255. }
  256. else
  257. {
  258. CGDirectDisplayID displayID = inDisplayID ? inDisplayID : kCGDirectMainDisplay;
  259. CFArrayRef modeArrayRef = CGDisplayAvailableModes(displayID);
  260. CFIndex numModes = CFArrayGetCount(modeArrayRef);
  261. for (i = 0; i < numModes; i++)
  262. {
  263. CFDictionaryRef modeRef = (CFDictionaryRef)CFArrayGetValueAtIndex(modeArrayRef, i);
  264. long value = 0;
  265. CFNumberRef valueRef;
  266. Boolean success;
  267. valueRef = (CFNumberRef)CFDictionaryGetValue(modeRef, kCGDisplayBitsPerPixel);
  268. success = CFNumberGetValue(valueRef, kCFNumberLongType, &value);
  269. int depth = value;
  270. if (depth != 32) continue;
  271. valueRef = (CFNumberRef)CFDictionaryGetValue(modeRef, kCGDisplayWidth);
  272. success = CFNumberGetValue(valueRef, kCFNumberLongType, &value);
  273. int width = value;
  274. valueRef = (CFNumberRef)CFDictionaryGetValue(modeRef, kCGDisplayHeight);
  275. success = CFNumberGetValue(valueRef, kCFNumberLongType, &value);
  276. int height = value;
  277. UInt32 resFlags = 0;
  278. CFBooleanRef boolRef;
  279. if (CFDictionaryGetValueIfPresent (modeRef, kCGDisplayModeIsStretched, (const void **)&boolRef))
  280. if (CFBooleanGetValue (boolRef))
  281. resFlags |= kRes_Stretched;
  282. if (inCallback)
  283. success = inCallback(displayID, width, height, depth, 0);
  284. else
  285. success = true;
  286. if (success)
  287. modes.insert(MakeRes(width, height, depth, resFlags));
  288. }
  289. }
  290. total = modes.size();
  291. if (ioList)
  292. {
  293. std::set<Res>::iterator it = modes.begin();
  294. for (i = 0; it != modes.end(); i++)
  295. ioList[i] = *it++;
  296. }
  297. return total;
  298. }
  299. static void BuildRefreshRates(CGDirectDisplayID inDisplayID, int inWidth, int inHeight, std::list<Fixed>* inList, ValidModeCallbackProc inCallback)
  300. {
  301. CGDirectDisplayID displayID = inDisplayID ? inDisplayID : kCGDirectMainDisplay;
  302. CFArrayRef modeArrayRef = CGDisplayAvailableModes(displayID);
  303. CFIndex numModes = CFArrayGetCount(modeArrayRef);
  304. inList->clear();
  305. for (int i = 0; i < numModes; i++)
  306. {
  307. CFDictionaryRef modeRef = (CFDictionaryRef)CFArrayGetValueAtIndex(modeArrayRef, i);
  308. long value = 0;
  309. CFNumberRef valueRef;
  310. Boolean success;
  311. valueRef = (CFNumberRef)CFDictionaryGetValue(modeRef, kCGDisplayBitsPerPixel);
  312. success = CFNumberGetValue(valueRef, kCFNumberLongType, &value);
  313. int depth = value;
  314. if (depth != 32) continue;
  315. valueRef = (CFNumberRef)CFDictionaryGetValue(modeRef, kCGDisplayWidth);
  316. success = CFNumberGetValue(valueRef, kCFNumberLongType, &value);
  317. int width = value;
  318. valueRef = (CFNumberRef)CFDictionaryGetValue(modeRef, kCGDisplayHeight);
  319. success = CFNumberGetValue(valueRef, kCFNumberLongType, &value);
  320. int height = value;
  321. if (width == inWidth && height == inHeight)
  322. {
  323. double freqDouble;
  324. valueRef = (CFNumberRef)CFDictionaryGetValue(modeRef, kCGDisplayRefreshRate);
  325. success = CFNumberGetValue(valueRef, kCFNumberDoubleType, &freqDouble);
  326. Fixed freq = FloatToFixed(freqDouble);
  327. if (inCallback)
  328. success = inCallback(displayID, width, height, depth, freq);
  329. else
  330. success = true;
  331. if (success)
  332. inList->push_back(freq);
  333. }
  334. }
  335. // Disallow 0, which we reserve to mean "automatic"
  336. inList->remove(0);
  337. inList->sort();
  338. // Remove duplicates - yes they can occur.
  339. inList->unique();
  340. }
  341. static void BuildRefreshPopupButton(ControlRef inControl, std::list<Fixed>* inList)
  342. {
  343. MenuRef menu = GetControlPopupMenuRef(inControl);
  344. assert(menu);
  345. if (!menu) return;
  346. // The menu has two permanent items - "Auto" & a divider line. Delete everything else.
  347. DeleteMenuItems(menu, 3, CountMenuItems(menu)-2);
  348. for (std::list<Fixed>::const_iterator iter = inList->begin(); iter != inList->end(); ++iter)
  349. {
  350. float value = FixedToFloat(*iter);
  351. CFStringRef menuString = CFStringCreateWithFormat (kCFAllocatorDefault, 0, CFSTR("%g Hz"), value);
  352. InsertMenuItemTextWithCFString(menu, menuString, CountMenuItems(menu), 0, 0);
  353. }
  354. SetControlMaximum(inControl, CountMenuItems(menu));
  355. }
  356. static SInt32 FindRefreshPopupMenuItem(std::list<Fixed>* inList, Fixed inFrequency)
  357. {
  358. SInt32 index = 3; // skip over the "Auto" and divider ine
  359. for (std::list<Fixed>::const_iterator iter = inList->begin(); iter != inList->end(); ++iter)
  360. {
  361. if (*iter == inFrequency)
  362. return index;
  363. index++;
  364. }
  365. return 1; // Return the "Automatic" item if we didn't find a match
  366. }
  367. static void BuildResolutionPopupButton(ControlRef inControl, CGDirectDisplayID inDisplayID, ValidModeCallbackProc inCallback)
  368. {
  369. // Get the list of valid resolutions
  370. int count = BuildResolutionList(inDisplayID, NULL, inCallback);
  371. Res resList[count];
  372. BuildResolutionList(inDisplayID, resList, inCallback);
  373. // Clear the menu
  374. MenuRef menu = GetControlPopupMenuRef(inControl);
  375. assert(menu);
  376. if (!menu) return;
  377. DeleteMenuItems(menu, 1, CountMenuItems(menu));
  378. OSStatus err;
  379. while (count--)
  380. {
  381. CFStringRef menuString = CFStringCreateWithFormat (kCFAllocatorDefault, 0, CFSTR("%d x %d %@"),
  382. resList[count].width, resList[count].height, (resList[count].resFlags & kRes_Stretched) ? CFSTR("(Stretched)") : CFSTR(""));
  383. InsertMenuItemTextWithCFString (menu, menuString, 0, 0, 0);
  384. err = SetMenuItemProperty (menu, 1, kAppCreator, 'Res ', sizeof(resList[count]), &resList[count]);
  385. }
  386. SetControlMaximum(inControl, CountMenuItems(menu));
  387. }
  388. static void GetResolutionFromPopupMenuItem(ControlRef inControl, MenuItemIndex inItem, int *outX, int *outY, int *outDepth, UInt32 *outResFlags)
  389. {
  390. MenuRef menu = GetControlPopupMenuRef(inControl);
  391. Res res;
  392. OSStatus err;
  393. err = GetMenuItemProperty (menu, inItem, kAppCreator, 'Res ', sizeof(res), NULL, &res);
  394. if (!err)
  395. {
  396. *outX = res.width;
  397. *outY = res.height;
  398. *outResFlags = res.resFlags;
  399. *outDepth = 32;
  400. }
  401. }
  402. static void AdjustResolutionPopupMenu(ControlRef inControl, CGDirectDisplayID inDisplayID, bool isFullscreen, int& screenwidth, int& screenheight, int& screendepth, UInt32& screenResFlags)
  403. {
  404. int screenX = INT_MAX, screenY = INT_MAX;
  405. // In windowed mode, you have to disable resolutions that are larger than the current screen size
  406. if (!isFullscreen)
  407. {
  408. screenX = (int)CGDisplayPixelsWide(inDisplayID);
  409. screenY = (int)CGDisplayPixelsHigh(inDisplayID);
  410. }
  411. MenuRef menu = GetControlPopupMenuRef(inControl);
  412. int resX, resY, depth;
  413. UInt32 resFlags;
  414. int count = CountMenuItems(menu);
  415. int item;
  416. for( item = 1; item <= count; item++)
  417. {
  418. GetResolutionFromPopupMenuItem(inControl, item, &resX, &resY, &depth, &resFlags);
  419. if (screenX < resX || screenY < resY)
  420. DisablePopupMenuItem(inControl, item);
  421. else
  422. EnablePopupMenuItem(inControl, item);
  423. if (resX == screenwidth && resY == screenheight && depth == screendepth && resFlags == screenResFlags)
  424. SetControlValue(inControl, item);
  425. }
  426. // If we just disabled the current item, then choose something else.
  427. if (!IsPopupMenuItemEnabled(inControl, GetControlValue (inControl)))
  428. {
  429. for(item = 1; item <= count; item++)
  430. {
  431. if (IsPopupMenuItemEnabled(inControl, item))
  432. {
  433. SetControlValue(inControl, item);
  434. GetResolutionFromPopupMenuItem(inControl, item, &screenwidth, &screenheight, &screendepth, &screenResFlags);
  435. break;
  436. }
  437. }
  438. }
  439. }
  440. static void AdjustDisplayControls(PrefInfo *prefInfo)
  441. {
  442. // Build new resolution popup and select appropriate resolution
  443. if ((prefInfo->prefGameDisplayMode != kFullScreen))
  444. {
  445. BuildResolutionPopupButton(prefInfo->resolutionPopup, (CGDirectDisplayID)-1, prefInfo->callback);
  446. if (prefInfo->multiMonitor)
  447. EnableControl(prefInfo->chooseMonitorsBtn);
  448. }
  449. else
  450. {
  451. BuildResolutionPopupButton(prefInfo->resolutionPopup, prefInfo->prefDisplayID, prefInfo->callback);
  452. if (prefInfo->multiMonitor)
  453. EnableControl(prefInfo->chooseMonitorsBtn);
  454. }
  455. AdjustResolutionPopupMenu(prefInfo->resolutionPopup, prefInfo->prefDisplayID,
  456. prefInfo->prefGameDisplayMode == kFullScreen,
  457. prefInfo->prefWidth, prefInfo->prefHeight, prefInfo->prefDepth, prefInfo->prefResFlags);
  458. // Build new refresh popup and select appropriate rate
  459. BuildRefreshRates(prefInfo->prefDisplayID, prefInfo->prefWidth, prefInfo->prefHeight,
  460. &prefInfo->refreshRates, prefInfo->callback);
  461. BuildRefreshPopupButton(prefInfo->refreshRatePopup, &prefInfo->refreshRates);
  462. if (prefInfo->refreshRates.size() == 0)
  463. { // No refresh rates, so pick Auto
  464. prefInfo->freqMenuIndex = 1;
  465. prefInfo->prefFrequency = 0;
  466. }
  467. else
  468. {
  469. prefInfo->freqMenuIndex = FindRefreshPopupMenuItem(&prefInfo->refreshRates, prefInfo->prefFrequency);
  470. if (prefInfo->freqMenuIndex == 1)
  471. prefInfo->prefFrequency = 0; // just in case FindRefreshPopupMenuItem didn't find prefInfo->prefFrequency
  472. }
  473. SetControlValue (prefInfo->refreshRatePopup, prefInfo->freqMenuIndex);
  474. // Disable refresh rate if NOT fullscreen
  475. if ((prefInfo->prefGameDisplayMode != kFullScreen) || (prefInfo->refreshRates.size() == 0))
  476. DisableControl (prefInfo->refreshRatePopup);
  477. else
  478. EnableControl (prefInfo->refreshRatePopup);
  479. }
  480. #pragma mark -
  481. static pascal OSStatus PrefHandler( EventHandlerCallRef inHandler, EventRef inEvent, void* inUserData )
  482. {
  483. #pragma unused( inHandler )
  484. HICommand cmd;
  485. OSStatus result = eventNotHandledErr;
  486. PrefInfo* prefInfo = (PrefInfo*)inUserData;
  487. // The direct object for a 'process commmand' event is the HICommand.
  488. // Extract it here and switch off the command ID.
  489. GetEventParameter( inEvent, kEventParamDirectObject, typeHICommand, NULL, sizeof( cmd ), NULL, &cmd );
  490. switch ( cmd.commandID )
  491. {
  492. case kHICommandOK:
  493. prefInfo->okPressed = true;
  494. prefInfo->prefAlways = GetControlValue (prefInfo->alwaysBtn);
  495. prefInfo->prefOpenAL = GetControlValue (prefInfo->openALBtn);
  496. CFPreferencesSetAppValue (kPref_PrefsDialogAlways,
  497. prefInfo->prefAlways ? kCFBooleanTrue : kCFBooleanFalse,
  498. kCFPreferencesCurrentApplication);
  499. CFPreferencesSetAppValue (kPref_PrefsDialogOpenAL,
  500. prefInfo->prefOpenAL ? kCFBooleanTrue : kCFBooleanFalse,
  501. kCFPreferencesCurrentApplication);
  502. CFPreferencesAppSynchronize (kCFPreferencesCurrentApplication);
  503. QuitAppModalLoopForWindow( prefInfo->window );
  504. result = noErr;
  505. break;
  506. case kHICommandCancel:
  507. prefInfo->okPressed = false;
  508. QuitAppModalLoopForWindow( prefInfo->window );
  509. result = noErr;
  510. break;
  511. case kCmdFullscreen:
  512. case kCmdInAWindow:
  513. if (cmd.commandID == kCmdFullscreen)
  514. prefInfo->prefGameDisplayMode = kFullScreen;
  515. else
  516. prefInfo->prefGameDisplayMode = kWindow;
  517. SetControlValue (prefInfo->fullscreenBtn, prefInfo->prefGameDisplayMode == kFullScreen);
  518. SetControlValue (prefInfo->inAWindowBtn, 1 - (prefInfo->prefGameDisplayMode == kFullScreen));
  519. if (prefInfo->prefGameDisplayMode == kFullScreen)
  520. EnableControl (prefInfo->refreshRatePopup);
  521. else
  522. DisableControl (prefInfo->refreshRatePopup);
  523. if (prefInfo->multiMonitor)
  524. EnableControl (prefInfo->chooseMonitorsBtn);
  525. else
  526. DisableControl (prefInfo->chooseMonitorsBtn);
  527. // Adjust resolutions, refresh rates
  528. AdjustDisplayControls(prefInfo);
  529. result = noErr;
  530. break;
  531. case kCmdChooseMonitors:
  532. {
  533. PickMonitor((DisplayIDType*)&prefInfo->prefDisplayID, prefInfo->window);
  534. // Adjust resolutions, refresh rates for potentially new display ID
  535. AdjustDisplayControls(prefInfo);
  536. break;
  537. }
  538. case kCmdResolution:
  539. {
  540. // Pick a new resolution
  541. int item = GetControlValue(prefInfo->resolutionPopup);
  542. GetResolutionFromPopupMenuItem(prefInfo->resolutionPopup, item, &prefInfo->prefWidth, &prefInfo->prefHeight, &prefInfo->prefDepth, &prefInfo->prefResFlags);
  543. // Adjust refresh menu
  544. BuildRefreshRates(prefInfo->prefDisplayID, prefInfo->prefWidth, prefInfo->prefHeight, &prefInfo->refreshRates, prefInfo->callback);
  545. BuildRefreshPopupButton(prefInfo->refreshRatePopup, &prefInfo->refreshRates);
  546. prefInfo->freqMenuIndex = FindRefreshPopupMenuItem(&prefInfo->refreshRates, prefInfo->prefFrequency);
  547. if (prefInfo->freqMenuIndex == 1)
  548. prefInfo->prefFrequency = 0; // just in case FindRefreshPopupMenuItem didn't find prefInfo->prefFrequency
  549. SetControlValue (prefInfo->refreshRatePopup, prefInfo->freqMenuIndex);
  550. // Disable refresh rate if NOT fullscreen
  551. if ((prefInfo->prefGameDisplayMode != kFullScreen) || (prefInfo->refreshRates.size() == 0))
  552. DisableControl (prefInfo->refreshRatePopup);
  553. else
  554. EnableControl (prefInfo->refreshRatePopup);
  555. break;
  556. }
  557. case kCmdRefreshRate:
  558. {
  559. // Keep prefInfo->prefFrequency updated for the other controls to reference
  560. prefInfo->freqMenuIndex = GetControlValue (prefInfo->refreshRatePopup);
  561. if (prefInfo->freqMenuIndex == 1)
  562. prefInfo->prefFrequency = 0;
  563. else
  564. {
  565. std::list<Fixed>::const_iterator iter = prefInfo->refreshRates.begin();
  566. for (int i = 0; i < prefInfo->freqMenuIndex-3; i++)
  567. iter++;
  568. prefInfo->prefFrequency = *iter;
  569. }
  570. break;
  571. }
  572. }
  573. return result;
  574. }
  575. #pragma mark -
  576. static DEFINE_ONE_SHOT_HANDLER_GETTER(PrefHandler)
  577. OSStatus CreateGameDisplayPreferencesDialog(const GameDisplayInfo *inGDInfo,
  578. WindowRef *outWindow, ValidModeCallbackProc inCallback)
  579. {
  580. OSStatus err = noErr;
  581. // Build up a structure to pass to the window handler we are about
  582. // to install. We store the window itself, as well as the original
  583. // states of our settings. We use this to revert if the user clicks
  584. // the cancel button.
  585. static PrefInfo prefInfo;
  586. prefInfo.prefGameDisplayMode = inGDInfo->mode;
  587. prefInfo.prefDisplayID = inGDInfo->displayID;
  588. prefInfo.prefWidth = inGDInfo->width;
  589. prefInfo.prefHeight = inGDInfo->height;
  590. prefInfo.prefDepth = inGDInfo->depth;
  591. prefInfo.prefFrequency = inGDInfo->frequency;
  592. prefInfo.prefResFlags = inGDInfo->resFlags;
  593. prefInfo.window = NULL;
  594. prefInfo.okPressed = false;
  595. Boolean result;
  596. Boolean keyFound;
  597. result = CFPreferencesGetAppBooleanValue (kPref_PrefsDialogAlways, kCFPreferencesCurrentApplication, &keyFound);
  598. prefInfo.prefAlways = result && keyFound;
  599. result = CFPreferencesGetAppBooleanValue (kPref_PrefsDialogOpenAL, kCFPreferencesCurrentApplication, &keyFound);
  600. prefInfo.prefOpenAL = result && keyFound;
  601. prefInfo.callback = inCallback;
  602. // If DoPreferences is called at the start of the game, prefInfo.prefDisplayID needs to be checked
  603. // to see if it is still a valid display ID.
  604. if (!ValidDisplayID(prefInfo.prefDisplayID))
  605. prefInfo.prefDisplayID = kCGDirectMainDisplay; // revert to main
  606. // Fetch the dialog
  607. IBNibRef aslNib;
  608. CFBundleRef theBundle = CFBundleGetMainBundle();
  609. err = CreateNibReferenceWithCFBundle(theBundle, CFSTR("ASLCore"), &aslNib);
  610. err = ::CreateWindowFromNib(aslNib, CFSTR( "Preferences" ), &prefInfo.window );
  611. if (err != noErr)
  612. return err;
  613. SetWRefCon(prefInfo.window, (long)&prefInfo);
  614. // Locate all the controls
  615. GetControlByID( prefInfo.window, &kFullscreenBtn, &prefInfo.fullscreenBtn ); assert(prefInfo.fullscreenBtn);
  616. GetControlByID( prefInfo.window, &kInAWindowBtn, &prefInfo.inAWindowBtn ); assert(prefInfo.inAWindowBtn);
  617. GetControlByID( prefInfo.window, &kResolutionPopup, &prefInfo.resolutionPopup ); assert(prefInfo.resolutionPopup);
  618. GetControlByID( prefInfo.window, &kRefreshRatePopup, &prefInfo.refreshRatePopup ); assert(prefInfo.refreshRatePopup);
  619. GetControlByID( prefInfo.window, &kChooseMonitorsBtn, &prefInfo.chooseMonitorsBtn ); assert(prefInfo.chooseMonitorsBtn);
  620. GetControlByID( prefInfo.window, &kAlwaysBtn, &prefInfo.alwaysBtn ); assert(prefInfo.alwaysBtn);
  621. GetControlByID( prefInfo.window, &kOpenALBtn, &prefInfo.openALBtn ); assert(prefInfo.openALBtn);
  622. // Disable the "choose monitor" button if we've only got one to pick from
  623. prefInfo.multiMonitor = CanUserPickMonitor();
  624. if (!prefInfo.multiMonitor)
  625. {
  626. DisableControl (prefInfo.chooseMonitorsBtn);
  627. prefInfo.prefDisplayID = 0;
  628. }
  629. // Prepare the resolutions and refresh rates popup menus
  630. AdjustDisplayControls(&prefInfo);
  631. // Set up the controls
  632. SetControlValue (prefInfo.refreshRatePopup, prefInfo.freqMenuIndex);
  633. SetControlValue (prefInfo.fullscreenBtn, prefInfo.prefGameDisplayMode == kFullScreen);
  634. SetControlValue (prefInfo.inAWindowBtn, prefInfo.prefGameDisplayMode == kWindow);
  635. SetControlValue (prefInfo.alwaysBtn, prefInfo.prefAlways);
  636. SetControlValue (prefInfo.openALBtn, prefInfo.prefOpenAL);
  637. // Create our UPP and install the handler.
  638. EventTypeSpec cmdEvent = { kEventClassCommand, kEventCommandProcess };
  639. EventHandlerUPP handler = GetPrefHandlerUPP();
  640. InstallWindowEventHandler( prefInfo.window, handler, 1, &cmdEvent, &prefInfo, NULL );
  641. // Position and show the window
  642. RepositionWindow( prefInfo.window, NULL, kWindowAlertPositionOnMainScreen );
  643. if (outWindow)
  644. *outWindow = prefInfo.window;
  645. return err;
  646. }
  647. //------------------------------------------------------------------------------------
  648. // ¥ RunGameDisplayPreferencesDialog
  649. //------------------------------------------------------------------------------------
  650. // Runs the Mac-specific preferences dialog.
  651. OSStatus RunGameDisplayPreferencesDialog(GameDisplayInfo *outGDInfo, WindowRef inWindow)
  652. {
  653. PrefInfo *prefInfo = (PrefInfo*)GetWRefCon(inWindow);
  654. ShowWindow( inWindow );
  655. // Now we run modally. We will remain here until the PrefHandler
  656. // calls QuitAppModalLoopForWindow if the user clicks OK or
  657. // Cancel.
  658. RunAppModalLoopForWindow( inWindow );
  659. // OK, we're done. Dispose of our window.
  660. // TODO: Are we supposed to uninstall event handlers?
  661. DisposeWindow( inWindow );
  662. // Return settings to caller
  663. if (prefInfo->okPressed)
  664. {
  665. outGDInfo->mode = prefInfo->prefGameDisplayMode;
  666. outGDInfo->width = prefInfo->prefWidth;
  667. outGDInfo->height = prefInfo->prefHeight;
  668. outGDInfo->depth = prefInfo->prefDepth;
  669. outGDInfo->frequency = prefInfo->prefFrequency;
  670. outGDInfo->resFlags = prefInfo->prefResFlags;
  671. outGDInfo->displayID = prefInfo->prefDisplayID;
  672. }
  673. return prefInfo->okPressed ? noErr : userCanceledErr;
  674. }