Retired Document
Important: This sample code may not represent best practices for current development. The project may use deprecated symbols and illustrate technologies and techniques that are no longer recommended.
AuntieDialog.cp
/* |
File : AuntieDialog.cp |
Description : Free of the Dialog Manager at last! See: "AuntieDialog.html" |
Author : PCG |
Copyright : © Copyright 1999-2000 Apple Computer, Inc. All rights reserved. |
Disclaimer : IMPORTANT: This Apple software is supplied to you by Apple Computer, Inc. |
("Apple") in consideration of your agreement to the following terms, and your |
use, installation, modification or redistribution of this Apple software |
constitutes acceptance of these terms. If you do not agree with these terms, |
please do not use, install, modify or redistribute this Apple software. |
In consideration of your agreement to abide by the following terms, and subject |
to these terms, Apple grants you a personal, non-exclusive license, under AppleÕs |
copyrights in this original Apple software (the "Apple Software"), to use, |
reproduce, modify and redistribute the Apple Software, with or without |
modifications, in source and/or binary forms; provided that if you redistribute |
the Apple Software in its entirety and without modifications, you must retain |
this notice and the following text and disclaimers in all such redistributions of |
the Apple Software. Neither the name, trademarks, service marks or logos of |
Apple Computer, Inc. may be used to endorse or promote products derived from the |
Apple Software without specific prior written permission from Apple. Except as |
expressly stated in this notice, no other rights or licenses, express or implied, |
are granted by Apple herein, including but not limited to any patent rights that |
may be infringed by your derivative works or by other works in which the Apple |
Software may be incorporated. |
The Apple Software is provided by Apple on an "AS IS" basis. APPLE MAKES NO |
WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED |
WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND OPERATION ALONE OR IN |
COMBINATION WITH YOUR PRODUCTS. |
IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL OR |
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE |
GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) |
ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, MODIFICATION AND/OR DISTRIBUTION |
OF THE APPLE SOFTWARE, HOWEVER CAUSED AND WHETHER UNDER THEORY OF CONTRACT, TORT |
(INCLUDING NEGLIGENCE), STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN |
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
*/ |
#define DEBUG_REGION_STOP 0 |
#define DEBUG_MOUSE_RGN 0 |
#include "AuntieDialog.h" |
#ifdef __MWERKS__ |
// includes for MetroWerks CodeWarrior |
#include <Debugging.h> |
#include <ControlDefinitions.h> |
#include <Resources.h> |
#include <Gestalt.h> |
#include <Sound.h> |
#endif |
#if PRAGMA_STRUCT_ALIGN |
#pragma options align=mac68k |
#elif PRAGMA_STRUCT_PACKPUSH |
#pragma pack(push, 2) |
#elif PRAGMA_STRUCT_PACK |
#pragma pack(2) |
#endif |
typedef struct |
{ |
UInt32 placeHolder; |
Rect displayRect; |
UInt8 flags; |
unsigned char itemData [ ]; |
} |
tDialogItem, *tDialogItemP, **tDialogItemH; |
typedef struct |
{ |
SInt16 count; |
tDialogItem items [ ]; |
} |
tDialogItemList, *tDialogItemListP, **tDialogItemListH; |
typedef struct |
{ |
UInt16 itemType; |
ControlFontStyleRec itemData; |
unsigned char fontName [ ]; |
} |
tDialogFontItem, *tDialogFontItemP, **tDialogFontItemH; |
typedef struct |
{ |
UInt16 version; |
UInt16 count; |
tDialogFontItem items [ ]; |
} |
tDialogFontItemList, *tDialogFontItemListP, **tDialogFontItemListH; |
#if PRAGMA_STRUCT_ALIGN |
#pragma options align=reset |
#elif PRAGMA_STRUCT_PACKPUSH |
#pragma pack(pop) |
#elif PRAGMA_STRUCT_PACK |
#pragma pack() |
#endif |
#pragma mark PRIVATE TYPES |
typedef struct RegionIsUnderMouseState *RegionIsUnderMouseStateP, **RegionIsUnderMouseStateH; |
struct RegionIsUnderMouseState |
{ |
RgnHandle mouseRgn; |
Point localWhere; |
}; |
typedef struct AuntieDialogState *AuntieDialogStateP, **AuntieDialogStateH; |
struct AuntieDialogState |
{ |
AuntieDialogStateP next; |
RgnHandle mouseRgn; |
}; |
static AuntieDialogState *gAuntieDialogState; |
#pragma mark - |
#if !DEBUG_MOUSE_RGN |
# define DebugRegion(w,r) |
#else |
static void DebugRegion (WindowRef window, RgnHandle rgn) |
{ |
GrafPtr preservedPort; |
if (window) |
{ |
GetPort (&preservedPort); |
SetPortWindowPort (window); |
} |
InvertRgn (rgn); |
#if DEBUG_REGION_STOP |
Debugger ( ); |
#else |
UInt32 finalTicks; |
Delay (10,&finalTicks); |
#endif |
InvertRgn (rgn); |
if (window) |
{ |
SetPort (preservedPort); |
} |
} |
#endif |
static OSStatus LocalToGlobalRgn (WindowRef window, RgnHandle rgn) |
{ |
OSStatus err = noErr; |
GrafPtr preservedPort; |
if (window) |
{ |
GetPort (&preservedPort); |
SetPortWindowPort (window); |
} |
Point offsetPoint = { 0, 0 }; |
LocalToGlobal (&offsetPoint); |
OffsetRgn (rgn,offsetPoint.h,offsetPoint.v); |
err = QDError ( ); |
if (window) |
{ |
SetPort (preservedPort); |
} |
return err; |
} |
static void LocalToGlobalRect (Rect &rect) |
{ |
Point offsetPoint = { 0, 0 }; |
LocalToGlobal (&offsetPoint); |
OffsetRect (&rect,offsetPoint.h,offsetPoint.v); |
} |
static void GlobalToLocalRegion (RgnHandle rgnH) |
{ |
Point offsetPoint = { 0, 0 }; |
GlobalToLocal (&offsetPoint); |
OffsetRgn (rgnH,offsetPoint.h,offsetPoint.v); |
} |
#pragma mark - |
#define HandleStateHasLockBit(hs) (hs & 0x80 ? true : false) |
struct StHandleLocker |
{ |
Handle fHandle; |
SInt8 fHandleState; |
StHandleLocker (void *h, OSStatus &err); |
~StHandleLocker (void); |
}; |
StHandleLocker::StHandleLocker (void *h, OSStatus &err) : fHandle (nil) |
{ |
if (!h) |
err = noErr; |
else |
{ |
fHandleState = HGetState (Handle (h)); |
err = MemError ( ); |
if (!err && !HandleStateHasLockBit (fHandleState)) |
{ |
MoveHHi (Handle (h)); |
err = MemError ( ); |
if (!err) |
{ |
HLock (Handle (h)); |
err = MemError ( ); |
if (!err) |
{ |
fHandle = Handle (h); |
} |
} |
} |
} |
} |
StHandleLocker::~StHandleLocker (void) // explicitly outline to avoid CW Pro 2 68K codegen bug |
{ |
if (fHandle && !HandleStateHasLockBit (fHandleState)) |
HUnlock (fHandle); |
} |
struct StRegion |
{ |
RgnHandle fRgn; |
StRegion (OSStatus &err); |
StRegion (OSStatus &err, RgnHandle rgn); |
StRegion (OSStatus &err, const Rect &); |
~StRegion (void); |
operator RgnHandle (void) { return fRgn; } |
}; |
StRegion::StRegion (OSStatus &err) : fRgn (NewRgn ( )) |
{ |
err = fRgn ? noErr : QDError ( ); |
} |
StRegion::StRegion (OSStatus &err, RgnHandle rgn) : fRgn (NewRgn ( )) |
{ |
if (!rgn) |
{ |
err = nilHandleErr; |
} |
else if (!fRgn) |
{ |
err = QDError ( ); |
} |
else |
{ |
CopyRgn (rgn,fRgn); |
err = QDError ( ); |
} |
} |
StRegion::StRegion (OSStatus &err, const Rect &rect) : fRgn (NewRgn ( )) |
{ |
if (!fRgn) |
{ |
err = QDError ( ); |
} |
else |
{ |
RectRgn (fRgn,&rect); |
err = QDError ( ); |
} |
} |
StRegion::~StRegion (void) // explicitly outline to avoid CW Pro 2 68K codegen bug |
{ |
if (fRgn) DisposeRgn (fRgn); |
} |
struct StGWorld |
{ |
GWorldPtr fPreservedWorld; |
GDHandle fPreservedDevice; |
GWorldPtr fGWorld; |
Rect fBoundsRect; |
operator GWorldPtr (void) { return fGWorld; } |
StGWorld (const Rect &boundsRect, OSStatus &); |
~StGWorld (void); |
}; |
StGWorld::StGWorld (const Rect &boundsRect, OSStatus &err) : fGWorld (nil) |
{ |
Rect globalRect = boundsRect; |
LocalToGlobalRect (globalRect); |
GDHandle maxDevice = GetMaxDevice (&globalRect); |
if (!maxDevice) |
err = QDError ( ); |
else |
{ |
PixMapHandle gdPMap = (**maxDevice).gdPMap; |
short pixelSize = (**gdPMap).pixelSize; |
err = NewGWorld (&fGWorld, pixelSize, &boundsRect, nil, maxDevice, useTempMem | noNewDevice); |
if (!err) |
{ |
PixMapHandle worldPix = GetGWorldPixMap (fGWorld); |
if (!LockPixels (worldPix)) |
{ |
err = QDError ( ); |
DisposeGWorld (fGWorld); |
fGWorld = nil; |
} |
else |
{ |
GetGWorld (&fPreservedWorld,&fPreservedDevice); |
StRegion preservedWorldClipRgn (err); |
if (!err) |
{ |
StRegion preservedWorldVisRgn (err); |
if (!err) |
{ |
GetPortClipRegion (fPreservedWorld,preservedWorldClipRgn); |
GetPortVisibleRegion (fPreservedWorld,preservedWorldVisRgn); |
fBoundsRect = boundsRect; |
RGBColor preservedForeColor, preservedBackColor; |
GetForeColor (&preservedForeColor); |
GetBackColor (&preservedBackColor); |
ForeColor (blackColor); |
BackColor (whiteColor); |
SetGWorld (fGWorld,nil); |
SetClip (preservedWorldClipRgn); |
SetPortVisibleRegion (fGWorld,preservedWorldVisRgn); |
ForeColor (blackColor); |
BackColor (whiteColor); |
Rect worldRect; |
GetPortBounds (fGWorld,&worldRect); |
PixMapHandle preservedPix = GetPortPixMap (fPreservedWorld); |
CopyBits ( BitMapPtr (*preservedPix), BitMapPtr (*worldPix), |
&boundsRect, &worldRect, |
srcCopy, nil ); |
RGBForeColor (&preservedForeColor); |
RGBBackColor (&preservedBackColor); |
SetGWorld (fPreservedWorld,fPreservedDevice); |
RGBForeColor (&preservedForeColor); |
RGBBackColor (&preservedBackColor); |
SetGWorld (fGWorld,nil); |
} |
} |
} |
} |
} |
} |
StGWorld::~StGWorld (void) |
{ |
if (fGWorld) |
{ |
SetGWorld (fGWorld,nil); |
ForeColor (blackColor); |
BackColor (whiteColor); |
SetGWorld (fPreservedWorld,fPreservedDevice); |
RGBColor preservedForeColor, preservedBackColor; |
GetForeColor (&preservedForeColor); |
GetBackColor (&preservedBackColor); |
ForeColor (blackColor); |
BackColor (whiteColor); |
Rect worldRect; |
GetPortBounds (fGWorld,&worldRect); |
PixMapHandle worldPix = GetGWorldPixMap (fGWorld); |
PixMapHandle preservedPix = GetPortPixMap (fPreservedWorld); |
CopyBits ( BitMapPtr (*worldPix), BitMapPtr (*preservedPix), |
&worldRect, &fBoundsRect, |
srcCopy, nil ); |
RGBForeColor (&preservedForeColor); |
RGBBackColor (&preservedBackColor); |
DisposeGWorld (fGWorld); |
fGWorld = nil; |
} |
} |
#pragma mark - |
static OSStatus ControlIsEditText (ControlRef control, Boolean &isEditText) |
{ |
OSStatus err = noErr; |
UInt32 actualBufferSize; |
SInt16 procID; |
err = GetControlProperty (control,kAuntieDialogSignature,kAuntieDialogControlPropertyTagProcID,sizeof(procID),&actualBufferSize,&procID); |
if (err == controlPropertyNotFoundErr) |
{ |
isEditText = false; |
err = noErr; |
} |
else if (!err) |
{ |
if (sizeof (procID) != actualBufferSize) |
{ |
err = controlPropertyInvalid; |
} |
else |
{ |
procID &= 0xFFF0; // strip variant bits |
isEditText = (procID == kControlEditTextProc); |
} |
} |
return err; |
} |
static OSStatus ControlIsScrollBar (ControlRef control, Boolean &isScrollBar) |
{ |
OSStatus err = noErr; |
UInt32 actualBufferSize; |
SInt16 procID; |
err = GetControlProperty (control,kAuntieDialogSignature,kAuntieDialogControlPropertyTagProcID,sizeof(procID),&actualBufferSize,&procID); |
if (err == controlPropertyNotFoundErr) |
{ |
isScrollBar = false; |
err = noErr; |
} |
else if (!err) |
{ |
if (sizeof (procID) != actualBufferSize) |
{ |
err = controlPropertyInvalid; |
} |
else |
{ |
procID &= 0xFFF0; // strip variant bits |
isScrollBar = (procID == kControlScrollBarProc || procID == scrollBarProc); |
} |
} |
return err; |
} |
static OSStatus ControlIsIcon (ControlRef control, Boolean &isIcon) |
{ |
OSStatus err = noErr; |
UInt32 actualBufferSize; |
SInt16 procID; |
err = GetControlProperty (control,kAuntieDialogSignature,kAuntieDialogControlPropertyTagProcID,sizeof(procID),&actualBufferSize,&procID); |
if (err == controlPropertyNotFoundErr) |
{ |
isIcon = false; |
err = noErr; |
} |
else if (!err) |
{ |
if (sizeof (procID) != actualBufferSize) |
{ |
err = controlPropertyInvalid; |
} |
else |
{ |
procID &= 0xFFF0; // strip variant bits |
isIcon = (procID == kControlIconProc); |
} |
} |
return err; |
} |
static OSStatus SearchChildControls ( ControlRef parent, |
ControlHierarchySearchProcPtr chspp, |
ControlRef *found, |
void *userData ) |
{ |
OSStatus err = noErr; |
ControlRef controlDummy; |
if (!found) found = &controlDummy; |
*found = nil; |
UInt16 index; |
err = CountSubControls (parent,&index); |
if (err == errControlIsNotEmbedder) |
err = noErr; |
else if (!err) while (index) |
{ |
ControlRef child; |
err = GetIndexedSubControl (parent,index,&child); |
if (err) |
{ |
if (err == errControlIsNotEmbedder) |
err = noErr; |
break; |
} |
// recur before calling back; clients depend on it |
*found = nil; |
err = SearchChildControls (child,chspp,found,userData); |
if (err || *found) break; |
Boolean stop = false; |
err = chspp (child,&stop,userData); |
if (err) break; |
if (stop) |
{ |
*found = child; |
break; |
} |
--index; |
} |
return err; |
} |
static OSStatus AuntieDialogSetControlData (ControlRef control, DialogItemType itemType, ConstStr255Param itemData) |
{ |
OSStatus err = noErr; |
switch (itemType) |
{ |
case kStaticTextDialogItem : |
case kEditTextDialogItem : |
err = SetControlData (control, kControlEntireControl, 'text', *itemData, Ptr (itemData + 1)); |
break; |
} |
return err; |
} |
static OSStatus GetWholeControlRegion |
(ControlRef parent, RgnHandle parentRgn, Boolean includeChildren) |
{ |
OSStatus err = noErr; |
// for calcCntlRgn, SendControlMessage should always return 0; |
// if it doesn't, the meaning is undefined; so ignore it |
#ifdef __MWERKS__ |
(void) SendControlMessage (parent, calcCntlRgn, (SInt32)parentRgn); |
#else |
(void) SendControlMessage (parent, calcCntlRgn, (void *)parentRgn); |
#endif |
if (includeChildren) do |
{ |
StRegion childRgn (err); |
if (err) break; |
UInt16 index; |
err = CountSubControls (parent,&index); |
if (err == errControlIsNotEmbedder) |
{ |
err = noErr; |
} |
else if (err) |
{ |
break; |
} |
else while (index) |
{ |
ControlRef child; |
err = GetIndexedSubControl (parent,index,&child); |
if (err) |
{ |
if (err == errControlIsNotEmbedder) |
err = noErr; |
break; |
} |
err = GetWholeControlRegion (child,childRgn,includeChildren); |
if (err) break; |
UnionRgn (childRgn,parentRgn,parentRgn); |
--index; |
} |
} |
while (false); |
return err; |
} |
static OSStatus PostProcessNewlyCreatedControl |
(tDialogItemP item, tDialogFontItemP fontItem, DialogItemType itemType, |
SInt16 procID, WindowRef window, ControlRef control, Boolean invalidate, SInt32 suggestedID, |
ControlCreationProcPtr ccpp, void *ccppUserData) |
{ |
OSStatus err = noErr; |
do |
{ |
MoveControl (control,item->displayRect.left,item->displayRect.top); |
err = AutoEmbedControl (control,window); |
if (err) break; |
err = AuntieDialogSetControlData (control,itemType,item->itemData); |
if (err) break; |
err = SetControlProperty (control,kAuntieDialogSignature,kAuntieDialogControlPropertyTagProcID,sizeof(procID),&procID); |
if (err) break; |
if (kItemDisableBit & (item->flags)) |
{ |
switch (itemType) |
{ |
case kIconDialogItem : |
case kPictureDialogItem : |
case kStaticTextDialogItem : |
// do nothing; resource settings make no sense |
break; |
default : |
err = AuntieDialogDisableControl (control); |
break; |
} |
if (err) break; |
} |
if (fontItem && fontItem->itemType) |
{ |
enum { kControlUseFontNameMask = kControlAddToMetaFontMask }; |
if (fontItem->itemData.flags & kControlUseFontNameMask) |
{ |
GetFNum (fontItem->fontName, &(fontItem->itemData.font)); |
fontItem->itemData.flags |= kControlUseFontMask; |
} |
fontItem->itemData.flags &= ~kControlAddToMetaFontMask; |
err = SetControlData ( control, |
kControlEntireControl, |
kControlFontStyleTag, |
sizeof (fontItem->itemData), |
Ptr (&(fontItem->itemData)) ); |
if (err == errDataNotSupported) |
err = noErr; |
else if (err) |
break; |
} |
if (invalidate) |
{ |
err = InvalControl (control,false); |
if (err) break; |
} |
ControlID controlID = { kAuntieDialogSignature, suggestedID }; |
err = SetControlID (control,&controlID); |
if (err) break; |
if (ccpp) |
{ |
err = ccpp (control,ccppUserData); |
if (err) break; |
} |
} |
while (false); |
return err; |
} |
static OSStatus GetProcIDFromControlResource (SInt16 resID, SInt16 &procID) |
{ |
OSStatus err = noErr; |
ControlTemplateHandle cth = ControlTemplateHandle (GetResource ('CNTL',resID)); |
if (!cth) |
{ |
err = ResError ( ); |
if (!err) err = resNotFound; |
} |
else |
{ |
procID = (**cth).controlDefProcID; |
// we assume the resource is purgeable, just like GetNewControl does |
} |
return err; |
} |
static OSStatus DetermineControlParameters ( tDialogItemP item, |
DialogItemType itemType, |
ConstStr255Param &title, |
SInt16 &resID, |
SInt16 &procID, |
SInt16 &initialValue, |
SInt16 &minimumValue, |
SInt16 &maximumValue ) |
{ |
OSStatus err = noErr; |
title = "\p"; |
resID = * (SInt16 *) (item->itemData + 1); |
procID = 0; |
initialValue = 0; |
minimumValue = 0; |
maximumValue = 0; |
switch (itemType) |
{ |
case kResourceControlDialogItem : |
err = GetProcIDFromControlResource (resID,procID); |
break; |
case kIconDialogItem : |
title = "\picon"; |
// Note that we create enabled icon dialog items with |
// the tracking variant and disabled icon dialog items |
// without the tracking variant. We also track icon |
// controls carefully so as to avoid the old (icky) |
// Dialog Manager icon tracking behavior. See |
// ClickAuntieDialogInContent for note regarding the |
// behavior we avoid. |
if (kItemDisableBit & (item->flags)) |
{ |
procID = kControlIconNoTrackProc; |
} |
else |
{ |
procID = kControlIconProc; |
} |
initialValue = resID; |
break; |
case kPictureDialogItem : |
title = "\ppicture"; |
if (kItemDisableBit & (item->flags)) |
procID = kControlPictureNoTrackProc; |
else |
procID = kControlPictureProc; |
initialValue = resID; |
break; |
case kButtonDialogItem : |
title = item->itemData; |
procID = kControlPushButtonProc; |
maximumValue = 1; |
break; |
case kCheckBoxDialogItem : |
title = item->itemData; |
procID = kControlCheckBoxProc; |
maximumValue = 1; |
break; |
case kRadioButtonDialogItem : |
title = item->itemData; |
procID = kControlRadioButtonProc; |
maximumValue = 1; |
break; |
case kStaticTextDialogItem : |
title = "\pstatic text"; |
procID = kControlStaticTextProc; |
break; |
case kEditTextDialogItem : |
// |
// We boldly specify kControlEditTextInlineInputProc |
// instead of kControlEditTextProc because the extra |
// variant bits are ignored if they are not supported. |
// This means the AuntieDialog clients running under |
// versions of Mac OS prior to 8.5 simply and silently |
// do not get super-cool inline input support. Sorry! |
// There's not a lot this code can do without bloating. |
// |
title = "\pedit text"; |
procID = kControlEditTextInlineInputProc; |
break; |
case kUserDialogItem : |
title = "\puser item"; |
procID = kControlUserPaneProc; |
initialValue = kControlSupportsEmbedding; |
break; |
} |
return err; |
} |
static OSStatus AddAuntieDialogItem |
(tDialogItemP item, tDialogFontItemP fontItem, WindowRef window, |
Boolean invalidate, SInt32 suggestedID, |
ControlCreationProcPtr ccpp, void *ccppUserData) |
{ |
OSStatus err = noErr; |
ConstStr255Param title; |
DialogItemType itemType = DialogItemType (~kItemDisableBit & (item->flags)); |
SInt16 resID, procID, initialValue, minimumValue, maximumValue; |
err = DetermineControlParameters |
(item,itemType,title,resID,procID,initialValue,minimumValue,maximumValue); |
if (!err) |
{ |
ControlHandle control; |
if (kResourceControlDialogItem == itemType) |
{ |
control = GetNewControl (resID,window); |
} |
else if (0 == procID) |
{ |
err = paramErr; |
} |
else |
{ |
control = NewControl ( window, |
&(item->displayRect), |
title, |
true, |
initialValue, |
minimumValue, |
maximumValue, |
procID, |
0 ); |
} |
if (!err) |
{ |
if (!control) |
{ |
err = nilHandleErr; |
} |
else |
{ |
err = PostProcessNewlyCreatedControl |
(item,fontItem,itemType,procID,window,control,invalidate,suggestedID,ccpp,ccppUserData); |
if (err) |
{ |
DisposeControl (control); |
} |
} |
} |
} |
return err; |
} |
static tDialogItemP GetNextDialogItem (tDialogItemP item) |
{ |
UInt8 itemDataLen = *(item->itemData); |
itemDataLen += itemDataLen % 2; |
return tDialogItemP (Ptr (item) + sizeof (*item) + itemDataLen); |
} |
static tDialogFontItemP GetNextDialogFontItem (tDialogFontItemP item) |
{ |
if (!item) |
return nil; |
if (!(item->itemType)) |
return tDialogFontItemP (Ptr (item) + sizeof (item->itemType)); |
return tDialogFontItemP (Ptr (item) + sizeof (*item) + *(item->fontName) + 1); |
} |
static ThemeBrush ChooseInitialWindowThemeBrush (WindowRef window) |
{ |
WindowRef front = FrontWindow ( ); |
if (!front || front == window) |
{ |
return kThemeBrushDialogBackgroundActive; |
} |
return kThemeBrushDialogBackgroundInactive; |
} |
static Boolean AuntieWindowIsVisible (WindowRef window) |
{ |
Boolean result = IsWindowVisible (window); |
#if !TARGET_CARBON |
// |
// This is only an optimization. Since Carbonating it |
// would cause us to do more work than Window Manager |
// would in inhibiting the unnecessary update event, |
// we simply skip the optimization. |
// |
if (result) |
if (EmptyRgn (window->visRgn)) |
result = false; |
#endif |
return result; |
} |
static OSStatus AppendControlsFromItemList |
(tDialogItemListP items, tDialogFontItemListP fontItems, |
WindowRef window, UInt16 controlIndexOffset, |
ControlCreationProcPtr ccpp, void *ccppUserData) |
{ |
OSStatus err = noErr; |
if (items->count > -1) |
{ |
Boolean windowIsVisible = AuntieWindowIsVisible (window); |
GrafPtr preservedPort; |
if (windowIsVisible) |
{ |
GetPort (&preservedPort); |
SetPortWindowPort (window); |
HidePen ( ); |
} |
do |
{ |
tDialogItemP item = items->items; |
tDialogFontItemP fontItem = fontItems ? fontItems->items : nil; |
SInt16 itemCount = SInt16 (items->count + 1), |
fontItemCount = SInt16 (fontItems ? fontItems->count : 0), |
count = (itemCount < fontItemCount) ? itemCount : fontItemCount, |
index = 1; |
do |
{ |
SInt32 suggestedID = index + controlIndexOffset; |
err = AddAuntieDialogItem (item,fontItem,window,windowIsVisible,suggestedID,ccpp,ccppUserData); |
if (err) break; |
item = GetNextDialogItem (item); |
fontItem = GetNextDialogFontItem (fontItem); |
} |
while (++index <= count); |
if (err) break; |
while (index <= itemCount) |
{ |
SInt32 suggestedID = index + controlIndexOffset; |
err = AddAuntieDialogItem (item,nil,window,windowIsVisible,suggestedID,ccpp,ccppUserData); |
if (err) break; |
item = GetNextDialogItem (item); |
++index; |
} |
if (err) break; |
} |
while (false); |
if (windowIsVisible) |
{ |
ShowPen ( ); |
SetPort (preservedPort); |
} |
} |
return err; |
} |
static Boolean IsAuntieDialogWindow (WindowRef window) |
{ |
// pass Ptr (kBlessedBusErrorBait) because TradOS and CarbonLib implementations reject nil even when buffer size is 0 [Radar 2455997] |
return !GetWindowProperty (window, kAuntieDialogSignature, kAuntieDialogWindowPropertyTagIsAuntieDialog, 0, nil, Ptr (kBlessedBusErrorBait)); |
} |
static OSStatus NewAuntieWindowFromDialogResource |
(short &resID, WindowRef behind, Boolean &shouldBeVisible, WindowRef *result) |
{ |
OSStatus err = noErr; |
DialogTHndl dialogRes = DialogTHndl (GetResource ('DLOG',resID)); |
if (!dialogRes) |
{ |
err = ResError ( ); |
if (!err) err = resNotFound; |
} |
else do |
{ |
StHandleLocker dialogResLock (dialogRes,err); |
if (err) break; |
DialogTPtr dialog = *dialogRes; |
*result = NewCWindow ( nil, |
&(dialog->boundsRect), |
dialog->title, |
false, |
dialog->procID, |
behind, |
dialog->goAwayFlag, |
dialog->refCon ); |
if (!*result) |
{ |
err = nilHandleErr; |
break; |
} |
// pass Ptr (kBlessedBusErrorBait) because TradOS and CarbonLib implementations reject nil even when buffer size is 0 [Radar 2455997] |
err = SetWindowProperty (*result, kAuntieDialogSignature, kAuntieDialogWindowPropertyTagIsAuntieDialog, 0, Ptr (kBlessedBusErrorBait)); |
if (err) |
{ |
DisposeWindow (*result); |
break; |
} |
shouldBeVisible = dialog->visible; |
resID = dialog->itemsID; |
} |
while (false); |
return err; |
} |
static OSStatus IsControlOurs (ControlRef control, Boolean &isOurs) |
{ |
ControlID controlID; |
OSStatus err = GetControlID (control,&controlID); |
if (!err) |
{ |
isOurs = (kAuntieDialogSignature == controlID.signature); |
} |
return err; |
} |
static OSStatus CountSubControlsDeep (ControlRef parent, UInt16 *deepCount) |
{ |
OSStatus err = noErr; |
UInt16 shallowIndex; |
err = CountSubControls (parent,&shallowIndex); |
if (err == errControlIsNotEmbedder) |
err = noErr; |
else if (!err) |
{ |
*deepCount += shallowIndex; |
while (shallowIndex) |
{ |
ControlRef child; |
err = GetIndexedSubControl (parent,shallowIndex,&child); |
if (err) |
{ |
if (err == errControlIsNotEmbedder) |
{ |
*deepCount -= shallowIndex; |
err = noErr; |
} |
break; |
} |
Boolean isOurs; |
err = IsControlOurs (child,isOurs); |
if (err) break; |
if (!isOurs) |
{ |
*deepCount -= 1; |
} |
else |
{ |
err = CountSubControlsDeep (child,deepCount); |
if (err) break; |
} |
--shallowIndex; |
} |
} |
return err; |
} |
static OSStatus AppendDialogItemsAsControls |
(short resID, WindowRef window, ControlRef root, |
ControlCreationProcPtr ccpp, void *ccppUserData) |
{ |
OSStatus err = noErr; |
do |
{ |
tDialogItemListH items = tDialogItemListH (GetResource ('DITL',resID)); |
if (!items) |
{ |
err = ResError ( ); |
if (err) break; |
} |
if (items) |
{ |
tDialogFontItemListH fontItemsH = tDialogFontItemListH (GetResource ('dftb',resID)); |
if (!fontItemsH) |
{ |
err = ResError ( ); |
if (err) break; |
} |
else if ((**fontItemsH).version != 0) |
{ |
fontItemsH = nil; |
} |
StHandleLocker itemsLock (items,err); |
if (err) break; |
StHandleLocker fontItemsLock (fontItemsH,err); |
if (err) break; |
UInt16 controlCount = 0; |
err = CountSubControlsDeep (root,&controlCount); |
if (err) break; |
tDialogFontItemListP fontItemsP = fontItemsH ? *fontItemsH : nil; |
err = AppendControlsFromItemList (*items,fontItemsP,window,controlCount,ccpp,ccppUserData); |
if (err) break; |
} |
} |
while (false); |
return err; |
} |
static OSStatus ShouldDrawControlsOffScreen (WindowRef window, Boolean &canSupport) |
{ |
OSStatus err = noErr; |
// Some day, when I'm really bored, I'll figure out all the compatibility nonsense |
// which will allow me to determine whether the controls within the visible region |
// of the given window are sufficiently unbuggy so as to support off-screen drawing. |
// (There are problems on older systems with the group box and edit text CDEFs.) For |
// now, we just determine whether the system is going to do the off-screen goo on our |
// behalf. |
canSupport = !QDIsPortBuffered (GetWindowPort (window)); |
return err; |
} |
static OSStatus UpdateAuntieDialog (WindowRef window) |
{ |
OSStatus err = noErr; |
GrafPtr preservedPort; |
GetPort (&preservedPort); |
SetPortWindowPort (window); |
BeginUpdate (window); |
do |
{ |
// |
// Our off-screen drawing scheme depends on DrawControlInCurrentPort. |
// Some older control definitions, however, do not properly support |
// drawing into ports other than their owners, so we're prepared to |
// do all our drawing on-screen even though it will flicker. |
// |
CGrafPtr windowPort = GetWindowPort (window); |
Boolean tryOffscreen = !QDIsPortBuffered (windowPort); |
if (tryOffscreen) |
{ |
err = ShouldDrawControlsOffScreen (window,tryOffscreen); |
if (err) break; |
} |
StRegion visibleRegion (err); |
if (err) break; |
(void) GetPortVisibleRegion (windowPort, visibleRegion); |
if (tryOffscreen) |
{ |
ControlRef rootControl; |
err = GetRootControl (window,&rootControl); |
if (err) break; |
Rect visibleRect; |
GetRegionBounds (visibleRegion,&visibleRect); |
StGWorld myGWorld (visibleRect,err); |
// don't bother to check err; better flicker than fail |
EraseRgn (visibleRegion); |
DrawControlInCurrentPort (rootControl); |
// StGWorld's destructor is called, blitting the window contents |
} |
else |
{ |
// Many controls smash the GrafPort state because, |
// according to a secret report written by the |
// Control Manager engineer and obtained by our staff |
// from reliable sources, "they suck". We don't need to |
// save any state for the off-screen case because we |
// don't care if the off-screen port's state gets wibbled. |
EraseRgn (visibleRegion); |
ThemeDrawingState drawingState; |
err = GetThemeDrawingState (&drawingState); |
if (err) break; |
UpdateControls (window,visibleRegion); |
err = SetThemeDrawingState (drawingState,true); |
if (err) break; |
} |
} |
while (false); |
EndUpdate (window); |
SetPort (preservedPort); |
return err; |
} |
static OSStatus ActivateAuntieDialog (WindowRef window, Boolean becomingActive) |
{ |
OSStatus err = noErr; |
do |
{ |
ControlRef rootControl; |
err = GetRootControl (window,&rootControl); |
if (err) break; |
GrafPtr preservedPort; |
GetPort (&preservedPort); |
SetPortWindowPort (window); |
// We temporarily hide the pen so we can muck with the window non-atomically |
// and then redraw it all at once double-buffered with UpdateAuntieDialog |
// at the end. This is much sweeter than that flickery old Dialog Manager. |
// Too bad DeactivateControl doesn't know the right thing to do for scroll |
// bars and isn't perfect on older systems. |
HidePen ( ); |
if (becomingActive) |
{ |
err = ActivateControl (rootControl); |
} |
else |
{ |
err = DeactivateControl (rootControl); |
} |
if (err) |
{ |
ShowPen ( ); |
SetPort (preservedPort); |
break; |
} |
ThemeBrush windowThemeBrush = becomingActive ? |
kThemeBrushDialogBackgroundActive : kThemeBrushDialogBackgroundInactive; |
err = SetThemeWindowBackground (window, windowThemeBrush, true); |
ShowPen ( ); |
SetPort (preservedPort); |
if (err) break; |
err = UpdateAuntieDialog (window); |
if (err) break; |
} |
while (false); |
return err; |
} |
static OSStatus ClickAuntieDialogInContent ( const EventRecord &event, |
WindowRef window, |
ControlRef &itemHit, |
FocusChangeValidationProcPtr fcvpp ) |
{ |
OSStatus err = noErr; |
itemHit = nil; |
GrafPtr preservedPort; |
GetPort (&preservedPort); |
SetPortWindowPort (window); |
Point localWhere = event.where; |
GlobalToLocal (&localWhere); |
SetPort (preservedPort); |
ControlPartCode cpc = FindControl (localWhere,window,&itemHit); |
if (kControlNoPart == cpc) |
{ |
// FindControl reports the control the mouse is over, |
// even when that control reports that none of its |
// parts are hit. We assume most developers don't |
// want to know when the user clicks inside a |
// control which claims it wasn't hit; for example, |
// the portion of a tabs controls which contains other |
// controls. |
itemHit = nil; |
} |
else do |
{ |
ControlRef focus; |
err = GetKeyboardFocus (window,&focus); |
if (err) break; |
if (focus != itemHit) |
{ |
UInt32 controlFeatures; |
err = GetControlFeatures (itemHit,&controlFeatures); |
if (err) break; |
if (kControlSupportsFocus & controlFeatures) |
{ |
if (kControlGetsFocusOnClick & controlFeatures) |
{ |
if (fcvpp) |
{ |
Boolean allowFocusChange = true; |
err = fcvpp (focus,&allowFocusChange); |
if (err || !allowFocusChange) break; |
} |
err = SetKeyboardFocus (window,itemHit,cpc); |
if (err) break; |
} |
} |
} |
cpc = HandleControlClick (itemHit,localWhere,event.modifiers,ControlActionUPP(-1)); |
if (kControlNoPart == cpc) |
{ |
itemHit = nil; |
} |
// We assume most developers do not want the icon CDEF |
// to cause a dialog item hit; it's just necessary because |
// of Dialog Manager history. Spoof away the icky behavior. |
// If the variant indicates tracking behavior, then we assume |
// the client really wants it. See DetermineControlParameters |
// for a note about the decisions we make while creating icon |
// dialog items; the no-track variants are actually quite |
// common without effort from developers. |
else if (kControlIconPart == cpc && (1 & GetControlVariant (itemHit))) |
{ |
Boolean isIcon; |
err = ControlIsIcon (itemHit,isIcon); |
if (err) break; |
if (isIcon) |
{ |
itemHit = nil; |
} |
} |
} |
while (false); |
return err; |
} |
static OSStatus ClickAuntieDialog ( const EventRecord &event, |
ControlRef &itemHit, |
FocusChangeValidationProcPtr fcvpp ) |
{ |
OSStatus err = noErr; |
WindowRef window = nil; |
if (inContent == FindWindow (event.where, &window)) |
{ |
err = ClickAuntieDialogInContent (event,window,itemHit,fcvpp); |
} |
return err; |
} |
static OSStatus IdleControlsInAllVisibleWindows (void) |
{ |
OSStatus err = noErr; |
WindowRef window = FrontWindow ( ); |
if (window) |
{ |
StRegion updateRgn (err); |
if (!err) do |
{ |
if (IsWindowVisible (window)) |
if (IsAuntieDialogWindow (window)) |
if (!IsWindowUpdatePending (window)) |
IdleControls (window); |
window = GetNextWindow (window); |
} |
while (window); |
} |
return err; |
} |
static OSStatus KeyAuntieDialog (const EventRecord &event, FocusChangeValidationProcPtr fcvpp) |
{ |
OSStatus err = noErr; |
WindowRef window = FrontWindow ( ); |
if (!window) |
err = paramErr; |
else do |
{ |
ControlRef focus; |
err = GetKeyboardFocus (window,&focus); |
if (err) break; |
if ((event.message & charCodeMask) != '\t') |
{ |
err = HandleControlKey (focus, SInt16 (event.message & keyCodeMask), SInt16 (event.message & charCodeMask), event.modifiers); |
if (err) break; |
} |
else if (focus) |
{ |
if (fcvpp) |
{ |
Boolean allowFocusChange = true; |
err = fcvpp (focus,&allowFocusChange); |
if (err || !allowFocusChange) break; |
} |
if (event.modifiers & shiftKey) |
{ |
err = ReverseKeyboardFocus (window); |
} |
else |
{ |
err = AdvanceKeyboardFocus (window); |
} |
if (err) break; |
} |
} |
while (false); |
return err; |
} |
static pascal OSStatus ControlHasDefaultTag (ControlRef candidate, Boolean *isDefault, void *) |
{ |
Size actualSize; |
OSStatus err = GetControlData |
(candidate, kControlNoPart, kControlPushButtonDefaultTag, |
sizeof (*isDefault), Ptr (isDefault), &actualSize); |
if (err == errDataNotSupported) |
{ |
*isDefault = false; |
err = noErr; |
} |
return err; |
} |
static OSStatus SetMinimalCursorRegion (Point where, RgnHandle rgn) |
{ |
if (!rgn) return nilHandleErr; |
Rect cursorRect = { where.v, where.h, SInt16 (where.v + 1), SInt16 (where.h + 1) }; |
RectRgn (rgn,&cursorRect); |
return QDError ( ); |
} |
static OSStatus SetAuntieDialogMouseRegionsEmpty (AuntieDialogStateP adStateP) |
{ |
OSStatus err = noErr; |
if (adStateP) |
{ |
err = SetAuntieDialogMouseRegionsEmpty (adStateP->next); |
if (!err && adStateP->mouseRgn) |
{ |
SetEmptyRgn (adStateP->mouseRgn); |
err = QDError ( ); |
} |
} |
return err; |
} |
static pascal OSStatus RemoveChildRegion ( ControlRef control, |
Boolean *stop, |
void *userData ) |
{ |
OSStatus err = noErr; |
do |
{ |
StRegion controlRgn (err); |
if (err) break; |
err = GetWholeControlRegion (control,controlRgn,false); |
if (err) break; |
DiffRgn (RgnHandle (userData), controlRgn, RgnHandle (userData)); |
err = QDError ( ); |
if (err) break; |
*stop = false; |
} |
while (false); |
return err; |
} |
static pascal OSStatus RegionIsUnderMouse ( ControlRef control, |
Boolean *stop, |
void *userData ) |
{ |
OSStatus err = noErr; |
do |
{ |
StRegion controlRgn (err); |
if (err) break; |
err = GetWholeControlRegion (control,controlRgn,false); |
if (err) break; |
if (PtInRgn (RegionIsUnderMouseStateP (userData)->localWhere, controlRgn)) |
{ |
if (RegionIsUnderMouseStateP (userData)->mouseRgn) |
{ |
CopyRgn (controlRgn,RegionIsUnderMouseStateP (userData)->mouseRgn); |
err = QDError ( ); |
if (err) break; |
} |
*stop = true; |
break; |
} |
else if (RegionIsUnderMouseStateP (userData)->mouseRgn) |
{ |
UnionRgn ( controlRgn, RegionIsUnderMouseStateP (userData)->mouseRgn, |
RegionIsUnderMouseStateP (userData)->mouseRgn ); |
err = QDError ( ); |
if (err) break; |
} |
*stop = false; |
} |
while (false); |
return err; |
} |
static pascal OSStatus MouseMovedOutsideFrontWindow (RgnHandle mouseRgn, WindowRef front) |
{ |
OSStatus err = noErr; |
do |
{ |
StRegion grayRgn (err, GetGrayRgn ( )); |
if (err) break; |
GDHandle mainDevice = GetMainDevice ( ); |
Rect mainDeviceRect = (**mainDevice).gdRect; |
StRegion mainDeviceRgn (err,mainDeviceRect); |
if (err) break; |
UnionRgn (grayRgn,mainDeviceRgn,mouseRgn); |
err = QDError ( ); |
if (err) break; |
StRegion contentRgn (err); |
if (err) break; |
err = GetWindowRegion (front,kWindowContentRgn,contentRgn); |
if (err) break; |
DiffRgn (mouseRgn,contentRgn,mouseRgn); |
err = QDError ( ); |
if (err) break; |
#if DEBUG_MOUSE_RGN |
ClipRect (&mainDeviceRect); |
DebugRegion (nil,mouseRgn); |
#endif |
} |
while (false); |
return err; |
} |
static pascal OSStatus MouseMovedInsideFrontWindow (Point where, RgnHandle mouseRgn, WindowRef window, Cursor &newCursorImage) |
{ |
OSStatus err; |
GrafPtr preservedPort; |
GetPort (&preservedPort); |
SetPortWindowPort (window); |
Point localWhere = where; |
GlobalToLocal (&localWhere); |
SetPort (preservedPort); |
do |
{ |
// |
// FindWindow reports inContent when the cursor is |
// inside the content region but outside the visible |
// region. This is too much of a corner case to bother |
// doing well. |
// |
StRegion visibleRegion (err); |
if (err) break; |
(void) GetPortVisibleRegion (GetWindowPort (window), visibleRegion); |
if (!PtInRgn (localWhere,visibleRegion)) |
{ |
if (mouseRgn) |
{ |
err = SetMinimalCursorRegion (where,mouseRgn); |
if (err) break; |
} |
} |
else |
{ |
// Neither FindControl nor FindControlUnderMouse does |
// what we need; there's a gap between the area a default |
// button reports as hittable and the outer edge of the |
// region the control reports it occupies. There's no way |
// for us to get this region in the general case, so we |
// ignore hittability until after we determine which control |
// we're hovering over. If we're hovering over no control, |
// then the cursor region needs to be the area occupied |
// by no controls. RegionIsUnderMouse does some tricky |
// stuff in concert with SearchControlHierarchy to make |
// this happen. |
if (mouseRgn) |
{ |
SetEmptyRgn (mouseRgn); |
err = QDError ( ); |
if (err) break; |
} |
ControlRef control; |
RegionIsUnderMouseState riumState = { mouseRgn, localWhere }; |
// NB: RegionIsUnderMouse *must* be aware that the mouse region may be nil! |
// As of this writing, all appears well in this regard. |
err = SearchControlHierarchy (window,RegionIsUnderMouse,&control,&riumState); |
if (err) break; |
if (mouseRgn) |
{ |
if (!control) |
{ |
StRegion visibleRegion (err); |
if (err) break; |
(void) GetPortVisibleRegion (GetWindowPort (window), visibleRegion); |
DiffRgn (visibleRegion,mouseRgn,mouseRgn); |
err = QDError ( ); |
if (err) break; |
#if DEBUG_MOUSE_RGN |
DebugRegion (window,mouseRgn); |
#endif |
err = LocalToGlobalRgn (window,mouseRgn); |
if (err) break; |
} |
else |
{ |
// Here we do a special hack for edit text controls. |
// We really need a way to send a message to a control |
// so it can set its own cursor, but, until we get that |
// message, we special-case this one control, because |
// it's the one we know needs an I-Beam cursor. But this |
// hack is even ickier than you would expect, since, as of |
// this writing, GetControlRegion returns errInvalidPartCode |
// when asked for kControlEditTextPart (Radar 2469443). |
// We're forced to fall back to the lame 1x1 cursor region. |
Boolean isEditText; |
err = ControlIsEditText (control,isEditText); |
if (err) break; |
if (isEditText) |
{ |
err = SetMinimalCursorRegion (where,mouseRgn); |
if (err) break; |
if (TestControl (control, localWhere) == kControlEditTextPart) |
{ |
newCursorImage = ** GetCursor (iBeamCursor); |
} |
} |
else |
{ |
err = SearchChildControls (control,RemoveChildRegion,nil,mouseRgn); |
if (err) break; |
StRegion visibleRegion (err); |
if (err) break; |
(void) GetPortVisibleRegion (GetWindowPort (window), visibleRegion); |
(void) SectRgn (visibleRegion,mouseRgn,mouseRgn); |
err = QDError ( ); |
if (err) break; |
#if DEBUG_MOUSE_RGN |
DebugRegion (window,mouseRgn); |
#endif |
err = LocalToGlobalRgn (window,mouseRgn); |
if (err) break; |
} |
} |
} |
} |
} |
while (false); |
return err; |
} |
static OSStatus MouseMovedAuntieDialog (const EventRecord &event, WindowRef front, AuntieDialogState &adState) |
{ |
OSStatus err = noErr; |
do |
{ |
err = SetAuntieDialogMouseRegionsEmpty (adState.next); |
if (err) break; |
Cursor newCursorImage; |
(void) GetQDGlobalsArrow (&newCursorImage); |
if (!front || !IsAuntieDialogWindow (front)) |
{ |
if (adState.mouseRgn) |
{ |
err = SetMinimalCursorRegion (event.where,adState.mouseRgn); |
if (err) break; |
} |
} |
else |
{ |
WindowRef window; |
short fwpc = FindWindow (event.where,&window); |
if (fwpc != inContent || window != front) |
{ |
err = MouseMovedOutsideFrontWindow (adState.mouseRgn,front); |
if (err) break; |
} |
else |
{ |
err = MouseMovedInsideFrontWindow (event.where,adState.mouseRgn,front,newCursorImage); |
if (err) break; |
} |
} |
SetCursor (&newCursorImage); |
} |
while (false); |
return err; |
} |
static OSStatus OSEventAuntieDialog (const EventRecord &event, AuntieDialogState &adState) |
{ |
OSStatus err = noErr; |
WindowRef front = FrontWindow ( ); |
UInt8 osMessage = UInt8 ((osEvtMessageMask & event.message) >> 24); |
if (mouseMovedMessage == osMessage) |
{ |
err = MouseMovedAuntieDialog (event,front,adState); |
} |
else if (suspendResumeMessage == osMessage) |
{ |
if (front && IsAuntieDialogWindow (front)) |
{ |
err = ActivateAuntieDialog (front, (resumeFlag & event.message) == resumeFlag); |
} |
} |
return err; |
} |
static OSStatus EstablishFirstFocusForFrontWindow (WindowRef window) |
{ |
OSStatus err = noErr; |
window = FrontWindow ( ); |
if (window && IsAuntieDialogWindow (window)) do |
{ |
ControlRef rootControl; |
err = GetRootControl (window,&rootControl); |
if (err) break; |
UInt32 actualSize; |
err = GetControlProperty ( rootControl, |
kAuntieDialogSignature, |
kAuntieDialogControlPropertyTagTriedFocus, |
0, &actualSize, |
Ptr (kBlessedBusErrorBait)); |
if (err == controlPropertyNotFoundErr) |
{ |
ControlRef focus; |
err = GetKeyboardFocus (window,&focus); |
if (err) break; |
if (!focus) |
{ |
err = AdvanceKeyboardFocus (window); |
if (err == errCouldntSetFocus) |
{ |
err = noErr; |
} |
else if (err) |
{ |
break; |
} |
err = SetControlProperty ( rootControl, |
kAuntieDialogSignature, |
kAuntieDialogControlPropertyTagTriedFocus, |
0, Ptr (kBlessedBusErrorBait) ); |
} |
} |
} |
while (false); |
return err; |
} |
static pascal OSStatus AuntieDialogSelectDispatch ( const EventRecord &event, |
ControlRef &itemHit, |
FocusChangeValidationProcPtr fcvpp, |
AuntieDialogState &adState ) |
{ |
OSStatus err = noErr; |
static const ProcessSerialNumber myPSN = { 0, kCurrentProcess }; |
WindowRef window = nil; |
switch (event.what) |
{ |
case keyDown : |
case autoKey : |
err = KeyAuntieDialog (event,fcvpp); |
break; |
case osEvt : |
err = OSEventAuntieDialog (event,adState); |
break; |
case activateEvt : |
err = ActivateAuntieDialog (WindowRef (event.message), event.modifiers & activeFlag ? true : false); |
break; |
case updateEvt : |
err = UpdateAuntieDialog (WindowRef (event.message)); |
if (err) break; |
// Force Process Manager to deliver one null |
// event as quickly as possible regardless |
// of the app's specified sleep tolerance |
// so that we'll get a chance to establish |
// focus a little more snappily than we |
// otherwise would. On a Quadra 800 running |
// 8.1 and no apps other than AuntieDialogTest |
// (I even quit the Finder), this makes us |
// up to 6 ticks snappier. If the machine is |
// actually doing something (I tested with |
// a large Finder copy), this can make us |
// an order of magnitude snappier (in other |
// words, it can buy us a whole second). |
err = WakeUpProcess (&myPSN); |
break; |
case mouseDown : |
err = ClickAuntieDialog (event,itemHit,fcvpp); |
break; |
case nullEvent : |
// Waiting to establish focus until now ensures |
// we will never draw a focus ring before its |
// owning control has been completely drawn. |
// Well, sort of. See Radar 2458453. |
err = EstablishFirstFocusForFrontWindow (window); |
break; |
} |
if (!err) |
{ |
if (!window) |
{ |
window = FrontWindow ( ); |
} |
while (window) |
{ |
if (IsAuntieDialogWindow (window)) |
{ |
ControlRef rootControl; |
err = GetRootControl (window,&rootControl); |
if (err) break; |
UInt32 ticksAtLastIdle, actualSize; |
err = GetControlProperty ( rootControl, |
kAuntieDialogSignature, |
kAuntieDialogControlPropertyTagTicksAtLastIdle, |
sizeof (ticksAtLastIdle), |
&actualSize, |
&ticksAtLastIdle ); |
if (err == controlPropertyNotFoundErr) |
{ |
ticksAtLastIdle = 0; |
err = noErr; |
} |
else if (!err && actualSize != sizeof (ticksAtLastIdle)) |
{ |
err = controlPropertyInvalid; |
} |
if (!err && TickCount ( ) > ticksAtLastIdle + AuntieDialogGetIdleInterval (window)) |
{ |
err = IdleControlsInAllVisibleWindows ( ); |
if (!err) |
{ |
ticksAtLastIdle = TickCount ( ); |
err = SetControlProperty ( rootControl, |
kAuntieDialogSignature, |
kAuntieDialogControlPropertyTagTicksAtLastIdle, |
sizeof (ticksAtLastIdle), |
&ticksAtLastIdle ); |
} |
} |
break; |
} |
do |
{ |
window = GetNextWindow (window); |
} |
while (window && !IsWindowVisible (window)); |
} |
} |
return err; |
} |
static OSStatus AuntieModalDialogMouseUpDown ( WindowRef clientWindow, |
EventRecord *event, |
ControlRef &itemHit, |
FocusChangeValidationProcPtr fcvpp, |
AuntieDialogState &adState ) |
{ |
OSStatus err = noErr; |
do |
{ |
WindowRef window; |
err = IsAuntieDialogEvent (event,&window); |
if (err) break; |
if (window == clientWindow) |
{ |
err = AuntieDialogSelectDispatch (*event,itemHit,fcvpp,adState); |
if (err) break; |
} |
else if (mouseDown == event->what) |
{ |
if (window) |
{ |
SysBeep (10); |
} |
else |
{ |
Rect dragWindowBounds; |
switch (FindWindow (event->where,&window)) |
{ |
case inMenuBar : |
SysBeep (10); |
break; |
case inDrag : |
if (window != clientWindow) |
SysBeep (10); |
else |
{ |
BitMap screenBits; |
GetQDGlobalsScreenBits (&screenBits); |
dragWindowBounds = screenBits.bounds; |
InsetRect (&dragWindowBounds,4,4); |
DragWindow (window,event->where,&dragWindowBounds); |
} |
break; |
} |
} |
} |
} |
while (false); |
return err; |
} |
static pascal OSStatus ControlNeedsIdle (ControlRef candidate, Boolean *needsIdle, void *) |
{ |
UInt32 features; |
OSStatus err = GetControlFeatures (candidate, &features); |
if (err) return err; |
*needsIdle = (kControlWantsIdle & features) == kControlWantsIdle; |
return err; |
} |
static OSStatus IsAuntieDialogNullEvent (WindowRef &window) |
{ |
OSStatus err = noErr; |
WindowRef front = FrontWindow ( ); |
if (front) |
{ |
if (IsAuntieDialogWindow (front)) do |
{ |
ControlRef rootControl; |
err = GetRootControl (front,&rootControl); |
if (err) break; |
UInt32 actualSize; |
err = GetControlProperty ( rootControl, |
kAuntieDialogSignature, |
kAuntieDialogControlPropertyTagTriedFocus, |
0, &actualSize, |
Ptr (kBlessedBusErrorBait)); |
if (err == controlPropertyNotFoundErr) |
{ |
window = front; |
err = noErr; |
break; |
} |
if (err) break; |
} |
while (false); |
if (!err && !window) |
{ |
WindowRef scanner = front; |
do |
{ |
ControlRef needsIdle; |
err = SearchControlHierarchy (scanner,ControlNeedsIdle,&needsIdle,nil); |
if (err) break; |
if (needsIdle) |
{ |
window = scanner; |
break; |
} |
do |
{ |
scanner = GetNextWindow (scanner); |
} |
while (scanner && (!IsWindowVisible (scanner) || !IsAuntieDialogWindow (scanner))); |
} |
while (scanner); |
} |
} |
return err; |
} |
static pascal OSStatus ControlHasCommand (ControlRef control, Boolean *stop, void *key) |
{ |
ControlID controlID; |
OSStatus err = GetControlID (control,&controlID); |
if (!err) |
{ |
const ControlID *keyP = (const ControlID *) key; |
*stop = keyP->signature == controlID.signature && keyP->id == controlID.id; |
} |
return err; |
} |
static OSStatus ReestablishFocusOnNextNullEvent (ControlRef rootControl) |
{ |
OSStatus err = RemoveControlProperty (rootControl,kAuntieDialogSignature,kAuntieDialogControlPropertyTagTriedFocus); |
if (err == controlPropertyNotFoundErr) |
{ |
err = noErr; |
} |
return err; |
} |
//////////////////////////////////////////////////////////////////////////////////////////////// |
#pragma mark - |
#pragma mark API ENTRY POINTS |
#pragma mark - |
//////////////////////////////////////////////////////////////////////////////////////////////// |
pascal OSStatus AuntieDialogSelect ( const EventRecord *event, |
RgnHandle mouseRgn, |
ControlRef *itemHit, |
FocusChangeValidationProcPtr fcvpp ) |
{ |
if (!event) return paramErr; |
OSStatus err = noErr; |
ControlRef dummyItemHit; |
if (!itemHit) itemHit = &dummyItemHit; |
*itemHit = nil; |
AuntieDialogState adState = { gAuntieDialogState, mouseRgn }; |
gAuntieDialogState = &adState; |
err = AuntieDialogSelectDispatch (*event,*itemHit,fcvpp,adState); |
gAuntieDialogState = adState.next; |
return err; |
} |
pascal OSStatus IsAuntieDialogEvent ( const EventRecord *event, |
WindowRef *window ) |
{ |
if (!event) return paramErr; |
OSStatus err = noErr; |
WindowRef dummyWindow; |
if (!window) window = &dummyWindow; |
*window = nil; |
UInt8 osMessage; |
switch (event->what) |
{ |
case nullEvent : |
err = IsAuntieDialogNullEvent (*window); |
break; |
case osEvt : |
osMessage = UInt8 ((osEvtMessageMask & event->message) >> 24); |
if (mouseMovedMessage != osMessage && suspendResumeMessage != osMessage) |
{ |
break; |
} |
*window = FrontWindow ( ); |
if (*window) |
{ |
if (!IsAuntieDialogWindow (*window)) |
{ |
*window = nil; |
} |
} |
break; |
case keyDown : |
case autoKey : |
*window = FrontWindow ( ); |
if (*window) |
{ |
if (!IsAuntieDialogWindow (*window)) |
{ |
*window = nil; |
} |
else if (!IsWindowHilited (*window)) // background app? |
{ |
*window = nil; |
} |
} |
break; |
case updateEvt : |
case activateEvt : |
*window = WindowRef (event->message); |
if (!IsAuntieDialogWindow (*window)) |
{ |
*window = nil; |
} |
break; |
case mouseDown : |
case mouseUp : |
switch (FindWindow (event->where, window)) |
{ |
case inContent : |
if (IsAuntieDialogWindow (*window)) break; |
if (FrontWindow ( ) == *window) break; |
// fall through |
default : |
*window = nil; |
break; |
} |
break; |
default : |
*window = nil; |
break; |
} |
return err; |
} |
pascal OSStatus StandardAuntieModalEventFilter ( WindowRef window, |
EventRecord *event, |
ControlRef *itemHit ) |
{ |
OSStatus err = noErr; |
if (event->what == keyDown) |
{ |
switch (charCodeMask & event->message) |
{ |
case 0x03 : // enter |
case 0x0D : // return |
err = SearchControlHierarchy (window,ControlHasDefaultTag,itemHit,nil); |
if (err) break; |
if (*itemHit) |
{ |
HiliteControl (*itemHit,1); |
UInt32 finalTicks; |
Delay (8,&finalTicks); |
HiliteControl (*itemHit,0); |
} |
break; |
} |
} |
return err; |
} |
pascal OSStatus AuntieModalDialog ( WindowRef clientWindow, |
ControlRef *itemHit, |
AuntieModalFilterProcPtr amfpp, |
FocusChangeValidationProcPtr fcvpp ) |
{ |
OSStatus err = noErr; |
*itemHit = nil; |
StRegion mouseRgn (err); |
if (!err) |
{ |
ControlRef dummyItemHit; |
if (!itemHit) |
{ |
itemHit = &dummyItemHit; |
} |
if (!amfpp) |
{ |
amfpp = StandardAuntieModalEventFilter; |
} |
SelectWindow (clientWindow); |
UInt32 caretTime = GetCaretTime ( ); |
AuntieDialogState adState = { gAuntieDialogState, mouseRgn }; |
gAuntieDialogState = &adState; |
do |
{ |
EventRecord event; |
(void) WaitNextEvent (everyEvent,&event,caretTime,mouseRgn); |
*itemHit = nil; |
err = amfpp (clientWindow,&event,itemHit); |
if (err || *itemHit) break; |
if (event.what == mouseDown || event.what == mouseUp) |
{ |
err = AuntieModalDialogMouseUpDown (clientWindow,&event,*itemHit,fcvpp,adState); |
} |
else |
{ |
WindowRef window; |
err = IsAuntieDialogEvent (&event,&window); |
if (err) break; |
if (window) |
{ |
err = AuntieDialogSelectDispatch (event,*itemHit,fcvpp,adState); |
if (err) break; |
} |
} |
} |
while (!*itemHit); |
gAuntieDialogState = adState.next; |
} |
return err; |
} |
pascal OSStatus CountControlsInWindow (WindowRef window, UInt16 *result) |
{ |
OSStatus err = noErr; |
do |
{ |
ControlRef root; |
err = GetRootControl (window,&root); |
if (err) break; |
*result = 0; |
err = CountSubControlsDeep (root,result); |
if (err) break; |
} |
while (false); |
return err; |
} |
pascal OSStatus ReestablishFocusOnNextNullEvent (WindowRef window) |
{ |
OSStatus err = noErr; |
ControlRef rootControl; |
err = GetRootControl (window,&rootControl); |
if (!err) |
{ |
err = ReestablishFocusOnNextNullEvent (rootControl); |
} |
return err; |
} |
pascal OSStatus DisposeChildControls (ControlRef parent) |
{ |
// |
// Because of the wacky way in which some controls create |
// other controls (case in point: list box), we need to |
// be careful to delete only the "parent" controls and |
// let them delete their own "child" controls. (Under some |
// versions of Control Manager, the "child" controls are |
// not children according to the embedding hierarchy, |
// hence the scare quotes I'm using to describe them.) |
// In order to avoid knowing anything about how the "parent" |
// controls relate to the "child" controls within the |
// embedding hierarchy, we do some tricks with iteration. |
// |
// There's another tricky thing going on in this function. |
// (Sorry; I know that exceeds my per-function tricky thing |
// allotment, but there didn't seem to be an alternative.) |
// Instead of deleting the controls outright at the end, |
// we first gather up the regions of each control and all |
// of its children, mark each control as hidden without |
// allowing it to erase itself, and then invalidate the |
// gathered regions. This means all drawing which results |
// from the deletion of these controls will be done during |
// the next update event, which means we can erase the old |
// controls at the same time we draw the new ones, which |
// looks sweet if the drawing occurs off-screen. |
// |
OSStatus err = noErr; |
WindowRef parentWindow = GetControlOwner (parent); |
do |
{ |
ControlRef focus; |
err = GetKeyboardFocus (parentWindow, &focus); |
if (err) break; |
StRegion controlsRgn (err); |
if (err) break; |
StRegion childRgn (err); |
if (err) break; |
UInt16 index; |
err = CountSubControls (parent,&index); |
if (err) break; |
while (index) |
{ |
ControlRef child; |
err = GetIndexedSubControl (parent,index,&child); |
if (err) break; |
if (IsControlVisible (child)) |
{ |
err = GetWholeControlRegion (child,childRgn,true); |
if (err) break; |
UnionRgn (childRgn,controlsRgn,controlsRgn); |
err = QDError ( ); |
if (err) break; |
err = SetControlVisibility (child,false,false); |
if (err) break; |
} |
Boolean controlIsOurs; |
err = IsControlOurs (child,controlIsOurs); |
if (err) break; |
if (!controlIsOurs) |
{ |
--index; |
} |
else |
{ |
DisposeControl (child); |
err = CountSubControls (parent,&index); |
if (err) break; |
} |
} |
if (err) break; |
// |
// Some controls (I won't name any names, Mr. List Box) |
// are in the habit of under-reporting their regions. |
// Rather than attempting to detect which ones lie and |
// which ones tell the truth, we assume they are all |
// lying. It's OK to invalidate a few pixels too many. |
// |
InsetRgn (controlsRgn,-1,-1); |
err = QDError ( ); |
if (err) break; |
InvalWindowRgn (parentWindow,controlsRgn); |
err = QDError ( ); |
if (err) break; |
err = ReestablishFocusOnNextNullEvent (parentWindow); |
if (err) break; |
} |
while (false); |
return err; |
} |
pascal OSStatus AppendDialogItemsAsControls ( short resID, |
WindowRef window, |
ControlCreationProcPtr ccpp, |
void *ccppUserData ) |
{ |
OSStatus err = noErr; |
do |
{ |
ControlRef rootControl; |
err = GetRootControl (window,&rootControl); |
if (err) break; |
err = AppendDialogItemsAsControls (resID,window,rootControl,ccpp,ccppUserData); |
if (err) break; |
err = ReestablishFocusOnNextNullEvent (rootControl); |
if (err) break; |
} |
while (false); |
return err; |
} |
pascal OSStatus NewAuntieDialog |
(short resID, WindowRef window, |
ControlCreationProcPtr ccpp, void *ccppUserData) |
{ |
OSStatus err = noErr; |
do |
{ |
ThemeBrush windowThemeBrush = ChooseInitialWindowThemeBrush (window); |
err = SetThemeWindowBackground (window, windowThemeBrush, false); |
if (err) break; |
GrafPtr preservedPort; |
GetPort (&preservedPort); |
SetPortWindowPort (window); |
TextFont (0); |
SetPort (preservedPort); |
ControlRef root; |
err = CreateRootControl (window,&root); |
if (err) break; |
SetControlTitle (root,"\proot"); |
err = AppendDialogItemsAsControls (resID,window,root,ccpp,ccppUserData); |
if (err) break; |
} |
while (false); |
return err; |
} |
pascal OSStatus GetAuntieDialog |
(short resID, WindowRef behind, |
ControlCreationProcPtr ccpp, void *ccppUserData, |
WindowRef *result) |
{ |
OSStatus err = noErr; |
Boolean shouldBeVisible; |
err = NewAuntieWindowFromDialogResource (resID,behind,shouldBeVisible,result); |
if (!err) |
{ |
err = NewAuntieDialog (resID,*result,ccpp,ccppUserData); |
if (err) |
{ |
DisposeWindow (*result); |
*result = nil; |
} |
else if (shouldBeVisible) |
{ |
ShowWindow (*result); |
} |
} |
return err; |
} |
pascal OSStatus SetDefaultControl (ControlRef control, Boolean willBeDefault) |
{ |
OSStatus err = noErr; |
if (!control) |
{ |
err = paramErr; |
} |
else |
{ |
err = SetControlData (control, kControlNoPart, kControlPushButtonDefaultTag, sizeof (willBeDefault), Ptr (&willBeDefault)); |
} |
return err; |
} |
pascal OSStatus SearchControlHierarchy |
(WindowRef window, ControlHierarchySearchProcPtr chspp, ControlRef *found, void *userData) |
{ |
if (!chspp) return paramErr; |
OSStatus err = noErr; |
do |
{ |
ControlRef rootControl; |
err = GetRootControl (window,&rootControl); |
if (err) break; |
err = SearchChildControls (rootControl,chspp,found,userData); |
if (err) break; |
} |
while (false); |
return err; |
} |
pascal OSStatus InvalControl (ControlRef control, Boolean includeChildren) |
{ |
OSStatus err = noErr; |
do |
{ |
StRegion controlRgn (err); |
if (err) break; |
err = GetWholeControlRegion (control,controlRgn,includeChildren); |
if (err) break; |
// |
// Some controls (I won't name any names, Mr. List Box) |
// are in the habit of under-reporting their regions. |
// Rather than attempting to detect which ones lie and |
// which ones tell the truth, we assume they are all |
// lying. It's OK to invalidate a few pixels too many, |
// expecially if you have double-buffered updates. |
// |
InsetRgn (controlRgn,-1,-1); |
err = QDError ( ); |
if (err) break; |
InvalWindowRgn (GetControlOwner (control), controlRgn); |
err = QDError ( ); |
if (err) break; |
} |
while (false); |
return err; |
} |
pascal UInt32 AuntieDialogGetIdleInterval (WindowRef) |
{ |
return GetCaretTime ( ) - 1; |
} |
pascal OSStatus AuntieDialogDisableControl (ControlRef control) |
{ |
Boolean isScrollBar; |
OSStatus err = ControlIsScrollBar (control, isScrollBar); |
if (!err) |
{ |
if (isScrollBar) |
{ |
HiliteControl (control,254); |
} |
else |
{ |
HiliteControl (control,255); |
} |
} |
return err; |
} |
pascal OSStatus AuntieDialogEnableControl (ControlRef control) |
{ |
HiliteControl (control,0); |
return noErr; |
} |
pascal OSStatus AuntieDialogEnableDisableControl (ControlRef control, Boolean enableDisable) |
{ |
return enableDisable ? AuntieDialogEnableControl (control) : AuntieDialogDisableControl (control); |
} |
Copyright © 2003 Apple Computer, Inc. All Rights Reserved. Terms of Use | Privacy Policy | Updated: 2003-01-30