/* Pulldown menu code.
   Copyright (C) 1994 Miguel de Icaza.
   
   This program 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 of the License, or
   (at your option) any later version.
   
   This program 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 this program; if not, write to the Free Software
   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.  */

#include <config.h>
#include "tty.h"
#include <string.h>
#include <stdarg.h>
#include <sys/types.h>
#include <ctype.h>
#include <malloc.h>
#include "mad.h"
#include "util.h"
#include "menu.h"
#include "dialog.h"
#include "global.h"
#include "color.h"
#include "main.h"
#include "mouse.h"
#include "win.h"
#include "key.h"	/* For mi_getch() */

/* "$Id: menu.c,v 1.18 1995/02/21 19:06:48 miguel Exp $" */

extern int is_right;
int menubar_visible = 1;	/* This is the new default */

Menu create_menu (char *name, menu_entry *entries, int count)
{
    Menu menu;

    menu = (Menu) xmalloc (sizeof (*menu), "create_menu");
    menu->count = count;
    menu->max_entry_len = 0;
    menu->entries = entries;
    menu->name = name;
    return menu;
}

static void menubar_drop_compute (WMenu *menubar)
{
    const Menu menu = menubar->menu [menubar->selected];
    int   max_entry_len = 0;
    int i;
    
    for (i = 0; i < menu->count; i++)
	max_entry_len = max (max_entry_len, strlen (menu->entries [i].text));
    menubar->max_entry_len = max_entry_len = max (max_entry_len, 20);
}

static void menubar_paint_idx (WMenu *menubar, int idx, int color)
{
    const Menu menu = menubar->menu [menubar->selected];
    const int y = 2 + idx;
    const int x = (menubar->selected * 12) + 1;

    widget_move (&menubar->widget, y, x);
    attrset (color);
    hline (' ', menubar->max_entry_len+2);
    if (!*menu->entries [idx].text){
    	attrset (SELECTED_COLOR);
        widget_move (&menubar->widget, y, x + 1);
    	hline (slow_terminal ? ' ' : ACS_HLINE, menubar->max_entry_len);
    } else {
	char *text = menu->entries [idx].text;
	int  hotkey = menu->entries [idx].hot_key;
	char *save;
	
        printw ("%c%-s", menu->entries [idx].first_letter, text);

	if (!slow_terminal){
	    save = text;

	    /* First, try to highlight the hotkey */
	    while (*text && *text != hotkey)
		text++;

	    if (!*text)
		text = save;
	    
	    if (*text){
		widget_move (&menubar->widget, y, x+text-save+1);
		attrset (color == MENU_SELECTED_COLOR ?
			 MENU_HOTSEL_COLOR : MENU_HOT_COLOR);
		addch (*text);
	    }
	}
    }
    widget_move (&menubar->widget, y, x + 1);
}

static INLINE void menubar_draw_drop (WMenu *menubar)
{
    const int count = (menubar->menu [menubar->selected])->count;
    int   i;
    int   sel = menubar->subsel;
    int   column = (menubar->selected * 12);

    attrset (SELECTED_COLOR);
    draw_box (menubar->widget.parent,
	      menubar->widget.y+1, menubar->widget.x + column,
	      count+2, menubar->max_entry_len + 4);

    column++;
    for (i = 0; i < count; i++){
	if (i == sel)
	    continue;
	menubar_paint_idx (menubar, i, MENU_ENTRY_COLOR);
    }
    menubar_paint_idx (menubar, sel, MENU_SELECTED_COLOR);
}

static void menubar_draw (WMenu *menubar)
{
    const int items = menubar->items;
    int   i;
    
    /* First draw the complete menubar */
    attrset (SELECTED_COLOR);
    widget_move (&menubar->widget, 0, 0);

    /* ncurses bug: it should work with hline but it does not */
    for (i =  menubar->widget.cols; i; i--)
	addch (' ');

    attrset (SELECTED_COLOR);
    /* Now each one of the entries */
    for (i = 0; i < items; i++){
	if (menubar->active)
	    attrset(i == menubar->selected?MENU_SELECTED_COLOR:SELECTED_COLOR);
	widget_move (&menubar->widget, 0, menubar->widget.x + 2 + (i * 12));
	printw ("%s", menubar->menu [i]->name);
    }

    if (menubar->dropped)
	menubar_draw_drop (menubar);
    else 
	widget_move (&menubar->widget, 0, menubar->widget.x + 3 +
		     menubar->selected * 12);
}

