/* NeXTstep menu module
   Copyright (C) 1986, 1988, 1993 Free Software Foundation, Inc.

This file is part of GNU Emacs.

GNU Emacs is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2, or (at your option)
any later version.

GNU Emacs is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with GNU Emacs; see the file COPYING.  If not, write to
the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.  */

#import <appkit/appkit.h>

#include <signal.h>
#include "config.h"
#include "lisp.h"
#include "frame.h"
#include "buffer.h"
#include "window.h"
#include "keyboard.h"
#include "commands.h"
#include "blockinput.h"
#include "nsterm.h"
#include "dispextern.h"
#include "termhooks.h"
#include "multi-frame.h"

#define MenuStagger 10.0

static id oldmenu=nil;
Lisp_Object ns_menu_path;

DEFUN ("ns-popup-menu",Fns_popup_menu, Sns_popup_menu, 1, 2, 0,
  "Pop up a deck-of-cards menu and return user's selection.\n\
POSITION is a position specification.  This is either a mouse button event\n\
or a list ((XOFFSET YOFFSET) WINDOW)\n\
where XOFFSET and YOFFSET are positions in pixels from the top left\n\
corner of WINDOW's frame.  (WINDOW may be a frame object instead of a window.)\n\
This controls the position of the center of the first line\n\
in the first pane of the menu, not the top left of the menu as a whole.\n\
\n\
MENU is a specifier for a menu.  For the simplest case, MENU is a keymap.\n\
The menu items come from key bindings that have a menu string as well as\n\
a definition; actually, the \"definition\" in such a key binding looks like\n\
\(STRING . REAL-DEFINITION).  To give the menu a title, put a string into\n\
the keymap as a top-level element.\n\n\
You can also use a list of keymaps as MENU.\n\
  Then each keymap makes a separate pane.\n\
When MENU is a keymap or a list of keymaps, the return value\n\
is a list of events.\n\n\
Alternatively, you can specify a menu of multiple panes\n\
  with a list of the form (TITLE PANE1 PANE2...),\n\
where each pane is a list of form (TITLE ITEM1 ITEM2...).\n\
Each ITEM is normally a cons cell (STRING . VALUE);\n\
but a string can appear as an item--that makes a nonselectable line\n\
in the menu.\n\
With this form of menu, the return value is VALUE from the chosen item.")
  (position, menu)
     Lisp_Object position, menu;
   {
   id panel;
   Lisp_Object window,x,y,tem;
   struct frame *f;
   NXPoint p;

   if (NILP(position))
      {
      /* Don't create a menu.  Just precalculate the keyboard equivalents */
      return Qnil;
      }

   if (CONSP(ns_menu_path))
      {
      Lisp_Object ret=XCONS(ns_menu_path)->car;
      ns_menu_path=XCONS(ns_menu_path)->cdr;
      return ret;
      }

   check_ns();

   if (EQ (position, Qt))
      {
      /* Use the mouse's current position.  */
      struct frame *new_f=0;

      if (mouse_position_hook)
         (*mouse_position_hook) (&new_f, 0, 0, 0, &x, &y, 0);
      if (new_f != 0)
         XSETFRAME (window, new_f);
      else
         {
         window = selected_window;
         x = make_number(0);
         y = make_number(0);
         }
      }
   else
      {
      CHECK_CONS (position,0);
      tem = Fcar (position);
      if (XTYPE (tem) == Lisp_Cons)
         {
         window = Fcar (Fcdr (position));
         x = Fcar(tem);
         y = Fcar(Fcdr (tem));
         }
      else
         {
         tem = Fcar (Fcdr (position));
         window = Fcar (tem);
         tem = Fcar (Fcdr (Fcdr (tem)));
         x = Fcar (tem);
         y = Fcdr (tem);
         }
      }

   CHECK_NUMBER (x,0);
   CHECK_NUMBER (y,0);

   if (FRAMEP (window))
      {
      f = XFRAME (window);

      p.x = 0;
      p.y = 0;
      }
   else
      {
      CHECK_LIVE_WINDOW (window, 0);
      f = XFRAME (WINDOW_FRAME (XWINDOW (window)));

      p.x = ((int)(f->output_data.ns->face->width) * XWINDOW (window)->left);
      p.y = (f->output_data.ns->line_height * XWINDOW (window)->top);
      }

   p.x+=XINT(x); p.y+=XINT(y);
   [f->output_data.ns->view convertPoint:&p toView:nil];
   [[f->output_data.ns->view window] convertBaseToScreen:&p];
   panel=[[EmacsMenuPanel alloc] initFromMenu:menu];

   tem=[panel runMenuAt:p.x:p.y];
   [panel close];
   [[selected_frame->output_data.ns->view window] makeKeyWindow];
   return tem;
   }

