Pie Menus for Windows

Circular pie menus are faster and more accurate than the now-familiar linear menus. Carl presents five functions that enable you to implement pie menus for Windows, using the "owner draw" features in the Windows API.


November 01, 1992
URL:http://www.drdobbs.com/windows/pie-menus-for-windows/184408870

Figure 1


Copyright © 1992, Dr. Dobb's Journal

NOV92: PIE MENUS FOR WINDOWS

PIE MENUS FOR WINDOWS

Circular menus give a new look to old Windows

This article contains the following executables: PIEMENU.ARC

Carl Rollo

Carl is a freelance programmer in the Washington, DC area. He can be contacted at 4431 Lehigh Road, Suite 104, College Park, MD 20740.


Last year in Dr. Dobb's, Don Hopkins described a form of circular menus dubbed "pie menus" that he originally developed at the University of Maryland's Human-Computer Interaction Laboratory. (See "The Design and Implementation of Pie Menus," DDJ, December 1991.) Although circular menus were around before pie menus, Don increased the selection area for a single item from a small target in a circle of small targets to the much larger wedge of a pie diagram. This is not a trivial improvement. According to "An Empirical Comparison of Pie and Linear Menus" (see "References"), pie menus are faster (15-20 percent) than linear menus and generally produce less errors when used to make selections. A subsequent study by Mills and Prime found that, "The fastest and least error prone amongst the menu styles proved to be the circular menu." As you may remember, Don committed his concept to the public domain.

In this article, I describe an implementation of pie menus for Microsoft Windows. I'm using this code as a test-bench to compare the use of pie menus to the conventional menus of a current (and very popular) GUI design. The basic aims of the work are to keep the implementation simple while conforming as closely as possible to Windows' existing menu logic. While this discussion is limited to what's involved in implementing pie menus for Windows, I've also written three Windows programs which demonstrate the use of pie menus: One uses only Windows menu styles; another has a layer of conventional Windows menus that call a layer of pie menus; and the third is the same application, using only pie menus. Figure 1 shows the output for this last program. These programs, along with detailed notes about them, are available electronically; see "Availability," page 5.

A Pie for Your Window

If we are going to add a new menu concept to Windows, we want the technique to be quick to implement, while not adding a large amount of overhead to the existing storage requirements. Since storage and speed are often mutual trade-offs, this implementation leans in the direction of speed at the cost of some extra storage.

What do we need to bake a pie menu for our Window? First, look at piemenu.h (Listing One, page 118), which lists the header file, and piemenu.c (Listing Two, page 118), which lists the five new functions.

The header file consists of two structure definitions, the function prototypes, and a few miscellaneous constant definitions. The PIE_OPTION structure definition defines data on each selection option in a pie menu. This includes the option's label (if any), the value to be returned if the option is selected, the background color for the option's pie wedge, the angle (from 0) of the wedge's upper boundary, the rectangle enclosing the option label, a switch indicating if the option is currently selected, and so on. All this data is stored, rather than calculated when needed, in order to make drawing a pie menu as speedy as possible.

The structure definition PIE_MENU defines an array of eight PIE_OPTION structures plus parameters common to a single pie menu. These include the radius of the pie chart in the menu, the value to be returned if no selection is made (the user hits Escape), the number of options in the menu (1-8), and switches to indicate whether labels are to be inside or outside the pie as well as whether or not to check the currently selected option. Eight is the maximum number of options offered in a single pie menu. You can change this by modifying the source code, but be aware that experts in human-computer inter-action have determined that eight options is the optimum. After eight, you should split the choices up into multiple or cascading menus. The pie-menu header file also defines a far pointer to a PIE_MENU structure in the Windows style of long pointers as LPPIEMENU-STRUCT. We can use this definition when supplying our pie-menu definitions to the functions.

