/* * TransSkel - Transportable Macintosh application skeleton * Release 3.17 * * Please report problems to Paul DuBois. * * * TransSkel is in the public domain and was originally written by: * * Paul DuBois * Wisconsin Regional Primate Research Center * 1220 Capitol Court * Madison, WI 53715-1299 USA * * Internet: dubois@primate.wisc.edu * * Additional changes were made by: * * Owen Hartnett * OHM Software Company * 163 Richard Drive * Tiverton, RI 02878 USA * * Internet: omh@cs.brown.edu * UUCP: uunet!brunix!omh * * Owen is also responsible for the port to THINK Pascal. * * Bob Schumaker joined the cast between versions 2.0 and 3.0. * * Bob Schumaker * The AMIX Corporation * 1881 Landings Drive * Mountain View, CA 94043-0848 * * Internet: bob@markets.amix.com * UUCP: {sun, uunet, netcom}!markets!bob * CIS: 72227,2103 * AOL: BSchumaker * * This version of TransSkel is written for THINK C 6.0.1. * THINK C is a trademark of: * * Symantec Corporation * 10201 Torre Avenue * Cupertino, CA 95014 USA * * Reference key: * IM Inside Macintosh * TN Macintosh Technical Notes * MHIG Macintosh Human Interface Guidelines * HIN Human Interface Notes * PGMF Programmer's Guide to MultiFinder (APDA) * TPN TransSkel Programmer's Notes * * Recent history is given below. Earlier change history is in TSHistory. * If you've been writing applications with an earlier release, READ THAT FILE! * * 06 Feb 94 Release 3.10 * - Modified entire library source so that it can be compiled with either * C-compatible or Pascal-compatible bindings. This allows a binary library * to be compiled that can be linked into THINK Pascal applications while * preserving compatibility with existing TransSkel C applications. This * modification was suggested by Lionel Cons, who also contributed a working * prototype and the major portion of the Pascal header file. * - Fixed cast bug in SkelEventLoop(). * 07 Feb 94 * - SkelApple() now takes an empty string or nil to indicate no application item, * rather than nil only. * 08 Feb 94 * - SkelPositionWindow() positions windows using the structure rectangle rather * than the content rectangle now. Give better positioning. * * 17 Feb 94 Release 3.11 * - New function SkelTestRectVisible() to check whether or not a * rectangle is completely contained within the desktop region. * - SkelAlert() uses SkelTestRectVisible() to check whether an alert will be * entirely visible when it's supposed to be positioned on the parent window. * If not, it's positioned on the parent device instead. * - When position type is skelPositionOnParentWind, SkelGetReferenceRect() now * returns the structure rectangle rather than the content rectangle. * - SkelPositionWindow() now accounts for difference between width of structure * and content rectangle of window to be positioned. * 20 Feb 94 * - Pascal bindings for the interface functions are now standard. C bindings * are no longer used. * * 14 Apr 94 Release 3.12 * - Added SkelDlogTracksCursor() function. * * 22 Apr 94 Release 3.13 * - New function SkelGetWindPropData(). Note caveat in comment preceding * function definition. * 23 Apr 94 * - Started adding support for movable modal dialogs. * - Added variable hasGetWVariant used to tell if GetWVariant() exists * for determining whether a dialog is a movable modal or not. * - Standard dialog filter passes command-clicks in drag region of underlying * windows to TransSkel's event router. * 25 Apr 94 * - Command-period isn't passed to menu routines any more. Instead the key * handler is called. The key handler can notice the Cancel and respond * accordingly if it wants to. * - All dialog events for modeless and movable modeless dialog are now handled * in SkelRouteEvent(). (Null events, mouse and key clicks, updates, activates.) * - Since dialog support is now integrated into the main event routing, the * old DoDialog() function obsolete. * - The supportDialogs #define is also obsolete. * - The dialog event mask is also obsolete, so SkelSetDlogMask() and * SkelGetDlogMask() are obsolete, too, and have been dropped. * - Command-clicks in drag region of underlying windows while movable modal * dialog is frontmost are processed. * 26 Apr 94 * - New routines SkelIsDlog() and SkelIsMMDlog() for determining whether a * window is a dialog or movable modal dialog, respectively. * - Movable modal dialogs can now be registered with SkelDialog(). The property * added by the routine is now skelWPropModeless or skelWPropMovableModal depending * on the type of dialog window. * - Renamed SkelRouteEvent() to RouteEvent(). SkelRouteEvent() is now a call * to RouteEvent() plus the stuff that used to be in SkelEventLoop() for polling * window-specific idle-time procedures. The effect is that idle procedures that * are supposed to run even for windows that are not frontmost no longer freeze * when a modal dialog came up (if you use SkelDlogFilter()). * 27 Apr 94 * - Dropped the distinctions between GetWDHandler(), GetDHandler(), and * GetWHandler(). With more pervasive dialog support, the instances where it * really matters whether a dialog or non-dialog window is being handled are * rare. GetWHandler() now suffices everywhere. Renamed DetachWDHandler() and * oldWDHandler to DetachWHandler() and oldWHandler for consistency. * - GetWHandler() now returns faster when the argument is nil. * - Revised the argument structure of SkelDialog() to allow an event filter * to be specified. * - Bug fix: SkelAddWindProp() was passing propData as the propType argument * to SkelGetWindProp() when checking to see if a property of the given type * already existed. * 29 Apr 94 * - New routine SkelDlogMapKeyToButton() for helping to map return, enter, * escape and command-period to default or cancel button in dialogs. * - Window-specific idle-time routines now run when application is in background. * 02 May 94 Release 3.14 * Changed the skelResumeProc member of SkelInitParams structure from type * ResumeProcPtr to type SkelResumeProcPtr. ResumeProcPtr is disappearing from * Apple's header files, since you're only supposed to pass nil to InitDialogs() * under System 7 now. This should help TransSkel compile under THINK C 7. * * 03 May 94 Release 3.15 * - Fixed bug in SkelDlogFilter.c/ModalFilterYD() causing machine lockup * if SkelDlogFilterYD() was used. * * 04 May 94 Release 3.16 * - Fixed bug in RouteEvent() causing crashes if application idle-time function * changed the front window. * * 15 May 94 Release 3.17 * - Changed references to QuickDraw globals so they're written in terms of the * qd struct. E.g., thePort -> qd.thePort, gray -> qd.gray. * - Redid some pattern references so they'll compile whether or not * dangerousPattern is defined, and if universal headers are used. * 25 May 94 * - Try to cast the argument to InitDialogs() properly depending on whether or * not the universal headers are used. */ # include # include # include /* * TransSkel1.h contains defines, typedefs, and public function * prototypes */ # include "TransSkel1.h" /**** use modified header file -- L. Tierney */ /* * New(TypeName) returns handle to new object, for any TypeName. * If there is insufficient memory, the result is nil. */ # define New(type) (type **) NewHandle ((Size) sizeof (type)) /* -------------- */ /* Internal types */ /* -------------- */ /* * Private data types for window and menu handlers */ typedef struct WHandler WHandler, *WHPtr, **WHHandle; struct WHandler { WindowPtr whWind; /* window/dialog to handle */ SkelWindMouseProcPtr whMouse; /* mouse-click handler */ SkelWindKeyProcPtr whKey; /* key-click handler */ SkelWindUpdateProcPtr whUpdate; /* update handler */ SkelWindActivateProcPtr whActivate; /* activate event handler */ SkelWindCloseProcPtr whClose; /* close "event" handler */ SkelWindClobberProcPtr whClobber; /* window disposal proc */ SkelWindIdleProcPtr whIdle; /* main loop idle proc */ SkelWindZoomProcPtr whZoom; /* zoom proc */ SkelWindSelectProcPtr whSelect; /* item selection proc (dialog) */ ModalFilterProcPtr whFilter; /* event filter proc (dialog) */ Rect whGrow; /* limits on window sizing */ Boolean whSized; /* true = window was resized */ Boolean whFrontOnly; /* idle only when window active */ short whFlags; /* various flags */ SkelWindPropHandle whProperties; /* property list */ WHHandle whNext; /* next window handler */ }; typedef struct MHandler MHandler, *MHPtr, **MHHandle; struct MHandler { short mhID; /* menu id */ SkelMenuSelectProcPtr mhSelect; /* item selection handler */ SkelMenuClobberProcPtr mhClobber; /* menu disposal proc */ Boolean mhSubMenu; /* whether submenu */ MHHandle mhNext; /* next menu handler */ }; /* ------------------------------------------- */ /* Prototypes for internal (private) functions */ /* ------------------------------------------- */ static WHHandle GetWHandler (WindowPtr w); static void DetachWHandler (WHHandle wh); static void RouteEvent (EventRecord *evt); static void DoMenuCommand (long command); static void DoMenuHook (void); static void DoMouse (WHHandle h, EventRecord *evt); static void DoKey (WHHandle h, char ch, unsigned char code, short mods); static void DoUpdate (EventRecord *evt); static void DoActivate (EventRecord *evt); static void DoClose (WHHandle h); static void DoClobber (WHHandle h); static void DoDlogEvt (DialogPtr dlog, EventRecord *evt); static Boolean DoDlogFilter (DialogPtr dlog, EventRecord *evt); static void DoGrow (WHHandle h, Point startPt); static void DoZoom (WHHandle h, short partCode); /* ------------------ */ /* Internal variables */ /* ------------------ */ /* * Window and menu handler variables. * * whList and mhList are the lists of window and menu handlers. * mhClobOnRmve is true if the menu handler disposal proc * is to be called when a handler is removed. It is temporarily set * false when handlers are installed for menus that already * have handlers - the old handler is removed WITHOUT calling the * disposal proc. The effect is to replace the handler for the menu * without destroying the menu itself. * * dragRect determines the limits on window dragging. It is set in * SkelInit() to the bounding box of the desktop region inset by 4 pixels. * * growRect contains the default limits on window sizing. It is set in * SkelInit(). The lower limits on window sizing of 80 pixels both directions * is sufficient to allow text windows room to draw a grow box and scroll * bars without having the thumb and arrows overlap. The upper limits are * determined from the screen size. (Probably incorrectly for the case of > 1 * screen.) * These default values may be changed if with SkelGrowBounds if they are * not appropriate. * * zoomProc is the default zoom procedure to use if the window does not have * one of its own. zoomProc may be nil, in which case the default is to zoom * to just about full window size. * * mhDrawBarOnRmve determines whether the menu bar is redrawn by * SkelRmveMenu() after taking a menu out of the menu bar. Normally * it's true, but SkelClobber() sets it false temporarily to avoid * flicker as each menu is removed. */ static WHHandle whList = (WHHandle) nil; static Rect dragRect; static Rect growRect; static SkelWindZoomProcPtr zoomProc = (SkelWindZoomProcPtr) nil; static MHHandle mhList = (MHHandle) nil; static Boolean mhClobOnRmve = true; static Boolean mhDrawBarOnRmve = true; /* * Miscellaneous * * - skelEnv contains SysEnvirons() information. * - sysVersion contains the system software version. * - hasGestalt is true if Gestalt() is supported. * - has64KROM is true if the current machine has the 64K ROM. * - hasGetWVariant is true if GetWVariant() is supported. * - mBarHeight is menu bar height. Window sizing, zooming and dragging * code takes this into account. Initialized in SkelInit(), which see * for teeth-gnashing over such a simple thing. * - doneFlag determines when SkelEventLoop() returns. It is set by calling * SkelStopEventLoop(), which is how the host requests a halt. * - pIdle points to a background procedure, to be run during event * processing. Set it with SkelSetIdle(). If nil, there's no * procedure. * - pEvent points to an event-inspecting hook, to be run whenever an * event occurs. Set it with SkelSetEventHook(). If nil, there's no * procedure. * - eventMask controls the event types requested by GetNextEvent() or * WaitNextEvent() in SkelEventLoop(). * - pMenuHook points to a procedure called whenever a menu selection is about * to be executed. nil if no hook. * - diskInitPt is the location at which the disk initialization dialog * appears, if an uninitialized disk is inserted. * - eventModifiers is the value of the modifiers field of the current event. * - eventPtr points to the current event (nil if none seen yet). * - defInitParams contains the default SkelInit() parameters if caller passes * nil. */ static SysEnvRec skelEnv; static long sysVersion = 0; static Boolean hasGestalt; static Boolean has64KROM; static Boolean hasGetWVariant; static short mBarHeight; static short doneFlag; static short eventMask = everyEvent ^ keyUpMask; static short eventModifiers = 0; static EventRecord *eventPtr = (EventRecord *) nil; static Point diskInitPt = { /* v = */ 120, /* h = */ 100 }; static SkelIdleProcPtr pIdle = (SkelIdleProcPtr) nil; static SkelEventHookProcPtr pEvent = (SkelEventHookProcPtr) nil; static SkelMenuHookProcPtr pMenuHook = (SkelMenuHookProcPtr) nil; static SkelInitParams defInitParams = { 6, /* no. of times to call MoreMasters() */ (GrowZoneProcPtr) nil, /* GrowZone proc */ (SkelResumeProcPtr) nil, /* resume proc */ 0L /* stack adjustment */ }; /* * Multitasking support stuff * * hasWNE is true if WaitNextEvent() is available. * * inForeground is true if application is running in foreground (not * suspended). Initially true, per PGMF 3-1. * * getFrontClicks indicates whether the application wants to receive * content-area clicks that bring it to the foreground. * * fgWaitTime and bgWaitTime are WaitNextEvent() times for foreground and * background states. */ static Boolean hasWNE; static Boolean inForeground = true; static long fgWaitTime = 6L; /* 0.1 seconds */ static long bgWaitTime = 300L; /* 5.0 seconds */ static Boolean getFrontClicks = false; static SkelSuspendResumeProcPtr pSuspendResume = (SkelSuspendResumeProcPtr) nil; static SkelClipCvtProcPtr pClipCvt = (SkelClipCvtProcPtr) nil; static WindowPtr oldWindow = (WindowPtr) nil; static WHHandle oldWHandler = (WHHandle) nil; /* * Apple Event support */ static Boolean hasAppleEvents = 0; static SkelAEHandlerProcPtr pAEHandler = (SkelAEHandlerProcPtr) nil; /* --------------------------- */ /* Initialization and shutdown */ /* --------------------------- */ /* * Initialize the various Macintosh Managers and lots of other stuff. * * FlushEvents does NOT toss disk insert events; this is so disks * inserted while the application is starting up don't result * in dead drives. * * initParams contains initialization parameters: * - the number of times to call MoreMasters * - the address of a grow zone procedure to call if memory allocation * problems occur (nil if none to be used) * - the address of a resume procedure to pass to InitDialogs() * (nil if none is to be used) * - amount to adjust the application stack size by (default 0; no adjustment) * * if initParams is nil, defaults are used. */ pascal void SkelInit (SkelInitParamsPtr initParams) { EventRecord dummyEvent; Handle h; long result; short i; if (initParams == (SkelInitParams *) nil) initParams = &defInitParams; if (initParams->skelGzProc != (GrowZoneProcPtr) nil) /* It would be better to deallocate this UPP, but isn't worth the effort */ SetGrowZone (NewGrowZoneProc((ProcPtr) initParams->skelGzProc)); SetApplLimit (GetApplLimit () - initParams->skelStackAdjust); MaxApplZone (); for (i = 0; i < initParams->skelMoreMasters; i++) MoreMasters (); FlushEvents (everyEvent - diskMask, 0 ); InitGraf (&thePort); InitFonts (); InitWindows (); InitMenus (); TEInit (); /* * Cast argument according to whether or not universal headers are used */ InitDialogs (initParams->skelResumeProc); InitCursor (); (void) SysEnvirons (1, &skelEnv); sysVersion = (long) skelEnv.systemVersion; has64KROM = (skelEnv.machineType == envMac || skelEnv.machineType == envXL); /* * If 64K ROM machine, use hard-coded value of 20. Otherwise use * Script Manager routine GetMBarHeight(). (This assumes, just to be * safe, that GetMBarHeight() glue doesn't return 20 on 64K ROM systems, * which it very well may. The low memory variable MBarHeight (0x0BAA) * isn't used because it doesn't exist on 64K ROM machines (TN OV 4, p.7). */ mBarHeight = (has64KROM ? 20 : GetMBarHeight ()); /* * Determine whether WaitNextEvent() is implemented (TN's OV 16 and TN TB 14) */ if (has64KROM) hasWNE = false; else hasWNE = SkelTrapAvailable (_WaitNextEvent); hasGestalt = SkelTrapAvailable (_Gestalt); hasAppleEvents = hasGestalt && Gestalt (gestaltAppleEventsAttr, &result) == noErr && (result & (1 << gestaltAppleEventsPresent)); /* * Determine whether GetWVariant() exists for checking whether a dialog is * a movable modal or not. The variant code can be gotten other ways, but * the existence of trap precedes the existence of movalable modal windows, * so if the trap doesn't exist, movable modals aren't likely to, either. */ hasGetWVariant = SkelTrapAvailable (_GetWVariant); /* * Check whether application wants to get "bring to front" clicks. */ if ((h = GetResource ('SIZE', -1)) != (Handle) nil) { getFrontClicks = (((**(short **) h) & 0x200) != 0); ReleaseResource (h); } /* * Window dragging limits are determined from bounding box of desktop. * Upper limits of window sizing are related to that. Both can involve * multiple monitors, and should allow for menu bar. dragRect is inset * so as to leave at least 4 pixels of window title bar visible in both * directions (IM I-289). * * GetGrayRgn() bounding box gives desktop extents. On 64K ROM * machines, GetGrayRgn() might not be present; could use GrayRgn * bounding box, but use qd.screenBits.bounds - menu bar, to avoid * low memory access. The two should be equivalent. */ if (has64KROM) { dragRect = screenBits.bounds; dragRect.top += mBarHeight; } else { /* GetGrayRgn () already takes menu bar into account */ dragRect = (**GetGrayRgn ()).rgnBBox; } SetRect (&growRect, 80, 80, dragRect.right - dragRect.left, dragRect.bottom - dragRect.top); InsetRect (&dragRect, 4, 4); /* let application come to front in multitasking environment, TN TB 35, p.8 */ (void) EventAvail (everyEvent, &dummyEvent); (void) EventAvail (everyEvent, &dummyEvent); (void) EventAvail (everyEvent, &dummyEvent); (void) EventAvail (everyEvent, &dummyEvent); } /* * Copy the default initialization parameters into the structure * pointed to by initParams. */ pascal void SkelGetInitParams (SkelInitParamsPtr initParams) { *initParams = defInitParams; } /* * Clobber all the menu, window and dialog handlers. Tell SkelRmveMenu() * not to redraw menu bar so it doesn't flicker as menus are removed, * then redraw it manually. * * Before removing window handlers, hide all the windows. Do this from * back to front (more esthetic and speedier). If a window belongs to a DA, * close the DA. (For early systems (e.g., 4.1), if you leave a DA open, * the system crashes the next time you try to open that DA.) */ pascal void SkelCleanup (void) { Boolean oldFlag; short theKind; WindowPeek w; WindowPtr lastVis; for (;;) { lastVis = (WindowPtr) nil; for (w = (WindowPeek) FrontWindow (); w != (WindowPeek) nil; w = w->nextWindow) { if (w->visible) lastVis = (WindowPtr) w; } if (lastVis == (WindowPtr) nil) /* no more visible windows */ break; if (lastVis != (WindowPtr) nil) { theKind = ((WindowPeek) lastVis)->windowKind; if (theKind < 0) /* DA, close it */ CloseDeskAcc (theKind); else HideWindow (lastVis); } } while (whList != (WHHandle) nil) SkelRmveWind ((**whList).whWind); oldFlag = mhDrawBarOnRmve; mhDrawBarOnRmve = false; while (mhList != (MHHandle) nil) SkelRmveMenu (GetMenuHandle((**mhList).mhID)); mhDrawBarOnRmve = oldFlag; DrawMenuBar (); } /* ----------------------------------- */ /* Execution environment interrogation */ /* ----------------------------------- */ #define trapMask 0x0800 static short NumToolboxTraps (void) { if (NGetTrapAddress (_InitGraf, ToolTrap) == NGetTrapAddress (0xaa6e, ToolTrap)) return (0x200); return (0x400); } static TrapType GetTrapType (short theTrap) { return ((theTrap & trapMask) ? ToolTrap : OSTrap); } pascal Boolean SkelTrapAvailable (short theTrap) { TrapType tType; if ((tType = GetTrapType (theTrap)) == ToolTrap) { theTrap &= 0x07ff; if (theTrap >= NumToolboxTraps ()) theTrap = _Unimplemented; } return (NGetTrapAddress (theTrap, tType) != NGetTrapAddress (_Unimplemented, ToolTrap)); } /* * Query the TransSkel execution environment. Shouldn't be called until * after SkelInit() has been called. Result is undefined if selector isn't * legal. */ pascal long SkelQuery (short selector) { long result; Rect r; RgnHandle rgn; switch (selector) { case skelQVersion: result = ((long) skelMajorRelease << 16) | skelMinorRelease; break; case skelQSysVersion: result = sysVersion; break; case skelQHasWNE: result = hasWNE ? 1 : 0; break; case skelQHas64KROM: result = has64KROM ? 1 : 0; break; case skelQMBarHeight: result = mBarHeight; break; case skelQHasColorQD: result = skelEnv.hasColorQD ? 1 : 0; break; case skelQQDVersion: /* get QuickDraw version number */ if (!hasGestalt || Gestalt (gestaltQuickdrawVersion, &result) != noErr) result = 0; /* assume original QuickDraw */ break; case skelQInForeground: result = inForeground ? 1 : 0; break; case skelQHasGestalt: result = hasGestalt ? 1 : 0; break; case skelQHasAppleEvents: result = hasAppleEvents ? 1 : 0; break; case skelQGrayRgn: rgn = NewRgn (); if (rgn != (RgnHandle) nil) { if (has64KROM) { r = screenBits.bounds; r.top += mBarHeight; RectRgn (rgn, &r); } else { /* GetGrayRgn () already takes menu bar into account */ CopyRgn (GetGrayRgn (), rgn); } } result = (long) rgn; break; default: /* result is undefined! */ break; } return (result); } /* ------------------------------------- */ /* Event loop initiation and termination */ /* ------------------------------------- */ /* * Main event loop. * * - Take care of DA's with SystemTask() if necessary. * - Get an event. * - Pass event to event router. * * doneFlag is restored to its previous value upon exit. This allows * SkelEventLoop() to be called recursively. */ pascal void SkelEventLoop (void) { EventRecord evt; Boolean oldDoneFlag; long waitTime; oldDoneFlag = doneFlag; /* save in case this is a recursive call */ doneFlag = false; /* set for this call */ while (!doneFlag) { if (hasWNE) { waitTime = (inForeground ? fgWaitTime : bgWaitTime); (void) WaitNextEvent (eventMask, &evt, waitTime, nil); } else { /* * On some early versions of the system software, it cannot * be assumed that the event contains a null event if the * GetNextEvent() return value is false. GetNextEvent() calls * SystemEvent() to handle some DA events, and returns false * if the event was handled. However, in such cases the event * record may still have the event that occurred, *not* a null * event. To avoid problems later with misinterpreting the * event as non-null, force it to look like a null event. */ SystemTask (); if (!GetNextEvent (eventMask, &evt)) evt.what = nullEvent; } SkelRouteEvent (&evt); } doneFlag = oldDoneFlag; /* restore in case this was a recursive call */ } /* * Tell current instance of SkelEventLoop() to drop dead */ pascal void SkelStopEventLoop (void) { doneFlag = true; } /* ----------------- */ /* Event dispatching */ /* ----------------- */ /* * Route a single event and run window idle procedures. * * If the event is a null-event, call the "no-event" handler for the front * window and for any other windows with idle procedures that are always * supposed to run. This is done in such a way that it is safe for idle * procs to remove the window handler for their own window if they want * (unlikely, but...). */ pascal void SkelRouteEvent (EventRecord *evt) { WHHandle wh, wh2; GrafPtr tmpPort; WindowPtr w; SkelWindIdleProcPtr p; RouteEvent (evt); /* * Run applicable window idle procs. Make sure to save and restore * the port, since idle procs for the non-active window may be run. */ if (evt->what == nullEvent) { GetPort (&tmpPort); for (wh = whList; wh != (WHHandle) nil; wh = wh2) { wh2 = (**wh).whNext; w = (**wh).whWind; if (w == FrontWindow () || !(**wh).whFrontOnly) { if ((p = (**wh).whIdle) != (SkelWindIdleProcPtr) nil) { if (!hasWNE) SystemTask (); SetPort (w); (*p) (); } } } SetPort (tmpPort); } } /* * General event dispatch routine. * * If there is an event-handling hook and it handles the event, the * event is not further processed here. Otherwise, run the application's idle * time procedure if the event is a null event, then process the event. * * Null events are sent through DialogSelect() if a dialog is active. This * is necessary to make sure the caret blinks if a dialog has any editText * items. * * Network events are not supported as per the deprecation in TN NW 07. * Application-defined events 1, 2 and 3 are not handled, either. */ static void RouteEvent (EventRecord *evt) { Point evtPt; GrafPtr evtPort; short evtPart; short evtMods; char evtChar; long evtMsge; unsigned char evtCode; WHHandle wh; WindowPtr frontWind; Boolean frontIsDlog; short osMsge; Boolean osResume; Boolean osClipCvt; Rect r1, r2; WStateData **wdh; SignedByte state; /* save values for SkelGetCurrentEvent() and SkelGetModifiers() */ eventPtr = evt; eventModifiers = evt->modifiers; /* don't bother handling event below if event hook does so here */ if (pEvent != (SkelEventHookProcPtr) nil && (*pEvent) (evt)) return; frontWind = FrontWindow (); frontIsDlog = SkelIsDlog (frontWind); evtPt = evt->where; evtMods = evt->modifiers; evtMsge = evt->message; switch (evt->what) { case nullEvent: /* * Run the application idle-time function. If the front window is * a dialog window, pass the event to the dialog event handler; this * is necessary to make the caret blink if it has an edit text item. * Don't use frontWind after calling the idle-time function, since * the function might change the front window! */ if (pIdle != (SkelIdleProcPtr) nil) (*pIdle) (); if (SkelIsDlog (FrontWindow ())) DoDlogEvt (FrontWindow (), evt); break; /* * Mouse click. Get the window in which the click occurred, and * the part of the window. */ case mouseDown: evtPart = FindWindow (evtPt, &evtPort); wh = GetWHandler (evtPort); /* * Beep if a click occurs outside of a movable modal dialog box. * Exceptions: allow clicks in menu bar, and command-clicks in * drag region of underlying windows. */ if (SkelIsMMDlog (frontWind) && !PtInRgn (evtPt, ((WindowPeek) frontWind)->strucRgn)) { if (evtPart != inMenuBar && !(evtPart == inDrag && evtPort != frontWind && (evtMods & cmdKey))) { SysBeep (1); break; } } switch (evtPart) { /* * Click in desk accessory window. Pass back to the system. */ case inSysWindow: SystemClick (evt, evtPort); break; /* * Click in menu bar. Track the mouse and execute * selected command, if any. */ case inMenuBar: DoMenuHook (); DoMenuCommand (MenuSelect (evtPt)); break; /* * Click in grow box. Resize window. */ case inGrow: DoGrow (wh, evtPt); break; /* * Click in title bar. Drag the window around. * Problem fix: DragWindow() seems to call StillDown() * first, so that clicks in drag regions while machine is * busy don't otherwise bring window to front if the mouse * is already up by the time DragWindow() is called. So the * window is selected first to make sure it's at least * activated (unless the command key is down, IM I-289). * * Also offset the window's userState by the amount of the drag * (it'd be simpler to set it to the final content rect but the * window might be in zoomed state rather than user state). */ case inDrag: if (evtPort != frontWind && (evtMods & cmdKey) == 0) SelectWindow (evtPort); SkelGetWindContentRect (evtPort, &r1); /* post-drag position */ DragWindow (evtPort, evtPt, &dragRect); SkelGetWindContentRect (evtPort, &r2); /* post-drag position */ wdh = (WStateData **)(((WindowPeek) evtPort)->dataHandle); state = HGetState ((Handle) wdh); HLock ((Handle) wdh); OffsetRect (&(**wdh).userState, r2.left - r1.left, r2.top - r1.top); HSetState ((Handle) wdh, state); break; /* * Click in close box. Call the close proc if the window * has one. */ case inGoAway: if (TrackGoAway (evtPort, evtPt)) DoClose (wh); break; /* * Click in zoom box. Track the click and then zoom the * window if necessary. */ case inZoomIn: case inZoomOut: if (TrackBox (evtPort, evtPt, evtPart)) DoZoom (wh, evtPart); break; /* * Click in content region. If the window wasn't frontmost * (active), just select it, otherwise pass the click to the * window's mouse click handler. Exception: if the application * wants to receive content clicks event in non-frontmost windows, * select the window and "repeat" the click. */ case inContent: if (evtPort != frontWind) { SelectWindow (evtPort); if (!getFrontClicks) /* don't pass click to handler */ break; SetPort (evtPort); } if (frontIsDlog) DoDlogEvt (evtPort, evt); else DoMouse (wh, evt); break; } break; /* mouseDown */ /* * Key down event. If the command key was down, process as menu * item selection, otherwise pass the character and the modifiers * flags to the active window's key handler. * * Command-period is not supposed to be used as a menu-item equivalent. * Consequently, that's noticed as a special case and not passed to * the menu routines. */ case keyDown: case autoKey: evtChar = evtMsge & charCodeMask; evtCode = (evtMsge & keyCodeMask) >> 8; /* hope bit 7 isn't set! */ if ((evtMods & cmdKey) && !SkelCmdPeriod (evt)) /* try menu equivalent */ { DoMenuHook (); if (mhList) DoMenuCommand (MenuKey (evtChar)); break; } if (frontIsDlog) DoDlogEvt (frontWind, evt); else DoKey (GetWHandler (frontWind), evtChar, evtCode, evtMods); break; /* * Key up event. Key-ups are signified by setting the high bit * of the key code. This never executes unless the application * changes the system event mask *and* the TransSkel event mask. */ case keyUp: evtChar = evtMsge & charCodeMask; /* probably 0? */ evtCode = ((evtMsge & keyCodeMask) >> 8) | 0x80; if (frontIsDlog) DoDlogEvt (frontWind, evt); else DoKey (GetWHandler (frontWind), evtChar, evtCode, evtMods); break; /* * Update a window. */ case updateEvt: DoUpdate (evt); break; /* * Activate or deactivate a window. */ case activateEvt: DoActivate (evt); break; /* * handle inserts of uninitialized disks. Deactivate the frontmost * window since the disk-init dialog doesn't do anything with * activate events for other windows. */ case diskEvt: if (HiWord (evtMsge) != noErr) { SkelActivate (FrontWindow (), false); DILoad (); (void) DIBadMount (diskInitPt, evtMsge); DIUnload (); } break; case osEvt: /* aka app4Evt aka MultiFinder event */ /* rip the message field into constituent parts */ osMsge = ((evtMsge >> 24) & 0xff); /* high byte */ osResume = (Boolean) ((evtMsge & resumeFlag) != 0); osClipCvt = (Boolean) ((evtMsge & convertClipboardFlag) != 0); switch (osMsge) { case suspendResumeMessage: /* * Tell application it's being suspended or resumed * Tell application to convert scrap if necessary */ inForeground = osResume; if (pSuspendResume != (SkelSuspendResumeProcPtr) nil) (*pSuspendResume) (inForeground); if (!osResume) /* always convert on suspend */ osClipCvt = true; if (osClipCvt && pClipCvt != (SkelClipCvtProcPtr) nil) (*pClipCvt) (inForeground); break; case mouseMovedMessage: /* recompute mouse region -- not implemented */ break; /* * 0xfd is a child-died event -- not implemented here since it's * only had limited use, e.g., by certain debuggers. The child pid * is byte 2 ((evtMsge >> 16) & 0xff) case 0xfd: break; */ default: /* other OS event */ /* pass event to catch-all handler -- not implemented */ break; } break; case kHighLevelEvent: if (pAEHandler != (SkelAEHandlerProcPtr) nil) (*pAEHandler) (evt); break; } } /* * Activate or deactivate a window by synthesizing a fake * activate event and sending it through the event router. * Useful for activating a window when you don't know its * activate function. */ pascal void SkelActivate (WindowPtr w, Boolean active) { EventRecord evt; if (w != (WindowPtr) nil) { evt.what = activateEvt; evt.modifiers = active ? activeFlag : 0; evt.when = TickCount (); SetPt (&evt.where, 0, 0); evt.message = (long) w; SkelRouteEvent (&evt); } } /* * Call a window's close procedure. Useful for closing a window when you * don't know its close function. * * This function knows how to close Desk Accessories. */ pascal void SkelClose (WindowPtr w) { if (w != (WindowPtr) nil) { if (((WindowPeek) w)->windowKind < 0) /* DA window */ CloseDeskAcc (((WindowPeek) w)->windowKind); else DoClose (GetWHandler (w)); } } /* * Set the TransSkel event mask. Does not have anything to do with the * system event mask. See TPN 3. */ pascal void SkelSetEventMask (short mask) { eventMask = mask; } /* * Return the event mask. */ pascal short SkelGetEventMask (void) { return (eventMask); } /* * Install an idle-time task. If p is nil, the current task is * disabled. */ pascal void SkelSetIdle (SkelIdleProcPtr p) { pIdle = p; } /* * Return the current idle-time task. Return nil if none. */ pascal SkelIdleProcPtr SkelGetIdle (void) { return (pIdle); } /* * Install an event-inspecting hook. If p is nil, the hook is * disabled. */ pascal void SkelSetEventHook (SkelEventHookProcPtr p) { pEvent = p; } /* * Return the current event-inspecting hook. Return nil if none. */ pascal SkelEventHookProcPtr SkelGetEventHook (void) { return (pEvent); } pascal void SkelSetSuspendResume (SkelSuspendResumeProcPtr p) { pSuspendResume = p; } pascal SkelSuspendResumeProcPtr SkelGetSuspendResume (void) { return (pSuspendResume); } pascal void SkelSetClipCvt (SkelClipCvtProcPtr p) { pClipCvt = p; } pascal SkelClipCvtProcPtr SkelGetClipCvt (void) { return (pClipCvt); } pascal void SkelSetWaitTimes (long fgTime, long bgTime) { fgWaitTime = fgTime; bgWaitTime = bgTime; } pascal void SkelGetWaitTimes (long *pFgTime, long *pBgTime) { if (pFgTime != (long) nil) *pFgTime = fgWaitTime; if (pBgTime != (long) nil) *pBgTime = bgWaitTime; } pascal EventRecord * SkelGetCurrentEvent (void) { return (eventPtr); } pascal short SkelGetModifiers (void) { return (eventModifiers); } pascal void SkelSetAEHandler (SkelAEHandlerProcPtr p) { pAEHandler = p; } pascal SkelAEHandlerProcPtr SkelGetAEHandler (void) { return (pAEHandler); } /* -------------------------------------------------------------------- */ /* Window-handler event routing routines */ /* */ /* See manual for discussion of port-setting behavior: the current */ /* port is set to a window when it becomes active in DoActivate(). */ /* -------------------------------------------------------------------- */ /* * Process dialog event. dlog is the dialog to which the event applies. * Give the filter a chance at the event first. If the filter doesn't * handle it, pass the event to DialogSelect(). If DialogSelect() selects * an item, pass the item to the window's item selection function, if * there is one. This is used to dispose of dialog events that aren't * handled in some other more direct fashion. */ static void DoDlogEvt (DialogPtr dlog, EventRecord *evt) { short item; WHHandle wh; SkelWindSelectProcPtr select; if (DoDlogFilter (dlog, evt)) return; if (DialogSelect (evt, &dlog, &item) && (wh = GetWHandler (dlog)) != (WHHandle) nil && (select = (**wh).whSelect) != (SkelWindSelectProcPtr) nil) { (*select) (dlog, item); } } /* * Run a dialog's filter function to give the filter first chance * at the event. * * The filter function returns false if it doesn't handle the event. * It returns true if it handled the event, in which case it should * set the item parameter. The item will be passed to the dialog's * item selection function. * * If the filter function returns true, look up the handler again * just in case the filter function also called SkelRmveDlog(). * If it did, the handler will have become invalid. Looking it * up again avoids disaster. */ static Boolean DoDlogFilter (DialogPtr dlog, EventRecord *evt) { short item; WHHandle wh; SkelWindSelectProcPtr select; ModalFilterProcPtr filter; Boolean result = false; if ((wh = GetWHandler (dlog)) != (WHHandle) nil && (filter = (**wh).whFilter) != (ModalFilterProcPtr) nil) { if ((*filter) (dlog, evt, &item)) { if ((wh = GetWHandler (dlog)) != (WHHandle) nil && (select = (**wh).whSelect) != (SkelWindSelectProcPtr) nil) (*select) (dlog, item); result = true; } } return (result); } /* * Pass local mouse coordinates, click time, and the modifiers flag * word to the handler. Should not be necessary to set the port, as * the click is passed to the active window's handler. */ static void DoMouse (WHHandle h, EventRecord *evt) { Point thePt; if (h != (WHHandle) nil && (**h).whMouse != (SkelWindMouseProcPtr) nil) { SetPort((**h).whWind); thePt = evt->where; /* make local copy */ GlobalToLocal (&thePt); (*(**h).whMouse) (thePt, evt->when, evt->modifiers); } } /* * Pass the character code, key code and the modifiers flag word to * the handler. Should not be necessary to set the port, as the click * is passed to the active window's handler. */ static void DoKey (WHHandle h, char c, unsigned char code, short mods) { if (h != (WHHandle) nil && (**h).whKey != (SkelWindKeyProcPtr) nil) { SetPort((**h).whWind); (*(**h).whKey) ((short) c, (short) code, mods); } } /* * Call the window updating procedure, passing to it an indicator whether * the window has been resized or not. Then clear the flag, assuming * the update proc took whatever action was necessary to respond to * resizing. * * The Begin/EndUpdate stuff is done to clear the update region even if * the handler doesn't have any update proc. Otherwise the Window * Manager will keep generating update events for the window, stalling * updates of other windows. * * For dialog windows, UpdtDialog() does the normal item updating. The * filter procedure can take care of non-item drawing, such as a bold * outline around a default button. * * Saves, sets, and restore the port, since it's not always the * active window that is updated. */ static void DoUpdate (EventRecord *evt) { WHHandle h; GrafPtr port; GrafPtr tmpPort; port = (WindowPtr) evt->message; GetPort (&tmpPort); SetPort (port); BeginUpdate (port); if (SkelIsDlog (port)) { if (!DoDlogFilter (port, evt)) UpdateDialog (port, port->visRgn); /* let Dialog Manager finish update */ } else { h = GetWHandler (port); if (h != (WHHandle) nil) { if ((**h).whUpdate != (SkelWindUpdateProcPtr) nil) (*(**h).whUpdate) ((**h).whSized); (**h).whSized = false; } } EndUpdate (port); SetPort (tmpPort); } /* * Pass activate/deactivate notification to handler. On activate, * set the port to the window coming active. Normally this is done by * the user clicking in a window. * * *** BUT *** * Under certain conditions, a deactivate may be generated for a window * that has never had the port set to it by a preceding activate. For * instance, if an application puts up window A, then window B in front * of A, then starts processing events, the first events will be a * deactivate for A and an activate for B. Therefore, since it can't be * assumed that an activate ever set the port to A, the port needs to be * set for deactivates as well so drawing occurs in the correct port. * * This matters not a whit for the more usual cases that occur. If a * deactivate for one window is followed by an activate for another, the * port will still be switched properly to the newly active window. If * no activate follows the deactivate, the deactivated window is the last * one, and it doesn't matter what the port ends up set to, anyway. * * On deactivate, port is saved and restored in case deactivate is due to * a modal dialog having been brought in front and port changed to it * explicitly by the application. The deactivate shouldn't leave the port * changed away from the dialog! * * For dialogs, DoDlogEvt() is called, allowing DialogSelect() to do * whatever it does for dialog activates. The handler's activate procedure * is called in addition to this (e.g., to hilite controls or text selections, * adjust menus). */ static void DoActivate (EventRecord *evt) { WHHandle h; GrafPtr port; GrafPtr tmpPort; Boolean active; active = (evt->modifiers & activeFlag); port = (WindowPtr) evt->message; GetPort (&tmpPort); /* save so can restore if deactivate */ SetPort (port); if (SkelIsDlog (port)) DoDlogEvt (port, evt); else { h = GetWHandler (port); if (h != (WHHandle) nil) { if ((**h).whActivate != (SkelWindActivateProcPtr) nil) (*(**h).whActivate) (active); } } if (!active) SetPort (tmpPort); } /* * Execute a window handler's close box proc. The close proc for * handlers for temp windows that want to remove themselves when the * window is closed can call SkelRmveWind to dispose of the window * and remove the handler from the window handler list. Thus, windows * may be dynamically created and destroyed without filling up the * handler list with a bunch of invalid handlers. * * If the handler doesn't have a close proc, just hide the window. * The host should provide some way of reopening the window (perhaps * a menu selection). Otherwise the window will be lost from user * control if it is hidden, since it won't receive user-initiated * events. * * This is called both for regular and dialog windows. * * Normally this is invoked because the close box of the active window * is clicked, in which case the port will be set to the window. However, * SkelClose() allows the application to close an aritrary window, not just * the frontmost one -- so the port is saved and restored. */ static void DoClose (WHHandle h) { GrafPtr tmpPort; if (h != (WHHandle) nil) { GetPort (&tmpPort); SetPort ((**h).whWind); if ((**h).whClose != (SkelWindCloseProcPtr) nil) (*(**h).whClose) (); else HideWindow ((**h).whWind); SetPort (tmpPort); } } /* * Execute a window handler's clobber proc. This is called both * for regular and dialog windows. * * Saves sets, and restores the port, since any window (not just active * one) may be clobbered at any time. * * Don't need to check whether handler is nil, as in other handler * procedures, since this is only called by SkelRmveWind with a * known-valid handler. */ static void DoClobber (WHHandle h) { GrafPtr tmpPort; GetPort (&tmpPort); SetPort ((**h).whWind); if ((**h).whClobber != (SkelWindClobberProcPtr) nil) (*(**h).whClobber) (); SetPort (tmpPort); } /* * Handlers for window events not requiring application handler routines * to be called. */ /* * Have either zoomed a window or sized it manually. Invalidate * it to force an update and set the 'resized' flag in the window * handler true. The port is assumed to be set to the port that changed * size. Handler is assumed non-nil. */ static void TriggerUpdate (WHHandle h) { GrafPtr port = (**h).whWind; InvalRect (&port->portRect); (**h).whSized = true; } /* Extrnal interface to TriggerUpdate -- L. Tierney */ pascal void SkelTriggerUpdate (WindowPtr w) { WHHandle h; GrafPtr savePort; GetPort(&savePort); SetPort(w); h = GetWHandler(w); if (h != nil) TriggerUpdate(h); SetPort(savePort); } /* * Size a window, using the grow limits in the handler record. * * The portRect is invalidated to force an update event. The window's * update handler procedure should check the parameter passed to it to * check whether the window has changed size, if it needs to adjust * itself to the new size. THIS IS A CONVENTION. Update procs must * notice grow "events", there is no procedure specifically for that. * * The clipping rectangle is not reset. If the host application * keeps the clipping set equal to the portRect or something similar, * then it will have to arrange to treat window growing with more * care. * * Since the grow region of only the active window may be clicked, * it should not be necessary to set the port. */ static void DoGrow (WHHandle h, Point startPt) { GrafPtr growPort; Rect growRect; long growRes; GrafPtr tmpPort; /* port savig added -- L. Tierney */ if (h != (WHHandle) nil) { growPort = (**h).whWind; growRect = (**h).whGrow; /* growRes will be zero if the size was not actually changed */ GetPort (&tmpPort); /* save port and set to */ SetPort (growPort); /* window to grow -- L. Tierney */ if (growRes = GrowWindow (growPort, startPt, &growRect)) { SizeWindow (growPort, LoWord (growRes), HiWord (growRes), false); TriggerUpdate (h); } SetPort (tmpPort); /* restore original port -- L. Tierney */ } } /* * Zoom the current window. Very similar to DoGrow, but window * is erased before zooming for nicer visual effect (per IM IV-50, * TN TB 30, p.4). * * Normally, since only the active window has a visible zoom box and * TransSkel sets the port to active window, this routine is triggered * by user-initiated clicks in zoom box and the port will be set to * the zoomed window. * * However, it is possible for zooms to be software initiated by the * application itself on any window; for such cases the port needs * to be saved and set before the zoom and restored afterward. */ static void DoZoom (WHHandle h, short zoomDir) { GrafPtr w; GrafPtr tmpPort; Rect r, growRect; if (h != (WHHandle) nil) { w = (**h).whWind; GetPort (&tmpPort); /* save port and set to */ SetPort (w); /* zoomed window */ if ((**h).whZoom != (SkelWindZoomProcPtr) nil) ((**h).whZoom) (w, zoomDir); /* custom zoom proc */ else if (zoomProc != (SkelWindZoomProcPtr) nil) (*zoomProc) (w, zoomDir); /* custom default zoom proc */ else /* default zooming */ { EraseRect (&w->portRect); if (zoomDir == inZoomOut) /* zooming to default state */ { /* * Get the usable area of the device containing most of the * window. (Can ignore the result because the rect is always * correct. Pass nil for device parameter because it's * irrelevant.) Then adjust rect for title bar height, and * inset it slightly. */ (void) SkelGetWindowDevice (w, (GDHandle *) nil, &r); r.top += SkelGetWindTitleHeight (w) - 1; /* leave 3-pixel border */ InsetRect (&r, 3, 3); /* clip to grow limits */ growRect = (**h).whGrow; growRect.left = growRect.top = 0; OffsetRect (&growRect, r.left, r.top); SectRect (&r, &growRect, &r); (**(WStateData **)(((WindowPeek)w)->dataHandle)).stdState = r; } ZoomWindow (w, zoomDir, false); } SetPort (tmpPort); /* restore original port */ TriggerUpdate (h); } } /* --------------------------------------------------------- */ /* Window handler installation/removal/modification routines */ /* --------------------------------------------------------- */ /* * Install handler for a window and set current port to it. Remove * any previous handler for it. Pass the following parameters: * * w * Pointer to the window to be handled. Must be created by host. * doMouse * Proc to handle mouse clicks in window. The proc will be * passed the point (in local coordinates), the time of the * click, and the modifier flags word. * doKey * Proc to handle key clicks in window. The proc will be passed * the character and the modifier flags word. * doUpdate * Proc for updating window. TransSkel brackets calls to update * procs with calls to BeginUpdate and EndUpdate, so the visRgn * is set up correctly. A flag is passed indicating whether the * window was resized or not. BY CONVENTION, the entire portRect * is invalidated when the window is resized or zoomed. That way, * the handler's update proc can redraw the entire content region * without interference from BeginUpdate/EndUpdate. The flag * is set to false after the update proc is called; the * assumption is made that the proc will notice the resizing and * respond appropriately. * doActivate * Proc to execute when window is activated or deactivated. * A boolean is passed to it which is true if the window is * coming active, false if it's going inactive. * doClose * Proc to execute when mouse clicked in close box. Useful * mainly to temp window handlers that want to know when to * self-destruct (with SkelRmveWind). * doClobber * Proc for disposal of handler's data structures * doWIdle * Proc to execute when no events are pending. * idleFrontOnly * True if doWIdle should execute on no events only when * w is frontmost, false if executes all the time. Note * that if it always goes, everything else may be slowed down! * * If a particular procedure is not needed (e.g., key events are * not processed by a handler), pass nil in place of the appropriate * procedure address. * * Return true if successful, false if no handler could be allocated. * If false is returned, the port will not have been changed. */ pascal Boolean SkelWindow (WindowPtr w, SkelWindMouseProcPtr doMouse, SkelWindKeyProcPtr doKey, SkelWindUpdateProcPtr doUpdate, SkelWindActivateProcPtr doActivate, SkelWindCloseProcPtr doClose, SkelWindClobberProcPtr doClobber, SkelWindIdleProcPtr doWIdle, Boolean idleFrontOnly) { WHHandle whNew, whCur; SkelWindPropHandle wph = (SkelWindPropHandle) nil; /* Get new handler immediately, fail if can't allocate */ if ((whNew = New (WHandler)) == (WHHandle) nil) return (false); /* * If there's a current handler for the window, remove it, but first * grab the property list from it so it can be transferred to the new * handler. */ if ((whCur = GetWHandler (w)) != (WHHandle) nil) { wph = (**whCur).whProperties; (**whCur).whProperties = (SkelWindPropHandle) nil; DetachWHandler (whCur); } /* * Attach new handler to list of handlers. It is attached to the * beginning of the list, which is simpler; the order is presumably * irrelevant to the host, anyway. * * Then fill in handler fields (including properties attached to any * previous handler). */ (**whNew).whNext = whList; whList = whNew; (**whNew).whWind = w; (**whNew).whMouse = doMouse; (**whNew).whKey = doKey; (**whNew).whUpdate = doUpdate; (**whNew).whActivate = doActivate; (**whNew).whClose = doClose; (**whNew).whClobber = doClobber; (**whNew).whZoom = (SkelWindZoomProcPtr) nil; (**whNew).whIdle = doWIdle; (**whNew).whGrow = growRect; (**whNew).whSized = false; (**whNew).whFrontOnly = idleFrontOnly; (**whNew).whFlags = 0; (**whNew).whProperties = wph; (**whNew).whSelect = (SkelWindSelectProcPtr) nil; (**whNew).whFilter = (ModalFilterProcPtr) nil; SetPort (w); return (true); } /* * Remove a window handler. This calls the handler's window disposal * routine and then takes the handler out of the handler list and * disposes of it (including its property list). * * SkelRmveWind is also called by SkelRmveDlog. */ pascal void SkelRmveWind (WindowPtr w) { WHHandle h; if ((h = GetWHandler (w)) == (WHHandle) nil) return; DoClobber (h); /* call disposal routine */ SkelRmveWindProp (w, skelWPropAll); /* toss properties */ DetachWHandler (h); /* remove handler for window from list */ } /* * Install a handler for a modeless or movable modal dialog window and set * the port to it. Remove any previous handler for it. SkelDialog calls * SkelWindow as a subsidiary to install a window handler, then sets * the event procedure on return. A property is also added to the window * to indicate that it's a modeless or movable modal dialog. * * Pass the following parameters: * * dlog * Pointer to the dialog to be handled. Must be created by host. * doFilter * Filter procedure to look at events before they are otherwise * processed. * doSelect * Procedure to execute when an item is "selected" (e.g., a mouse * click occurs in it). * doClose * Procedure to execute when mouse clicked in close box. Useful * mainly to dialog handlers that want to know when to * self-destruct (with SkelRmveDlog). * doClobber * Procedure for disposal of handler's data structures * * If a particular procedure is not needed, pass nil in place of * the appropriate procedure address. * * Return true if successful, false if no handler could be allocated. * If false is returned, the port will not have been changed. */ pascal Boolean SkelDialog (DialogPtr dlog, ModalFilterProcPtr doFilter, SkelWindSelectProcPtr doSelect, SkelWindCloseProcPtr doClose, SkelWindClobberProcPtr doClobber) { WHHandle wh; short propType; if (!SkelWindow (dlog, nil, nil, nil, nil, doClose, doClobber, nil, true)) return (false); propType = (SkelIsMMDlog (dlog) ? skelWPropMovableModal : skelWPropModeless); if (!SkelAddWindProp (dlog, propType, (long) 0)) { SkelRmveDlog (dlog); return (false); } wh = GetWHandler (dlog); (**wh).whSelect = doSelect; (**wh).whFilter = doFilter; return (true); } /* * Remove a dialog and its handler */ pascal void SkelRmveDlog (DialogPtr dlog) { SkelRmveWind (dlog); } /* * Override the default sizing limits for a window, or, if w * is nil, reset the default limits used by SkelWindow. */ pascal void SkelSetGrowBounds (WindowPtr w, short hLo, short vLo, short hHi, short vHi) { WHHandle wh; Rect r; if (w == (WindowPtr) nil) SetRect (&growRect, hLo, vLo, hHi, vHi); else if ((wh = GetWHandler (w)) != (WHHandle) nil) { SetRect (&r, hLo, vLo, hHi, vHi); (**wh).whGrow = r; } } pascal void SkelSetZoom (WindowPtr w, SkelWindZoomProcPtr pZoom) { WHHandle h; if (w == (WindowPtr) nil) zoomProc = pZoom; else if ((h = GetWHandler (w)) != (WHHandle) nil) (**h).whZoom = pZoom; } /* * Return zoom proc associated with window, nil if there isn't one. * Return default zoom proc if window is nil. */ pascal SkelWindZoomProcPtr SkelGetZoom (WindowPtr w) { WHHandle h; if (w == (WindowPtr) nil) return (zoomProc); if ((h = GetWHandler (w)) != (WHHandle) nil) return ((**h).whZoom); return ((SkelWindZoomProcPtr) nil); } pascal Boolean SkelWindowRegistered (WindowPtr w) { return ((Boolean) (GetWHandler (w) != (WHHandle) nil)); } /* * Routines to determine whether a given window is a dialog, or a movable * modal dialog. Safe to pass nil. */ pascal Boolean SkelIsDlog (WindowPtr w) { return (w != (WindowPtr) nil && ((WindowPeek)w)->windowKind == dialogKind); } pascal Boolean SkelIsMMDlog (WindowPtr w) { return (SkelIsDlog (w) && hasGetWVariant && GetWVariant (w) == movableDBoxProc); } /* ------------------------ */ /* Handler finders/removers */ /* ------------------------ */ /* * Get handler associated with a window. * * Return nil if window doesn't belong to any known handler. * * This routine is absolutely fundamental to TransSkel. */ static WHHandle GetWHandler (WindowPtr w) { WHHandle h; if (w == (WindowPtr) nil) return ((WHHandle) nil); if (w == oldWindow) return (oldWHandler); /* return handler of cached window */ for (h = whList; h != (WHHandle) nil; h = (**h).whNext) { if ((**h).whWind == w) { oldWindow = w; /* set cached window and handler */ oldWHandler = h; return (h); } } return ((WHHandle) nil); } /* * Detach a handler from the handler list and destroy it. * * Clear window cache variable, just in case it points to the window * whose hander is being destroyed (and thus has become invalid). */ static void DetachWHandler (WHHandle wh) { WHHandle h, h2; if (whList != (WHHandle) nil) /* if list empty, ignore */ { if (whList == wh) /* is it the first element? */ { h2 = whList; whList = (**whList).whNext; } else for (h = whList; h != (WHHandle) nil; h = h2) { h2 = (**h).whNext; if (h2 == (WHHandle) nil) return; /* handler not in list! (huh?) */ if (h2 == wh) /* found it */ { (**h).whNext = (**h2).whNext; break; } } DisposeHandle ((Handle) h2); /* get rid of handler record */ } oldWindow = (WindowPtr) nil; /* clear window cache variables */ oldWHandler = (WHHandle) nil; } /* ------------------------------------------------------- */ /* Menu handler installation/removal/modification routines */ /* ------------------------------------------------------- */ /* * Install handler for a menu. Remove any previous handler for it. * Pass the following parameters: * * theMenu * Handle to the menu to be handled. Must be created by host. * doSelect * Proc that handles selection of items from menu. If this is * nil, the menu is installed, but nothing happens when items * are selected from it. * doClobber * Proc for disposal of handler's data structures. Usually * nil for menus that remain in menu bar until program * termination. * subMenu * True if the menu is a submenu (not installed in menu bar). * drawBar * True if menu bar is to be drawn after menu is installed. * (Ignored if the menu is a submenu.) * * Return true if successful, false if no handler could be allocated. */ pascal Boolean SkelMenu (MenuHandle m, SkelMenuSelectProcPtr doSelect, SkelMenuClobberProcPtr doClobber, Boolean subMenu, Boolean drawBar) { MHHandle mh; Boolean oldFlag; oldFlag = mhClobOnRmve; /* remove any previous handler for */ mhClobOnRmve = false; /* menu, without redrawing menu bar */ SkelRmveMenu (m); mhClobOnRmve = oldFlag; if ((mh = New (MHandler)) != (MHHandle) nil) { (**mh).mhNext = mhList; mhList = mh; (**mh).mhID = (**m).menuID; /* get menu id number */ (**mh).mhSelect = doSelect; /* install selection handler */ (**mh).mhClobber = doClobber; /* install disposal handler */ (**mh).mhSubMenu = subMenu; /* set submenu flag */ /* install menu in menu bar if not a submenu */ InsertMenu (m, subMenu ? -1 : 0); } if (drawBar && !subMenu) DrawMenuBar (); return ((Boolean) (mh != (MHHandle) nil)); } /* * Remove a menu handler. This calls the handler's menu disposal * routine and then takes the handler out of the handler list and * disposes of it. The menu bar is redrawn if the menu was not a * submenu and the global redraw flag hasn't been cleared. * * The menu MUST be deleted from the menu bar before calling the * clobber proc. Otherwise the menu bar will end up filled with * garbage if the menu was allocated with NewMenu (IM I-352). */ pascal void SkelRmveMenu (MenuHandle m) { short mID; MHHandle h, h2; SkelMenuClobberProcPtr p; mID = (**m).menuID; if (mhList != (MHHandle) nil) /* if list empty, ignore */ { if ((**mhList).mhID == mID) /* is it the first element? */ { h2 = mhList; mhList = (**mhList).mhNext; } else { for (h = mhList; h != (MHHandle) nil; h = h2) { h2 = (**h).mhNext; if (h2 == (MHHandle) nil) return; /* menu not in list! */ if ((**h2).mhID == mID) /* found it */ { (**h).mhNext = (**h2).mhNext; break; } } } DeleteMenu (mID); if (mhDrawBarOnRmve && !(**h2).mhSubMenu) DrawMenuBar (); if (mhClobOnRmve && (p = (**h2).mhClobber) != (SkelMenuClobberProcPtr) nil) (*p) (m); /* call disposal routine */ DisposeHandle ((Handle) h2); /* get rid of handler record */ } } /* * General menu-selection handler. Just passes selection to the handler's * select routine. If the select routine is nil, selecting items from * the menu is a nop. */ static void DoMenuCommand (long command) { short menu; short item; MHHandle mh; menu = HiWord (command); item = LoWord (command); for (mh = mhList; mh != (MHHandle) nil; mh = (**mh).mhNext) { if (menu == (**mh).mhID && (**mh).mhSelect != (SkelMenuSelectProcPtr) nil) { (*(**mh).mhSelect) (item, menu); break; } } HiliteMenu (0); /* command done, turn off menu hiliting */ } /* * Menu is about to be pulled down or command-key executed. Call menu * hook if there is one so application can set menus/items appropriately. */ static void DoMenuHook (void) { if (pMenuHook != (SkelMenuHookProcPtr) nil) (*pMenuHook) (); } pascal void SkelSetMenuHook (SkelMenuHookProcPtr p) { pMenuHook = p; } pascal SkelMenuHookProcPtr SkelGetMenuHook (void) { return (pMenuHook); } /* ------------------------ */ /* Window property routines */ /* ------------------------ */ /* * Add a property to a window. Fail if the window is unregistered * or can't allocate memory for a new property structure. If the * window already has such a property, fail. * * Returns a handle to the new property for success, nil for failure. */ pascal Boolean SkelAddWindProp (WindowPtr w, short propType, long propData) { WHHandle wh; SkelWindPropHandle ph; if (propType == skelWPropAll) return (false); if ((ph = SkelGetWindProp (w, propType)) != (SkelWindPropHandle) nil) return (false); /* if window is unregistered, or can't allocate structure, fail */ if ((wh = GetWHandler (w)) == (WHHandle) nil || (ph = New (SkelWindProperty)) == (SkelWindPropHandle) nil) return (false); (**ph).skelWPropType = propType; (**ph).skelWPropData = propData; (**ph).skelWPropNext = (**wh).whProperties; (**wh).whProperties = ph; return (true); } /* * Remove a window property. Does nothing if the window isn't * registered or if the window doesn't have the given property. * * If propType is skelWPropAll, SkelRmveWindProp() calls itself * recursively to remove all the properties on a window. This * means that if you put skelWPropAll into the skelWPropType field * of a property, you'll get an infinite loop here. */ pascal void SkelRmveWindProp (WindowPtr w, short propType) { WHHandle wh; SkelWindPropHandle ph, ph2, pNext; if ((wh = GetWHandler (w)) == (WHHandle) nil || (ph = SkelGetWindProp (w, propType)) == (SkelWindPropHandle) nil) return; if (propType == skelWPropAll) /* remove all properties */ { while ((ph = (**wh).whProperties) != (SkelWindPropHandle) nil) SkelRmveWindProp (w, (**ph).skelWPropType); return; } /* remove particular property */ if ((ph2 = (**wh).whProperties) == ph) /* remove first in list */ (**wh).whProperties = (**ph).skelWPropNext; else { while ((pNext = (**ph2).skelWPropNext) != (SkelWindPropHandle) nil) { if (pNext == ph) { (**ph2).skelWPropNext = (**ph).skelWPropNext; break; } ph2 = pNext; } } DisposeHandle ((Handle) ph); } /* * Find the given property for the window. Fail if window is * unregistered or has no such property. */ pascal SkelWindPropHandle SkelGetWindProp (WindowPtr w, short propType) { WHHandle wh; SkelWindPropHandle ph = (SkelWindPropHandle) nil; if ((wh = GetWHandler (w)) != (WHHandle) nil) { if (propType == skelWPropAll) /* return head of list */ ph = (**wh).whProperties; else for (ph = (**wh).whProperties; ph != (SkelWindPropHandle) nil; ph = (**ph).skelWPropNext) { if ((**ph).skelWPropType == propType) break; } } return (ph); } /* * Find the data value for a given property for the window. Return 0 if window * is unregistered or has no such property. * * If you need to be able to distinquish an error return from a valid zero-value * data value, you should call SkelGetWindProp() instead, check for an error, * and extract the data value if there was no error. * * skelWPropAll is not a valid properly type for this call. */ pascal long SkelGetWindPropData (WindowPtr w, short propType) { SkelWindPropHandle ph; ph = SkelGetWindProp (w, propType); if (ph != (SkelWindPropHandle) nil) return ((**ph).skelWPropData); return (0); } /* * SkelCmdPeriod.c -- Figure out, in an internationally compatible way, whether * an event is a command-period key event. * * See TN TE 23, International Cancelling, for the rationale on how this works. */ # include # include "TransSkel1.h" # define kMaskModifiers 0xfe00 # define kMaskVirtualKey 0x0000ff00 # define kMaskAscii1 0x00ff0000 # define kMaskAscii2 0x000000ff # define period '.' pascal Boolean SkelCmdPeriod (EventRecord *evt) { short keyCode; long virtualKey; long keyCId; long keyInfo; long state; long lowChar, highChar; Handle hKCHR; if (evt->what == keyDown || evt->what == autoKey) { if (evt->modifiers & cmdKey) /* cmd key is down */ { /* find out ASCII equivalent for key */ virtualKey = (evt->message & kMaskVirtualKey) >> 8; /* "and" out command key, "or" in the virtual key */ keyCode = (evt->modifiers & kMaskModifiers) | virtualKey; keyCId = GetScriptVariable (GetScriptManagerVariable (smKeyScript), smScriptKeys); hKCHR = GetResource ('KCHR', keyCId); if (hKCHR != (Handle) nil) { state = 0; /* don't bother locking because KeyTrans doesn't move memory */ keyInfo = KeyTranslate (*hKCHR, keyCode, (void *) &state); ReleaseResource (hKCHR); } else keyInfo = evt->message; lowChar = keyInfo & kMaskAscii2; highChar = (keyInfo & kMaskAscii1) >> 16; if (lowChar == period || highChar == period) return (true); } } return (false); } /* * Get the device containing most of a window's content rectangle and the * largest usable rectangle on that device. If the device is the main * device, the rectangle is adjusted not to contain the menu bar area. * * The result is used by TransSkel for window zooming. Normally, the * caller adjusts the top of the rectangle to account for the title bar * height, then insets it by a few pixels in order to leave a little * space around the window edges. SkelGetWindowDevice() itself does * not account for the title bar height. That responsibility is * left with the caller, which can call SkelGetWindTitleHeight() to * find this out. * * Returns true if the window overlaps some device in its current * position. False can be returned, for instance, if an application * saves document window positions and a document is saved while * positioned on a second monitor, then opened on a system that doesn't * have a second monitor. * * The returned device value will be nil on systems that don't have GDevices * (i.e.,, that don't support Color QuickDraw), even if the function result * is true. * * If the window does not overlap any device, the device and devRect arguments * are filled in with the values for the main device. The rectangle can * be used to position the window so it can be made visible. * * If the caller is not interested in the device or rectangle, nil * can be passed instead of the address of a variable. * * References: TN TB 30, HIN 6, HIN 7. */ # include "TransSkel1.h" pascal Boolean SkelGetWindowDevice (WindowPtr wind, GDHandle *gd, Rect *devRect) { Rect r; Boolean isMain; Boolean result; /* get window content rect in global coordinates */ SkelGetWindContentRect (wind, &r); result = SkelGetRectDevice (&r, gd, devRect, &isMain); if (isMain && devRect != (Rect *) nil) devRect->top += SkelQuery (skelQMBarHeight); return (result); } /* * Determine height of a window's title bar. This is determined as the * difference between the top of the window's structure and content rects. * * This function will not necessarily work for windows with strange shapes * or that have a title bar on the side. */ # include "TransSkel1.h" pascal short SkelGetWindTitleHeight (WindowPtr w) { Rect content; Rect structure; SkelGetWindContentRect (w, &content); SkelGetWindStructureRect (w, &structure); return (content.top - structure.top); } /* * Calculate content or structure rectangle for a window. These are * core routines because they are called by other core routines, * e.g., SkelGetWindowDevice() and SkelGetWindTitleHeight (). */ # include "TransSkel1.h" # define kTranslate 0x4000 /* * Get content rectangle and convert it to global coordinates */ pascal void SkelGetWindContentRect (WindowPtr w, Rect *rp) { GrafPtr oldPort; GetPort (&oldPort); SetPort (w); *rp = w->portRect; LocalToGlobal (&topLeft (*rp)); LocalToGlobal (&botRight (*rp)); SetPort (oldPort); } /* * Get structure rectangle. This is already in global coordinates, but the * tricky part is that it isn't valid if the window is invisible. * * If window's visible, the structure region's valid, so get the bounding box. * * If the window's not visible, fling it out into space, make it visible, get * the structure region bounding box, make it invisible again and restore it to * its normal position. Use ShowHide() for this since it doesn't change the * window's hiliting or position in the stacking order. The rectangle * calculated this way has to be moved back, too, since it's obtained when the * window is in flung position. * * I have seen similar code that also saves and restored the window's userState, * but Inside Macintosh (Toolbox Essentials, p. 4-70) explicitly states that * the userState isn't modified when you just move a window, so I don't see the * point. */ pascal void SkelGetWindStructureRect (WindowPtr w, Rect *rp) { Rect content; if (((WindowPeek) w)->visible) *rp = (**(* (WindowPeek) w).strucRgn).rgnBBox; else { SkelGetWindContentRect (w, &content); /* get upper-left coords */ MoveWindow (w, kTranslate, content.top, false); /* fling window */ ShowHide (w, true); *rp = (**(* (WindowPeek) w).strucRgn).rgnBBox; ShowHide (w, false); MoveWindow (w, content.left, content.top, false); /* unfling window */ OffsetRect (rp, content.left - kTranslate, 0); /* unfling struct rect */ } } /* * Given a rectangle, determine the following values: * - Which device contains more of the rectangle than any other * - The device rectangle (this includes the menu bar area if the device * is the main device) * - Whether or not the device is the main device * * These values are stuffed into the arguments, which are passed as * variable addresses. If you're not interested in a particular value, * pass nil for the corresponding argument. * * The return value if true if the rectangle overlaps some device, * false if it lies outside all devices. If the rectangle overlaps no * device, non-nil arguments are filled in with the main device, the main * device rect, and true, respectively. This is useful, e.g., for callers * that may want to reposition a window if its content rectangle isn't * visible on some monitor. * * The returned device value will be nil on systems that don't have GDevices * (i.e.,, that don't support Color QuickDraw), even if the function result * is true. * * References: TN TB 30. */ # include "TransSkel1.h" pascal Boolean SkelGetRectDevice (Rect *rp, GDHandle *rGD, Rect *devRect, Boolean *isMain) { GDHandle gd, curGD; Rect gdRect, curRect, iSectRect; long maxArea, area; Boolean main = false; Boolean result; gd = (GDHandle) nil; /* no device for rectangle known yet */ if (!SkelQuery (skelQHasColorQD)) { /* * No Color QuickDraw implies only one screen, which is therefore * the main device. Test rectangle against full screen, setting * result true if they intersect. */ main = true; gdRect = screenBits.bounds; result = SectRect (rp, &gdRect, &iSectRect); } else { /* determine device having maximal overlap with r */ maxArea = 0; for (curGD = GetDeviceList (); curGD != (GDHandle) nil; curGD = GetNextDevice (curGD)) { /* only consider active screen devices */ if (!TestDeviceAttribute (curGD, screenDevice) || !TestDeviceAttribute (curGD, screenActive)) continue; curRect = (**curGD).gdRect; if (!SectRect (rp, &curRect, &iSectRect)) continue; area = (long) (iSectRect.right - iSectRect.left) * (long) (iSectRect.bottom - iSectRect.top); if (maxArea < area) { maxArea = area; gd = curGD; gdRect = curRect; result = true; /* rectangle overlaps some device */ } } if (gd == (GDHandle) nil) /* rectangle overlaps no device, use main */ { gd = GetMainDevice (); gdRect = (**gd).gdRect; result = false; } main = (gd == GetMainDevice ()); } /* fill in non-nil arguments */ if (rGD != (GDHandle *) nil) *rGD = gd; if (devRect != (Rect *) nil) *devRect = gdRect; if (isMain != (Boolean *) nil) *isMain = main; return (result); } /* * 09 Jun 92 * - Check whether default button is actually *active* when return/enter * is typed. Sheesh. * 11 Jun 92 * - Added flag argument to SkelDlogFilter() allowing caller to determine * whether or not standard filter should do return/enter processing. Since * this is now under control of caller, the test is whether the flag is true, * rather than whether there's a dialog-specific filter or not. * 04 Nov 93 * - Made the filter function check whether there's an event hook installed * and pass the event to it if so, before dealing with the event. * 03 Jan 94 * - Added functions SkelDlogDefaultItem() and SkelDlogCancelItem() for * specifying which dialog items are the default and cancel items. This * allows (i) a different item than aDefItem to be used for the OK button * and (ii) keyboard equivalents for the Cancel item to be handled * automatically by the standard filter. * 04 Jan 94 * - Added function SkelDlogFilterYD() for installing filters that can be used * for calls like the System 7 Standard File dialogs CustomGetFile() and * CustomPutFile(). * - Changed type of SkelDlogFilter() from SkelDlogFilterProcPtr to * ModalFilterProcPtr. (They're equivalent, and compiler now treats them * that way, so there's no need for SkelDlogFilterProcPtr anymore.) * 07 Jan 94 * - StandardFilter() now only checks keyDown, not autoKey; if the keypress * is one it would respond to, the dialog will end up being dismissed and there * is no opportunity for autoKey to occur. * 12 Jan 94 * - Stopped using CallPascalB(); the compiler's smart enough now to figure out * when Pascal calling conventions need to be used with (*func)(args) syntax. * 13 Jan 94 * - Formerly when a key was typed that maps to a button, the standard filter * checked the button to see if it was active. If so, it flashed the button and * returned the item. Otherwise it returned false. This leads to the behavior * that the key ends up in the current edittext item if there is one when the * button is inactive and not otherwise. Now the filter flashes the button and * returns true if the button is active, but turns the event into a null event * if the button's inactive so the key doesn't get processed further. * 14 Apr 94 * - Added stuff for testing whether the pointer is in an edit field and * changing the pointer to an I-beam if so. This is enabled/disabled by * calling SkelDlogTracksCursor(). * - Also remembered to actually initialize the cancelItem field of the * FilterInfo struct when a new one is pushed on the stack... * 23 Apr 94 * - Changed stack depth from 10 to 5. No more than 2 alerts/dialogs should * appear at once, anyway, so even 5 is excessive. * - Pass mouse click events to regular event router if the click is in the * drag region of an underlying window and the command-key is down. This * allows those windows to be dragged around in their current plane under * modal dialogs. (As per Apple's Human Interface Guidelines.) * 26 Apr 94 * - Send null events to SkelRouteEvent(), without claiming to have handled * them. This helps keep window-specific idle procedures running if they're * supposed to run even when the window isn't frontmost. * 29 Apr 94 * - Bug fix: TestDlogButton() wasn't checking properly to see whether or not * a button was disabled. Fixed. * - Removed TestDlogButton() and key mapping code from StandardFilter() and * repackaged it at SkelDlogMapKeyToButton() because the key mapping operation * is one that's useful from modeless and movable modal dialog filters, too. * - On update or activate, instead of testing whether the window to which the * event applies is registered with TransSkel, just check whether it's the modal * being handled. If not, pass the event to the router. Even if the event's * for another dialog, the router will now handle it correctly, whether the * window is registered or not. * 03 May 94 * - Introduced a bug in ModalFilterYD() in release 3.13 by putting an extra * semicolon at the end of the first "if" line. Resulted in machine lockups. * Fixed. */ # include "TransSkel1.h" /* * Filter function stack. Used so that the filter function mechanism * can be reentrant (multiple simultaneous alerts and dialogs). Maximum * reentrancy level is controlled by maxStack. * * The filter field is a generic pointer because functions of different * types are assigned to it. * * The defaultItem and cancelItem fields are positive to indicate an * item to which return/enter and cmd-period/escape should be mapped. * They are zero to indicate no key mapping. defaultItem can be * negative to indicate that return/enter should be mapped to the * aDefItem item in the dialog/alert record. */ # define maxStack 5 typedef struct FilterInfo FilterInfo; struct FilterInfo { ProcPtr filter; short defaultItem; short cancelItem; Boolean trackCursor; }; static short top = -1; static FilterInfo filterStack[maxStack]; /* * Standard filter function. The regular alert- or dialog-specific filter is * piggybacked onto this. The standard filter intercepts events that would * be ignored otherwise (currently this includes OS events, command clicks * in drag region of underlying windows, and update and activate/deactivate * events for windows other than the given dialog) and shoves them through * the normal TransSkel event dispatch mechanism. * * The standard filter also performs key-to-button mapping, returning the * default button when return/enter is typed, and/or the cancel button * when cmd-period/escape is typed. * * Returns false even for an event that's handled, if it doesn't result in * an item hit, but map the event to a null event so the Dialog Manager doesn't * try to do anything further with it. */ static pascal Boolean StandardFilter (DialogPtr d, EventRecord *e, short *item) { FilterInfo *info; short what = e->what; SkelEventHookProcPtr p; GrafPtr port; if ((p = SkelGetEventHook ()) != (SkelEventHookProcPtr) nil) { if ((*p) (e)) /* call hook, and return if hook handles event */ return (true); } info = &filterStack[top]; /* * If the event is a null event and the filter should make the cursor an * I-beam when the pointer's in an edit text field, check that. */ if (what == nullEvent) { if (info->trackCursor) SkelSetDlogCursor (d); SkelRouteEvent (e); return (false); } if (what == osEvt) /* MultiFinder event */ { SkelRouteEvent (e); e->what = nullEvent; return (false); } /* * Command clicks in the drag region of underlying windows are processed, * allowing those windows to be dragged around in their current plane under * the dialog. This doesn't test whether the port is the same as the dialog * because modals don't have a drag region. */ if (what == mouseDown && (e->modifiers & cmdKey) && FindWindow (e->where, &port) == inDrag) { SkelRouteEvent (e); e->what = nullEvent; return (false); } if (what == updateEvt || what == activateEvt) { if (d != (DialogPtr) e->message) { SkelRouteEvent (e); e->what = nullEvent; return (false); } } /* * If event is a key-press, handle key-to-button mapping if necessay. */ if (what == keyDown) { if (SkelDlogMapKeyToButton (d, e, item, info->defaultItem, info->cancelItem)) return (true); } return (false); } /* * Run ModalDialog()-style filter. * * If dialog-specific filter doesn't handle the event, pass it to * StandardFilter(). */ static pascal Boolean ModalFilter (DialogPtr d, EventRecord *e, short *item) { FilterInfo *info; info = &filterStack[top]; if (info->filter != nil && (*(ModalFilterProcPtr) info->filter) (d, e, item)) return (true); if (StandardFilter (d, e, item)) return (true); return (false); } /* * Run ModalDialog()-style filter that takes a data argument. * * If dialog-specific filter doesn't handle the event, pass it to * StandardFilter(). */ static pascal Boolean ModalFilterYD (DialogPtr d, EventRecord *e, short *item, void *data) { FilterInfo *info; info = &filterStack[top]; if (info->filter != nil && (*(ModalFilterYDProcPtr) info->filter) (d, e, item, data)) return (true); if (StandardFilter (d, e, item)) return (true); return (false); } /* * Install a ModalDialog()-style filter, or a filter that takes a data * argument. * * In each case, remember the filter specified by the caller, so it can be * invoked later. Return a pointer to a function that invokes the filter * specified by the caller, and the standard filter if necessary. The caller * passes the pointer returned to an alert or dialog call that takes a filter * function argument. * * doReturn is true if the standard filter should try to handle return/enter * key as a synonym for clicking the default button if that button is active. */ static void InstallNewFilter (ProcPtr filter, Boolean doReturn) { FilterInfo *info; info = &filterStack[++top]; info->filter = filter; info->defaultItem = (doReturn ? -1 : 0); info->cancelItem = 0; info->trackCursor = false; } static ModalFilterUPP modalFilterUPP = NULL; pascal ModalFilterUPP SkelDlogFilter (ModalFilterProcPtr filter, Boolean doReturn) { InstallNewFilter ((ProcPtr) filter, doReturn); if (! modalFilterUPP) modalFilterUPP = NewModalFilterProc((ProcPtr) ModalFilter); return (modalFilterUPP); } pascal ModalFilterYDProcPtr SkelDlogFilterYD (ModalFilterYDProcPtr filter, Boolean doReturn) { InstallNewFilter ((ProcPtr) filter, doReturn); return (ModalFilterYD); } /* * Remove the last filter function (which reinstalls the previous one * as a side effect). */ pascal void SkelRmveDlogFilter (void) { --top; } /* * Set default or cancel item for the current filter */ pascal void SkelDlogDefaultItem (short item) { if (top >= 0) filterStack[top].defaultItem = item; } pascal void SkelDlogCancelItem (short item) { if (top >= 0) filterStack[top].cancelItem = item; } pascal void SkelDlogTracksCursor (Boolean track) { if (top >= 0) filterStack[top].trackCursor = track; } /* * SkelDoEvents (mask) - process all pending events of types indicated in mask * SkelDoUpdates () - process all pending update events * * These routines may be called any time subsequent to the call of SkelInit(). */ # include "TransSkel1.h" static short sdeMask; /* * Make sure any events of proper type are processed before * proceeding. I.e., wait until there are no more events of the * type we're waiting for, then terminate SkelDoEvents(). */ static pascal void CheckEvents (void) { EventRecord event; if (!EventAvail (sdeMask, &event)) SkelStopEventLoop (); } /* * Process all events of type(s) given in mask. It is possible to call this * recursively. * Operation: * - Save current SkelDoEvents() mask, current TransSkel event mask, and * current background procedure. * - Install the new mask into TransSkel and save a copy in a local variable. * Install a new background procedure that checks whether any events of the * desired type(s) are available or not. * - Call SkelMain() to initiate an event loop. The background task calls * SkelWhoa() to terminate SkelMain() when there are no more events of * interest available. * - Restore the previous background procedure and TransSkel mask, and * previous SkelDoEvents() mask. The latter is necessary in case this is * a recursive call. */ pascal void SkelDoEvents (short mask) /* can be called recursively */ { short oldSdeMask; short oldTSMask; SkelIdleProcPtr oldIdle; oldIdle = SkelGetIdle (); /* get current idle proc */ oldTSMask = SkelGetEventMask (); /* and event mask */ oldSdeMask = sdeMask; /* and SkelDoEvents() processing types */ SkelSetIdle (CheckEvents); /* install new idle & mask */ SkelSetEventMask (mask); sdeMask = mask; /* <- so CheckEvents can find mask */ SkelEventLoop (); /* handle given event types only */ SkelSetIdle (oldIdle); /* restore stuff that was changed */ SkelSetEventMask (oldTSMask); sdeMask = oldSdeMask; } pascal void SkelDoUpdates (void) { SkelDoEvents (updateMask); } /* * SkelDlogMapKeyToButton () * * This routine looks at an event to see whether or not it's a key event * that should be mapped onto an item hit in a dialog's default or cancel * button. * * defaultItem is 0 if no return/enter mapping should be done, > 0 to * explicitly specify the item that return/enter map to, < 0 to map * return/enter to the item specified as the default in the dialog record. * * cancelItem is 0 if no escape/cmd-period mapping should be done, > 0 to * explicitly specify the item that escape/cmd-period map to. * * If the key maps to a button item, the button is flashed for visual * feedback if it is hilited normally (not dimmed). If the button is enabled, * the item parameter is set to the item number and the function returns true. * If the key doesn't map to an item or the button is disabled, the function * returns false. * * If the key maps to a button, but the button isn't hilited properly or is * disabled, the event is mapped to a null event so that nothing else is done * with it. This is done based on the assumption that if the caller is trying * to do key mapping, it doesn't want the mapped keys to get into dialog edit * text items. */ # include "TransSkel1.h" # define returnKey 13 # define enterKey 3 # define escapeKey 27 # define normalHilite 0 # define dimHilite 255 /* * Function that checks whether a dialog item is a button. If so, * flash it. In addition, if it's active, return true to indicate an * item hit. Otherwise return false. */ static Boolean TestDlogButton (DialogPtr d, short item) { ControlHandle ctrl; Handle itemHandle; short itemType; Rect itemRect; GetDialogItem (d, item, &itemType, &itemHandle, &itemRect); if ((itemType & (ctrlItem + btnCtrl)) == (ctrlItem + btnCtrl)) { ctrl = (ControlHandle) itemHandle; if ((**ctrl).contrlHilite == normalHilite) { SkelFlashButton (ctrl); if ((itemType & itemDisable) == 0) return (true); } } return (false); } pascal Boolean SkelDlogMapKeyToButton (DialogPtr dlog, EventRecord *evt, short *item, short defaultItem, short cancelItem) { char c; short i; c = evt->message & charCodeMask; if (c == returnKey || c == enterKey) { i = defaultItem; if (i != 0) { if (i < 0) i = ((DialogPeek) dlog)->aDefItem; if (TestDlogButton (dlog, i)) { *item = i; return (true); } evt->what = nullEvent; } } if (c == escapeKey || SkelCmdPeriod (evt)) { *item = cancelItem; if (*item > 0) { if (TestDlogButton (dlog, *item)) return (true); evt->what = nullEvent; } } return (false); } /* * Set cursor to I-beam if it's in an edit text item of the given dialog. * * Does nothing if the FindDItem() trap doesn't exist (which it doesn't in * versions of system software prior to 3.2). */ # include # include "TransSkel1.h" pascal void SkelSetDlogCursor (DialogPtr d) { GrafPtr savePort; Point pt; short i; if (SkelTrapAvailable (_FindDItem)) { /* * Get cursor local coordinates. One assumes the port will be set * to the dialog, but you never know... */ GetPort (&savePort); SetPort (d); GetMouse (&pt); SetPort (savePort); i = FindDialogItem (d, pt) + 1; if (i > 0 && (SkelGetDlogType (d, i) & editText) == 0) i = 0; if (i > 0) SetCursor (*GetCursor (iBeamCursor)); else InitCursor (); } } /* * Get type of dialog item. */ # include "TransSkel1.h" pascal short SkelGetDlogType (DialogPtr d, short item) { short type; Handle h; Rect r; GetDialogItem (d, item, &type, &h, &r); return (type); } /* * Flash a push button to simulate a click in it. */ # include "TransSkel1.h" # define hiliteClicks 8 pascal void SkelFlashButton (ControlHandle ctrl) { short oldHilite; unsigned long dummy; oldHilite = (**ctrl).contrlHilite; HiliteControl (ctrl, kControlButtonPart); /* flash it */ Delay ((long) hiliteClicks, &dummy); HiliteControl (ctrl, oldHilite); } /* * Position a window according to the given position type, using the * given positioning ratios. * * Position types: * skelPositionNone -- leave window in current position * skelPositionOnMainDevice -- position on main device * skelPositionOnParentWindow -- position on frontmost visible window * skelPositionOnParentScreen -- position on screen of frontmost visible window * * If there's no frontmost window, positions that use it default to * skelPositionOnMainDevice. * * For best results, window should not be visible. Otherwise you'll end * up moving it while it's visible. * * 08 Feb 94 * - Position window using structure rather than content rectangle. */ # include "TransSkel1.h" pascal void SkelPositionWindow (WindowPtr w, short positionType, Fixed hRatio, Fixed vRatio) { Rect contentRect, structRect, refRect; short hDiff, vDiff; if (positionType == skelPositionNone) /* leave window as is */ return; /* get rect to use as reference against which to position window rect */ SkelGetReferenceRect (&refRect, positionType); /* * Use structure rect as the rect to be positioned, but when moving window, * offset by difference between upper left of structure and content rects, * since MoveWindow() positions the content rect to the given position. */ SkelGetWindContentRect (w, &contentRect); SkelGetWindStructureRect (w, &structRect); hDiff = contentRect.left - structRect.left; vDiff = contentRect.top - structRect.top; SkelPositionRect (&refRect, &structRect, hRatio, vRatio); MoveWindow (w, structRect.left + hDiff, structRect.top + vDiff, false); } /* * Get bounding rectangle of a dialog item. */ # include "TransSkel1.h" pascal void SkelGetDlogRect (DialogPtr d, short item, Rect *r) { short type; Handle h; GetDialogItem (d, item, &type, &h, r); } /* * Set bounding rectangle of a dialog item. */ # include "TransSkel1.h" pascal void SkelSetDlogRect (DialogPtr d, short item, Rect *r) { short type; Handle h; Rect rOld; GetDialogItem (d, item, &type, &h, &rOld); SetDialogItem (d, item, type, h, r); } /* * Return a handle to the control associated with a dialog item. */ # include "TransSkel1.h" pascal ControlHandle SkelGetDlogCtl (DialogPtr d, short item) { short type; Handle h; Rect r; GetDialogItem (d, item, &type, &h, &r); return ((ControlHandle) h); } /* * Associate a button-outlining function with the given item, which should * be a user item. The item outlined will be the default item, and should * be a push button. The user item bounding rectangle is positioned and * sized to surround the default item. * * There's a subtle point here -- the outline drawing proc is called when the * user item rect is becomes invalid, but the drawing proc bases its calculations * on the rect for the default button item. This works because the rect it * calculates based on the button rect is identical to that of the user item. * * If you change the button's hiliting state, you should make sure the outline * is redrawn by invalidating its bounding rectangle. You can avoid unnecessary * redrawing by using SkelSetDlogCtlHilite() like so: * * if (SkelSetDlogCtlHilite (dlog, buttonItem, newHilite) * { * SkelGetDlogRect (dlog, outlineItem, &r); * InvalRect (&r); * } */ # include "TransSkel1.h" /* * Draw heavy outline around default dialog button. */ static pascal void DrawDlogButtonOutline (DialogPtr d, short item) { SkelDrawButtonOutline (SkelGetDlogCtl (d, ((DialogPeek) d)->aDefItem)); } static ControlActionUPP DrawDlogButtonOutlineUPP = NULL; pascal void SkelSetDlogButtonOutliner (DialogPtr d, short item) { short type; Handle h; Rect r, rJunk; short defItem; /* find default item bounding rectangle */ defItem = ((DialogPeek) d)->aDefItem; GetDialogItem (d, defItem, &type, &h, &r); /* get user item, position rectangle, and install draw proc using it */ GetDialogItem (d, item, &type, &h, &rJunk); InsetRect (&r, -4, -4); if (! DrawDlogButtonOutlineUPP) DrawDlogButtonOutlineUPP = NewControlActionProc((ProcPtr) DrawDlogButtonOutline); SetDialogItem (d, item, type, (Handle) DrawDlogButtonOutlineUPP, &r); } /* * Draw a heavy outline around a push button. Draw the outline in black * unless the button is inactive, in which case draw it gray. When drawing * gray outlines, use a true RGB gray if the monitor supports it and the button * belongs to a color GrafPort, and pattern gray otherwise. This matches how * the Control Manager draws titles in dimmed buttons. * * It's absolutely astonishing how much work is necessary to draw a gray * outline. This is evidenced not only by all the code below, but also by * the efforted expended in SkelGetRectDevice() to make sure the gray is * computed using the characteristics of the proper device. It's a wonder * it doesn't take 10 minutes to draw dimmed outlines. */ # include /* for GetGray() */ # include "TransSkel1.h" # define normalHilite 0 # define dimHilite 255 pascal void SkelDrawButtonOutline (ControlHandle ctrl) { GrafPtr oldPort; PenState penState; Rect r, r2; short curvature; Boolean haveRGBGray; RGBColor fgColor, newFgColor, bgColor; GDHandle gDev; GetPort (&oldPort); SetPort ((**ctrl).contrlOwner); GetPenState (&penState); PenNormal (); r = (**ctrl).contrlRect; InsetRect (&r, -4, -4); curvature = (r.bottom - r.top) / 2 + 2; PenSize (3, 3); if ((**ctrl).contrlHilite == normalHilite) /* button active, draw black */ FrameRoundRect (&r, curvature, curvature); else /* button inactive, draw gray */ { /* * Try to get RGB gray value appropriate for the device on which * the button is displayed if have color GrafPort. */ haveRGBGray = false; if (((CGrafPtr) (**ctrl).contrlOwner)->portVersion & 0xc000) { /* convert rect to global coordinates */ r2 = r; LocalToGlobal (&topLeft (r2)); LocalToGlobal (&botRight (r2)); (void) SkelGetRectDevice (&r2, &gDev, (Rect *) nil, (Boolean *) nil); /* test unnecessary unless for some reason rect isn't on any device! */ if (gDev != (GDHandle) nil) { GetBackColor (&bgColor); GetForeColor (&fgColor); newFgColor = fgColor; haveRGBGray = GetGray (gDev, &bgColor, &newFgColor); } } /* * Draw using colored gray if possible, else using pattern gray */ if (haveRGBGray) RGBForeColor (&newFgColor); else PenPat (&gray); FrameRoundRect (&r, curvature, curvature); if (haveRGBGray) RGBForeColor (&fgColor); } SetPenState (&penState); SetPort (oldPort); } /* * SkelPositionRect() * * Position a rectangle relative to reference rectangle so that space above/below * and to left/right of rectangle that's moved maintains ratio given by hRatio and * vRatio. This is useful for establishing initial window positions or positioning * alerts/dialogs. * * "inside" isn't quite the right word, since the moved rectangle need not * actually be smaller than the reference rectangle. * * Examples: * * Center a rectangle inside the reference rectangle: * SkelPositionRect (&ref, &r, FixRatio (1, 2), FixRatio (1, 2)); * * Leave 1/3 of vertical space above positioned rectangle, 2/3 of space below: * SkelPositionRect (&ref, &r, FixRatio (1, 2), FixRatio (1, 3)); * * Algorithm may not work correctly if rects have negative coordinates. */ # include # include "TransSkel1.h" pascal void SkelPositionRect (Rect *refRect, Rect *r, Fixed hRatio, Fixed vRatio) { short hOff, vOff; /* align topleft of rects (simplifies calculations) */ OffsetRect (r, refRect->left - r->left, refRect->top - r->top); /* calculate offsets in each direction for given ratios */ hOff = Fix2Long (FixMul (Long2Fix ((long) (refRect->right - r->right)), hRatio)); vOff = Fix2Long (FixMul (Long2Fix ((long) (refRect->bottom - r->bottom)), vRatio)); /* move rect by appropriate amount */ OffsetRect (r, hOff, vOff); } /* * Get a reference rectangle for window positioning. * * Reference rect for position types: * skelPositionNone -- no reference rect * skelPositionOnMainDevice -- usable area on main device * skelPositionOnParentWindow -- content rect of frontmost visible window * skelPositionOnParentDevice -- usable area on screen of frontmost visible window * * If there's no frontmost window, positions that use it default to * skelPositionOnMainDevice. * * Result for position skelPositionNone is same as for skelPositionOnMainDevice * just so that result isn't undefined, but caller would be better off to handle * skelPositionNone case itself. * * 18 Feb 94 * - Return structure rect rather than content rect of parent window when * position type is skelPositionOnParentWindow. */ # include "TransSkel1.h" pascal void SkelGetReferenceRect (Rect *r, short positionType) { WindowPtr frontWind = FrontWindow (); /* * Assume default positioning will be with reference to main device. * This will also be used as the fallback for positionings that use * FrontWindow() if FrontWindow() is nil. */ SkelGetMainDeviceRect (r); if (positionType == skelPositionNone) /* leave window as is */ return; /* * Find frontmost visible window */ frontWind = FrontWindow (); while (frontWind != (WindowPtr) nil && !((WindowPeek) frontWind)->visible) frontWind = (WindowPtr) ((WindowPeek) frontWind)->nextWindow; switch (positionType) { case skelPositionOnParentWindow: if (frontWind != (WindowPtr) nil) SkelGetWindStructureRect (frontWind, r); break; case skelPositionOnParentDevice: if (frontWind != (WindowPtr) nil) (void) SkelGetWindowDevice (frontWind, (GDHandle *) nil, r); break; } } /* * Get usable area on main device. Does not include menu bar; if that's * of interest, do this: * * SkelGetMainDeviceRect (&r); * r.top -= SkelQuery (skelQMBarHeight); */ # include "TransSkel1.h" pascal void SkelGetMainDeviceRect (Rect *r) { if (!SkelQuery (skelQHasColorQD)) /* no devices, use screenBits */ *r = screenBits.bounds; else *r = (**GetMainDevice ()).gdRect; r->top += SkelQuery (skelQMBarHeight); }