DEFUN ("ns-popup-dialog", Fns_popup_dialog, Sns_popup_dialog, 2, 2, 0,
  "Pop up a dialog box and return user's selection.\n\
POSITION specifies which frame to use.\n\
This is normally a mouse button event or a window or frame.\n\
If POSITION is t, it means to use the frame the mouse is on.\n\
The dialog box appears in the middle of the specified frame.\n\
\n\
CONTENTS specifies the alternatives to display in the dialog box.\n\
It is a list of the form (TITLE ITEM1 ITEM2...).\n\
Each ITEM is a cons cell (STRING . VALUE).\n\
The return value is VALUE from the chosen item.\n\n\
An ITEM may also be just a string--that makes a nonselectable item.\n\
An ITEM may also be nil--that means to put all preceding items\n\
on the left of the dialog box and all following items on the right.\n\
\(By default, approximately half appear on each side.)")
  (position, contents)
     Lisp_Object position, contents;
   {
   id dialog;
   Lisp_Object window,tem;
   struct frame *f;
   NXPoint p;

   check_ns();
   if (EQ (position, Qt))
      {
      window = selected_window;
      }
   else if (CONSP (position))
      {
      Lisp_Object tem;
      tem = Fcar (position);
      if (XTYPE (tem) == Lisp_Cons)
         window = Fcar (Fcdr (position));
      else
         {
         tem = Fcar (Fcdr (position));  /* EVENT_START (position) */
         window = Fcar (tem);	     /* POSN_WINDOW (tem) */
         }
      }
   else if (FRAMEP (position))
      {
      window = position;
      }
   else
      {
      CHECK_LIVE_WINDOW (position,0);
      window = position;
      }

   if (FRAMEP (window))
      f = XFRAME (window);
   else
      {
      CHECK_LIVE_WINDOW (window, 0);
      f = XFRAME (WINDOW_FRAME (XWINDOW (window)));
      }
   p.x = (int)f->output_data.ns->left +
         ((int)(f->output_data.ns->face->width) * f->width)/2;
   p.y = (int)f->output_data.ns->top +
         (f->output_data.ns->line_height * f->height)/2;
   dialog=[[EmacsDialogPanel alloc] initFromContents:contents];

   tem=[dialog runDialogAt:p.x:p.y];
   [dialog close];
   [[selected_frame->output_data.ns->view window] makeKeyWindow];
   return tem;
   }

void ns_free_frame_menubar (struct frame *f)
   {
   id menu=[NXApp mainMenu];

   if (oldmenu==nil)
      oldmenu=[[EmacsMenu alloc] initTitle:"Garbage"];

   [menu mark];
   [menu freeMarked];
   }

void ns_set_frame_menubar (struct frame *f, int first_time, int deep_p)
   {
   int i,len;
   Lisp_Object items,tail,key,string;
   id menu=[NXApp mainMenu];

   if (first_time)
      {
      menu=nil;
      if (oldmenu!=nil) [oldmenu free];
      oldmenu=nil;
      }
   
   if (f!=selected_frame)
      return;
   
   if (menu==nil)
      {
      menu=[[EmacsMenu alloc] initTitle:[NXApp appName]];
      [NXApp setMainMenu:menu];
      }

   if (NILP (items=FRAME_MENU_BAR_ITEMS (f)))
      items = FRAME_MENU_BAR_ITEMS (f) = menu_bar_items (FRAME_MENU_BAR_ITEMS (f));

   if (oldmenu==nil)
      oldmenu=[[EmacsMenu alloc] initTitle:"Garbage"];

   [menu mark];

   for(i=0;i<XVECTOR(items)->size;i+=4)
      {
      key=XVECTOR(items)->contents[i];
      string=XVECTOR(items)->contents[i+1];
      for(tail=XVECTOR(items)->contents[i+2];CONSP(tail);tail=XCONS(tail)->cdr)
         [menu adjustKey:key binding:XCONS(tail)->car string:string];
      }

   [menu freeMarked];
   }

DEFUN ("ns-reset-menu",Fns_reset_menu, Sns_reset_menu, 0, 0, 0,
  "Cause the NS menu to be re-calculated.")
   ()
   {
   ns_set_frame_menubar(selected_frame,1, 0);
   return Qnil;
   }

Lisp_Object ns_map_event_to_object (struct input_event *event, struct frame *f)
   {
   if (CONSP(ns_menu_path))
      {
      Lisp_Object ret=XCONS(ns_menu_path)->car;
      ns_menu_path=XCONS(ns_menu_path)->cdr;
      return ret;
      }
   return Qnil;
   }

syms_of_nsmenu ()
   {
   ns_menu_path=Qnil;
   staticpro(&ns_menu_path);
   defsubr (&Sns_popup_menu);
   defsubr (&Sns_popup_dialog);
   defsubr (&Sns_reset_menu);
   }