To create a pie menu, call CreatePieMenuIndirect, which is modeled after such Windows functions as CreateBitmapIndirect. The idea is to let the applications programmer create a PIE_MENU structure wherever it is most convenient in storage, load those elements of the structure needed to define the menu, and let the function fill out the rest of the parameters. CreatePieMenuIndirect takes a far pointer to a PIE_MENU structure (LPPIEMENUSTRUCT) and completes the structure based on the information already loaded. We need to tell the function how many options we want in the pie menu, what value is to be returned for each option, what value to return if no choice was made, whether we want labels and where to put them, and whether to check the current selection. If we want labels and colors for each option, we will also have to load these before calling CreatePieMenuIndirect.

The other two arguments to CreatePieMenuIndirect are the width and height of the window used to display our pie menu. If you look at regular Windows menus, you'll see they are actually displayed in a rectangular area with a border, and that the cursor responds to the menu only in this area. This is a specialized child window created and operated by Windows. Normally, we define our menu using a script file for the resource compiler, and Windows determines how big this window will be. But the method we will use to implement our pie menus requires that we specify our window size beforehand. (The dimensions are in pixels.)

Let's take a quick look at what CreatePieMenuIndirect does to complete our pie-menu structure. First, the function performs some basic error checking on the values we have supplied. Then it calculates the angular displacement of each wedge in our pie by dividing 360 degrees by the number of options we have specified. The end coordinates of the upper bound of each wedge are calculated at the same time. Why these parameters?

If you look at the description of the Windows Pie function in the SDK, you'll see that these parameters are exactly what is needed to make a call to this function. With everything calculated in advance, the function to draw our pie menu is mostly a call to the Windows Pie function. The next bit of code builds brushes to fill in the background of each wedge in our pie and set labels on or off, depending upon our original choices. The remainder of the code in CreatePieMenuIndirect concerns itself with placing labels for the options. Properly labeling a pie chart is more a matter of aesthetics than programming. A pie with only two divisions is labeled differently than one with six. Also, our pie menus can be labeled either inside or outside the pie, and the problems involved in placing labels are completely different between the two formats.

Since we normally label pie charts inside the wedges, why provide the ability to label outside? Remember, we are trying to create a menu that looks like a pie chart, not a pie chart itself. We don't have or want exploded segments in our pie--they would just confuse the user. Short labels inside the pie and longer labels outside have the same problem. UI designers tell us that such changes of menu style are a basic no-no.

So where do the outside labels come in? If you have several very long labels, you will find that a standard Windows menu can cover quite a bit of your client window. Because Windows uses only horizontal text for menus and because outside labeling on a pie menu is clustered around a central region (the pie), the resulting pie menu will be wider but take less vertical space in the client window. If you implement the code for pie menus on your PC, you can try this out, comparing pie menus to regular menus when they both contain long labels.

By now experienced Windows programmers will have said, "Wait a minute, it's fine to calculate coordinates for your arcs and labels, but what coordinate system are you using? In what mapping mode?" The answer is that the arc end-points don't have to be in any coordinate system as yet because they are functions of the radius, which--we will assume--fits into the window requested. The labels are another matter, however. Here, we create a memory device context (DC), set up the MM_ISOTROPIC mapping mode, and scale our window-viewport coordinate systems contingent on whether the programmer wants inside or outside labels. For inside labels, we scale the pie menu to the size of the menu's window dimensions; for outside labels, we determine the leftmost and rightmost label positions and scale that range to the window dimensions.

Note that all this positioning must be done with regard to a DC. CreatePieMenuIndirect creates a DC compatible with the display, but you might ask yourself "How do we know where the menu is going to show up when it gets displayed?" we don't. The technique that we use to implement pie menus in Windows actually relies on a DC created by Windows when the user requests the menu. At that point we scale and redraw everything.

A final word on CreatePieMenuIndirect and labels. If labels are requested to be inside the pie and a label won't fit in its wedge, the label is shut off. So, if you get a pie menu with some labels missing, either expand your menu dimensions or shorten the offending labels.

