/***************************************************************************
 * popmenu_pak.c -general-purpose dynamic Pop-up Menu routines to help make*
 *                programming alot easier.                                 *
 *                (c) 1990 VIDEOWORKS Computer Applications                *
 *                All rights reserved.                                     *
 *                129 Orchard Avenue, Rocky Mount, VA 24151                *
 *                (703) 483-8219 / 489-3863                                *
 *                                                                         *
 *                Designed and Developed by Paul T. Miller                 *
 *                                                                         *
 * Program Name:  N/A                                                      *
 * Version:       1                                                        *
 * Revision:      0                                                        *
 *-------------------------------------------------------------------------*
 * File: (popmenu_pak.c) dynamic window-relative pop-up menu routines      *
 *-------------------------------------------------------------------------*
 * Modification History                                                    *
 * Date     Author   Comment                                               *
 * -------- ------   -------                                               *
 * 03-29-90    PTM   Created. Initialization/Setup
 * 04-01-90    PTM   PMenu drawing/handling. RastPort FONT characteristics
 * 05-07-90    PTM   Modify PMenu box expansion to fit in window
 * 05-09-90    PTM   Move itemtext positioning to drawmenu()
 *
 ***************************************************************************/

#include "popmenu_pak.h"

#ifndef GRAPHICS_GFXMACROS_H
#include <graphics/gfxmacros.h>
#endif

/* Here are some constants for itemtext placement and item sizes */
#define PMENU_WOFF      16    /* sixteen pixels wider than longest text */
#define PITEM_VOFF      2     /* extra pixels for item (+font height) */
#define PITEM_YOFF      2     /* vertical index into item for text */
#define PITEM_XOFF      2     /* horizontal index into item for text */

struct PMenu *BuildPMenu(item, flags, inum, title, id)
struct PMenuItem *item;
USHORT flags;
SHORT inum;
UBYTE *title;
USHORT id;
{
   struct PMenu *pmenu;
   struct IntuiText *itext = NULL;
   struct Border *border;
   struct Gadget *gad;

   /* Allocate memory for menu, gadget, gadget text, and border */

   pmenu = (struct PMenu *)AllocMem(sizeof(struct PMenu), MEMF_CLEAR);
   if (!pmenu) return(NULL);

   gad = (struct Gadget *)AllocMem(sizeof(struct Gadget), MEMF_CLEAR);
   if (!gad)
   {
      FreeMem(pmenu, sizeof(struct PMenu));
      return(NULL);
   }

   border = (struct Border *)AllocMem(sizeof(struct Border), MEMF_CLEAR);
   if (!border)
   {
      FreeMem(gad, sizeof(struct Gadget));
      FreeMem(pmenu, sizeof(struct PMenu));
      return(NULL);
   }

   itext = (struct IntuiText *)AllocMem(sizeof(struct IntuiText), MEMF_CLEAR);
   if (!itext)
   {
      FreeMem(border, sizeof(struct Border));
      FreeMem(gad, sizeof(struct Gadget));
      FreeMem(pmenu, sizeof(struct PMenu));
      return(NULL);
   }

   if (flags & SHADOWED)
      border->Count = 8;
   else
      border->Count = 5;

   border->XY = (SHORT *)AllocMem(sizeof(SHORT)*16, MEMF_CLEAR);
   border->FrontPen = 1;
   border->BackPen = 0;
   border->DrawMode = JAM1;

   pmenu->FirstPItem = item;
   pmenu->Flags = flags;
   pmenu->MenuName = (UBYTE *)AllocMem(80, MEMF_CLEAR);
   if (title)
   {
      strcpy(pmenu->MenuName, title);
      pmenu->Flags |= TITLED;    /* title supplied by user */
   }

   if (pmenu->FirstPItem)
   {
      if (inum < 1) inum = 1;
      pmenu->ActiveItem = GetPItem(pmenu, inum);
      pmenu->Flags |= PREDEFINED;
   }
   itext->FrontPen = 1;    /* Build text for user-controlled menu title */
   itext->BackPen = 0;
   itext->DrawMode = JAM1;
   itext->IText = pmenu->MenuName;

   gad->NextGadget = NULL;
   gad->GadgetType = BOOLGADGET;
   gad->Activation = GADGIMMEDIATE | FOLLOWMOUSE;
   gad->Flags = GADGHCOMP;
   gad->GadgetRender = (APTR)border;
   gad->SelectRender = NULL;
   gad->MutualExclude = NULL;
   gad->SpecialInfo = NULL;
   gad->GadgetText = itext;
   gad->GadgetID = id;
   gad->UserData = (APTR)pmenu;
   pmenu->PMenuGadget = gad;

   return(pmenu);
}

