home *** CD-ROM | disk | FTP | other *** search
/ Celestin Apprentice 4 / Apprentice-Release4.iso / Source Code / C / Code Resources / PopupCDEF 1.3 / Projects / CDEF / Source / PopupLib.c < prev    next >
Encoding:
Text File  |  1995-12-04  |  93.9 KB  |  3,063 lines  |  [TEXT/CWIE]

  1. /* See the file Distribution for distribution terms.
  2.     (c) Copyright 1994 Ari Halberstadt */
  3.  
  4. /*    Functions implementing a popup menu. To create a popup call
  5.     PopupBegin and then PopupCurrentSet to set the initial item.
  6.     Use PopupCurrent to find out which item was selected. There
  7.     are quite a few other functions for controling how the popup
  8.     is displayed: whether the popup is drawn, whether the popup
  9.     is visible, enabling and disabling the popup, whether to
  10.     use the window font to display the popup, setting the text
  11.     style for the title, and more. The defaults are set by
  12.     PopupBegin to be those recommended by Apple in IM-VI. This
  13.     library is the basis for a popup menu 'CDEF'.
  14.     
  15.     You can use the popup CDEF to create a popup menu. The control's
  16.     contrlData field will contain a handle to the popup. If you need to
  17.     directly modify the popup, you can call the popup library routines
  18.     on the handle. However, first call PopupVersion and make sure it
  19.     is equal to kPopupVersion. If it isn't, then don't use the popup
  20.     library to modify the control. In practice, it is better to use
  21.     Control Manager routines to modify the popup.
  22.     
  23.     If you implement keyboard equivalents of menu commands, then you can
  24.     use PopupHilite to hilite the title of the menu while executing the
  25.     menu command.
  26.     
  27.     950828 aih
  28.     - fixed test from PopupMenuSelect -- checks HiWord, not LoWord
  29.     - PopupSelect returns a different part code if no selection is made
  30.     - MenuDisable is set to zero before PopupMenuSelect is called (so can
  31.     test for user not selecting any menu item)
  32.     - removed toggle attribute
  33.     - tests for grayishTextOr using gestalt
  34.     - uses onscreen port's (possibly non-white) bkPixPat when drawing offscreen,
  35.     yet allows to override using a menu or control color table, and inversion
  36.     of title works reasonably well
  37.     - popup can be beveled
  38.  
  39.     950824 aih
  40.     - title's height is calculated using the style attributes in which it will
  41.     be drawn
  42.  
  43.     950716 aih
  44.     - added glue for mdef patch for compiling as native ppc
  45.  
  46.     950707 aih
  47.     - font script for a menu item is set even if use window font is true
  48.     - found bug in type-in popup menu size calculation, if menu was not
  49.     empty and type-in attribute was true, triangle's size would be incorrect
  50.  
  51.     950629 aih
  52.     - restores menuProc field before calling original menuProc
  53.  
  54.     950621 aih
  55.     - clip region is intersected with port's clip region, so popup
  56.     CDEF doesn't draw outside of clip region
  57.     - added routine descriptor for device loop callback, so can
  58.     compile for PPC
  59.     - added toggle attribute
  60.  
  61.     941126 aih
  62.     - Supports control color tables.
  63.     - Popup title colors are swapped when hiliting the
  64.     title. This is how the system MDEF and other CDEFs
  65.     behave. The title is simply inverted when drawing in
  66.     black and white.
  67.     - Removed support for old headers (pre-Universal Headers).
  68.     
  69.     940908 aih
  70.     - Fixed disabling of items > 15.
  71.     
  72.     940716 aih
  73.     - Fixed almost all color problems.
  74.     - Improved right justification of menus, depending on system
  75.     justification value.
  76.     
  77.     940712 aih
  78.     - Adapted for color QuickDraw and offscreen graphics worlds.
  79.     - Added support for color menu entries.
  80.     
  81.     940706 aih
  82.     - Adapted for universal headers
  83.     
  84.     940315 aih
  85.     - The current selection will display items with small icons properly.
  86.     Formerly, this would display a large icon for items that had small or
  87.     reduced icons, and wouldn't display the large icon once a different icon
  88.     had been selected. Reduced icons still aren't displayed in the current
  89.     selection, and I'm not sure why not, since I copy the command key character
  90.     for reduced icons (0x1D) to the menu item used to draw the current
  91.     selection.
  92.     - When the useWFont variation code is used, the menu will be drawn in
  93.     the font and size of the popup menu's port. Formerly, using the useWFont
  94.     variation code produced incorrect results, but I fixed this by setting the
  95.     system font and size low-memory globals instead of trying to change the
  96.     font and size of the Window Manager's port.
  97.     - The currently selected item is drawn in gray if it is disabled. Formerly,
  98.     the item was only drawn in gray if it was disabled and if the menu item
  99.     was drawn using the MDEF.
  100.     - Updated "To Do" list.
  101.     - Clicking in the popup's title will also pull down the menu. This
  102.     is consistent with the operation of the system 7 popup CDEF.
  103.     - Added support for the popupFixedWidth variation code.
  104.     
  105.     931228 aih
  106.     - Continued cleaning up code, added some more comments.
  107.     - Uses menu definition function to draw the current selection. This
  108.     eliminated a couple of rectangles from the popup structure and
  109.     means that the current selection will always be drawn correctly,
  110.     even if it includes icons and command keys. A special one item menu
  111.     is used to hold a copy of the currently selected menu item. This special
  112.     menu is then used to calculate the height of the item and to draw the
  113.     item.
  114.     
  115.     931226 aih
  116.     - Major overhaul. Rewrote rectangle calculation code, since it wasn't
  117.     working right. It's still not as simple as I'd like, but it's better
  118.     than before. Also added attributes to make more compatible with Apple's
  119.     popup CDEF (such as using the window font to display the menu).
  120.     
  121.     931224 aih
  122.     - Converted to use handles.
  123.     - Removed dependence on all other libraries, so that this library
  124.     is completely self contained; the few external functions that were
  125.     necessary (e.g., strcpy, TruncatePString) were copied or coded directly into
  126.     this file. This will make this library more suitable for use as a
  127.     CDEF since the linker won't pull in a lot of extra code from other
  128.     libraries.
  129.     
  130.     931223 aih
  131.     - updated for current version of libraries
  132.     - added port parameter to PopupBegin
  133.     
  134.     911111 aih
  135.     - Removed useless "PopupObj..." stuff
  136.     
  137.     910301-05 aih
  138.     - Update events are handled
  139.     - Added comment describing this file
  140.     - The popup's title is allocated as a handle in the heap
  141.     - The popup is allocated as a handle in the heap
  142.     - Attribute values are only updated if nescessary
  143.     - The function PopupDraw first draws the popup to an offscreen bitmap
  144.     and then copies it to the popup's port. This eliminates flicker when
  145.     the popup is used as a CDEF, since the Control Manager sends a draw
  146.     message to a control whenever the control is clicked, even if no
  147.     drawing is actually needed.
  148.     
  149.     910105 aih
  150.     - Inserted this standard header in all files
  151.  
  152.     901215 Ari Halberstadt (aih)
  153.     - Created this file */
  154.  
  155. #include <Fonts.h>
  156. #include <GestaltEqu.h>
  157. #include <Icons.h>
  158. #include <LowMem.h>
  159. #include <Memory.h>
  160. #include <OSUtils.h>
  161. #include <Palettes.h>
  162. #include <QuickDraw.h>
  163. #include <Resources.h>
  164. #include <Script.h>
  165. #include <TextEdit.h>
  166. #include <TextUtils.h>
  167. #include <Traps.h>
  168. #include <ToolUtils.h>
  169. #include <Windows.h>
  170. #include "PopupLib.h"
  171.  
  172. /*-------------------------------------------------------------------------*/
  173. /* conditional compilation */
  174. /*-------------------------------------------------------------------------*/
  175.  
  176. /*    Define as 1 to bevel the popup when the port's depth is at
  177.     least kBevelDepth. */
  178. #define DRAW_BEVEL                    (0)
  179.  
  180. /*    Define as 1 to draw a shaded triangle when the bevel attribute is
  181.     enabled. */
  182. #define DRAW_SHADED_TRIANGLE        (1)
  183.     
  184. /* To reduce flicker the popup is normally first drawn to an off-screen
  185.     bitmap, then copied to the screen. To make debugging easier disable
  186.     offscreen drawing. Then you can step through each drawing routine to
  187.     ensure that it is drawing correctly. */
  188. #define DRAW_OFFSCREEN                (1)
  189.  
  190. /* Define DRAW_POPUP as 1 to enable drawing of the various parts of the
  191.     the menu. You will normally want this to be enabled, but, while
  192.     debugging, you may want to see only the outlines of the parts of
  193.     the menu. To do so, you can set DRAW_POPUP to 0 and DRAW_RECTANGLES
  194.     to 1. */
  195. #define DRAW_POPUP                    (1)
  196.  
  197. /* Define DRAW_RECTANGLES as 1 to enable framing of the various rectangles
  198.     of the menu. This is useful when debugging since it clearly displays the
  199.     rectangles, which otherwise can be tricky to calculate. */
  200. #define DRAW_RECTANGLES                (0)
  201.  
  202. /*    To test right-justified menus without using a right-justified
  203.     script system, define FLUSH_RIGHT as 1. */
  204. #define FLUSH_RIGHT                    (0)
  205.  
  206. /*    To copy the port's background pattern to the offscreen graphics world,
  207.     defined COPY_BACKPAT as 1. This may not work correctly, so it's disabled
  208.     for now. You might want to enable this if the popup is used in a port
  209.     with a non-white background pattern. */
  210. #define COPY_BACKPAT                    (0)
  211.  
  212. /* Another useful debugging trick is to define the following as something
  213.     greater than 1. This helps spot and fix off-by-one errors caused by
  214.     not taking into account the size of the frame and drop shadow. 
  215.     A value of at least 1/4" (18 pixels) provides good visual feedback
  216.     and allows using a ruler to get rough measurements of areas. */
  217. #define kFrameSize                    (1)    /* width of frame around popup */
  218. #define kShadowSize                    (1)    /* width of shadow around popup */
  219. #define kBevelSize                    (1)    /* width of bevel for 3D effect */
  220.  
  221. /*-------------------------------------------------------------------------*/
  222. /* constants */
  223. /*-------------------------------------------------------------------------*/
  224.  
  225. #define kEllipses                        '…'    /* character appended to long strings */
  226. #define kShadowOffset                (3)    /* amount to offset shadow from frame */
  227. #define kIconMarginV                    (2)    /* vertical margin around icon */
  228. #define kTriangleMargin                (5)    /* margin around triangle */
  229. #define kTriangleHeight                (5)    /* height for type-in popups */
  230. #define kTitleMargin                    (5)    /* margin around title */
  231. #define kTitleMarginBottom            (1)    /* margin below title */
  232.  
  233. #define kBevelDepth                    (8)    /* minimum port depth for beveling */
  234. #define kDisableDepth                (2)    /* minimum depth for using color (rather
  235.                                                         than a gray pattern) to disable items */
  236.  
  237. /* flags returned for part colors */
  238. typedef enum {
  239.     kPartColorNone                        = 0,
  240.     kPartColorBack                        = 1<<0,
  241.     kPartColorFore                        = 1<<1,
  242.     kPartColorBackAndFore            = kPartColorBack + kPartColorFore,
  243.     kPartColorLast
  244. } PartColorFlag;
  245.  
  246. /* special values for the command key field of a menu item */
  247. enum {
  248.     kCmdHier = hMenuCmd,
  249.     kCmdScript,
  250.     kCmdIconReduced,
  251.     kCmdIconSmall,
  252.     kCmdReserved
  253. };
  254.  
  255. /*-------------------------------------------------------------------------*/
  256. /* types */
  257. /*-------------------------------------------------------------------------*/
  258.  
  259. /*    Private part codes. */
  260. typedef enum {
  261.     kPopupPartFirst = 0,        /* first part */
  262.     kPopupPartBlackAndWhite = 0,/* always black and white */
  263.     kPopupPartTitle,            /* title rectangle */
  264.     kPopupPartItem,            /* current item rectangle */
  265.     kPopupPartIcon,             /* current item icon */
  266.     kPopupPartTriangle,        /* triangle */
  267.     kPopupPartFrame,            /* frame around current item */
  268.     kPopupPartShadow,            /* drop shadow */
  269.     kPopupPartDefault,        /* default color */
  270.     kPopupPartLast
  271. } PopupPartType;
  272.  
  273. /* for saving/restoring a port's text state */
  274. typedef struct {
  275.     short    font;
  276.     Style    face;
  277.     short    mode;
  278.     short    size;
  279. } TextState;
  280.  
  281. /* for saving/restoring a port's color state */
  282. typedef struct {
  283.     RGBColor fore;
  284.     RGBColor back;
  285. } ColorState;
  286.  
  287. /* structure for saving the drawing environment when drawing the current
  288.     selection */
  289. typedef struct {
  290.     TextState textState;        /* saved text style */
  291.     ColorState colorState;    /* saved color state */
  292. } PopupDrawSelectionStructure;
  293.  
  294. /* Drawing environment data that must be remembered before any drawing
  295.     of the popup menu is begun and restored after all drawing has finished. */
  296. typedef struct {
  297.     GrafPtr        port;                /* graf port on entry to popup cdef */
  298.     PenState        pen;                /* saved pen state for popup's port */
  299.     TextState    text;                /* saved text state for popup's port */
  300.     ColorState    color;            /* saved color state for popup's port */
  301.     RgnHandle    clip;                /* saved clip region for popup's port */
  302.     short            sysfont;            /* saved system font */
  303.     short            syssize;            /* saved system font size */
  304.     short            wmgrfont;        /* saved window manager port font */
  305.     short            wmgrsize;        /* saved window manager port font size */
  306.     Boolean        inserted;        /* true if inserted menu into menu list */
  307. } PopupDrawStateType;
  308.  
  309. /* used for translating a part code into a color part code */
  310. typedef struct {
  311.     short fore;
  312.     short back;
  313.     short disabled;
  314. } PopupPartTableType;
  315.  
  316. /*-------------------------------------------------------------------------*/
  317. /* assertions */
  318. /*-------------------------------------------------------------------------*/
  319.  
  320. #if ASSERTS_DONT_WORK_TOO_WELL && DEBUG_CDEF
  321.  
  322.     #define require(x)            ((void) ((x) || PopupAssertFailed()))
  323.     #define ensure(x)                ((void) ((x) || PopupAssertFailed()))
  324.     #define check(x)                ((void) ((x) || PopupAssertFailed()))
  325.  
  326.     static int PopupAssertFailed(void)
  327.     {
  328.         DebugStr((StringPtr) "\pPopupAssertFailed");
  329.         return(0);
  330.     }
  331.  
  332.     static Boolean RectValid(const Rect *r)
  333.     {
  334.         if (r->top > r->bottom) return(false);
  335.         if (r->left > r->right) return(false);
  336.         return(true);
  337.     }
  338.     
  339.     static short RectWidth(const Rect *r)
  340.     {
  341.         return(r->right - r->left);
  342.     }
  343.     
  344.     static short RectHeight(const Rect *r)
  345.     {
  346.         return(r->bottom - r->top);
  347.     }
  348.     
  349.     static Boolean RectWithin(const Rect *r1, const Rect *r2)
  350.     {
  351.         return(    r1->left >= r2->left && r1->right <= r2->right &&
  352.                     r1->top >= r2->top && r1->bottom <= r2->bottom);
  353.     }
  354.     
  355. #else /* DEBUG_CDEF */
  356.  
  357.     #define require(x)            ((void) 0)
  358.     #define ensure(x)                ((void) 0)
  359.     #define check(x)                ((void) 0)
  360.     
  361. #endif /* DEBUG_CDEF */
  362.  
  363. /*-------------------------------------------------------------------------*/
  364. /* math utilities */
  365. /*-------------------------------------------------------------------------*/
  366.  
  367. /* return minimum of two signed long integers */
  368. static long lmin(long a, long b)
  369. {
  370.     return(a < b ? a : b);
  371. }
  372.  
  373. /* return maximum of two signed long integers */
  374. static long lmax(long a, long b)
  375. {
  376.     return(a > b ? a : b);
  377. }
  378.  
  379. /*-------------------------------------------------------------------------*/
  380. /* trap utilities */
  381. /*-------------------------------------------------------------------------*/
  382.  
  383. /* Functions for determining whether a trap is available. Based on
  384.    functions given in IM VI. */
  385.  
  386. /* TrapToolboxCount returns the number of ToolBox traps. */
  387. static short TrapToolboxCount(void)
  388. {
  389.    short result = 0;
  390.    
  391.    if (NGetTrapAddress(_InitGraf, ToolTrap) == NGetTrapAddress(0xAA6E, ToolTrap))
  392.       result = 0x0200;
  393.    else
  394.       result = 0x0400;
  395.    return(result);
  396. }
  397.  
  398. /* TrapType returns the type of the trap (ToolBox or Operating System). */
  399. static TrapType GetTrapType(short trap)
  400. {
  401.    return((trap & 0x0800) > 0 ? ToolTrap : OSTrap);
  402. }
  403.  
  404. /* TrapAvailable returns true if the trap is implemented. */
  405. static Boolean TrapAvailable(short trap)
  406. {
  407.    TrapType type;
  408.    
  409.    type = GetTrapType(trap);
  410.    if (type == ToolTrap) {
  411.       trap &= 0x07FF;
  412.       if (trap >= TrapToolboxCount())
  413.          trap = _Unimplemented;
  414.    }
  415.    return(NGetTrapAddress(trap, type) != NGetTrapAddress(_Unimplemented, ToolTrap));
  416. }
  417.  
  418. /*-------------------------------------------------------------------------*/
  419. /* gestalt utilities */
  420. /*-------------------------------------------------------------------------*/
  421.  
  422. /* return the version of the Macintosh system software */
  423. static short MacVersion(void)
  424. {
  425.     OSErr err;
  426.     long response;
  427.     
  428.     err = Gestalt(gestaltSystemVersion, &response);
  429.     return(! err ? LoWord(response) : 0);
  430. }
  431.     
  432. /* return the version of QuickDraw */
  433. static short MacVersionQD(void)
  434. {
  435.     OSErr err;
  436.     long response;
  437.     
  438.     err = Gestalt(gestaltQuickdrawVersion, &response);
  439.     return(! err ? LoWord(response) : 0);
  440. }
  441.  
  442. /* true if the Macintosh supports color QuickDraw */
  443. static Boolean MacHasColorQD(void)
  444. {
  445.     return(MacVersionQD() >= gestalt8BitQD);
  446. }
  447.  
  448. /* true if color QuickDraw supports grayishTextOr */
  449. static Boolean MacHasGrayishTextOr(void)
  450. {
  451.     OSErr err;
  452.     long response;
  453.     
  454.     err = Gestalt(gestaltQuickdrawFeatures, &response);
  455.     return(! err ? (response & gestaltHasGrayishTextOr) != 0 : false);
  456. }
  457.  
  458. /* true if the Macintosh supports offscreen graphics worlds */
  459. static Boolean MacHasGWorlds(void)
  460. {
  461.     /*    GWorlds are supported if (MacVersionQD() >= gestalt32BitQD) but
  462.         they behave sufficiently differently in systems before 7.0 that
  463.         it's difficult to get them to work with both pre-7.0 and post-7.0
  464.         systems, so we just support GWorlds in post-7.0 systems. */
  465.     return(MacVersion() >= 0x0700);
  466. }
  467.  
  468. /* true if the Macintosh has the icon utilities */
  469. static Boolean MacHasIconUtilities(void)
  470. {
  471.     /* Develop, Issue 19, page 105 */
  472.     return(TrapAvailable(_IconDispatch));
  473. }
  474.  
  475. /*-------------------------------------------------------------------------*/
  476. /* glue utilities for patching mdef */
  477. /*-------------------------------------------------------------------------*/
  478.  
  479. /*    Initializes a glue structure for 68k or PowerPC. You must call this
  480.     function before using the 'proc' function, at least once every time the
  481.     CDEF is called. This will account for the possibility that the routine
  482.     to be called has been moved in memory, or that the popup's data were
  483.     last used by a version of the CDEF running in a different ISA. In
  484.     other words, you must call this function whenever the address of the
  485.     function in the 'proc' parameter may have changed and whenever the
  486.     ISA of the code being executed may have changed.
  487.     This function won't move or purge memory. */
  488. void PopupGlueInit(PopupGlueUnion *glue, ProcPtr proc, ProcInfoType info)
  489. {
  490.     #if GENERATINGCFM
  491.         /* initialize routine descriptor */
  492.         RoutineDescriptor descriptor = BUILD_ROUTINE_DESCRIPTOR(0, 0);
  493.         glue->ppc.descriptor = descriptor;
  494.         glue->ppc.descriptor.routineRecords[0].procInfo = info;
  495.         glue->ppc.descriptor.routineRecords[0].procDescriptor = proc;
  496.     #else
  497.         /* initialize 68K glue code */
  498.         #pragma unused (info)
  499.         glue->m68k.lea = 0x41fa0006;        /* lea pc+8, a0 */
  500.         glue->m68k.move = 0x2050;            /* movea.l (a0) a0 */
  501.         glue->m68k.jmp = 0x4ed0;            /* jmp (a0) */
  502.         glue->m68k.proc = proc;                /* destination address */
  503.     #endif
  504. }
  505.  
  506. /*-------------------------------------------------------------------------*/
  507. /* graphics utilities */
  508. /*-------------------------------------------------------------------------*/
  509.  
  510. /* true if the port is a color port */
  511. static Boolean PortIsColor(GrafPtr port)
  512. {
  513.     return((((CGrafPtr) port)->portVersion & 0xC000) == 0xC000);
  514. }
  515.  
  516. /* return the port's background pixel pattern */
  517. static PixPatHandle GetPortBackPixPat(GrafPtr port)
  518. {
  519.     return(PortIsColor(port) ? ((CGrafPtr) port)->bkPixPat : NULL);
  520. }
  521.  
  522. /* return the pixel depth of the pixel map */
  523. static short GetPixMapDepth(PixMapHandle pixmap)
  524. {
  525.     return((**pixmap).pixelSize);
  526. }
  527.  
  528. /* return the pixel depth of the port */
  529. static short GetPortDepth(GrafPtr port)
  530. {
  531.     short depth;
  532.     
  533.     depth = 1;
  534.     if (PortIsColor(port))
  535.         depth = GetPixMapDepth(((CGrafPtr) port)->portPixMap);
  536.     return(depth);
  537. }
  538.  
  539. /* return the pixel depth of the graphics device */
  540. static short GetDeviceDepth(GDHandle device)
  541. {
  542.     return(GetPixMapDepth((**device).gdPMap));
  543. }
  544.  
  545. /* Return a handle to the video device with the smallest pixel depth
  546.     that intersects the specified rectangle (given in global coordinates).
  547.     If no video device intersects the specified rectangle, then NULL is
  548.     returned. This is analogous to GetMaxDevice. */
  549. static GDHandle GetMinDevice(const Rect *globalRect)
  550. {
  551.     GDHandle mindevice;
  552.     GDHandle curdevice;
  553.     Rect deviceRect;
  554.  
  555.     mindevice = NULL;
  556.     curdevice = GetDeviceList();
  557.     while (curdevice) {
  558.         if (TestDeviceAttribute(curdevice, screenDevice) &&
  559.              TestDeviceAttribute(curdevice, screenActive))
  560.         {
  561.             deviceRect = (**curdevice).gdRect;
  562.             if (SectRect(&deviceRect, globalRect, &deviceRect)) {
  563.                 if (! mindevice ||
  564.                      GetDeviceDepth(curdevice) <
  565.                      GetDeviceDepth(mindevice))
  566.                 {
  567.                     mindevice = curdevice;
  568.                 }
  569.             }
  570.         }
  571.         curdevice = GetNextDevice(curdevice);
  572.     }
  573.     return(mindevice);
  574. }
  575.  
  576. /* return the pixel map of the graphics world */
  577. static PixMapHandle GetPixMap(GWorldPtr gworld)
  578. {
  579.     return(MacVersion() >= 0x0700 ? GetGWorldPixMap(gworld) : gworld->portPixMap);
  580. }
  581.  
  582. /* get the text attributes of the current port */
  583. static void GetTextState(TextState *text)
  584. {
  585.     GrafPtr port;
  586.     
  587.     GetPort(&port);
  588.     text->font = port->txFont;
  589.     text->face = port->txFace;
  590.     text->mode = port->txMode;
  591.     text->size = port->txSize;
  592. }
  593.  
  594. /* set the text attributes of the current port */
  595. static void SetTextState(const TextState *text)
  596. {
  597.     TextFont(text->font);
  598.     TextFace(text->face);
  599.     TextMode(text->mode);
  600.     TextSize(text->size);
  601. }
  602.  
  603. /* set the text attributes of the current port to their defaults */
  604. static void TextNormal(void)
  605. {
  606.     TextFont(0);
  607.     TextFace(0);
  608.     TextSize(0);
  609.     TextMode(srcOr);
  610. }
  611.  
  612. /* get the color attributes of the current port */
  613. static void GetColorState(ColorState *color)
  614. {
  615.     if (MacHasColorQD()) {
  616.         GetForeColor(&color->fore);
  617.         GetBackColor(&color->back);
  618.     }
  619. }
  620.  
  621. /* set the color attributes of the current port */
  622. static void SetColorState(const ColorState *color)
  623. {
  624.     if (MacHasColorQD()) {
  625.         RGBForeColor(&color->fore);
  626.         RGBBackColor(&color->back);
  627.     }
  628. }
  629.  
  630. /* true if the two colors are identical */
  631. static Boolean RGBColorEqual(const RGBColor *rgb1, const RGBColor *rgb2)
  632. {
  633.     return(    rgb1->red == rgb2->red &&
  634.                 rgb1->green == rgb2->green &&
  635.                 rgb1->blue == rgb2->blue);
  636. }
  637.  
  638. /*-------------------------------------------------------------------------*/
  639. /* rectangle utilities */
  640. /*-------------------------------------------------------------------------*/
  641.  
  642. /*    Convert the rectangle to global coordinates. */
  643. static void RectLocalToGlobal(Rect *r)
  644. {
  645.     Point origin = { 0, 0 };
  646.     
  647.     LocalToGlobal(&origin);
  648.     OffsetRect(r, origin.h, origin.v);
  649. }
  650.  
  651. /* Flip the rectangle 'flip' horizontally (mirror image) relative to
  652.     the rectangle 'within'. Used for right-aligned popups. */    
  653. static void RectFlip(Rect *flip, const Rect *within)
  654. {
  655.     short offset;
  656.     
  657.     require(RectValid(flip));
  658.     require(RectValid(within));
  659.     require(within->left <= flip->left && flip->right <= within->right);
  660.     offset = (within->right - flip->right) - (flip->left - within->left);
  661.     flip->left += offset;
  662.     flip->right += offset;
  663. }
  664.  
  665. /* Paint over the rectangle with the gray pattern. Used for disabling
  666.     text in black and white ports. */
  667. static void RectDisable(const Rect *r)
  668. {
  669.     PenState pen;
  670.     Pattern pat;
  671.  
  672.     require(RectValid(r));
  673.     GetPenState(&pen);
  674.     PenMode(patBic);
  675.     GetIndPattern(&pat, sysPatListID, 4);
  676.     PenPat(&pat);
  677.     PaintRect(r);
  678.     SetPenState(&pen);
  679. }
  680.  
  681. /*-------------------------------------------------------------------------*/
  682. /* erasing rectangles and regions */
  683. /*-------------------------------------------------------------------------*/
  684.  
  685. /* erase the rectangle using an RGB pattern (override bkPat) */
  686. static void RectEraseRGBPat(const Rect *r)
  687. {
  688.     PixPatHandle backPat;
  689.     RGBColor backColor;
  690.     GrafPtr port;
  691.  
  692.     GetPort(&port);
  693.     if (GetPortDepth(port) > 1) {
  694.         GetBackColor(&backColor);
  695.         backPat = NewPixPat();
  696.         MakeRGBPat(backPat, &backColor);
  697.         FillCRect(r, backPat);
  698.         DisposePixPat(backPat);
  699.     }
  700.     else
  701.         EraseRect(r);
  702. }
  703.  
  704. /* erase the region an RGB pattern (override bkPat) */
  705. static void RgnEraseRGBPat(RgnHandle r)
  706. {
  707.     PixPatHandle backPat;
  708.     RGBColor backColor;
  709.     GrafPtr port;
  710.  
  711.     GetPort(&port);
  712.     if (GetPortDepth(port) > 1) {
  713.         GetBackColor(&backColor);
  714.         backPat = NewPixPat();
  715.         MakeRGBPat(backPat, &backColor);
  716.         FillCRgn(r, backPat);
  717.         DisposePixPat(backPat);
  718.     }
  719.     else
  720.         EraseRgn(r);
  721. }
  722.  
  723. /* erase the rectangle */
  724. static void RectErase(const Rect *r, Boolean useBackColor)
  725. {
  726.     if (useBackColor)
  727.         RectEraseRGBPat(r);
  728.     else
  729.         EraseRect(r);
  730. }
  731.  
  732. /* erase the region */
  733. static void RgnErase(RgnHandle r, Boolean useBackColor)
  734. {
  735.     if (useBackColor)
  736.         RgnEraseRGBPat(r);
  737.     else
  738.         EraseRgn(r);
  739. }
  740.  
  741. /*-------------------------------------------------------------------------*/
  742. /* icon utilities */
  743. /*-------------------------------------------------------------------------*/
  744.  
  745. /*    PlotSICN draws a small black and white icon from a resource of type
  746.     'SICN' that defines a 16 by 16 bit image, in the specified rectangle.
  747.     This is similar to the ToolBox routine PlotIcon. */
  748. static void PlotSICN(const Rect *bounds, Handle sicn)
  749. {
  750.     SignedByte state;
  751.     BitMap sicnBits;
  752.     GrafPtr port;
  753.     
  754.     state = HGetState(sicn);
  755.     MoveHHi(sicn);
  756.     HLock(sicn);
  757.     GetPort(&port);
  758.     sicnBits.rowBytes = 2;
  759.     sicnBits.baseAddr = *sicn;
  760.     sicnBits.bounds = *bounds;
  761.     CopyBits(&sicnBits, &port->portBits, bounds, bounds, srcCopy, NULL);
  762.     HSetState(sicn, state);
  763. }
  764.  
  765. /*-------------------------------------------------------------------------*/
  766. /* string utilities */
  767. /*-------------------------------------------------------------------------*/
  768.  
  769. /* Clip the string to a maximum width by truncating it and adding an
  770.     extra character and return final width of string. This is analogous
  771.     to TruncString, but it works with both systems 6 and 7. */
  772. static short TruncatePString(Str255 str, short maxwidth)
  773. {
  774.     const char extra = kEllipses;
  775.     short width;
  776.  
  777.     require(maxwidth >= 0);
  778.     if (MacVersion() >= 0x0700) {
  779.         (void) TruncString(maxwidth, str, truncEnd);
  780.         width = StringWidth(str);
  781.     }
  782.     else {
  783.         width = StringWidth(str);
  784.         if (width > maxwidth) {
  785.             if (maxwidth == 0) {
  786.                 /* optimization */
  787.                 width = 0;
  788.                 *str = 0;
  789.             }
  790.             else {
  791.                 /* truncate one character (or multi-byte character)
  792.                     at a time from the end of the string */
  793.                 while (*str > 0) {
  794.                     str[*str] = extra;
  795.                     width = StringWidth(str);
  796.                     if (width <= maxwidth)
  797.                         break;
  798.                     while (CharByte((Ptr) str, *str) > 0) {
  799.                         check(*str > 1);
  800.                         --*str;
  801.                     }
  802.                     check(*str > 0);
  803.                     --*str;
  804.                 }
  805.             }
  806.         }
  807.     }
  808.     ensure(width <= maxwidth);
  809.     ensure(width == StringWidth(str));
  810.     return(width);
  811. }
  812.  
  813. /*-------------------------------------------------------------------------*/
  814. /* menu utilities */
  815. /*-------------------------------------------------------------------------*/
  816.  
  817. /* true if the menu item is enabled */
  818. static Boolean MenuItemEnabled(MenuHandle menu, short item)
  819. {
  820.     unsigned long flags;
  821.     
  822.     require(0 <= item && item <= CountMItems(menu));
  823.     flags = (**menu).enableFlags;
  824.     return((flags & 1) != 0 && (item > 31 || (flags & (1UL << item)) != 0));
  825. }
  826.  
  827. /* Get information about the icon for the specified menu item. */
  828. static void MenuItemIcon(MenuHandle menu, short item,
  829.     ResType *iconType, short *iconID, Point *iconSize)
  830. {
  831.     short itemCmd;
  832.     short itemIcon;
  833.     Rect iconRect;
  834.     Handle iconRsrc;
  835.     
  836.     *iconID = 0;
  837.     *iconType = 0;
  838.     iconSize->h = 0;
  839.     iconSize->v = 0;
  840.     if (item > 0) {
  841.         GetItemCmd(menu, item, &itemCmd);
  842.         GetItemIcon(menu, item, &itemIcon);
  843.         if (itemIcon && itemCmd != kCmdScript) {
  844.             *iconID = itemIcon + 256;
  845.             if (MacHasColorQD() && GetResource('cicn', *iconID)) {
  846.                 *iconType = 'cicn';
  847.                 iconRsrc = GetResource('cicn', *iconID);
  848.                 if (iconRsrc && *iconRsrc) {
  849.                     /* get icon's size from its pixmap */
  850.                     iconRect = (**(CIconHandle) iconRsrc).iconPMap.bounds;
  851.                     iconSize->h = iconRect.right - iconRect.left;
  852.                     iconSize->v = iconRect.bottom - iconRect.top;
  853.                 }
  854.             }
  855.             else if (itemCmd == kCmdIconSmall) {
  856.                 *iconType = 'SICN';
  857.                 iconSize->h = iconSize->v = 16;
  858.             }
  859.             else {
  860.                 *iconType = 'ICON';
  861.                 iconSize->h = iconSize->v = 32;
  862.             }
  863.             if (itemCmd == kCmdIconReduced) {
  864.                 /* reduce the size of the icon */
  865.                 iconSize->h /= 2;
  866.                 iconSize->v /= 2;
  867.             }
  868.         }
  869.     }
  870. }
  871.  
  872. /* If the menu item has a color associated with it, this sets the
  873.     'foreColor' and 'backColor' parameters to the menu item's colors
  874.     and returns true. Otherwise, the 'foreColor' and 'backColor'
  875.     parameters are not changed and the function returns false. */
  876. static Boolean MenuItemColor(MenuHandle menu, short item,
  877.     RGBColor *foreColor, RGBColor *backColor)
  878. {
  879.     MCEntryPtr mcentry;    /* pointer to menu item's color entry */
  880.     Boolean color;            /* true if the item has a color */
  881.     
  882.     color = false;
  883.     mcentry = GetMCEntry((**menu).menuID, item);
  884.     if (item) {
  885.         if (mcentry) {
  886.             /* use item's color entry */
  887.             if (foreColor) *foreColor = mcentry->mctRGB2;
  888.             if (backColor) *backColor = mcentry->mctRGB4;
  889.             color = true;
  890.         }
  891.         else {
  892.             /* use default color entry */
  893.             mcentry = GetMCEntry((**menu).menuID, 0);
  894.             if (mcentry) {
  895.                 if (foreColor) *foreColor = mcentry->mctRGB3;
  896.                 if (backColor) *backColor = mcentry->mctRGB4;
  897.                 color = true;
  898.             }
  899.         }
  900.     }
  901.     else if (mcentry) {
  902.         /* use title's color entry */
  903.         if (foreColor) *foreColor = mcentry->mctRGB1;
  904.         if (backColor) *backColor = mcentry->mctRGB4;
  905.         color = true;
  906.     }
  907.     return(color);
  908. }
  909.  
  910. /*    This is similar to MenuItemColor, but returns the colors from the
  911.     color table for the menu bar. */
  912. static Boolean MenuItemColorDefault(short item,
  913.     RGBColor *foreColor, RGBColor *backColor)
  914. {
  915.     MCEntryPtr mcentry;    /* pointer to menu item's color entry */
  916.     Boolean color;            /* true if the item has a color */
  917.     
  918.     color = false;
  919.     mcentry = GetMCEntry(0, 0);
  920.     if (mcentry) {
  921.         color = true;
  922.         if (item > 0) {
  923.             /* use item entry */
  924.             if (foreColor) *foreColor = mcentry->mctRGB3;
  925.             if (backColor) *backColor = mcentry->mctRGB2;
  926.         }
  927.         else {
  928.             /* use title entry */
  929.             if (foreColor) *foreColor = mcentry->mctRGB1;
  930.             if (backColor) *backColor = mcentry->mctRGB4;
  931.         }
  932.     }
  933.     return(color);
  934. }
  935.  
  936. /*-------------------------------------------------------------------------*/
  937. /* control utilities */
  938. /*-------------------------------------------------------------------------*/
  939.  
  940. /*    Return the control's color table, or NULL if it doesn't have
  941.     a color table. */
  942. static CCTabHandle ControlColorTable(ControlHandle ctl)
  943. {
  944.     AuxCtlHandle aux;
  945.     
  946.     return(GetAuxiliaryControlRecord(ctl, &aux) ? (**aux).acCTable : NULL);
  947. }
  948.  
  949. /* If the part has an entry in the control's color table, then this
  950.     sets the 'color' parameter to the color specified in the table
  951.     and returns true, otherwise the 'color' parameter is not changed
  952.     and the function returns false. */
  953. static Boolean ControlCTPartColor(CCTabHandle cct, short part,
  954.     RGBColor *color)
  955. {
  956.     Boolean found;
  957.     short i;
  958.     
  959.     found = false;
  960.     if (cct) {
  961.         for (i = 0; i <= (**cct).ctSize && ! found; i++) {
  962.             if ((**cct).ctTable[i].value == part) {
  963.                 if (color)
  964.                     *color = (**cct).ctTable[i].rgb;
  965.                 found = true;
  966.             }
  967.         }
  968.     }
  969.     return(found);
  970. }
  971.  
  972. /*-------------------------------------------------------------------------*/
  973. /* popup menu routines */
  974. /*-------------------------------------------------------------------------*/
  975.  
  976. /* Returns true if the popup handle is valid. */
  977. Boolean PopupValid(PopupHandle popup)
  978. {
  979.     if (! popup) return(false);
  980.     if (GetHandleSize((Handle) popup) < sizeof(PopupType)) return(false);
  981.     if ((**popup).version != kPopupVersion) return(false);
  982.     return(true);
  983. }
  984.  
  985. /*-------------------------------------------------------------------------*/
  986. /* port setup and restore */
  987. /*-------------------------------------------------------------------------*/
  988.  
  989. /* Inserts the menu into the hierarchical menu list if necessary.
  990.     This also sets the menu's color table if there is a 'mctb'
  991.     resource with the same ID as the menu. True is returned if the
  992.     menu was inserted into the menu table, in which case
  993.     PopupDeleteMenu should be called when finished using the menu. */
  994. static Boolean PopupInsertMenu(PopupHandle popup)
  995. {
  996.     Boolean inserted;
  997.     SignedByte state;
  998.     Handle mctb;
  999.     
  1000.     require(PopupValid(popup));
  1001.     inserted = false;
  1002.     if (! GetMenuHandle((**(**popup).menu).menuID)) {
  1003.         inserted = true;
  1004.         InsertMenu((**popup).menu, hierMenu);
  1005.         if (MacHasColorQD()) {
  1006.             mctb = GetResource('mctb', (**(**popup).menu).menuID);
  1007.             if (mctb && *mctb) {
  1008.                 state = HGetState(mctb);
  1009.                 MoveHHi(mctb);
  1010.                 HLock(mctb);
  1011.                 SetMCEntries(*(short *) *mctb, (MCTablePtr) (*mctb + sizeof(short)));
  1012.                 HSetState(mctb, state);
  1013.             }
  1014.         }
  1015.     }
  1016.     return(inserted);
  1017. }
  1018.  
  1019. /* Remove the menu from the menu list. */
  1020. static void PopupDeleteMenu(PopupHandle popup, Boolean inserted)
  1021. {
  1022.     require(PopupValid(popup));
  1023.     if (inserted)
  1024.         DeleteMenu((**(**popup).menu).menuID);
  1025. }
  1026.  
  1027. /* Return the alignment for the font in which the popup menu is drawn. */
  1028. static short PopupFontJust(PopupHandle popup)
  1029. {
  1030.     GrafPtr savePort;
  1031.     short just;
  1032.     
  1033.     #if FLUSH_RIGHT
  1034.         just = teFlushRight;
  1035.     #else /* FLUSH_RIGHT */
  1036.         if ((**popup).attr.windowFont) {
  1037.             GetPort(&savePort);
  1038.             SetPort((**popup).port);
  1039.             just = GetScriptVariable(FontScript(), smScriptJust);
  1040.             SetPort(savePort);
  1041.         }
  1042.         else
  1043.             just = GetSysDirection();
  1044.     #endif /* FLUSH_RIGHT */
  1045.     return(just);
  1046. }
  1047.  
  1048. /* Return the pixel depth of the port or device to which the popup is
  1049.     being drawn. */
  1050. static short PopupPixelDepth(PopupHandle popup)
  1051. {
  1052.     short depth;
  1053.     
  1054.     depth = GetPortDepth((**popup).port);
  1055.     if ((**popup).draw.gdepth)
  1056.         depth = lmin(depth, (**popup).draw.gdepth);
  1057.     return(depth);
  1058. }
  1059.  
  1060. /* Return true if the popup is drawn into a color port. */
  1061. static Boolean PopupPortIsColor(PopupHandle popup)
  1062. {
  1063.     return(PopupPixelDepth(popup) > 1);
  1064. }
  1065.  
  1066. /* Remember the current drawing environment of the system font and
  1067.     size and sets the system font and size to the popup font and size.
  1068.     Must be balanced with a call to PopupPortRestoreSystem. */
  1069. static void PopupPortSetupSystem(PopupHandle popup, PopupDrawStateType *state)
  1070. {
  1071.     GrafPtr svport;
  1072.     GrafPtr wmgrPort;
  1073.     
  1074.     if ((**popup).attr.windowFont) {
  1075.  
  1076.         /* force rebuild of font cache so MDEF will use the new fonts */
  1077.         FlushFonts();
  1078.  
  1079.         /* If using the window's font, then set the system font and size
  1080.             to the font and size of the popup's port. */
  1081.         state->sysfont = LMGetSysFontFam();
  1082.         state->syssize = LMGetSysFontSize();
  1083.         LMSetSysFontFam((**popup).port->txFont);
  1084.         LMSetSysFontSize((**popup).port->txSize);
  1085.  
  1086.         /* The menu definition function gets the font size in which to draw the
  1087.             popup menu from the font size of the window manager port. So, to
  1088.             set the system font, in addition to setting a low-memory global,
  1089.             we also have to set the font size for the window manager port. */
  1090.         GetPort(&svport);
  1091.         GetWMgrPort(&wmgrPort);
  1092.         SetPort(wmgrPort);
  1093.         state->wmgrfont = wmgrPort->txFont;
  1094.         state->wmgrsize = wmgrPort->txFont;
  1095.         TextFont((**popup).port->txFont);
  1096.         TextSize((**popup).port->txSize);
  1097.         SetPort(svport);
  1098.     }
  1099. }
  1100.  
  1101. /* Restore the current drawing environment of the system font and size. */
  1102. static void PopupPortRestoreSystem(PopupHandle popup, const PopupDrawStateType *state)
  1103. {
  1104.     GrafPtr svport;
  1105.     GrafPtr wmgrPort;
  1106.     
  1107.     if ((**popup).attr.windowFont) {
  1108.         LMSetSysFontFam(state->sysfont);
  1109.         LMSetSysFontSize(state->syssize);    
  1110.         GetPort(&svport);
  1111.         GetWMgrPort(&wmgrPort);
  1112.         SetPort(wmgrPort);
  1113.         TextFont(state->wmgrfont);
  1114.         TextSize(state->wmgrsize);
  1115.         SetPort(svport);
  1116.     }
  1117. }
  1118.  
  1119. /* Remember the current drawing environment and set the drawing environment 
  1120.     to that needed by the popup menu. Must be balanced with a call
  1121.     to PopupPortRestore. */
  1122. static void PopupPortSetup(PopupHandle popup, PopupDrawStateType *state)
  1123. {
  1124.     require(! (**popup).state.drawSetup);    
  1125.     
  1126.     #if FLUSH_RIGHT
  1127.         SetSysDirection(teFlushRight);
  1128.     #endif
  1129.  
  1130.     /* increment setup counter so we know when this is the first setup call */
  1131.     (**popup).state.drawSetup++;
  1132.     
  1133.     /* remember current port */
  1134.     GetPort(&state->port);
  1135.  
  1136.     /* save settings for popup's port */
  1137.     SetPort((**popup).port);
  1138.     GetPenState(&state->pen);
  1139.     GetTextState(&state->text);
  1140.     GetColorState(&state->color);
  1141.     
  1142.     /* remember popup port's default colors */
  1143.     if ((**popup).state.drawSetup == 1) {
  1144.         (**popup).draw.fore = state->color.fore;
  1145.         (**popup).draw.back = state->color.back;
  1146.     }
  1147.     
  1148.     /* switch to offscreen graphics world */
  1149.     if ((**popup).draw.gworld && state->port == (GrafPtr) (**popup).draw.gworld)
  1150.         SetGWorld((**popup).draw.gworld, NULL);
  1151.     
  1152.     /* reset text and pen settings to default system settings */
  1153.     TextNormal();
  1154.     PenNormal();
  1155.     
  1156.     /* if using the window's font, use font and font size of popup's port */
  1157.     if ((**popup).attr.windowFont) {
  1158.         TextFont(state->text.font);
  1159.         TextSize(state->text.size);
  1160.     }
  1161.     
  1162.     /* save clip region and clip to the popup's maximum bounding
  1163.         rectangle intersected with old clip region */
  1164.     state->clip = NewRgn();
  1165.     if (state->clip) {
  1166.         Rect maxbounds = (**popup).maxbounds;
  1167.         RgnHandle boundsClip = NewRgn();
  1168.         check(boundsClip != NULL);
  1169.         RectRgn(boundsClip, &maxbounds);
  1170.         GetClip(state->clip);
  1171.         SectRgn(state->clip, boundsClip, boundsClip);
  1172.         SetClip(boundsClip);
  1173.         DisposeRgn(boundsClip);
  1174.     }
  1175.     
  1176.     /* insert menu into menu list so we can access its color table */
  1177.     state->inserted = PopupInsertMenu(popup);
  1178.  
  1179.     /* setup system port */
  1180.     PopupPortSetupSystem(popup, state);
  1181.     
  1182.     ensure((**popup).state.drawSetup > 0);
  1183. }
  1184.  
  1185. /* Restore the drawing environment to its original state. */
  1186. static void PopupPortRestore(PopupHandle popup, PopupDrawStateType *state)
  1187. {
  1188.     require((**popup).state.drawSetup > 0);
  1189.     PopupPortRestoreSystem(popup, state);
  1190.     PopupDeleteMenu(popup, state->inserted);
  1191.     if (state->clip) {
  1192.         SetClip(state->clip);
  1193.         DisposeRgn(state->clip);
  1194.         state->clip = NULL;
  1195.     }
  1196.     SetColorState(&state->color);
  1197.     SetTextState(&state->text);
  1198.     SetPenState(&state->pen);
  1199.     SetPort(state->port);    
  1200.     #if FLUSH_RIGHT
  1201.         SetSysDirection(0);
  1202.     #endif
  1203.     (**popup).state.drawSetup--;
  1204.     ensure((**popup).state.drawSetup >= 0);
  1205. }
  1206.  
  1207. /*    Setup the drawing environment for drawing the current selection string. */
  1208. static void PopupPortSelectionSetup(PopupHandle popup,
  1209.     PopupDrawSelectionStructure *save)
  1210. {
  1211.     Style itemStyle;            /* menu item's style */
  1212.     short itemCmd;                /* item's command character */
  1213.     short itemScript;            /* item's script code */
  1214.     long itemFondSize;        /* item's font ID and size */
  1215.  
  1216.     /* save the text and color states */
  1217.     GetTextState(&save->textState);
  1218.     GetColorState(&save->colorState);
  1219.  
  1220.     /* get current item's font and style */
  1221.     if ((**popup).state.current > 0) {
  1222.  
  1223.         /* set the font script of the menu item */
  1224.         GetItemCmd((**popup).menu, (**popup).state.current, &itemCmd);
  1225.         if (itemCmd == kCmdScript) {
  1226.             GetItemIcon((**popup).menu, (**popup).state.current, &itemScript);
  1227.             if (GetScriptVariable(itemScript, smScriptEnabled)) {
  1228.                 itemFondSize = GetScriptVariable(itemScript, smScriptSysFondSize);
  1229.                 TextFont(HiWord(itemFondSize));
  1230.                 TextSize(LoWord(itemFondSize));
  1231.             }
  1232.         }
  1233.         
  1234.         /* set the text style of the menu item */
  1235.         GetItemStyle((**popup).menu, (**popup).state.current, &itemStyle);
  1236.         TextFace(itemStyle);
  1237.     }
  1238. }
  1239.  
  1240. /* restore the drawing environment after drawing the current selection */
  1241. static void PopupPortSelectionRestore(PopupHandle popup,
  1242.     const PopupDrawSelectionStructure *save)
  1243. {
  1244.     #pragma unused(popup)
  1245.     SetTextState(&save->textState);
  1246.     SetColorState(&save->colorState);
  1247. }
  1248.  
  1249. /*-------------------------------------------------------------------------*/
  1250. /* rectangle calculation routines */
  1251. /*-------------------------------------------------------------------------*/
  1252.  
  1253. /* set popup's rectangles and erase old popup if the frame has changed */
  1254. static void PopupRectanglesSet(PopupHandle popup, const PopupRectanglesType *r)
  1255. {
  1256.     Rect bounds;
  1257.     RgnHandle eraseRgn;
  1258.     RgnHandle boundsRgn;
  1259.     
  1260.     bounds = (**popup).r.bounds;
  1261.     if (! EqualRect(&bounds, &r->bounds)) {
  1262.         eraseRgn = NewRgn();
  1263.         boundsRgn = NewRgn();
  1264.         if (eraseRgn && boundsRgn) {
  1265.             /* to reduce flicker only erase the
  1266.                 area that doesn't overlap */
  1267.             RectRgn(eraseRgn, &bounds);
  1268.             RectRgn(boundsRgn, &r->bounds);
  1269.             DiffRgn(eraseRgn, boundsRgn, eraseRgn);
  1270.             EraseRgn(eraseRgn);
  1271.             DisposeRgn(eraseRgn);
  1272.             DisposeRgn(boundsRgn);
  1273.         }
  1274.         else
  1275.             RectErase(&bounds, false);
  1276.     }
  1277.     (**popup).r = *r;
  1278. }
  1279.  
  1280. /* calculate the popup's rectangles; this is an ugly function
  1281.     which I'd like to redo */
  1282. void PopupCalculate(PopupHandle popup)
  1283. {
  1284.     struct {                                /* margins around some items */
  1285.         struct { short left, right; } selection;
  1286.         struct { short left, right; } title;
  1287.         struct { short left, right; } triangle;
  1288.     } margin = {
  1289.         { kFrameSize, 0 },
  1290.         { kTitleMargin, kTitleMargin },
  1291.         { 0, kTriangleMargin },
  1292.     };
  1293.     struct {                                /* minimum widths of each item */
  1294.         short content;
  1295.         short hilite;
  1296.         short title;
  1297.         short triangle;
  1298.         short selection;
  1299.     } minwidth;
  1300.     short lineHeight;                    /* height of a line */
  1301.     short menuHeight;                    /* height of the selected menu item */
  1302.     short menuWidth;                    /* width of the menu */
  1303.     short triangleHeight;            /* height of triangle */
  1304.     short triangleWidth;                /* width of triangle */
  1305.     short maxwidth;                    /* for calculating maximum widths of items */
  1306.     short width;                        /* for calculating widths of items */
  1307.     Point iconSize;                    /* size of current item's icon */
  1308.     Rect maxbounds;                    /* popup's maximum bounding rectangle */
  1309.     ResType iconType;                    /* resource type of current item's icon */
  1310.     short iconID;                        /* resource ID  of current item's icon */
  1311.     FontInfo font;                        /* info about the current font size */
  1312.     Str255 title;                        /* the popup's title */
  1313.     Rect content;                        /* rectangle enclosing content (excludes frame) */
  1314.     GrafPtr port;                        /* the port the popup is drawn into */
  1315.     PopupRectanglesType r;            /* the calculated rectangles */
  1316.     PopupDrawSelectionStructure saveState; /* saved state for calculating menu height */
  1317.     PopupDrawStateType saveDrawState;        /* save port settings */
  1318.     
  1319.     /* don't bother calculating if drawing is turned off */
  1320.     if (! (**popup).attr.draw) return;
  1321.     
  1322.     /* initialize settings */
  1323.     PopupPortSetup(popup, &saveDrawState);
  1324.     PopupTitle(popup, title);
  1325.     GetPort(&port);
  1326.     
  1327.     /* get width of menu, except for the width of the triangle */
  1328.     CalcMenuSize((**popup).menu);
  1329.     menuWidth = (**(**popup).menu).menuWidth;
  1330.  
  1331.     /* setup port for current selection string */
  1332.     PopupPortSelectionSetup(popup, &saveState);
  1333.  
  1334.     /* get information about the font in which the current selection
  1335.         will be drawn */
  1336.     GetFontInfo(&font);
  1337.     lineHeight = font.ascent + font.descent + font.leading;
  1338.     check(lineHeight > 0);
  1339.     
  1340.     /* calculate size of triangle */
  1341.     if ((**popup).attr.typeIn)
  1342.         triangleHeight = kTriangleHeight;
  1343.     else
  1344.         triangleHeight = lineHeight / 3;
  1345.     triangleWidth = triangleHeight * 2;
  1346.     check(triangleWidth % 2 == 0);
  1347.     
  1348.     /* add the width of the triangle to the menu's width */
  1349.     menuWidth += triangleWidth + kTriangleMargin;
  1350.     
  1351.     /* calculate height of current selection */
  1352.     menuHeight = 0;
  1353.     MenuItemIcon((**popup).menu, (**popup).state.current,
  1354.         &iconType, &iconID, &iconSize);
  1355.     if (iconSize.v > 0)
  1356.         menuHeight = iconSize.v + kIconMarginV;
  1357.     
  1358.     /* the height must be at least as large as the minimum height
  1359.         of a line and the minimum height needed to display the
  1360.         triangle */
  1361.     if (menuHeight < lineHeight)
  1362.         menuHeight = lineHeight;
  1363.     if (menuHeight < triangleHeight + 4)
  1364.         menuHeight = triangleHeight + 4;
  1365.         
  1366.     /* restore port */
  1367.     PopupPortSelectionRestore(popup, &saveState);
  1368.  
  1369.     /* get information about the font in which the title will be drawn */
  1370.     {
  1371.         Style style = port->txFace;
  1372.         TextFace((**popup).attr.title.style);
  1373.         GetFontInfo(&font);
  1374.         TextFace(style);
  1375.     }
  1376.     lineHeight = font.ascent + font.descent + font.leading;
  1377.  
  1378.     /* adjust margins */
  1379.     
  1380.     if (! *title || (**popup).attr.typeIn) {
  1381.         /* Type-in popups don't display the title or the current selection,
  1382.             so those parts' margins aren't needed. Also, if the title is empty
  1383.             then the title's margins can also be empty. */
  1384.         if ((**popup).attr.typeIn) {
  1385.             margin.triangle.left = 3;
  1386.             margin.triangle.right = 4;
  1387.             menuHeight = lineHeight;
  1388.             font.widMax = 0;
  1389.         }
  1390.         margin.title.left = 0;
  1391.         margin.title.right = 0;
  1392.     }
  1393.     
  1394.     /* for right justified popups we need a little extra space for the drop
  1395.         shadow between the current selection area and the title */
  1396.     if (PopupFontJust(popup) == teFlushRight)
  1397.         margin.selection.left += kShadowSize;
  1398.         
  1399.     /* calculate minimum widths */
  1400.     
  1401.     minwidth.triangle = triangleWidth;
  1402.     minwidth.title = (*title ? font.widMax : 0);
  1403.     minwidth.hilite = minwidth.title + margin.title.left + margin.title.right;
  1404.     minwidth.selection = font.widMax + minwidth.triangle + margin.triangle.left + margin.triangle.right;
  1405.     minwidth.content = minwidth.hilite + minwidth.selection +
  1406.                             margin.selection.left + margin.selection.right;
  1407.     
  1408.     /* calculate rectangles */
  1409.     
  1410.     /* copy maxbounds rectangle */
  1411.     maxbounds = (**popup).maxbounds;
  1412.     
  1413.     /* calculate content rectangle; all the other rectangles will be
  1414.         calculated relative to this rectangle */
  1415.     content.top = maxbounds.top + kFrameSize;
  1416.     content.left = maxbounds.left + kFrameSize;
  1417.     content.bottom = content.top + menuHeight;
  1418.     content.right = content.left +
  1419.         lmax(maxbounds.right - maxbounds.left - 2 * kFrameSize  - kShadowSize,
  1420.              minwidth.content);
  1421.     
  1422.     /* vertically center content in maxbounds */
  1423.     {    short offset = ((maxbounds.bottom - maxbounds.top) -
  1424.                                (content.bottom - content.top)) / 2;
  1425.         OffsetRect(&content, 0, lmax(0, offset - kShadowSize));
  1426.     }
  1427.     
  1428.     /* calculate hilite rectangle */
  1429.     r.hilite = content;
  1430.     r.hilite.left = content.left;
  1431.     r.hilite.right = content.right - minwidth.selection;
  1432.  
  1433.     /* calculate title rectangle */
  1434.     check(content.bottom - content.top >= lineHeight);
  1435.     r.title.top = content.top + (content.bottom - content.top - lineHeight) / 2;
  1436.     r.title.left = content.left + margin.title.left;
  1437.     r.title.bottom = r.title.top + lineHeight - kTitleMarginBottom;
  1438.     /* calculate width of title rectangle */
  1439.     if ((**popup).attr.typeIn || ! *title)
  1440.         width = 0; /* don't show title */
  1441.     else {
  1442.         /* popup has a title */
  1443.         maxwidth =
  1444.             r.hilite.right - r.hilite.left -
  1445.             margin.title.left - margin.title.right;
  1446.         check(maxwidth >= minwidth.title);
  1447.         if ((**popup).attr.title.width) {
  1448.             /* use fixed title width as specified by application */
  1449.             width = lmin(maxwidth, (**popup).attr.title.width);
  1450.             width = lmax(width, minwidth.title);
  1451.         }
  1452.         else {
  1453.             /* use actual width of title string */
  1454.             Style style = port->txFace;
  1455.             TextFace((**popup).attr.title.style);
  1456.             width = TruncatePString(title, maxwidth);
  1457.             TextFace(style);
  1458.         }
  1459.         check(width <= maxwidth);
  1460.     }
  1461.     check(width >= minwidth.title);
  1462.     r.title.right = r.title.left + width;
  1463.  
  1464.     /* adjust right edge of hilite rectangle now that width of title is known */
  1465.     r.hilite.right = r.title.right + margin.title.right;
  1466.     
  1467.     /* calculate selection rectangle, initially using the maximum possible width */
  1468.     r.selection = content;
  1469.     r.selection.left = r.hilite.right + margin.selection.left;
  1470.     r.selection.right = content.right - margin.selection.right;
  1471.  
  1472.     /* calculate width of current selection */
  1473.     if ((**popup).attr.typeIn)
  1474.         width = minwidth.selection;
  1475.     else if ((**popup).attr.fixedWidth)
  1476.         width = lmax(r.selection.right - r.selection.left, minwidth.selection);
  1477.     else {
  1478.         maxwidth = r.selection.right - r.selection.left;
  1479.         width = lmin(maxwidth, menuWidth);
  1480.         width = lmax(width, minwidth.selection);
  1481.     }
  1482.  
  1483.     /* adjust right edge now that we know how wide everything is */
  1484.     r.selection.right = r.selection.left + width;
  1485.     content.right = r.selection.right + margin.selection.right;
  1486.     
  1487.     /* calculate triangle rectangle--center triangle vertically at
  1488.         right edge of selection rectangle */
  1489.     check(content.bottom - content.top >= triangleHeight);
  1490.     r.triangle.top = content.top + (content.bottom - content.top - triangleHeight) / 2;
  1491.     r.triangle.left = r.selection.right - triangleWidth - margin.triangle.right;
  1492.     r.triangle.bottom = r.triangle.top + triangleHeight;
  1493.     r.triangle.right = r.triangle.left + triangleWidth;
  1494.     
  1495.     /* calculate frame rectangle--surrounds content area, and includes the
  1496.         frame and shadow */
  1497.     r.bounds.top = content.top - kFrameSize;
  1498.     r.bounds.left = content.left - kFrameSize;
  1499.     r.bounds.right = content.right + kFrameSize;
  1500.     r.bounds.bottom = content.bottom + kFrameSize + kShadowSize;
  1501.     if (PopupFontJust(popup) != teFlushRight)
  1502.         r.bounds.right += kShadowSize; /* in teFlushRight shadow is part of content */
  1503.     
  1504.     /* mirror image rectangles if using a right justified menu */
  1505.     if (PopupFontJust(popup) == teFlushRight) {
  1506.         Rect flipbounds;
  1507.         
  1508.         /* copy and adjust maxbounds so flipping will work */
  1509.         flipbounds.top = maxbounds.top;
  1510.         flipbounds.left = maxbounds.left;
  1511.         flipbounds.bottom = maxbounds.top +
  1512.             lmax(maxbounds.bottom - maxbounds.top,
  1513.                  r.bounds.bottom - r.bounds.top);
  1514.         flipbounds.right = maxbounds.left +
  1515.             lmax(maxbounds.right - maxbounds.left,
  1516.                  r.bounds.right - r.bounds.left);
  1517.  
  1518.         /* flip the rectangles */
  1519.         RectFlip(&r.bounds, &flipbounds);
  1520.         RectFlip(&r.hilite, &flipbounds);
  1521.         RectFlip(&r.selection, &flipbounds);
  1522.         RectFlip(&r.title, &flipbounds);        
  1523.         RectFlip(&r.triangle, &flipbounds);
  1524.         if (PopupFontJust(popup) != teFlushRight)
  1525.             RectFlip(&r.triangle, &r.selection);
  1526.     }
  1527.     
  1528.     /* make sure everything's ok */
  1529.     check(RectWidth(&r.hilite) >= minwidth.hilite);
  1530.     check(RectWidth(&r.title) >= minwidth.title);
  1531.     check(RectWidth(&r.selection) >= minwidth.selection);
  1532.     check(RectWidth(&r.triangle) >= minwidth.triangle);
  1533.     check(RectWithin(&r.hilite, &r.bounds));
  1534.     check(RectWithin(&r.title, &r.hilite));
  1535.     check(RectWithin(&r.selection, &r.bounds));
  1536.     check(RectWithin(&r.triangle, &r.selection));
  1537.     check(! RectWithin(&r.hilite, &r.selection));
  1538.     check(! RectWithin(&r.selection, &r.hilite));
  1539.  
  1540.     /* set the popup's rectangles and restore the environment */
  1541.     PopupRectanglesSet(popup, &r);
  1542.     PopupPortRestore(popup, &saveDrawState);
  1543. }
  1544.  
  1545. /*-------------------------------------------------------------------------*/
  1546. /* popup colors */
  1547. /*-------------------------------------------------------------------------*/
  1548.  
  1549. /*    Set the 'foreColor' and 'backColor' parameters to a blend of those
  1550.     colors, adjusted for the current graphics device. */
  1551. static void PopupGetGray(PopupHandle popup,
  1552.     RGBColor *backColor,
  1553.     RGBColor *foreColor,
  1554.     RGBColor *grayColor)
  1555. {
  1556.     RGBColor tmpColor;
  1557.  
  1558.     tmpColor = *foreColor;
  1559.     if ((**popup).draw.gdevice)
  1560.         (void) GetGray((**popup).draw.gdevice, backColor, &tmpColor);
  1561.     else
  1562.         (void) GetGray(GetMainDevice(), backColor, &tmpColor);
  1563.     *grayColor = tmpColor;
  1564. }
  1565.  
  1566. /* Set the 'foreColor' and 'backColor' parameters to the foreground and
  1567.     background colors for the specified part of the control. The 'textMode'
  1568.     parameter is set to grayishTextOr if text should be grayed out,
  1569.     otherwise it is set to the text mode of the current port. The
  1570.     current port should be the port into which the popup is being
  1571.     drawn.
  1572.     
  1573.     Returns flags indicating if the part was found in the control
  1574.     or menu color tables (that is, color is not a default color
  1575.     and an entry for the color exists in the control or menu's
  1576.     color table).*/
  1577. static PartColorFlag PopupPartColor(PopupHandle popup, short part,
  1578.     RGBColor *foreColor, RGBColor *backColor, short *textMode)
  1579. {
  1580.     const RGBColor whiteRGBColor = { 65535, 65535, 65535 };
  1581.     const RGBColor blackRGBColor = { 0, 0, 0 };
  1582.     const PopupPartTableType table[kPopupPartLast - kPopupPartFirst] = {
  1583.         { kPopupPartBlackAndWhite, kPopupPartBlackAndWhite, kPopupPartBlackAndWhite, },
  1584.         { kPopupPartTitleFore, kPopupPartTitleBack, kPopupPartTitleDisabled, },
  1585.         { kPopupPartItemFore, kPopupPartItemBack, kPopupPartItemDisabled, },
  1586.         { kPopupPartIconFore, kPopupPartIconBack, kPopupPartIconDisabled, },
  1587.         { kPopupPartTriangleFore, kPopupPartTriangleBack, kPopupPartTriangleDisabled, },
  1588.         { kPopupPartFrameFore, kPopupPartFrameBack, kPopupPartFrameDisabled, },
  1589.         { kPopupPartShadowFore, kPopupPartShadowBack, kPopupPartShadowDisabled, },
  1590.         { kPopupPartDefaultFore, kPopupPartDefaultBack, kPopupPartDefaultDisabled, },
  1591.     };
  1592.     const PopupPartTableType *entry;    /* entry in table */
  1593.     Boolean defaultFore;        /* true if got a default foreground color from cctb */
  1594.     Boolean defaultBack;        /* true if got a default background color from cctb */
  1595.     Boolean fore;                /* true if got a foreground color from cctb */
  1596.     Boolean back;                /* true if got a background color from cctb */
  1597.     Boolean gray;                /* true if should select a gray color */
  1598.     Boolean enabled;            /* true if the part is enabled */
  1599.     PartColorFlag found;        /* flags if found a color entry */
  1600.     CCTabHandle cct;            /* control's color table */
  1601.     CCTabHandle dcct;            /* default control color table */
  1602.     short item;                    /* menu item to get color for */
  1603.     
  1604.     require((**popup).state.drawSetup);
  1605.     require(kPopupPartFirst <= part && part < kPopupPartLast);
  1606.     
  1607.     /* only use color if the destination port is a color port
  1608.         (otherwise, the colors would be mapped to black, which
  1609.         would be pretty ugly) */
  1610.     *textMode = (**popup).port->txMode;
  1611.     if (PopupPortIsColor(popup)) {
  1612.         
  1613.         check(MacHasColorQD());
  1614.         
  1615.         /* get control color tables */
  1616.         dcct = ControlColorTable(NULL);
  1617.         cct = ControlColorTable((**popup).ctl);
  1618.         if (cct == dcct)
  1619.             cct = NULL;
  1620.             
  1621.         /* find entry in part code table */
  1622.         entry = table + (part - kPopupPartFirst);
  1623.         
  1624.         /* determine if part is enabled */
  1625.         enabled = (**popup).attr.enabled;
  1626.         if (enabled) {
  1627.             switch (part) {
  1628.             case kPopupPartItem:
  1629.             case kPopupPartIcon:
  1630.                 enabled = MenuItemEnabled((**popup).menu, (**popup).state.current);
  1631.                 break;
  1632.             }
  1633.         }
  1634.         
  1635.         /* set gray mode */
  1636.         gray = false;
  1637.  
  1638.         /* get default colors from the control color table */
  1639.         *foreColor = blackRGBColor;
  1640.         *backColor = whiteRGBColor;
  1641.         defaultFore = ControlCTPartColor(cct, kPopupPartDefaultFore, foreColor);
  1642.         defaultBack = ControlCTPartColor(cct, kPopupPartDefaultBack, backColor);
  1643.         if (! defaultFore) {
  1644.             *foreColor = (**popup).draw.fore;
  1645.             defaultFore = ! RGBColorEqual(&blackRGBColor, foreColor);
  1646.         }
  1647.         if (! defaultBack) {
  1648.             *backColor = (**popup).draw.back;
  1649.             defaultBack = ! RGBColorEqual(&whiteRGBColor, backColor);
  1650.         }
  1651.         
  1652.         /* get color for part */
  1653.         found = kPartColorNone;
  1654.         fore = back = false;
  1655.         switch (part) {
  1656.         case kPopupPartBlackAndWhite:
  1657.             *foreColor = blackRGBColor;
  1658.             *backColor = whiteRGBColor;
  1659.             found = kPartColorBackAndFore;
  1660.             break;
  1661.         case kPopupPartDefault:
  1662.             break;
  1663.         case kPopupPartTitle:
  1664.         case kPopupPartItem:
  1665.         case kPopupPartIcon:
  1666.             switch (part) {
  1667.             case kPopupPartTitle:
  1668.                 item = 0;
  1669.                 break;
  1670.             case kPopupPartItem:
  1671.                 item = (**popup).state.current;
  1672.                 break;
  1673.             case kPopupPartIcon:
  1674.                 item = (**popup).state.current;
  1675.                 break;
  1676.             }
  1677.             if (MenuItemColor((**popup).menu, item, foreColor, backColor))
  1678.                 found = kPartColorBackAndFore;
  1679.             if (found == kPartColorNone) {
  1680.                 fore = ControlCTPartColor(cct, entry->fore, foreColor);
  1681.                 back = ControlCTPartColor(cct, entry->back, backColor);
  1682.                 if (fore) found |= kPartColorFore;
  1683.                 if (back) found |= kPartColorBack;
  1684.                 if (! fore && ! defaultFore) fore = MenuItemColorDefault(item, foreColor, NULL);
  1685.                 if (! back && ! defaultBack) back = MenuItemColorDefault(item, NULL, backColor);
  1686.                 if (! fore && ! defaultFore) (void) ControlCTPartColor(dcct, cTextColor, foreColor);
  1687.                 if (! back && ! defaultBack) (void) ControlCTPartColor(dcct, cBodyColor, backColor);
  1688.             }
  1689.             if (! enabled) {
  1690.                 if (ControlCTPartColor(cct, entry->disabled, foreColor))
  1691.                     found |= kPartColorFore;
  1692.                 else {
  1693.                     gray = (part == kPopupPartIcon);
  1694.                     if (MacHasGrayishTextOr())
  1695.                           *textMode = grayishTextOr;
  1696.                     else
  1697.                         gray = true;
  1698.                   }
  1699.               }
  1700.             break;
  1701.         case kPopupPartTriangle:
  1702.         case kPopupPartFrame:
  1703.         case kPopupPartShadow:
  1704.             fore = ControlCTPartColor(cct, entry->fore, foreColor);
  1705.             back = ControlCTPartColor(cct, entry->back, backColor);
  1706.             if (fore) found |= kPartColorFore;
  1707.             if (back) found |= kPartColorBack;
  1708.             switch (part) {
  1709.             case kPopupPartTriangle:
  1710.                 if (! fore && ! defaultFore) (void) ControlCTPartColor(dcct, cTextColor, foreColor);
  1711.                 if (! back && ! defaultBack) (void) ControlCTPartColor(dcct, cBodyColor, backColor);
  1712.                 break;
  1713.             case kPopupPartShadow:
  1714.             case kPopupPartFrame:
  1715.                 if (! fore && ! defaultFore) (void) ControlCTPartColor(dcct, cFrameColor, foreColor);
  1716.                 break;
  1717.             }
  1718.             if (! enabled) {
  1719.                   gray = ! ControlCTPartColor(cct, entry->disabled, foreColor);
  1720.                 if (! gray)
  1721.                     found |= kPartColorFore;
  1722.               }
  1723.             if (    (found & kPartColorFore) == kPartColorNone &&
  1724.                     part == kPopupPartShadow &&
  1725.                     (**popup).attr.bevel &&
  1726.                     PopupPixelDepth(popup) >= kBevelDepth)
  1727.             {
  1728.                 gray = true;
  1729.             }
  1730.             break;
  1731.         default:
  1732.             /* error */
  1733.             check(false);
  1734.         }
  1735.         
  1736.         /* derive a gray color from a blend of the foreground and background colors */
  1737.         if (gray)
  1738.             PopupGetGray(popup, backColor, foreColor, foreColor);
  1739.     }
  1740.     return(found);
  1741. }
  1742.  
  1743. /*    Set the foreground and background colors to the colors appropriate for
  1744.     drawing the specified part of the control, or invert the colors if the
  1745.     'invert' flag is true. Returns non-zero if the part was found in the control
  1746.     or menu color table (that is, the color is not a default color) and
  1747.     the popup is being drawn in a color port. */
  1748. static PartColorFlag PopupPartColorInvertSet(PopupHandle popup, short part, Boolean invert)
  1749. {
  1750.     PartColorFlag found;
  1751.     RGBColor foreColor;
  1752.     RGBColor backColor;
  1753.     short mode;
  1754.     
  1755.     found = kPartColorNone;
  1756.     if (PopupPortIsColor(popup)) {
  1757.         if (invert)
  1758.             found = PopupPartColor(popup, part, &backColor, &foreColor, &mode);
  1759.         else
  1760.             found = PopupPartColor(popup, part, &foreColor, &backColor, &mode);
  1761.         RGBForeColor(&foreColor);
  1762.         RGBBackColor(&backColor);
  1763.         TextMode(mode);
  1764.     }
  1765.     return(found);
  1766. }
  1767.  
  1768. /*    See PopupPartColorInvertSet. */
  1769. static PartColorFlag PopupPartColorSet(PopupHandle popup, short part)
  1770. {
  1771.     return(PopupPartColorInvertSet(popup, part, false));
  1772. }
  1773.  
  1774. /* Calculate bevel colors. */
  1775. static void PopupBevelColors(PopupHandle popup,
  1776.     RGBColor *bevelUpperColor,
  1777.     RGBColor *bevelLowerColor,
  1778.     RGBColor *bevelCornerColor)
  1779. {
  1780.     RGBColor frameColor;
  1781.     RGBColor itemColor;
  1782.     RGBColor dummyColor;
  1783.     short dummyMode;
  1784.  
  1785.     (void) PopupPartColor(popup, kPopupPartFrame, &frameColor, &dummyColor, &dummyMode);
  1786.     (void) PopupPartColor(popup, kPopupPartItem, &dummyColor, &itemColor, &dummyMode);
  1787.     (void) PopupPartColor(popup, kPopupPartBlackAndWhite, &dummyColor, bevelUpperColor, &dummyMode);
  1788.     PopupGetGray(popup, &itemColor, &frameColor, bevelLowerColor);
  1789.     PopupGetGray(popup, bevelUpperColor, bevelLowerColor, bevelCornerColor);
  1790. }    
  1791.  
  1792. /*-------------------------------------------------------------------------*/
  1793. /* more drawing routines */
  1794. /*-------------------------------------------------------------------------*/
  1795.  
  1796. /* calculate frame rectangle */
  1797. static void PopupFrameRectangle(PopupHandle popup, Rect *frame, Boolean shadow)
  1798. {
  1799.     *frame = (**popup).r.bounds;
  1800.     if (PopupFontJust(popup) == teFlushRight)
  1801.         frame->right = (**popup).r.selection.right + kFrameSize + kShadowSize;
  1802.     else
  1803.         frame->left = (**popup).r.selection.left - kFrameSize;
  1804.     if (! shadow) {
  1805.         frame->right -= kShadowSize;
  1806.         frame->bottom -= kShadowSize;
  1807.     }
  1808. }
  1809.  
  1810. /* draw the bevel around the popup menu */
  1811. static void PopupDrawBevel(PopupHandle popup, const Rect *bevel)
  1812. {
  1813.     RGBColor bevelUpperColor;
  1814.     RGBColor bevelLowerColor;
  1815.     RGBColor bevelCornerColor;
  1816.  
  1817.     require((**popup).state.drawSetup);
  1818.     require(RectValid(bevel));
  1819.  
  1820.     /* get bevel colors */
  1821.     PopupBevelColors(popup, &bevelUpperColor, &bevelLowerColor, &bevelCornerColor);
  1822.     PopupGetGray(popup, &bevelUpperColor, &bevelLowerColor, &bevelLowerColor);
  1823.     PopupGetGray(popup, &bevelUpperColor, &bevelLowerColor, &bevelCornerColor);
  1824.  
  1825.     /* draw upper bevel */
  1826.     RGBForeColor(&bevelUpperColor);
  1827.     PenSize(kBevelSize, kBevelSize);
  1828.     MoveTo(bevel->left, bevel->bottom - kBevelSize - kShadowSize);
  1829.     LineTo(bevel->left, bevel->top);
  1830.     LineTo(bevel->right - kBevelSize - kShadowSize, bevel->top);
  1831.     
  1832.     /* draw lower bevel */
  1833.     RGBForeColor(&bevelLowerColor);
  1834.     PenSize(kBevelSize, kBevelSize);
  1835.     MoveTo(bevel->right - kBevelSize, bevel->top);
  1836.     LineTo(bevel->right - kBevelSize, bevel->bottom - kBevelSize);
  1837.     LineTo(bevel->left, bevel->bottom - kBevelSize);
  1838.     
  1839.     /* draw corners of bevel */
  1840.     RGBForeColor(&bevelCornerColor);        
  1841.     PenSize(kBevelSize, kBevelSize);
  1842.     MoveTo(bevel->right - kBevelSize, bevel->top);
  1843.     LineTo(bevel->right - kBevelSize, bevel->top); 
  1844.     MoveTo(bevel->left, bevel->bottom - kBevelSize);
  1845.     LineTo(bevel->left, bevel->bottom - kBevelSize);
  1846. }
  1847.  
  1848. /* draw the frame and shadow around the popup menu */
  1849. static void PopupDrawFrame(PopupHandle popup)
  1850. {
  1851.     Rect frame;
  1852.     Rect erase;
  1853.     PenState pen;
  1854.     ColorState color;
  1855.     Boolean partHasBackColor;
  1856.  
  1857.     require((**popup).state.drawSetup);
  1858.     
  1859.     /* save state */
  1860.     GetPenState(&pen);
  1861.     GetColorState(&color);
  1862.     
  1863.     /* calculate frame rectangle */
  1864.     PopupFrameRectangle(popup, &frame, false);
  1865.     
  1866.     /* draw upper part of frame */
  1867.     (void) PopupPartColorSet(popup, kPopupPartFrame);
  1868.     PenSize(kFrameSize, kFrameSize);
  1869.     MoveTo(frame.left, frame.bottom - kFrameSize - kShadowSize);
  1870.     LineTo(frame.left, frame.top);
  1871.     LineTo(frame.right - kFrameSize - kShadowSize, frame.top);
  1872.  
  1873.     /* draw lower part of frame (we could use a different color here) */
  1874.     (void) PopupPartColorSet(popup, kPopupPartFrame);
  1875.     PenSize(kFrameSize, kFrameSize);
  1876.     MoveTo(frame.right - kFrameSize, frame.top);
  1877.     LineTo(frame.right - kFrameSize, frame.bottom - kFrameSize);
  1878.     LineTo(frame.left, frame.bottom - kFrameSize);
  1879.  
  1880.     /* draw bevel */
  1881.     if ((**popup).attr.bevel && PopupPixelDepth(popup) >= kBevelDepth) {
  1882.         Rect bevel = frame;
  1883.         InsetRect(&bevel, kFrameSize, kFrameSize);
  1884.         PopupDrawBevel(popup, &bevel);
  1885.     }
  1886.  
  1887.     /* draw drop shadow */
  1888.     (void) PopupPartColorSet(popup, kPopupPartShadow);
  1889.     PenSize(kShadowSize, kShadowSize);
  1890.     MoveTo(frame.right, frame.top + kShadowOffset);
  1891.     LineTo(frame.right, frame.bottom);
  1892.     LineTo(frame.left + kShadowOffset, frame.bottom);
  1893.     
  1894.     /* erase the few pixels of the shadow that aren't drawn */
  1895.     partHasBackColor = ((PopupPartColorSet(popup, kPopupPartDefault) & kPartColorBack) != 0);
  1896.     erase.top = frame.top;
  1897.     erase.left = frame.right;
  1898.     erase.bottom = frame.top + kShadowOffset;
  1899.     erase.right = frame.right + kShadowSize;
  1900.     RectErase(&erase, partHasBackColor);
  1901.     erase.top = frame.bottom;
  1902.     erase.left = frame.left;
  1903.     erase.bottom = frame.bottom + kShadowSize;
  1904.     erase.right = frame.left + kShadowOffset;
  1905.     RectErase(&erase, partHasBackColor);
  1906.     
  1907.     /* restore state */
  1908.     SetPenState(&pen);
  1909.     SetColorState(&color);
  1910. }
  1911.  
  1912. /* draw the triangle */
  1913. static void PopupDrawTriangle(PopupHandle popup)
  1914. {
  1915.     Rect triangle;            /* triangle's rectangle */
  1916.     short    width;            /* width of current line */
  1917.     short    height;            /* height of triangle */
  1918.     short    i;                    /* index to lines of triangle */
  1919.     PenState penState;
  1920.     ColorState colorState;
  1921.     
  1922.     require((**popup).state.drawSetup);
  1923.     
  1924.     /* reset environment */
  1925.     GetPenState(&penState);
  1926.     GetColorState(&colorState);
  1927.     PenNormal();
  1928.     
  1929.     /* erase triangle */
  1930.     triangle = (**popup).r.triangle;
  1931.     RectErase(&triangle, (PopupPartColorSet(popup, kPopupPartItem) & kPartColorBack) != 0);
  1932.  
  1933.     /* draw triangle */
  1934.     (void) PopupPartColorSet(popup, kPopupPartTriangle);
  1935.     height = triangle.bottom - triangle.top;
  1936.     width = triangle.right - triangle.left;
  1937.     check(width % 2 == 0);
  1938.     for (i = 0; width >= 0; i++) {
  1939.         MoveTo(triangle.left + i, triangle.top + i);
  1940.         LineTo(triangle.left + i + width, triangle.top + i);
  1941.         width -= 2;
  1942.     }
  1943.     
  1944.     #if DRAW_SHADED_TRIANGLE
  1945.  
  1946.         /* draw shaded triangle */
  1947.         if ((**popup).attr.bevel && PopupPixelDepth(popup) >= kBevelDepth) {
  1948.             struct { long red, green, blue; } decrColor; /* decrement for drawing triangle */
  1949.             RGBColor lightColor;            /* color of lightest part of triangle */
  1950.             RGBColor darkColor;            /* color of darkest part of triangle */
  1951.             RGBColor lineColor;            /* color of current line */
  1952.             RGBColor whiteColor;
  1953.             RGBColor dummyColor;
  1954.             short dummyMode;
  1955.     
  1956.             /* get colors, for calculating shading of triangle */
  1957.             (void) PopupPartColor(popup, kPopupPartFrame, &darkColor, &dummyColor, &dummyMode);
  1958.             (void) PopupPartColor(popup, kPopupPartBlackAndWhite, &dummyColor, &whiteColor, &dummyMode);
  1959.             PopupGetGray(popup, &darkColor, &whiteColor, &lightColor);
  1960.             PopupGetGray(popup, &lightColor, &whiteColor, &lightColor);
  1961.  
  1962.             /* calculate start color and increment for shaded triangle */
  1963.             #ifdef OUTLINE_TRIANGLE
  1964.                 /* compile these statements to draw the shading within
  1965.                     an outline of the triangle (IMHO, less aesthetic) */
  1966.                 InsetRect(&triangle, 1, 1); triangle.left++;
  1967.             #endif /* OUTLINE_TRIANGLE */
  1968.             width = triangle.right - triangle.left;
  1969.             if (width > 0) {
  1970.                 decrColor.red = ((long) lightColor.red - darkColor.red) / width;
  1971.                 decrColor.green = ((long) lightColor.green - darkColor.green) / width;
  1972.                 decrColor.blue =  ((long) lightColor.blue - darkColor.blue) / width;
  1973.                 lineColor = lightColor;
  1974.                 if (decrColor.red || decrColor.green || decrColor.blue) {
  1975.         
  1976.                     /* draw shading inside triangle */
  1977.                     for (i = 0; i < width; i++) {
  1978.                         RGBForeColor(&lineColor);
  1979.                         lineColor.red -= decrColor.red;
  1980.                         lineColor.green -= decrColor.green;
  1981.                         lineColor.blue -= decrColor.blue;
  1982.                         MoveTo(triangle.left + i, triangle.top);
  1983.                         LineTo(triangle.left + i - i / 2, triangle.top + i / 2);
  1984.                     }
  1985.                 }
  1986.             }
  1987.         }
  1988.  
  1989.     #endif /* DRAW_SHADED_TRIANGLE */
  1990.  
  1991.     /* restore port */
  1992.     SetColorState(&colorState);
  1993.     SetPenState(&penState);
  1994. }
  1995.  
  1996. /* draw the string within the bounding rectangle; if the string is too long,
  1997.     it is truncated and an elipses character is appended */
  1998. static void PopupDrawString(PopupHandle popup, Str255 str,
  1999.     const Rect *bounds, short just)
  2000. {
  2001.     #pragma unused(popup)
  2002.     FontInfo    font;
  2003.     short width;
  2004.     
  2005.     require((**popup).state.drawSetup);
  2006.     require(RectValid(bounds));
  2007.     GetFontInfo(&font);
  2008.     width = TruncatePString(str, bounds->right - bounds->left);
  2009.     if (just == teFlushRight)
  2010.         MoveTo(bounds->right - width, bounds->bottom - font.descent);
  2011.     else if (just == teCenter)
  2012.         MoveTo(bounds->left + (bounds->right - bounds->left - width) / 2, bounds->bottom - font.descent);
  2013.     else
  2014.         MoveTo(bounds->left, bounds->bottom - font.descent);
  2015.     DrawString(str);
  2016. }
  2017.  
  2018. /* draw the current selection string and icon */
  2019. static void PopupDrawSelection(PopupHandle popup)
  2020. {
  2021.     PopupDrawSelectionStructure saveState; /* saved state */
  2022.     Str255 itemString;        /* string of currently selected menu item */
  2023.     FontInfo font;                /* information about the current font */
  2024.     Rect selectionRect;        /* rectangle to draw string in */
  2025.     short selectionHeight;    /* height of current selection rectangle */
  2026.     short lineHeight;            /* height of a line of text */
  2027.     short tweak;                /* for tweaking position if not using Chicago */
  2028.     Rect iconRect;                /* rectangle to draw icon in */
  2029.     Point iconSize;            /* size of current item's icon */
  2030.     ResType iconType;            /* resource type of current item's icon */
  2031.     short iconID;                /* resource ID of current item's icon */
  2032.     Handle iconHandle;        /* handle to current item's icon */
  2033.     SignedByte iconState;    /* state of handle to icon */
  2034.     Boolean iconDraw;            /* true if should draw the icon */
  2035.     IconTransformType iconTransform; /* transformation to apply when drawing icon */
  2036.     
  2037.     require((**popup).state.drawSetup);
  2038.     
  2039.     /* set up drawing environment */
  2040.     PopupPortSelectionSetup(popup, &saveState);
  2041.     
  2042.     /* initially, set the rectangle within which to draw the string
  2043.         equal to the entire current selection rectangle and erase it
  2044.         to the background color */
  2045.     selectionRect = (**popup).r.selection;
  2046.     RectErase(&selectionRect, (PopupPartColorSet(popup, kPopupPartItem) & kPartColorBack) != 0);
  2047.     
  2048.     if ((**popup).attr.typeIn) {
  2049.         /* don't draw selection contents for type-in style popup menu  */
  2050.         PopupPortSelectionRestore(popup, &saveState);
  2051.         return;
  2052.     }
  2053.         
  2054.     GetFontInfo(&font);
  2055.     lineHeight = font.ascent + font.descent + font.leading;
  2056.     
  2057.     /* calculate size of current item's icon */
  2058.     MenuItemIcon((**popup).menu, (**popup).state.current,
  2059.         &iconType, &iconID, &iconSize);
  2060.  
  2061.     /* adjust tweak value if drawing in a font other than Chicago */
  2062.     tweak = 0;
  2063.     if ((**popup).attr.windowFont && (**popup).port->txFont != systemFont)
  2064.         tweak = 2;
  2065.         
  2066.     /* exclude mark character from selection rectangle */
  2067.     if (PopupFontJust(popup) == teFlushRight)
  2068.         selectionRect.right -= font.widMax + tweak;
  2069.     else
  2070.         selectionRect.left += font.widMax + tweak;
  2071.     
  2072.     /* exclude triangle from selection rectangle */
  2073.     if (PopupFontJust(popup) == teFlushRight)
  2074.         selectionRect.left += (**popup).r.triangle.right - (**popup).r.triangle.left + kTriangleMargin;
  2075.     else
  2076.         selectionRect.right -= (**popup).r.triangle.right - (**popup).r.triangle.left + kTriangleMargin;
  2077.     
  2078.     /* fix selection rectangle */
  2079.     if (selectionRect.right < selectionRect.left)
  2080.         selectionRect.right = selectionRect.left;
  2081.     if (selectionRect.left > selectionRect.right)
  2082.         selectionRect.left = selectionRect.right;
  2083.     check(RectValid(&selectionRect));
  2084.     
  2085.     /* get the rectangle for later calculating the icon rectangle */
  2086.     iconRect = selectionRect;
  2087.     
  2088.     /* exclude icon from selection rectangle */
  2089.     if (iconSize.h > 0) {
  2090.         if (PopupFontJust(popup) == teFlushRight)
  2091.             selectionRect.right -= iconSize.h + CharWidth(' ');
  2092.         else
  2093.             selectionRect.left += iconSize.h + CharWidth(' ');
  2094.     }
  2095.     
  2096.     /* align the bounding box within which to draw the current selection
  2097.         string with the base line of the title string and with the edge
  2098.         of the current selection rectangle */
  2099.     selectionHeight = selectionRect.bottom - selectionRect.top;
  2100.     check(selectionHeight >= lineHeight);
  2101.     selectionRect.top += (selectionHeight - lineHeight) / 2 - 1;
  2102.     selectionRect.bottom = selectionRect.top + lineHeight;
  2103.  
  2104.     /* fix selection rectangle */
  2105.     if (selectionRect.right < selectionRect.left)
  2106.         selectionRect.right = selectionRect.left;
  2107.     if (selectionRect.left > selectionRect.right)
  2108.         selectionRect.left = selectionRect.right;
  2109.     check(RectValid(&selectionRect));
  2110.     
  2111.     /* set the item's color */
  2112.     
  2113.     /* draw the item's string */
  2114.     (void) PopupPartColorSet(popup, kPopupPartItem);
  2115.     GetMenuItemText((**popup).menu, (**popup).state.current, itemString);
  2116.     PopupDrawString(popup, itemString, &selectionRect, PopupFontJust(popup));
  2117.     
  2118.     /* calculate the icon's rectangle, but don't draw the icon if
  2119.         it overlaps the item's text or the triangle */
  2120.     // program_note: how is the system MDEF calculating the positions of non-cicn icons???
  2121.     iconDraw = true;
  2122.     check(iconRect.bottom - iconRect.top >= iconSize.v);
  2123.     iconRect.top += (iconRect.bottom - iconRect.top - iconSize.v) / 2;
  2124.     iconRect.bottom = iconRect.top + iconSize.v;
  2125.     if (PopupFontJust(popup) == teFlushRight) {
  2126.         iconRect.left = iconRect.right - iconSize.h;
  2127.         if (iconRect.left < selectionRect.right ||
  2128.              iconRect.left < (**popup).r.triangle.right)
  2129.         {
  2130.             iconDraw = false;
  2131.         }
  2132.     }
  2133.     else {
  2134.         iconRect.right = iconRect.left + iconSize.h;
  2135.         if (iconRect.right > selectionRect.left ||
  2136.              iconRect.right > (**popup).r.triangle.left)
  2137.         {
  2138.             iconDraw = false;
  2139.         }
  2140.     }
  2141.         
  2142.     /* determine transformation to apply when drawing icon */
  2143.     iconTransform = ttNone;
  2144.     if (! (**popup).attr.enabled ||
  2145.          ! MenuItemEnabled((**popup).menu, (**popup).state.current))
  2146.     {
  2147.         /* We paint over the menu with a gray pattern if the port depth
  2148.             is less than kDisableDepth, and this would interfere with the
  2149.             gray pattern applied when the ttDisabled mode is used. */
  2150.         if (PopupPixelDepth(popup) >= kDisableDepth)
  2151.             iconTransform = ttDisabled;
  2152.     }
  2153.  
  2154.     /* draw the item's icon */
  2155.     if (iconDraw && iconType) {
  2156.         switch (iconType) {
  2157.         case 'SICN':
  2158.             (void) PopupPartColorSet(popup, kPopupPartIcon);
  2159.             iconHandle = GetResource(iconType, iconID);
  2160.             if (iconHandle && *iconHandle) {
  2161.                 iconState = HGetState(iconHandle);
  2162.                 HNoPurge(iconHandle);
  2163.                 if (MacHasIconUtilities())
  2164.                     PlotSICNHandle(&iconRect, atNone, iconTransform, iconHandle);
  2165.                 else
  2166.                     PlotSICN(&iconRect, iconHandle);
  2167.                 HSetState(iconHandle, iconState);
  2168.             }
  2169.             break;
  2170.         case 'ICON':
  2171.             (void) PopupPartColorSet(popup, kPopupPartIcon);
  2172.             iconHandle = GetResource(iconType, iconID);
  2173.             if (iconHandle && *iconHandle) {
  2174.                 iconState = HGetState(iconHandle);
  2175.                 HNoPurge(iconHandle);
  2176.                 if (MacHasIconUtilities())
  2177.                     PlotIconHandle(&iconRect, atNone, iconTransform, iconHandle);
  2178.                 else
  2179.                     PlotIcon(&iconRect, iconHandle);
  2180.                 HSetState(iconHandle, iconState);
  2181.             }
  2182.             break;
  2183.         case 'cicn':
  2184.             check(MacHasColorQD());
  2185.             (void) PopupPartColorSet(popup, kPopupPartBlackAndWhite);
  2186.             iconHandle = (Handle) GetCIcon(iconID);
  2187.             if (iconHandle) {
  2188.                 if (*iconHandle) {
  2189.                     if (MacHasIconUtilities())
  2190.                         PlotCIconHandle(&iconRect, atNone, iconTransform, (CIconHandle) iconHandle);
  2191.                     else
  2192.                         PlotCIcon(&iconRect, (CIconHandle) iconHandle);
  2193.                 }
  2194.                 DisposeCIcon((CIconHandle) iconHandle);
  2195.             }
  2196.             break;
  2197.         }
  2198.     }
  2199.  
  2200.     /* gray over entire selection if item is disabled and we're drawing
  2201.         into a black and white port */
  2202.     if (PopupPixelDepth(popup) < kDisableDepth &&
  2203.          ! MenuItemEnabled((**popup).menu, (**popup).state.current))
  2204.     {
  2205.         selectionRect = (**popup).r.selection;
  2206.         RectDisable(&selectionRect);
  2207.     }
  2208.  
  2209.     /* restore drawing environment */
  2210.     PopupPortSelectionRestore(popup, &saveState);
  2211.     
  2212. }
  2213.  
  2214. /* draw the title string */
  2215. static void PopupDrawTitle(PopupHandle popup, Boolean invert)
  2216. {
  2217.     Str255 title;                /* title string */
  2218.     Rect rtitle;                /* title's text rectangle */
  2219.     Rect rhilite;                /* title's hilite rectangle */
  2220.     TextState saveText;        /* save text state */
  2221.     ColorState saveColor;    /* saved color state */
  2222.     
  2223.     require((**popup).state.drawSetup);
  2224.     require(! (**popup).attr.typeIn);
  2225.  
  2226.     /* save port */
  2227.     GetTextState(&saveText);
  2228.     GetColorState(&saveColor);
  2229.     
  2230.     /* get rectangles */
  2231.     rhilite = (**popup).r.hilite;
  2232.     rtitle = (**popup).r.title;
  2233.     
  2234.     /* set the title's colors */
  2235.         
  2236.     /* set the text style */
  2237.     TextFace((**popup).attr.title.style);
  2238.  
  2239.     /* draw the title */
  2240.     if ((PopupPartColorInvertSet(popup, kPopupPartTitle, invert) & kPartColorBack) != 0)
  2241.         RectErase(&rhilite, true);
  2242.     else
  2243.         RectErase(&rhilite, invert);
  2244.     PopupTitle(popup, title);
  2245.     PopupDrawString(popup, title, &rtitle, (**popup).attr.title.just);
  2246.     
  2247.     /* restore port's settings */
  2248.     SetColorState(&saveColor);
  2249.     SetTextState(&saveText);
  2250. }
  2251.  
  2252. /* gray out entire popup menu if it's disabled */
  2253. static void PopupDrawEnabled(PopupHandle popup)
  2254. {
  2255.     Rect bounds;
  2256.     
  2257.     require((**popup).state.drawSetup);
  2258.     if (PopupPixelDepth(popup) < kDisableDepth && ! (**popup).attr.enabled) {
  2259.         bounds = (**popup).r.bounds;
  2260.         RectDisable(&bounds);
  2261.     }
  2262. }
  2263.  
  2264. /* erase the popup menu */
  2265. static void PopupErase(PopupHandle popup, Boolean all)
  2266. {
  2267.     ColorState state;
  2268.     Rect maxboundsRect;
  2269.     Rect tmpRect;
  2270.     RgnHandle rgn1;
  2271.     RgnHandle rgn2;
  2272.     Boolean partHasBackColor;
  2273.  
  2274.     require((**popup).state.drawSetup);
  2275.     GetColorState(&state);
  2276.     partHasBackColor = ((PopupPartColorSet(popup, kPopupPartDefault) & kPartColorBack) != 0);
  2277.     maxboundsRect = (**popup).maxbounds;
  2278.     if (all)
  2279.         RectErase(&maxboundsRect, partHasBackColor);
  2280.     else {
  2281.         /* a bunch of region calculations to minimize
  2282.             the amount of flickering of the popup's
  2283.             frame */
  2284.         rgn1 = NewRgn();
  2285.         rgn2 = NewRgn();
  2286.         if (! rgn1 || ! rgn2)
  2287.             RectErase(&maxboundsRect, partHasBackColor);
  2288.         else {
  2289.             tmpRect = (**popup).maxbounds;
  2290.             RectRgn(rgn1, &tmpRect);
  2291.             PopupFrameRectangle(popup, &tmpRect, true);
  2292.             RectRgn(rgn2, &tmpRect);
  2293.             DiffRgn(rgn1, rgn2, rgn1);
  2294.             tmpRect = (**popup).r.hilite;
  2295.             RectRgn(rgn2, &tmpRect);
  2296.             DiffRgn(rgn1, rgn2, rgn1);
  2297.             RgnErase(rgn1, partHasBackColor);
  2298.         }
  2299.         if (rgn1) DisposeRgn(rgn1);
  2300.         if (rgn2) DisposeRgn(rgn2);
  2301.     }
  2302.     SetColorState(&state);
  2303. }
  2304.  
  2305. #if DRAW_RECTANGLES
  2306.  
  2307. /* draw frames around the parts of the popup; this is useful when
  2308.     debugging */
  2309. static void PopupDrawRectangles(PopupHandle popup)
  2310. {
  2311.     struct {
  2312.         long maxbounds;
  2313.         long bounds;
  2314.         long frame;
  2315.         long shadow;
  2316.         long title;
  2317.         long hilite;
  2318.         long selection;
  2319.         long triangle;
  2320.     } partColor = {
  2321.         blackColor,
  2322.         blueColor,
  2323.         yellowColor,
  2324.         magentaColor,
  2325.         redColor,
  2326.         cyanColor,
  2327.         greenColor,
  2328.         blueColor,
  2329.     };
  2330.     PenState penState;
  2331.     long foreColor;
  2332.     Rect shadow;
  2333.     Rect frame;
  2334.     GrafPtr port;
  2335.     
  2336.     /* setup port */
  2337.     GetPort(&port);
  2338.     GetPenState(&penState);
  2339.     foreColor = port->fgColor;
  2340.     PenNormal();
  2341.     
  2342.     /* draw outline of frame and shadow */
  2343.     if (kShadowSize > 2 && kFrameSize > 2) {
  2344.     
  2345.         /* calculate frame */
  2346.         frame = (**popup).r.bounds;    
  2347.         if (PopupFontJust(popup) == teFlushRight)
  2348.             frame.right = (**popup).r.selection.right + kFrameSize + kShadowSize;
  2349.         else
  2350.             frame.left = (**popup).r.selection.left - kFrameSize;
  2351.         frame.right -= kShadowSize;
  2352.         frame.bottom -= kShadowSize;
  2353.         
  2354.         /* draw outline of frame */
  2355.         ForeColor(partColor.frame);
  2356.         FrameRect(&frame);
  2357.         InsetRect(&frame, kFrameSize, kFrameSize);
  2358.         FrameRect(&frame);
  2359.         InsetRect(&frame, -kFrameSize, -kFrameSize);
  2360.         
  2361.         /* draw outline of shadow */
  2362.         ForeColor(partColor.shadow);
  2363.         shadow.top = frame.bottom;
  2364.         shadow.left = frame.left + kShadowOffset;
  2365.         shadow.bottom = shadow.top + kShadowSize;
  2366.         shadow.right = frame.right + kShadowSize;
  2367.         FrameRect(&shadow);
  2368.         shadow.top = frame.top + kShadowOffset;
  2369.         shadow.left = frame.right;
  2370.         shadow.bottom = frame.bottom + kShadowSize;
  2371.         shadow.right = shadow.left + kShadowSize;
  2372.         FrameRect(&shadow);
  2373.     }
  2374.  
  2375.     /* draw the other rectangles */
  2376.  
  2377.     ForeColor(partColor.maxbounds);
  2378.     frame = (**popup).maxbounds;
  2379.     FrameRect(&frame);
  2380.  
  2381.     ForeColor(partColor.bounds);
  2382.     frame = (**popup).r.bounds;
  2383.     FrameRect(&frame);
  2384.  
  2385.     ForeColor(partColor.title);
  2386.     frame = (**popup).r.title;
  2387.     FrameRect(&frame);
  2388.  
  2389.     ForeColor(partColor.hilite);
  2390.     frame = (**popup).r.hilite;
  2391.     FrameRect(&frame);
  2392.  
  2393.     ForeColor(partColor.selection);
  2394.     frame = (**popup).r.selection;
  2395.     FrameRect(&frame);
  2396.  
  2397.     ForeColor(partColor.triangle);
  2398.     frame = (**popup).r.triangle;
  2399.     FrameRect(&frame);
  2400.     
  2401.     /* restore port */
  2402.     SetPenState(&penState);
  2403.     ForeColor(foreColor);
  2404. }
  2405.  
  2406. #endif /* DRAW_RECTANGLES */
  2407.  
  2408. /* draw the menu into the popup's port */
  2409. static void PopupDrawDirect(PopupHandle popup)
  2410. {
  2411.     PopupDrawStateType saveDrawState; /* save port settings */
  2412.     
  2413.     require(PopupValid(popup));
  2414.  
  2415.     /* setup drawing environment */
  2416.     PopupPortSetup(popup, &saveDrawState);
  2417.     
  2418.     /* erase the entire popup */
  2419.     PopupErase(popup, false);
  2420.  
  2421.     #if DRAW_POPUP
  2422.         /* draw all the parts of the popup */
  2423.         if (! (**popup).attr.typeIn)
  2424.             PopupDrawTitle(popup, false);
  2425.         PopupDrawSelection(popup);
  2426.         PopupDrawTriangle(popup);
  2427.         PopupDrawFrame(popup);
  2428.         PopupDrawEnabled(popup);
  2429.     #endif /* DRAW_POPUP */
  2430.     
  2431.     #if DRAW_RECTANGLES
  2432.         PopupDrawRectangles(popup);
  2433.     #endif /* DRAW_RECTANGLES */
  2434.  
  2435.     /* restore drawing environment */
  2436.     PopupPortRestore(popup, &saveDrawState);
  2437. }
  2438.  
  2439. /* callback for DeviceLoop function */
  2440. static pascal void PopupDrawDeviceLoopCallBack(short depth, short flags,
  2441.     GDHandle device, long data)
  2442. {
  2443.     #pragma unused(flags)
  2444.     PopupHandle popup;
  2445.     
  2446.     popup = (PopupHandle) data;
  2447.     (**popup).draw.gdevice = device;
  2448.     (**popup).draw.gdepth = depth;
  2449.     PopupDrawDirect(popup);
  2450. }
  2451.  
  2452. /* Draw the popup using a device loop. The 'drawRgn' parameter should be
  2453.     set to the region enclosing the popup's maxbound's rectangle, in
  2454.     global coordinates. For greater efficiency, the popup's draw region
  2455.     can be set to the intersection of the popup's bounding rectangle
  2456.     and its port's clip and visRgn. */
  2457. static void PopupDrawDeviceLoop(PopupHandle popup)
  2458. {
  2459.     RgnHandle drawRgn;
  2460.     
  2461.     #if GENERATINGCFM
  2462.         RoutineDescriptor drawProcRD = BUILD_ROUTINE_DESCRIPTOR(uppDeviceLoopDrawingProcInfo, PopupDrawDeviceLoopCallBack);
  2463.         DeviceLoopDrawingUPP uppDrawProc = &drawProcRD;
  2464.     #else
  2465.         DeviceLoopDrawingUPP uppDrawProc = PopupDrawDeviceLoopCallBack;
  2466.     #endif
  2467.  
  2468.     drawRgn = NewRgn();
  2469.     if (drawRgn) {
  2470.         CopyRgn((**popup).port->clipRgn, drawRgn);
  2471.         SectRgn((**popup).port->visRgn, drawRgn, drawRgn);
  2472.     }    
  2473.     if (MacHasColorQD() && drawRgn)
  2474.         DeviceLoop(drawRgn, uppDrawProc, (long) popup, 0);
  2475.     else {
  2476.         (**popup).draw.gdevice = NULL;
  2477.         PopupDrawDirect(popup);
  2478.     }
  2479.     if (drawRgn)
  2480.         DisposeRgn(drawRgn);
  2481. }
  2482.  
  2483. /* Draw the popup to an offscreen bitmap, then copy the bitmap to the screen. */
  2484. void PopupDraw(PopupHandle popup)
  2485. {
  2486.     const RGBColor whiteRGBColor = { 65535, 65535, 65535 };
  2487.     const RGBColor blackRGBColor = { 0, 0, 0 };
  2488.     RGBColor        saveForeColor;    /* saved foreground color */
  2489.     RGBColor        saveBackColor;    /* saved background color */
  2490.     CGrafPtr        saveCPort;        /* saved graphics port */
  2491.     GDHandle        saveGDH;            /* saved graphics device */
  2492.     Boolean        direct;            /* true if should draw to onscreen port */
  2493.     GWorldPtr    gworld;            /* temporary pointer to offscreen graphics world */
  2494.     GDHandle        gdevice;            /* temporary pointer to graphics device of gworld */
  2495.     short            gdepth;            /* temporary storage for depth of graphics world */
  2496.     Rect            popupRect;        /* popup's bounding rectangle */
  2497.     GWorldFlags    updateFlags;    /* flags returned by UpdateGWorld */
  2498.     
  2499.     require(PopupValid(popup));
  2500.     
  2501.     /* don't draw popup if drawing is disabled */
  2502.     if ( ! (**popup).attr.draw)
  2503.         return;
  2504.     
  2505.     /* try to draw from the offscreen graphics world */
  2506.     direct = true;
  2507.     (**popup).draw.gdepth = 0;
  2508.     (**popup).draw.gdevice = NULL;
  2509.     if ((**popup).draw.gworld) {
  2510.  
  2511.         /* use an offscreen graphics world */
  2512.         check(MacHasGWorlds());
  2513.         
  2514.         /* get the popup's bounding rectangle, in global coordinates */
  2515.         popupRect = (**popup).maxbounds;
  2516.         RectLocalToGlobal(&popupRect);
  2517.  
  2518.         /* Determine if the image spans multiple monitors with different
  2519.             pixel depths. If so, then using CopyBits to draw the image from
  2520.             an offscreen bitmap causes the image to be displayed poorly
  2521.             on the monitor with the smaller pixel depth. In particular,
  2522.             on a 1-bit screen, colors are mapped to black, and a disabled popup
  2523.             (grayed out) may not be displayed at all. While there may be some
  2524.             trick to getting offscreen graphics worlds to work well in this
  2525.             situation, for now we just use a regular device loop to draw the
  2526.             image to each screen device. */
  2527.         direct = false;
  2528.         if (MacHasColorQD()) {
  2529.             GDHandle mindevice;
  2530.             GDHandle maxdevice;
  2531.             mindevice = GetMinDevice(&popupRect);
  2532.             maxdevice = GetMaxDevice(&popupRect);
  2533.             if (    maxdevice &&
  2534.                     mindevice &&
  2535.                     mindevice != maxdevice &&
  2536.                     GetDeviceDepth(mindevice) !=
  2537.                     GetDeviceDepth(maxdevice))
  2538.             {
  2539.                 direct = true;
  2540.             }
  2541.         }
  2542.         
  2543.         if (! direct) {
  2544.         
  2545.             /* assume we'll draw directly to the screen if anything goes wrong */
  2546.             direct = true;
  2547.             
  2548.             /* According to NIM:Imaging With QuickDraw, p6-9, we only
  2549.                 need to call UpdateGWorld if the pixmap has been purged,
  2550.                 after the window has been moved, and after update events.
  2551.                 While it is simple enough to determine if the pixmap has
  2552.                 been purged or if the window has been moved, it is not
  2553.                 easy for a CDEF to determine when an update event has
  2554.                 occurred. So, we always call UpdateGWorld whenever the
  2555.                 popup is redrawn. It doesn't hurt (except for a possible
  2556.                 performance hit) to call UpdateGWorld more than necessary. */
  2557.     
  2558.             /* update the offscreen graphics world */
  2559.             gworld = (**popup).draw.gworld;
  2560.             updateFlags = UpdateGWorld(&gworld, 0, &popupRect, NULL, NULL, 0);
  2561.             if ((updateFlags & gwFlagErr) == 0) {
  2562.                 (**popup).draw.gworld = gworld;
  2563.                 
  2564.                 /* lock the offscreen pixmap */
  2565.                 if (LockPixels(GetPixMap(gworld))) {
  2566.                 
  2567.                     /* we're drawing offscreen */
  2568.                     direct = false;
  2569.                     
  2570.                     /* get the popup's bounding rectangle, in local coordinates */
  2571.                     popupRect = (**popup).maxbounds;
  2572.                     
  2573.                     /* set the gworld's origin to coincide with the popup's origin */
  2574.                     GetGWorld(&saveCPort, &saveGDH);
  2575.                     SetGWorld(gworld, NULL);
  2576.                     SetOrigin(popupRect.left, popupRect.top);
  2577.                     check(EqualRect(&gworld->portRect, &popupRect));
  2578.  
  2579.                     #if COPY_BACKPAT
  2580.                         /* copy the popup port's background pixpat to the offscreen graphics world */
  2581.                         if (GetPortBackPixPat((**popup).port)) {
  2582.                             PixPatHandle bkPixPat = GetPortBackPixPat((**popup).port);
  2583.                             if ((**bkPixPat).patType != 0)
  2584.                                 BackPixPat(bkPixPat);
  2585.                             else {
  2586.                                 Pattern bkpat = (**bkPixPat).pat1Data;
  2587.                                 BackPat(&bkpat);
  2588.                             }
  2589.                         }
  2590.                         else
  2591.                             BackPat(&(**popup).port->bkPat);
  2592.                     #endif /* COPY_BACKPAT */
  2593.                     
  2594.                     /* draw the popup to the offscreen graphics world */
  2595.                     gdevice = GetGWorldDevice(gworld);
  2596.                     gdepth = (gdevice ? GetDeviceDepth(gdevice) : 1);
  2597.                     (**popup).draw.gdevice = gdevice;
  2598.                     (**popup).draw.gdepth = gdepth;
  2599.                     ClipRect(&popupRect);
  2600.                     EraseRect(&popupRect);
  2601.                     PopupDrawDirect(popup);
  2602.                     
  2603.                     /* setup port for CopyBits */
  2604.                     SetGWorld(saveCPort, saveGDH);
  2605.                     SetPort((**popup).port);
  2606.  
  2607.                     /* use black and white to avoid colorization by CopyBits */
  2608.                     if (MacHasColorQD()) {
  2609.                         GetForeColor(&saveForeColor);
  2610.                         GetBackColor(&saveBackColor);
  2611.                         RGBForeColor(&blackRGBColor);
  2612.                         RGBBackColor(&whiteRGBColor);
  2613.                     }
  2614.                     
  2615.                     /* copy the offscreen pixmap to the onscreen port */
  2616.                     check(EqualRect(&gworld->portRect, &popupRect));
  2617.                     CopyBits(
  2618.                         &((GrafPtr) gworld)->portBits,
  2619.                         &(**popup).port->portBits,
  2620.                         &popupRect, &popupRect,
  2621.                         srcCopy, NULL);
  2622.  
  2623.                     /* restore colors */
  2624.                     RGBForeColor(&saveForeColor);
  2625.                     RGBBackColor(&saveBackColor);
  2626.                     
  2627.                     /* reset offscreen origin for better performance on next UpdateGWorld */
  2628.                     SetGWorld(gworld, NULL);
  2629.                     SetOrigin(0, 0);
  2630.                     
  2631.                     /* restore port */
  2632.                     SetGWorld(saveCPort, saveGDH);
  2633.                     
  2634.                     /* unlock the offscreen pixmap */
  2635.                     UnlockPixels(GetPixMap(gworld));
  2636.                 }
  2637.             }
  2638.         }
  2639.     }
  2640.  
  2641.     /* If we couldn't allocate an offscreen graphics world, or conditions
  2642.         weren't appropriate for drawing using the offscreen graphics world,
  2643.         we can still draw the popup directly to the onscreen port. */
  2644.     if (direct)
  2645.         PopupDrawDeviceLoop(popup);
  2646. }
  2647.  
  2648. /* hilite the popup's title; useful for keyboard equivalents of commands */
  2649. static void PopupHilite(PopupHandle popup, Boolean invert)
  2650. {
  2651.     Rect hilite;
  2652.     
  2653.     require(PopupValid(popup));
  2654.     require((**popup).state.drawSetup);
  2655.     if ((**popup).attr.draw) {
  2656.         hilite = (**popup).r.hilite;
  2657.         if (! EmptyRect(&hilite)) {
  2658.             if (PopupPortIsColor(popup))
  2659.                 PopupDrawTitle(popup, invert);
  2660.             else
  2661.                 InvertRect(&hilite);
  2662.         }
  2663.     }
  2664. }
  2665.  
  2666. /*-------------------------------------------------------------------------*/
  2667. /* handling clicks in the popup menu */
  2668. /*-------------------------------------------------------------------------*/
  2669.  
  2670. /* A handle to this structure is installed in the menu's menuProc field. */
  2671. typedef struct {
  2672.     PopupGlueUnion glue;    /* glue stuff */
  2673.     Handle proc;            /* original MDEF handle */
  2674.     short width;            /* width of menu */
  2675. } PatchMenuProcStructure, **PatchMenuProcHandle;
  2676.  
  2677. /* Menu proc to patch the mSizeMsg, allowing us to set the menu width to
  2678.     include the triangle. This is installed just before calling
  2679.     PopupMenuSelect, and is removed immediately afterwards. */
  2680. static pascal void PatchMenuProc(short message, MenuHandle menu,
  2681.     Rect *menuRect, Point hitPt, short *whichItem)
  2682. {
  2683.     PatchMenuProcHandle patch;
  2684.     SignedByte originalState;
  2685.     Handle originalProc;
  2686.     Handle saveProc;
  2687.  
  2688.     patch = (PatchMenuProcHandle) (**menu).menuProc;    
  2689.     saveProc = (**menu).menuProc;
  2690.     originalProc = (**patch).proc;
  2691.     originalState = HGetState(originalProc);
  2692.     HLock(originalProc);
  2693.     (**menu).menuProc = originalProc;
  2694.     CallMenuDefProc((MenuDefProcPtr) *originalProc, message, menu, menuRect, hitPt, whichItem);
  2695.     (**menu).menuProc = saveProc;
  2696.     if (message == mSizeMsg)
  2697.         (**menu).menuWidth = (**patch).width;
  2698.     HSetState(originalProc, originalState);
  2699. }
  2700.     
  2701. /* True if point (in local coordinates) is within the popup menu.
  2702.     Call this before calling PopupSelect. */
  2703. Boolean PopupWithin(PopupHandle popup, Point pt)
  2704. {
  2705.     Rect hilite;
  2706.     Rect selection;
  2707.     Rect maxbounds;
  2708.     Boolean result;
  2709.     
  2710.     require(PopupValid(popup));
  2711.     result = false;
  2712.     if ((**popup).attr.enabled) {
  2713.         hilite = (**popup).r.hilite;
  2714.         selection = (**popup).r.selection;
  2715.         maxbounds = (**popup).maxbounds;
  2716.         result = (PtInRect(pt, &maxbounds) &&
  2717.                      (PtInRect(pt, &selection) || PtInRect(pt, &hilite)));
  2718.     }
  2719.     return(result);
  2720. }
  2721.  
  2722. /* call this when there's a mouse down in a popup menu */
  2723. short PopupSelect(PopupHandle popup)
  2724. {
  2725.     long chosen;            /* item selected from menu */
  2726.     Point    location;        /* top left of menu */
  2727.     short oldMenuWidth;    /* saved menu width */
  2728.     Handle oldMenuProc;    /* saved menuProc */
  2729.     short menuWidth;        /* width of menu */
  2730.     PopupDrawStateType saveDrawState; /* saved port settings */
  2731.     short part;                /* part selected in control */
  2732.  
  2733.     require(PopupValid(popup));
  2734.     LMSetMenuDisable(0);
  2735.     part = kPopupPartNotSelected;
  2736.     if (StillDown()) {
  2737.     
  2738.         /* setup port */
  2739.         PopupPortSetup(popup, &saveDrawState);
  2740.         
  2741.         /* hilite title */
  2742.         PopupHilite(popup, true);
  2743.         
  2744.         /* adjust width of menu */
  2745.         CalcMenuSize((**popup).menu);
  2746.         oldMenuWidth = menuWidth = (**(**popup).menu).menuWidth;
  2747.         if (! (**popup).attr.typeIn) {
  2748.         
  2749.             /* make the menu wide enough to include the triangle */
  2750.             menuWidth += (**popup).r.triangle.right - (**popup).r.triangle.left  + kTriangleMargin;
  2751.             if ((**popup).attr.fixedWidth) {
  2752.                 /* make menu fill all of current selection rectangle */
  2753.                 menuWidth = (**popup).r.selection.right - (**popup).r.selection.left;
  2754.             }
  2755.             if (menuWidth < oldMenuWidth)
  2756.                 menuWidth = oldMenuWidth;
  2757.                 
  2758.             /* Though we patch the menu's mdef to ignore the width calculated
  2759.                 by the mSizeMsg message, we still need to explicitely set the
  2760.                 menu's width. This is necessary since PopupMenuSelect on
  2761.                 non-color macs (e.g., a Plus running system 7) doesn't send
  2762.                 the mSizeMsg message before drawing the menu, while on other
  2763.                 macs (e.g., a Quadra running system 7 Pro) the mSizeMsg is
  2764.                 sent by PopupMenuSelect. */
  2765.             (**(**popup).menu).menuWidth = menuWidth;
  2766.         }
  2767.         
  2768.         /* patch the menu's mdef to ignore the width calculated by
  2769.             the mSizeMsg message */
  2770.         oldMenuProc = (**(**popup).menu).menuProc;
  2771.         if ((**popup).menuProc) {
  2772.             PatchMenuProcHandle patch = (PatchMenuProcHandle) (**popup).menuProc;
  2773.             PopupGlueInit(&(**patch).glue, (ProcPtr) PatchMenuProc, uppMenuDefProcInfo);
  2774.             (**(**popup).menu).menuProc = (**popup).menuProc;
  2775.             (**patch).proc = oldMenuProc;
  2776.             (**patch).width = menuWidth;
  2777.         }
  2778.             
  2779.         /* calculate position for popup menu */
  2780.         location.v = (**popup).r.selection.top;
  2781.         if (PopupFontJust(popup) == teJustRight)
  2782.             location.h = (**popup).r.selection.right - menuWidth;
  2783.         else
  2784.             location.h = (**popup).r.selection.left;
  2785.         LocalToGlobal(&location);
  2786.                 
  2787.         /* let user select an item from the menu */
  2788.         chosen = PopUpMenuSelect((**popup).menu, location.v, location.h,
  2789.                                          (**popup).state.current);
  2790.  
  2791.         /* unhilite the menu */
  2792.         PopupHilite(popup, false);
  2793.  
  2794.         /* restore environment */
  2795.         (**(**popup).menu).menuProc = oldMenuProc;
  2796.         (**(**popup).menu).menuWidth = oldMenuWidth;
  2797.         PopupPortRestore(popup, &saveDrawState);
  2798.  
  2799.         /* display the selected item */
  2800.         if (HiWord(chosen)) {
  2801.             part = kPopupPartSelected;
  2802.             PopupCurrentSet(popup, LoWord(chosen));
  2803.         }
  2804.     }
  2805.     ensure(PopupValid(popup));
  2806.     return(part);
  2807. }
  2808.     
  2809. /*-------------------------------------------------------------------------*/
  2810. /* getting and setting attributes */
  2811. /*-------------------------------------------------------------------------*/
  2812.  
  2813. /* return the version of the library that created the popup menu */
  2814. short PopupVersion(PopupHandle popup)
  2815. {
  2816.     return((**popup).version);
  2817. }
  2818.  
  2819. /* return the currently selected menu item */
  2820. short PopupCurrent(PopupHandle popup)
  2821. {
  2822.     require(PopupValid(popup));
  2823.     return((**popup).state.current);
  2824. }
  2825.  
  2826. /* set the currently selected menu item */
  2827. void PopupCurrentSet(PopupHandle popup, short current)
  2828. {
  2829.     short item, nitems;
  2830.  
  2831.     require(PopupValid(popup));
  2832.     nitems = CountMItems((**popup).menu);
  2833.     for (item = 1; item <= nitems; item++)
  2834.         SetItemMark((**popup).menu, item, noMark);
  2835.     SetItemMark((**popup).menu, current, (**popup).attr.mark);
  2836.     (**popup).state.current = current;
  2837. }
  2838.  
  2839. /* turn drawing on or off */
  2840. void PopupDrawSet(PopupHandle popup, Boolean draw)
  2841. {
  2842.     require(PopupValid(popup));
  2843.     (**popup).attr.draw = draw;
  2844. }
  2845.  
  2846. /* enable or disable the menu */
  2847. void PopupEnableSet(PopupHandle popup, Boolean enabled)
  2848. {
  2849.     require(PopupValid(popup));
  2850.     (**popup).attr.enabled = enabled;
  2851. }
  2852.  
  2853. /* set the character used to mark the current menu item */
  2854. void PopupMarkSet(PopupHandle popup, char mark)
  2855. {
  2856.     require(PopupValid(popup));
  2857.     SetItemMark((**popup).menu, (**popup).state.current, mark);
  2858.     (**popup).attr.mark = mark;
  2859. }
  2860.  
  2861. /* turn type-in style popup menu on or off */
  2862. void PopupTypeInSet(PopupHandle popup, Boolean typeIn)
  2863. {
  2864.     require(PopupValid(popup));
  2865.     (**popup).attr.typeIn = typeIn;
  2866. }
  2867.  
  2868. /* turn bevel drawing on or off */
  2869. void PopupBevelSet(PopupHandle popup, Boolean bevel)
  2870. {
  2871.     require(PopupValid(popup));
  2872.     (**popup).attr.bevel = bevel;
  2873. }
  2874.  
  2875. /* return rectangle enclosing all of popup; this is the rectangle
  2876.     enclosing the parts of the popup that are actually visible, and
  2877.     may be smaller than the rectangle specified with PopupBoundsSet. */
  2878. void PopupBounds(PopupHandle popup, Rect *bounds)
  2879. {
  2880.     require(PopupValid(popup));
  2881.     *bounds = (**popup).r.bounds;
  2882.     ensure(RectValid(bounds));
  2883. }
  2884.  
  2885. /* set popup's maximum bounding rectangle; all drawing is clipped
  2886.     to this rectangle */
  2887. void PopupBoundsSet(PopupHandle popup, const Rect *maxbounds)
  2888. {
  2889.     PopupDrawStateType saveDrawState;
  2890.     Rect oldmaxbounds;
  2891.     
  2892.     require(PopupValid(popup));
  2893.     require(RectValid(maxbounds));
  2894.     oldmaxbounds = (**popup).maxbounds;
  2895.     if (! EqualRect(&oldmaxbounds, maxbounds)) {
  2896.         if ((**popup).attr.draw) {
  2897.             PopupPortSetup(popup, &saveDrawState);
  2898.             PopupErase(popup, true);
  2899.             PopupPortRestore(popup, &saveDrawState);
  2900.         }
  2901.         (**popup).maxbounds = *maxbounds;
  2902.     }
  2903. }
  2904.  
  2905. /* return popup's title string */
  2906. void PopupTitle(PopupHandle popup, Str255 title)
  2907. {
  2908.     *title = 0;
  2909.     if ((**popup).attr.title.string) {
  2910.         BlockMoveData(*(**popup).attr.title.string, title,
  2911.             **(**popup).attr.title.string + 1);
  2912.     }
  2913. }
  2914.  
  2915. /* set popup's title string */
  2916. void PopupTitleSet(PopupHandle popup, const Str255 title)
  2917. {
  2918.     Str255 oldtitle;
  2919.     
  2920.     require(PopupValid(popup));
  2921.     if ((**popup).attr.title.string) {
  2922.         PopupTitle(popup, oldtitle);
  2923.         if (! EqualString(title, oldtitle, true, true))
  2924.             PtrToXHand(title, (**popup).attr.title.string, *title + 1);
  2925.     }
  2926. }
  2927.  
  2928. /* set width of popup's title; the title is resized dynamically
  2929.     if the width is zero */
  2930. void PopupTitleWidthSet(PopupHandle popup, short width)
  2931. {
  2932.     require(PopupValid(popup));
  2933.     require(width >= 0);
  2934.     (**popup).attr.title.width = width;
  2935. }
  2936.  
  2937. /* set the text style in which the popup's title will be drawn */
  2938. void PopupTitleStyleSet(PopupHandle popup, Style style)
  2939. {
  2940.     require(PopupValid(popup));
  2941.     (**popup).attr.title.style = style;
  2942. }
  2943.  
  2944. /* set whether the popup will use the window's font for drawing the
  2945.     title, current selection, and menu */    
  2946. void PopupWindowFontSet(PopupHandle popup, Boolean wfont)
  2947. {
  2948.     require(PopupValid(popup));
  2949.     (**popup).attr.windowFont = wfont;
  2950. }
  2951.  
  2952. /* set whether the popup will use a fixed width or will be resized
  2953.     dynamically */
  2954. void PopupFixedWidthSet(PopupHandle popup, Boolean fixedWidth)
  2955. {
  2956.     require(PopupValid(popup));
  2957.     (**popup).attr.fixedWidth = fixedWidth;
  2958. }
  2959.  
  2960. /* set the justification style for drawing the popup */
  2961. void PopupJustSet(PopupHandle popup, short just)
  2962. {
  2963.     require(PopupValid(popup));
  2964.     (**popup).attr.title.just = just;
  2965. }
  2966.  
  2967. /*-------------------------------------------------------------------------*/
  2968. /* allocation and disposal */
  2969. /*-------------------------------------------------------------------------*/
  2970.  
  2971. /*    Create a popup menu within the rectangle in the specified port.
  2972.     Drawing is initially off. Since the popup's title is initially empty,
  2973.     you should call PopupTitleSet if you want the popup to have a title.
  2974.     When you are finished configuring the popup, call PopupDrawSet to
  2975.     enable drawing and then call PopupCalculate. The popup's rectangles
  2976.     are only calculated when drawing is enabled. The popup menu allocates
  2977.     several utility handles, but will function, albeit not as well, even
  2978.     if it can't allocate any of the utility handles. To reduce flicker,
  2979.     the popup menu is drawn to an offscreen graphics world and then copied
  2980.     to the screen. The storage for the offscreen pixmap is kept in a
  2981.     relocatable and purgeable block. */
  2982. PopupHandle PopupBegin(GrafPtr port,
  2983.     MenuHandle menu,
  2984.     const Rect *maxbounds,
  2985.     ControlHandle ctl)
  2986. {
  2987.     PopupHandle popup;
  2988.     void *tmp;
  2989.  
  2990.     require(port != NULL);    
  2991.     require(menu != NULL);
  2992.     require(ctl != NULL);
  2993.     require(RectValid(maxbounds));
  2994.     
  2995.     /* allocate popup */
  2996.     popup = (PopupHandle) NewHandleClear(sizeof(PopupType));
  2997.     if (popup) {
  2998.     
  2999.         /* initialize internal state */
  3000.         (**popup).privateData.mHandle = menu;
  3001.         (**popup).privateData.mID = (**menu).menuID;
  3002.         (**popup).version = kPopupVersion;
  3003.         (**popup).port = port;
  3004.         (**popup).menu = menu;
  3005.         (**popup).ctl = ctl;
  3006.         (**popup).maxbounds = *maxbounds;
  3007.         (**popup).attr.bevel = (DRAW_BEVEL != 0);
  3008.  
  3009.         /* initialize flags affecting display and operation of menu */
  3010.         (**popup).attr.enabled = true;
  3011.         (**popup).attr.mark = checkMark;
  3012.         #if FLUSH_RIGHT
  3013.             (**popup).attr.title.just = teFlushRight;
  3014.         #else
  3015.             (**popup).attr.title.just = PopupFontJust(popup);
  3016.         #endif
  3017.         
  3018.         /* allocate title */
  3019.         tmp = NewHandleClear(1);
  3020.         (**popup).attr.title.string = tmp;
  3021.         
  3022.         /* allocate glue handle for patching MDEF */
  3023.         tmp = NewHandle(sizeof(PatchMenuProcStructure));
  3024.         if (tmp)
  3025.             (**popup).menuProc = tmp;
  3026.     
  3027.         /* Allocate an offscreen graphics world. The offscreen pixmap is
  3028.             purgeable, since we can always rebuild the data it contains and
  3029.             since we can always draw the popup to the onscreen port, though
  3030.             the image may flicker a bit. */
  3031.         #if DRAW_OFFSCREEN
  3032.             if (MacHasGWorlds()) {
  3033.                 Rect globalBounds;
  3034.                 GWorldPtr gworld;
  3035.                 GrafPtr savePort;
  3036.                 GetPort(&savePort);
  3037.                 SetPort((**popup).port);
  3038.                 globalBounds = (**popup).maxbounds;
  3039.                 RectLocalToGlobal(&globalBounds);
  3040.                 if (NewGWorld(&gworld, 0, &globalBounds, NULL, NULL, pixPurge) == noErr)
  3041.                     (**popup).draw.gworld = gworld;
  3042.                 SetPort(savePort);
  3043.             }
  3044.         #endif /* DRAW_OFFSCREEN */
  3045.     }
  3046.     ensure(! popup || PopupValid(popup));
  3047.     return(popup);
  3048. }
  3049.  
  3050. /* dispose of the popup menu */
  3051. void PopupEnd(PopupHandle popup)
  3052. {
  3053.     require(! popup || PopupValid(popup));
  3054.     if (popup) {
  3055.         if ((**popup).menuProc) DisposeHandle((**popup).menuProc);
  3056.         if ((**popup).draw.gworld) DisposeGWorld((**popup).draw.gworld);
  3057.         if ((**popup).attr.title.string) DisposeHandle((**popup).attr.title.string);
  3058.         DisposeHandle((Handle) popup);
  3059.         popup = NULL;
  3060.     }
  3061.     ensure(! PopupValid(popup));
  3062. }
  3063.