The DrawPieMenu function takes a DC and our far pointer to a PIEMENUSTRUCT and calls the Windows Pie function for each wedge in our pie menu. The code is straightforward, and only two points are really worth noting. First, if we have requested a check mark to be displayed by the current selection, it appears that DrawPieMenu inverts the rectangle around the label instead of writing a check mark. This is because pie menus generally use a lot more color than Windows' linear menus, and the check mark gets lost in that much color. Inverting the label area is much more noticeable.

Second is the business of centering the mouse cursor at the center of the pie. You have to know that, if you are going to take over the cursor in Windows, you both get and set its current position in screen coordinates. This is fine, but we are going to draw pie menus using logical coordinates and a DC that we know next to nothing about. Fortunately the Windows function Get-DCOrg enables us to get the origin of our DC in device coordinates (relative to the screen origin); another function, LPtoDP, lets us convert logical coordinates to device coordinates. We can then use the device coordinates to set the cursor in the center of our pie menu.

PieMenuProc is basically the inverse function to DrawPieMenu. We get the current location of the cursor, convert the screen coordinates to logical coordinates, and calculate the central angle of the radius that runs through this point. If the distance to the cursor is not greater than the radius (the cursor is still in the pie), we hunt through the list of angles for each wedge until we find the wedge in which the cursor rests. This is the option value that will be returned to the calling code. There is a simple matter of setting the rectangle-inversion switch if required, and we are done.

Two other functions are needed to provide information for PieMenuProc about the device context of the menu. It turns out that by the time we are ready to convert the cursor location, we will have lost this information. DP_to_LP is a function that replaces the Windows function DPtoLP, which converts device coordinates to logical coordinates, given a device context. DP_to_LP performs the same task with information supplied by the remaining function, GetViewportWindowParms. This function simply collects information from a DC and stores it in a privately defined data structure.

Implementation

To implement pie menus in our Windows program we need to accomplish three tasks:

To link the pie menu into the Windows-menu system, we design our resource file as usual for a Windows program. Layout POPUP menus and put INACTIVE MENUITEMs into the POPUP menus in which we want to install pie menus. Then load the POPUP menus in WinMain using the LoadMenu function, and append the pie menus to the INACTIVE items using AppendMenu. At this point we use the "Owner Draw" menu feature. In the call to AppendMenu, declare the menu style to be MF_OWNERDRAW. When Windows requires our pie menu to be displayed, it will call on us to provide the graphics.

Displaying an Owner Draw menu requires that our Windows program process two additional messages: WM_MEASUREITEM and WM_DRAWITEM. To handle WM_MEASUREITEM, we provide Windows with dimensions of a rectangle that declare how much screen space the pie menu requires. The message WM_DRAWITEM is passed when Windows wants us to draw the pie menu (using DrawPieMenu) or when Windows lets us know that the pie menu has been selected and needs processing. If the menu has been selected, we save its menuID.

Obtaining and processing the user's selection from a pie menu requires our program to process two messages: WM_COMMAND and WM_MENUSELECT. The WM_COMMAND message is standard for processing menus in Windows. Pie menus simply require additional layers of C switch statements to handle each layer of menus. For example, to handle a two-level pie-menu system we must have two levels of case analysis. At the upper level, options that have a second level of pie menus must call the function TrackPopupMenu to display the appropriate second-level pie menu. Options without dependent menus must be processed with code to perform their function.

The WM_MENUSELECT message is required to process a pie menu but is not usually required to process a regular Windows menu. In handling this message we should first be sure that the menu called was a pie menu and that an option was selected. We then get the code associated with the user's choice by calling PieMenuProc. Pie menus require that we process the menuID value. If no further values depend on this value, we simply assign the value returned from PieMenuProc to the appropriate variable and continue.

If other menus are dependent on the menuID, the logic must be split on the basis of the value obtained from PieMenuProc. If this choice has no further menus under it, we post ourselves a WM_COMMAND message with the value from PieMenuProc as the selection value (wParam) and process the choice as usual. If menus are dependent on this choice, we let Windows send us a WM_COMMAND with the menuID. This message will trigger our call to TrackPopupMenu and take us to the next level of pie menus.