@implementation EmacsMenuCell
- initTextCell:(const char *)aString
   {
   [super initTextCell:aString];
   value=Qnil;
   staticpro(&value);
   supercell=nil;
   return self;
   }

- free
   {
   if ([self hasSubmenu])
      {
      [[self target] close];
      [[self target] free];
      }
   staticunpro(&value);
   return [super free];
   }

- (Lisp_Object) value
   {
   return value;
   }

- setValue:(Lisp_Object)nvalue
   {
   value=nvalue;
   return self;
   }

- supercell
   {
   return supercell;
   }

- setSupercell:(id) nsupercell
   {
   supercell=nsupercell;
   return self;
   }
@end

@implementation EmacsMenu
- initTitle:(const char *)aTitle
   {
   id oldproto;
   [super initTitle:aTitle];
   minmarked=0;
   wasVisible=NO;
   if ((oldproto=[matrix setPrototype:nil])!=nil)
      [oldproto free];
   [matrix setCellClass:[EmacsMenuCell class]];
   [matrix setAutodisplay:NO];
   return self;
   }

- (BOOL)isAutodisplay
   {
   return NO;
   }

- (BOOL)commandKey:(NXEvent *)theEvent
   {
   if (selected_frame && selected_frame->output_data.ns->view)
      return [selected_frame->output_data.ns->view keyDown:theEvent]!=nil;
   return NO;
   }

- addItem:(Lisp_Object)val string:(Lisp_Object)aString bind:(Lisp_Object)bind
   {
   unsigned int key=0;
   id cell;

   if (!NILP(bind))
      {
      Lisp_Object tail,vec;
      for(tail=Fwhere_is_internal(bind,Qnil,Qnil,Qt);
          XTYPE(tail)==Lisp_Cons;tail=XCONS(tail)->cdr)
         {
         vec=XCONS(tail)->car;
         if (!VECTORP(vec) ||
             XVECTOR(vec)->size!=1 ||
             XTYPE(XVECTOR(vec)->contents[0])!=Lisp_Int)
            continue;
         key=XFASTINT(XVECTOR(vec)->contents[0]);
         if ((key&(alt_modifier|hyper_modifier|super_modifier))==super_modifier)
            {
            key=NILP(ns_iso_latin) ? (key&0xff) : iso2nsmap[key&0xff];
            break;
            }
         else
            {
            key=0;
            }
         }
      }

   cell=[[EmacsMenuCell alloc] initTextCell:XSTRING(aString)->data];
   [cell setAction:@selector(menuDown:)];
   [cell setKeyEquivalent:key];
   [cell setSupercell:supercell];
   [cell setValue:val];
   [self addCell:cell];
   return cell;
   }

- supercell
   {
   return supercell;
   }

- setSupercell:(id) nsupercell
   {
   supercell=nsupercell;
   return self;
   }

- mark
   {
   int i;
   id cell;

   for (i=0;(cell=[matrix cellAt:i:0])!=nil;i++)
      if ([cell hasSubmenu]) [[cell target] mark];
   minmarked=0;
   return self;
   }

- freeMarked
   {
   int i;
   id cell;

   while ((cell=[matrix cellAt:minmarked:0])!=nil)
      {
      if ([cell hasSubmenu]) [[cell target] swapout];
      [self transferCell:minmarked to:oldmenu];
      }

   for (i=0;(cell=[matrix cellAt:i:0])!=nil;i++)
      if ([cell hasSubmenu]) [[cell target] freeMarked];

   if ([matrix needsDisplay])
      {
      [self sizeToFit];
      [matrix displayIfNeeded];
      [matrix setNeedsDisplay:NO];
      }

   return self;
   }

- findCell:(Lisp_Object)value
   {
   int i;
   id cell;

   for (i=0;(cell=[matrix cellAt:i:0])!=nil;i++)
      if ([cell value]==value) return cell;

   return nil;
   }

- (int)findCellNumber:(Lisp_Object)value
   {
   int i;
   id cell;

   for (i=0;(cell=[matrix cellAt:i:0])!=nil;i++)
      if ([cell value]==value) return i;

   return -1;
   }

- transferCell:(int)row to:newmenu
   {
   id cell=[matrix cellAt:row:0];
   if (cell!=nil)
      {
      [self removeCell:cell];
      [newmenu addCell:cell];
      }
   return cell;
   }

- addCell:cell
   {
   [matrix insertRowAt:minmarked];
   [[matrix putCell:cell at:minmarked:0] free];
   [matrix setNeedsDisplay:YES];
   }

- removeCell:cell
   {
   int i;
   id ncell;

   for (i=0;(ncell=[matrix cellAt:i:0])!=nil;i++)
      if (cell==ncell) break;
   if (ncell==nil) return nil;

   [matrix removeRowAt:i andFree:NO];
   if (i<minmarked) minmarked--;
   [matrix setNeedsDisplay:YES];
   }