void SetPMenuColor(pmenu, frontpen, backpen)
struct PMenu *pmenu;
USHORT frontpen, backpen;
{
   struct Border *border;
   struct IntuiText *itext;

   if (pmenu)
   {
      itext = (struct IntuiText *)pmenu->PMenuGadget->GadgetText;
      border = (struct Border *)pmenu->PMenuGadget->GadgetRender;
      if (itext)
      {
         itext->FrontPen = frontpen;
         itext->BackPen = backpen;
      }
      if (border)
      {
         border->FrontPen = frontpen;
         border->BackPen = backpen;
      }
   }
}

InitPMenu(window, pmenu, x, y)
struct Window *window;
struct PMenu *pmenu;
SHORT x, y;
{
   struct RastPort *rp = window->RPort;
   struct Gadget *gad;
   ULONG flags;

   if (!window || !pmenu)
      return(NULL);

   flags = window->IDCMPFlags;
   ModifyIDCMP(window, flags | GADGETUP);

   pmenu->PMenuWindow = window;

   /* no user-set width, so find width/height of activation box */
   if (pmenu->Width == 0)
      pmenu->Width = (SHORT)find_maxpitemwidth(pmenu) + PMENU_WOFF;

   if (pmenu->Height == 0)
      pmenu->Height = rp->TxHeight+PITEM_VOFF;

   /* find out where in the window to render the window */
   if (x == -1)
      pmenu->LeftEdge = window->Width - pmenu->Width - 5;
   else if (x < -1)
      pmenu->LeftEdge = window->Width + x;
   else
      pmenu->LeftEdge = x;

   if (y == -1)
      pmenu->TopEdge = window->Height - pmenu->Height - 5;
   else if (y < -1)
      pmenu->TopEdge = window->Height + y;
   else
      pmenu->TopEdge = y;

   /* set up activation gadget attributes */
   gad = pmenu->PMenuGadget;
   gad->LeftEdge = pmenu->LeftEdge;
   gad->TopEdge = pmenu->TopEdge;
   gad->Width = pmenu->Width;
   gad->Height = pmenu->Height;

   calc_border(pmenu);

   pmenu->Flags |= MENUENABLED;
   pmenu->Flags |= MENU_INIT;

   AddGadget(window, pmenu->PMenuGadget, -1);
   RefreshPMenu(pmenu);

   return(1);
}

find_maxpitemwidth(pmenu)
struct PMenu *pmenu;
{
   int length, max = 0;
   struct PMenuItem *item;

   for (item = pmenu->FirstPItem; item; item = item->NextPItem)
   {
      if (item->Flags & ITEMTEXT)
      {
         length = IntuiTextLength((struct IntuiText *)item->ItemFill);
         if (length > max) max = length;
      }
   }
   return(max);
}

void calc_border(pmenu)
struct PMenu *pmenu;
{
   struct Border *b;
   SHORT *dat;

   if (pmenu)
   {
      b = (struct Border *)pmenu->PMenuGadget->GadgetRender;
      dat = b->XY;

      if (pmenu->Flags & SHADOWED)
      {
         b->Count = 8;
         dat[0] = dat[2] = pmenu->Width+1;
         dat[1] = dat[4] = 1;
         dat[3] = dat[5] = pmenu->Height+1;
         dat[6] = dat[11] = dat[12] = dat[13] = dat[14] = 0;
         dat[7] = dat[9] = dat[15] = pmenu->Height;
         dat[8] = dat[10] = pmenu->Width;
      }
      else
      {
         b->Count = 5;
         dat[0] = dat[1] = dat[3] = dat[6] = dat[8] = dat[9] = 0;
         dat[2] = dat[4] = pmenu->Width;
         dat[5] = dat[7] = pmenu->Height;
      }
   }
}

void RefreshPMenu(pmenu)
struct PMenu *pmenu;
{
   struct IntuiText *itext;
   struct PMenuItem *item;
   int x, y, w, h;
   UBYTE bpen;

   bpen = pmenu->PMenuGadget->GadgetText->BackPen;