If this quick description leaves any questions unanswered, look at the sample code that's available electronically, which includes a step-by-step implementation of pie menus starting with a simple Windows program with no pie menus (chords.c), followed by the same program with only a bottom layer of pie menus (piedemo1.c), and finally the "chords" program with two layers of pie menus (piedemo2.c). The extrapolation from two to three or more layers of pie menus follows the same pattern.

Summary

When designing pie menus for an application, you'll find that they're particularly suitable to option suites already connected in the human mind with circular designs. Presenting colors in the form of a color wheel was used long before the digital computer came around, making PieDemo's menus seem very appropriate. Other option suites that come to mind are: options that deal with orientation around the compass, options connected with time, and any business-graphics pie chart. (Use a pie menu to present tax-dollar breakdowns and explode each selection into a detailed report.)

In using pie menus, take this bit of advice: Don't chase multiple pie menus. What do I mean? Well, if you are used to regular Windows menus, you have probably gotten into the habit of moving the mouse as the next menu in a sequence is displayed. But pie menus move the cursor for you, right to the center of the menu, after which it is only a short move to pick your choice.

You might consider using pie menus that pop up wherever the cursor is when say, the right mouse button, is pushed. If you are interested in implementing this technique take a look at the POPMENU program in Petzold's Programming Windows.

Finally, the real benefit of pie menus is that they are faster and more accurate to use when selecting up to eight options (which means for most menus). On top of that, all the work is in the public domain, free of copyright restrictions, available for any developer to use.

Acknowledgments

A special note of thanks to Ben Shneiderman, director of the Human-Computer Interface Laboratory at the University of Maryland, College Park, for reviewing this article.

References

Callahan, J., D. Hopkins, M. Weiser, and B. Shneiderman. "An Empirical Comparison of Pie and Linear Menus." Human Factors in Computing Systems, SIGCHI Proceedings: ACM Press, 1988.

Kurtenbach, G. and W. Buxton. "Issues in Combining Marking and Direct Manipulation Techniques." UIST '91 Proceedings: ACM Press, 1991.

Mills, Z. and M. Prime. "Are All Menus the Same?--An Empirical Study." Human-Computer Interface, INTERACT '90 Proceedings: Elsevier Science Publishers, 1990.

Newman, W.M. and R.F. Sproull. Principles of Interactive Computer Graphics, first edition. Berkeley, CA: McGraw-Hill, 1973.

Petzold, Charles, Programming Windows, second edition. Redmond, WA: Microsoft Press, 1990.



_PIE MENUS FOR WINDOWS_
by Carl Rollo


[LISTING ONE]



/* --PIEMENU.H-header file for pie menu routines-Carl C. Rollo, 1991,1992 -- */

#define MAXOPTIONS 8
#define PI 3.14159265
#define TWO_PI (2 * PI)

#define WHITE   RGB (255, 255, 255)
#define BLACK   RGB (0,     0,   0)

typedef struct
 {
  char   *name;     /* label for this option */
  BOOL   showlabel; /* indicates whether label is to be displayed */
  BOOL   checked;   /* indicates when option is to be checked */
  short  value;     /* value to be returned if option is chosen */
  COLORREF color;   /* color of pie wedge for this option, default = WHITE */
  HBRUSH brush;     /* handle to brush for filling wedge with color */
  double maxangle;  /* angle of upper bound of option wedge */
  POINT  endarc;    /* coordinates of upper bound of option wedge arc */
  RECT   txt;       /* coords. of rectangle bounding text label */
 } PIE_OPTION;

typedef struct
 {
  short      radius;             /* radius of pie */
  short      no_choice;          /* value to be returned if no choice is made */
  short      n;                  /* number of options in menu */
  PIE_OPTION option[MAXOPTIONS]; /* data structures for each option */
  BOOL       InsideLabels;       /* switch to indicate if labels are to be
                 ** placed inside or outside the wedge */
  BOOL       UseChecks;          /* indicates when selection is to be checked */
 } PIE_MENU;