- adjustMap:(Lisp_Object)keymap;
   {
   Lisp_Object item;

   for (keymap=XCONS(keymap)->cdr;
        XTYPE (keymap) == Lisp_Cons;
        keymap = XCONS (keymap)->cdr)
      {
      item = XCONS (keymap)->car;
      if (XTYPE(item) == Lisp_Cons)
         [self adjustKey:XCONS(item)->car binding: XCONS(item)->cdr];
      else if (VECTORP(item))
         {
         int len = XVECTOR (item)->size,c;
         for (c=0; c<len; c++)
            [self adjustKey:c binding:XVECTOR(item)->contents[c]];
         }
      }
   return self;
   }

- adjustKey:(Lisp_Object)key binding:(Lisp_Object)bind
   {
   if (XTYPE(bind) == Lisp_Cons)
      [self adjustKey:key binding:XCONS(bind)->cdr string:XCONS(bind)->car];
   else if (EQ (bind, Qundefined))
      [self removeCell:[self findCell:key]];
   return self;
   }

- adjustKey:(Lisp_Object)key binding:(Lisp_Object)bind string:(Lisp_Object)string
   {
   int i;
   id cell=nil;

   if (XTYPE(string)!=Lisp_String)
      return nil;
   
   if (((cell=[matrix cellAt:minmarked:0])!=nil) && ([cell value]==key))
      {
      }
   else if ((i=[self findCellNumber:key])>=0)
      {
      cell=[self transferCell:i to:self];
      }
   else if ((i=[oldmenu findCellNumber:key])>=0)
      {
      cell=[oldmenu transferCell:i to:self];
      if ([cell hasSubmenu]) [[cell target] swapin];
      }
   else if (XTYPE(bind)==Lisp_Cons && XCONS(bind)->car==Qkeymap)
      {
      cell=[self addItem:key string:string bind:Qnil];
      }
   else
      {
      cell=[self addItem:key string:string bind:bind];
      }

   if (XTYPE(bind)==Lisp_Cons && XCONS(bind)->car==Qkeymap)
      {
      id nmenu;
      
      if (![cell hasSubmenu])
         {
         nmenu=[[EmacsMenu alloc] initTitle:XSTRING(string)->data];
         [self setSubmenu:nmenu forItem:cell];
         [nmenu setSupercell:cell];         
         [matrix setNeedsDisplay:YES];
         }
      else
         {
         nmenu=[cell target];
         }
      
      [nmenu adjustMap:bind];
      }
   else
      {
      if ([cell hasSubmenu])
         {
         [self removeCell:cell];
         cell=[self addItem:key string:string bind:bind];
         }
      }
   
   if (strcmp([cell title],XSTRING(string)->data))
      {
      [cell setTitle:XSTRING(string)->data];
      [matrix setNeedsDisplay:YES];
      }
   
#if 0
      {
      BOOL oenable=[cell isEnabled],nenable=YES;
      if (XTYPE(bind)==Lisp_Symbol)
         {
         Lisp_Object tem = Fget (bind, Qmenu_enable);
         if (!NILP (tem) && NILP(Feval(tem)))
            nenable=NO;
         }

      if (oenable!=nenable)
         {
         [matrix setNeedsDisplay:YES];
         [cell setEnabled:nenable];
         }
      }
#endif
   
   minmarked++;
   }

- swapout
   {
   int i;
   id cell;

   wasVisible=[self isVisible];
   [self orderOut:self];
   for (i=0;(cell=[matrix cellAt:i:0])!=nil;i++)
      if ([cell hasSubmenu]) [[cell target] swapout];
   }

- swapin
   {
   int i;
   id cell;

   if (wasVisible)
      {
      [self orderFront:self];
      }
   for (i=0;(cell=[matrix cellAt:i:0])!=nil;i++)
      if ([cell hasSubmenu]) [[cell target] swapin];
   }
@end

@implementation EmacsMenuCard
- init
   {
   [super init];
   popup = nil;
   [self setTitle:""];
   [self setFont:[Font userFixedPitchFontOfSize:0 matrix:NX_FLIPPEDMATRIX]];
   return self;
   }

- free
   {
   [popup free];
   return [super free];
   }

- addTitle: (char *)name
   {
   [self setTitle:name];
   [self setAlignment:NX_CENTERED];
   return self;
   }

