PopUpTkl.c

/*
    File:       PopUpTkl.c
 
    Contains:   the code to process the mPopUpMsg message from the Menu
                Manager.  This involves calculating the rectangle that the popup menu should
                appear in.
 
    Written by:     
 
    Copyright:  Copyright © 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 <OSUtils.h>
#include <Script.h>
#include <Types.h>
#include "Concordia.h"
#include "DrawTkl.h"
#include "PopUpTkl.h"
#include "SizeTkl.h"
 
 
/******************************************************************************\
* Prototypes
\******************************************************************************/
 
void FindScreen (Rect *menuRect,
                 Rect *retScreenRect);
 
 
#pragma segment Main
/******************************************************************************\
* DoPopUpMsg - Calculate a popup menu's rectangle
*
* DoPopUpMsg calculates the menu rectangle for the popup menu specified by
* TheMenu.  This rectangle is returned in MenuRect.  The top-left corner of the
* menu item specified by PopUpItem will appear at the coordinates given by
* TopPos and LeftPos.  The vertical coordinate of the top of the menu's
* rectangle is returned.  If the menu runs off of the screen, its size and
* position will be adjusted.
*
* Coding Notes
* #A# - This loop calculates the total height of the menu (Height), the width of
*       the menu (Width), the height of the tallest item in the menu
*       (MaxHeight), and the distance from the top of the menu to the item
*       specified by PopUpItem (ItemPos).
* #B# - Read this as: if the height of this menu is too small to display the
*       tallest menu item and two scroll icons,then. . .
* #C# - If the height of the tallest menu item plus two scroll icons is more
*       than the height of the entire menu, adjust the menu's position so that
*       the entire menu is displayed.  Otherwise, set the height of the menu to
*       the height of the tallest item plus two scroll icons.
\******************************************************************************/
 
short
DoPopUpMsg (MenuHandle TheMenu,Rect* MenuRect,short TopPos,short LeftPos,short PopUpItem)
{
    Str255      *ItemString; //-> Item's string
    ItemInfoPtr ItemInfo;    //-> Item info
    short       TestWidth;   //Width of item to test
    short       TestHeight;  //Width of menu to test
    short       Height;      //Height of menu
    short       Width;       //Width of menu
    short       MaxHeight;   //Height of tallest menu item
    short       ItemInd;     //Item number of item being checked.
    short       ItemPos;     //Dist from menu top to PopUpItem in pixels
    short       MenuTop;     //Coord of top of menu rect, even if off screen
    Rect        ScrnRect;    //Rectangle of screen minus menu bar and margins
    short       HeightAdj;   //New coord of menu top/bottom if small menu
 
    Height = Width = MaxHeight = 0;
    ItemInd = 1;
    ItemPos = 0;
    HLock ((Handle) TheMenu);
    ItemString = (Str255 *) (**TheMenu).menuData;
    ItemString = (Str255 *) ((Byte *) ItemString + strSize (*ItemString));
    while ((*ItemString) [0] != (char) 0) //#A#
        {
        ItemInfo = (ItemInfoPtr) ((Byte *) *ItemString + strSize (*ItemString));
        TestHeight = CalcItemHeight (*ItemString, ItemInfo);
        TestWidth = CalcItemWidth (*ItemString, ItemInfo);
        if (ItemInd == PopUpItem)
            ItemPos = Height;
        if (TestWidth > Width)
            Width = TestWidth;
        if (TestHeight > MaxHeight)
            MaxHeight = TestHeight;
        Height += TestHeight;
        ItemInd += 1;
        ItemString = (Str255 *) (ItemInfo + 1);
        }
    HUnlock ((Handle) TheMenu);
    MenuTop = TopPos - ItemPos;
    MenuRect->left = LeftPos;
    MenuRect->top = MenuTop;
    MenuRect->right = MenuRect->left + Width;
    MenuRect->bottom = MenuRect->top + Height;
    FindScreen (MenuRect, &ScrnRect);
    InsetRect (&ScrnRect, scrnMargin, scrnMargin);
    if (MenuRect->top < ScrnRect.top)
        {
        MenuRect->top = ScrnRect.top;
        if (MenuRect->bottom - MenuRect->top - (short) (2 * scrlIconHeight) <
                MaxHeight) //#B#
            {
            if (MaxHeight + (short) (2 * scrlIconHeight) >= Height) //#C#
                HeightAdj = MenuRect->top + Height;
            else
                HeightAdj = MenuRect->top + MaxHeight + (short) (2 *
                        scrlIconHeight);
            MenuTop += HeightAdj - MenuRect->bottom;
            MenuRect->bottom = HeightAdj;
            }
        }
    if (MenuRect->right > ScrnRect.right)
        OffsetRect (MenuRect, ScrnRect.right - MenuRect->right, 0);
    if (MenuRect->left < ScrnRect.left)
        OffsetRect (MenuRect, ScrnRect.left - MenuRect->left, 0);
    if (MenuRect->bottom > ScrnRect.bottom)
        {
        MenuRect->bottom = ScrnRect.bottom;
        if (MenuRect->bottom - MenuRect->top - (short) (2 * scrlIconHeight) <
                MaxHeight) //#B#
            {
            if (MaxHeight + (short) (2 * scrlIconHeight) >= Height) //#C#
                HeightAdj = MenuRect->bottom - Height;
            else
                HeightAdj = MenuRect->bottom - MaxHeight - (short) (2 *
                        scrlIconHeight);
            MenuTop -= MenuRect->top - HeightAdj;
            MenuRect->top = HeightAdj;
            }
        }
    return MenuTop;
    }
 
 
