/***************************************************************************
 * newmenus.c -   WorkBench 2.0 gadtools.library NewMenu functions for 1.3 *
 *                (c) 1991 Videoworks Computer Applications                *
 *                All rights reserved.                                     *
 *                                                                         *
 *                Written by Paul T. Miller                                *
 *                                                                         *
 * MODULE NOTES   Developed under Lattice/SAS 5.10                         *
 *                Link with standard 1.3 code to use 2.0 NewMenu facility  *
 *                Include "newmenus.h" if 2.0 includes are unavailable     *
 *                Handles correct menu, item, and sub-item sizes and       *
 *                   offsets, based on normal and checked items            *
 *                Handles automatic generation of NM_BARLABEL bars         *
 *                Spaces are inserted automatically in front of checkable  *
 *                   items                                                 *
 *                Handles NewMenu sub-menu "»" symbol automatically        *
 *                Does not handle IM_ITEM and IM_SUB image item types -    *
 *                   Use TEXT item labels only                             *
 *                The VisualInfo pointer is ignored by LayoutMenuItemsA()  *
 *                The GTMN_FullMenu tag is ignored by CreateMenusA()       *
 *                Default highlighting is HIGHCOMP, but this can be changed*
 *                   manually by modifying the returned Menu and MenuItems *
 *                Spaces are inserted automatically in front of checkable  *
 *                   items                                                 *
 *                DO NOT use the GTMN_TextAttr tag - non-default fonts     *
 *                   are supported, but do NOT work                        *
 *                                                                         *
 * Program Name:  N/A                                                      *
 * Version:       1                                                        *
 * Revision:      1                                                        *
 *-------------------------------------------------------------------------*
 * File: (newmenus.c) WorkBench 2.0 (1.3-compatible) NewMenu functions     *
 *-------------------------------------------------------------------------*
 * Modification History                                                    *
 * Date     Author   Comment                                               *
 * -------- ------   -------                                               *
 * 11-09-90    PTM   Created. WB2.0 NewMenu allocation, deallocation, and
 *                   layout
 * 11-25-90    PTM   Add v36 menuitem userdata allocation and tags
 * 06-12-91    PTM   Add v37 tags
 ***************************************************************************/

#include <exec/types.h>
#include <exec/memory.h>
#include <intuition/intuition.h>
#include <graphics/gfxbase.h>

#include <proto/exec.h>
#include <proto/intuition.h>
#include <proto/graphics.h>

#ifndef NEWMENUS_H
#include "newmenus.h"
#endif

/* internal Menu function structures/flags (for easy UserData handling) */
struct ExtMenu {
   struct Menu Menu;
   APTR        UserData;
};

struct ExtMenuItem {
   struct MenuItem MenuItem;
   APTR        UserData;
};

#define SPECIAL_ITEMTEXT   0x8000   /* menu flag for allocated item text */


/* module-local functions */
static void free_itemtext(struct MenuItem *);

extern struct GfxBase *GfxBase;
extern struct IntuitionBase *IntuitionBase;

/**/
/* Menu functions */
/**/

struct Menu *CreateMenus(newmenu, tag1)
struct NewMenu *newmenu;
Tag tag1;
{
   return(CreateMenusA(newmenu, (struct TagItem *)&tag1));
}

struct Menu *CreateMenusA(newmenu, taglist)
struct NewMenu *newmenu;
struct TagItem *taglist;
{
   struct NewMenu *nm;
   struct Menu *menu, *lastmenu, *m;
   struct MenuItem *item, *lastitem, *lastsub;
   struct IntuiText *itext;
   struct TagItem *tag;
   int pos;
   UBYTE frontpen = 0, last_type = NM_END;
   ULONG *error = NULL;

   pos = 0;
   while (tag = &taglist[pos])
   {
      if (tag->ti_Tag == TAG_DONE)
         break;

      switch (tag->ti_Tag)
      {
         case GTMN_FrontPen:
            frontpen = tag->ti_Data;
            break;
         case GTMN_FullMenu:
            break;
         case GTMN_SecondaryError:
            error = (ULONG *)tag->ti_Data;
            break;
      }
      pos++;
   }

   pos = 0;
   menu = lastmenu = NULL;