static INLINE void menubar_remove (WMenu *menubar)
{
    menubar->subsel = 0;
    if (menubar->dropped){
	menubar->dropped = 0;
	do_refresh ();
	menubar->dropped = 1;
    }
}

static void menubar_left (WMenu *menu)
{
    menubar_remove (menu);
    menu->selected = (menu->selected - 1) % menu->items;
    if (menu->selected < 0)
	menu->selected = menu->items -1;
    menubar_drop_compute (menu);
    menubar_draw (menu);
}

static void menubar_right (WMenu *menu)
{
    menubar_remove (menu);
    menu->selected = (menu->selected + 1) % menu->items;
    menubar_drop_compute (menu);
    menubar_draw (menu);
}

static void menubar_finish (WMenu *menubar)
{
    menubar->dropped = 0;
    menubar->active = 0;
    menubar->widget.lines = 1;
    widget_want_hotkey (menubar->widget, 0);
    dlg_select_nth_widget (menubar->widget.parent,
			   menubar->previous_selection);
    do_refresh ();
}

static void menubar_drop (WMenu *menubar, int selected)
{
    menubar->dropped = 1;
    menubar->selected = selected;
    menubar->subsel = 0;
    menubar_drop_compute (menubar);
    menubar_draw (menubar);
}

static void menubar_execute (WMenu *menubar, int entry)
{
    const Menu menu = menubar->menu [menubar->selected];
    
    is_right = menubar->selected != 0;
    (*menu->entries [entry].call_back)(0);
    menubar_finish (menubar);
}

static void menubar_move (WMenu *menubar, int step)
{
    const Menu menu = menubar->menu [menubar->selected];

    menubar_paint_idx (menubar, menubar->subsel, MENU_ENTRY_COLOR);
    do {
	menubar->subsel += step;
	if (menubar->subsel < 0)
	    menubar->subsel = menu->count - 1;
	
	menubar->subsel %= menu->count;
    } while  (!menu->entries [menubar->subsel].call_back);
    menubar_paint_idx (menubar, menubar->subsel, MENU_SELECTED_COLOR);
}

static int menubar_handle_key (WMenu *menubar, int key)
{
    int   i;

    /* Lowercase */
    if (key < 256 && isascii (key)) /* Linux libc.so.5.x.x bug fix */
	key = tolower (key);
    
    if (is_abort_char (key)){
	menubar_finish (menubar);
	return 1;
    }

    if (key == KEY_LEFT || key == XCTRL('b')){
	menubar_left (menubar);
	return 1;
    } else if (key == KEY_RIGHT || key == XCTRL ('f')){
	menubar_right (menubar);
	return 1;
    }

    if (!menubar->dropped){
	const int items = menubar->items;
	for (i = 0; i < items; i++){
	    const Menu menu = menubar->menu [i];

	    /* Hack, we should check for the upper case letter */
	    if (tolower (menu->name [1]) == key){
		menubar_drop (menubar, i);
		return 1; 
	    }
	}
	if (key == KEY_ENTER || key == XCTRL ('n') || key == KEY_DOWN
	    || key == '\n'){
	    menubar_drop (menubar, menubar->selected);
	    return 1;
	}
	return 1;
    } else {
	const int selected = menubar->selected;
	const Menu menu = menubar->menu [selected];
	const int items = menu->count;
	char l;
	int m;
	
	for (i = 0; i < items; i++){
	    if (!menu->entries [i].call_back)
		continue;
	    
	    m = menu->entries [i].hot_key;
	    if (m < 256 && isascii (m))
	        m = tolower (m);
	    if (key != m)
		continue;
	    
	    menubar_execute (menubar, i);
	    return 1;
	}

	for (i = 0; i < items; i++){
	    l = tolower (menu->entries [i].text [0]);
	    if (l != key)
		continue;
	    
	    menubar_execute (menubar, i);
	    return 1;
	}
	
	if (key == KEY_ENTER || key == '\n'){
	    menubar_execute (menubar, menubar->subsel);
	    return 1;
	}
	
	
	if (key == KEY_DOWN || key == XCTRL ('n'))
	    menubar_move (menubar, 1);
	
	if (key == KEY_UP || key == XCTRL ('p'))
	    menubar_move (menubar, -1);
    }
    return 0;
}