- addItem: (char *)name value:(Lisp_Object)val enabled:(BOOL)enabled
   {
   NXSize s;
   NXRect r;
   id item;

   if (popup == nil)
      {
      popup = [[PopUpList alloc] init];
      [popup changeButtonTitle:NO];
      [popup setTarget:self];
      [popup setAction:@selector(clicked:)];
      [popup setFont:
         [Font userFixedPitchFontOfSize:0 matrix:NX_FLIPPEDMATRIX]];
      NXAttachPopUpList(self, popup);
      [(item=[popup addItem:[self title]]) setTag:Qundefined];
      [item setAlignment:NX_CENTERED];
      [self setAlignment:NX_CENTERED];
      [item calcCellSize:&s];
      [self sizeTo:s.width:s.height];
      }

   item = [popup addItem:name];
   [item setTag:(int)val];
   [item setIconPosition:NX_TITLEONLY];
   [item setAlignment:NX_LEFTALIGNED];
   if (!enabled)
      {
      [item setEnabled:NO];
      }
   [item calcCellSize:&s];
   [self getFrame:&r];
   if (s.width > r.size.width)
      [self sizeTo:s.width:r.size.height];
   return self;
   }

- clicked:sender
   {
   static id sellist=nil;
   Lisp_Object seltag;

   if (sellist!=nil) [sellist empty];
   sellist=[sender getSelectedCells:sellist];
   if ([sellist count]<1) return self;

   seltag = (Lisp_Object)[[sellist objectAt:0] tag];
   if (! EQ(seltag,Qundefined))
      [NXApp stopModal:seltag];
   return self;
   }
@end

@implementation EmacsMenuPanel
- initContent:(const NXRect *)contentRect style:(int)aStyle backing
             :(int)backingType buttonMask:(int)mask defer:(BOOL)flag
   {
   aStyle=NX_TITLEDSTYLE;
   mask=NX_CLOSEBUTTONMASK;
   flag=YES;
   list=nil;
   maxwidth=0;
   [super initContent:contentRect style:aStyle backing:backingType
          buttonMask:mask defer:flag];
   [self setOneShot:YES];
   [self setFreeWhenClosed:YES];
   [self setHideOnDeactivate:YES];
   return self;
   }

- windowWillClose: sender
   {
   [NXApp stopModal:Qnil];
   return nil;
   }

static void process_keymap(id window,Lisp_Object keymap,Lisp_Object prefix,
                           char *name)
   {
   Lisp_Object tail,pending_maps,tem;
   int card;

   pending_maps=Qnil;

   card = [window cards];
   if (name) [window addTitle:name to:card];

   for (tail=keymap; XTYPE(tail)==Lisp_Cons; tail=XCONS(tail)->cdr)
      {
      Lisp_Object item=XCONS(tail)->car,item1,item2,def,enabled;
      if (XTYPE(item)==Lisp_Cons)
         {
         item1=XCONS(item)->cdr;
         if (XTYPE(item1)==Lisp_Cons)
            {
            item2=XCONS(item1)->car;
            if (XTYPE(item2)==Lisp_String)
               {
               def=XCONS(item1)->cdr;
               enabled=Qt;
               if (XTYPE(def)==Lisp_Symbol)
                  {
                  tem=Fget(def,Qmenu_enable);
                  if (!NILP(tem)) enabled=Feval(tem);
                  }
               if (XSTRING (item2)->data[0] == '@' && !NILP(Fkeymapp(def)))
                  pending_maps = Fcons(Fcons(def,Fcons(item2, XCONS(item)->car)),
                                       pending_maps);
               else
                  [window addItem:XSTRING(item2)->data
                          value:Freverse(Fcons(XCONS(item)->car,prefix))
                          enabled:!NILP(enabled) at:card];
               }
            }
         }
      else if (VECTORP(item))
         {
         int len=XVECTOR(item)->size;
         int c;
         for(c=0; c<len; c++)
            {
            Lisp_Object character;
            XSETFASTINT(character, c);
            item1=XVECTOR(item)->contents[c];
            if (XTYPE(item1)==Lisp_Cons)
               {
               item2=XCONS(item1)->car;
               if (XTYPE(item2)==Lisp_String)
                  {
                  def=XCONS(item1)->cdr;
                  enabled=Qt;
                  if (XTYPE(def)==Lisp_Symbol)
                     {
                     tem=Fget(def,Qmenu_enable);
                     if (!NILP(tem)) enabled=Feval(tem);
                     }
                  if (XSTRING (item2)->data[0] == '@' && !NILP(Fkeymapp(def)))
                     pending_maps = Fcons(Fcons(def,Fcons(item2, XCONS(item)->car)),
                                          pending_maps);
                  else
                     [window addItem:XSTRING(item2)->data
                             value:Freverse(Fcons(XCONS(item)->car,prefix))
                             enabled:!NILP(enabled) at:card];
                  }
               }
            }
         }
      }

   for(tail=pending_maps;XTYPE(tail)==Lisp_Cons;tail=XCONS(tail)->cdr)
      {
      tem=XCONS(tail)->car;
      process_keymap(window,XCONS(tem)->car,
                     Fcons(XCONS(XCONS(tem)->cdr)->cdr,prefix),
                     (char *)XSTRING(XCONS(XCONS(tem)->cdr)->car)->data +1);
      }
   }