   while (1)
   {
      nm = &newmenu[pos];

      /* skip image items */
      if (nm->nm_Type == IM_ITEM || nm->nm_Type == IM_SUB)
      {
         pos++;
         continue;
      }
      if (nm->nm_Type == NM_END)
         break;

      /* check for invalid arrangements (sub following a title, first entry
         NOT a title, two titles in a row) */
      if ((last_type == NM_TITLE && nm->nm_Type == NM_SUB) ||
          (last_type == NM_END && nm->nm_Type != NM_TITLE) ||
          (last_type == NM_TITLE && nm->nm_Type == NM_TITLE))
      {
         if (error)
            *error = GTMENU_INVALID;
         FreeMenus(menu);
         return(NULL);
      }
      last_type = nm->nm_Type;

      if (nm->nm_Type == NM_TITLE)
      {
         m = (struct Menu *)AllocMem(sizeof(struct ExtMenu), MEMF_CLEAR);
         if (!m)
         {
            if (error)
               *error = GTMENU_NOMEM;
            FreeMenus(menu);
            return(NULL);
         }
         /* copy over the UserData extension */
         ((struct ExtMenu *)m)->UserData = nm->nm_UserData;

         if (lastmenu)
            lastmenu->NextMenu = m;
         else
            menu = m;                  /* first menu */
         lastmenu = m;
         m->MenuName = (BYTE *)nm->nm_Label;
         if (!(nm->nm_Flags & NM_MENUDISABLED))
            m->Flags = MENUENABLED;
         lastitem = NULL;
      }
      else
      {
         if (nm->nm_Type != NM_ITEM && nm->nm_Type != NM_SUB)
         {
            /* an invalid menu type was specified */
            if (error)
               *error = GTMENU_INVALID;
            FreeMenus(menu);
            return(NULL);
         }
         item = (struct MenuItem *)AllocMem(sizeof(struct ExtMenuItem), MEMF_CLEAR);
         itext = (struct IntuiText *)AllocMem(sizeof(struct IntuiText), MEMF_CLEAR);
         if (!item || !itext)
         {
            if (!itext) FreeMem(itext, sizeof(struct IntuiText));
            if (!item) FreeMem(item, sizeof(struct MenuItem));
            if (error)
               *error = GTMENU_NOMEM;
            FreeMenus(menu);
            return(NULL);
         }
         /* copy over the UserData extension */
         ((struct ExtMenuItem *)item)->UserData = nm->nm_UserData;

         itext->DrawMode = JAM1;
         itext->FrontPen = frontpen;
         itext->LeftEdge = 0;
         itext->TopEdge = 1;
         itext->IText = (UBYTE *)nm->nm_Label;

         item->ItemFill = (APTR)itext;
         item->MutualExclude = nm->nm_MutualExclude;
         item->Flags = nm->nm_Flags | ITEMTEXT | HIGHCOMP;
         if (!(nm->nm_Flags & NM_ITEMDISABLED))
            item->Flags |= ITEMENABLED;
         if (nm->nm_CommKey)
         {
            item->Command = (BYTE)nm->nm_CommKey[0];
            item->Flags |= COMMSEQ;
         }
         if (nm->nm_Label == NM_BARLABEL)
         {
            item->SelectFill = (APTR)1;         /* allocate a bar later */
            item->Flags &= ~ITEMENABLED;
            item->Flags |= SPECIAL_ITEMTEXT;
         }

         switch (nm->nm_Type)
         {
            case NM_ITEM:
               if (lastitem)
                  lastitem->NextItem = item;
               else
                  lastmenu->FirstItem = item;
               lastitem = item;
               lastsub = NULL;
               break;
            case NM_SUB:
               if (lastsub)
                  lastsub->NextItem = item;
               else
                  lastitem->SubItem = item;
               lastsub = item;
               break;
         }
      }
      pos++;
   }
   return(menu);
}

BOOL LayoutMenuItems(firstitem, vi, tag1)
struct MenuItem *firstitem;
APTR vi;
Tag tag1;
{
   return(LayoutMenuItemsA(firstitem, vi, (struct TagItem *)&tag1));
}

BOOL LayoutMenuItemsA(firstitem, vi, taglist)
struct MenuItem *firstitem;
APTR vi;
struct TagItem *taglist;
{
   struct Menu *menu = NULL;
   struct MenuItem *item;
   struct IntuiText *itext;
   UBYTE *text, fontheight, fontwidth;
   struct TextFont *font;
   WORD y, width, len, max = 0;
   BOOL command = 0, check = 0, sub = 0;
   struct TagItem *tag;
   LONG tagnum;
   struct TextAttr *textattr = NULL;

   tagnum = 0;

   while (tag = &taglist[tagnum])
   {
      if (tag->ti_Tag == TAG_DONE)
         break;

      switch (tag->ti_Tag)
      {
         case GTMN_TextAttr:
            textattr = (struct TextAttr *)tag->ti_Data;
            break;
         case GTMN_Menu:
            menu = (struct Menu *)tag->ti_Data;
            break;
      }
      tag++;
   }

   if (textattr)
   {
      font = OpenFont(textattr);
      if (font)
      {
         fontwidth = font->tf_XSize;
         fontheight = font->tf_YSize;
         CloseFont(font);
      }
      else
         textattr = NULL;
   }
   if (!textattr)
   {
      fontwidth = GfxBase->DefaultFont->tf_XSize;
      fontheight = GfxBase->DefaultFont->tf_YSize;
   }