typedef PIE_MENU FAR * LPPIEMENUSTRUCT;

typedef struct
 {
  DWORD DCOrg;
  RECT  ClientRect;
  DWORD ViewportExt;
  DWORD ViewportOrg;
  DWORD WindowExt;
  DWORD WindowOrg;
 } VIEWWINDATA;
typedef VIEWWINDATA FAR * LPVIEWWINDATA;

extern BOOL CreatePieMenuIndirect (LPPIEMENUSTRUCT, short *, short *);
extern void DrawPieMenu (HDC, LPPIEMENUSTRUCT);
extern short PieMenuProc (LPVIEWWINDATA, LPPIEMENUSTRUCT);
extern LPVIEWWINDATA GetViewportWindowParms (HDC hDC);
extern BOOL DP_to_LP(LPVIEWWINDATA, LPPOINT, int);





[LISTING TWO]


#include <windows.h>
#include <math.h>
#include <stdarg.h>
#include "piemenu.h"
/* ----- Functions for creating, drawing, and operating Pie Menus ------
  CreatePieMenuIndirect()  - creates a Pie Menu data structure
  DrawPieMenu()          - draws the Pie Menu given a Device Context
  PieMenuProc()          - determines what option was selected
  GetViewportWindowParms() - gets Device Context parameters for Menu
  DP_to_LP()             - converts cursor coordinates without a Device Context
   -----  copyright 1991, 1992 by Carl C. Rollo  ----- */

