/* =========================================================================== Doom 3 GPL Source Code Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company. This file is part of the Doom 3 GPL Source Code ("Doom 3 Source Code"). Doom 3 Source Code is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Doom 3 Source Code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Doom 3 Source Code. If not, see . 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. 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. =========================================================================== */ #include "../../idlib/precompiled.h" #include #include "PreferencesDialog.h" #include "PickMonitor.h" #include #include static idCVar r_stretched( "r_stretched", "0", CVAR_ARCHIVE | CVAR_BOOL, "Used stretched resolution" ); #define kPref_PrefsDialogAlways CFSTR("PrefsDialogAlways") #define kPref_PrefsDialogOpenAL CFSTR("UseOpenAL") #ifndef kAppCreator #define kAppCreator 'DOM3' // Creator type #endif const UInt32 kRes_Stretched = (1 << 0); // set if the resolution is a stretched mode (kCGDisplayModeIsStretched) const UInt32 kRes_Safe = (1 << 1); // ¥¥¥Ê(currently unused) set if the resolution is safe (kCGDisplayModeIsSafeForHardware) // Data to be presented and edited in the prefs dialog struct PrefInfo { // prefs values GameDisplayMode prefGameDisplayMode; CGDirectDisplayID prefDisplayID; int prefWidth; int prefHeight; int prefDepth; Fixed prefFrequency; UInt32 prefResFlags; Boolean prefAlways; Boolean prefOpenAL; bool okPressed; // Set to true if the user pressed the OK button // The following are private data passed from GameDisplayPreferencesDialog() to it's command handler. WindowRef window; ControlRef fullscreenBtn; ControlRef inAWindowBtn; ControlRef resolutionPopup; ControlRef refreshRatePopup; ControlRef chooseMonitorsBtn; ControlRef alwaysBtn; ControlRef openALBtn; ValidModeCallbackProc callback; // To validate display modes bool multiMonitor; // Does user have multiple monitors std::list refreshRates; // List of refresh rates available for the selected monitor SInt32 freqMenuIndex; }; bool R_GetModeInfo( int *width, int *height, int mode ); static int GetScreenIndexForDisplayID( CGDirectDisplayID inDisplayID ) { unsigned int i; OSErr err; int r_screen = -1; CGDisplayCount count; err = CGGetActiveDisplayList(0, NULL, &count); if (noErr == err) { CGDirectDisplayID displays[count]; err = CGGetActiveDisplayList(count, displays, &count); if (noErr == err) { for ( i = 0; i < count; i++) if (displays[i] == inDisplayID) r_screen = i; } } return r_screen; } static CGDirectDisplayID GetDisplayIDForScreenIndex( int inScreenIndex ) { OSErr err; int r_screen = -1; CGDisplayCount count; err = CGGetActiveDisplayList(0, NULL, &count); if (noErr == err) { CGDirectDisplayID displays[count]; err = CGGetActiveDisplayList(count, displays, &count); if (noErr == err) { if ( inScreenIndex >= 0 && inScreenIndex <= count ) return displays[inScreenIndex]; } } return (CGDirectDisplayID)r_screen; } void Sys_DoPreferences( void ) { // An NSKeyDown event is not fired if the user holds down Cmd during startup. // Cmd is treated purely as a modifier. To capture the user // holding down Cmd, you would need to override NSApplication's // keydown handler. That's overkill for a single check at // startup, use the Carbon GetKeys approach. unsigned char km[16]; const int kMacKeyCodeCommand = 0x37; KeyMap *keymap = (KeyMap*)&km; GetKeys(*keymap); Boolean prefAways, keyFound, useOpenAL; prefAways = CFPreferencesGetAppBooleanValue ( kPref_PrefsDialogAlways, kCFPreferencesCurrentApplication, &keyFound ); bool fAlways = prefAways && keyFound; if ( fAlways || ( km[kMacKeyCodeCommand>>3] >> ( kMacKeyCodeCommand & 7 ) ) & 1 ) { GameDisplayInfo info; info.mode = cvarSystem->GetCVarBool( "r_fullscreen" ) ? kFullScreen : kWindow; info.displayID = GetDisplayIDForScreenIndex( cvarSystem->GetCVarInteger( "r_screen" ) ); int w = 800, h = 600; R_GetModeInfo( &w, &h, cvarSystem->GetCVarInteger( "r_mode" ) ); info.width = w; info.height = h; info.depth = 32; info.frequency = cvarSystem->GetCVarInteger( "r_maxDisplayRefresh" ); info.windowLoc.x = 0; info.windowLoc.y = 0; info.flags = 0; info.resFlags = 0; if ( r_stretched.GetBool() ) info.resFlags |= kRes_Stretched; WindowRef prefWindow; if ( CreateGameDisplayPreferencesDialog( &info, &prefWindow ) == noErr ) { if ( RunGameDisplayPreferencesDialog( &info, prefWindow ) == noErr ) { cvarSystem->SetCVarBool( "r_fullscreen", info.mode == kFullScreen ); int i = 0; int r_mode = -1; while ( r_mode == -1 && R_GetModeInfo( &w, &h, i ) ) { if ( w == info.width && h == info.height ) r_mode = i; i++; } cvarSystem->SetCVarInteger( "r_mode", r_mode ); if ( r_mode == -1 ) { cvarSystem->SetCVarInteger( "r_customWidth", info.width ); cvarSystem->SetCVarInteger( "r_customHeight", info.height ); } float r = (float) info.width / (float) info.height; if ( r > 1.7f ) cvarSystem->SetCVarInteger( "r_aspectRatio", 1 ); // 16:9 else if ( r > 1.55f ) cvarSystem->SetCVarInteger( "r_aspectRatio", 2 ); // 16:10 else cvarSystem->SetCVarInteger( "r_aspectRatio", 0 ); // 4:3 r_stretched.SetBool( info.resFlags & kRes_Stretched ); cvarSystem->SetCVarInteger( "r_screen", GetScreenIndexForDisplayID( info.displayID ) ); cvarSystem->SetCVarInteger( "r_minDisplayRefresh", (int)FixedToFloat( info.frequency ) ); cvarSystem->SetCVarInteger( "r_maxDisplayRefresh", (int)FixedToFloat( info.frequency ) ); } else { Sys_Quit(); } } } useOpenAL = CFPreferencesGetAppBooleanValue (kPref_PrefsDialogOpenAL, kCFPreferencesCurrentApplication, &keyFound); if ( keyFound && useOpenAL ) { cvarSystem->SetCVarInteger( "com_asyncSound", 1 ); cvarSystem->SetCVarInteger( "s_useOpenAL", 1 ); } else { cvarSystem->SetCVarInteger( "com_asyncSound", 2 ); cvarSystem->SetCVarInteger( "s_useOpenAL", 0 ); } } #define EnablePopupMenuItem(inControl,inMenuItem) EnableMenuItem(GetControlPopupMenuRef(inControl),inMenuItem) #define DisablePopupMenuItem(inControl,inMenuItem) DisableMenuItem(GetControlPopupMenuRef(inControl),inMenuItem) #define IsPopupMenuItemEnabled(inControl,inMenuItem) IsMenuItemEnabled(GetControlPopupMenuRef(inControl),inMenuItem) // Command IDs used in the NIB file enum { kCmdFullscreen = 'Full', kCmdInAWindow = 'Wind', kCmdResolution = 'Reso', kCmdRefreshRate = 'Refr', kCmdChooseMonitors = 'Moni', }; // Control IDs used in the NIB file static const ControlID kFullscreenBtn = { 'PREF', 1 }; static const ControlID kInAWindowBtn = { 'PREF', 2 }; static const ControlID kResolutionPopup = { 'PREF', 3 }; static const ControlID kRefreshRatePopup = { 'PREF', 4 }; static const ControlID kChooseMonitorsBtn = { 'PREF', 5 }; static const ControlID kAlwaysBtn = { 'PREF', 6 }; static const ControlID kOpenALBtn = { 'PREF', 7 }; struct Res { int width; int height; int depth; UInt32 resFlags; }; static bool operator< (const Res& a, const Res& b) { if (a.width == b.width) { if (a.height == b.height) { if (a.resFlags == b.resFlags) { return (a.depth < b.depth); } return (a.resFlags < b.resFlags); } return (a.height < b.height); } return (a.width < b.width); } inline Res MakeRes(int width, int height, int depth) { Res temp = { width, height, depth, 0 }; return temp; } inline Res MakeRes(int width, int height, int depth, UInt32 resFlags) { Res temp = { width, height, depth, resFlags }; return temp; } static bool ValidDisplayID (CGDirectDisplayID inDisplayID) { unsigned int i; CGDisplayErr err; CGDisplayCount count; err = CGGetActiveDisplayList(0, NULL, &count); if (noErr == err) { CGDirectDisplayID displays[count]; err = CGGetActiveDisplayList(count, displays, &count); if (noErr == err) { for ( i = 0; i < count; i++) if (displays[i] == inDisplayID) return true; } } return false; } static int BuildResolutionList(CGDirectDisplayID inDisplayID, Res *ioList, ValidModeCallbackProc inCallback) { std::set modes; int i, total = 0; if (inDisplayID == (CGDirectDisplayID)-1) // special case, not associated with any display { Res stdModes[] = { { 640, 480 }, { 800, 600 }, { 1024, 768 }, { 1152, 768 }, { 1280, 854 }, { 1280, 960 }, { 1280, 1024 }, { 1440, 900 } }; total = sizeof(stdModes) / sizeof(Res); for (i = 0; i < total; i++) { if (inCallback == NULL || inCallback(inDisplayID, stdModes[i].width, stdModes[i].height, 32, 0)) modes.insert( MakeRes(stdModes[i].width, stdModes[i].height, 32) ); } } else { CGDirectDisplayID displayID = inDisplayID ? inDisplayID : kCGDirectMainDisplay; CFArrayRef modeArrayRef = CGDisplayAvailableModes(displayID); CFIndex numModes = CFArrayGetCount(modeArrayRef); for (i = 0; i < numModes; i++) { CFDictionaryRef modeRef = (CFDictionaryRef)CFArrayGetValueAtIndex(modeArrayRef, i); long value = 0; CFNumberRef valueRef; Boolean success; valueRef = (CFNumberRef)CFDictionaryGetValue(modeRef, kCGDisplayBitsPerPixel); success = CFNumberGetValue(valueRef, kCFNumberLongType, &value); int depth = value; if (depth != 32) continue; valueRef = (CFNumberRef)CFDictionaryGetValue(modeRef, kCGDisplayWidth); success = CFNumberGetValue(valueRef, kCFNumberLongType, &value); int width = value; valueRef = (CFNumberRef)CFDictionaryGetValue(modeRef, kCGDisplayHeight); success = CFNumberGetValue(valueRef, kCFNumberLongType, &value); int height = value; UInt32 resFlags = 0; CFBooleanRef boolRef; if (CFDictionaryGetValueIfPresent (modeRef, kCGDisplayModeIsStretched, (const void **)&boolRef)) if (CFBooleanGetValue (boolRef)) resFlags |= kRes_Stretched; if (inCallback) success = inCallback(displayID, width, height, depth, 0); else success = true; if (success) modes.insert(MakeRes(width, height, depth, resFlags)); } } total = modes.size(); if (ioList) { std::set::iterator it = modes.begin(); for (i = 0; it != modes.end(); i++) ioList[i] = *it++; } return total; } static void BuildRefreshRates(CGDirectDisplayID inDisplayID, int inWidth, int inHeight, std::list* inList, ValidModeCallbackProc inCallback) { CGDirectDisplayID displayID = inDisplayID ? inDisplayID : kCGDirectMainDisplay; CFArrayRef modeArrayRef = CGDisplayAvailableModes(displayID); CFIndex numModes = CFArrayGetCount(modeArrayRef); inList->clear(); for (int i = 0; i < numModes; i++) { CFDictionaryRef modeRef = (CFDictionaryRef)CFArrayGetValueAtIndex(modeArrayRef, i); long value = 0; CFNumberRef valueRef; Boolean success; valueRef = (CFNumberRef)CFDictionaryGetValue(modeRef, kCGDisplayBitsPerPixel); success = CFNumberGetValue(valueRef, kCFNumberLongType, &value); int depth = value; if (depth != 32) continue; valueRef = (CFNumberRef)CFDictionaryGetValue(modeRef, kCGDisplayWidth); success = CFNumberGetValue(valueRef, kCFNumberLongType, &value); int width = value; valueRef = (CFNumberRef)CFDictionaryGetValue(modeRef, kCGDisplayHeight); success = CFNumberGetValue(valueRef, kCFNumberLongType, &value); int height = value; if (width == inWidth && height == inHeight) { double freqDouble; valueRef = (CFNumberRef)CFDictionaryGetValue(modeRef, kCGDisplayRefreshRate); success = CFNumberGetValue(valueRef, kCFNumberDoubleType, &freqDouble); Fixed freq = FloatToFixed(freqDouble); if (inCallback) success = inCallback(displayID, width, height, depth, freq); else success = true; if (success) inList->push_back(freq); } } // Disallow 0, which we reserve to mean "automatic" inList->remove(0); inList->sort(); // Remove duplicates - yes they can occur. inList->unique(); } static void BuildRefreshPopupButton(ControlRef inControl, std::list* inList) { MenuRef menu = GetControlPopupMenuRef(inControl); assert(menu); if (!menu) return; // The menu has two permanent items - "Auto" & a divider line. Delete everything else. DeleteMenuItems(menu, 3, CountMenuItems(menu)-2); for (std::list::const_iterator iter = inList->begin(); iter != inList->end(); ++iter) { float value = FixedToFloat(*iter); CFStringRef menuString = CFStringCreateWithFormat (kCFAllocatorDefault, 0, CFSTR("%g Hz"), value); InsertMenuItemTextWithCFString(menu, menuString, CountMenuItems(menu), 0, 0); } SetControlMaximum(inControl, CountMenuItems(menu)); } static SInt32 FindRefreshPopupMenuItem(std::list* inList, Fixed inFrequency) { SInt32 index = 3; // skip over the "Auto" and divider ine for (std::list::const_iterator iter = inList->begin(); iter != inList->end(); ++iter) { if (*iter == inFrequency) return index; index++; } return 1; // Return the "Automatic" item if we didn't find a match } static void BuildResolutionPopupButton(ControlRef inControl, CGDirectDisplayID inDisplayID, ValidModeCallbackProc inCallback) { // Get the list of valid resolutions int count = BuildResolutionList(inDisplayID, NULL, inCallback); Res resList[count]; BuildResolutionList(inDisplayID, resList, inCallback); // Clear the menu MenuRef menu = GetControlPopupMenuRef(inControl); assert(menu); if (!menu) return; DeleteMenuItems(menu, 1, CountMenuItems(menu)); OSStatus err; while (count--) { CFStringRef menuString = CFStringCreateWithFormat (kCFAllocatorDefault, 0, CFSTR("%d x %d %@"), resList[count].width, resList[count].height, (resList[count].resFlags & kRes_Stretched) ? CFSTR("(Stretched)") : CFSTR("")); InsertMenuItemTextWithCFString (menu, menuString, 0, 0, 0); err = SetMenuItemProperty (menu, 1, kAppCreator, 'Res ', sizeof(resList[count]), &resList[count]); } SetControlMaximum(inControl, CountMenuItems(menu)); } static void GetResolutionFromPopupMenuItem(ControlRef inControl, MenuItemIndex inItem, int *outX, int *outY, int *outDepth, UInt32 *outResFlags) { MenuRef menu = GetControlPopupMenuRef(inControl); Res res; OSStatus err; err = GetMenuItemProperty (menu, inItem, kAppCreator, 'Res ', sizeof(res), NULL, &res); if (!err) { *outX = res.width; *outY = res.height; *outResFlags = res.resFlags; *outDepth = 32; } } static void AdjustResolutionPopupMenu(ControlRef inControl, CGDirectDisplayID inDisplayID, bool isFullscreen, int& screenwidth, int& screenheight, int& screendepth, UInt32& screenResFlags) { int screenX = INT_MAX, screenY = INT_MAX; // In windowed mode, you have to disable resolutions that are larger than the current screen size if (!isFullscreen) { screenX = (int)CGDisplayPixelsWide(inDisplayID); screenY = (int)CGDisplayPixelsHigh(inDisplayID); } MenuRef menu = GetControlPopupMenuRef(inControl); int resX, resY, depth; UInt32 resFlags; int count = CountMenuItems(menu); int item; for( item = 1; item <= count; item++) { GetResolutionFromPopupMenuItem(inControl, item, &resX, &resY, &depth, &resFlags); if (screenX < resX || screenY < resY) DisablePopupMenuItem(inControl, item); else EnablePopupMenuItem(inControl, item); if (resX == screenwidth && resY == screenheight && depth == screendepth && resFlags == screenResFlags) SetControlValue(inControl, item); } // If we just disabled the current item, then choose something else. if (!IsPopupMenuItemEnabled(inControl, GetControlValue (inControl))) { for(item = 1; item <= count; item++) { if (IsPopupMenuItemEnabled(inControl, item)) { SetControlValue(inControl, item); GetResolutionFromPopupMenuItem(inControl, item, &screenwidth, &screenheight, &screendepth, &screenResFlags); break; } } } } static void AdjustDisplayControls(PrefInfo *prefInfo) { // Build new resolution popup and select appropriate resolution if ((prefInfo->prefGameDisplayMode != kFullScreen)) { BuildResolutionPopupButton(prefInfo->resolutionPopup, (CGDirectDisplayID)-1, prefInfo->callback); if (prefInfo->multiMonitor) EnableControl(prefInfo->chooseMonitorsBtn); } else { BuildResolutionPopupButton(prefInfo->resolutionPopup, prefInfo->prefDisplayID, prefInfo->callback); if (prefInfo->multiMonitor) EnableControl(prefInfo->chooseMonitorsBtn); } AdjustResolutionPopupMenu(prefInfo->resolutionPopup, prefInfo->prefDisplayID, prefInfo->prefGameDisplayMode == kFullScreen, prefInfo->prefWidth, prefInfo->prefHeight, prefInfo->prefDepth, prefInfo->prefResFlags); // Build new refresh popup and select appropriate rate BuildRefreshRates(prefInfo->prefDisplayID, prefInfo->prefWidth, prefInfo->prefHeight, &prefInfo->refreshRates, prefInfo->callback); BuildRefreshPopupButton(prefInfo->refreshRatePopup, &prefInfo->refreshRates); if (prefInfo->refreshRates.size() == 0) { // No refresh rates, so pick Auto prefInfo->freqMenuIndex = 1; prefInfo->prefFrequency = 0; } else { prefInfo->freqMenuIndex = FindRefreshPopupMenuItem(&prefInfo->refreshRates, prefInfo->prefFrequency); if (prefInfo->freqMenuIndex == 1) prefInfo->prefFrequency = 0; // just in case FindRefreshPopupMenuItem didn't find prefInfo->prefFrequency } SetControlValue (prefInfo->refreshRatePopup, prefInfo->freqMenuIndex); // Disable refresh rate if NOT fullscreen if ((prefInfo->prefGameDisplayMode != kFullScreen) || (prefInfo->refreshRates.size() == 0)) DisableControl (prefInfo->refreshRatePopup); else EnableControl (prefInfo->refreshRatePopup); } static pascal OSStatus PrefHandler( EventHandlerCallRef, EventRef inEvent, void* inUserData ) { HICommand cmd; OSStatus result = eventNotHandledErr; PrefInfo* prefInfo = (PrefInfo*)inUserData; // The direct object for a 'process commmand' event is the HICommand. // Extract it here and switch off the command ID. GetEventParameter( inEvent, kEventParamDirectObject, typeHICommand, NULL, sizeof( cmd ), NULL, &cmd ); switch ( cmd.commandID ) { case kHICommandOK: prefInfo->okPressed = true; prefInfo->prefAlways = GetControlValue (prefInfo->alwaysBtn); prefInfo->prefOpenAL = GetControlValue (prefInfo->openALBtn); CFPreferencesSetAppValue (kPref_PrefsDialogAlways, prefInfo->prefAlways ? kCFBooleanTrue : kCFBooleanFalse, kCFPreferencesCurrentApplication); CFPreferencesSetAppValue (kPref_PrefsDialogOpenAL, prefInfo->prefOpenAL ? kCFBooleanTrue : kCFBooleanFalse, kCFPreferencesCurrentApplication); CFPreferencesAppSynchronize (kCFPreferencesCurrentApplication); QuitAppModalLoopForWindow( prefInfo->window ); result = noErr; break; case kHICommandCancel: prefInfo->okPressed = false; QuitAppModalLoopForWindow( prefInfo->window ); result = noErr; break; case kCmdFullscreen: case kCmdInAWindow: if (cmd.commandID == kCmdFullscreen) prefInfo->prefGameDisplayMode = kFullScreen; else prefInfo->prefGameDisplayMode = kWindow; SetControlValue (prefInfo->fullscreenBtn, prefInfo->prefGameDisplayMode == kFullScreen); SetControlValue (prefInfo->inAWindowBtn, 1 - (prefInfo->prefGameDisplayMode == kFullScreen)); if (prefInfo->prefGameDisplayMode == kFullScreen) EnableControl (prefInfo->refreshRatePopup); else DisableControl (prefInfo->refreshRatePopup); if (prefInfo->multiMonitor) EnableControl (prefInfo->chooseMonitorsBtn); else DisableControl (prefInfo->chooseMonitorsBtn); // Adjust resolutions, refresh rates AdjustDisplayControls(prefInfo); result = noErr; break; case kCmdChooseMonitors: { PickMonitor((DisplayIDType*)&prefInfo->prefDisplayID, prefInfo->window); // Adjust resolutions, refresh rates for potentially new display ID AdjustDisplayControls(prefInfo); break; } case kCmdResolution: { // Pick a new resolution int item = GetControlValue(prefInfo->resolutionPopup); GetResolutionFromPopupMenuItem(prefInfo->resolutionPopup, item, &prefInfo->prefWidth, &prefInfo->prefHeight, &prefInfo->prefDepth, &prefInfo->prefResFlags); // Adjust refresh menu BuildRefreshRates(prefInfo->prefDisplayID, prefInfo->prefWidth, prefInfo->prefHeight, &prefInfo->refreshRates, prefInfo->callback); BuildRefreshPopupButton(prefInfo->refreshRatePopup, &prefInfo->refreshRates); prefInfo->freqMenuIndex = FindRefreshPopupMenuItem(&prefInfo->refreshRates, prefInfo->prefFrequency); if (prefInfo->freqMenuIndex == 1) prefInfo->prefFrequency = 0; // just in case FindRefreshPopupMenuItem didn't find prefInfo->prefFrequency SetControlValue (prefInfo->refreshRatePopup, prefInfo->freqMenuIndex); // Disable refresh rate if NOT fullscreen if ((prefInfo->prefGameDisplayMode != kFullScreen) || (prefInfo->refreshRates.size() == 0)) DisableControl (prefInfo->refreshRatePopup); else EnableControl (prefInfo->refreshRatePopup); break; } case kCmdRefreshRate: { // Keep prefInfo->prefFrequency updated for the other controls to reference prefInfo->freqMenuIndex = GetControlValue (prefInfo->refreshRatePopup); if (prefInfo->freqMenuIndex == 1) prefInfo->prefFrequency = 0; else { std::list::const_iterator iter = prefInfo->refreshRates.begin(); for (int i = 0; i < prefInfo->freqMenuIndex-3; i++) iter++; prefInfo->prefFrequency = *iter; } break; } } return result; } static DEFINE_ONE_SHOT_HANDLER_GETTER(PrefHandler) OSStatus CreateGameDisplayPreferencesDialog(const GameDisplayInfo *inGDInfo, WindowRef *outWindow, ValidModeCallbackProc inCallback) { OSStatus err = noErr; // Build up a structure to pass to the window handler we are about // to install. We store the window itself, as well as the original // states of our settings. We use this to revert if the user clicks // the cancel button. static PrefInfo prefInfo; prefInfo.prefGameDisplayMode = inGDInfo->mode; prefInfo.prefDisplayID = inGDInfo->displayID; prefInfo.prefWidth = inGDInfo->width; prefInfo.prefHeight = inGDInfo->height; prefInfo.prefDepth = inGDInfo->depth; prefInfo.prefFrequency = inGDInfo->frequency; prefInfo.prefResFlags = inGDInfo->resFlags; prefInfo.window = NULL; prefInfo.okPressed = false; Boolean result; Boolean keyFound; result = CFPreferencesGetAppBooleanValue (kPref_PrefsDialogAlways, kCFPreferencesCurrentApplication, &keyFound); prefInfo.prefAlways = result && keyFound; result = CFPreferencesGetAppBooleanValue (kPref_PrefsDialogOpenAL, kCFPreferencesCurrentApplication, &keyFound); prefInfo.prefOpenAL = result && keyFound; prefInfo.callback = inCallback; // If DoPreferences is called at the start of the game, prefInfo.prefDisplayID needs to be checked // to see if it is still a valid display ID. if (!ValidDisplayID(prefInfo.prefDisplayID)) prefInfo.prefDisplayID = kCGDirectMainDisplay; // revert to main // Fetch the dialog IBNibRef aslNib; CFBundleRef theBundle = CFBundleGetMainBundle(); err = CreateNibReferenceWithCFBundle(theBundle, CFSTR("ASLCore"), &aslNib); err = ::CreateWindowFromNib(aslNib, CFSTR( "Preferences" ), &prefInfo.window ); if (err != noErr) return err; SetWRefCon(prefInfo.window, (long)&prefInfo); // Locate all the controls GetControlByID( prefInfo.window, &kFullscreenBtn, &prefInfo.fullscreenBtn ); assert(prefInfo.fullscreenBtn); GetControlByID( prefInfo.window, &kInAWindowBtn, &prefInfo.inAWindowBtn ); assert(prefInfo.inAWindowBtn); GetControlByID( prefInfo.window, &kResolutionPopup, &prefInfo.resolutionPopup ); assert(prefInfo.resolutionPopup); GetControlByID( prefInfo.window, &kRefreshRatePopup, &prefInfo.refreshRatePopup ); assert(prefInfo.refreshRatePopup); GetControlByID( prefInfo.window, &kChooseMonitorsBtn, &prefInfo.chooseMonitorsBtn ); assert(prefInfo.chooseMonitorsBtn); GetControlByID( prefInfo.window, &kAlwaysBtn, &prefInfo.alwaysBtn ); assert(prefInfo.alwaysBtn); GetControlByID( prefInfo.window, &kOpenALBtn, &prefInfo.openALBtn ); assert(prefInfo.openALBtn); // Disable the "choose monitor" button if we've only got one to pick from prefInfo.multiMonitor = CanUserPickMonitor(); if (!prefInfo.multiMonitor) { DisableControl (prefInfo.chooseMonitorsBtn); prefInfo.prefDisplayID = 0; } // Prepare the resolutions and refresh rates popup menus AdjustDisplayControls(&prefInfo); // Set up the controls SetControlValue (prefInfo.refreshRatePopup, prefInfo.freqMenuIndex); SetControlValue (prefInfo.fullscreenBtn, prefInfo.prefGameDisplayMode == kFullScreen); SetControlValue (prefInfo.inAWindowBtn, prefInfo.prefGameDisplayMode == kWindow); SetControlValue (prefInfo.alwaysBtn, prefInfo.prefAlways); SetControlValue (prefInfo.openALBtn, prefInfo.prefOpenAL); // Create our UPP and install the handler. EventTypeSpec cmdEvent = { kEventClassCommand, kEventCommandProcess }; EventHandlerUPP handler = GetPrefHandlerUPP(); InstallWindowEventHandler( prefInfo.window, handler, 1, &cmdEvent, &prefInfo, NULL ); // Position and show the window RepositionWindow( prefInfo.window, NULL, kWindowAlertPositionOnMainScreen ); if (outWindow) *outWindow = prefInfo.window; return err; } //------------------------------------------------------------------------------------ // ¥ RunGameDisplayPreferencesDialog //------------------------------------------------------------------------------------ // Runs the Mac-specific preferences dialog. OSStatus RunGameDisplayPreferencesDialog(GameDisplayInfo *outGDInfo, WindowRef inWindow) { PrefInfo *prefInfo = (PrefInfo*)GetWRefCon(inWindow); ShowWindow( inWindow ); // Now we run modally. We will remain here until the PrefHandler // calls QuitAppModalLoopForWindow if the user clicks OK or // Cancel. RunAppModalLoopForWindow( inWindow ); // OK, we're done. Dispose of our window. // TODO: Are we supposed to uninstall event handlers? DisposeWindow( inWindow ); // Return settings to caller if (prefInfo->okPressed) { outGDInfo->mode = prefInfo->prefGameDisplayMode; outGDInfo->width = prefInfo->prefWidth; outGDInfo->height = prefInfo->prefHeight; outGDInfo->depth = prefInfo->prefDepth; outGDInfo->frequency = prefInfo->prefFrequency; outGDInfo->resFlags = prefInfo->prefResFlags; outGDInfo->displayID = prefInfo->prefDisplayID; } return prefInfo->okPressed ? noErr : userCanceledErr; }