   if (pmenu && (pmenu->Flags & MENU_INIT))
   {
      if (pmenu->Flags & TITLED)
         pmenu->PMenuGadget->GadgetText->IText = pmenu->MenuName;
      else
      {
         item = pmenu->ActiveItem;
         if (item->Flags & ITEMTEXT)
         {
            itext = (struct IntuiText *)pmenu->ActiveItem->ItemFill;
            pmenu->PMenuGadget->GadgetText->IText = itext->IText;
         }
      }
      itext = pmenu->PMenuGadget->GadgetText;
      itext->LeftEdge = (pmenu->Width - IntuiTextLength(itext))/2;
      itext->TopEdge = PITEM_YOFF;
      x = pmenu->LeftEdge;
      y = pmenu->TopEdge;
      w = pmenu->Width;
      h = pmenu->Height;
      SetDrMd(pmenu->PMenuWindow->RPort, JAM1);
      SetAPen(pmenu->PMenuWindow->RPort, bpen);
      RectFill(pmenu->PMenuWindow->RPort, x, y, x+w, y+h);
      RefreshGList(pmenu->PMenuGadget, pmenu->PMenuWindow, NULL, 1);
   }
}

void RemovePMenu(pmenu)
struct PMenu *pmenu;
{
   if (pmenu && pmenu->Flags & MENU_INIT)
   {
      pmenu->Flags &= ~MENU_INIT;
      pmenu->Flags &= ~MENUENABLED;
      RemoveGadget(pmenu->PMenuWindow, pmenu->PMenuGadget);
   }
}

void FreePMenu(pmenu)
struct PMenu *pmenu;
{
   struct Border *border;

   if (pmenu)
   {
      RemovePMenu(pmenu);

      FreeMem(pmenu->PMenuGadget->GadgetText, sizeof(struct IntuiText));
      FreeMem(pmenu->MenuName, 80);

      border = (struct Border *)pmenu->PMenuGadget->GadgetRender;
      if (border)
      {
         FreeMem(border->XY, sizeof(SHORT) * 16);
         FreeMem(border, sizeof(struct Border));
      }
      FreeMem(pmenu->PMenuGadget, sizeof(struct Gadget));
      FreeMem(pmenu, sizeof(struct PMenu));
   }
}

void SetActiveItem(pmenu, inum)
struct PMenu *pmenu;
SHORT inum;
{
   struct PMenuItem *item;

   if (inum < 1) return;

   if (pmenu && (pmenu->Flags & COMMPMENU))
   {
      item = GetPItem(pmenu, inum);
      if (item)
      {
         pmenu->ActiveItem = item;
         RefreshPMenu(pmenu);
      }
   }
}

struct PMenuItem *GetPItem(pmenu, inum)
struct PMenu *pmenu;
SHORT inum;
{
   struct PMenuItem *item;
   UCOUNT i = 1;

   for (item = pmenu->FirstPItem; item; item = item->NextPItem, i++)
      if (i == inum)
         return(item);

   return(NULL);
}

SHORT GetPItemNum(pmenu, item)
struct PMenu *pmenu;
struct PMenuItem *item;
{
   struct PMenuItem *pitem;
   SHORT i = 0;

   if (item == NULL) return(0);

   if (pmenu->FirstPItem == item)
      return(1);

   for (pitem = pmenu->FirstPItem; pitem; pitem = pitem->NextPItem, i++)
      if (pitem == item)
         return((SHORT)(i+1));

   return(0);
}

CountPMenuItems(pmenu)
struct PMenu *pmenu;
{
   struct PMenuItem *item;
   UCOUNT i;

   if (pmenu->FirstPItem == NULL)
      return(0);

   for (i = 0, item = pmenu->FirstPItem; item; item = item->NextPItem, i++)
      ;

   return(i+1);
}

/* Sets the menu activation box of a popup menu to specified text:
   Only if an initial constant title was supplied with BuildPMenu() */

void SetPMenuText(pmenu, text)
struct PMenu *pmenu;
UBYTE *text;
{
   if (pmenu)
      if (pmenu->Flags & TITLED)
      {
         strcpy(pmenu->MenuName, text);
         RefreshPMenu(pmenu);
      }
}

USHORT HandlePMenu(pmenu)
struct PMenu *pmenu;
{
   ULONG oldflags;
   struct Window *win;
   struct BitMap *sbm;
   struct PMenuItem *item;
   struct IntuiMessage *message;
   ULONG class;
   USHORT code, inum;
   SHORT mx, my;
   SHORT lnum = 0, mousemoved = 0;

   if (!pmenu) return(NULL);

   calc_menusize(pmenu);
   sbm = savebackground(pmenu);

   drawpmenu(pmenu);

   win = pmenu->PMenuWindow;
   oldflags = win->IDCMPFlags;
   ReportMouse(win, TRUE);
   ModifyIDCMP(win, oldflags | MOUSEMOVE | MOUSEBUTTONS);

   lnum = GetPItemNum(pmenu, pmenu->ActiveItem);
   complement_item(pmenu, lnum);

