ChooseTkl.c

/*
    File:       ChooseTkl.c
 
    Contains:   just one enourmously long routine, DoChooseMsg.  This
                routine handles the display of a menu after it's been pulled down or popped up
                and before it's erased.
 
    Written by:     
 
    Copyright:  Copyright © 1991-1999 by Apple Computer, Inc., All Rights Reserved.
 
                You may incorporate this Apple sample source code into your program(s) without
                restriction. This Apple sample source code has been provided "AS IS" and the
                responsibility for its operation is yours. You are not permitted to redistribute
                this Apple sample source code as "Apple sample source code" after having made
                changes. If you're going to re-distribute the source, we require that you make
                it clear in the source that the code was descended from Apple sample source
                code, but that you've made changes.
 
    Change History (most recent first):
                8/10/1999   Karl Groethe    Updated for Metrowerks Codewarror Pro 2.1
                
 
*/
 
/******************************************************************************\
* Header Files
\******************************************************************************/
 
#include <Memory.h>
#include <Menus.h>
#include <Quickdraw.h>
#include <Types.h>
#include "ChooseTkl.h"
#include "Concordia.h"
#include "DrawTkl.h"
#include "SizeTkl.h"
 
 
/******************************************************************************\
* Constants & Macros
\******************************************************************************/
 
#define noScroll   ((short) 0) //Must not scroll
#define upScroll   ((short) 1) //Must scroll up
#define downScroll ((short) 2) //Must scroll down
 
/* System Globals */
#define sgHuhRect     *((Rect *) 0x09FA)  //MenuSelect seems to need this
#define sgMenuDisable *((long *) 0x0B54)  //MenuDisable system global (IM-V)
 
 
/******************************************************************************\
* Function Prototypes
\******************************************************************************/
 
void RedrawItem (MenuHandle, short, Rect *, short);
 
 
#pragma segment Main
/******************************************************************************\
* DoChooseMsg - Choose a menu item
*
* DoChooseMsg is an unbelievably long routine which processes the mChooseMsg
* menu message for the menu specified by TheMenu.  DoChooseMsg will determine
* which menu items must be hilighted and unhighlighted, and will highlight and
* unhighlight those items appropriately.  If TheMenu has scroll arrows and the
* location of the mouse (given in HitPt), is located in these scroll arrows,
* DoChooseMsg will scroll the menu.  DoChooseMsg will then draw any newly-
* exposed menu items.  When the end of TheMenu is reached while scrolling, the
* scroll arrow at that end will be erased and the menu item behind it will be
* drawn.  If a scrolling menu was at one end when it's scrolled, DoChooseMsg
* will draw a new scroll arrow at that end.  The item number of the chosen item
* is returned, or 0 if no item is chosen.
*
* Coding Notes
* #A# - This loop calculates the item number and item rectangle of the item
*       being chosen, the item rectangle of the item that was chosen the last
*       time DoChooseMsg was called, and the sum of the heights of all of the
*       items in TheMenu.  If no item is chosen or if the item is disabled, the
*       item number will be calculated as 0.
* #B# - Read as: If the vertical coordinate in the TopMenuItem system global
*       doesn't coincide with the top of the menu rectangle, this menu has a top
*       scroll arrow.
* #C# - Read as: If the vertical coordinate in the AtMenuBottom system global
*       doesn't coincide with the bottom of the menu rectangle, this menu has a
*       bottom scroll arrow.
* #D# - If the menu has a top or bottom scroll arrow, set the clipping region so
*       that the scroll arrows won't be stomped on when items are hilighted and
*       unhilighted.
* #E# - If we're scrolling and there's no scroll bar at the other end, it must
*       be drawn and the scroll rectangle adjusted for that.
* #F# - If we've scrolled to an end of a menu, the scroll bar at that end must
*       be drawn over.
* #G# - This loop draws the menu items that intersect with UpdateRect.
* #H# - If we saved the clip region, we'd better restore it.
* #I# - Side Effect: System globals TopMenuItem and AtMenuBottom are modified
*       here.  Icky, Poo!
* #J# - Set high word of MenuDisable system global to menu ID, and low word to
*       the item number.  Yes folks, even if the item is disabled.
* #K# - If the item is disabled, its item number will be the 2's complement of
*       whatever it should be.
* #L# - Why do I have to do this?  BEATS THE HELL OUT OF ME!
* #M# - Even if the new item isn't hierarchical, it might still get stomped on
*       when a hierarchical menu is erased.
* #N# - We donÕt need the menuEnabled bit any more so get rid of it by shifting
*       it out.  Then we have to set the high bit so that all items beyond the
*       31st will be enabled.  With arithmetic right shifting, this high bit
*       will be preserved.
* #O# - If a hierarchical menu is up right at the top or bottom of the screen
*       and the user moves the mouse into the scroll area of the main menu, we
*       get a mChooseMsg with the hit point in the scroll area before the child
*       menu is brought down.  That would cause DoChooseMsg to scroll the main
*       menu while the child menu is still up.  Because the Menu Bar Definition
*       Procedure saves the bits behind the child menu and because the child
*       menu slightly overlaps the main menu, scrolling while the hierarchical
*       menu is up causes a little bit of garbage to appear on the screen.  To
*       prevent this, the DoDrawMsg routine clears a field in the MBSaveLoc
*       record to 0.  DoChooseMsg checks this field.  If itÕs set to 1, then
*       scrolling is done as usual.  If itÕs clear, then scrolling is supressed
*       to give the menu bar definition procedure some time to bring down the
*       hierarchical menu first.
\******************************************************************************/
 