   /* first get the maximum string length */
   for (item = firstitem; item; item = item->NextItem)
   {
      itext = (struct IntuiText *)item->ItemFill;
      if (itext->IText != (UBYTE *)-1)
         len = strlen(itext->IText);
      if (len > max)
         max = len;
   }
   len = max;
   width = (len * fontwidth) + 3;

   /* look for possible modifiers */
   for (item = firstitem; item; item = item->NextItem)
   {
      if (item->Flags & COMMSEQ)
         command = 1;
      if (item->Flags & CHECKIT)
         check = 1;
      if (item->SubItem)
         sub = 1;
   }
   /* and modify the menu width appropriately */
   if (command)
      width += COMMWIDTH;
   if (check)
      width += CHECKWIDTH + 2;
   if (sub || command)
      width += (fontwidth * 2);     /* insert two characters */

   /* adjust each item that can have a check, or allocate text for an item
      that has a submenu, inserting a '»' */

   for (item = firstitem; item; item = item->NextItem)
   {
      itext = (struct IntuiText *)item->ItemFill;

      if (item->Flags & CHECKIT)
         itext->LeftEdge += CHECKWIDTH;

      if (item->SubItem)
      {
         text = (UBYTE *)AllocMem(strlen(itext->IText)+3, NULL);
         if (text)
         {
            strcpy(text, itext->IText);
            strcat(text, " »");
            itext->IText = text;
            /* now tell the menu item that it has custom-allocated text */
            item->Flags |= SPECIAL_ITEMTEXT;
         }
      }
   }

   /* now assign the menu positions/sizes */
   y = 0;
   for (item = firstitem; item; item = item->NextItem)
   {
      item->LeftEdge = 0;
      item->TopEdge = y;
      item->Width = width;
      item->Height = fontheight + 1;
      if (item->SelectFill)                     /* allocate a barlabel */
      {
         item->SelectFill = NULL;
         itext = (struct IntuiText *)item->ItemFill;
         itext->IText = (UBYTE *)AllocMem(len+1, MEMF_CLEAR);
         setmem(itext->IText, len, (ULONG)'~');
         itext->TopEdge++;
         item->Height -= 3;
      }
      y += item->Height;
   }
   return(1);
}

BOOL LayoutMenus(firstmenu, vi, tag1)
struct Menu *firstmenu;
APTR vi;
Tag tag1;
{
   return(LayoutMenusA(firstmenu, vi, (struct TagItem *)&tag1));
}

BOOL LayoutMenusA(firstmenu, vi, taglist)
struct Menu *firstmenu;
APTR vi;
struct TagItem *taglist;
{
   struct Menu *menu;
   struct MenuItem *item, *sub;
   WORD mx;
   UBYTE fontwidth;

   fontwidth = GfxBase->DefaultFont->tf_XSize;

   mx = 2;
   menu = firstmenu;
   while (menu)
   {
      menu->TopEdge = 0;
      menu->LeftEdge = mx;
      menu->Width = (strlen(menu->MenuName) + 1) * fontwidth;
      menu->Height = 0;
      if (menu->FirstItem)
      {
         LayoutMenuItemsA(menu->FirstItem, vi, taglist);
         for (item = menu->FirstItem; item; item = item->NextItem)
         {
            if (item->SubItem)
            {
               LayoutMenuItemsA(item->SubItem, vi, taglist);
               for (sub = item->SubItem; sub; sub = sub->NextItem)
               {
                  sub->TopEdge--;
                  sub->Width += 2;
                  sub->LeftEdge = item->Width - (fontwidth * 2) - 5;
               }
            }
         }
      }
      mx += menu->Width + fontwidth;
      menu = menu->NextMenu;
   }
   return(1);
}

void FreeMenus(menu)
struct Menu *menu;
{
   struct Menu *m;
   struct MenuItem *item, *sub;

   while (m = menu)
   {
      while (item = menu->FirstItem)
      {
         while (sub = item->SubItem)
         {
            item->SubItem = sub->NextItem;
            free_itemtext(sub);
            FreeMem(sub, sizeof(struct MenuItem)+4L);
         }
         menu->FirstItem = item->NextItem;
         free_itemtext(item);
         FreeMem(item, sizeof(struct MenuItem)+4L);
      }
      menu = m->NextMenu;
      FreeMem(m, sizeof(struct Menu)+4L);
   }
}

/* module-local functions */

static void free_itemtext(item)
struct MenuItem *item;
{
   struct IntuiText *itext;

   if (item)
   {
      itext = (struct IntuiText *)item->ItemFill;
      if (item->Flags & SPECIAL_ITEMTEXT)    /* a barlabel or custom text */
         FreeMem(itext->IText, strlen(itext->IText)+1);
      FreeMem(itext, sizeof(struct IntuiText));
   }
}
