home *** CD-ROM | disk | FTP | other *** search
/ MacFormat 1995 January / macformat-020.iso / Shareware City / Developers / apps.to.go / DTS.Draw / TearOff.MDEF.c < prev    next >
Encoding:
Text File  |  1994-05-05  |  29.0 KB  |  913 lines  |  [TEXT/MPS ]

  1. /*
  2. ** Apple Macintosh Developer Technical Support
  3. ** Simple Tear-Off Menu Definition
  4. **
  5. ** File:        TearOff.MDEF.c
  6. ** Written by:    C.K. Haun and Eric Soldan
  7. **
  8. ** Copyright © 1992-1993 Apple Computer, Inc.
  9. ** All rights reserved.
  10. */
  11.  
  12. /* You may incorporate this sample code into your applications without
  13. ** restriction, though the sample code has been provided "AS IS" and the
  14. ** responsibility for its operation is 100% yours.  However, what you are
  15. ** not permitted to do is to redistribute the source as "DSC Sample Code"
  16. ** after having made changes. If you're going to re-distribute the source,
  17. ** we require that you make it clear in the source that the code was
  18. ** descended from Apple Sample Code, but that you've made changes. */
  19.  
  20. /*
  21.  
  22. What does it take to tear a menu off? Do you need to patch things?
  23. Do you need to write an assembly language interface to the Menu Manager? 
  24. Do I need weird stand-alone globals?
  25.  
  26. Nah, just a little MDEF.
  27.  
  28. And here it is.  This MDEF 'tears off', allowing you to know the user
  29. has torn it off, and at which point you'll make a palette window for it.
  30. No patches, no hacking, just code.
  31.  
  32. This MDEF is also a CIconic MDEF, but the same technique will work
  33. with a Text menu, or anything else.
  34.  
  35. NOTE:  The MDEF handles non-color quickdraw machines correctly.  For non-color quickdraw
  36. machines, the bitmap of the CIcon is used, and CopyBits is used for these systems.
  37.  
  38.  
  39. How The Tear Off Part Works:
  40.  
  41. When the user is tracking around in a menu, you are periodically called to highlight and
  42. de-highlight the items the user is tracking over.  This is the whole key to tearing off.
  43. When the user goes outside the bounding rectangle of the menu, the Menu Manager tells you
  44. to de-highlight the last item they were over, and does NOT tell you to highlight a new one.
  45.  
  46. This MDEF watches for that situation, when it sees that there are no items  highlighted
  47. (easier than you'd think) it checks to see if the user is dragging in the Desktop area
  48. (screen minus this menu and the menu bar) and  if they are, it drags a gray outline of
  49. the menu around until they:
  50.     1) release the mouse.
  51.     2) go back in the menu or menu bar.
  52.  
  53. If they release the mouse in a valid area for a tear off (not in the menu bar or current menu)
  54. then the MDEF lets you, the application, know that the user tore off.  It does this by passing
  55. back an item number of -1, that's the flag to you that says "Hey, something tore it".
  56. The 'where' the user wants is a little sneakier.  The Menu struture that this MDEF uses has a
  57. dummy item at the end.  That dummy item is never drawn, it's just a placeholder.  When the
  58. user tears-off, I stuff the rectangle that they tore off at in the last menu item, as bytes
  59. 1 through 8 in the string.
  60.  
  61. So in your application, you can do a 
  62.     GetMenuItemText(theMenu,CountMItems(theMenu),tempString);
  63.     BlockMove((Ptr)tempString + 1), (Ptr)&myTornOffRct, sizeof(Rect));
  64.  
  65. and create your palette at that rect.
  66.  
  67.  
  68. Why not something like:
  69.     GetMenuItemText(theMenu,CountMItems(theMenu),tempString);
  70.     myTornOffRct = *(Rect *)(tempString + 1);
  71.  
  72. Because this code will crash on 68000 machines (a move with an odd address, ya know).
  73. So do the ugly BlockMove and you'll be happy.
  74.  
  75. Pretty simple, huh?
  76.  
  77.  
  78. Pay the most attention to the code in the ChooseIt and DragTearOff functions.
  79.  
  80.  
  81. How The CIcon Part Works:
  82.  
  83. Drawing CIcons instead of text in a menu is not hard, the only thing that's at all interesting
  84. in that part of the code is how the CIcon id's are stored for the menu items.
  85.  
  86. Ascii text is used for the CIcon id's.  Actual numeric values are not used.  Numeric
  87.  
  88. Actual numeric values for the CIcons are NOT stored in the menu structure.  Numeric values could
  89. have been used, but this would make it hard to use ResEdit to edit the menu structure.
  90. Therefore the CIcon resource IDs are stored in their string representation.
  91.  
  92. This has been extended to allow alternate CIcons per menu item.  By marking the menu item,
  93. you can determine which cicon to actually draw.  (DTS.Draw uses this facility, so there is a
  94. complete sample demonstrating this.)
  95.  
  96. The special dummy menu item on the end does more than just return the rect in bytes 1-8.
  97. It also allows you to use ResEdit to determine other attributes of the menu without
  98. having to modify and recompile the MDEF.
  99.  
  100. The format for the extra menu item is:
  101.  
  102. bytes 1-8:    Reserved to return the tear-off rect.
  103.             A -1 is returned as the menu item to indicate to the application that the user
  104.             tore-off the menu.
  105.  
  106. bytes 9-N:    16 ascii numbers, delimited by commas.
  107.             These numbers tell the MDEF how to behave.  The numbers are:
  108.  
  109. 1) Number of columns in menu
  110.  
  111. 2) Menu vert  size
  112. 3) Menu horiz size
  113.  
  114. 4) Item vert  size
  115. 5) Item horiz size
  116.  
  117. 6) CIcon vert  size
  118. 7) CIcon horiz size
  119.  
  120. 8) CIcon vert  offset
  121. 9) CIcon horiz offset
  122.  
  123. 10) Invert vert  size
  124. 11) Invert horiz size
  125.  
  126. 12) Invert vert  offset
  127. 13) Invert horiz offset
  128.  
  129. 14) Invert vert  border size
  130. 15) Invert horiz border size
  131.  
  132. 16) No invert -- use CIcon offset
  133.  
  134. The numbers end up mapping out 3 working rectangles.  They can be charted:
  135.  
  136.     a------------------------------------------+
  137.     |                                          |
  138.     |  c------------------------------------+  |
  139.     |  |                                    |  |
  140.     |  |  e------------------------------+  |  |
  141.     |  |  |                              |  |  |
  142.     |  |  |                              |  |  |
  143.     |  |  |                              |  |  |
  144.     |  |  |                              |  |  |
  145.     |  |  |                              |  |  |
  146.     |  |  |                              |  |  |
  147.     |  |  |                              |  |  |
  148.     |  |  |                              |  |  |
  149.     |  |  |                              |  |  |
  150.     |  |  |                              |  |  |
  151.     |  |  +------------------------------f  |  |
  152.     |  |                                    |  |
  153.     |  +------------------------------------d  |
  154.     |                                          |
  155.     +------------------------------------------b
  156.  
  157. Rect a-b is the item rect.  The item rect is calculated based on the number of columns in
  158. the menu, the menu item number, and the menu item vert/horiz sizes.
  159.  
  160. Rect c-d is the cicon rect.  The cicon is drawn inside the item rect.  The size is determined by
  161. the cicon vert/horiz sizes.  Where in the item rect is determined by the cicon vert/horiz offsets.
  162.  
  163.  
  164. Rect e-f is the invert area for the item.  Either the entire invert rect is inverted, or just
  165. the frame.  If the Invert vert/horiz border size is non-zero, then just the frame of the
  166. invert rect is inverted.
  167.  
  168.  
  169. Here is a sample entry for the TearOff.MDEF to interpret.
  170.     Remember, it is added to the menu as the last item.  The last item is never
  171.     displayed.  It is just used for information.
  172.  
  173.          #  #   #  #  #  #  # # #  #  # # # # # #
  174.                                    1  1 1 1 1 1 1
  175.          1  2   3  4  5  6  7 8 9  0  1 2 3 4 5 6
  176. -------------------------------------------------
  177. TearRect,1,119,31,20,32,20,32,0,0,19,31,0,0,2,2,0
  178.    |     |  |   |  |  |  |  | | |  |  | | | | | |
  179.    |     |  |   |  |  |  |  | | |  |  | | | | | -- Don't do an invert to show selection.  CIcons
  180.    |     |  |   |  |  |  |  | | |  |  | | | | |    can look bad inverted sometimes.  For these cases,
  181.    |     |  |   |  |  |  |  | | |  |  | | | | |    you may wish to draw a different CIcon when the
  182.    |     |  |   |  |  |  |  | | |  |  | | | | |    user is tracking across the item.  If this value
  183.    |     |  |   |  |  |  |  | | |  |  | | | | |    is 0, then the invert rules are applied.  If this
  184.    |     |  |   |  |  |  |  | | |  |  | | | | |    value is non-zero, then it is added to the CIcon
  185.    |     |  |   |  |  |  |  | | |  |  | | | | |    ID that would normally be drawn at that point,
  186.    |     |  |   |  |  |  |  | | |  |  | | | | |    and that CIcon is drawn instead.  When the user
  187.    |     |  |   |  |  |  |  | | |  |  | | | | |    tracks outside this item and an invert would
  188.    |     |  |   |  |  |  |  | | |  |  | | | | |    normally be done to unhilite the item, the regular
  189.    |     |  |   |  |  |  |  | | |  |  | | | | |    CIcon is redrawn.
  190.    |     |  |   |  |  |  |  | | |  |  | | | | |
  191.    |     |  |   |  |  |  |  | | |  |  | | | | ---- Invert horiz border size.  If this and vert are
  192.    |     |  |   |  |  |  |  | | |  |  | | | |      non-zero, then the invert rect has a frame size
  193.    |     |  |   |  |  |  |  | | |  |  | | | |      of the horiz/vert values.
  194.    |     |  |   |  |  |  |  | | |  |  | | | |
  195.    |     |  |   |  |  |  |  | | |  |  | | | ------ Invert vert border size.  If this and horiz are
  196.    |     |  |   |  |  |  |  | | |  |  | | |        non-zero, then the invert rect has a frame size
  197.    |     |  |   |  |  |  |  | | |  |  | | |        of the horiz/vert values.
  198.    |     |  |   |  |  |  |  | | |  |  | | |
  199.    |     |  |   |  |  |  |  | | |  |  | | -------- Invert horiz offset.  The invert rect is calculated,
  200.    |     |  |   |  |  |  |  | | |  |  | |          and then it is offset by this amount horizontally.
  201.    |     |  |   |  |  |  |  | | |  |  | |
  202.    |     |  |   |  |  |  |  | | |  |  | ---------- Invert vert offset.  The invert rect is calculated,
  203.    |     |  |   |  |  |  |  | | |  |  |            and then it is offset by this amount vertically.
  204.    |     |  |   |  |  |  |  | | |  |  |
  205.    |     |  |   |  |  |  |  | | |  |  ------------ Invert horiz size.  The invert rect is this wide.
  206.    |     |  |   |  |  |  |  | | |  |               It's upper left corner is the item corner, offset
  207.    |     |  |   |  |  |  |  | | |  |               by the horiz/vert invert offsets.
  208.    |     |  |   |  |  |  |  | | |  |
  209.    |     |  |   |  |  |  |  | | |  --------------- Invert vert size.  The invert rect is this tall.
  210.    |     |  |   |  |  |  |  | | |                  It's upper left corner is the item corner, offset
  211.    |     |  |   |  |  |  |  | | |                  by the horiz/vert invert offsets.
  212.    |     |  |   |  |  |  |  | | |
  213.    |     |  |   |  |  |  |  | | ------------------ CIcon horiz offset.  The CIcon rect is calculated,
  214.    |     |  |   |  |  |  |  | |                    and then it is offset by this amount horizontally.
  215.    |     |  |   |  |  |  |  | |
  216.    |     |  |   |  |  |  |  | -------------------- CIcon vert offset.  The CIcon rect is calculated,
  217.    |     |  |   |  |  |  |  |                      and then it is offset by this amount vertically.
  218.    |     |  |   |  |  |  |  |
  219.    |     |  |   |  |  |  |  |--------------------- CIcon horiz size.  The CIcon rect is this wide.
  220.    |     |  |   |  |  |  |                         It's upper left corner is the item corner, offset
  221.    |     |  |   |  |  |  |                         by the CIcon horiz/vert offsets.
  222.    |     |  |   |  |  |  |
  223.    |     |  |   |  |  |  ------------------------- CIcon vert size.  The CIcon rect is this tall.
  224.    |     |  |   |  |  |                            It's upper left corner is the item corner, offset
  225.    |     |  |   |  |  |                            by the CIcon horiz/vert offsets.
  226.    |     |  |   |  |  |
  227.    |     |  |   |  |  ---------------------------- Item horiz size.  The item rect is this wide.
  228.    |     |  |   |  |                               It's upper left corner is the item corner, offset
  229.    |     |  |   |  |                               by the item horiz/vert offsets.
  230.    |     |  |   |  |
  231.    |     |  |   |  ------------------------------- Item vert size.  The item rect is this tall.
  232.    |     |  |   |                                  It's upper left corner is the item corner, offset
  233.    |     |  |   |                                  by the item horiz/vert offsets.
  234.    |     |  |   |
  235.    |     |  |   ---------------------------------- Menu horiz size.  The entire menu is this wide.
  236.    |     |  |
  237.    |     |  -------------------------------------- Menu vert size.  The entire menu is this tall.
  238.    |     |
  239.    |     ----------------------------------------- Number of columns in menu.
  240.    |
  241.    ----------------------------------------------- Return result for the TearOff rect.  This must
  242.                                                    8 bytes long exactly.
  243.    
  244.  
  245. Note that you can stop putting values in at any point.  Any fields that don't have a value are
  246. assumed to be 0, except for the number-of-columns field.  (0 columns is a bad thing.)  The
  247. number-of-columns field is assumed to be 1.
  248.  
  249. An additional feature of the MDEF is that you can control how it behaves on color v.s. b/w
  250. monitors.  Of course, just by the fact the the MDEF uses CIcons, you have a good deal of control
  251. over how the menu looks on different monitors.  You may also wish to control other behaviors,
  252. depending on the screen depth.
  253.  
  254. The sample extra-item above was:
  255.  
  256.     TearRect,1,119,31,20,32,20,32,0,0,19,31,0,0,2,2
  257.  
  258. If we change this to:
  259.  
  260.     TearRect,1,119,31,20,32,20,32,0,0,19,31,0,0,2.0,2.0
  261.  
  262. we get a different behavior for b/w screens v.s. color screens.  Actually, we get a different
  263. behavior for not-enough-color screens v.s. enough color.  Not enough color is any monitor with
  264. less than 8-bit color.
  265.  
  266. The 2.0 entries are split values.  The 2 is used if the screen has enough color, and the 0 is
  267. used if it doesn't.  From the above usage table, we can see that if either if the invert
  268. border sizes is 0, then it inverts the entire border rect.  By adding the .0 to these entries,
  269. we now have a different behavior for enough-color v.s. not enough color.
  270.  
  271. What if we want the distinction to be any amount of color v.s. b/w?  Then we do this:
  272.  
  273.     TearRect,1.2,119,31,20,32,20,32,0,0,19,31,0,0,2.0,2.0
  274.  
  275. Since the number of columns doesn't change based on the scren depth, putting a .2 on the
  276. number of columns field would seem to make mo sense.  However, when this field has a split value,
  277. it is interpreted differently for the number-of-columns field.  It is used as the minimum depth
  278. of the screen for it to be considered a color screen.  The default is 8, but by extending the
  279. number-of-columns field, you can override the 8 default and use whatever depth you like.  So,
  280. for this case, a screen depth of 2 or greater is a color screen, and 1-deep is b/w.
  281.  
  282.  
  283. • Things that AREN'T in here! •
  284. This MDEF does NOT scroll, support heirarchical menus, or PopUps.
  285. The thought of a Hierarchical tear-off can cause brain seizures.
  286.  
  287. If you need more information about HMenus, scrolling, and Popups, please
  288. see the excellent MDEF sample called Concordia on the Developer CD.
  289.  
  290. Any bugs or comments....
  291. C.K. Haun or Eric Soldan
  292. Apple DTS (ALink : DEVSUPPORT)
  293.  
  294. */
  295.  
  296.  
  297.  
  298. #include <Types.h>
  299. #include <Quickdraw.h>
  300. #include <Balloons.h>
  301. #include <Controls.h>
  302. #include <Events.h>
  303. #include <Memory.h>
  304. #include <ToolUtils.h>
  305. #include <Menus.h>
  306. #include <Packages.h>
  307. #include <Resources.h>
  308. #include <Script.h>
  309. #include <GestaltEqu.h>
  310.  
  311. #include "GWLayers.h"
  312.  
  313. #define kSlop            6
  314. #define kWDEFTitleSize    9
  315. #define kExtremeNeg        -32767 + 1
  316. #define kExtremePos         32767 - 1 
  317.  
  318. #define kItemRect    0
  319. #define kIconRect    1
  320. #define kInvertRect  2
  321. #define kMaxNumParms 16
  322.  
  323. #define kNumCols            0
  324. #define kMenuHeight         1
  325. #define kMenuWidth          2
  326. #define kItemHeight         3
  327. #define kItemWidth          4
  328. #define kIconHeight         5
  329. #define kIconWidth          6
  330. #define kIconHeightOffset   7
  331. #define kIconWidthOffset    8
  332. #define kInvertHeight       9
  333. #define kInvertWidth        10
  334. #define kInvertHeightOffset 11
  335. #define kInvertWidthOffset  12
  336. #define kInvertBorderHeight 13
  337. #define kInvertBorderWidth  14
  338. #define kInvertIconOffsetID    15
  339.  
  340. #define kQDOriginal 0
  341.  
  342. pascal void main(short message, MenuHandle menu, Rect *menuRct, Point hit, short *item);
  343.  
  344. void        DrawItem         (MenuHandle menu, Rect menuRct, short item, Boolean invert, Boolean invOn);
  345. void        ChooseIt         (MenuHandle menu, Rect menuRct, Point hit, short *item);
  346. Rect        CalcItemRect     (MenuHandle menu, Rect menuRct, short item, Boolean rectType);
  347. RgnHandle    CalcItemSelectRgn(MenuHandle menu, Rect menuRct, short item);
  348. short        GetNumItems      (MenuHandle menu);
  349. short        GetMenuData      (MenuHandle menu, short data[]);
  350. Boolean        DragTearOff      (Rect menuRct, Point thePoint, Rect *passedRct);
  351. Rect        FrameGrayRect    (Rect rct);
  352.  
  353. Rect        GetMainScreenRect(void);
  354. long        GetGestaltResult(OSType gestaltSelector);
  355. short        GetMainScreenDepth(void);
  356.  
  357. void        DoBalloon(MenuHandle menu, Rect menuRct, Point tip, short item);
  358. void        UndoBalloon(void);
  359.  
  360.  
  361.  
  362. /*****************************************************************************/
  363. /*****************************************************************************/
  364.  
  365.  
  366.  
  367. pascal void main(short message, MenuHandle menu, Rect *menuRct, Point hit, short *item)
  368. {
  369.     short    qq, numItems, data[kMaxNumParms];
  370.  
  371.     switch (message) {
  372.         case mDrawMsg:
  373.             numItems = GetNumItems(menu);
  374.             for (qq = 1; qq <= numItems; qq++) DrawItem(menu, *menuRct, qq, false, false);
  375.             break;
  376.  
  377.         case mChooseMsg:
  378.             ChooseIt(menu, *menuRct, hit, item);
  379.             break;
  380.  
  381.         case mSizeMsg:
  382.             GetMenuData(menu, data);
  383.             (*menu)->menuHeight = data[kMenuHeight];
  384.             (*menu)->menuWidth  = data[kMenuWidth];
  385.             break;
  386.  
  387.         case mPopUpMsg:            /* I am not supporting Popups, so I'm ignoring these */
  388.         case mDrawItemMsg:
  389.         case mCalcItemMsg:
  390.             break;
  391.     }
  392. }
  393.  
  394.  
  395.  
  396. /*****************************************************************************/
  397.  
  398.  
  399.  
  400. /* ChooseIt is the main work-horse routine.
  401. ** The Menu Manager calls this routine continually during tracking to highlight
  402. ** and dehighlight the items the user is tracking over */
  403.  
  404. void    ChooseIt(MenuHandle menu, Rect menuRct, Point hit, short *item)
  405. {
  406.     short        ii, numItems;
  407.     long        enableFlags;
  408.     Str63        infoText;
  409.     Rect        itemRct, tearRct;
  410.     Boolean        justUnselected;
  411.  
  412.     enableFlags = (*menu)->enableFlags;
  413.     if (!(enableFlags & 0x01)) enableFlags = 0;
  414.  
  415.     numItems = GetNumItems(menu);
  416.  
  417.     if (*item == -1) {
  418.         UndoBalloon();
  419.         GetMenuItemText(menu, numItems + 1, infoText);
  420.         BlockMove(infoText + 1, (Ptr)&tearRct, sizeof(Rect));
  421.         InsetRect(&tearRct, -1, -1);
  422.         FrameGrayRect(tearRct);
  423.         return;
  424.     }
  425.  
  426.     if ((*item < 0) || (*item > numItems)) return;
  427.         /* Make sure values are valid. */
  428.  
  429.     for (ii = numItems; ii; --ii) {
  430.         itemRct = CalcItemRect(menu, menuRct, ii, kItemRect);    /* Get rect for first/next item. */
  431.         if (PtInRect(hit, &itemRct)) break;                        /* Are we in an item?  If so, we
  432.                                                                 ** know what we want to know. */
  433.     }
  434.  
  435.     justUnselected = false;
  436.  
  437.     if (*item != ii) {                /* If things have changed... */
  438.  
  439.         if (*item) {                /* If we had an old selected item, deselect it. */
  440.             UndoBalloon();
  441.             DrawItem(menu, menuRct, *item, true, false);
  442.             justUnselected = true;
  443.         }
  444.  
  445.         if (*item = ii) {        /* If we have a new selected item, select it. */
  446.             if ((enableFlags & (0x01 << ii)))
  447.                 DrawItem(menu, menuRct, ii, true, true);
  448.             else
  449.                 *item = 0;
  450.             DoBalloon(menu, menuRct, hit, ii);
  451.         }
  452.     }
  453.     else DoBalloon(menu, menuRct, hit, *item);
  454.  
  455.     if (justUnselected) {        /* If we just unselected an item... */
  456.  
  457.         if (!ii) {                /* If we haven't selected a new item... */
  458.  
  459.             if ((enableFlags & (0x01 << ++numItems))) {        /* If TearOff feature is enabled... */
  460.  
  461.                 if (DragTearOff(menuRct, hit, &tearRct)) {
  462.                     /* if this returned true, then they let up on the mouse whilst they were dragging */
  463.                     /* the outline, otherwise they went back and we press on */
  464.                     /* So, the final rectangle is in passedRct. */
  465.                     /* And, as I mentioned above, we have a dummy item at the end of our menu. */
  466.                     /* That dummy item is where we will sneakily store the rectangle, like so... */
  467.  
  468.                     UndoBalloon();
  469.  
  470.                     GetMenuItemText(menu, numItems, infoText);
  471.                     BlockMove((Ptr)&tearRct, infoText + 1, sizeof(Rect));
  472.                     SetMenuItemText(menu, numItems, infoText);
  473.  
  474.                     *item = -1;
  475.                 }
  476.             }
  477.         }
  478.     }
  479. }
  480.  
  481.  
  482.  
  483. /*****************************************************************************/
  484.  
  485.  
  486.  
  487. /* DrawItem draws our item.
  488. ** In this MDEF, I grab the text of the menu item, change it to a number, and then get
  489. ** a cicon with that resID  and plot that cicon. */
  490.  
  491. void DrawItem(MenuHandle menu, Rect menuRct, short theItem, Boolean invert, Boolean invOn)
  492. {
  493.     Rect        iconRct, rct;
  494.     Str32        itemText, str;
  495.     long        num;
  496.     short        mark, i, ii;
  497.     CIconHandle    cicon;
  498.     RgnHandle    rgn, oldClip, newClip;
  499.     short        data[kMaxNumParms];
  500.  
  501.     GetItemMark(menu, theItem, &mark);
  502.     if (mark >= '0') mark -= '0';
  503.  
  504.     GetMenuItemText(menu, theItem, itemText);
  505.     itemText[++itemText[0]] = ',';            /* Give us a terminator character. */
  506.     for (ii = 0; mark > -1; --mark) {
  507.         if (ii >= itemText[0]) break;
  508.         for (i = 0; itemText[++ii] != ','; ++i);
  509.         BlockMove(itemText + ii - i, str + 1, str[0] = i);
  510.         StringToNum(str, &num);
  511.     }
  512.  
  513.     if (invert) {
  514.         GetMenuData(menu, data);
  515.         if (data[kInvertIconOffsetID]) {
  516.             if (invOn)
  517.                 num += data[kInvertIconOffsetID];
  518.         }
  519.         else {
  520.             rgn = CalcItemSelectRgn(menu, menuRct, theItem);
  521.             InvertRgn(rgn);
  522.             DisposeRgn(rgn);
  523.             return;
  524.         }
  525.     }
  526.  
  527.     if (cicon = ReadCIcon(num)) {
  528.         iconRct = CalcItemRect(menu, menuRct, theItem, kIconRect);
  529.         SectRect(&iconRct, &menuRct, &rct);
  530.         oldClip = NewRgn();
  531.         newClip = NewRgn();
  532.         RectRgn(newClip, &rct);
  533.         GetClip(oldClip);
  534.         SetClip(newClip);
  535.         DrawCIcon(cicon, iconRct);
  536.         SetClip(oldClip);
  537.         DisposeRgn(oldClip);
  538.         DisposeRgn(newClip);
  539.         KillCIcon(cicon);
  540.     }
  541. }
  542.  
  543.  
  544.  
  545. /*****************************************************************************/
  546.  
  547.  
  548. /*
  549. ** DragTearOff does the actual tearing off.
  550. **
  551. ** As long as the mouse stays outside the menu and the menu bar area, I will
  552. ** drag a rectangle around.  This ends if:
  553. **
  554. ** 1) The user lets up on the mouse.  In that case, I return the ending rectangle
  555. **      and a TRUE
  556. ** 2) The user goes back into the menu or menu bar.  In that case, I return FALSE
  557. */
  558.  
  559. Boolean DragTearOff(Rect menuRct, Point thePoint, Rect *passedRct)
  560. {
  561.     Rect        dragRct, menubarRct;
  562.     Point        endPoint, pp;
  563.     long        ll;
  564.     Boolean        dragRctOn;
  565.  
  566.     if (PtInRect(thePoint, &menuRct)) return(false);
  567.         /* If currently in the menu, get out of here. */
  568.  
  569.     menubarRct = GetMainScreenRect();        /* Calc the menubar rect. */
  570.     menubarRct.bottom = menubarRct.top + GetMBarHeight();
  571.  
  572.     if (PtInRect(thePoint, &menubarRct)) return(false);
  573.         /* If currently in the menubar, get out of here. */
  574.  
  575.     dragRct = menuRct;            /* Drag this puppy around until the user lets go,
  576.                                 ** or until the user bumps into the badRgn. */
  577.  
  578.     InsetRect(&dragRct, kSlop, kSlop);
  579.     ll = PinRect(&dragRct, thePoint);
  580.     InsetRect(&dragRct, -kSlop, -kSlop);
  581.  
  582.     pp = *(Point *)≪
  583.     OffsetRect(&dragRct, thePoint.h - pp.h, thePoint.v - pp.v);
  584.         /* Get the rect to be over the mouse location by at least a little slop. */
  585.  
  586.     InsetRect(&dragRct, -1, -1);
  587.  
  588.     /* Keep tracking the mouse and moving the rect until they release the mouse button,
  589.     ** or until they bump into the menu or the menubar. */
  590.  
  591.     dragRctOn = false;
  592.  
  593.     while (StillDown()) {
  594.  
  595.         GetMouse(&endPoint);
  596.  
  597.         if (!EqualPt(endPoint, thePoint)) {                /* They moved... */
  598.  
  599.             if (dragRctOn) {
  600.                 FrameGrayRect(dragRct);                    /* Erase the old rect */
  601.                 dragRctOn = false;
  602.             }
  603.  
  604.             if (PtInRect(thePoint, &menuRct)) break;
  605.             if (PtInRect(thePoint, &menubarRct)) break;
  606.  
  607.             OffsetRect(&dragRct, endPoint.h - thePoint.h, endPoint.v - thePoint.v);
  608.             thePoint = endPoint;
  609.  
  610.             InsetRect(&menuRct, -kSlop, -kSlop);
  611.             dragRctOn = !PtInRect(thePoint, &menuRct);
  612.             InsetRect(&menuRct, kSlop, kSlop);
  613.  
  614.             if (dragRctOn) FrameGrayRect(dragRct);        /* Frame the new position */
  615.             Delay(1, &ll);                                /* Reduce tearing. */
  616.         }
  617.     }
  618.  
  619.     if (dragRctOn) dragRct = FrameGrayRect(dragRct);        /* Erase the gray region. */
  620.     InsetRect(&dragRct, 1, 1);
  621.  
  622.     *passedRct = dragRct;            /* pass back the end rect position, whatever it is */
  623.     return(dragRctOn);                /* and go hither */
  624. }
  625.  
  626.  
  627.  
  628. /*****************************************************************************/
  629.  
  630.  
  631.  
  632. Rect    CalcItemRect(MenuHandle menu, Rect menuRct, short item, Boolean rectType)
  633. {
  634.     Rect    itemRct;
  635.     short    ii, data[kMaxNumParms];
  636.  
  637.     GetMenuData(menu, data);
  638.  
  639.     ii = --item / data[kNumCols];
  640.     itemRct.top = ii * data[kItemHeight];
  641.     switch (rectType) {
  642.         case kItemRect:
  643.             SetRect(&itemRct, 0, itemRct.top, data[kItemWidth], itemRct.top + data[kItemHeight]);
  644.             break;
  645.         case kIconRect:
  646.             SetRect(&itemRct, 0, itemRct.top, data[kIconWidth], itemRct.top + data[kIconHeight]);
  647.             OffsetRect(&itemRct, data[kIconWidthOffset], data[kIconHeightOffset]);
  648.             break;
  649.         case kInvertRect:
  650.             SetRect(&itemRct, 0, itemRct.top, data[kInvertWidth], itemRct.top + data[kInvertHeight]);
  651.             OffsetRect(&itemRct, data[kInvertWidthOffset], data[kInvertHeightOffset]);
  652.             break;
  653.     }
  654.  
  655.     OffsetRect(&itemRct, menuRct.left, menuRct.top);
  656.  
  657.     ii *= data[kNumCols];
  658.     ii = item - ii;
  659.     OffsetRect(&itemRct, ii * data[kItemWidth], 0);
  660.  
  661.     return(itemRct);
  662. }
  663.  
  664.  
  665.  
  666. /*****************************************************************************/
  667.  
  668.  
  669.  
  670. RgnHandle    CalcItemSelectRgn(MenuHandle menu, Rect menuRct, short item)
  671. {
  672.     Rect        rct;
  673.     RgnHandle    rgn, interiorRgn;
  674.     short        data[kMaxNumParms];
  675.  
  676.     rct = CalcItemRect(menu, menuRct, item, kInvertRect);
  677.     RectRgn(rgn = NewRgn(), &rct);
  678.  
  679.     GetMenuData(menu, data);
  680.     if (data[kInvertBorderHeight] || data[kInvertBorderWidth]) {
  681.         InsetRect(&rct, data[kInvertBorderWidth], data[kInvertBorderHeight]);
  682.         RectRgn(interiorRgn = NewRgn(), &rct);
  683.         DiffRgn(rgn, interiorRgn, rgn);
  684.         DisposeRgn(interiorRgn);
  685.     }
  686.  
  687.     return(rgn);
  688. }
  689.  
  690.  
  691.  
  692. /*****************************************************************************/
  693.  
  694.  
  695.  
  696. short    GetMenuData(MenuHandle menu, short data[])
  697. {
  698.     short    numItems, ii, parm, i, c, depth, colorDepth;
  699.     Str255    infoText;
  700.     Str15    str;
  701.     long    num;
  702.  
  703.     for (i = 0; i < kMaxNumParms; ++i) data[i] = 0;
  704.  
  705.     GetMenuItemText(menu, (numItems = GetNumItems(menu)) + 1, infoText);
  706.  
  707.     for (;;) {            /* Make sure there are no invisible chars (c/r's) on end of string. */
  708.         if (infoText[0] < 9) break;
  709.         if ((infoText[infoText[0]] > ' ') && (infoText[infoText[0]] < 'f')) break;
  710.         --infoText[0];
  711.     }
  712.  
  713.     infoText[++infoText[0]] = ',';        /* Give us a terminator character. */
  714.  
  715.     ii = 8;
  716.     if (infoText[9] == ',') ++ii;        /* Skip possible first optional delimiter. */
  717.  
  718.     depth      = GetMainScreenDepth();
  719.     colorDepth = 8;
  720.  
  721.     for (parm = 0; parm < kMaxNumParms; ++parm) {
  722.  
  723.         if (ii >= infoText[0]) break;
  724.  
  725.         for (i = 0;; ++i) {
  726.             c = infoText[++ii];
  727.             if ((!c) || (c == ',') || (c == '.')) break;
  728.         }
  729.         if (!c) break;
  730.         BlockMove(infoText + ii - i, str + 1, str[0] = i);
  731.         StringToNum(str, &num);
  732.         data[parm] = num;
  733.  
  734.         if (c == '.') {
  735.             for (i = 0;; ++i) {
  736.                 c = infoText[++ii];
  737.                 if ((!c) || (c == ',')) break;
  738.             }
  739.             if (!c) break;
  740.             BlockMove(infoText + ii - i, str + 1, str[0] = i);
  741.             StringToNum(str, &num);
  742.             if (!parm) {
  743.                 colorDepth = num;
  744.             }
  745.             else
  746.                 if (depth < colorDepth)
  747.                     data[parm] = num;
  748.         }
  749.     }
  750.  
  751.     if (!data[kNumCols]) data[kNumCols] = 1;
  752.  
  753.     return(numItems);
  754. }
  755.  
  756.  
  757.  
  758. /*****************************************************************************/
  759.  
  760.  
  761.  
  762. short    GetNumItems(MenuHandle menu)
  763. {
  764.     return(CountMItems(menu) - 1);
  765. }
  766.  
  767.  
  768.  
  769. /*****************************************************************************/
  770.  
  771.  
  772.  
  773. Rect    FrameGrayRect(Rect frameRct)
  774. {
  775.     PenState    thePen;
  776.     RgnHandle    oldClip;
  777.     Pattern        Gray;
  778.     Rect        rct;
  779.     short        mbh;
  780.  
  781.     GetPenState(&thePen);        /* I'm going to be changing pen information, so save the old */
  782.  
  783.     /* get the pattern I want from the System file, since I don't have */
  784.     /* access to Application Globals (A5) since I'm standalone */
  785.     /* I _could_ rely on the current port information, but I'm too */
  786.     /* paranoid for that */
  787.  
  788.     Gray.pat[0] = 0;
  789.     GetIndPattern(&Gray, sysPatListID, 4);
  790.  
  791.     GetClip(oldClip = NewRgn());
  792.     SetRect(&rct, kExtremeNeg, kExtremeNeg, kExtremePos, kExtremePos);
  793.     ClipRect(&rct);        /* Allow drawing everywhere. */
  794.  
  795.     PenMode(patXor);            /* set up for gray dragging */
  796.     PenPat(&Gray);
  797.  
  798.     mbh = GetMBarHeight() + kWDEFTitleSize;
  799.     if (frameRct.top < mbh) OffsetRect(&frameRct, 0, (mbh - frameRct.top));
  800.     FrameRect(&frameRct);
  801.  
  802.     SetPenState(&thePen);        /* restore the pen */
  803.     SetClip(oldClip);            /* restore the original clip */
  804.     DisposeRgn(oldClip);
  805.  
  806.     return(frameRct);
  807. }
  808.  
  809.  
  810.  
  811. /*****************************************************************************/
  812.  
  813.  
  814.  
  815. #ifdef THINK_C
  816. Rect    GetMainScreenRect(void)
  817. {
  818.     GDHandle    mainDevice;
  819.     GrafPtr        mainPort;
  820.  
  821.     if (((GetGestaltResult(gestaltQuickdrawVersion) >> 8) & 0xFF) > kQDOriginal) {
  822.         mainDevice = GetMainDevice();
  823.         return((*mainDevice)->gdRect);
  824.     }
  825.     else {
  826.         GetWMgrPort(&mainPort);
  827.         return(mainPort->portRect);
  828.     }
  829. }
  830. #endif
  831.  
  832.  
  833.  
  834. /*****************************************************************************/
  835.  
  836.  
  837.  
  838. #ifdef THINK_C
  839. long    GetGestaltResult(OSType gestaltSelector)
  840. {
  841.     long    gestaltResult;
  842.  
  843.     if (Gestalt(gestaltSelector, &gestaltResult) == noErr)
  844.         return(gestaltResult);
  845.     else
  846.         return(0);
  847. }
  848. #endif
  849.  
  850.  
  851.  
  852. /*****************************************************************************/
  853.  
  854.  
  855.  
  856. short    GetMainScreenDepth(void)
  857. {
  858.     GDHandle        mainDevice;
  859.     PixMapHandle    pmap;
  860.  
  861.     if (((GetGestaltResult(gestaltQuickdrawVersion) >> 8) & 0xFF) > kQDOriginal) {
  862.         mainDevice = GetMainDevice();
  863.         pmap       = (*mainDevice)->gdPMap;
  864.         return((*pmap)->pixelSize);
  865.     }
  866.  
  867.     return(1);
  868. }
  869.  
  870.  
  871.  
  872. /*****************************************************************************/
  873. /*****************************************************************************/
  874. /*****************************************************************************/
  875.  
  876.  
  877.  
  878. void    DoBalloon(MenuHandle menu, Rect menuRct, Point tip, short item)
  879. {
  880.     Rect    rct;
  881.     short    v;
  882.  
  883.     if (GetGestaltResult(gestaltSystemVersion) >= 0x0700) {
  884.         if (StillDown()) {
  885.             if(!HMIsBalloon()) {
  886.                 rct = CalcItemRect(menu, menuRct, item, kItemRect);
  887.                 v  = rct.bottom - rct.top;
  888.                 v /= 2;
  889.                 tip.v = rct.top + v;
  890.                 tip.h = rct.right - 8;
  891.                 HMShowMenuBalloon(item, (*menu)->menuID, (*menu)->enableFlags, 0, tip, nil, nil, 0, 0);
  892.             }
  893.         }
  894.     }
  895. }
  896.  
  897.  
  898.  
  899. /*****************************************************************************/
  900.  
  901.  
  902.  
  903. void    UndoBalloon(void)
  904. {
  905.     if (GetGestaltResult(gestaltSystemVersion) >= 0x0700) {
  906.         if(HMIsBalloon())
  907.             HMRemoveBalloon();
  908.     }
  909. }
  910.  
  911.  
  912.  
  913.