void process_list(id window,Lisp_Object list)
   {
   Lisp_Object tail,pane,tem;
   int card;

   card = [window cards];
   for(;XTYPE(list)==Lisp_Cons;list=XCONS(list)->cdr)
      {
      pane=XCONS(list)->car;
      if (XTYPE(pane)!=Lisp_Cons || XTYPE(XCONS(pane)->car)!=Lisp_String) continue;
      for(tail=pane;XTYPE(tail)==Lisp_Cons;tail=XCONS(tail)->cdr)
         {
         tem=XCONS(tail)->car;
         if (XTYPE(tem)==Lisp_String)
            {
	      if (tail!=pane)		/* Not very first item? */
		  [window addItem:XSTRING(tem)->data value:Qnil
	                  enabled:NO at:card];
	      else
		  [window addTitle:XSTRING(tem)->data to:card];
            }
         else if (XTYPE(tem)==Lisp_Cons || XTYPE(XCONS(tem)->car)==Lisp_String)
            {
            [window addItem:XSTRING(XCONS(tem)->car)->data value:XCONS(tem)->cdr
                    enabled:YES at:card];
            }
         }
      }
   }


- addItem:(char *)str value:(Lisp_Object)val enabled:(BOOL)enabled at:(int)pane
   {
   int row;
   id card;
   NXRect area;

   row = [list count];
   for(;row<=pane;row++)
      {
      [list addObject:card=[[EmacsMenuCard alloc] init]];
      [[self contentView] addSubview:card];
      [[card cell] calcCellSize:&area.size];
      if (area.size.width > maxwidth)  maxwidth=area.size.width;
      area.origin.x = MenuStagger*pane;
      area.origin.y = area.size.height*pane;
      [card setFrame: &area];
      }
   card = [list objectAt:pane];
   [card addItem:str value:val enabled:enabled];
   [card getFrame:&area];
   if (area.size.width > maxwidth)  maxwidth=area.size.width;
   }

- addTitle:(char *)str to:(int)pane
   {
   int row;
   id card;
   NXRect area;

   row = [list count];
   for(;row<=pane;row++)
      {
      [list addObject:card=[[EmacsMenuCard alloc] init]];
      [[self contentView] addSubview:card];
      [[card cell] calcCellSize:&area.size];
      if (area.size.width > maxwidth)  maxwidth=area.size.width;
      area.origin.x = MenuStagger*pane;
      area.origin.y = area.size.height*pane;
      [card setFrame: &area];
      }

   card = [list objectAt:pane];
   [card addTitle:str];
   [[card cell] calcCellSize:&area.size];
   if (area.size.width > maxwidth)  maxwidth=area.size.width;
   }

- initFromMenu:(Lisp_Object)menu
   {
   Lisp_Object title;
   NXSize spacing = {0,0};
   [super init];

      {
      list=[[List alloc] init];
      [[self contentView] setFlipped:YES];
      }

   if (!NILP (Fkeymapp(menu)))
      {
      int i,j;
      Lisp_Object keymap = get_keymap(menu);
      process_keymap(self,keymap,Qnil,0);
      title = map_prompt(keymap);
      }
   else if (XTYPE(menu)==Lisp_Cons && !NILP(Fkeymapp (Fcar(menu))))
      {
      Lisp_Object tem,keymap;
      for(title=Qnil;XTYPE(menu) == Lisp_Cons;menu = Fcdr(menu))
         {
         keymap=get_keymap(Fcar(menu));
         process_keymap(self,keymap,Qnil,0);
         tem = map_prompt (keymap);
         if (NILP(title) && !NILP(tem)) title=tem;
         }
      }
   else
      {
      Lisp_Object tem;
      title=Fcar(menu);
      process_list(self,Fcdr(menu));
      }

   if (XTYPE(title)==Lisp_String) [self setTitle:XSTRING(title)->data];

      {
      int i;
      NXRect r,s,t;

      [[list objectAt:0] getFrame:&t];
      for (i=0; i<[list count]; i++)
	  [[list objectAt:i] sizeTo:maxwidth:t.size.height];

      [self getFrame:&r];
      [[self contentView] getFrame:&s];
      r.size.height += t.size.height*i-s.size.height;
      r.size.width  += maxwidth+(i-1)*MenuStagger-s.size.width;
      [self placeWindow:&r];
      }

   return self;
   }

- free
   {
   id card;

   [[list freeObjects] free];
   return [super free];
   }

- (int)cards
   {
   return [list count];
   }