/******************************************************************************\
* Private: FindScreen - Find rectangle of the screen containing most of the menu
*
* Color QuickDraw machines can have more than one screen, and pop-up menus
* should appear on the screen that contains the most of the menuÕs area.  This
* function returns the rectangle of the screen (in global coordinates) that
* contains most of the menu whose rectangle (also in global coordinates) is
* passed in the menuRect parameter.  The screenÕs rectangle is returned in the
* retScreenRect parameter.  If the screen that contains the most of the menu is
* the main screen (i.e. the screen with the menu bar), then the height of the
* menu bar is added to the top of retScreenRect, effectively removing the menu
* bar from the rectangle.  When Concordia is used on a machine without Color
* QuickDraw, then the rectangle of the main screen is always returned.  Non-
* Color QuickDraw machines with more than one screen (available from third-party
* manufacturers) patch QuickDraw to support their screens, and itÕs impossible
* to determine the rectangles of the auxiliary screens without using
* manufacturer-specific techniques.  Because of this, Concordia does not take
* advantage of auxiliary screens on non-Color QuickDraw machines.
*
* The rectangles of all attached screens are determined through the GDevice list
* thatÕs maintained by the system.  ThereÕs one GDevice in the list for every
* screen attached to the system.  GDevice routines only exist on Color QuickDraw
* machines, so the first thing that FindScreen does is to determine whether itÕs
* running on a machine that has Color QuickDraw or not.  If itÕs not, then the
* screen rectangle is taken from the screenBits.bounds QuickDraw global.  If
* Color QuickDraw is available, then the GDevice list is used to determine the
* appropriate screen.
*
* The first GDevice in the GDevice list is retrieved from the GetDeviceList
* function.  The next GDevice in the list is retrieved from the GetNextDevice
* function.  Through these two functions, every GDevice in the GDevice list can
* be retrieved.
*
* For every GDevice in the list, the area of intersection between the menuÕs
* rectangle and the screenÕs rectangle (found in the gdRect field of the GDevice
* structure) is calculated.  If the area of intersection is the greatest found
* so far, then it and its GDevice is saved, and the rectangle of that screen is
* placed into retScreenRect.
*
* After every GDevice in the GDevice list has been checked, the GDevice for the
* screen containing most of the menuÕs rectangle is checked to see if itÕ the
* GDevice for the main screen.  This is done by comparing the GDevice handle
* against the handle for the main screenÕs GDevice returned by GetMainDevice.
* If the GDevice is in fact the main screenÕs GDevice, then the height of the
* menu bar is removed from retScreenRect.
\******************************************************************************/
 
static void
FindScreen (menuRect, retScreenRect)
    Rect *menuRect;      /* Menu rectangle before processing for screen */
    Rect *retScreenRect; /* Returns rectangle of screen appropriate for menu */
    {
    GDHandle  aGDevice;   /* Handle to each screenÕs GDevice */
    GDHandle  maxGDevice; /* Handle to GDevice containing most of menu */
    long      area;       /* Menu rect/screen rect intersection area */
    long      maxArea;    /* Max menu rect/screen rect intersect area found */
    Rect      commonRect; /* Intersection between menu rect and screen rect */
    SysEnvRec environs;   /* Current running environment */
 
    /* Determine whether Color QuickDraw is implemented or not */
    (void) SysEnvirons (curSysEnvVers, /*<*/&environs);
 
    /* Use GDevices if Color QuickDraw is implemented, else use screenBits */
    if (environs.hasColorQD)
        {
        /* Assume max intersection area is 0 and get first GDevice in list */
        maxArea = 0L;
        maxGDevice = nil;
        aGDevice = GetDeviceList ();
 
        /* Loop through all screen GDevices */
        while (aGDevice != nil)
            {
            /* Calc area of intersection between menu rect and screen rect */
            SectRect (menuRect, &(**aGDevice).gdRect, /*<*/&commonRect);
            area = (long) (commonRect.bottom - commonRect.top) * (commonRect.
                    right - commonRect.left);
 
            /* If max area found so far, get screenÕs rect and save area */
            if (area > maxArea)
                {
                *retScreenRect = (**aGDevice).gdRect;
                maxGDevice = aGDevice;
                maxArea = area;
                }
 
            /* Go to the next GDevice in the list */
            aGDevice = GetNextDevice (aGDevice);
            }
 
        /* If GDevice with most of menu is main screen, remove MBar height */
        if (maxGDevice == GetMainDevice ())
            retScreenRect->top += GetMBarHeight ();
        }
    else
        {
        *retScreenRect = qd.screenBits.bounds;
        retScreenRect->top += GetMBarHeight ();
        }
    }