mac-rom/Toolbox/DialogMgr/ModalDialogMenuExtensions.c

677 lines
19 KiB
C
Raw Normal View History

/*
File: ModalDialogMenuExtensions.c
Contains: C language portions of Modal Dialog Menu Access Facillity
Written by: Darin Adler
Copyright: <EFBFBD> 1990-1992 by Apple Computer, Inc., all rights reserved.
Change History (most recent first):
<23> 11/3/92 DTY Strip out unnecessary includes.
<22> 6/10/92 JSM Moved this file to ModalDialogMenuExtensions.c from
ModalDialogMenuPatches.c since it<EFBFBD>s used by both the ROM and
System, keeping all the old revisions.
<21> 5/29/92 DCL Included TextUtils.p. EqualString was moved for the New Inside
Mac.
<20> 3/23/92 JSM OSEvents.h is obsolete, use Events.h.
<19> 2/20/91 KSM gbm,#KSM-3: Don't check spareflag of a NIL window.
<18> 2/10/91 KSM DBA,#80950: Fix to handle graying of Applications Menu properly.
<17> 1/5/91 fjs (KSM) do not allow non-printing function keys
<16> 12/18/90 KSM <rlc> Fix bug where DialogMgr incorrectly sets up the EditField
of the dialog record causing the Edit Menu to be incorrectly
enabled.
<15> 12/3/90 RLC <ksm> Change calls to IsWindowModal() to call the newer
GetWindowModalClass instead.
<14> 11/26/90 KSM (dba+dh)Remove extraneous ifdefs around includes. Also only
analyze in foregnd.
<13> 11/14/90 JL Changing InvalidMenuBar back to InvalMenuBar because Invalid is
misleading. All Inval<EFBFBD> calls should be expanded to Invalidate if
they are expanded at all.
<12> 11/13/90 KSM <dba>Fix bug where we checked spareFlag directly instead of
calling IsWindowModal. IsWindowModal handles nil correctly, and
is smarter about variation 1 of WDEF 0. Change
SetSaveMenuEnableState to save the menu state, even if we are
only setting up the system menus and not disabling the
application menus. This fixes a bug where the system menus get
permanently disabled.
<11> 11/6/90 KSM <rlc>Revisit role of SetSaveMenuEnableState and ActiveWindowNeedsHelp
to handle the additional case of apps using modal window kinds, but
never calling _ModalDialog.
<10> 10/29/90 KSM <dba>Remove some dead code. Update to look for the modal dialog bit
before handling menus. Enable edit menu iff edit text is active.
<9> 10/15/90 JSM Really change InvalMenuBar to InvalidMenuBar.
<8> 10/15/90 JL Changing InvalMenuBar to InvalidMenuBar.
<7> 7/31/90 KSM Fix edit menu analysis state handling.
<6> 7/13/90 RLC Fix bug in testing menuselect result, hiword will be zero, but
entire longword might not be.
<5> 7/13/90 KSM Fix edit menu enabling check. Remove extra InvalMenuBar call in
EnableTheEditMenu.
<4> 7/10/90 dba get rid of C warnings
<3> 7/2/90 KSM Add edit menu handling.
<2> 6/8/90 KSM Update to use new IsFrontWindow modal calls.
<1> 6/1/90 KSM First checked in.
To Do:
add a MENU resource to indicate names of Edit menu items
find items from the Edit menu to enable them
use knowledge about Dialog Manager to know if Edit menu items should be enabled
note that we should *only* enable edit menus if the frontmost window is a
windowKind == dialogKind and there is an appropriate active editText field
(a visible TextEdit record that is currently active)
enable help menu
pass Cut, Copy, Paste, Clear, Select All through dialog filter as command keys
make a separate patch to Dialog Manager to make it handle command keys
use a global flag to prevent from saving the menu state twice
??? save menu state in a global instead of returning it as a function result
find all the *** and get rid of them
*/
/*
Modal Dialog Menu Access
Theory of Operation (Volume 1, Chapter 1, section 1)
The state of the world is <EFBFBD>normal.<EFBFBD>
STATE TRANSITION TABLE
State Event Event Event MenuSelect FlushEvents SysBeep
=MouseDn =NullEvt =Other (from App) (from anyone) (from App)
----- --------- --------- --------- ----------- ------------- -------------
0 1 0 0 0 0 0
1 0 3 0 0 0 2
2 0 3 0 0 0 2
3 0 0 0 0 0 0
What this means is:
State 0: This is the normal state of this code. If we see a mousedown in the menubar, we might
want to handle the menuselect call, but the app might already be doing this. So we save this
event and move to state 1.
State 1: We have seen a mousedown, but we are waiting to see if the application
is handling menus during modal dialogs. If we see the app call menuselect, we know it is
handling menus and return to state 0. If we see a flushevents call, we always go to state 0
and forget any event information we saved coming to this state. If we see a SysBeep, it might
be that that ModalDialog filter proc is complaining we clicked out of the dialog, or we may just
have gotten an extra beep. The SysBeep is "saved" and we go to state 2. A null event assumes
that the app had the change to handle the menus, but did not - so we assume it won't, go to
state 3.
State 2: We saw a sysbeep. If we see a null event, we have "eaten" the sysbeep and go to state
3. If we see another SysBeep, we Beep (for the one that got us to this state) and save off the
new SysBeep and stay in this state.
State 3: We've done our normal code and go back to state 0. This is implemented in the opposite
order, in that, we set the state to zero as we proceed on into the regular code.
------------------------------------------
In general, new patches do the following:
FlushEvents: go to state zero
MenuSelect: go to state zero (if the app called it)
SysBeep: if state 1, don't beep and go to state 2
if state 2, beep and stay in state 2
FilterEvent if state 0, mousedown goes to state 1
if state 1, null event goes to state 3, otherwise state 0
if state 2, null event goes to state 3, otherwise state 0
if state 3, we'll be on our way to state 0 anyway.
*/
#include <Dialogs.h>
#include <Events.h>
#include <Memory.h>
#include <Menus.h>
#include <Processes.h>
#include <TextUtils.h>
#include <Windows.h>
#include <DialogsPriv.h>
#include <MenuMgrPriv.h>
#define kFunctionKey 0x10
#define kCut 0x78
#define kCopy 0x63
#define kPaste 0x76
// Menu Mgr. data structures
typedef struct
{
short lastMenu;
short lastRight;
short reserved;
}
MenuListHeader;
typedef struct
{
MenuHandle handle;
short left;
}
MenuListEntry;
// menu bar iteration
MenuListHeader** GetMenuList(void);
MenuListEntry* GetNextMenu(short* offset);
// other menu operations
Boolean MenuBarEmpty(void);
Boolean PtInMenuBar(Point);
Boolean IsThisASystemMenu(short menuID);
Boolean IsModal(WindowPtr);
MenuHandle FindMenuByTitle(const unsigned char* title);
MenuHandle FindAppleMenu(void);
long GetMenuFlags(MenuHandle);
void DisableAppMenus(void);
void EnableTheEditMenu(void);
// routines to save enable state of all menus in the menu bar
Handle SaveMenusEnableState(void);
// globals (implemented in assembly) to keep our state
enum
{
kApplicationHandlesMenus,
kHandleSystemMenusDisabled,
kHandleMenusEditMenuDisabled,
kHandleMenusEditMenuEnabled,
};
typedef short TAnalyzedWindowState;
typedef struct
{
MenuHandle handle;
long savedEnableFlags;
}
MenuStateEntry;
pascal WindowPtr GetAnalyzedWindow(void);
pascal void SetAnalyzedWindow(WindowPtr);
pascal TAnalyzedWindowState GetAnalyzedWindowState(void);
pascal void SetAnalyzedWindowState(TAnalyzedWindowState);
pascal Handle GetSavedMenuState(void);
pascal void SetSavedMenuState(Handle);
// call to enable all menus for us
pascal void ModalDialogMenuSetup(Boolean nowModal)
= {0xAA67};
// heuristics used by the patches
pascal TAnalyzedWindowState ActiveWindowNeedsHelp(void);
// routines called by assembly language
pascal Handle SetSaveMenusEnableState(TAnalyzedWindowState state); // save state and enable selected menus
pascal void RestoreMenusEnableState(Handle state);
pascal void FilterEvent(EventRecord*);
// implementations
MenuListHeader** GetMenuList(void)
{
long result = (* (long*) 0xA1C);
if (result & 1)
return 0;
else
return (MenuListHeader**) result;
}
MenuListEntry* GetNextMenu(short* offset)
{
MenuListHeader** list = GetMenuList();
if (list == 0)
return 0;
*offset += sizeof(MenuListEntry);
if (*offset > (**list).lastMenu)
return 0;
else
return (MenuListEntry*) (((char*) *list) + *offset);
}
Boolean MenuBarEmpty(void)
{
short offset = 0;
return GetNextMenu(&offset) == 0; // no next menu means no menus in bar
}
Handle SaveMenusEnableState(void)
{
short offset;
MenuListEntry* mlEntry;
Handle handle;
offset = 0;
// First check to see if we already had saved the state (reentrant check)
if (GetSavedMenuState())
return 0;
// make a handle for saved enable states
handle = NewHandle(0);
if (handle == 0)
return 0;
// traverse the menus, saving state
while (mlEntry = GetNextMenu(&offset))
{
MenuStateEntry saved;
MenuHandle menu;
// make the saved state, and append it to the handle
menu = mlEntry->handle;
saved.handle = menu;
saved.savedEnableFlags = (**menu).enableFlags;
if (PtrAndHand((Ptr) &saved, handle, sizeof(MenuStateEntry)) != noErr)
{
DisposHandle(handle);
return 0;
}
}
// Be sure to save the state in our global
SetSavedMenuState(handle);
return handle;
}
pascal void RestoreMenusEnableState(Handle state)
{
short offset = 0;
MenuListEntry* mlEntry;
// traverse the menus, restoring all the enable flags
while (mlEntry = GetNextMenu(&offset))
{
MenuHandle handle = mlEntry->handle;
MenuStateEntry* stateEntry = (MenuStateEntry*) *state;
MenuStateEntry* afterLast = (MenuStateEntry*) (((char*) stateEntry) + GetHandleSize(state));
// search through the state handle for saved state for this menu
while (stateEntry < afterLast)
{
if (stateEntry->handle == handle)
{
// found it, jam it in enable flags and continue (unless system menu)
if (!IsThisASystemMenu((**handle).menuID))
(**handle).enableFlags = stateEntry->savedEnableFlags;
break;
}
stateEntry++;
}
}
DisposHandle(state);
// Be sure to clean up our global
SetSavedMenuState(0);
ModalDialogMenuSetup(false); // Tell system menus to return to normal state
}
void DisableAppMenus(void)
{
short offset = 0;
MenuListEntry* mlEntry;
// traverse the menus, setting all the enable flags to zero (unless system menu)
while (mlEntry = GetNextMenu(&offset))
{
if (!IsThisASystemMenu((**(mlEntry->handle)).menuID))
(**(mlEntry->handle)).enableFlags = 0;
}
}
Boolean ThereIsAnActiveEditTextFieldInThisWindow(WindowPtr active)
{
if (((WindowPeek)active)->windowKind == dialogKind)
{
// For some reason, NewDialog does not set up the editField in the DialogRecord.
// The next DialogMgr call does though, hence this peculiar call to GetDItem.
// Additionally, call SelIText on a stattext field points editField to that
// statText field, which belies the name editField.
short kind;
Handle h;
Rect r;
register short editField = ((DialogPeek)active)->editField; // Get the edit field locally
if (editField < 0) editField = 0; // Force into range
GetDItem(active, ++editField, &kind, &h, &r); // Inc. editField before use
// Check for editField erroneously pointing to a statText field (MPW 3.2 case)
if ((editField < 0) || ((kind & 0x7f) == editText))
if (((DialogPeek)active)->editField >= 0)
if ((**(((DialogPeek)active)->textH)).active != 0)
return true;
}
return false;
}
pascal Handle SetSaveMenusEnableState(TAnalyzedWindowState state)
{
/*
3 cases: regular window (do nothing)
modal window w/o _ModalDialog call (setup system menus)
_ModalDialog called (setup all menus)
*/
Handle result = nil;
if (state != kApplicationHandlesMenus)
{
// These 2 calls need to be in the opposite order in restore
ModalDialogMenuSetup(true); // System menus always need to be set up <18>
result = SaveMenusEnableState();
if (result)
{
// we have some kind of modal situation; check if we should setup all menus
if (state != kHandleSystemMenusDisabled)
{
// kHandleMenusEditMenuDisabled or kHandleMenusEditMenuEnabled: setup all the menus
DisableAppMenus();
if (state == kHandleMenusEditMenuEnabled)
EnableTheEditMenu();
}
}
}
return result;
}
void EnableTheEditMenu()
{
MenuListEntry* mlEntry;
short offset = 0;
while (mlEntry = GetNextMenu(&offset))
{
// Walk across the menus and look for Cut, Copy, and Paste (and maybe Undo) ...
MenuHandle menu = mlEntry->handle;
short items = CountMItems(menu);
short i = (short) ((items - 2) + 1); // Skip the last 2 items, since we are looking for a cluster of 3
if (i > (3 + 1)) i = (3 + 1); // Look at the first 3 items of each menu at most
while (--i > 0) // Start at the bottom of the menu, since cmd-X is usually item 3
{
short cmdChar;
GetItemCmd(menu, i, &cmdChar);
if (cmdChar == 'X')
{
long flagsToSet = (7 << i) + 1; // Compute flagsToSet now before incrementing i
GetItemCmd(menu, ++i, &cmdChar); // Check the next item (should be Copy)
if (cmdChar == 'C') // We found cmd-C, find Paste
{
GetItemCmd(menu, ++i, &cmdChar);
if (cmdChar == 'V') // We found cmd-V! This is Edit menu
(**menu).enableFlags |= flagsToSet; // So, set the flags
}
return; // Once we find cmd-X, don<6F>t look any more
}
}
}
}
MenuHandle FindMenuByTitle(const unsigned char* title)
{
short offset = 0;
MenuListEntry* mlEntry;
// traverse the menus, searching for the proper menu item
while (mlEntry = GetNextMenu(&offset))
{
MenuHandle menu = mlEntry->handle;
if (EqualString((**menu).menuData, title, false, false))
return menu;
}
return 0;
}
pascal TAnalyzedWindowState ActiveWindowNeedsHelp(void)
{
WindowPtr active;
TAnalyzedWindowState oldState;
TAnalyzedWindowState newState;
ProcessSerialNumber ourPSN;
ProcessSerialNumber currentPSN;
Boolean inFront;
if (MenuBarEmpty()) // short-circuit when there are no menus
return false;
// short circuit when current application is not frontmost since its menubar is not visible
inFront = false;
(void) GetFrontProcess(&ourPSN);
currentPSN.highLongOfPSN = 0;
currentPSN.lowLongOfPSN = kCurrentProcess;
(void) SameProcess(&currentPSN, &ourPSN, &inFront);
if (inFront == false)
return false;
active = FrontWindow();
oldState = GetAnalyzedWindowState();
if (active != GetAnalyzedWindow())
{
Boolean calledModalDialog;
// latch onto the active window
SetAnalyzedWindow(active);
SetAnalyzedWindowState(kApplicationHandlesMenus);
if (active)
{
// <18> <KSM The rest of the code in this block is change>
// Any window that is a modal dialog (WDEF 0 and variant=dBoxProc) window,
// or was the the active window at the time _ModalDialog was called
// requires us to disallow switching out (i.e., we must disable the Process menu).
// IsModal() OR _ModalDialog implies set up System Menus for a modal dialog
// But ONLY if _ModalDialog was called for this window should we check to see
// if we should handle the menus on the application's behalf.
// Find out if we called ModalDialog on this window
calledModalDialog = ((((WindowPeek)active)->spareFlag & systemHandlesMenusMask) != 0);
// Should we disable System Menus (either case)?
if (IsModal(active) || calledModalDialog)
SetAnalyzedWindowState(kHandleSystemMenusDisabled); // disable System Menus
// Should we handle menus for the application (only if ModalDialog was called)?
if (calledModalDialog)
{
// if application handles menus, Apple menu
// (or at least About item) will be disabled
MenuHandle appleMenu = FindAppleMenu();
long appleMenuFlags = GetMenuFlags(appleMenu);
if (appleMenu && (appleMenuFlags & 1) && (appleMenuFlags & 2))
{
// the application doesn<73>t handle menus, we will
if (ThereIsAnActiveEditTextFieldInThisWindow(active))
SetAnalyzedWindowState(kHandleMenusEditMenuEnabled);
else
SetAnalyzedWindowState(kHandleMenusEditMenuDisabled);
// since we allow choices from the menu bar, unhilite the menu to make that obvious
HiliteMenu(0);
}
}
}
}
newState = GetAnalyzedWindowState();
// if the state has changed, menu titles may have dimmed/undimmed so redraw them
if (oldState != newState)
InvalMenuBar();
// return true if we are handling menus
return newState;
}
pascal void FilterEvent(EventRecord* event)
{
if ((event->what == nullEvent) || (event->what == updateEvt))
(void) ActiveWindowNeedsHelp();
else if (event->what == mouseDown)
{
if (PtInMenuBar(event->where))
{
TAnalyzedWindowState state = ActiveWindowNeedsHelp();
if ((state == kHandleMenusEditMenuEnabled) || (state == kHandleMenusEditMenuDisabled))
{
long result;
register short menuID;
event->what = nullEvent;
// track the menus
result = MenuSelect(event->where);
if (menuID = (short) (result >> 16))
{
short cmdChar;
GetItemCmd(GetMHandle(menuID), (short) result, &cmdChar);
// following is a compatibility hack for Word which does not respect enableFlags
if (cmdChar != 0)
{
EvQElPtr postedEvent;
if (PPostEvent(keyDown, cmdChar, &postedEvent) != noErr)
// if the event was not posted, unhilite the menu now
HiliteMenu(0);
else
// otherwise, wait for MenuKey to unhilite it
postedEvent->evtQModifiers = cmdKey;
}
}
}
}
}
else if (event->what == keyDown)
{
if ((event->modifiers & cmdKey) || ((char) event->message == kFunctionKey))
{
TAnalyzedWindowState state = ActiveWindowNeedsHelp();
if ((state == kHandleMenusEditMenuEnabled) || (state == kHandleMenusEditMenuDisabled))
{
long result;
unsigned char key = (char) event->message;
if (key == kFunctionKey)
{
unsigned char virtualKey = (event->message & keyCodeMask) >> 8;
switch (virtualKey)
{
case kCut:
key = 'X';
break;
case kCopy:
key = 'C';
break;
case kPaste:
key = 'V';
break;
default:
key = 0;
break;
}
}
// just hilite the menus when the relevant key is pressed
if (key && (result = MenuKey(key)))
{
long dummy;
Delay(8, &dummy);
}
HiliteMenu(0);
}
}
}
if (event->what != nullEvent)
SetAnalyzedWindow((WindowPtr) 1); // set to something that is never a window
}
long GetMenuFlags(MenuHandle menu)
{
if (menu)
{
Handle savedFlags = GetSavedMenuState();
if (savedFlags)
{
MenuStateEntry* stateEntry = (MenuStateEntry*) *savedFlags;
MenuStateEntry* afterLast = (MenuStateEntry*) (((char*) stateEntry) + GetHandleSize(savedFlags));
// search through the state handle for saved state for this menu
while (stateEntry < afterLast)
{
if (stateEntry->handle == menu)
// found it, return saved enable flags
return stateEntry->savedEnableFlags;
++stateEntry;
}
}
return (**menu).enableFlags; // didn<64>t find a saved state, so return the flags from the menu
}
return 0; // didn<64>t find a menu, so return nothing enabled
}
MenuHandle FindAppleMenu(void)
{
// *** use MENU resource
return FindMenuByTitle("\p\024");
}
Boolean PtInMenuBar(Point pt)
{
Rect mbRect;
(void) GetMBARRect(&mbRect);
return PtInRect(pt, &mbRect);
}
Boolean IsThisASystemMenu(short menuID)
{
Boolean isSys;
if (IsSystemMenu(menuID, &isSys) != noErr)
return 0;
return isSys;
}
Boolean IsModal(WindowPtr active)
{
short class;
if (GetWindowModalClass(active,&class) != noErr)
return false;
return (class == dBoxProc);
}