- (Lisp_Object)runMenuAt:(NXCoord)x:(NXCoord)y
   {
   NXEvent e;
   NXModalSession session;
   NXRect r;
   int ret;

   if ([list count] == 0)
     return Qnil;
   
   [[list objectAt:0] getFrame:&r];
   [[self contentView] convertPoint:&r.origin toView:nil];

   [[self moveTo: x-r.size.width/2.0: y-r.origin.y+r.size.height/2.0]
    orderFront:NXApp];

   [NXApp beginModalSession:&session for:self];
   [self setHideOnDeactivate:YES];
   while ((ret=[NXApp runModalSession:&session])==NX_RUNCONTINUES)
      NXGetOrPeekEvent(DPS_ALLCONTEXTS,&e,NX_ALLEVENTS,NX_FOREVER,0,1);

   [NXApp endModalSession:&session];

   return (Lisp_Object)ret;
   }
@end

@implementation EmacsDialogPanel

#define SPACER		8.0
#define ICONSIZE	50.0
#define TEXTHEIGHT	20.0

- initContent:(const NXRect *)contentRect style:(int)aStyle backing
             :(int)backingType buttonMask:(int)mask defer:(BOOL)flag
   {
   NXSize spacing = {SPACER,SPACER};
   NXRect area;
   char this_cmd_name[80];
   id cell,tem;

   aStyle=NX_TITLEDSTYLE;
   mask= 0; // NX_CLOSEBUTTONMASK;
   flag=YES;
   rows=0;
   cols=1;
   [super initContent:contentRect style:aStyle backing:backingType
          buttonMask:mask defer:flag];
   [[self contentView] setFlipped:YES];
   [[self contentView] setAutoresizeSubviews:YES];

   area.origin.x   = SPACER;
   area.origin.y   = SPACER;
   area.size.width = ICONSIZE;
   area.size.height= ICONSIZE;
   tem = [[Button alloc] initFrame:&area icon:"app"
			 tag:0 target:nil action:NULL key:0 enabled:NO];
   [[self contentView] addSubview:tem];
   [tem setBordered:NO];

   if (XTYPE(this_command)==Lisp_Symbol)
      {
      int i;

      strcpy(this_cmd_name,XSYMBOL(this_command)->name->data);
      if (this_cmd_name[0] >= 'a' && this_cmd_name[0] <= 'z')
	 this_cmd_name[0] = this_cmd_name[0]-'a'+'A';
      for (i=0; this_cmd_name[i] != '\0'; i++)
	 {
	 if (this_cmd_name[i] == '-')
	    {
	    this_cmd_name[i]=' ';
	    if (this_cmd_name[i+1] >= 'a' && this_cmd_name[i+1] <= 'z')
	       this_cmd_name[i+1] = this_cmd_name[i+1]-'a'+'A';
	    }
	 }
      }
   else
      strcpy(this_cmd_name,"Emacs");
   area.origin.x   = ICONSIZE+2*SPACER;
   area.origin.y   = ICONSIZE/2-10+SPACER;
   area.size.width = 400;
   area.size.height= TEXTHEIGHT;
   command = [[TextField alloc] initFrame:&area];
   [[self contentView] addSubview:command];
   [command setStringValue:this_cmd_name];
   [command setBackgroundTransparent:YES];
   [command setBezeled:NO];
   [command setSelectable:NO];
   [command setFont: [Font systemFontOfSize:18.0 matrix:NX_FLIPPEDMATRIX]];

   area.origin.x   = 0;
   area.origin.y   = ICONSIZE+2*SPACER;
   area.size.width = 400;
   area.size.height= 2;
   tem = [[Box alloc] initFrame:&area];
   [[self contentView] addSubview:tem];
   [tem setTitlePosition:NX_NOTITLE];
   [tem setAutosizing:NX_WIDTHSIZABLE];

   area.origin.x   = 2*SPACER;
   area.origin.y  += 2*SPACER;
   area.size.width = 400;
   area.size.height= TEXTHEIGHT;
   title = [[TextField alloc] initFrame:&area];
   [[self contentView] addSubview:title];
   [title setBackgroundTransparent:YES];
   [title setBezeled:NO];
   [title setSelectable:NO];
   [title setFont: [Font systemFontOfSize:14.0 matrix:NX_FLIPPEDMATRIX]];

   cell = [[ButtonCell alloc] initTextCell:""];
   // Make it look like nothing's here
   [cell setBordered:NO];
   [cell setEnabled:NO];
   [cell setParameter:NX_BUTTONINSET to:8];

   matrix = [[Matrix alloc] initFrame:contentRect mode:NX_HIGHLIGHTMODE
			    prototype:cell numRows:0 numCols:1];
   [[self contentView] addSubview:matrix];
   [matrix moveTo:SPACER:area.origin.y+TEXTHEIGHT+2*SPACER];
   [matrix setIntercell:&spacing];

   [self setOneShot:YES];
   [self setFreeWhenClosed:YES];
   [self setHideOnDeactivate:YES];
   return self;
   }

