home *** CD-ROM | disk | FTP | other *** search
/ MacFormat 1995 January / macformat-020.iso / Shareware City / Developers / PopupCDEF-10b4 / Source / PopupLib.c < prev    next >
Encoding:
Text File  |  1994-10-01  |  68.6 KB  |  2,281 lines  |  [TEXT/KAHL]

  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.     94/09/08 aih
  28.     - Fixed disabling of items > 15.
  29.     
  30.     94/07/16 aih
  31.     - Fixed almost all color problems.
  32.     - Improved right justification of menus, depending on system
  33.     justification value.
  34.     
  35.     94/07/12 aih
  36.     - Adapted for color QuickDraw and offscreen graphics worlds.
  37.     - Added support for color menu entries.
  38.     
  39.     94/07/06 aih
  40.     - Adapted for universal headers
  41.     
  42.     94/03/15 aih
  43.     - The current selection will display items with small icons properly.
  44.     Formerly, this would display a large icon for items that had small or
  45.     reduced icons, and wouldn't display the large icon once a different icon
  46.     had been selected. Reduced icons still aren't displayed in the current
  47.     selection, and I'm not sure why not, since I copy the command key character
  48.     for reduced icons (0x1D) to the menu item used to draw the current
  49.     selection.
  50.     - When the useWFont variation code is used, the menu will be drawn in
  51.     the font and size of the popup menu's port. Formerly, using the useWFont
  52.     variation code produced incorrect results, but I fixed this by setting the
  53.     system font and size low-memory globals instead of trying to change the
  54.     font and size of the Window Manager's port.
  55.     - The currently selected item is drawn in gray if it is disabled. Formerly,
  56.     the item was only drawn in gray if it was disabled and if the menu item
  57.     was drawn using the MDEF.
  58.     - Updated "To Do" list.
  59.     - Clicking in the popup's title will also pull down the menu. This
  60.     is consistent with the operation of the system 7 popup CDEF.
  61.     - Added support for the popupFixedWidth variation code.
  62.     
  63.     93/12/28 aih
  64.     - Continued cleaning up code, added some more comments.
  65.     - Uses menu definition function to draw the current selection. This
  66.     eliminated a couple of rectangles from the popup structure and
  67.     means that the current selection will always be drawn correctly,
  68.     even if it includes icons and command keys. A special one item menu
  69.     is used to hold a copy of the currently selected menu item. This special
  70.     menu is then used to calculate the height of the item and to draw the
  71.     item.
  72.     
  73.     93/12/26 aih
  74.     - Major overhaul. Rewrote rectangle calculation code, since it wasn't
  75.     working right. It's still not as simple as I'd like, but it's better
  76.     than before. Also added attributes to make more compatible with Apple's
  77.     popup CDEF (such as using the window font to display the menu).
  78.     
  79.     93/12/24 aih
  80.     - Converted to use handles.
  81.     - Removed dependence on all other libraries, so that this library
  82.     is completely self contained; the few external functions that were
  83.     necessary (e.g., strcpy, TruncatePString) were copied or coded directly into
  84.     this file. This will make this library more suitable for use as a
  85.     CDEF since the linker won't pull in a lot of extra code from other
  86.     libraries.
  87.     
  88.     93/12/23 aih
  89.     - updated for current version of libraries
  90.     - added port parameter to PopupBegin
  91.     
  92.     91/11/11 aih
  93.     - Removed useless "PopupObj..." stuff
  94.     
  95.     91/03/01-05 aih
  96.     - Update events are handled
  97.     - Added comment describing this file
  98.     - The popup's title is allocated as a handle in the heap
  99.     - The popup is allocated as a handle in the heap
  100.     - Attribute values are only updated if nescessary
  101.     - The function PopupDraw first draws the popup to an offscreen bitmap
  102.     and then copies it to the popup's port. This eliminates flicker when
  103.     the popup is used as a CDEF, since the Control Manager sends a draw
  104.     message to a control whenever the control is clicked, even if no
  105.     drawing is actually needed.
  106.     
  107.     91/01/05 aih
  108.     - Inserted this standard header in all files
  109.  
  110.     90/12/15 Ari Halberstadt (aih)
  111.     - Created this file */
  112.  
  113. #include <Fonts.h>
  114. #include <GestaltEqu.h>
  115. #include <Icons.h>
  116. #include <Memory.h>
  117. #include <Palettes.h>
  118. #include <Resources.h>
  119. #include <Script.h>
  120. #include <TextEdit.h>
  121. #include <TextUtils.h>
  122. #include <ToolUtils.h>
  123. #include "PopupLib.h"
  124. #include "PopupLM.h"
  125.  
  126. /*---------------------------------------------------------------------------*/
  127. /* constants */
  128. /*---------------------------------------------------------------------------*/
  129.  
  130. #ifndef NDEBUG
  131.     
  132.     /* Define DRAW_POPUP as 1 to enable drawing of the various parts of the
  133.         the menu. You will normally want this to be enabled, but, while
  134.         debugging, you may want to see only the outlines of the parts of
  135.         the menu. To do so, you can set DRAW_POPUP to 0 and DRAW_RECTANGLES
  136.         to 1. */
  137.     #define DRAW_POPUP                (1)
  138.  
  139.     /* To reduce flicker the popup is normally first drawn to an off-screen
  140.         bitmap, then copied to the screen. To make debugging easier disable
  141.         offscreen drawing. Then you can step through each draw routine to
  142.         ensure that it is drawing correctly. */
  143.     #define DRAW_OFFSCREEN            (1)
  144.     
  145.     /* Define DRAW_RECTANGLES as 1 to enable framing of the various rectangles
  146.         of the menu. This is useful when debugging since it clearly displays the
  147.         rectangles, which otherwise can be tricky to calculate. */
  148.     #define DRAW_RECTANGLES            (0)
  149.  
  150.     /*    To test right-justified menus without using a right-justified
  151.         script system, define TEST_FLUSH_RIGHT as 1. */
  152.     #define TEST_FLUSH_RIGHT        (0)
  153.     
  154.     /* Another useful debugging trick is to define the following as something
  155.         greater than 1. This helps spot and fix off-by-one errors caused by
  156.         not taking into account the size of the frame and drop shadow. 
  157.         A value of at least 1/4" (18 pixels) provides good visual feedback
  158.         and allows using a ruler to get rough measurements of areas. */
  159.     #define kFrameSize                (1)    /* width of frame around popup */
  160.     #define kShadowSize                (1)    /* width of shadow around popup */
  161.     
  162. #else /* NDEBUG */
  163.  
  164.     #define DRAW_POPUP                (1)    /* if 1, draws the popup menu */
  165.     #define DRAW_OFFSCREEN            (1)    /* if 1, uses offscreen bitmap */
  166.     #define DRAW_RECTANGLES            (0)    /* if 1, draws frames around areas */
  167.     #define TEST_FLUSH_RIGHT        (0)    /* if 1, menus are always right justified */
  168.     #define kFrameSize                (1)    /* width of frame around popup */
  169.     #define kShadowSize                (1)    /* size of shadow around popup */
  170.     
  171. #endif /* NDEBUG */
  172.  
  173. #define kEllipses                        '…'    /* character appended to long strings */
  174. #define kShadowOffset                (3)    /* amount to offset shadow from frame */
  175. #define kArrowWidth                    (11)    /* width of the down arrow */
  176. #define kArrowHeight                    (6)    /* height of the down arrow */
  177. #define kIconMarginH                    (4)    /* horizontal margin around icon */
  178. #define kIconMarginV                    (2)    /* vertical margin around icon */
  179. #define kArrowMargin                    (5)    /* margin around arrow */
  180. #define kTitleMargin                    (5)    /* margin around title */
  181. #define kTitleMarginBottom            (1)    /* margin below title */
  182.  
  183. /* special values for the command key field of a menu item */
  184. enum {
  185.     kCmdHier = hMenuCmd,
  186.     kCmdScript,
  187.     kCmdIconReduced,
  188.     kCmdIconSmall,
  189.     kCmdReserved
  190. };
  191.  
  192. #ifdef OLD_HEADERS
  193.     /* things defined in <Icons.h> for the icon utilities */
  194.     typedef short IconTransformType;
  195.     typedef short IconAlignmentType;
  196.     enum { ttNone = 0, atNone = 0, ttDisabled = 1 };
  197.     pascal OSErr PlotCIconHandle(const Rect *theRect,
  198.                                 IconAlignmentType align,
  199.                                 IconTransformType transform,
  200.                                 CIconHandle theCIcon)
  201.         = {0x303C, 0x061F, 0xABC9};
  202. #endif
  203.  
  204. /*---------------------------------------------------------------------------*/
  205. /* assertions */
  206. /*---------------------------------------------------------------------------*/
  207.  
  208. #define require(x)                    ((void) 0)
  209. #define ensure(x)                        ((void) 0)
  210. #define check(x)                        ((void) 0)
  211.  
  212. /*---------------------------------------------------------------------------*/
  213. /* math utilities */
  214. /*---------------------------------------------------------------------------*/
  215.  
  216. /* return minimum of two signed long integers */
  217. static long min(long a, long b)
  218. {
  219.     return(a < b ? a : b);
  220. }
  221.  
  222. /* return maximum of two signed long integers */
  223. static long max(long a, long b)
  224. {
  225.     return(a > b ? a : b);
  226. }
  227.  
  228. /*---------------------------------------------------------------------------*/
  229. /* gestalt utilities */
  230. /*---------------------------------------------------------------------------*/
  231.  
  232. /* return the version of the Macintosh system software */
  233. static short MacVersion(void)
  234. {
  235.     OSErr err;
  236.     long response;
  237.     
  238.     err = Gestalt(gestaltSystemVersion, &response);
  239.     return(! err ? LoWord(response) : 0);
  240. }
  241.     
  242. /* return the version of QuickDraw */
  243. static short MacVersionQD(void)
  244. {
  245.     OSErr err;
  246.     long response;
  247.     
  248.     err = Gestalt(gestaltQuickdrawVersion, &response);
  249.     return(! err ? LoWord(response) : 0);
  250. }
  251.  
  252. /* true if the Macintosh supports color QuickDraw */
  253. static Boolean MacHasColorQD(void)
  254. {
  255.     return(MacVersionQD() >= gestalt8BitQD);
  256. }
  257.  
  258. /* true if the Macintosh supports offscreen graphics worlds */
  259. static Boolean MacHasGWorlds(void)
  260. {
  261.     /*    GWorlds are supported if (MacVersionQD() >= gestalt32BitQD) but
  262.         they behave sufficiently differently in systems before 7.0 that
  263.         it's difficult to get them to work with both pre-7.0 and post-7.0
  264.         systems, so we just support GWorlds in post-7.0 systems. */
  265.     return(MacVersion() >= 0x0700);
  266. }
  267.  
  268. /* true if the Macintosh has the icon utilities */
  269. static Boolean MacHasIconUtilities(void)
  270. {
  271.     OSErr err;
  272.     long response;
  273.     
  274.     // program_note: gestalt doesn't recognize the gestaltIconUtilitiesAttr,
  275.     // even though the icon utilities are available (this is with system 7 pro),
  276.     // so we just test for system 7.0
  277.     return(MacVersion() >= 0x0700);
  278.     //err = Gestalt(gestaltIconUtilitiesAttr, &response);
  279.     //return(! err ? (response & (1<<gestaltIconUtilitiesPresent)) != 0 : false);
  280. }
  281.  
  282. /*---------------------------------------------------------------------------*/
  283. /* graphics utilities */
  284. /*---------------------------------------------------------------------------*/
  285.  
  286. /* return the pixel depth of the pixel map */
  287. static short GetPixMapDepth(PixMapHandle pixmap)
  288. {
  289.     return((**pixmap).pixelSize);
  290. }
  291.  
  292. /* return the pixel depth of the port */
  293. static short GetPortDepth(CGrafPtr port)
  294. {
  295.     short depth;
  296.     
  297.     depth = 1;
  298.     if ((port->portVersion & 0xC000) == 0xC000)
  299.         depth = GetPixMapDepth(port->portPixMap);
  300.     return(depth);
  301. }
  302.  
  303. /* return the pixel depth of the graphics device */
  304. static short GetDeviceDepth(GDHandle device)
  305. {
  306.     return(GetPixMapDepth((**device).gdPMap));
  307. }
  308.  
  309. /* Return a handle to the video device with the smallest pixel depth
  310.     that intersects the specified rectangle (given in global coordinates).
  311.     If no video device intersects the specified rectangle, then NULL is
  312.     returned. */
  313. static GDHandle GetMinDevice(const Rect *globalRect)
  314. {
  315.     GDHandle mindevice;
  316.     GDHandle curdevice;
  317.     Rect deviceRect;
  318.  
  319.     mindevice = NULL;
  320.     curdevice = GetDeviceList();
  321.     while (curdevice) {
  322.         if (TestDeviceAttribute(curdevice, screenDevice) &&
  323.              TestDeviceAttribute(curdevice, screenActive))
  324.         {
  325.             deviceRect = (**curdevice).gdRect;
  326.             if (SectRect(&deviceRect, globalRect, &deviceRect)) {
  327.                 if (! mindevice ||
  328.                      GetDeviceDepth(curdevice) <
  329.                      GetDeviceDepth(mindevice))
  330.                 {
  331.                     mindevice = curdevice;
  332.                 }
  333.             }
  334.         }
  335.         curdevice = GetNextDevice(curdevice);
  336.     }
  337.     return(mindevice);
  338. }
  339.  
  340. /* return the pixel map of the graphics world */
  341. static PixMapHandle GetPixMap(GWorldPtr gworld)
  342. {
  343.     return(MacVersion() >= 0x0700 ? GetGWorldPixMap(gworld) : gworld->portPixMap);
  344. }
  345.  
  346. /* get the text attributes of the current port */
  347. static void GetTextState(TextState *text)
  348. {
  349.     GrafPtr port;
  350.     
  351.     GetPort(&port);
  352.     text->font = port->txFont;
  353.     text->face = port->txFace;
  354.     text->mode = port->txMode;
  355.     text->size = port->txSize;
  356. }
  357.  
  358. /* set the text attributes of the current port */
  359. static void SetTextState(const TextState *text)
  360. {
  361.     TextFont(text->font);
  362.     TextFace(text->face);
  363.     TextMode(text->mode);
  364.     TextSize(text->size);
  365. }
  366.  
  367. /* set the text attributes of the current port to their defaults */
  368. static void TextNormal(void)
  369. {
  370.     TextFont(0);
  371.     TextFace(0);
  372.     TextSize(0);
  373.     TextMode(srcOr);
  374. }
  375.  
  376. /* get the color attributes of the current port */
  377. static void GetColorState(ColorState *color)
  378. {
  379.     if (MacHasColorQD()) {
  380.         GetForeColor(&color->fore);
  381.         GetBackColor(&color->back);
  382.     }
  383. }
  384.  
  385. /* set the color attributes of the current port */
  386. static void SetColorState(const ColorState *color)
  387. {
  388.     if (MacHasColorQD()) {
  389.         RGBForeColor(&color->fore);
  390.         RGBBackColor(&color->back);
  391.     }
  392. }
  393.  
  394. /*---------------------------------------------------------------------------*/
  395. /* rectangle utilities */
  396. /*---------------------------------------------------------------------------*/
  397.  
  398. /*    Convert the rectangle to global coordinates. */
  399. static void RectLocalToGlobal(Rect *r)
  400. {
  401.     Point origin = { 0, 0 };
  402.     
  403.     LocalToGlobal(&origin);
  404.     OffsetRect(r, origin.h, origin.v);
  405. }
  406.  
  407. /* Flip the rectangle 'flip' horizontally (mirror image) relative to
  408.     the rectangle 'within'. */    
  409. static void RectFlip(Rect *flip, const Rect *within)
  410. {
  411.     short offset;
  412.     
  413.     require(RectValid(flip));
  414.     require(RectValid(within));
  415.     require(within->left <= flip->left && flip->right <= within->right);
  416.     offset = (within->right - flip->right) - (flip->left - within->left);
  417.     flip->left += offset;
  418.     flip->right += offset;
  419. }
  420.  
  421. /* paint over the rectangle with the gray pattern */
  422. static void RectDisable(const Rect *r)
  423. {
  424.     PenState pen;
  425.     Pattern pat;
  426.  
  427.     require(RectValid(r));
  428.     GetPenState(&pen);
  429.     PenMode(patBic);
  430.     #ifdef OLD_HEADERS
  431.         GetIndPattern(pat, sysPatListID, 4);
  432.         PenPat(pat);
  433.     #else
  434.         GetIndPattern(&pat, sysPatListID, 4);
  435.         PenPat(&pat);
  436.     #endif
  437.     PaintRect(r);
  438.     SetPenState(&pen);
  439. }
  440.  
  441. /*---------------------------------------------------------------------------*/
  442. /* icon utilities */
  443. /*---------------------------------------------------------------------------*/
  444.  
  445. /*    PlotSICN draws a small black and white icon from a resource of type
  446.     'SICN' that defines a 16 by 16 bit image, in the specified rectangle.
  447.     This is similar to the ToolBox routine PlotIcon. */
  448. static void PlotSICN(const Rect *bounds, Handle sicn)
  449. {
  450.     SignedByte state;
  451.     BitMap sicnBits;
  452.     GrafPtr port;
  453.     
  454.     state = HGetState(sicn);
  455.     MoveHHi(sicn);
  456.     HLock(sicn);
  457.     GetPort(&port);
  458.     sicnBits.rowBytes = 2;
  459.     sicnBits.baseAddr = *sicn;
  460.     sicnBits.bounds = *bounds;
  461.     CopyBits(&sicnBits, &port->portBits, bounds, bounds, srcCopy, NULL);
  462.     HSetState(sicn, state);
  463. }
  464.  
  465. /*---------------------------------------------------------------------------*/
  466. /* string utilities */
  467. /*---------------------------------------------------------------------------*/
  468.  
  469. /* fit string to width by truncating it and adding an extra character,
  470.     return width of string */
  471. static short TruncatePString(Str255 str, short maxwidth)
  472. {
  473.     const char extra = kEllipses;
  474.     short extrawidth;
  475.     short width;
  476.  
  477.     require(maxwidth >= 0);
  478.     if (MacVersion() >= 0x0700) {
  479.         (void) TruncString(maxwidth, str, truncEnd);
  480.         width = StringWidth(str);
  481.     }
  482.     else {
  483.         width = StringWidth(str);
  484.         if (width > maxwidth) {
  485.             if (maxwidth == 0) {
  486.                 /* optimization */
  487.                 width = 0;
  488.                 *str = 0;
  489.             }
  490.             else {
  491.                 /* truncate one character (or multi-byte character)
  492.                     at a time */
  493.                 while (*str > 0) {
  494.                     str[*str] = extra;
  495.                     width = StringWidth(str);
  496.                     if (width <= maxwidth)
  497.                         break;
  498.                     while (CharByte((Ptr) str, *str) > 0) {
  499.                         check(*str > 1);
  500.                         --*str;
  501.                     }
  502.                     check(*str > 0);
  503.                     --*str;
  504.                 }
  505.             }
  506.         }
  507.     }
  508.     ensure(width <= maxwidth);
  509.     ensure(width == StringWidth(str));
  510.     return(width);
  511. }
  512.  
  513. /*---------------------------------------------------------------------------*/
  514. /* menu utilities */
  515. /*---------------------------------------------------------------------------*/
  516.  
  517. /* true if the menu item is enabled */
  518. static Boolean MenuItemEnabled(MenuHandle menu, short item)
  519. {
  520.     unsigned long flags;
  521.     
  522.     require(0 <= item && item <= CountMItems(menu));
  523.     flags = (**menu).enableFlags;
  524.     return((flags & 1) != 0 && (item > 31 || (flags & (1UL << item)) != 0));
  525. }
  526.  
  527. /* get info about the icon for the specified menu item */
  528. static void MenuItemIcon(MenuHandle menu, short item,
  529.     ResType *iconType, short *iconID, Point *iconSize)
  530. {
  531.     short itemCmd;
  532.     short itemIcon;
  533.     Rect iconRect;
  534.     Handle iconRsrc;
  535.     
  536.     *iconID = 0;
  537.     *iconType = 0;
  538.     iconSize->h = iconSize->v = 0;
  539.     if (item > 0) {
  540.         GetItemCmd(menu, item, &itemCmd);
  541.         GetItemIcon(menu, item, &itemIcon);
  542.         if (itemIcon && itemCmd != kCmdScript) {
  543.             *iconID = itemIcon + 256;
  544.             if (itemCmd == kCmdIconReduced)
  545.                 iconSize->h = iconSize->v = 16;
  546.             if (MacHasColorQD() && GetResource('cicn', *iconID)) {
  547.                 *iconType = 'cicn';
  548.                 iconRsrc = GetResource('cicn', *iconID);
  549.                 if (iconRsrc && *iconRsrc && itemCmd != kCmdIconReduced) {
  550.                     /* get icon's size from its pixmap */
  551.                     iconRect = (**(CIconHandle) iconRsrc).iconPMap.bounds;
  552.                     iconSize->h = iconRect.right - iconRect.left;
  553.                     iconSize->v = iconRect.bottom - iconRect.top;
  554.                 }
  555.             }
  556.             else if (itemCmd == kCmdIconSmall) {
  557.                 *iconType = 'SICN';
  558.                 iconSize->h = iconSize->v = 16;
  559.             }
  560.             else {
  561.                 *iconType = 'ICON';
  562.                 if (itemCmd != kCmdIconReduced)
  563.                     iconSize->h = iconSize->v = 32;
  564.             }
  565.         }
  566.     }
  567. }
  568.  
  569. /* returns the foreground and background colors for the menu item */
  570. static Boolean MenuItemColor(MenuHandle menu, short item,
  571.     RGBColor *foreColor, RGBColor *backColor)
  572. {
  573.     MCEntryPtr mcentry;    /* pointer to menu item's color entry */
  574.     RGBColor grayColor;    /* for graying item if it's disabled */
  575.     Boolean color;            /* true if the item has a color */
  576.     
  577.     color = false;
  578.     mcentry = GetMCEntry((**menu).menuID, item);
  579.     if (item) {
  580.         if (mcentry) {
  581.             /* use item's color entry */
  582.             *foreColor = mcentry->mctRGB2;
  583.             *backColor = mcentry->mctRGB4;
  584.             color = true;
  585.         }
  586.         else {
  587.             /* use default color entry */
  588.             mcentry = GetMCEntry((**menu).menuID, 0);
  589.             if (mcentry) {
  590.                 *foreColor = mcentry->mctRGB3;
  591.                 *backColor = mcentry->mctRGB4;
  592.                 color = true;
  593.             }
  594.         }
  595.     }
  596.     else if (mcentry) {
  597.         /* use title's color entry */
  598.         *foreColor = mcentry->mctRGB1;
  599.         *backColor = mcentry->mctRGB4;
  600.         color = true;
  601.     }
  602.     return(color);
  603. }
  604.  
  605. /*---------------------------------------------------------------------------*/
  606. /* popup menu routines */
  607. /*---------------------------------------------------------------------------*/
  608.  
  609. /* true if the popup is valid */
  610. Boolean PopupValid(PopupHandle popup)
  611. {
  612.     return((**popup).version == kPopupVersion);
  613. }
  614.  
  615. /*---------------------------------------------------------------------------*/
  616. /* port setup and restore */
  617. /*---------------------------------------------------------------------------*/
  618.  
  619. /* Return the alignment for the font in which the popup menu is drawn. */
  620. static short PopupJust(PopupHandle popup)
  621. {
  622.     GrafPtr savePort;
  623.     short just;
  624.     
  625.     if ((**popup).attr.wfont) {
  626.         GetPort(&savePort);
  627.         SetPort((**popup).port);
  628.         just = GetScript(FontScript(), smScriptJust);
  629.         SetPort(savePort);
  630.     }
  631.     else
  632.         just = GetSysJust();
  633.     return(just);
  634. }
  635.  
  636. /* The menu definition function gets the font size in which to draw the
  637.     popup menu from the font size of the window manager port. So, to
  638.     set the system font, in addition to setting a low-memory global,
  639.     we also have to set the font size for the window manager port. */
  640. static void SetSysFontSize(short size)
  641. {
  642.     GrafPtr svport;
  643.     GrafPtr wmgrPort;
  644.     
  645.     GetPort(&svport);
  646.     GetWMgrPort(&wmgrPort);
  647.     SetPort(wmgrPort);
  648.     TextSize(size);
  649.     SetPort(svport);
  650. }
  651.  
  652. /* Returns the pixel depth of the port or device to which the popup is
  653.     being drawn. */
  654. static short PopupPixelDepth(PopupHandle popup)
  655. {
  656.     short depth;
  657.     
  658.     depth = GetPortDepth((CGrafPtr) (**popup).port);
  659.     if ((**popup).draw.gdepth)
  660.         depth = min(depth, (**popup).draw.gdepth);
  661.     return(depth);
  662. }
  663.  
  664. /* Remember the current drawing environment of the system font and size.
  665.     Must be balanced with a call to PopupPortRestoreSystem. */
  666. static void PopupPortSetupSystem(PopupHandle popup)
  667. {
  668.     /* If using the window's font, then set the system font and size
  669.         to the font and size of the popup's port. */
  670.     if ((**popup).attr.wfont) {
  671.         (**popup).draw.save.sysfont = LMGetSysFontFam();
  672.         (**popup).draw.save.syssize = LMGetSysFontSize();
  673.         LMSetSysFontFam((**popup).port->txFont);
  674.         LMSetSysFontSize((**popup).port->txSize);
  675.         SetSysFontSize((**popup).port->txSize);
  676.     }
  677. }
  678.  
  679. /* Restore the current drawing environment of the system font and size. */
  680. static void PopupPortRestoreSystem(PopupHandle popup)
  681. {
  682.     if ((**popup).attr.wfont) {
  683.         LMSetSysFontFam((**popup).draw.save.sysfont);
  684.         LMSetSysFontSize((**popup).draw.save.syssize);    
  685.         SetSysFontSize((**popup).draw.save.syssize);
  686.     }
  687. }
  688.  
  689. /* Remember the current drawing environment and set the drawing environment 
  690.     to that needed by the popup menu. Must be balanced with a call
  691.     to PopupPortRestore. */
  692. static void PopupPortSetup(PopupHandle popup)
  693. {
  694.     GrafPtr port;
  695.     PenState pen;
  696.     TextState text;
  697.     ColorState color;
  698.     
  699.     require(! (**popup).state.drawset);    
  700.     
  701.     #if TEST_FLUSH_RIGHT
  702.         SetSysJust(teFlushRight);
  703.     #endif
  704.  
  705.     /* remember current port */
  706.     GetPort(&port);
  707.     (**popup).draw.save.port = port;
  708.  
  709.     /* save settings for popup's port */
  710.     SetPort((**popup).port);
  711.     GetPenState(&pen);
  712.     GetTextState(&text);
  713.     GetColorState(&color);
  714.     (**popup).draw.save.pen = pen;
  715.     (**popup).draw.save.text = text;
  716.     (**popup).draw.save.color = color;
  717.     if ((**popup).draw.gworld && port == (GrafPtr) (**popup).draw.gworld)
  718.         SetGWorld((**popup).draw.gworld, NULL);
  719.     
  720.     /* reset text settings to default system settings */
  721.     TextNormal();
  722.     
  723.     /* if using the window's font, use font and font size of popup's port */
  724.     if ((**popup).attr.wfont) {
  725.         TextFont(text.font);
  726.         TextSize(text.size);
  727.     }
  728.     
  729.     /* save clip region and clip to the popup's maximum bounding rectangle */
  730.     if ((**popup).draw.save.clip) {
  731.         Rect maxbounds = (**popup).r.maxbounds;
  732.         GetClip((**popup).draw.save.clip);
  733.         ClipRect(&maxbounds);
  734.     }
  735.         
  736.     (**popup).state.drawset = true;
  737.     ensure((**popup).state.drawset);
  738. }
  739.  
  740. /* Restore the drawing environment to its original state. */
  741. static void PopupPortRestore(PopupHandle popup)
  742. {
  743.     GrafPtr port;
  744.     PenState pen;
  745.     TextState text;
  746.     ColorState color;
  747.     GrafPtr wmgrPort;
  748.     TextState wmgrText;
  749.  
  750.     #if TEST_FLUSH_RIGHT
  751.         SetSysJust(0);
  752.     #endif
  753.  
  754.     require((**popup).state.drawset);
  755.     port = (**popup).draw.save.port;
  756.     pen = (**popup).draw.save.pen;
  757.     text = (**popup).draw.save.text;
  758.     color = (**popup).draw.save.color;
  759.     if ((**popup).draw.save.clip) {
  760.         SetClip((**popup).draw.save.clip);
  761.         SetEmptyRgn((**popup).draw.save.clip);
  762.     }
  763.     SetColorState(&color);
  764.     SetTextState(&text);
  765.     SetPenState(&pen);
  766.     SetPort(port);    
  767.     (**popup).state.drawset = false;    
  768.     ensure(! (**popup).state.drawset);
  769. }
  770.  
  771. /* structure for saving the drawing environment when drawing the current
  772.     selection */
  773. typedef struct {
  774.     TextState textState;        /* saved text style */
  775.     ColorState colorState;    /* saved color state */
  776. } PopupDrawSelectionStructure;
  777.  
  778. /*    Setup the drawing environment for drawing the current selection string. */
  779. static void PopupDrawSelectionSetup(PopupHandle popup,
  780.     PopupDrawSelectionStructure *save)
  781. {
  782.     Rect selection;            /* current selection rectangle */
  783.     Style itemStyle;            /* menu item's style */
  784.     short itemCmd;                /* item's command character */
  785.     short itemScript;            /* item's script code */
  786.     long itemFondSize;        /* item's font ID and size */
  787.  
  788.     /* save the text and color states */
  789.     GetTextState(&save->textState);
  790.     GetColorState(&save->colorState);
  791.     
  792.     /* set the font script of the menu item */
  793.     if (! (**popup).attr.wfont) {
  794.         GetItemCmd((**popup).menu, (**popup).state.current, &itemCmd);
  795.         if (itemCmd == kCmdScript) {
  796.             GetItemIcon((**popup).menu, (**popup).state.current, &itemScript);
  797.             if (GetScript(itemScript, smScriptEnabled)) {
  798.                 itemFondSize = GetScript(itemScript, smScriptSysFondSize);
  799.                 TextFont(HiWord(itemFondSize));
  800.                 TextSize(LoWord(itemFondSize));
  801.             }
  802.         }
  803.     }
  804.     
  805.     /* set the text style of the menu item */
  806.     GetItemStyle((**popup).menu, (**popup).state.current, &itemStyle);
  807.     TextFace(itemStyle);
  808.     
  809.     /* draw the text in gray if the control or the item is disabled */
  810.     if (PopupPixelDepth(popup) > 1 &&
  811.          (! (**popup).attr.enabled ||
  812.           ! MenuItemEnabled((**popup).menu, (**popup).state.current)))
  813.     {
  814.         TextMode(grayishTextOr);
  815.     }
  816. }
  817.  
  818. /* restore the drawing environment after drawing the current selection */
  819. static void PopupDrawSelectionRestore(PopupHandle popup,
  820.     const PopupDrawSelectionStructure *save)
  821. {
  822.     SetTextState(&save->textState);
  823.     SetColorState(&save->colorState);
  824. }
  825.  
  826. /*---------------------------------------------------------------------------*/
  827. /* rectangle calculation routines */
  828. /*---------------------------------------------------------------------------*/
  829.  
  830. /* set popup's rectangles and erase old popup if the frame has changed */
  831. static PopupRectanglesSet(PopupHandle popup, const PopupRectanglesType *r)
  832. {
  833.     Rect bounds;
  834.     RgnHandle eraseRgn;
  835.     
  836.     bounds = (**popup).r.bounds;
  837.     if (! EqualRect(&bounds, &r->bounds)) {
  838.         eraseRgn = NewRgn();
  839.         if (eraseRgn && (**popup).draw.utilRgn) {
  840.             /* to reduce flicker only erase the
  841.                 area that doesn't overlap */
  842.             check(EmptyRgn((**popup).draw.utilRgn));
  843.             RectRgn(eraseRgn, &bounds);
  844.             RectRgn((**popup).draw.utilRgn, &r->bounds);
  845.             DiffRgn(eraseRgn, (**popup).draw.utilRgn, eraseRgn);
  846.             EraseRgn(eraseRgn);
  847.             DisposeRgn(eraseRgn);
  848.             SetEmptyRgn((**popup).draw.utilRgn);
  849.         }
  850.         else
  851.             EraseRect(&bounds);
  852.     }
  853.     (**popup).r = *r;
  854. }
  855.  
  856. /* calculate the popup's rectangles */
  857. void PopupCalculate(PopupHandle popup)
  858. {
  859.     struct {                                /* margins around some items */
  860.         struct { short left, right; } selection;
  861.         struct { short left, right; } title;
  862.         struct { short left, right; } arrow;
  863.     } margin = {
  864.         { kFrameSize, 0 },            /* selection */
  865.         { kTitleMargin, kTitleMargin },    /* title */
  866.         { 0, kArrowMargin },            /* arrow */
  867.     };
  868.     struct {                                /* minimum widths of each item */
  869.         short content;
  870.         short hilite;
  871.         short title;
  872.         short arrow;
  873.         short selection;
  874.     } minwidth;
  875.     short lineHeight;                    /* height of a line */
  876.     short menuHeight;                    /* height of the selected menu item */
  877.     short menuWidth;                    /* width of the menu */
  878.     short maxwidth;                    /* for calculating maximum widths of items */
  879.     short width;                        /* for calculating widths of items */
  880.     Point iconSize;                    /* size of current item's icon */
  881.     ResType iconType;                    /* resource type of current item's icon */
  882.     short iconID;                        /* resource ID  of current item's icon */
  883.     FontInfo font;                        /* info about the current font size */
  884.     Str255 title;                        /* the popup's title */
  885.     Rect content;                        /* rectangle enclosing content (excludes frame) */
  886.     PopupRectanglesType r;            /* the calculated rectangles */
  887.     PopupDrawSelectionStructure saveState; /* saved state for calculating menu height */
  888.     GrafPtr port;                        /* the current port being drawn into */
  889.     
  890.     /* don't calculate if drawing is turned off */
  891.     if (! (**popup).attr.draw) return;
  892.     
  893.     /* initialize settings */
  894.     PopupPortSetup(popup);
  895.     PopupTitle(popup, title);
  896.     GetPort(&port);
  897.     
  898.     /* The following calls to CalcMenuSize and GetFontInfo have a bizarre
  899.         requirement. When using a window font other than the system font
  900.         (i.e., using the popupUseWFont variation code) the first call to
  901.         CalcMenuSize would calculate the menu's size using the standard
  902.         system font, instead of the font that the CDEF specified. The
  903.         second call to CalcMenuSize calculates the menu's size using
  904.         the correct font size. Similarly, if GetFontInfo were called before
  905.         CalcMenuSize were called, GetFontInfo would return information about
  906.         the wrong font. This seems either like a bug in the OS (both systems
  907.         6.0.5 and 7.0), or an attempt by Apple to work around some other
  908.         bug in the OS. It took me a long time to figure out a work-around
  909.         for this problem. */
  910.         
  911.     /* get width of menu, including the width of the down arrow */
  912.     PopupPortSetupSystem(popup);
  913.     CalcMenuSize((**popup).menu);
  914.     CalcMenuSize((**popup).menu);
  915.     PopupPortRestoreSystem(popup);
  916.     menuWidth = (**(**popup).menu).menuWidth + kArrowWidth + kArrowMargin;
  917.  
  918.     /* setup port for current selection string */
  919.     PopupDrawSelectionSetup(popup, &saveState);
  920.  
  921.     /* get information about the font in which the current selection
  922.         will be drawn */
  923.     GetFontInfo(&font);
  924.     lineHeight = font.ascent + font.descent + font.leading;
  925.     
  926.     /* calculate height of current selection */
  927.     menuHeight = 0;
  928.     MenuItemIcon((**popup).menu, (**popup).state.current,
  929.         &iconType, &iconID, &iconSize);
  930.     if (iconSize.v > 0)
  931.         menuHeight = iconSize.v + kIconMarginV;
  932.     
  933.     /* the height must be at least as large as the minimum height
  934.         of a line and the minimum height needed to display the down
  935.         arrow */
  936.     if (menuHeight < lineHeight)
  937.         menuHeight = lineHeight;
  938.     if (menuHeight < kArrowHeight + 4)
  939.         menuHeight = kArrowHeight + 4;
  940.         
  941.     /* restore port */
  942.     PopupDrawSelectionRestore(popup, &saveState);
  943.  
  944.     /* get information about the font in which the title will be drawn */
  945.     GetFontInfo(&font);
  946.     lineHeight = font.ascent + font.descent + font.leading;
  947.     
  948.     /* adjust margins */
  949.     
  950.     if (! *title || (**popup).attr.typein) {
  951.         /* Type-in popups don't display the title or the current selection,
  952.             so their margins aren't needed. If the title is empty then the
  953.             title's margins can also be empty. */
  954.         if ((**popup).attr.typein) {
  955.             margin.arrow.left = 3;
  956.             margin.arrow.right = 3;
  957.             menuHeight = lineHeight;
  958.             font.widMax = 0;
  959.         }
  960.         margin.title.left = 0;
  961.         margin.title.right = 0;
  962.     }
  963.     
  964.     /* for right justified popups we need a little extra space for the drop
  965.         shadow between the current selection area and the title */
  966.     if ((**popup).attr.just == teFlushRight)
  967.         margin.selection.left += kShadowSize;
  968.         
  969.     /* calculate minimum widths */
  970.     
  971.     minwidth.arrow = kArrowWidth;
  972.     minwidth.title = (*title ? font.widMax : 0);
  973.     minwidth.hilite = minwidth.title + margin.title.left + margin.title.right;
  974.     minwidth.selection = font.widMax + minwidth.arrow + margin.arrow.left + margin.arrow.right;
  975.     minwidth.content = minwidth.hilite + minwidth.selection +
  976.                             margin.selection.left + margin.selection.right;
  977.     
  978.     /* calculate rectangles */
  979.     
  980.     /* copy maxbounds rectangle */
  981.     r.maxbounds = (**popup).r.maxbounds;
  982.     
  983.     /* calculate content rectangle; all the other rectangles will be
  984.         calculated relative to this rectangle */
  985.     content.top = r.maxbounds.top + kFrameSize;
  986.     content.left = r.maxbounds.left + kFrameSize;
  987.     content.bottom = content.top + menuHeight;
  988.     content.right = content.left +
  989.         max(r.maxbounds.right - r.maxbounds.left - 2 * kFrameSize - kShadowSize,
  990.              minwidth.content);
  991.  
  992.     /* vertically center content in maxbounds */
  993.     {    short offset = ((r.maxbounds.bottom - r.maxbounds.top) -
  994.                                (content.bottom - content.top)) / 2;
  995.         OffsetRect(&content, 0, max(0, offset - kShadowSize));
  996.     }
  997.     
  998.     /* calculate hilite rectangle */
  999.     r.hilite = content;
  1000.     r.hilite.left = content.left;
  1001.     r.hilite.right = content.right - minwidth.selection;
  1002.  
  1003.     /* calculate title rectangle */
  1004.     check(content.bottom - content.top >= lineHeight);
  1005.     r.title.top = content.top + (content.bottom - content.top - lineHeight) / 2;
  1006.     r.title.left = content.left + margin.title.left;
  1007.     r.title.bottom = r.title.top + lineHeight - kTitleMarginBottom;
  1008.     /* calculate width of title rectangle */
  1009.     if ((**popup).attr.typein || ! *title)
  1010.         width = 0; /* don't show title */
  1011.     else {
  1012.         /* popup has a title */
  1013.         maxwidth =
  1014.             r.hilite.right - r.hilite.left -
  1015.             margin.title.left - margin.title.right;
  1016.         check(maxwidth >= minwidth.title);
  1017.         if ((**popup).attr.title.width) {
  1018.             /* use fixed title width as specified by application */
  1019.             width = min(maxwidth, (**popup).attr.title.width);
  1020.             width = max(width, minwidth.title);
  1021.         }
  1022.         else {
  1023.             /* use actual width of title string */
  1024.             Style style = port->txFace;
  1025.             TextFace((**popup).attr.title.style);
  1026.             width = TruncatePString(title, maxwidth);
  1027.             TextFace(style);
  1028.         }
  1029.         check(width <= maxwidth);
  1030.     }
  1031.     check(width >= minwidth.title);
  1032.     r.title.right = r.title.left + width;
  1033.  
  1034.     /* adjust right edge of hilite rectangle now that width of title is known */
  1035.     r.hilite.right = r.title.right + margin.title.right;
  1036.     
  1037.     /* calculate selection rectangle, initially using the maximum possible width */
  1038.     r.selection = content;
  1039.     r.selection.left = r.hilite.right + margin.selection.left;
  1040.     r.selection.right = content.right - margin.selection.right;
  1041.  
  1042.     /* calculate width of current selection */
  1043.     if ((**popup).attr.typein)
  1044.         width = minwidth.selection;
  1045.     else if ((**popup).attr.fixedwidth)
  1046.         width = max(r.selection.right - r.selection.left, minwidth.selection);
  1047.     else {
  1048.         maxwidth = r.selection.right - r.selection.left;
  1049.         check(maxwidth >= minwidth.selection);
  1050.         width = min(maxwidth, menuWidth);
  1051.         width = max(width, minwidth.selection);
  1052.     }
  1053.  
  1054.     /* adjust right edge now that we know how wide everything is */
  1055.     r.selection.right = r.selection.left + width;
  1056.     content.right = r.selection.right + margin.selection.right;
  1057.     
  1058.     /* calculate arrow rectangle--center arrow vertically at right edge of
  1059.         selection rectangle */
  1060.     check(content.bottom - content.top >= kArrowHeight);
  1061.     r.arrow.top = content.top + (content.bottom - content.top - kArrowHeight) / 2;
  1062.     r.arrow.left = r.selection.right - kArrowWidth - margin.arrow.right;
  1063.     r.arrow.bottom = r.arrow.top + kArrowHeight;
  1064.     r.arrow.right = r.arrow.left + kArrowWidth;
  1065.  
  1066.     /* calculate frame rectangle--surrounds content area, and includes the
  1067.         frame and shadow */
  1068.     r.bounds.top = content.top - kFrameSize;
  1069.     r.bounds.left = content.left - kFrameSize;
  1070.     r.bounds.right = content.right + kFrameSize;
  1071.     r.bounds.bottom = content.bottom + kFrameSize + kShadowSize;
  1072.     if ((**popup).attr.just != teFlushRight)
  1073.         r.bounds.right += kShadowSize; /* in teFlushRight shadow is part of content */
  1074.     
  1075.     /* mirror image rectangles if using a right justified menu */
  1076.     if ((**popup).attr.just == teFlushRight) {
  1077.         Rect maxbounds;
  1078.         
  1079.         /* copy and adjust maxbounds so flipping will work */
  1080.         maxbounds.top = r.maxbounds.top;
  1081.         maxbounds.left = r.maxbounds.left;
  1082.         maxbounds.bottom = r.maxbounds.top +
  1083.             max(r.maxbounds.bottom - r.maxbounds.top,
  1084.                  r.bounds.bottom - r.bounds.top);
  1085.         maxbounds.right = r.maxbounds.left +
  1086.             max(r.maxbounds.right - r.maxbounds.left,
  1087.                  r.bounds.right - r.bounds.left);
  1088.  
  1089.         /* flip the rectangles */
  1090.         RectFlip(&r.bounds, &maxbounds);
  1091.         RectFlip(&r.hilite, &maxbounds);
  1092.         RectFlip(&r.selection, &maxbounds);
  1093.         RectFlip(&r.title, &maxbounds);        
  1094.         RectFlip(&r.arrow, &maxbounds);
  1095.         if (PopupJust(popup) != teFlushRight)
  1096.             RectFlip(&r.arrow, &r.selection);
  1097.     }
  1098.     
  1099.     /* make sure everything's ok */
  1100.     check(RectWidth(&r.hilite) >= minwidth.hilite);
  1101.     check(RectWidth(&r.title) >= minwidth.title);
  1102.     check(RectWidth(&r.selection) >= minwidth.selection);
  1103.     check(RectWidth(&r.arrow) >= minwidth.arrow);
  1104.     check(RectWithin(&r.hilite, &r.bounds));
  1105.     check(RectWithin(&r.title, &r.hilite));
  1106.     check(RectWithin(&r.selection, &r.bounds));
  1107.     check(RectWithin(&r.arrow, &r.selection));
  1108.     check(! RectWithin(&r.hilite, &r.selection));
  1109.     check(! RectWithin(&r.selection, &r.hilite));
  1110.  
  1111.     /* set the popup's rectangles and restore the environment */
  1112.     PopupRectanglesSet(popup, &r);
  1113.     PopupPortRestore(popup);
  1114. }
  1115.  
  1116. /*---------------------------------------------------------------------------*/
  1117. /* more drawing routines */
  1118. /*---------------------------------------------------------------------------*/
  1119.  
  1120. /* Inserts the menu into the heirarchical menu list if necessary.
  1121.     This also sets the menu's color table if there is a 'mctb'
  1122.     resource with the same ID as the menu. True is returned if the
  1123.     menu was inserted into the menu table, in which case you should
  1124.     call DeleteMenu when finished using the menu. */
  1125. static Boolean PopupInsertMenu(PopupHandle popup)
  1126. {
  1127.     Boolean inserted;
  1128.     SignedByte state;
  1129.     Handle mctb;
  1130.     
  1131.     require(PopupValid(popup));
  1132.     inserted = false;
  1133.     if (! GetMHandle((**(**popup).menu).menuID)) {
  1134.         inserted = true;
  1135.         InsertMenu((**popup).menu, -1);
  1136.         if (MacHasColorQD()) {
  1137.             mctb = GetResource('mctb', (**(**popup).menu).menuID);
  1138.             if (mctb && *mctb) {
  1139.                 state = HGetState(mctb);
  1140.                 MoveHHi(mctb);
  1141.                 HLock(mctb);
  1142.                 SetMCEntries(*(short *) *mctb, (MCTablePtr) (*mctb + sizeof(short)));
  1143.                 HSetState(mctb, state);
  1144.             }
  1145.         }
  1146.     }
  1147.     return(inserted);
  1148. }
  1149.  
  1150. /* Set the 'foreColor' and 'backColor' parameters to the foreground and
  1151.     background colors for the specified part of the control, and return
  1152.     true if should use color when drawing the part. */
  1153. static Boolean PopupPartColor(PopupHandle popup, short part,
  1154.     RGBColor *foreColor, RGBColor *backColor)
  1155. {
  1156.     RGBColor grayColor;    /* for graying item if it's disabled */
  1157.     Boolean color;            /* true if should use color */
  1158.     
  1159.     /* only use color if the destination port is a color port
  1160.         (otherwise, the colors would be mapped to black, which
  1161.         would be pretty ugly) */ 
  1162.     color = (PopupPixelDepth(popup) > 1);
  1163.     if (color) {
  1164.         check(MacHasColorQD());
  1165.         *backColor = (**popup).draw.save.color.back;
  1166.         *foreColor = (**popup).draw.save.color.fore;
  1167.         switch (part) {
  1168.         case kPopupPartTitle:
  1169.             color = MenuItemColor((**popup).menu, 0, foreColor, backColor);
  1170.             break;
  1171.         case kPopupPartItem:
  1172.             color = MenuItemColor((**popup).menu, (**popup).state.current,
  1173.                                             foreColor, backColor);
  1174.             break;
  1175.         case kPopupPartIcon:
  1176.             /* if the item is disabled, then select a color to match the color
  1177.                 of the disabled text */
  1178.             color = MenuItemColor((**popup).menu, (**popup).state.current,
  1179.                                             foreColor, backColor);
  1180.             if (color &&
  1181.                  ! (**popup).attr.enabled ||
  1182.                  ! MenuItemEnabled((**popup).menu, (**popup).state.current))
  1183.             {
  1184.                 if ((**popup).draw.gdevice)
  1185.                     (void) GetGray((**popup).draw.gdevice, backColor, foreColor);
  1186.                 else
  1187.                     (void) GetGray(GetMainDevice(), backColor, foreColor);
  1188.             }
  1189.             break;
  1190.         case kPopupPartArrow:
  1191.         case kPopupPartFrame:
  1192.         case kPopupPartShadow:
  1193.             /* select a color in which to draw the part if it's disabled */
  1194.             if (! (**popup).attr.enabled) {
  1195.                 if ((**popup).draw.gdevice)
  1196.                     (void) GetGray((**popup).draw.gdevice, backColor, foreColor);
  1197.                 else
  1198.                     (void) GetGray(GetMainDevice(), backColor, foreColor);
  1199.             }
  1200.             break;
  1201.         }
  1202.     }
  1203.     return(color);
  1204. }
  1205.  
  1206. /*    Set the foreground and background colors to the colors appropriate for
  1207.     drawing the specified part of the control. */
  1208. static void PopupPartColorSet(PopupHandle popup, short part)
  1209. {
  1210.     RGBColor foreColor;
  1211.     RGBColor backColor;
  1212.     
  1213.     if (PopupPartColor(popup, part, &foreColor, &backColor)) {
  1214.         RGBForeColor(&foreColor);
  1215.         RGBBackColor(&backColor);
  1216.     }
  1217. }
  1218.  
  1219. /* draw the frame around the popup menu */
  1220. static void PopupDrawFrame(PopupHandle popup)
  1221. {
  1222.     Rect frame;
  1223.     PenState pen;
  1224.     ColorState color;
  1225.     
  1226.     require((**popup).state.drawset);
  1227.     
  1228.     /* save state */
  1229.     GetPenState(&pen);
  1230.     GetColorState(&color);
  1231.     
  1232.     /* draw frame */
  1233.     PopupPartColorSet(popup, kPopupPartFrame);
  1234.     frame = (**popup).r.bounds;    
  1235.     if ((**popup).attr.just == teFlushRight)
  1236.         frame.right = (**popup).r.selection.right + kFrameSize + kShadowSize;
  1237.     else
  1238.         frame.left = (**popup).r.selection.left - kFrameSize;
  1239.     frame.right -= kShadowSize;
  1240.     frame.bottom -= kShadowSize;
  1241.     PenSize(kFrameSize, kFrameSize);
  1242.     FrameRect(&frame);
  1243.  
  1244.     /* draw drop shadow */
  1245.     PopupPartColorSet(popup, kPopupPartShadow);
  1246.     PenSize(kShadowSize, kShadowSize);
  1247.     MoveTo(frame.right, frame.top + kShadowOffset);
  1248.     LineTo(frame.right, frame.bottom);
  1249.     LineTo(frame.left + kShadowOffset, frame.bottom);
  1250.     
  1251.     /* restore state */
  1252.     SetPenState(&pen);
  1253.     SetColorState(&color);
  1254. }
  1255.  
  1256. /* draw the down arrow */
  1257. static void PopupDrawArrow(PopupHandle popup)
  1258. {
  1259.     Rect arrow;        /* arrow's rectangle */
  1260.     short    height;    /* height of arrow */
  1261.     short    width;    /* width of current line */
  1262.     short    i;            /* index to lines of arrow */
  1263.     ColorState color;
  1264.     
  1265.     require((**popup).state.drawset);
  1266.     GetColorState(&color);
  1267.     PopupPartColorSet(popup, kPopupPartArrow);
  1268.     arrow = (**popup).r.arrow;
  1269.     height = kArrowHeight;
  1270.     width = kArrowWidth - 1;
  1271.     for (i = 0; i < height; i++) {
  1272.         MoveTo(arrow.left + i, arrow.top + i);
  1273.         LineTo(arrow.left + i + width, arrow.top + i);
  1274.         width -= 2;
  1275.     }
  1276.     SetColorState(&color);
  1277. }
  1278.  
  1279. /* draw the string within the bounding rectangle; if the string is too wide, it's
  1280.     truncated and an ellipses character is appended to it */
  1281. static void PopupDrawString(PopupHandle popup, Str255 str, const Rect *bounds,
  1282.     short just)
  1283. {
  1284.     FontInfo    font;
  1285.     short width;
  1286.     
  1287.     require((**popup).state.drawset);
  1288.     require(RectValid(bounds));
  1289.     GetFontInfo(&font);
  1290.     width = TruncatePString(str, bounds->right - bounds->left);
  1291.     if (just == teFlushRight)
  1292.         MoveTo(bounds->right - width, bounds->bottom - font.descent);
  1293.     else
  1294.         MoveTo(bounds->left, bounds->bottom - font.descent);
  1295.     DrawString(str);
  1296. }
  1297.  
  1298. /* draw the current selection string and icon */
  1299. static void PopupDrawSelection(PopupHandle popup)
  1300. {
  1301.     PopupDrawSelectionStructure saveState; /* saved state */
  1302.     Str255 itemString;        /* string of currently selected menu item */
  1303.     FontInfo font;                /* information about the current font */
  1304.     Rect selectionRect;        /* rectangle to draw string in */
  1305.     short selectionHeight;    /* height of current selection rectangle */
  1306.     short lineHeight;            /* height of a line of text */
  1307.     Rect iconRect;                /* rectangle to draw icon in */
  1308.     Point iconSize;            /* size of current item's icon */
  1309.     ResType iconType;            /* resource type of current item's icon */
  1310.     short iconID;                /* resource ID of current item's icon */
  1311.     Handle iconHandle;        /* handle to current item's icon */
  1312.     SignedByte iconState;    /* state of handle to icon */
  1313.     Boolean iconDraw;            /* true if should draw the icon */
  1314.     IconTransformType iconTransform; /* transformation to apply when drawing icon */
  1315.     
  1316.     require((**popup).state.drawset);
  1317.     require(! (**popup).attr.typein);
  1318.     
  1319.     /* initially, set the rectangle within which to draw the string
  1320.         equal to the entire current selection rectangle */
  1321.     selectionRect = (**popup).r.selection;
  1322.     
  1323.     /* setup drawing environment */
  1324.     PopupDrawSelectionSetup(popup, &saveState);
  1325.     
  1326.     /* set the item's color */
  1327.     PopupPartColorSet(popup, kPopupPartItem);    
  1328.     EraseRect(&selectionRect);
  1329.     
  1330.     /* calculate size of current item's icon */
  1331.     MenuItemIcon((**popup).menu, (**popup).state.current,
  1332.         &iconType, &iconID, &iconSize);
  1333.  
  1334.     /* exclude icon from selection rectangle */
  1335.     if (iconSize.h > 0) {
  1336.         if (PopupJust(popup) == teFlushRight)
  1337.             selectionRect.right -= iconSize.h + kIconMarginH;
  1338.         else
  1339.             selectionRect.left += iconSize.h + kIconMarginH;
  1340.         check(RectValid(&selectionRect));
  1341.     }
  1342.     
  1343.     /* exclude arrow from selection rectangle */
  1344.     if (PopupJust(popup) == teFlushRight)
  1345.         selectionRect.left += kArrowWidth + kArrowMargin;
  1346.     else
  1347.         selectionRect.right -= kArrowWidth + kArrowMargin;
  1348.     check(RectValid(&selectionRect));
  1349.  
  1350.     /* align the bounding box within which to draw the current selection
  1351.         string with the base line of the title string and with the edge
  1352.         of the current selection rectangle */
  1353.     GetFontInfo(&font);
  1354.     lineHeight = font.ascent + font.descent + font.leading;
  1355.     selectionHeight = selectionRect.bottom - selectionRect.top;
  1356.     check(selectionHeight >= lineHeight);
  1357.     selectionRect.top += (selectionHeight - lineHeight) / 2 - 1;
  1358.     selectionRect.bottom = selectionRect.top + lineHeight;
  1359.     if (PopupJust(popup) == teFlushRight)
  1360.         selectionRect.right -= font.widMax;
  1361.     else
  1362.         selectionRect.left += font.widMax;
  1363.     check(RectValid(&selectionRect));
  1364.     
  1365.     /* draw the item string */
  1366.     GetItem((**popup).menu, (**popup).state.current, itemString);
  1367.     PopupDrawString(popup, itemString, &selectionRect, PopupJust(popup));
  1368.             
  1369.     /* calculate the icon's rectangle, but don't draw the icon if
  1370.         it overlaps the item's text or the down arrow */
  1371.     iconDraw = true;
  1372.     iconRect = (**popup).r.selection;
  1373.     check(iconRect.bottom - iconRect.top >= iconSize.v);
  1374.     iconRect.top += (iconRect.bottom - iconRect.top - iconSize.v) / 2;
  1375.     iconRect.bottom = iconRect.top + iconSize.v;
  1376.     if (PopupJust(popup) == teFlushRight) {
  1377.         iconRect.right -= font.widMax;
  1378.         iconRect.left = iconRect.right - iconSize.h;
  1379.         if (iconRect.left < selectionRect.right ||
  1380.              iconRect.left < (**popup).r.arrow.right)
  1381.         {
  1382.             iconDraw = false;
  1383.         }
  1384.     }
  1385.     else {
  1386.         iconRect.left += font.widMax;
  1387.         iconRect.right = iconRect.left + iconSize.h;
  1388.         if (iconRect.right > selectionRect.left ||
  1389.              iconRect.right > (**popup).r.arrow.left)
  1390.         {
  1391.             iconDraw = false;
  1392.         }
  1393.     }
  1394.         
  1395.     /* determine transformation to apply when drawing icon */
  1396.     iconTransform = ttNone;
  1397.     if (! (**popup).attr.enabled ||
  1398.          ! MenuItemEnabled((**popup).menu, (**popup).state.current))
  1399.     {
  1400.         iconTransform = ttDisabled;
  1401.     }
  1402.  
  1403.     /* set the color for black and white icons */
  1404.     PopupPartColorSet(popup, kPopupPartIcon);
  1405.         
  1406.     /* draw the icon */
  1407.     if (iconDraw && iconType) {            
  1408.         switch (iconType) {
  1409.         case 'SICN':
  1410.             OffsetRect(&iconRect, 0, -1);
  1411.             iconHandle = GetResource(iconType, iconID);
  1412.             if (iconHandle && *iconHandle) {
  1413.                 iconState = HGetState(iconHandle);
  1414.                 HNoPurge(iconHandle);
  1415.                 PlotSICN(&iconRect, iconHandle);
  1416.                 HSetState(iconHandle, iconState);
  1417.             }
  1418.             break;
  1419.         case 'ICON':
  1420.             OffsetRect(&iconRect, -1, 0);
  1421.             iconHandle = GetResource(iconType, iconID);
  1422.             if (iconHandle && *iconHandle) {
  1423.                 iconState = HGetState(iconHandle);
  1424.                 HNoPurge(iconHandle);
  1425.                 PlotIcon(&iconRect, iconHandle);
  1426.                 HSetState(iconHandle, iconState);
  1427.             }
  1428.             break;
  1429.         case 'cicn':
  1430.             check(MacHasColorQD());
  1431.             iconHandle = (Handle) GetCIcon(iconID);
  1432.             if (iconHandle && *iconHandle) {
  1433.                 if (MacHasIconUtilities())
  1434.                     PlotCIconHandle(&iconRect, atNone, iconTransform, (CIconHandle) iconHandle);
  1435.                 else
  1436.                     PlotCIcon(&iconRect, (CIconHandle) iconHandle);
  1437.                 DisposeCIcon((CIconHandle) iconHandle);
  1438.             }
  1439.             break;
  1440.         }
  1441.     }
  1442.  
  1443.     /* restore drawing environment */
  1444.     PopupDrawSelectionRestore(popup, &saveState);
  1445.     
  1446.     /* gray over entire selection if item is disabled and we're drawing into a
  1447.         black and white port */
  1448.     if (PopupPixelDepth(popup) == 1 &&
  1449.          ! MenuItemEnabled((**popup).menu, (**popup).state.current))
  1450.     {
  1451.         selectionRect = (**popup).r.selection;
  1452.         RectDisable(&selectionRect);
  1453.     }
  1454. }
  1455.  
  1456. /* draw the title string */
  1457. static void PopupDrawTitle(PopupHandle popup)
  1458. {
  1459.     Str255 title;                /* title string */
  1460.     Rect rtitle;                /* title's text rectangle */
  1461.     Rect rhilite;                /* title's hilite rectangle */
  1462.     TextState saveText;        /* save text state */
  1463.     ColorState saveColor;    /* saved color state */
  1464.     GrafPtr port;                /* port we're drawing into */
  1465.     
  1466.     require((**popup).state.drawset);
  1467.     require(! (**popup).attr.typein);
  1468.  
  1469.     /* save port */
  1470.     GetPort(&port);
  1471.     GetTextState(&saveText);
  1472.     GetColorState(&saveColor);
  1473.     
  1474.     /* get rectangles */
  1475.     rhilite = (**popup).r.hilite;
  1476.     rtitle = (**popup).r.title;
  1477.     
  1478.     /* set the title's color */
  1479.     PopupPartColorSet(popup, kPopupPartTitle);
  1480.     EraseRect(&rhilite);
  1481.     
  1482.     /* set the text style */
  1483.     TextFace((**popup).attr.title.style);
  1484.     
  1485.     /* draw the text in gray if the control is disabled */
  1486.     if (PopupPixelDepth(popup) > 1 && ! (**popup).attr.enabled)
  1487.         TextMode(grayishTextOr);
  1488.     
  1489.     /* draw the title string */
  1490.     PopupTitle(popup, title);
  1491.     PopupDrawString(popup, title, &rtitle, (**popup).attr.just);
  1492.     
  1493.     /* restore port's settings */
  1494.     SetColorState(&saveColor);
  1495.     SetTextState(&saveText);
  1496. }
  1497.  
  1498. /* gray out menu if it's disabled */
  1499. static void PopupDrawEnabled(PopupHandle popup)
  1500. {
  1501.     Rect bounds;
  1502.     
  1503.     require(PopupValid(popup));
  1504.     require((**popup).state.drawset);
  1505.     if (PopupPixelDepth(popup) == 1 && ! (**popup).attr.enabled) {
  1506.         bounds = (**popup).r.bounds;
  1507.         RectDisable(&bounds);
  1508.     }
  1509. }
  1510.  
  1511. /* erase the entire popup menu */
  1512. static void PopupErase(PopupHandle popup)
  1513. {
  1514.     Rect bounds;
  1515.     
  1516.     require((**popup).state.drawset);
  1517.     bounds = (**popup).r.maxbounds;
  1518.     EraseRect(&bounds);
  1519. }
  1520.  
  1521. #if DRAW_RECTANGLES
  1522.  
  1523. /* draw frames around the parts of the popup; this is useful when
  1524.     debugging */
  1525. static void PopupDrawRectangles(PopupHandle popup)
  1526. {
  1527.     struct {
  1528.         long maxbounds;
  1529.         long frame;
  1530.         long shadow;
  1531.         long title;
  1532.         long hilite;
  1533.         long selection;
  1534.         long arrow;
  1535.     } partColor = {
  1536.         blackColor,
  1537.         yellowColor,
  1538.         magentaColor,
  1539.         redColor,
  1540.         cyanColor,
  1541.         greenColor,
  1542.         blueColor,
  1543.     };
  1544.     PenState penState;
  1545.     long foreColor;
  1546.     Rect shadow;
  1547.     Rect frame;
  1548.     GrafPtr port;
  1549.     
  1550.     /* setup port */
  1551.     GetPort(&port);
  1552.     GetPenState(&penState);
  1553.     foreColor = port->fgColor;
  1554.     PenNormal();
  1555.     
  1556.     /* draw outline of frame and shadow */
  1557.     if (kShadowSize > 2 && kFrameSize > 2) {
  1558.     
  1559.         /* calculate frame */
  1560.         frame = (**popup).r.bounds;    
  1561.         if ((**popup).attr.just == teFlushRight)
  1562.             frame.right = (**popup).r.selection.right + kFrameSize + kShadowSize;
  1563.         else
  1564.             frame.left = (**popup).r.selection.left - kFrameSize;
  1565.         frame.right -= kShadowSize;
  1566.         frame.bottom -= kShadowSize;
  1567.         
  1568.         /* draw outline of frame */
  1569.         ForeColor(partColor.frame);
  1570.         FrameRect(&frame);
  1571.         InsetRect(&frame, kFrameSize, kFrameSize);
  1572.         FrameRect(&frame);
  1573.         InsetRect(&frame, -kFrameSize, -kFrameSize);
  1574.         
  1575.         /* draw outline of shadow */
  1576.         ForeColor(partColor.shadow);
  1577.         shadow.top = frame.bottom;
  1578.         shadow.left = frame.left + kShadowOffset;
  1579.         shadow.bottom = shadow.top + kShadowSize;
  1580.         shadow.right = frame.right + kShadowSize;
  1581.         FrameRect(&shadow);
  1582.         shadow.top = frame.top + kShadowOffset;
  1583.         shadow.left = frame.right;
  1584.         shadow.bottom = frame.bottom + kShadowSize;
  1585.         shadow.right = shadow.left + kShadowSize;
  1586.         FrameRect(&shadow);
  1587.     }
  1588.  
  1589.     /* draw the other rectangles */
  1590.  
  1591.     ForeColor(partColor.maxbounds);
  1592.     frame = (**popup).r.maxbounds;
  1593.     FrameRect(&frame);
  1594.  
  1595.     ForeColor(partColor.title);
  1596.     frame = (**popup).r.title;
  1597.     FrameRect(&frame);
  1598.  
  1599.     ForeColor(partColor.hilite);
  1600.     frame = (**popup).r.hilite;
  1601.     FrameRect(&frame);
  1602.  
  1603.     ForeColor(partColor.selection);
  1604.     frame = (**popup).r.selection;
  1605.     FrameRect(&frame);
  1606.  
  1607.     ForeColor(partColor.arrow);
  1608.     frame = (**popup).r.arrow;
  1609.     FrameRect(&frame);
  1610.     
  1611.     /* restore port */
  1612.     SetPenState(&penState);
  1613.     ForeColor(foreColor);
  1614. }
  1615.  
  1616. #endif /* DRAW_RECTANGLES */
  1617.  
  1618. /* draw the menu into the current port */
  1619. static void PopupDrawDirect(PopupHandle popup)
  1620. {
  1621.     Boolean inserted; /* true if inserted menu into menu list */
  1622.     
  1623.     require(PopupValid(popup));
  1624.  
  1625.     /* setup drawing environment */
  1626.     PopupPortSetup(popup);
  1627.         
  1628.     /* erase the entire popup */
  1629.     PopupErase(popup);
  1630.     if ((**popup).attr.visible) {
  1631.     
  1632.         /* insert menu into menu list so we can access its color table */
  1633.         inserted = PopupInsertMenu(popup);
  1634.  
  1635.         #if DRAW_POPUP
  1636.             /* draw all the parts of the popup */
  1637.             if (! (**popup).attr.typein) {
  1638.                 PopupDrawTitle(popup);
  1639.                 PopupDrawSelection(popup);
  1640.             }
  1641.             PopupDrawFrame(popup);
  1642.             PopupDrawArrow(popup);
  1643.             PopupDrawEnabled(popup);
  1644.         #endif /* DRAW_POPUP */
  1645.         
  1646.         #if DRAW_RECTANGLES
  1647.             PopupDrawRectangles(popup);
  1648.         #endif /* DRAW_RECTANGLES */
  1649.  
  1650.         /* remove the menu from the menu list */
  1651.         if (inserted)
  1652.             DeleteMenu((**(**popup).menu).menuID);
  1653.     }
  1654.  
  1655.     /* restore drawing environment */
  1656.     PopupPortRestore(popup);
  1657. }
  1658.  
  1659. /* callback for DeviceLoop function when drawing without an offscreen bitmap */
  1660. static pascal void PopupDrawDeviceLoopCallBack(short depth, short flags,
  1661.     GDHandle device, long data)
  1662. {
  1663.     PopupHandle popup;
  1664.     
  1665.     popup = (PopupHandle) data;
  1666.     (**popup).draw.gdevice = device;
  1667.     (**popup).draw.gdepth = depth;
  1668.     PopupDrawDirect(popup);
  1669. }
  1670.  
  1671. /* Draw the popup using a device loop. The popup's utilRgn should be set to
  1672.     the region enclosing the popup's maxbound's rectangle, in global
  1673.     coordinates. For greater efficiency, the popup's utilRgn can be set to
  1674.     the intersection of the popup's bounding rectangle and its port's visRgn. */
  1675. static void PopupDrawDeviceLoop(PopupHandle popup)
  1676. {
  1677.     if (MacHasColorQD() && (**popup).draw.utilRgn) {
  1678.         DeviceLoop((**popup).draw.utilRgn,
  1679.             PopupDrawDeviceLoopCallBack, (long) popup, 0);
  1680.     }
  1681.     else {
  1682.         (**popup).draw.gdevice = NULL;
  1683.         PopupDrawDirect(popup);
  1684.     }
  1685. }
  1686.  
  1687. /* draw the popup to an offscreen bitmap, then copy the bitmap to the screen */
  1688. void PopupDraw(PopupHandle popup)
  1689. {
  1690.     CGrafPtr        saveCPort;        /* saved graphics port */
  1691.     GDHandle        saveGDH;            /* saved graphics device */
  1692.     Boolean        changed;            /* if true, image needs to be redrawn */
  1693.     Boolean        direct;            /* true if should draw to onscreen port */
  1694.     GWorldPtr    gworld;            /* temporary pointer to offscreen graphics world */
  1695.     GDHandle        gdevice;            /* temporary pointer to graphics device of gworld */
  1696.     short            gdepth;            /* temporary storage for depth of graphics world */
  1697.     Rect            popupRect;        /* popup's bounding rectangle */
  1698.     GWorldFlags    updateFlags;    /* flags returned UpdateGWorld */
  1699.     
  1700.     require(PopupValid(popup));
  1701.     if ( ! (**popup).attr.draw) return;
  1702.     
  1703.     /* get the popup's changed flag; the popup is reimaged if it changed */
  1704.     changed = (**popup).state.changed;
  1705.     (**popup).state.changed = false;
  1706.  
  1707.     /* calculate mask region for CopyBits */
  1708.     if ((**popup).draw.utilRgn) {
  1709.         check(EmptyRgn((**popup).draw.utilRgn));
  1710.         CopyRgn((**popup).port->clipRgn, (**popup).draw.utilRgn);
  1711.         SectRgn((**popup).port->visRgn, (**popup).draw.utilRgn,
  1712.             (**popup).draw.utilRgn);
  1713.     }
  1714.     
  1715.     /* try to draw from the offscreen graphics world */
  1716.     direct = true;
  1717.     (**popup).draw.gdepth = 0;
  1718.     (**popup).draw.gdevice = NULL;
  1719.     if ((**popup).draw.gworld) {
  1720.     
  1721.         /* use an offscreen graphics world */
  1722.         check(MacHasGWorlds());
  1723.         
  1724.         /* get the popup's bounding rectangle, in global coordinates */
  1725.         popupRect = (**popup).r.maxbounds;
  1726.         RectLocalToGlobal(&popupRect);
  1727.  
  1728.         /* Determine if the image spans multiple monitors with different
  1729.             pixel depths. If so, then using CopyBits to draw the image from
  1730.             an offscreen bitmap causes the image to be displayed poorly
  1731.             on the monitor with the smaller pixel depth. In particular,
  1732.             on a 1-bit screen, colors are mapped to black, and a disabled popup
  1733.             (grayed out) may not be displayed at all. While there may be some
  1734.             trick to getting offscreen graphics worlds to work well in this
  1735.             situation, for now we just use a regular device loop to draw the
  1736.             image to each screen device. */
  1737.         direct = false;
  1738.         if (MacHasColorQD()) {
  1739.             GDHandle mindevice;
  1740.             GDHandle maxdevice;
  1741.             mindevice = GetMinDevice(&popupRect);
  1742.             maxdevice = GetMaxDevice(&popupRect);
  1743.             if (    maxdevice &&
  1744.                     mindevice &&
  1745.                     mindevice != maxdevice &&
  1746.                     GetDeviceDepth(mindevice) !=
  1747.                     GetDeviceDepth(maxdevice))
  1748.             {
  1749.                 direct = true;
  1750.             }
  1751.         }
  1752.         
  1753.         if (! direct) {
  1754.         
  1755.             /* reset direct draw flag */
  1756.             direct = true;
  1757.             
  1758.             /* According to NIM:Imaging With QuickDraw, p6-9, we only need to call
  1759.                 UpdateGWorld if the pixmap has been purged, after the window has
  1760.                 been moved, and after update events. While it is simple enough to
  1761.                 determine if the pixmap has been purged or if the window has been
  1762.                 moved, it is not easy for a CDEF to determine when an update event
  1763.                 has occurred. So, we always call UpdateGWorld whenever the popup
  1764.                 is redrawn. It doesn't hurt (except for a possible performance hit)
  1765.                 to call UpdateGWorld more than necessary. */
  1766.     
  1767.             /* update the offscreen graphics world */
  1768.             gworld = (**popup).draw.gworld;
  1769.             updateFlags = UpdateGWorld(&gworld, 0, &popupRect, NULL, NULL, 0);
  1770.             if (updateFlags != gwFlagErr) {
  1771.                 (**popup).draw.gworld = gworld;
  1772.                 
  1773.                 /* lock the offscreen pixmap */
  1774.                 if (LockPixels(GetPixMap(gworld))) {
  1775.                 
  1776.                     /* we're drawing offscreen */
  1777.                     direct = false;
  1778.                     
  1779.                     /* get the popup's bounding rectangle, in local coordinates */
  1780.                     popupRect = (**popup).r.maxbounds;
  1781.                     
  1782.                     /* determine if the popup changed, and redraw it to the
  1783.                         offscreen graphics world if necessary */
  1784.                     if (! changed)
  1785.                         changed = ((updateFlags & reallocPix) != 0);
  1786.                     if (changed) {
  1787.                         gdevice = GetGWorldDevice(gworld);
  1788.                         gdepth = (gdevice ? GetDeviceDepth(gdevice) : 1);
  1789.                         (**popup).draw.gdevice = gdevice;
  1790.                         (**popup).draw.gdepth = gdepth;
  1791.                         GetGWorld(&saveCPort, &saveGDH);
  1792.                         SetGWorld(gworld, NULL);
  1793.                         SetOrigin(popupRect.left, popupRect.top);
  1794.                         check(EqualRect(&gworld->portRect, &popupRect));
  1795.                         ClipRect(&popupRect);
  1796.                         PopupDrawDirect(popup);
  1797.                         SetGWorld(saveCPort, saveGDH);
  1798.                     }
  1799.                     
  1800.                     /* copy the offscreen pixmap to the onscreen port */
  1801.                     check(EqualRect(&gworld->portRect, &popupRect));
  1802.                     CopyBits(
  1803.                         &((GrafPtr) gworld)->portBits,
  1804.                         &(**popup).port->portBits,
  1805.                         &popupRect, &popupRect,
  1806.                         srcCopy, (**popup).draw.utilRgn);
  1807.                     
  1808.                     /* unlock the offscreen pixmap */
  1809.                     UnlockPixels(GetPixMap(gworld));
  1810.                 }
  1811.             }
  1812.         }
  1813.     }
  1814.  
  1815.     /* couldn't allocate offscreen graphics world, or conditions weren't
  1816.         appropriate for drawing using an the offscreen graphics world, but
  1817.         the popup can still be drawn to the onscreen port */
  1818.     if (direct)
  1819.         PopupDrawDeviceLoop(popup);
  1820.  
  1821.     /* clear the utility region to save memory */
  1822.     if ((**popup).draw.utilRgn)
  1823.         SetEmptyRgn((**popup).draw.utilRgn);
  1824. }
  1825.  
  1826. /* hilite the popup's title; useful for keyboard equivalents of commands */
  1827. void PopupHilite(PopupHandle popup)
  1828. {
  1829.     GrafPtr port;
  1830.     Rect hilite;
  1831.     
  1832.     GetPort(&port);
  1833.     SetPort((**popup).port);
  1834.     hilite = (**popup).r.hilite;
  1835.     LMSetHiliteMode(LMGetHiliteMode() & ~(1<<hiliteBit));
  1836.     InvertRect(&hilite);
  1837.     SetPort(port);
  1838. }
  1839.  
  1840. /*---------------------------------------------------------------------------*/
  1841. /* event handling */
  1842. /*---------------------------------------------------------------------------*/
  1843.  
  1844. /* typedef for a menu definition function */
  1845. typedef pascal void (*MenuProcType)(short message, MenuHandle menu,
  1846.     Rect *menuRect, Point hitPt, short *item);
  1847.     
  1848. /* handle to this structure is installed in menu's menuProc field */
  1849. typedef struct {
  1850.     short jmp;                /* M68K jmp instruction */
  1851.     MenuProcType mdef;    /* address of our MDEF routine */
  1852.     Handle proc;            /* original MDEF handle */
  1853.     short width;            /* width of menu */
  1854. } PatchMenuProcStructure, **PatchMenuProcHandle;
  1855.  
  1856. /* Menu proc to patch the mSizeMsg, allowing us to set the menu width to
  1857.     include the down arrow. This is installed just before calling
  1858.     PopupMenuSelect, and is removed immediately afterwards. */
  1859. static pascal void PatchMenuProc(short message, MenuHandle menu,
  1860.     Rect *menuRect, Point hitPt, short *whichItem)
  1861. {
  1862.     Handle proc;
  1863.     SignedByte state;
  1864.     PatchMenuProcHandle patch;
  1865.     
  1866.     patch = (PatchMenuProcHandle) (**menu).menuProc;    
  1867.     proc = (**patch).proc;
  1868.     state = HGetState(proc);
  1869.     MoveHHi(proc);
  1870.     HLock(proc);
  1871.     ((MenuProcType) *proc)(message, menu, menuRect, hitPt, whichItem);
  1872.     HSetState(proc, state);
  1873.     if (message == mSizeMsg)
  1874.         (**menu).menuWidth = (**patch).width;
  1875. }
  1876.     
  1877. /* True if point (in local coordinates) is within the popup menu.
  1878.     Call this before calling PopupSelect. */
  1879. Boolean PopupWithin(PopupHandle popup, Point pt)
  1880. {
  1881.     Rect hilite;
  1882.     Rect selection;
  1883.     Boolean result;
  1884.     
  1885.     require(PopupValid(popup));
  1886.     result = false;
  1887.     if ((**popup).attr.visible && (**popup).attr.enabled) {
  1888.         hilite = (**popup).r.hilite;
  1889.         selection = (**popup).r.selection;
  1890.         result = (PtInRect(pt, &selection) || PtInRect(pt, &hilite));
  1891.     }
  1892.     return(result);
  1893. }
  1894.  
  1895. /* call this when there's a mouse down in a popup menu */
  1896. void PopupSelect(PopupHandle popup)
  1897. {
  1898.     long chosen;            /* item selected from menu */
  1899.     Point    location;        /* top left of menu */
  1900.     Boolean inserted;        /* true if inserted menu into menu list */
  1901.     Rect selection;        /* current selection rectangle */
  1902.     short oldMenuWidth;    /* saved menu width */
  1903.     Handle oldMenuProc;    /* saved menuProc */
  1904.     short menuWidth;        /* width of menu */
  1905.     PatchMenuProcHandle patch;
  1906.     
  1907.     require(PopupValid(popup));
  1908.     if (StillDown()) {
  1909.     
  1910.         /* setup port */
  1911.         PopupPortSetup(popup);
  1912.         
  1913.         /* setup system font and size */
  1914.         PopupPortSetupSystem(popup);
  1915.         
  1916.         /* insert menu into menu list */
  1917.         inserted = PopupInsertMenu(popup);
  1918.  
  1919.         /* hilite title */
  1920.         PopupHilite(popup);
  1921.         
  1922.         /* calculate position for popup menu */
  1923.         location.h = (**popup).r.selection.left;
  1924.         location.v = (**popup).r.selection.top;
  1925.         LocalToGlobal(&location);
  1926.                 
  1927.         /* adjust width of menu */
  1928.         CalcMenuSize((**popup).menu);
  1929.         oldMenuWidth = menuWidth = (**(**popup).menu).menuWidth;
  1930.         if (! (**popup).attr.typein) {
  1931.         
  1932.             /* make the menu wide enough to include the down arrow */
  1933.             menuWidth += kArrowWidth + kArrowMargin;
  1934.             if ((**popup).attr.fixedwidth) {
  1935.                 /* make menu fill all of current selection rectangle */
  1936.                 menuWidth = (**popup).r.selection.right - (**popup).r.selection.left;
  1937.             }
  1938.             if (menuWidth < oldMenuWidth)
  1939.                 menuWidth = oldMenuWidth;
  1940.                 
  1941.             /* Though we patch the menu's mdef to ignore the width calculated
  1942.                 by the mSizeMsg message, we still need to explicitely set the
  1943.                 menu's width. This is necessary since PopupMenuSelect on non-color
  1944.                 macs (e.g., a Plus running system 7) doesn't send the mSizeMsg
  1945.                 message before drawing the menu, while on other macs (e.g., a
  1946.                 Quadra running system 7 Pro) the mSizeMsg is sent by
  1947.                 PopupMenuSelect. */
  1948.             (**(**popup).menu).menuWidth = menuWidth;
  1949.         }
  1950.         
  1951.         /* patch the menu's mdef to ignore the width calculated by
  1952.             the mSizeMsg */
  1953.         oldMenuProc = (**(**popup).menu).menuProc;
  1954.         if ((**popup).menuProc) {
  1955.             patch = (PatchMenuProcHandle) (**popup).menuProc;
  1956.             (**patch).jmp = ASM_M68K_JMP;
  1957.             (**patch).mdef = PatchMenuProc;
  1958.             (**patch).proc = oldMenuProc;
  1959.             (**patch).width = menuWidth;
  1960.             (**(**popup).menu).menuProc = (**popup).menuProc;
  1961.         }
  1962.         
  1963.         /* let user select an item from the menu */
  1964.         chosen = PopUpMenuSelect((**popup).menu, location.v, location.h,
  1965.                                          (**popup).state.current);
  1966.         
  1967.         /* restore environment */
  1968.         (**(**popup).menu).menuProc = oldMenuProc;
  1969.         (**(**popup).menu).menuWidth = oldMenuWidth;
  1970.         if (inserted)
  1971.             DeleteMenu((**(**popup).menu).menuID);
  1972.         PopupHilite(popup);
  1973.         PopupPortRestore(popup);
  1974.         PopupPortRestoreSystem(popup);
  1975.         
  1976.         /* display the selected item */
  1977.         if (LoWord(chosen))
  1978.             PopupCurrentSet(popup, LoWord(chosen));
  1979.     }
  1980.     ensure(PopupValid(popup));
  1981. }
  1982.     
  1983. /*---------------------------------------------------------------------------*/
  1984. /* getting and setting attributes */
  1985. /*---------------------------------------------------------------------------*/
  1986.  
  1987. /* recalculate and redraw the popup menu */
  1988. static void PopupChanged(PopupHandle popup)
  1989. {
  1990.     (**popup).state.changed = true;
  1991.     PopupCalculate(popup);
  1992.     PopupDraw(popup);
  1993. }
  1994.  
  1995. /* return the version of the library that created the popup menu */
  1996. short PopupVersion(PopupHandle popup)
  1997. {
  1998.     return((**popup).version);
  1999. }
  2000.  
  2001. /* return the currently selected menu item */
  2002. short PopupCurrent(PopupHandle popup)
  2003. {
  2004.     require(PopupValid(popup));
  2005.     return((**popup).state.current);
  2006. }
  2007.  
  2008. /* set the currently selected menu item */
  2009. void PopupCurrentSet(PopupHandle popup, short current)
  2010. {
  2011.     short i, nitems;
  2012.     
  2013.     require(PopupValid(popup));
  2014.     nitems = CountMItems((**popup).menu);
  2015.     for (i = 1; i <= nitems; i++)
  2016.         SetItemMark((**popup).menu, i, noMark);
  2017.     SetItemMark((**popup).menu, current, (**popup).attr.mark);
  2018.     if (current != (**popup).state.current) {
  2019.         (**popup).state.current = current;
  2020.         PopupChanged(popup);
  2021.     }
  2022. }
  2023.  
  2024. /* turn drawing on or off */
  2025. void PopupDrawSet(PopupHandle popup, Boolean draw)
  2026. {
  2027.     require(PopupValid(popup));
  2028.     (**popup).attr.draw = draw;
  2029. }
  2030.  
  2031. /* make popup visible or invisible */
  2032. void PopupVisibleSet(PopupHandle popup, Boolean visible)
  2033. {    
  2034.     require(PopupValid(popup));    
  2035.     if (visible != (**popup).attr.visible) {
  2036.         (**popup).attr.visible = visible;
  2037.         PopupChanged(popup);
  2038.     }    
  2039. }
  2040.  
  2041. /* set the character used to mark the current menu item */
  2042. void PopupMarkSet(PopupHandle popup, char mark)
  2043. {
  2044.     require(PopupValid(popup));
  2045.     if (mark != (**popup).attr.mark) {
  2046.         (**popup).attr.mark = mark;
  2047.         SetItemMark((**popup).menu, (**popup).state.current, (**popup).attr.mark);
  2048.     }    
  2049. }
  2050.  
  2051. /* enable or disable the menu */
  2052. void PopupEnableSet(PopupHandle popup, Boolean enabled)
  2053. {
  2054.     require(PopupValid(popup));
  2055.     if (enabled != (**popup).attr.enabled) {
  2056.         (**popup).attr.enabled = enabled;
  2057.         PopupChanged(popup);
  2058.     }
  2059.     else if (! enabled) {
  2060.         /* the popup has to be reimaged or it won't get drawn properly
  2061.             when using CopyBits to a black and white grafport */
  2062.         PopupChanged(popup);
  2063.     }
  2064. }
  2065.  
  2066. /* turn type-in style popup menu on or off */
  2067. void PopupTypeInSet(PopupHandle popup, Boolean typein)
  2068. {
  2069.     require(PopupValid(popup));
  2070.     if (typein != (**popup).attr.typein) {
  2071.         (**popup).attr.typein = typein;
  2072.         PopupChanged(popup);
  2073.     }
  2074. }
  2075.  
  2076. /* return rectangle enclosing all of popup; this is the rectangle
  2077.     enclosing the parts of the popup that are actually visible, and
  2078.     may be smaller than the rectangle specified with PopupBoundsSet. */
  2079. void PopupBounds(PopupHandle popup, Rect *bounds)
  2080. {
  2081.     require(PopupValid(popup));
  2082.     *bounds = (**popup).r.bounds;
  2083.     ensure(RectValid(bounds));
  2084. }
  2085.  
  2086. /* set popup's maximum bounding rectangle; all drawing is clipped
  2087.     to this rectangle */
  2088. void PopupBoundsSet(PopupHandle popup, const Rect *maxbounds)
  2089. {
  2090.     Rect oldmax;
  2091.     
  2092.     require(PopupValid(popup));
  2093.     require(RectValid(maxbounds));
  2094.     oldmax = (**popup).r.maxbounds;
  2095.     if (! EqualRect(&oldmax, maxbounds)) {
  2096.         if ((**popup).attr.draw) {
  2097.             PopupPortSetup(popup);
  2098.             PopupErase(popup);
  2099.             PopupPortRestore(popup);
  2100.         }
  2101.         (**popup).r.maxbounds = *maxbounds;
  2102.         PopupChanged(popup);
  2103.     }
  2104. }
  2105.  
  2106. /* return popup's title string */
  2107. void PopupTitle(PopupHandle popup, Str255 title)
  2108. {
  2109.     *title = 0;
  2110.     if ((**popup).attr.title.str) {
  2111.         BlockMove(*(**popup).attr.title.str, title,
  2112.             **(**popup).attr.title.str + 1);
  2113.     }
  2114. }
  2115.  
  2116. /* set popup's title string */
  2117. void PopupTitleSet(PopupHandle popup, const Str255 title)
  2118. {
  2119.     Str255 oldtitle;
  2120.     
  2121.     require(PopupValid(popup));
  2122.     if ((**popup).attr.title.str) {
  2123.         PopupTitle(popup, oldtitle);
  2124.         if (! EqualString(title, oldtitle, true, true)) {
  2125.             PtrToXHand(title, (**popup).attr.title.str, *title + 1);
  2126.             PopupChanged(popup);
  2127.         }
  2128.     }
  2129. }
  2130.  
  2131. /* set width of popup's title; the title is resized dynamically
  2132.     if the width is zero */
  2133. void PopupTitleWidthSet(PopupHandle popup, short width)
  2134. {
  2135.     require(width >= 0);
  2136.     if (width != (**popup).attr.title.width) {
  2137.         (**popup).attr.title.width = width;
  2138.         PopupChanged(popup);
  2139.     }
  2140. }
  2141.  
  2142. /* set the text style in which the popup's title will be drawn */
  2143. void PopupTitleStyleSet(PopupHandle popup, Style style)
  2144. {
  2145.     if (style != (**popup).attr.title.style) {
  2146.         (**popup).attr.title.style = style;
  2147.         PopupChanged(popup);
  2148.     }
  2149. }
  2150.  
  2151. /* set whether the popup will use the window's font for drawing the
  2152.     title, current selection, and menu */    
  2153. void PopupUseWFontSet(PopupHandle popup, Boolean wfont)
  2154. {
  2155.     if (wfont != (**popup).attr.wfont) {
  2156.         (**popup).attr.wfont = wfont;
  2157.         PopupChanged(popup);
  2158.     }
  2159. }
  2160.  
  2161. /* set whether the popup will use a fixed width or will be resized
  2162.     dynamically */
  2163. void PopupFixedWidthSet(PopupHandle popup, Boolean fixedwidth)
  2164. {
  2165.     if (fixedwidth != (**popup).attr.fixedwidth) {
  2166.         (**popup).attr.fixedwidth = fixedwidth;
  2167.         PopupChanged(popup);
  2168.     }
  2169. }
  2170.  
  2171. /* set the justification style for drawing the popup */
  2172. void PopupJustSet(PopupHandle popup, short just)
  2173. {
  2174.     if (just != (**popup).attr.just) {
  2175.         (**popup).attr.just = just;
  2176.         PopupChanged(popup);
  2177.     }
  2178. }
  2179.  
  2180. /*---------------------------------------------------------------------------*/
  2181. /* allocation and disposal */
  2182. /*---------------------------------------------------------------------------*/
  2183.  
  2184. /*    Create a popup menu within the rectangle in the specified port. Drawing is
  2185.     initially off. Since the popup's title is initially empty, you should call
  2186.     PopupTitleSet if you want the popup to have a title. When you're finished
  2187.     configuring the popup, call PopupDrawSet to enable drawing and then call
  2188.     PopupCalculate. The popup's rectangles are only calculated when drawing
  2189.     is enabled. The popup menu allocates several utility handles, but will
  2190.     function, albeit not as well, even if it can't allocate any of the utility
  2191.     handles. To reduce flicker, the popup menu is drawn to an offscreen
  2192.     graphics world and then copied to the screen. The storage for the
  2193.     offscreen pixmap is kept in a relocatable and purgeable block. */
  2194. PopupHandle PopupBegin(GrafPtr port, MenuHandle menu, const Rect *maxbounds)
  2195. {
  2196.     PopupHandle popup;
  2197.     Rect globalbounds;
  2198.     void *tmp;
  2199.     
  2200.     require(menu != NULL);
  2201.     require(RectValid(maxbounds));
  2202.     
  2203.     /* allocate popup */
  2204.     popup = (PopupHandle) NewHandleClear(sizeof(PopupType));
  2205.     if (popup) {
  2206.     
  2207.         /* initialize internal state */
  2208.         (**popup).version = kPopupVersion;
  2209.         (**popup).port = port;
  2210.         (**popup).menu = menu;
  2211.         (**popup).private.mHandle = menu;
  2212.         (**popup).private.mID = (**menu).menuID;
  2213.         (**popup).r.maxbounds = *maxbounds;
  2214.         (**popup).state.changed = true;
  2215.         
  2216.         /* initialize flags affecting display and operation of menu */
  2217.         (**popup).attr.visible = true;
  2218.         (**popup).attr.enabled = true;
  2219.         (**popup).attr.mark = checkMark;
  2220.         #if TEST_FLUSH_RIGHT
  2221.             (**popup).attr.just = teFlushRight;
  2222.         #else
  2223.             (**popup).attr.just = PopupJust(popup);
  2224.         #endif
  2225.         
  2226.         /* allocate title */
  2227.         tmp = NewHandleClear(1);
  2228.         (**popup).attr.title.str = tmp;
  2229.  
  2230.         /* allocate region for saving and restoring the clip region */
  2231.         tmp = NewRgn();
  2232.         (**popup).draw.save.clip = tmp;
  2233.         
  2234.         /* allocate glue handle for MDEF */
  2235.         tmp = NewHandle(sizeof(PatchMenuProcStructure));
  2236.         if (tmp)
  2237.             (**popup).menuProc = tmp;
  2238.     
  2239.         /* allocate utility region */
  2240.         tmp = NewRgn();
  2241.         (**popup).draw.utilRgn = tmp;
  2242.  
  2243.         /* Allocate an offscreen graphics world. The offscreen pixmap is
  2244.             purgeable, since we can always rebuild the data it contains and
  2245.             since we can always draw the popup to the onscreen port, though
  2246.             the image may flicker a bit. */
  2247.         #if DRAW_OFFSCREEN
  2248.             if (MacHasGWorlds()) {
  2249.                 Rect globalBounds;
  2250.                 GWorldPtr gworld;
  2251.                 GrafPtr savePort;
  2252.                 GetPort(&savePort);
  2253.                 SetPort((**popup).port);
  2254.                 globalBounds = (**popup).r.maxbounds;
  2255.                 RectLocalToGlobal(&globalBounds);
  2256.                 if (NewGWorld(&gworld, 0, &globalBounds, NULL, NULL, pixPurge) == noErr)
  2257.                     (**popup).draw.gworld = gworld;
  2258.                 SetPort(savePort);
  2259.             }
  2260.         #endif /* DRAW_OFFSCREEN */
  2261.     }
  2262.     ensure(! popup || PopupValid(popup));
  2263.     return(popup);
  2264. }
  2265.  
  2266. /* dispose of the popup menu */
  2267. void PopupEnd(PopupHandle popup)
  2268. {
  2269.     require(! popup || PopupValid(popup));
  2270.     if (popup) {
  2271.         if ((**popup).menuProc) DisposeHandle((**popup).menuProc);
  2272.         if ((**popup).draw.gworld) DisposeGWorld((**popup).draw.gworld);
  2273.         if ((**popup).draw.utilRgn) DisposeRgn((**popup).draw.utilRgn);
  2274.         if ((**popup).draw.save.clip) DisposeRgn((**popup).draw.save.clip);
  2275.         if ((**popup).attr.title.str) DisposeHandle((**popup).attr.title.str);
  2276.         DisposeHandle((Handle) popup);
  2277.         popup = NULL;
  2278.     }
  2279.     ensure(! PopupValid(popup));
  2280. }
  2281.