short
DoChooseMsg (MenuHandle TheMenu,Rect* MenuRect,Point HitPt,short WhichItem)
{
    Str255        *ItemString; //-> Menu item's string
    ItemInfoPtr   ItemInfo;    //-> Item info record
    short         ItemHeight;  //Height of menu item in pixels
    Rect          ItemRect;    //Item's rectangle in screen coords
    short         TotHeight;   //Total height of menu in pixels
    Rect          NewRect;     //Rectangle of newly-chosen item in global coords
    short         NewItem;     //Item number of newly-chosen item
    Rect          OldRect;     //Rectangle of old chosen item in global coords
    short         OldItem;     //Item number of old chosen item
    short         CurrItem;    //Item number of item being processed
    short         NewIsHier;   //True if NewItem specifies hierarchical item
    short         OldIsHier;   //True if OldItem specifies hierarchical item
    short         HasTopScrl;  //True if menu has top scroll arrow
    short         HasBotScrl;  //True if menu has bottom scroll arrow
    short         MustScrl;    //Which direction must menu scroll, if any?
    short         ScrollAmt;   //Scrolling distance in pixels
    RgnHandle     SavedClip;   //=> Clip region before DoChooseMsg is called
    Rect          MenuClip;    //Menu's clip rectangle, if scroll icons present
    RgnHandle     UpdateRgn;   //=> Menu update region when scrolled
    Rect          UpdateRect;  //Menu update rectangle when scrolled
    long          EnableFlags; //Menu's enable flags
    Boolean       dontScroll;  /* True if scrolling shouldnÕt take place */
 
    EnableFlags = (unsigned long) (**TheMenu).enableFlags;
    if (EnableFlags & 1)
        {
        EnableFlags >>= (unsigned long) 1; //#N#
        EnableFlags |= 0x80000000;
        }
    else
        EnableFlags = (unsigned long) 0;
    SavedClip = (RgnHandle) null;
    NewRect = OldRect = *MenuRect;
    NewRect.top = NewRect.bottom = OldRect.top = OldRect.bottom = sgTopMenuItem;
    NewItem = 0;
    OldItem = 0;
    CurrItem = 1;
    TotHeight = 0;
    NewIsHier = OldIsHier = false;
    dontScroll = *((short *) ((*sgMBSaveLoc) + 16)) == 1; //#O#
    if (dontScroll)
        *((short *) ((*sgMBSaveLoc) + 16)) = 0;
    HLock ((Handle) TheMenu);
    ItemString = (Str255 *) (**TheMenu).menuData;
    ItemString = (Str255 *) ((Byte *) ItemString + strSize (*ItemString));
    while ((*ItemString) [0] != '\0') //#A#
        {
        ItemInfo = (ItemInfoPtr) ((Byte *) *ItemString + strSize (*ItemString));
        ItemHeight = CalcItemHeight (*ItemString, ItemInfo);
        if (NewItem == 0)
            {
            NewRect.bottom = NewRect.top + ItemHeight;
            if (HitPt.v > NewRect.top && HitPt.v <= NewRect.bottom)
                {
                if (EnableFlags & 1) //#K#
                    NewItem = CurrItem;
                else
                    NewItem = -CurrItem;
                if (ItemInfo->kbdEquiv == (char) hMenuCmd)
                    NewIsHier = true;
                }
            else
                NewRect.top += ItemHeight;
            }
        if (OldItem == 0)
            if (WhichItem == CurrItem)
                {
                OldRect.bottom = OldRect.top + ItemHeight;
                OldItem = WhichItem;
                if (ItemInfo->kbdEquiv == (char) hMenuCmd)
                    OldIsHier = true;
                }
            else
                OldRect.top += ItemHeight;
        TotHeight += ItemHeight;
        CurrItem += 1;
        ItemString = (Str255 *) (ItemInfo + 1);
        EnableFlags >>= 1;
        }
    HUnlock ((Handle) TheMenu);
    if (HitPt.h < MenuRect->left || HitPt.h > MenuRect->right)
        NewItem = 0;
    MustScrl = noScroll;
    HasTopScrl = HasBotScrl = false;
    if (sgTopMenuItem != MenuRect->top) //#B#
        {
        HasTopScrl = true;
        if (HitPt.v < MenuRect->top + scrlIconHeight)
            {
            NewItem = 0;
            MustScrl = downScroll;
            }
        }
    if (sgAtMenuBottom != MenuRect->bottom) //#C#
        {
        HasBotScrl = true;
        if (HitPt.v > MenuRect->bottom - scrlIconHeight)
            {
            NewItem = 0;
            MustScrl = upScroll;
            }
        }
    if (HasBotScrl || HasTopScrl) //#D#
        {
        MenuClip = *MenuRect;
        if (HasBotScrl)
            MenuClip.bottom -= scrlIconHeight;
        if (HasTopScrl)
            MenuClip.top += scrlIconHeight;
        SavedClip = NewRgn ();
        GetClip (SavedClip);
        ClipRect (&MenuClip);
        }
    if (NewItem == 0)
        sgMenuDisable = 0L;
    else
        {
        sgMenuDisable = (**TheMenu).menuID << (sizeof(long) / 2) | (NewItem > 0 ?
                NewItem : -NewItem); //#J# #K#
        if (NewItem < 0)
            NewItem = 0;
        }
    if (NewItem != 0 && NewIsHier) //#L#
        {
        sgHuhRect = NewRect;
        *((Rect *) ((*sgMBSaveLoc) + 6)) = NewRect;
        }
    if (NewItem != OldItem)
        {
        if (OldItem != 0)
            if (OldIsHier)
                RedrawItem (TheMenu, OldItem, &OldRect, false);
            else
                InvertRect (&OldRect);
        if (NewItem != 0)
            if (OldIsHier) //#M#
                RedrawItem (TheMenu, NewItem, &NewRect, true);
            else
                InvertRect (&NewRect);
        }
    if (!dontScroll && MustScrl && HitPt.h >= MenuRect->left && HitPt.h <=
            MenuRect->right)
        {
        if (MustScrl == upScroll)
            {
            if (! HasTopScrl) //#E#
                {
                DrawScroll (MenuRect, topScroll);
                MenuClip.top += scrlIconHeight;
                }
            ScrollAmt = MenuClip.bottom - HitPt.v;
            if (ScrollAmt < MenuRect->bottom - sgAtMenuBottom)
                ScrollAmt = MenuRect->bottom - sgAtMenuBottom;
            UpdateRgn = NewRgn ();
            ScrollRect (&MenuClip, 0, ScrollAmt, UpdateRgn);
            UpdateRect = (**UpdateRgn).rgnBBox;
            DisposeRgn (UpdateRgn);
            sgTopMenuItem += ScrollAmt; //#I#
            sgAtMenuBottom += ScrollAmt;
            if (sgAtMenuBottom == MenuRect->bottom) //#F#
                UpdateRect.bottom += scrlIconHeight;
            if (SavedClip == (RgnHandle) null)
                {
                SavedClip = NewRgn ();
                GetClip (SavedClip);
                }
            ClipRect (&UpdateRect);
            EraseRect (&UpdateRect);
            }
        else
            {
            if (! HasBotScrl) //#E#
                {
                DrawScroll (MenuRect, botScroll);
                MenuClip.bottom -= scrlIconHeight;
                }
            ScrollAmt = MenuClip.top - HitPt.v;
            if (ScrollAmt > MenuRect->top - sgTopMenuItem)
                ScrollAmt = MenuRect->top - sgTopMenuItem;
            UpdateRgn = NewRgn ();
            ScrollRect (&MenuClip, 0, ScrollAmt, UpdateRgn);
            UpdateRect = (**UpdateRgn).rgnBBox;
            DisposeRgn (UpdateRgn);
            sgTopMenuItem += ScrollAmt;
            sgAtMenuBottom += ScrollAmt;
            if (sgTopMenuItem == MenuRect->top) //#F#
                UpdateRect.top -= scrlIconHeight;
            if (SavedClip == (RgnHandle) null)
                {
                SavedClip = NewRgn ();
                GetClip (SavedClip);
                }
            ClipRect (&UpdateRect);
            EraseRect (&UpdateRect);
            }
        SetRect (&ItemRect, MenuRect->left, sgTopMenuItem, MenuRect->right,
                sgTopMenuItem);
        EnableFlags = (unsigned long) (**TheMenu).enableFlags;
        if (EnableFlags & 1)
            {
            EnableFlags >>= (unsigned long) 1;
            EnableFlags |= 0x80000000;
            }
        else
            EnableFlags = (unsigned long) 0;
        HLock ((Handle) TheMenu);
        ItemString = (Str255 *) (**TheMenu).menuData;
        ItemString = (Str255 *) ((Byte *) ItemString + strSize (*ItemString));
        while ((*ItemString) [0] != '\0') //#G#
            {
            ItemInfo = (ItemInfoPtr) ((Byte *) *ItemString + strSize
                    (*ItemString));
            ItemRect.bottom = ItemRect.top + CalcItemHeight (*ItemString,
                    ItemInfo);
            if (ItemRect.top < UpdateRect.bottom && ItemRect.bottom >
                    UpdateRect.top)
                DrawItem (*ItemString, ItemInfo, &ItemRect, EnableFlags & 1);
            EnableFlags >>= 1;
            ItemRect.top = ItemRect.bottom;
            ItemString = (Str255 *) (ItemInfo + 1);
            }
        HUnlock ((Handle) TheMenu);
        }
    if (SavedClip != (RgnHandle) null) //#H#
        {
        SetClip (SavedClip);
        DisposeRgn (SavedClip);
        }
    return NewItem;
    }
 
 