BOOL CreatePieMenuIndirect (LPPIEMENUSTRUCT p, short *pwidth, short *pheight)
 /* Initializes a PIE_MENU structure. Several more time-consuming calculations
 ** are made in this routine to ensure that the pie menu is displayed with
 ** minimum delay. This routine must be called before a pie menu is displayed!
 ** The allocation of the PIE_MENU structure is left to code outside this
 ** routine so that the programmer may choose to store the structure in local
 ** heap, data segment, or global memory. Routine returns TRUE if no error
 ** was encountered, FALSE otherwise.  */
 {
  short i, j, xc, yc;
  TEXTMETRIC tm;
  HDC hdc, hdcMem;
  double incvalue, angle, halfangle, h, rsquare;
  DWORD dwsize;
  short tx[4], ty[4], halfwid, halfht;

  if ( (p != NULL) && (0 < p->n) && (p->n <= MAXOPTIONS) &&
       (pwidth != NULL) && (pheight != NULL) && (*pwidth > 0) &&
       (*pheight > 0) )
   {
    if (p->radius <= 0)
     if (p->InsideLabels)
      p->radius = min (*pwidth, *pheight)/2;
     else
      return (FALSE);  /* must have a radius for outside labels */
    rsquare = p->radius * p->radius;
    incvalue = TWO_PI / p->n;  /* value in radians of each wedge */
    halfangle = incvalue/2.0;

    /* Calculate the angle of the upper bound of each wedge
    ** and the endpoint coordinates of the radius at this angle. */
    for (i = 0; i < p->n; i++)
     {
      p->option[i].maxangle = incvalue * (i + 1);
      p->option[i].endarc.x = p->radius * cos (p->option[i].maxangle) + .5;
      p->option[i].endarc.y = p->radius * sin (p->option[i].maxangle) + .5;
     }
    /* Build brush handles needed to fill the pie menu. */
    for (i = 0; i < p->n; i++)
     p->option[i].brush = CreateSolidBrush(p->option[i].color);
    /* Determine the label positions for each wedge. */
    hdc = CreateIC ("DISPLAY", NULL, NULL, NULL);
    hdcMem = CreateCompatibleDC (hdc);
    SetMapMode (hdcMem, MM_ISOTROPIC);
    if (p->InsideLabels)
     SetWindowExt (hdcMem, p->radius, p->radius);
    else
     SetWindowExt (hdcMem, *pwidth/2, *pheight/2);
    SetViewportExt (hdcMem, *pwidth/2, -(*pheight)/2);
    SetViewportOrg (hdcMem, *pwidth/2, *pheight/2);

    SelectObject (hdcMem, GetStockObject(SYSTEM_FONT));
    GetTextMetrics (hdcMem, &tm);
    /* Determine label positions for each wedge. */
    if (p->InsideLabels)
     {
      /* Labels are to be positioned inside the pie diagram.
      ** Calculate the label position for each wedge. */
      h = 0.7 * (p->radius * cos (halfangle));
      for (i = 0; i < p->n; i++)
       {
    if (p->option[i].name == NULL)
     {
      p->option[i].showlabel = FALSE;
      continue;
     }
    /* Positioning varies with number of wedges. */
    switch (p->n)
     {
      case 1 :
           xc = 0; yc = 0;
           break;
      case 2 : if (i == 0)
            {
             xc = 0; yc = p->radius/2;
            }
           else
            {
             xc = 0; yc = -(p->radius)/2;
            }
           break;
       case 3 : angle = p->option[i].maxangle - halfangle;
           xc = p->radius/2 * cos (angle);
           yc = p->radius/2 * sin (angle);
           break;
      default :
           angle = p->option[i].maxangle - halfangle;
           xc = h * cos (angle);
           yc = h * sin (angle);
     }
    /* Determine the dimensions of the label string using SYSTEM_FONT. */
    dwsize = GetTextExtent (hdcMem, p->option[i].name,
      lstrlen (p->option[i].name));
    /* Get coordinates of corners of the rectangle bounding the label */
    halfwid = (double) (LOWORD (dwsize))/2.0 + 0.5;
    halfht  = (double) (HIWORD (dwsize))/2.0 + 0.5;
    tx[0] = xc - halfwid;
    ty[0] = yc + halfht;
    tx[1] = xc + halfwid; ty[1] = ty[0];
    tx[2] = tx[1]; ty[2] = yc - halfht;
    tx[3] = tx[0]; ty[3] = ty[2];
    /* Calculate the counter-clockwise total angles for lines connecting
    ** the center of the pie to each of the corners of rectangle bounding
    ** label, and make sure that each line lies within wedge boundaries. */
    for (j = 0; j < 4; j++)
     {
      if ( (tx[j] * tx[j] + ty[j] * ty[j]) > rsquare)
       break;   /* point is outside the pie */
      if (tx[j] == 0)
       if (ty[j] > 0)
        angle = PI / 2.0;
       else
        angle = -PI / 2.0;
      else
       angle = atan ( (double) ty[j] / (double) tx[j] );
      if ( (tx[j] >= 0) && (ty[j] < 0) )
       angle += TWO_PI;
      else
       if (tx[j] < 0)
        angle += PI;
      if ( (angle <= ((i == 0) ? 0.0 : p->option[i-1].maxangle)) ||
           (angle >= p->option[i].maxangle) )
       break;
     }
    /* If one of the corners lies outside the wedge, set switch to
    ** indicate label is not to be shown. */
    if (j < 4)
     p->option[i].showlabel = FALSE;
    else
     {
      /* Set the location for the label */
      p->option[i].txt.left   = tx[0];
      p->option[i].txt.top    = ty[0];
      p->option[i].txt.right  = tx[2];
      p->option[i].txt.bottom = ty[2];
      p->option[i].showlabel = TRUE;
     }
       }
     }
    else
     {
      /* Labels are to be positioned outside the pie diagram. */
      for (i = 0; i < p->n; i++)
       {
    if (p->option[i].name == NULL)
     {
      p->option[i].showlabel = FALSE;
      continue;
     }
    /* Get the midpoint of the wedge. */
    if (i == 0) angle = 0.0;
    else angle = p->option[i-1].maxangle;
    angle = (p->option[i].maxangle - angle) / 2.0 + angle;
    xc = p->radius * cos (angle);
    yc = p->radius * sin (angle);
    /* Determine the dimensions of the label string using SYSTEM_FONT. */
    dwsize = GetTextExtent (hdcMem, p->option[i].name,
         lstrlen (p->option[i].name));
    /* Position text label based upon quadrant in which wedge is located */
    if (angle >= 0.0 && angle < PI/2.0)
     {
      /* First quadrant */
      p->option[i].txt.left   = xc;
      p->option[i].txt.top    = yc + HIWORD(dwsize);
      p->option[i].txt.right  = xc + LOWORD(dwsize);
      p->option[i].txt.bottom = yc;
     }
    else if (angle >= PI/2.0 && angle < PI)
     {
      /* Second quadrant */
      p->option[i].txt.left   = xc - (LOWORD(dwsize) + tm.tmAveCharWidth);
      p->option[i].txt.top    = yc +  HIWORD(dwsize);
      p->option[i].txt.right  = xc;
      p->option[i].txt.bottom = yc;
     }
    else if (angle >= PI && angle < 3.0 * PI/2.0)
     {
      /* Third quadrant */
      p->option[i].txt.left   = xc - (LOWORD(dwsize) + tm.tmAveCharWidth);
      p->option[i].txt.top    = yc;
      p->option[i].txt.right  = xc;
      p->option[i].txt.bottom = yc - HIWORD(dwsize);
     }
    else
     {
      p->option[i].txt.left   = xc;
      p->option[i].txt.top    = yc;
      p->option[i].txt.right  = xc + (LOWORD(dwsize) + tm.tmAveCharWidth);
      p->option[i].txt.bottom = yc -  HIWORD(dwsize);
     }
    p->option[i].showlabel = TRUE;

       }
     }
    DeleteDC (hdc);
    DeleteDC (hdcMem);
    return (TRUE);
   }
  return (FALSE);
 }