   while (1)
   {
      WaitPort(win->UserPort);
      while (message = (struct IntuiMessage *)GetMsg(win->UserPort))
      {
         class = message->Class;
         code = message->Code;
         mx = message->MouseX;
         my = message->MouseY;

         ReplyMsg(message);

         switch (class)
         {
            case MOUSEMOVE:
               mousemoved = 1;
               break;
            case MOUSEBUTTONS:
               switch (code)
               {
                  case SELECTUP:
                     item = getmenuitem(pmenu, mx, my);
                     restorebackground(pmenu, sbm);
                     ModifyIDCMP(win, oldflags);
                     inum = GetPItemNum(pmenu, item);
                     if (!(pmenu->Flags & TITLED))
                        SetActiveItem(pmenu, inum);
                     return(inum);
                     break;
               }
               break;
         }
      }
      if (mousemoved)
      {
         mousemoved = 0;
         handle_items(pmenu, mx, my, &lnum);
      }
   }
}

struct PMenuItem *getmenuitem(pmenu, mx, my)
struct PMenu *pmenu;
SHORT mx, my;
{
   struct PMenuItem *item;
   SHORT x, y, x2, y2;
   USHORT num;

   if (!pmenu) return(NULL);

   x = pmenu->JazzX;
   y = pmenu->JazzY;
   x2 = pmenu->JazzX + pmenu->BeatX;
   y2 = pmenu->JazzY + pmenu->BeatY;

   if (mx > x && my > y && mx < x2 && my < y2)
   {
      num = (my - y - PITEM_YOFF) / pmenu->Height;
      item = GetPItem(pmenu, (SHORT)(num+1));
      if (item->Flags & ITEMENABLED)
         return(item);
   }
   return(NULL);
}

void calc_menusize(pmenu)
struct PMenu *pmenu;
{
   UCOUNT num;

   num = CountPMenuItems(pmenu);

   if (pmenu->Flags & TITLED)    /* titled, so find width of largest item */
      pmenu->BeatX = find_maxpitemwidth(pmenu) + PMENU_WOFF;
   else
      pmenu->BeatX = pmenu->Width;  /* otherwise width/height of menu box */

   pmenu->BeatY = pmenu->Height * (num-1) + PITEM_VOFF-1;
   if (pmenu->Flags & SHADOWED)
   {
      pmenu->BeatX++;
      pmenu->BeatY++;
   }
   pmenu->JazzX = pmenu->LeftEdge;     /* upper-left corner of expanded */
   pmenu->JazzY = pmenu->TopEdge;

   /* shift menu into window if it extends out right side */
   if (pmenu->JazzX + pmenu->BeatX > pmenu->PMenuWindow->Width)
      pmenu->JazzX = pmenu->LeftEdge + pmenu->Width - pmenu->BeatX + 1;

   if (pmenu->Flags & COMMPMENU)
   {
      num = GetPItemNum(pmenu, pmenu->ActiveItem);
      pmenu->JazzY -= ((num-1) * pmenu->Height);
      while (pmenu->JazzY <= 0)
         pmenu->JazzY += pmenu->Height;
   }
}

struct BitMap *savebackground(pmenu)
struct PMenu *pmenu;
{
   int x, y, w, h;
   struct Window *win;
   struct BitMap *bm;

   win = pmenu->PMenuWindow;
   x = win->LeftEdge + pmenu->JazzX;
   y = win->TopEdge + pmenu->JazzY;
   w = pmenu->BeatX + 1;
   h = pmenu->BeatY + 1;

   bm = allocbitmap((USHORT)w, (USHORT)h, (UBYTE)win->RPort->BitMap->Depth);
   if (bm)
      BltBitMap(win->RPort->BitMap, x, y, bm, 0, 0, w, h, 0xc0, 0xff, NULL);

   return(bm);
}

void restorebackground(pmenu, bm)
struct PMenu *pmenu;
struct BitMap *bm;
{
   int x, y, w, h;
   struct Window *win;

   if (!pmenu) return;

   win = pmenu->PMenuWindow;
   x = win->LeftEdge + pmenu->JazzX;
   y = win->TopEdge + pmenu->JazzY;
   w = pmenu->BeatX + 1;
   h = pmenu->BeatY + 1;

   if (bm)
   {
      BltBitMap(bm, 0, 0, win->RPort->BitMap, x, y, w, h, 0xc0, 0xff, NULL);
      freebitmap(bm);
   }
   pmenu->Flags &= ~MIDRAWN;
}

void drawpmenu(pmenu)
struct PMenu *pmenu;
{
   int x, y, w, h, i;
   struct RastPort *rp;
   struct PMenuItem *item;
   struct IntuiText *itext;
   UBYTE fpen, bpen;

