home *** CD-ROM | disk | FTP | other *** search
/ Celestin Apprentice 2 / Apprentice-Release2.iso / Source Code / Pascal / Libraries / DBL Pascal Library / DefProcs / PopUp / PopUpCDEF.p < prev    next >
Encoding:
Text File  |  1993-03-30  |  29.6 KB  |  896 lines  |  [TEXT/PJMM]

  1. unit PopUpCDEF;
  2.  
  3. {David B. Lamkins, June 1991}
  4. {}
  5. {This is a CDEF for a pop-up menu that provides the following features:}
  6. {    • Displays styled items and hierarchical menus (limited to one level of submenus).}
  7. {    • Pop-up is in same font as menu.}
  8. {    • Limits drawn size to width of control rect, trims item text to fit…}
  9. {    • Recognizes both HiliteControl and HiliteMenu to enable/disable menu.}
  10. {    • Can pop-up even if the control is not visible, by calling TrackControl.}
  11. {    • Is (or should be… hasn't been tested) 32-bit clean.}
  12. {    • Handles “iconOnly” variant (CDEF ID*16+1) to support mixed pop-up/TE arrangement.}
  13. {    • Handles “useCTitle” variant (CDEF ID*16+2) to take title from control rather than menu.}
  14. {    • Handles “noShrink” variant (CDEF ID*16+4) to force menu not to shrink narrower than control rect.}
  15. {    • Handles “useWFont” variant (CDEF ID*16+8) to display menu using window font.}
  16. {}
  17. {Revision History:}
  18. {    DBL, 1 July 1991:    Now correctly handles 1- and 2-item editable pop-ups.}
  19. {                            No longer reduces control value after selecting a submenu.}
  20. {    DBL, 2 July 1991:    Doesn't draw if owner is not visible.}
  21. {                            Force contrlMax and contrlMin.}
  22. {                            Editable pop-up doesn't grab insertion point.}
  23. {    DBL, 3 July 1991:    Dim and disable TE fields with disabled editable pop-up.}
  24. {    DBL, 17 July 1991:    Don't wipe out TE contents if menu is initially empty.}
  25. {    DBL, 21 October 1991:    Use TE contents during initialization.}
  26. {    DBL, 5 June 1992:    Arrange menu handle and ID in private data to match Apple's CDEF.}
  27. {    DBL, 30 March 1993:    Case- and diacritical-insensitive match of TE to menu.}
  28. {                            Change dimming of TE so thin glyphs are readable on 1-bit displays.}
  29. {                            Finally fixed initialization of TE from menu.}
  30. {}
  31. {Bugs:}
  32. {    When high byte of control value is <> 0, doesn't pin control value or check for valid submenu before drawing.}
  33. {}
  34. {Use:}
  35. {    CNTL proc ID = 80, 81, 82, 84, 86, 88, 89, 90, 92 or 94 (since this is CDEF 5).}
  36. {    CNTL min initializes MENU ID — menu must be installed in “hierarchical” menu list.}
  37. {    CNTL max initializes item number of associated TE field for +1 and +9 variants.}
  38. {    CNTL title is menu title for +2, +4, +6, +10 and +14 variants.}
  39. {    CNTL refcon is unused.}
  40. {    CNTL value in template may be chosen to initially show some menu item other than first.}
  41. {    contrlValue is adjusted according to menu selection; high byte contains menu ID if selection}
  42. {      was from a submenu, otherwise zero. A menu may not have more than 255 items.}
  43. {    contrlMin is set to 1 and contrlMax is set to 255.}
  44. {    Calling SetCtlValue changes the displayed item. Can only be used to select main menu.}
  45. {    SetCtlMin and SetCtlMax do not affect the range of items displayed or selectable.}
  46. {    DITL rect must be at least as large as CNTL rect, otherwise Dialog Mgr won't detect hit in control.}
  47. {    A few characters of the item will be drawn, no matter how small you make the CNTL rect.}
  48. {    If menu is changed using Menu Mgr calls, use Draw1Control to update appearance of menu.}
  49. {    The TE field associated with an editable pop-up is changed to a disabled static text field}
  50. {      when the pop-up control is disabled.}
  51. {    If you want to pop up a hidden menu under the mouse: call MoveControl(…) with local mouse point,}
  52. {      followed by TrackControl ( …, POINTER(-1))}
  53. {    Don't use with item icons or command key equivalents.}
  54. {    You're responsible for installing and removing menus used by the control.}
  55. {    If the dialog contains TE fields, “useWFont” requires special handling. The following is}
  56. {      derived from Apple's Q&A Stack:}
  57. {            theDialog := GetNewDialog(…);}
  58. {            SetPort(theDialog);}
  59. {            TextFont(…);}
  60. {            TextSize(…);}
  61. {            ShowWindow(theDialog);}
  62. {            for i := 1 to 3 do}
  63. {                if EventAvail(everyEvent, evt) then}
  64. {                    ;}
  65. {            with DialogPeek(theDialog)^.textH^^ do}
  66. {                begin}
  67. {                    txFont := theDialog^.txFont;}
  68. {                    txSize := theDialog^.txSize;}
  69. {                end;}
  70. {            InitCursor;}
  71. {            repeat}
  72. {                ModalDialog(…);}
  73. {                …}
  74. {            until …;}
  75. {            DisposDialog(theDialog);}
  76. {}
  77. {Notes:}
  78. {    This could be rewritten to be a lot smaller by inlining nearly all of the routines. However,}
  79. {    I found it very difficult to keep track of what was going when I rewrote this as a monolith.}
  80. {    I figured that the clarity was worth the 700 or so bytes it cost.}
  81.  
  82. interface
  83.  
  84.     function main (varCode: Integer; theControl: ControlHandle; message: Integer; param: Longint): Longint;
  85.  
  86. implementation
  87.  
  88. {$SETC Debugging=False}
  89.  
  90.     function main (varCode: Integer; theControl: ControlHandle; message: Integer; param: Longint): Longint;
  91.         const
  92.             minChars = 3;            {show at least this many chars from the menu item}
  93.             iconOnly = 1;            {our variant codes}
  94.             useCTitle = 2;
  95.             noShrink = 4;
  96.             partCode = 1;            {our part code}
  97.  
  98.         type
  99.             PrivateData = record
  100.                     menuH: MenuHandle;    {handle to our menu}
  101.                     menuID: Integer;    {resource ID of menu — by convention, the first item}
  102.                     lastMenuID: Integer;    {resource ID of last menu selected}
  103.                     teItem: Integer;    {item list number of associated TE item}
  104.                     boundsRect: Rect;    {control's bounding rect - size of control plus drop shadow}
  105.                     menuRect: Rect;    {bounds of pop-up portion of control}
  106.                     shadowRect: Rect;    {bounds of pop-up portion, plus the drop shadow}
  107.                     menuEnabled: Boolean;    {true if menu and control are both enabled}
  108.                     colorQD: Boolean;    {set at init time if Color QD exists}
  109.                     useSysFont: Boolean;    {set at init time if not useWFont variant}
  110.                     teUnInited: Boolean;    {initialization flag}
  111.                     patGrey: Pattern;    {our own grey pattern - can't use globals}
  112.                     downIcon: BitMap;    {the infamous downward-pointing arrow}
  113.                     downIconBits: array[1..16] of Integer;    {the arrow’s bitmap}
  114.                 end;
  115.             DataPtr = ^PrivateData;
  116.             DataHandle = ^DataPtr;
  117.  
  118.     {Recursive function finds maximum width of menu. Limits depth of submenus.}
  119.         function MeasureItemsWidth (menuH: MenuHandle; menuWidth, depthRemaining: Integer): Integer;
  120.             var
  121.                 item: Integer;
  122.                 cmdChar: Char;
  123.                 markChar: Char;
  124.                 tempMenuH: MenuHandle;
  125.                 itemStyle: Style;
  126.                 itemText: Str255;
  127.                 itemWidth: Integer;
  128.         begin
  129.             if depthRemaining >= 0 then    {haven't reached depth cutoff}
  130.                 for item := 1 to CountMItems(menuH) do    {measure each item…}
  131.                     begin
  132.                         GetItemCmd(menuH, item, cmdChar);
  133.                         if cmdChar = CHR($1B) then
  134.                             begin
  135.                                 GetItemMark(menuH, item, markChar);
  136.                                 tempMenuH := GetMenu(ORD(markChar));
  137.                                 if tempMenuH <> nil then
  138.                                     menuWidth := MeasureItemsWidth(tempMenuH, menuWidth, depthRemaining - 1);
  139.                             end
  140.                         else
  141.                             begin
  142.                                 GetItemStyle(menuH, item, itemStyle);
  143.                                 GetItem(menuH, item, itemText);
  144.                                 TextFace(itemStyle);
  145.                                 itemWidth := TextWidth(@itemText[1], 0, length(itemText));
  146.                                 if itemWidth > menuWidth then    {…and remember the widest one}
  147.                                     menuWidth := itemWidth;
  148.                             end;
  149.                     end;
  150.             MeasureItemsWidth := menuWidth;
  151.         end;
  152.  
  153.         const
  154.             CalcMenuSizeTrapNum = $148;    {ToolTrap}
  155.             patchCode = '0000000000004E712F2F000441FA000A2F08207AFFEC4ED0205F225F2251303AFFE4B06900026F04334000024ED0';
  156.  
  157. { The following code puts a "tail patch" on CalcMenuSize. The patch forces menuWidth}
  158. { to be at least as wide as we want. We use this to keep the menu from shrinking horizontally}
  159. { during PopUpMenuSelect.}
  160. { 00000                                  proc}
  161. { 00000                       addr    ds.l        1                    ;the address of the real _CalcMenuSize}
  162. { 00004                       wid    ds.w        1                    ;our minimum width}
  163. { 00006   4E71            entry    nop                                ;enter the patch here}
  164. { 00008   2F2F 0004                move.l        4(sp),-(sp)        ;copy the MenuHandle}
  165. { 0000C   41FA 000A            lea.l        cont(pc),a0        ;make _CalcMenuSize return to the patch}
  166. { 00010   2F08                    move.l        a0,-(sp)}
  167. { 00012   207A FFEC                movea.l    addr(pc),a0        ;do the real _CalcMenuSize}
  168. { 00016   4ED0                    jmp        (a0)}
  169. { 00018   205F            cont    movea.l    (sp)+,a0            ;return here, and save return address}
  170. { 0001A   225F                    movea.l    (sp)+,a1            ;get the MenuHandle}
  171. { 0001C   2251                    movea.l    (a1),a1}
  172. { 0001E   303A FFE4                move.w    wid(pc),d0        ;get minimum width}
  173. { 00022   B069 0002            cmp.w        2(a1),d0            ;compare to MenuHandle.menuWidth}
  174. { 00026   6F04                    ble.s        return                ;at least as wide as the minimum?}
  175. { 00028   3340 0002            move.w    d0,2(a1)            ;no, force it}
  176. { 0002C   4ED0            return    jmp        (a0)                ;return from patched _CalcMenuSize}
  177. { 0002E                            end}
  178.  
  179.         type
  180.             PatchParams = record    {Overlays the patch code to fill in runtime variables}
  181.                     realTrap: Longint;
  182.                     ourWidth: Integer;
  183.                 end;
  184.             PatchParamsPtr = ^PatchParams;
  185.  
  186.         var
  187.             oldTrapAddress: Longint;
  188.             thePatch: Ptr;
  189.  
  190.         procedure PatchCalcMenuSize (minimumWidth: Integer);
  191.         begin
  192.             oldTrapAddress := NGetTrapAddress(CalcMenuSizeTrapNum, ToolTrap);
  193.             thePatch := NewPtr(length(patchCode) div 2);
  194.             if thePatch <> nil then
  195.                 begin
  196.                     StuffHex(thePatch, patchCode);
  197.                     with PatchParamsPtr(thePatch)^ do
  198.                         begin
  199.                             realTrap := oldTrapAddress;
  200.                             ourWidth := minimumWidth;
  201.                         end;
  202.                     NSetTrapAddress(ORD(thePatch) + SIZEOF(Integer) + SIZEOF(Longint), CalcMenuSizeTrapNum, ToolTrap);
  203.                 end;
  204.         end;
  205.  
  206.         procedure UnpatchCalcMenuSize;
  207.         begin
  208.             if thePatch <> nil then
  209.                 begin
  210.                     NSetTrapAddress(oldTrapAddress, CalcMenuSizeTrapNum, ToolTrap);
  211.                     DisposPtr(thePatch);
  212.                 end;
  213.         end;
  214.  
  215.     {Prepare to do PopUpMenuSelect. If control is visible, erase the menu and position the}
  216.     {pop-up relative to the menu rect. If the control is invisible, position the pop-up relative}
  217.     {to the control rect.}
  218.         function PreparePopUp: Point;
  219.             var
  220.                 pt: Point;
  221.         begin
  222.             with theControl^^, DataHandle(contrlData)^^ do
  223.                 begin
  224.                     if contrlVis = 0 then    {…figure out where to place the pop-up and…}
  225.                         begin
  226.                             pt := contrlRect.topLeft;
  227.                             AddPt(Point($FFF70002), pt);
  228.                         end
  229.                     else
  230.                         begin
  231.                             EraseRect(shadowRect);    {wipe out the menu, then…}
  232.                             pt := menuRect.topLeft;
  233.                             AddPt(Point($00010001), pt);
  234.                         end;
  235.                     LocalToGlobal(pt);    {…track the popup selection}
  236.                 end;
  237.             PreparePopUp := pt;
  238.         end;
  239.  
  240.     {Draw string in the current font. If wider than specified, trailing chars are trimmed}
  241.     {until minChars of original string remain. Note that string trimmed to minChars may}
  242.     {still exceed width.}
  243.         procedure DrawClippedString (theString: Str255; width, minChars: Integer);
  244.             const
  245.                 ellipsis = '…';
  246.             var
  247.                 thisLength, ellipsisLength: Integer;
  248.         begin
  249.             ellipsisLength := CharWidth(ellipsis);
  250.             thisLength := length(theString);
  251.             if TextWidth(@theString[1], 0, thisLength) > width then
  252.                 begin
  253.                     repeat
  254.                         thisLength := thisLength - 1
  255.                     until (TextWidth(@theString[1], 0, thisLength) + ellipsisLength <= width) or (thisLength <= minChars);
  256.                     thisLength := thisLength + 1;
  257.                     theString[thisLength] := ellipsis;
  258. {$PUSH}
  259. {$R-}
  260.                     theString[0] := CHR(thisLength);
  261. {$POP}
  262.                 end;
  263.             DrawString(theString);
  264.         end;
  265.  
  266.         const
  267.             iconSize = 16;    {don't change this unless you change the bitmap allocation and initialization}
  268.             halfIconSize = iconSize div 2;
  269.             optionVCheckMark = '√';    {real check-mark exists only in system font}
  270.  
  271.         var
  272.             savePort: GrafPtr;
  273.             saveFont: Integer;
  274.             saveSize: Integer;
  275.             saveFace: Style;
  276.             info: FontInfo;
  277.             titleRect: Rect;
  278.             iconRect: Rect;
  279.             adjustedControlRect: Rect;
  280.             textBaseline: Integer;
  281.             titleLeft: Integer;
  282.             itemLeft: Integer;
  283.             itemWidth: Integer;
  284.             altTitle: Boolean;
  285.             noText: Boolean;
  286.             useTE: Boolean;
  287.  
  288.         procedure PrepareToDraw;
  289.             const
  290.                 titleInset = 6;
  291.                 titleGap = 1;
  292.                 itemInset = 5;        {plus width of checkmark}
  293.                 itemTail = 3;            {plus width of icon}
  294.                 minWidth = 21;
  295.                 iconOffset = 2;
  296.             var
  297.                 itemKind: Integer;
  298.                 itemHandle: Handle;
  299.                 itemRect: Rect;
  300.                 textKind: Integer;
  301.                 newRight: Integer;
  302.                 centerLine: Integer;
  303.                 titleWidth: Integer;
  304.                 menuWidth: Integer;
  305.                 itemText: Str255;
  306.                 checkMarkWidth: Integer;
  307.         begin
  308.             with theControl^^, DataHandle(contrlData)^^ do
  309.                 begin
  310.                     with contrlOwner^ do
  311.                         begin    {remember the original font}
  312.                             saveFont := txFont;
  313.                             saveSize := txSize;
  314.                             saveFace := txFace;
  315.                         end;
  316.                     if useSysFont then        {if we need system font, set it}
  317.                         begin
  318.                             TextSize(0);
  319.                             TextFont(0);
  320.                         end;
  321.                     noText := BAND(varCode, iconOnly) <> 0;    {is this an icon-only pop-up?}
  322.                     menuEnabled := BTST(menuH^^.enableFlags, 0) and (contrlHilite <> 255);    {is our menu enabled?}
  323.                     useTE := noText and (teItem > 0);    {do we have an associated TE?}
  324.                     GetDItem(contrlOwner, teItem, itemKind, itemHandle, itemRect);
  325.                     textKind := BAND(itemKind, BNOT(itemDisable));
  326.                     useTE := useTE and ((textKind = statText) or (textKind = editText));    {is the TE item really a TE?}
  327.                     altTitle := BAND(varCode, useCTitle) <> 0;    {do we use title from CNTL?}
  328.                     TextFace([]);    {make sure our face is clean}
  329.                     GetFontInfo(info);    {we'll need to know about the font, soon}
  330.                     if noText then
  331.                         menuWidth := minWidth    {icon-only pop-up has fixed width}
  332.                     else
  333.                         begin    {normal pop-up width has to be figured using current font and style}
  334.                             if useSysFont then
  335.                                 checkMarkWidth := CharWidth(CHR(checkMark))
  336.                             else
  337.                                 checkMarkWidth := CharWidth(optionVCheckMark);
  338.                             menuWidth := MeasureItemsWidth(menuH, 0, 1) + itemInset + checkMarkWidth + itemTail + iconSize;
  339.                             TextFace([]);    {blank face again}
  340.                         end;
  341.                     if altTitle then    {pick up the title from the right place…}
  342.                         itemText := contrlTitle
  343.                     else
  344.                         itemText := menuH^^.menuData;
  345.                     titleWidth := TextWidth(@itemText[1], 0, length(itemText));    {…and measure its width}
  346.                     with info, contrlRect do
  347.                         if noText then    {icon-only popup is fixed size}
  348.                             SetRect(adjustedControlRect, left, top, left + minWidth, top + iconSize)
  349.                         else    {normal pop-up size depends on font used}
  350.                             begin
  351.                                 adjustedControlRect := contrlRect;
  352.                                 newRight := left + titleInset + titleWidth + titleGap + menuWidth;
  353.                                 if (BAND(varCode, noShrink) = 0) and (newRight < right) then
  354.                                     adjustedControlRect.right := newRight;
  355.                                 adjustedControlRect.bottom := top + ascent + descent + leading + 2;
  356.                                 textBaseline := adjustedControlRect.bottom - descent - leading - 1;
  357.                                 with adjustedControlRect do
  358.                                     if (textBaseline - top - ascent) > (bottom - textBaseline - descent) then
  359.                                         bottom := bottom + 1;    {equalize top/bottom spacing}
  360.                             end;
  361.                     titleRect := adjustedControlRect;    {size title portion of control separately}
  362.                     with titleRect do
  363.                         begin
  364.                             top := top + 1;
  365.                             if titleWidth > 0 then
  366.                                 right := left + titleInset + titleWidth + titleGap
  367.                             else
  368.                                 right := left;    {empty if no title}
  369.                             if right > contrlRect.right then    {Don't overflow the CNTL's rect}
  370.                                 right := contrlRect.right;
  371.                             titleLeft := left + titleInset;
  372.                         end;
  373.                     menuRect := adjustedControlRect;    {size pop-up portion separately}
  374.                     if not noText then
  375.                         menuRect.left := titleRect.right;    {menu starts where its title ends}
  376.                     with menuRect do
  377.                         begin
  378.                             itemWidth := right - left - itemInset - checkMarkWidth - itemTail - iconSize;
  379.                             itemLeft := left + itemInset + checkMarkWidth;
  380.                             centerLine := (bottom + top) div 2;
  381.                             SetRect(iconRect, right - iconSize - iconOffset, centerLine - halfIconSize, right - iconOffset, centerLine + halfIconSize);
  382.                         end;
  383.                     with menuRect do    {shadow extends beyond pop-up…}
  384.                         SetRect(shadowRect, left, top, right + 1, bottom + 1);
  385.                     with adjustedControlRect do    {…and beyond overall control}
  386.                         SetRect(boundsRect, left, top, right + 1, bottom + 1);
  387.                 end;
  388.         end;
  389.  
  390.         procedure RestoreAfterDrawing;
  391.         begin
  392.             with theControl^^, DataHandle(contrlData)^^ do
  393.                 begin
  394.                     TextFont(saveFont);    {put everything back the way it was}
  395.                     TextSize(saveSize);
  396.                     TextFace(saveFace);
  397.                 end;
  398.         end;
  399.  
  400.         var
  401.             teSaveID: Integer;
  402.             teSaveSelStart: Integer;
  403.             teSaveSelEnd: Integer;
  404.  
  405.         function SaveTE: Integer;
  406.         begin
  407.             with theControl^^, DialogPeek(contrlOwner)^ do
  408.                 begin
  409.                     if useTE then
  410.                         teSaveID := editField + 1
  411.                     else
  412.                         teSaveID := 0;
  413.                     if teSaveID > 0 then
  414.                         with textH^^ do
  415.                             begin
  416.                                 teSaveSelStart := selStart;
  417.                                 teSaveSelEnd := selEnd;
  418.                             end;
  419.                 end;
  420.             SaveTE := teSaveID;
  421.         end;
  422.  
  423.         procedure RestoreTE;
  424.         begin
  425.             if teSaveID > 0 then
  426.                 SelIText(theControl^^.contrlOwner, teSaveID, teSaveSelStart, teSaveSelEnd);
  427.         end;
  428.  
  429.         procedure DrawTitle;
  430.         begin
  431.             with theControl^^, DataHandle(contrlData)^^ do
  432.                 begin    {draw the title}
  433.                     EraseRect(titleRect);
  434.                     MoveTo(titleLeft, textBaseline);
  435.                     if altTitle then
  436.                         DrawString(contrlTitle)
  437.                     else
  438.                         DrawString(menuH^^.menudata);
  439.                     if contrlHilite = partCode then    {hilite the title}
  440.                         InvertRect(titleRect);
  441.                 end;
  442.         end;
  443.  
  444.         procedure DrawMenu;
  445.             var
  446.                 clip: RgnHandle;
  447.                 item: Integer;
  448.                 itemStyle: Style;
  449.                 itemText: Str255;
  450.                 tempMenuH: MenuHandle;
  451.                 itemKind: Integer;
  452.                 itemHandle: Handle;
  453.                 itemRect: Rect;
  454.                 saveClip: RgnHandle;
  455.         begin
  456.             with theControl^^, DataHandle(contrlData)^^ do
  457.                 begin
  458.                     if not noText then
  459.                         begin
  460.                             EraseRect(menuRect);    {draw the item text}
  461.                             MoveTo(itemLeft, textBaseline);
  462.                             tempMenuH := GetMHandle(lastMenuID);
  463.                             if tempMenuH = nil then
  464.                                 tempMenuH := menuH;
  465.                             item := BAND(contrlValue, $FF);
  466.                             GetItem(tempMenuH, item, itemText);
  467.                             GetItemStyle(tempMenuH, item, itemStyle);
  468.                             TextFace(itemStyle);
  469.                             DrawClippedString(itemText, itemWidth, minChars);
  470.                         end;
  471.                     clip := NewRgn;        {draw the downarrow icon}
  472.                     if clip <> nil then
  473.                         RectRgn(clip, menuRect);
  474.                     downIcon.baseAddr := @downIconBits;    {remember, the bit map is in a handle that may have moved!}
  475.                     CopyBits(downIcon, contrlOwner^.portBits, downIcon.bounds, iconRect, srcOr, clip);
  476.                     if clip <> nil then
  477.                         DisposeRgn(clip);
  478.                     if not menuEnabled then
  479.                         begin    {grey out disabled menu}
  480.                             if useTE then
  481.                                 begin    {change associated TE to static text…}
  482.                                     GetDItem(contrlOwner, teItem, itemKind, itemHandle, itemRect);
  483.                                     SetDItem(contrlOwner, teItem, statText + itemDisable, itemHandle, itemRect);
  484.                                     with DialogPeek(contrlOwner)^ do
  485.                                         if editField + 1 = teItem then
  486.                                             begin
  487.                                                 SelIText(contrlOwner, teItem, 0, 0);    {remove any hilite}
  488.                                                 editField := -1;
  489.                                             end;
  490.                                     PenNormal;    {draw fake frame around our static text}
  491.                                     InsetRect(itemRect, -3, -3);
  492.                                     FrameRect(itemRect);
  493.                                     InsetRect(itemRect, 1, 1);    {inset a grey frame.}
  494.                                     PenSize(2, 2);
  495.                                     PenPat(patGrey);
  496.                                     FrameRect(itemRect);
  497.                                 end;
  498.                             PenPat(patGrey);
  499.                             PenMode(patBic);
  500.                             PaintRect(adjustedControlRect);    {grey out the interior of the pop-up}
  501.                         end;
  502.                     PenNormal;    {frame is never greyed out}
  503.                     FrameRect(menuRect);        {draw the frame…}
  504.                     with menuRect do
  505.                         begin
  506.                             MoveTo(left + 2, bottom);        {…and the shadow}
  507.                             LineTo(right, bottom);
  508.                             LineTo(right, top + 2);
  509.                         end;
  510.                 end;
  511.         end;
  512.  
  513.         const
  514.             SysFontFam = $BA6;    {address of system global}
  515.  
  516.         type
  517.             IntPtr = ^Integer;
  518.  
  519.         var
  520.             colorPort: CGrafPtr;
  521.             monoPort: GrafPtr;
  522.  
  523.     {Because the standard MDEF always draws in the system font, we have to temporarily}
  524.     {override the system font in order to draw the pop-up in the window’s font.}
  525.         procedure SetPopUpFont;
  526.         begin
  527.             with theControl^^, DataHandle(contrlData)^^ do
  528.                 begin
  529.                     IntPtr(SysFontFam)^ := contrlOwner^.txFont;
  530.                     if colorQD then
  531.                         begin
  532.                             GetCWMgrPort(colorPort);
  533.                             SetPort(GrafPtr(colorPort));
  534.                         end
  535.                     else
  536.                         begin
  537.                             GetWMgrPort(monoPort);
  538.                             SetPort(monoPort);
  539.                         end;
  540.                     TextFont(contrlOwner^.txFont);
  541.                     TextSize(contrlOwner^.txSize);
  542.                 end;
  543.         end;
  544.  
  545.         procedure RestorePopUpFont;
  546.         begin
  547.             with theControl^^, DataHandle(contrlData)^^ do
  548.                 begin
  549.                     IntPtr(SysFontFam)^ := SystemFont;
  550.                     TextFont(SystemFont);
  551.                     TextSize(0);
  552.                     SetPort(contrlOwner);
  553.                 end;
  554.         end;
  555.  
  556.         procedure CoupleMenuToTE;
  557.             var
  558.                 oldTE: Integer;
  559.                 item: Integer;
  560.                 itemCount: Integer;
  561.                 itemKind: Integer;
  562.                 itemHandle: Handle;
  563.                 itemRect: Rect;
  564.                 menuText: Str255;
  565.                 startItem: Integer;
  566.                 foundItem: Integer;
  567.                 inMenu: Boolean;
  568.                 itemText: Str255;
  569.                 reFrame: Boolean;
  570.         begin
  571.             with theControl^^, DataHandle(contrlData)^^ do
  572.                 begin
  573.                     oldTE := SaveTE;
  574.                     if (contrlHilite >= partCode) or teUnInited then    {we're about to pop up or disable the menu, or are initing}
  575.                         begin
  576.                             GetDItem(contrlOwner, teItem, itemKind, itemHandle, itemRect);
  577.                             GetIText(itemHandle, itemText);    {what's in the TE?}
  578.                             if length(itemText) > 0 then
  579.                                 begin
  580.                                     itemCount := CountMItems(menuH);    {how many menu items?}
  581.                                     startItem := 1;
  582.                                     if itemCount > 1 then
  583.                                         begin    {what's the first “permanent” item in the menu?}
  584.                                             GetItem(menuH, 2, menuText);
  585.                                             if menuText[1] = '-' then
  586.                                                 startItem := 3;
  587.                                         end;
  588.                                     inMenu := False;    {haven't found TE contents in menu, yet}
  589.                                     for item := startItem to itemCount do
  590.                                         begin
  591.                                             GetItem(menuH, item, menuText);    {look in the menu}
  592.                                             if EqualString(menuText, itemText, False, False) then
  593.                                                 begin
  594.                                                     inMenu := True;    {menu item matched TE…}
  595.                                                     foundItem := item;    {…so remember where}
  596.                                                     Leave;
  597.                                                 end;
  598.                                         end;
  599.                                     if inMenu then
  600.                                         begin    {TE text matches a permanent item in the menu}
  601.                                             if startItem > 1 then {remove first two items}
  602.                                                 begin
  603.                                                     DelMenuItem(menuH, 1);
  604.                                                     DelMenuItem(menuH, 1);
  605.                                                     contrlValue := foundItem - 2;
  606.                                                 end
  607.                                             else
  608.                                                 contrlValue := foundItem;
  609.                                         end
  610.                                     else {TE text is not in menu}
  611.                                         begin
  612.                                             if startItem > 1 then    {alter existing temporary item}
  613.                                                 SetItem(menuH, 1, itemText)
  614.                                             else
  615.                                                 begin    {create new temporary item and divider}
  616.                                                     InsMenuItem(menuH, '-', 0);
  617.                                                     InsMenuItem(menuH, itemText, 0);
  618.                                                 end;
  619.                                             contrlValue := 1;
  620.                                         end;
  621.                                 end
  622.                             else {length(itemText) = 0)}
  623.                                 if teUninited then
  624.                                     begin
  625.                                         GetItem(menuH, contrlValue, menuText);    {look in the menu…}
  626.                                         SetIText(itemHandle, menuText);    {…and stuff it into the TE}
  627.                                     end;
  628.                             teUnInited := False;
  629.                         end
  630.                     else    {we're not about to pop up the menu, but we are drawing it…}
  631.                         begin    {…so update its associated TE field}
  632.                             GetItem(menuH, contrlValue, itemText);
  633.                             if length(itemText) > 0 then
  634.                                 begin
  635.                                     GetDItem(contrlOwner, teItem, itemKind, itemHandle, itemRect);
  636.                                     reFrame := menuEnabled and (itemKind = statText + itemDisable);
  637.                                     if reFrame then
  638.                                         begin
  639.                                             SetDItem(contrlOwner, teItem, editText, itemHandle, itemRect);
  640.                                             InsetRect(itemRect, -2, -2);
  641.                                             PenNormal;
  642.                                             PenSize(2, 2);
  643.                                             PenMode(patBic);
  644.                                             FrameRect(itemRect);
  645.                                         end;
  646.                                     if not useSysFont then
  647.                                         SetPopUpFont;
  648.                                     SelIText(contrlOwner, teItem, 0, 0);
  649.                                     SetIText(itemHandle, itemText);
  650.                                     if reFrame then
  651.                                         begin
  652.                                             InsetRect(itemRect, -1, -1);
  653.                                             PenNormal;
  654.                                             FrameRect(itemRect);
  655.                                         end;
  656.                                     if not useSysFont then
  657.                                         RestorePopUpFont;
  658.                                 end;
  659.                         end;
  660.                     if oldTE = teItem then        {hilite our TE if the insertion point…}
  661.                         SelIText(contrlOwner, teItem, 0, MAXINT)    {…was in our TE before, otherwise…}
  662.                     else
  663.                         RestoreTE;    {…put the insertion point back where it was}
  664.                 end;
  665.         end;
  666.  
  667.         function NonHierValue: Boolean;
  668.         begin
  669.             NonHierValue := BAND(theControl^^.contrlValue, $FF00) = 0;
  670.         end;
  671.  
  672.         procedure PinControlValue;
  673.             var
  674.                 itemCount: Integer;
  675.         begin
  676.             with theControl^^, DataHandle(contrlData)^^ do
  677.                 begin
  678.                     itemCount := CountMItems(menuH);    {pin control value}
  679.                     if NonHierValue then    {don't pin hierarchical!}
  680.                         begin
  681.                             if contrlValue < 1 then
  682.                                 contrlValue := 1;
  683.                             if contrlValue > itemCount then
  684.                                 contrlValue := itemCount;
  685.                         end;
  686.                 end;
  687.         end;
  688.  
  689.         procedure DoInitControl;
  690.             var
  691.                 macWorld: SysEnvRec;
  692.         begin
  693.             with theControl^^ do
  694.                 begin
  695. {$IFC Debugging}
  696.                     DebugStr('initCntl');
  697. {$ENDC}
  698.                     contrlData := NewHandleClear(SIZEOF(PrivateData));    {allocate private storage}
  699.                     if contrlData <> nil then
  700.                         begin
  701.                             HLock(contrlData);
  702.                             with DataHandle(contrlData)^^ do
  703.                                 begin    {create our local bitmap data}
  704.                                     StuffHex(@patGrey, 'AA55AA55AA55AA55');
  705.                                     StuffHex(@downIconBits[6], '3FF81FF00FE007C003800100');
  706.                                     with downIcon do
  707.                                         begin
  708.                         {Set baseAddr later, when we use this - bitmap is in a handle that may move…}
  709.                                             rowBytes := 2;
  710.                                             SetRect(bounds, 0, 0, iconSize, iconSize);
  711.                                         end;
  712.                                     teItem := contrlMax;        {remember which TE we use}
  713.                                     menuID := contrlMin;        {remember our menu's ID}
  714.                                     lastMenuID := menuID;
  715.                                     contrlMin := 1;                {init min and max values}
  716.                                     contrlMax := 255;
  717.                                     contrlAction := POINTER(-1);    {use our own action proc}
  718.                                     if SysEnvirons(1, macWorld) = noErr then    {is this a color machine?}
  719.                                         colorQD := macWorld.hasColorQD
  720.                                     else
  721.                                         colorQD := False;
  722.                                     useSysFont := BAND(varCode, useWFont) = 0;
  723.                                     teUnInited := True;        {Grrr… need this for one-time initialization in another part of code}
  724.                                 end;
  725.                             HUnLock(contrlData);
  726.                         end;
  727.                 end;
  728.         end;
  729.  
  730.         procedure DoDisposeControl;
  731.         begin
  732.             with theControl^^ do
  733.                 begin
  734. {$IFC Debugging}
  735.                     DebugStr('dispCntl');
  736. {$ENDC}
  737.                     if contrlData <> nil then
  738.                         DisposHandle(contrlData);    {don't need this any more}
  739.                 end
  740.         end;
  741.  
  742.         function DoTestControl: Integer;
  743.         begin
  744.             with DataHandle(theControl^^.contrlData)^^ do
  745.                 begin
  746. {$IFC Debugging}
  747.                     DebugStr('testCntl');
  748. {$ENDC}
  749.                     if menuEnabled and PtInRect(Point(param), menuRect) then
  750.                         DoTestControl := partCode    {hit our control}
  751.                     else
  752.                         DoTestControl := 0;
  753.                 end;
  754.         end;
  755.  
  756.         procedure DoCalcControlRegions;
  757.         begin
  758.             with DataHandle(theControl^^.contrlData)^^ do
  759.                 begin
  760. {$IFC Debugging}
  761.                     DebugStr('calcCRgns, calcCntlRgn, calcThumbRgn');
  762. {$ENDC}
  763.                     if (message <> calcCRgns) or not BTST(param, 31) then
  764.                         RectRgn(RgnHandle(Param), boundsRect);    {return control region}
  765.                 end;
  766.         end;
  767.  
  768.         procedure DoDrawControl;
  769.         begin
  770.             with theControl^^, DataHandle(contrlData)^^ do
  771.                 begin
  772. {$IFC Debugging}
  773.                     DebugStr('drawCntl');
  774. {$ENDC}
  775.                     if (contrlVis <> 0) and (WindowPeek(contrlOwner)^.visible) then
  776.             {draw only if control is visible and owner window is visible}
  777.                         begin
  778.                             GetPort(savePort);    {make sure we have right port}
  779.                             SetPort(contrlOwner);
  780.                             menuH := GetMHandle(menuID);    {get our main menu}
  781.                             if menuH <> nil then    {if menu exists…}
  782.                                 begin
  783.                                     PrepareToDraw;
  784.                                     begin    {…then we have to draw it, now}
  785.                                         PinControlValue;
  786.                                         if useTE then    {this is an “editable” pop-up…}
  787.                                             CoupleMenuToTE;    {…so we may have to adjust its menu…}
  788.                                         if not noText then
  789.                                             DrawTitle;
  790.                                         DrawMenu;
  791.                                     end;    {if contrlVis <> 0 then …}
  792.                                     RestoreAfterDrawing;
  793.                                 end
  794.                             else
  795.                                 FillRect(contrlRect, patGrey);    {no menu? draw a blank…}
  796.                             SetPort(savePort);    {put the port back to wherever it was}
  797.                         end;
  798.                 end;
  799.         end;
  800.  
  801.         procedure DoTrackControl;
  802.             var
  803.                 oldTE: Integer;
  804.                 item: Integer;
  805.                 pt: Point;
  806.                 selected: Longint;
  807.         begin
  808.             with theControl^^, DataHandle(contrlData)^^ do
  809.                 begin
  810. {$IFC Debugging}
  811.                     DebugStr('autoTrack');
  812. {$ENDC}
  813.                     GetPort(savePort);    {make sure we have right port}
  814.                     SetPort(contrlOwner);
  815.                     menuH := GetMHandle(menuID);    {get our main menu}
  816.                     if menuH <> nil then
  817.                         if CountMItems(menuH) > 0 then
  818.                             begin
  819.                                 oldTE := SaveTE;    {so we can put insertion point back later}
  820.                                 pt := PreparePopUp;
  821.                                 if useSysFont then
  822.                                     begin    {easy for system font}
  823.                                         if NonHierValue then
  824.                                             CheckItem(menuH, contrlValue, True)
  825.                                     end
  826.                                 else
  827.                                     begin    {must hack around to make popup appear in window font}
  828.                                         if NonHierValue then
  829.                                             SetItemMark(menuH, contrlValue, optionVCheckMark);
  830.                                         SetPopUpFont;
  831.                                     end;
  832.                                 if useTE then
  833.                                     SelIText(contrlOwner, teItem, 0, MAXINT);    {hilite the TE}
  834.                                 with menuRect do
  835.                                     PatchCalcMenuSize(right - left - 2);
  836.                                 CalcMenuSize(menuH);    {size the actual pop-up; something may have changed}
  837.                                 selected := PopUpMenuSelect(menuH, pt.v, pt.h, contrlValue);    {track it!}
  838.                                 UnpatchCalcMenuSize;
  839.                                 RestoreTE;
  840.                                 if not useSysFont then
  841.                                     RestorePopUpFont;    {if we messed with the system font, it was only temporary!}
  842.                                 if NonHierValue then
  843.                                     CheckItem(menuH, contrlValue, False);    {clear old check mark}
  844.                                 item := LoWord(selected);
  845.                                 if item > 0 then    {if a menu item was selected…}
  846.                                     begin    {…update the control value}
  847.                                         lastMenuID := HiWord(selected);
  848.                                         if lastMenuID = menuID then
  849.                                             contrlValue := item
  850.                                         else
  851.                                             contrlValue := BSL(lastMenuID, 8) + item;
  852.                                     end;
  853.                             end;
  854.                     SetPort(savePort);    {put the port back to wherever it was}
  855.                 end;
  856.         end;
  857.  
  858.         const
  859.             calcCntlRgn = 10;        {messages for “clean” control region inquiries}
  860.             calcThumbRgn = 11;
  861.  
  862.     begin {Main — Pop–Up Menu CDEF}
  863.         main := 0;    {normally we return a zero}
  864.         HLock(Handle(theControl));    {lock down the usual control data for the duration}
  865.         with theControl^^ do
  866.             begin
  867.                 if message = initCntl then
  868.                     DoInitControl
  869.  
  870.                 else if message = dispCntl then
  871.                     DoDisposeControl
  872.  
  873.                 else if contrlData <> nil then
  874.                     begin
  875.                         HLock(contrlData);    {lock down control's private data}
  876.                         with DataHandle(contrlData)^^ do
  877.                             case message of
  878.                                 drawCntl: 
  879.                                     DoDrawControl;
  880.                                 testCntl: 
  881.                                     main := DoTestControl;
  882.                                 calcCRgns, calcCntlRgn: 
  883.                                     DoCalcControlRegions;
  884.                                 autoTrack: 
  885.                                     DoTrackControl;
  886.  
  887.                                 otherwise
  888.                                     ;    {don't handle other messages}
  889.                             end;
  890.                         HUnLock(contrlData);
  891.                     end;
  892.             end;
  893.         HUnLock(Handle(theControl));
  894.     end;
  895.  
  896. end.