#pragma segment Main
/******************************************************************************\
* RedrawItem - Redraw a menu item for hierarchical menus
*
* RedrawItem redraws a menu item that's been clobbered by a hierarchical menu.
* The affected menu is specified by TheMenu.  The item number of the menu item
* to redraw is given in ItemNum, while the item's rectangle is given in
* ItemRect.  If the item is going to be selected, Select must be true so that
* the item will be redrawn in inverse.  Otherwise Select must be false so that
* the item will be redrawn normally.
\******************************************************************************/
 
static void
RedrawItem (MenuHandle TheMenu,short ItemNum,Rect* ItemRect,short Select)
{
    Str255       *ItemString; //-> Menu item's string
    ItemInfoPtr  ItemInfo;    //-> Item info record
    short        CurrItem;    //Item number of item being checked
    TextStateRec TextState;   //Current text modes, etc.
 
    CurrItem = 0;
    HLock ((Handle) TheMenu);
    ItemString = (Str255 *) (**TheMenu).menuData;
    ItemString = (Str255 *) ((Byte *) ItemString + strSize (*ItemString));
    while (ItemNum != CurrItem && (*ItemString) [0] != '\0')
        {
        ItemInfo = (ItemInfoPtr) ((Byte *) *ItemString + strSize
                (*ItemString));
        CurrItem += 1;
        if (ItemNum == CurrItem)
            {
            GetTextState (&TextState);
            EraseRect (ItemRect);
            DrawItem (*ItemString, ItemInfo, ItemRect, true);
            if (Select)
                InvertRect (ItemRect);
            SetTextState (&TextState);
            }
        else
            ItemString = (Str255 *) (ItemInfo + 1);
        }
    HUnlock ((Handle) TheMenu);
    }