/* ------------------------------------------------------------------ */
void DrawPieMenu (HDC hdc, LPPIEMENUSTRUCT p)
 /* Draws a Pie Menu on the Device Context pointed to by "hdc", using
 ** information given in the structure pointed to by "p".  */
 {
  short i;
  POINT center;
  DWORD dwOrg;
  HFONT hCurrFont;
  if (p != NULL)
   {
    hCurrFont = SelectObject (hdc, GetStockObject (SYSTEM_FONT));
    SetTextColor (hdc, BLACK);

    for (i = 0; i < p->n; i++)
     {
      SelectObject (hdc, p->option[i].brush);
      Pie (hdc, -(p->radius), p->radius, p->radius, -(p->radius),
      (i == 0) ? p->radius : p->option[i-1].endarc.x,
      (i == 0) ? 0 : p->option[i-1].endarc.y,
      p->option[i].endarc.x, p->option[i].endarc.y);
      if (p->option[i].showlabel)
       {
    if (p->option[i].color == BLACK)
     SetBkColor (hdc, WHITE);
    else
     SetBkColor (hdc, p->option[i].color);
    DrawText (hdc, p->option[i].name, -1, &(p->option[i].txt), DT_CENTER);
       }
      if (p->UseChecks != NULL)
       if (p->option[i].checked)
    InvertRect (hdc, &(p->option[i].txt));
     }
    /* Center the cursor in the Pie Menu */
    dwOrg = GetDCOrg(hdc);
    center.x = 0; center.y = 0;
    LPtoDP (hdc, (LPPOINT) ¢er, 1);
    center.x += LOWORD (dwOrg); center.y += HIWORD (dwOrg);
    SetCursorPos (center.x, center.y);

    SelectObject (hdc, hCurrFont);
   }
 }