   rp = pmenu->PMenuWindow->RPort;

   SetDrMd(rp, JAM1);

   fpen = pmenu->PMenuGadget->GadgetText->FrontPen;
   bpen = pmenu->PMenuGadget->GadgetText->BackPen;

   x = pmenu->JazzX;
   y = pmenu->JazzY;
   w = pmenu->BeatX;
   h = pmenu->BeatY;

   if (pmenu->Flags & SHADOWED)
   {
      w--;
      h--;
   }

   /* draw the menu outline */
   SetAPen(rp, fpen);
   RectFill(rp, x, y, x+w, y+h);
   SetAPen(rp, bpen);
   RectFill(rp, x+1, y+1, x+w-2, y+h-2);

   SetAPen(rp, fpen);
   if (pmenu->Flags & SHADOWED)
   {
      Move(rp, x+w+1, y+1);
      Draw(rp, x+w+1, y+h+1);
      Draw(rp, x+1, y+h+1);
   }

   for (i = 0, item = pmenu->FirstPItem; item; item = item->NextPItem, i++)
   {
      if (item->Flags & ITEMTEXT)
      {
         itext = (struct IntuiText *)item->ItemFill;
         itext->TopEdge = PITEM_YOFF;

         /* position item text */
         if (item->Flags & CENTERED)
            itext->LeftEdge = (w - IntuiTextLength(itext))/2;
         else
            itext->LeftEdge = PITEM_XOFF;

         SetAPen(rp, itext->FrontPen);
         Move(rp, x+itext->LeftEdge,
                  y + itext->TopEdge + (i * pmenu->Height) + rp->TxBaseline);
         Text(rp, itext->IText, strlen(itext->IText));
      }
   }
   pmenu->Flags |= MIDRAWN;
}

void handle_items(pmenu, mx, my, lnum)
struct PMenu *pmenu;
SHORT mx, my, *lnum;
{
   SHORT x, y, x2, y2;
   USHORT num;

   if (!pmenu) return;

   x = pmenu->JazzX;
   y = pmenu->JazzY;
   x2 = pmenu->JazzX + pmenu->BeatX - 1;
   y2 = pmenu->JazzY + pmenu->BeatY - 1;
   if (pmenu->Flags & SHADOWED)
   {
      x2--;
      y2--;
   }

   if (mx > x && my > y && mx < x2 && my < y2)
   {
      num = ((my - y - PITEM_VOFF) / pmenu->Height) + 1;
      if (num != *lnum)
      {
         if (*lnum != 0)
            complement_item(pmenu, *lnum);

         complement_item(pmenu, num);
      }
      *lnum = num;
      return;
   }
   if (*lnum != 0)
      complement_item(pmenu, *lnum);
   *lnum = 0;
}

void complement_item(pmenu, num)
struct PMenu *pmenu;
SHORT num;
{
   int x, y, w, h;
   struct PMenuItem *item;
   struct RastPort *rp = pmenu->PMenuWindow->RPort;
   BYTE old_mode = rp->DrawMode;

   item = GetPItem(pmenu, num);

   if (pmenu == NULL || item == NULL)
      return;

   if (item->Flags & ITEMENABLED)
   {
      x = pmenu->JazzX+1;
      y = pmenu->JazzY + (num-1)*pmenu->Height+1;
      w = pmenu->BeatX-4;
      h = pmenu->Height-1;

      SetDrMd(rp, COMPLEMENT);
      SetAPen(rp, 1);
      RectFill(rp, x, y, x+w, y+h);
      SetDrMd(rp, old_mode);
   }
}

struct BitMap *allocbitmap(width, height, depth)
USHORT width, height;
UBYTE depth;
{
   struct BitMap *bm;
   register int i;

   bm = (struct BitMap *)AllocMem(sizeof(struct BitMap), MEMF_CLEAR);
   if (bm)
   {
      InitBitMap(bm, (long)depth, (long)width, (long)height);

      for (i = 0; i < depth; i++)
      {
         bm->Planes[i] = (PLANEPTR)AllocRaster(width, height);
         if (!bm->Planes[i])
         {
            freebitmap(bm);
            return(NULL);
         }
      }
   }
   return(bm);
}

void freebitmap(bm)
struct BitMap *bm;
{
   register int i;

   if (bm)
   {
      for (i = 0; i < bm->Depth; i++)
         if (bm->Planes[i])
            FreeMem(bm->Planes[i], (long)(bm->BytesPerRow * bm->Rows));
      FreeMem(bm, sizeof(struct BitMap));
   }
}