static int menubar_callback (Dlg_head *h, WMenu *menubar, int msg, int par)
{
    switch (msg){
	/* We do not want the focus unless we have been activated */
    case WIDGET_FOCUS:
	if (menubar->active){
	    widget_want_cursor (menubar->widget, 1);

	    /* Trick to get all the mouse events */
	    menubar->widget.lines = LINES;

	    /* Trick to get all of the hotkeys */
	    widget_want_hotkey (menubar->widget, 1);
	    menubar->subsel = 0;
	    menubar_drop_compute (menubar);
	    menubar_draw (menubar);
	    return 1;
	} else
	    return 0;

	/* We don't want the buttonbar to activate while using the menubar */
    case WIDGET_HOTKEY:
    case WIDGET_KEY:
	if (menubar->active){
	    menubar_handle_key (menubar, par);
	    return 1;
	} else
	    return 0;

    case WIDGET_CURSOR:
	/* Put the cursor in a suitable place */
	return 0;
	
    case WIDGET_UNFOCUS:
	if (menubar->active)
	    return 0;
	else {
	    widget_want_cursor (menubar->widget, 0);
	    return 1;
	}

    case WIDGET_DRAW:
	if (menubar_visible)
	    menubar_draw (menubar);
    }
    return default_proc (h, msg, par);
}

static int menubar_event    (Gpm_Event *event, WMenu *menubar)
{
    int was_active;
    int new_selection;
    int left_x, right_x, bottom_y;

    if (!(event->type & (GPM_UP|GPM_DOWN|GPM_DRAG)))
	return MOU_NORMAL;
    
    if (!menubar->dropped){
	menubar->previous_selection = dlg_item_number(menubar->widget.parent);
	menubar->active = 1;
	menubar->dropped = 1;
	was_active = 0;
    } else
	was_active = 1;

    /* Mouse operations on the menubar */
    if (event->y == 1 || !was_active){
	if (event->type & GPM_UP)
	    return MOU_NORMAL;
    
	new_selection = 0;
	if (event->x < menubar->items * 13)
	    new_selection = event->x / 13;
	else
	    new_selection = menubar->items - 1;
	
	if (!was_active){
	    menubar->selected = new_selection;
	    dlg_select_widget (menubar->widget.parent, menubar);
	    menubar_drop_compute (menubar);
	    menubar_draw (menubar);
	    return MOU_NORMAL;
	}
	
	menubar_remove (menubar);

	menubar->selected = new_selection;

	menubar_drop_compute (menubar);
	menubar_draw (menubar);
	return MOU_NORMAL;
    }

    if (!menubar->dropped)
	return MOU_NORMAL;
    
    /* Ignore the events on anything below the third line */
    if (event->y <= 2)
	return MOU_NORMAL;
    
    /* Else, the mouse operation is on the menus or it is not */
    left_x = menubar->selected * 12;
    right_x = left_x + menubar->max_entry_len + 4;
    bottom_y = (menubar->menu [menubar->selected])->count + 3;

    if ((event->x > left_x) && (event->x < right_x) && (event->y < bottom_y)){
	int pos = event->y - 3;

	if (!menubar->menu [menubar->selected]->entries [pos].call_back)
	    return MOU_NORMAL;
	
	menubar_paint_idx (menubar, menubar->subsel, MENU_ENTRY_COLOR);
	menubar->subsel = pos;
	menubar_paint_idx (menubar, menubar->subsel, MENU_SELECTED_COLOR);

	if (event->type & GPM_UP)
	    menubar_execute (menubar, pos);
    } else
	if (event->type & GPM_DOWN)
	    menubar_finish (menubar);
	 
    return MOU_NORMAL;
}

static void menubar_destroy (WMenu *menubar)
{
}

WMenu *menubar_new (int y, int x, int cols, Menu menu [], int items)
{
    WMenu *menubar = (WMenu *) xmalloc (sizeof (WMenu), "menubar_new");

    init_widget (&menubar->widget, y, x, 1, cols,
                 (callback_fn) menubar_callback,
		 (destroy_fn)  menubar_destroy,
		 (mouse_h)     menubar_event);
    menubar->menu = menu;
    menubar->active = 0;
    menubar->dropped = 0;
    menubar->items = items;
    menubar->selected = 0;
    widget_want_cursor (menubar->widget, 0);
    
    return menubar;
}