/*  --------------------------------------------------------------------- */
short PieMenuProc (LPVIEWWINDATA vwd, LPPIEMENUSTRUCT p)
 {
  /* Retrieves the position of the cursor from within the Pie Menu and converts
  ** this into a selection value. If position can't be converted, "no_choice"
  ** value is returned. */
  short selection;
  short x, y, i, j;
  POINT position;
  double angle;
  RECT rect;

  /* Try to convert position coordinates into a selection */
  GetCursorPos( (LPPOINT) &position);
  position.x -= LOWORD(vwd->DCOrg); position.y -= HIWORD(vwd->DCOrg);
  DP_to_LP (vwd, (LPPOINT) &position, 1);
  x = position.x; y = position.y;
  selection = p->no_choice;     /* set result at "no selection" */
  if ( ((double)x * (double)x + (double)y * (double)y) >
     ((double)p->radius * (double)p->radius) )
   return (selection);     /* outside the circle */
  if (x == 0)
   if (y > 0)
    angle = PI/2.0;
   else
    angle = -PI/2.0;
  else
   angle = atan( (double) y / (double) x);
  if ((x >= 0) && (y < 0))
   angle += TWO_PI;
  else if (x < 0)
   angle += PI;
  for (i = 0; i < p->n; i++)
   if ( (angle >= ((i == 0) ? 0.0 : p->option[i-1].maxangle)) &&
    (angle < p->option[i].maxangle))
    {
     selection = p->option[i].value;
     break;
    }
  /* If we are using labels and inverting the previously selected
  ** option, shut the previously inverted label off here. */
  if (p->UseChecks)
   {
    for (j = 0; j < p->n; j++)
     if (p->option[j].checked)
      {
       p->option[j].checked = FALSE;
      }
    /* ...and turn the new one on here */
    if (selection != p->no_choice)
     p->option[i].checked = TRUE;
   }
  return(selection);
 }
/*  --------------------------------------------------------------------- */
 BOOL DP_to_LP (LPVIEWWINDATA vd, LPPOINT p, int ncount)
 /* A private version of the Windows SDK function, "DPtoLP", that does not
 ** use a Device Context to convert points from device coordinates to
 ** logical coordinates. DP_to_LP requires a pointer to a structure of type,
 ** VIEWWINDATA, that has been previously loaded with the window-viewport
 ** parameters required to convert the points. The remaining arguments are
 ** the same as those for "DPtoLP". The function returns TRUE if the points
 ** can be converted, FALSE otherwise. */
  {
   if (vd != NULL && ncount > 0)
    {
     while (ncount--)
      {
       p->x = (p->x - LOWORD((vd->ViewportOrg)) ) *
    (double) ((int) LOWORD((vd->WindowExt))) /
    (double) ((int) LOWORD((vd->ViewportExt)))
    + LOWORD((vd->WindowOrg));
       p->y = (p->y - HIWORD((vd->ViewportOrg)) ) *
    (double) ((int) HIWORD((vd->WindowExt))) /
    (double) ((int) HIWORD((vd->ViewportExt)))
    + HIWORD((vd->WindowOrg));
       p++;
      }
     return (TRUE);
    }
   else return (FALSE);
  }
/*  --------------------------------------------------------------------- */
 LPVIEWWINDATA GetViewportWindowParms (HDC hDC)
 /* Gets the origins and extents of the viewport and window definitions
 ** specified for the Device Context whose handle is passed in hDC. Also
 ** stores the origin of the Device Context. All results are stored in a
 ** structure of type, VIEWWINDATA, whose address is returned. A NULL
 ** handle passed to the routine returns a NULL pointer.  */
  {
   static VIEWWINDATA vwd;
   if (hDC != NULL)
    {
     vwd.DCOrg       = GetDCOrg(hDC);
     vwd.ViewportExt = GetViewportExt(hDC);
     vwd.ViewportOrg = GetViewportOrg(hDC);
     vwd.WindowExt   = GetWindowExt(hDC);
     vwd.WindowOrg   = GetWindowOrg(hDC);
     return ( (LPVIEWWINDATA) &vwd);
    }
   else
    return (NULL);
  }
/*  --------------------------------------------------------------------- */











Copyright © 1992, Dr. Dobb's Journal

Terms of Service | Privacy Statement | Copyright © 2024 UBM Tech, All rights reserved.