2 ===========================================================================
5 Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company.
7 This file is part of the Doom 3 GPL Source Code (?Doom 3 Source Code?).
9 Doom 3 Source Code is free software: you can redistribute it and/or modify
10 it under the terms of the GNU General Public License as published by
11 the Free Software Foundation, either version 3 of the License, or
12 (at your option) any later version.
14 Doom 3 Source Code is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 GNU General Public License for more details.
19 You should have received a copy of the GNU General Public License
20 along with Doom 3 Source Code. If not, see <http://www.gnu.org/licenses/>.
22 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.
24 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.
26 ===========================================================================
29 #include "../../idlib/precompiled.h"
30 #include <Carbon/Carbon.h>
31 #include "PickMonitor.h"
33 //====================================================================================
35 //====================================================================================
37 #define kMaxMonitors 16
39 //====================================================================================
41 //====================================================================================
53 //====================================================================================
55 //====================================================================================
56 static GDHandle sSelectedDevice;
57 static int sNumMonitors;
58 static Monitor sMonitors[kMaxMonitors];
60 static RGBColor rgbBlack = { 0x0000, 0x0000, 0x0000 };
61 static RGBColor rgbWhite = { 0xffff, 0xffff, 0xffff };
62 static RGBColor rgbGray = { 0x5252, 0x8A8A, 0xCCCC }; // this is the blue used in the Displays control panel
64 //====================================================================================
66 //====================================================================================
76 //====================================================================================
78 //====================================================================================
80 //-----------------------------------------------------------------------------
82 //-----------------------------------------------------------------------------
83 // Call this to initialize the specified user pane control before displaying
84 // the dialog window. Pass NULL for any user pane procs you don't need to install.
86 OSErr SetupUserPaneProcs( ControlRef inUserPane,
87 ControlUserPaneDrawProcPtr inDrawProc,
88 ControlUserPaneHitTestProcPtr inHitTestProc,
89 ControlUserPaneTrackingProcPtr inTrackingProc)
92 ControlUserPaneDrawUPP drawUPP;
93 ControlUserPaneHitTestUPP hitTestUPP;
94 ControlUserPaneTrackingUPP trackingUPP;
96 if (0 == inUserPane) return paramErr;
98 if (inDrawProc && noErr == err)
100 drawUPP = NewControlUserPaneDrawUPP(inDrawProc);
105 err = SetControlData( inUserPane,
106 kControlEntireControl,
107 kControlUserPaneDrawProcTag,
108 sizeof(ControlUserPaneDrawUPP),
111 if (inHitTestProc && noErr == err)
113 hitTestUPP = NewControlUserPaneHitTestUPP(inHitTestProc);
118 err = SetControlData( inUserPane,
119 kControlEntireControl,
120 kControlUserPaneHitTestProcTag,
121 sizeof(ControlUserPaneHitTestUPP),
124 if (inTrackingProc && noErr == err)
126 trackingUPP = NewControlUserPaneTrackingUPP(inTrackingProc);
128 if (0 == trackingUPP)
131 err = SetControlData( inUserPane,
132 kControlEntireControl,
133 kControlUserPaneTrackingProcTag,
134 sizeof(ControlUserPaneTrackingUPP),
142 //-----------------------------------------------------------------------------
143 // DisposeUserPaneProcs
144 //-----------------------------------------------------------------------------
145 // Call this to clean up when you're done with the specified user pane control.
147 OSErr DisposeUserPaneProcs(ControlRef inUserPane)
149 ControlUserPaneDrawUPP drawUPP;
150 ControlUserPaneHitTestUPP hitTestUPP;
151 ControlUserPaneTrackingUPP trackingUPP;
155 err = GetControlData(inUserPane, kControlEntireControl, kControlUserPaneDrawProcTag, sizeof(ControlUserPaneDrawUPP), (Ptr)&drawUPP, &actualSize);
156 if (err == noErr) DisposeControlUserPaneDrawUPP(drawUPP);
158 err = GetControlData(inUserPane, kControlEntireControl, kControlUserPaneHitTestProcTag, sizeof(ControlUserPaneHitTestUPP), (Ptr)&hitTestUPP, &actualSize);
159 if (err == noErr) DisposeControlUserPaneHitTestUPP(hitTestUPP);
161 err = GetControlData(inUserPane, kControlEntireControl, kControlUserPaneTrackingProcTag, sizeof(ControlUserPaneTrackingUPP), (Ptr)&trackingUPP, &actualSize);
162 if (err == noErr) DisposeControlUserPaneTrackingUPP(trackingUPP);
169 //-----------------------------------------------------------------------------
171 //-----------------------------------------------------------------------------
172 // Custom drawProc for our UserPane control.
174 static pascal void drawProc(ControlRef inControl, SInt16 inPart)
176 #pragma unused(inControl, inPart)
179 RGBColor saveForeColor;
180 RGBColor saveBackColor;
181 PenState savePenState;
183 GetForeColor(&saveForeColor);
184 GetBackColor(&saveBackColor);
185 GetPenState(&savePenState);
187 RGBForeColor(&rgbBlack);
188 RGBBackColor(&rgbWhite);
191 for (i = 0; i < sNumMonitors; i++)
193 RGBForeColor(&rgbGray);
194 PaintRect(&sMonitors[i].scaledRect);
195 if (sMonitors[i].isMain)
197 Rect r = sMonitors[i].scaledRect;
199 r.bottom = r.top + 6;
200 RGBForeColor(&rgbWhite);
202 RGBForeColor(&rgbBlack);
204 MoveTo(r.left, r.bottom);
205 LineTo(r.right, r.bottom);
207 if (sMonitors[i].device == sSelectedDevice)
210 RGBForeColor(&rgbBlack);
211 FrameRect(&sMonitors[i].scaledRect);
216 RGBForeColor(&rgbBlack);
217 FrameRect(&sMonitors[i].scaledRect);
221 // restore the original pen state and colors
222 RGBForeColor(&saveForeColor);
223 RGBBackColor(&saveBackColor);
224 SetPenState(&savePenState);
228 //-----------------------------------------------------------------------------
230 //-----------------------------------------------------------------------------
231 // Custom hitTestProc for our UserPane control.
232 // This allows FindControlUnderMouse() to locate our control, which allows
233 // ModalDialog() to call TrackControl() or HandleControlClick() for our control.
235 static pascal ControlPartCode hitTestProc(ControlRef inControl, Point inWhere)
237 // return a valid part code so HandleControlClick() will be called
238 return kControlButtonPart;
242 //-----------------------------------------------------------------------------
244 //-----------------------------------------------------------------------------
245 // Custom trackingProc for our UserPane control.
246 // This won't be called for our control unless the kControlHandlesTracking feature
247 // bit is specified when the userPane is created.
249 static pascal ControlPartCode trackingProc (
250 ControlRef inControl,
252 ControlActionUPP inActionProc)
254 #pragma unused (inControl, inStartPt, inActionProc)
257 for (i = 0; i < sNumMonitors; i++)
259 if (PtInRect(inStartPt, &sMonitors[i].scaledRect))
261 if (sMonitors[i].device != sSelectedDevice)
263 sSelectedDevice = sMonitors[i].device;
264 DrawOneControl(inControl);
270 return kControlNoPart;
277 //-----------------------------------------------------------------------------
278 // SetupPickMonitorPane
279 //-----------------------------------------------------------------------------
280 // Call this to initialize the user pane control that is the Pick Monitor
281 // control. Pass the ControlRef of the user pane control and a display ID
282 // for the monitor you want selected by default (pass 0 for the main monitor).
283 // Call this function before displaying the dialog window.
285 OSErr SetupPickMonitorPane(ControlRef inPane, DisplayIDType inDefaultMonitor)
287 GDHandle dev = GetDeviceList();
290 // make the default monitor the selected device
291 if (inDefaultMonitor)
292 DMGetGDeviceByDisplayID(inDefaultMonitor, &sSelectedDevice, true);
294 sSelectedDevice = GetMainDevice();
296 // build the list of monitors
298 while (dev && sNumMonitors < kMaxMonitors)
300 if (TestDeviceAttribute(dev, screenDevice) && TestDeviceAttribute(dev, screenActive))
302 sMonitors[sNumMonitors].device = dev;
303 sMonitors[sNumMonitors].origRect = (**dev).gdRect;
304 sMonitors[sNumMonitors].isMain = (dev == GetMainDevice());
307 dev = GetNextDevice(dev);
310 // calculate scaled rects
313 Rect origPaneRect, paneRect;
314 Rect origGrayRect, grayRect, scaledGrayRect;
315 float srcAspect, dstAspect, scale;
318 GetControlBounds(inPane, &origPaneRect);
319 paneRect = origPaneRect;
320 OffsetRect(&paneRect, -paneRect.left, -paneRect.top);
322 GetRegionBounds(GetGrayRgn(), &origGrayRect);
323 grayRect = origGrayRect;
324 OffsetRect(&grayRect, -grayRect.left, -grayRect.top);
326 srcAspect = (float)grayRect.right / (float)grayRect.bottom;
327 dstAspect = (float)paneRect.right / (float)paneRect.bottom;
329 scaledGrayRect = paneRect;
331 if (srcAspect < dstAspect)
333 scaledGrayRect.right = (float)paneRect.bottom * srcAspect;
334 scale = (float)scaledGrayRect.right / grayRect.right;
338 scaledGrayRect.bottom = (float)paneRect.right / srcAspect;
339 scale = (float)scaledGrayRect.bottom / grayRect.bottom;
342 for (i = 0; i < sNumMonitors; i++)
344 Rect r = sMonitors[i].origRect;
347 // normalize rect and scale
348 OffsetRect(&r, -r.left, -r.top);
349 r.bottom = (float)r.bottom * scale;
350 r.right = (float)r.right * scale;
352 // offset rect wrt gray region
353 OffsetRect(&r, (float)(r2.left - origGrayRect.left) * scale,
354 (float)(r2.top - origGrayRect.top) * scale);
356 sMonitors[i].scaledRect = r;
359 // center scaledGrayRect in the pane
360 OffsetRect(&scaledGrayRect, (paneRect.right - scaledGrayRect.right) / 2,
361 (paneRect.bottom - scaledGrayRect.bottom) / 2);
363 // offset monitors to match
364 for (i = 0; i < sNumMonitors; i++)
365 OffsetRect(&sMonitors[i].scaledRect, scaledGrayRect.left, scaledGrayRect.top);
370 // setup the procs for the pick monitor user pane
371 err = SetupUserPaneProcs(inPane, drawProc, hitTestProc, trackingProc);
376 //-----------------------------------------------------------------------------
377 // TearDownPickMonitorPane
378 //-----------------------------------------------------------------------------
379 // Disposes of everything associated with the Pick Monitor pane. You should
380 // call this when disposing the dialog.
382 OSErr TearDownPickMonitorPane(ControlRef inPane)
385 err = DisposeUserPaneProcs(inPane);
392 //------------------------------------------------------------------------------------
393 // ¥ PickMonitorHandler
394 //------------------------------------------------------------------------------------
395 // Our command handler for the PickMonitor dialog.
397 static pascal OSStatus PickMonitorHandler( EventHandlerCallRef inHandler, EventRef inEvent, void* inUserData )
399 #pragma unused( inHandler )
402 OSStatus result = eventNotHandledErr;
403 WindowRef theWindow = (WindowRef)inUserData;
405 // The direct object for a 'process commmand' event is the HICommand.
406 // Extract it here and switch off the command ID.
408 GetEventParameter( inEvent, kEventParamDirectObject, typeHICommand, NULL, sizeof( cmd ), NULL, &cmd );
410 switch ( cmd.commandID )
413 QuitAppModalLoopForWindow( theWindow );
417 case kHICommandCancel:
418 // Setting sSelectedDevice to zero will signal that the user cancelled.
420 QuitAppModalLoopForWindow( theWindow );
431 //-----------------------------------------------------------------------------
432 // CanUserPickMonitor
433 //-----------------------------------------------------------------------------
434 // Returns true if more than one monitor is available to choose from.
436 Boolean CanUserPickMonitor (void)
438 GDHandle dev = GetDeviceList();
442 // build the list of monitors
444 while (dev && numMonitors < kMaxMonitors)
446 if (TestDeviceAttribute(dev, screenDevice) && TestDeviceAttribute(dev, screenActive))
450 dev = GetNextDevice(dev);
453 if (numMonitors > 1) return true;
457 //-----------------------------------------------------------------------------
459 //-----------------------------------------------------------------------------
460 // Prompts for a monitor. Returns userCanceledErr if the user cancelled.
462 OSStatus PickMonitor (DisplayIDType *inOutDisplayID, WindowRef parentWindow)
465 OSStatus status = noErr;
466 static const ControlID kUserPane = { 'MONI', 1 };
471 CFBundleRef theBundle = CFBundleGetMainBundle();
472 status = CreateNibReferenceWithCFBundle(theBundle, CFSTR("ASLCore"), &aslNib);
473 status = ::CreateWindowFromNib(aslNib, CFSTR( "Pick Monitor" ), &theWindow );
477 return userCanceledErr;
481 // Put game name in window title. By default the title includes the token <<<kGameName>>>.
484 GetWTitle(theWindow, windowTitle);
485 FormatPStringWithGameName(windowTitle);
486 SetWTitle(theWindow, windowTitle);
489 // Set up the controls
491 ControlRef monitorPane;
492 GetControlByID( theWindow, &kUserPane, &monitorPane );
495 SetupPickMonitorPane(monitorPane, *inOutDisplayID);
497 // Create our UPP and install the handler.
499 EventTypeSpec cmdEvent = { kEventClassCommand, kEventCommandProcess };
500 EventHandlerUPP handler = NewEventHandlerUPP( PickMonitorHandler );
501 InstallWindowEventHandler( theWindow, handler, 1, &cmdEvent, theWindow, NULL );
506 ShowSheetWindow( theWindow, parentWindow );
508 ShowWindow( theWindow );
510 // Now we run modally. We will remain here until the PrefHandler
511 // calls QuitAppModalLoopForWindow if the user clicks OK or
514 RunAppModalLoopForWindow( theWindow );
516 // OK, we're done. Dispose of our window and our UPP.
517 // We do the UPP last because DisposeWindow can send out
518 // CarbonEvents, and we haven't explicitly removed our
519 // handler. If we disposed the UPP, the Toolbox might try
520 // to call it. That would be bad.
522 TearDownPickMonitorPane(monitorPane);
524 HideSheetWindow( theWindow );
525 DisposeWindow( theWindow );
526 DisposeEventHandlerUPP( handler );
528 // Return settings to caller
530 if (sSelectedDevice != 0)
532 // Read back the controls
533 DMGetDisplayIDByGDevice (sSelectedDevice, &*inOutDisplayID, true);
537 return userCanceledErr;