- windowWillClose: sender
   {
   [NXApp stopModal:Qnil];
   return nil;
   }

void process_dialog(id window,Lisp_Object list)
   {
   Lisp_Object item;
   int row;

   row=0;
   for(;XTYPE(list)==Lisp_Cons;list=XCONS(list)->cdr)
      {
      item=XCONS(list)->car;
      if (XTYPE(item)==Lisp_String)
	 {
	 [window addString:XSTRING(item)->data row:row];
	 row++;
         }
      else if (XTYPE(item)==Lisp_Cons)
         {
         [window addButton:XSTRING(XCONS(item)->car)->data
		 value:XCONS(item)->cdr row:row];
	 row++;
         }
      else if (NILP(item))
	 {
	 [window addSplit];
	 row=0;
	 }
      }
   }


- addButton:(char *)str value:(Lisp_Object)val row:(int)row
   {
   id cell;
       
   if (row >= rows)
      {
      [matrix addRow];
      rows++;
      }
   cell = [matrix cellAt:row:cols-1];
   [cell setTarget:self];
   [cell setAction:@selector(clicked:)];
   [cell setTitle:str];
   [cell setTag:(int)val];
   [cell setBordered:YES];
   [cell setEnabled:YES];
   }

- addString:(char *)str row:(int)row
   {
   id cell;
       
   if (row >= rows)
      {
      [matrix addRow];
      rows++;
      }
   cell = [matrix cellAt:row:cols-1];
   // No setAction here.  Disabled buttons can't trigger actions
   [cell setTitle:str];
   [cell setBordered:YES];
   [cell setEnabled:NO];
   }

- addSplit
   {
   [matrix addCol];
   cols++;
   }

- clicked:sender
   {
   static id sellist=nil;
   Lisp_Object seltag;

   if (sellist!=nil) [sellist empty];
   sellist=[sender getSelectedCells:sellist];
   if ([sellist count]<1) return self;

   seltag = (Lisp_Object)[[sellist objectAt:0] tag];
   if (! EQ(seltag,Qundefined))
      [NXApp stopModal:seltag];
   return self;
   }

- initFromContents:(Lisp_Object)contents
   {
   Lisp_Object head;
   [super init];

   if (XTYPE(contents)==Lisp_Cons)
      {
      Lisp_Object tem;
      head=Fcar(contents);
      process_dialog(self,Fcdr(contents));
      }

   if (XTYPE(head)==Lisp_String)
      [title setStringValue:XSTRING(head)->data];

      {
      int i;
      NXRect r,s,t;

      if (cols == 1 && rows > 1)	// Never told where to split
	 {
	 [matrix addCol];
	 for (i=0; i<rows/2; i++)
	    {
	    [matrix putCell:[matrix cellAt:(rows+1)/2:0] at:i:1];
	    [matrix removeRowAt:(rows+1)/2 andFree:NO];
	    }
	 }
      [matrix sizeToFit];
      [title sizeToFit];
      [command sizeToFit];

      [matrix getFrame:&t];
      [title getFrame:&r];
      if (r.size.width+r.origin.x > t.size.width+t.origin.x)
	 {
	 t.origin.x   = r.origin.x;
	 t.size.width = r.size.width;
	 }
      [command getFrame:&r];
      if (r.size.width+r.origin.x > t.size.width+t.origin.x)
	 {
	 t.origin.x   = r.origin.x;
	 t.size.width = r.size.width;
	 }

      [self getFrame:&r];
      [[self contentView] getFrame:&s];
      r.size.height += t.origin.y+t.size.height+SPACER-s.size.height;
      r.size.width  += t.origin.x+t.size.width +SPACER-s.size.width;
      [self placeWindow:&r];
      }

   return self;
   }

- free
   {
   [matrix free];
   return [super free];
   }

- (Lisp_Object)runDialogAt:(NXCoord)x:(NXCoord)y
   {
   NXEvent e;
   NXModalSession session;
   NXRect r;
   int ret;

//    [[list objectAt:0] getFrame:&r];
//    [[self contentView] convertPoint:&r.origin toView:nil];

//    [[self moveTo: x-r.size.width/2.0: y+r.size.height/2.0]
//     orderFront:NXApp];

   [self center];
   [self orderFront:NXApp];

   [NXApp beginModalSession:&session for:self];
   while ((ret=[NXApp runModalSession:&session])==NX_RUNCONTINUES)
      NXGetOrPeekEvent(DPS_ALLCONTEXTS,&e,NX_ALLEVENTS,NX_FOREVER,0,1);
   [NXApp endModalSession:&session];

   return (Lisp_Object)ret;
   }
@end
