/*
 * GraphApp - Cross-Platform Graphics Programming Library.
 *
 * File: menus.c -- creating menus, menubars, etc.
 * Platform: Windows  Version: 2.35  Date: 1998/04/04
 *
 * Version: 1.00  Changes: Original version by Lachlan Patrick.
 * Version: 2.00  Changes: New object class system.
 * Version: 2.15  Changes: Added submenu creation function.
 * Version: 2.20  Changes: New menuitem constructor.
 * Version: 2.30  Changes: Modified the shortcut key selector.
 * Version: 2.35  Changes: New reference count technique.
 */

/* Copyright (C) 1993-1998 Lachlan Patrick

   This file is part of GraphApp, a cross-platform C graphics library.

   GraphApp is free software; you can redistribute it and/or modify it
   under the terms of the GNU Library General Public License.
   GraphApp is distributed in the hope that it will be useful, but
   WITHOUT ANY WARRANTY.

   See the file COPYLIB.TXT for details.
*/

/*
 *  When you create menubars, menus and menuitems, they are added
 *  to the currentwindow. Menus are added to the current_menubar and
 *  menuitems are added to the current_menu.
 */

/* Copyright (C) 2004	The R Foundation

   Change for R - Chris Jackson.  Enabled menu shortcut keys, don't
   execute shortcut if menu item is grayed out */

#include "internal.h"
/*#include "config.h" */
#include <wchar.h>
#define mbs_init(x) memset(&x,0,sizeof(x))
size_t Rf_mbrtowc(wchar_t *wc, const char *s, size_t n, mbstate_t *ps);

/*
 *  Menu variables.
 */

	PROTECTED int     menus_active = 1;

	PROTECTED menubar current_menubar = NULL;
	PROTECTED menu    current_menu    = NULL;

	static    int     id = MinMenuID;

	PROTECTED HACCEL hAccel = 0;

/*
 *  Initialise menus. Actually, all we do here is load accelerators.
 *  The accelarator resource must have the name "ACCELS".
 *  If there are no accelerator resources, hAccel will be zero.
 */
PROTECTED
void init_menus(void)
{
    if (this_instance != 0)
	hAccel = LoadAccelerators(this_instance, "ACCELS");
}

/*
 *  Find the position of a menu on its parent menubar.
 *  Return zero for the first menu, one for the next etc.
 */
static int find_menu_position(menu parent, char *name)
{
    menu m, first;
    int which = 0;

    first = m = parent->child;
    if (first) {
	do {
	    if (strcmp(m->text, name) == 0)
		break;
	    which ++;
	    m = m->next;
	} while (m != first);
    }
    return which;
}

/*
 *  Private menu deletion function.
 */
static void private_delmenu(menu m)
{
    window w = parentwindow(m);
    if (m->kind == MenuitemObject)
	RemoveMenu(m->parent->handle, m->id, MF_BYCOMMAND);
    else if (m->kind == MenuObject) {
	DeleteMenu(m->parent->handle,
		   find_menu_position(m->parent, m->text),
		   MF_BYPOSITION);
    }
    else
	DestroyMenu(m->handle);
    if (w)
	DrawMenuBar(w->handle);
}

/*
 *  Find a char within a string.
 *  Return -1 if not found, or a number from 0 to strlen(str)-1
 *  to indicate where the char is.
 */
char *Rf_strchr(const char *s, int c); /* from util.c, MBCS-aware */

static int find_char(int ch, const char *str)
{
    char *p;
    p = Rf_strchr(str, ch);
    if(!p) return -1; else return p - str;
}

/*
 *  This function forms a search string, to look for the best
 *  letter to underline in a menu item. It first tries the
 *  accelerator key, then the uppercase letters from the menu name,
 *  then the digits, and finally the lowercase letters.
 *  It ignores spaces.
 */
static void set_search_string(char *search, const char *name, int key)
{
    int source;
    int dest = 0;
    int mb_len;
    mbstate_t mb_st;

    /* handle a couple of special cases first */
    if (! string_diff(name, "Cut"))  search[dest++] = 't';
    if (! string_diff(name, "Exit")) search[dest++] = 'x';
    if (! string_diff(name, "?")) search[dest++] = '?';

    {
	/* If there is an '&' in the string, use the next letter first */
	char *p = strchr(name, '&');
	if (p && *(p+1)) search[dest++] = *(p+1);
    }

    /* add the accelerator key if it is in the name string */
    if (key) {
	key = toupper(key);
	if (find_char(key, name) >= 0)
	    search[dest++] = (char) key;
	else {
	    key = tolower(key);
	    if (find_char(key, name) >= 0)
		search[dest++] = (char) key;
	}
    }
    /* add the uppercase letters */
    for (source=0; name[source]; source++) {
	mbs_init(mb_st);
	mb_len = Rf_mbrtowc(NULL, name + source, MB_CUR_MAX,&mb_st);
	if (mb_len > 1) source += mb_len-1;
	else
	    if (isupper(name[source])) search[dest++] = name[source];
    }
    /* add the digits */
    for (source=0; name[source]; source++) {
	mbs_init(mb_st);
	mb_len = Rf_mbrtowc(NULL, name + source, MB_CUR_MAX,&mb_st);
	if (mb_len > 1) source += mb_len-1;
	else
	    if (isdigit(name[source])) search[dest++] = name[source];
    }
    /* add the lowercase letters */
    for (source=0; name[source]; source++) {
	mbs_init(mb_st);
	mb_len = Rf_mbrtowc(NULL, name + source, MB_CUR_MAX,&mb_st);
	if (mb_len > 1) source += mb_len-1;
	else
	    if (islower(name[source])) search[dest++] = name[source];
    }
    /* end the search string */
    search[dest] = '\0';
}

/*
 *  Look through the list of siblings of the given object, and
 *  find a shortcut key from the search string which hasn't already
 *  been used by some other object. Return the valid shortcut char,
 *  or else zero if we can't find one.
 */
static int find_shortcut(object me, char *search)
{
    int source;
    object first, obj;

    first = me->parent->child;

    for (source = 0; search[source]; source++)
    {
	int mb_len;
	mbstate_t mb_st;
	mbs_init(mb_st);
	mb_len = Rf_mbrtowc(NULL, search + source, MB_CUR_MAX, &mb_st);
	if ( mb_len > 1 ) {
	    source += mb_len - 1;
	} else
	    /* for each character in the search string */
	    /* look through every sibling object */

	    for (obj = first; obj; obj = obj->next)
	    {
		if (obj == me) /* at end of list, success! */
		    return search[source];

		/* use uppercase comparisons */
		if (obj->shortcut == toupper(search[source]))
		    break; /* can't use this shortcut */

	    }
    }

    return 0;
}

/*
 *  Take "Open..." and 'O' and produce the string "Open...\tCtrl+O".
 *  This function also sets an object's shortcut key, which is the
 *  underlined letter in a menu item in Windows or X-Windows.
 */
static void setmenustring(object obj, char *buf, const char *name, int key)
{
    char search[256];
    int ch, where, source, dest = 0;
    char *extra = "\tCtrl+";

    set_search_string(search, name, key);
    ch = find_shortcut(obj, search);

    if (ch) /* found a valid shortcut key */
    {
	obj->shortcut = toupper(ch); /* case-insensitive */
	where = find_char(ch, name);

	for (source=0; source < where; source++)
	{
	    int mb_len;
	    int i;
	    mbstate_t mb_st;
	    mbs_init(mb_st);
	    mb_len = Rf_mbrtowc(NULL, name + source, MB_CUR_MAX, &mb_st);
	    if ( mb_len > 1 ) {
		for (i = 0 ; i < mb_len ; i++)
		    buf[dest++] = name[source+i];
		source += mb_len-1;
	    } else if(name[source] == '&') {
		/* skip it */
	    } else
		buf[dest++] = name[source];
	}
	buf[dest++] = '&';
	for (; name[source]; source++)
	    buf[dest++] = name[source];
    }
    else /* no shortcut key, just copy the name string except '&' */
    {
	for (source = 0; name[source]; source++)
	    if(name[source] != '&') buf[dest++] = name[source];
    }

    if (key) {
	for (source=0; extra[source]; source++)
	    buf[dest++] = extra[source];
	buf[dest++] = key;
    }

    buf[dest] = '\0';
}

/*
 *  Menu functions.
 */
menubar newmenubar(actionfn adjust_menus)
{
    object obj;
    HMENU hm;

    if (! current_window) {
	current_window = simple_window();
	show(current_window);
    }

    hm = CreateMenu();
    obj = new_object(MenubarObject, hm, current_window);
    if (obj) {
	current_window->menubar = obj;
	obj->menubar = NULL;
	obj->die = private_delmenu;
	obj->id = id++;
	obj->action = adjust_menus;
	obj->text = new_string("Menubar");
	current_menubar = obj;
	SetMenu(current_window->handle, hm);
    }
    return (menubar) obj;
}

BOOL myAppendMenu(HMENU h, UINT flags, UINT_PTR id, LPCTSTR name)
{
    if(localeCP > 0 && (localeCP != GetACP())) {
	wchar_t wc[100];
	mbstowcs(wc, name, 100);
	return AppendMenuW(h, flags, id, wc);
    } else
	return AppendMenuA(h, flags, id, name);
}


menu newsubmenu(menu parent, const char *name)
{
    object obj;
    HMENU hm;
    UINT flags = MF_POPUP;
    char str[256];
    if (! parent) {
	if (! current_menubar)
	    current_menubar = newmenubar(NULL);
	parent = current_menubar;
    }

    if (! parent)
	return NULL;
    if (! name)
	name = "";
    if (name[0] == '\t') {
	name += 1;
	flags |= MF_HELP;
    }

    if (parent->kind == WindowObject)
	hm = CreatePopupMenu();
    else
	hm = CreateMenu();
    obj = new_object(MenuObject, hm, parent);
    if (obj) {
	obj->die = private_delmenu;
	obj->id = id++;
	obj->text = new_string(name);
	setmenustring(obj, str, name, 0);
	name = str;
	current_menu = obj;
    }
    if (parent->kind != WindowObject)
	myAppendMenu(parent->handle, flags, (UINT_PTR) hm, name);
    if (parent == current_menubar)
	DrawMenuBar(current_menubar->parent->handle);

    return (menu) obj;
}

menu newmenu(const char *name)
{
    return newsubmenu(current_menubar, name);
}


menuitem newmenuitem(const char *name, int key, menufn fn)
{
    object obj;
    UINT flags;
    char str[256];

    if (! current_menu)
	current_menu = newmenu("Special");
    if (! current_menu)
	return NULL;
    if (! name)
	name = "-"; /* separator */

    key = toupper(key); /* make it uppercase */

    obj = new_object(MenuitemObject, 0, current_menu);
    if (obj) {
	obj->die = private_delmenu;
	obj->id = id++;
	obj->key = key;
	obj->action = fn;
	obj->value = 0;
	obj->text = new_string(name);
	obj->state |= Enabled;

	if (name[0] == '-') {
	    flags = MF_SEPARATOR;
	    name = NULL;
	} else {
	    flags = MF_STRING;
	    setmenustring(obj, str, name, key);
	    name = str;
	}

	myAppendMenu(current_menu->handle, flags, obj->id, name);
    }
    return (menuitem) obj;
}

/*
 *  Find various parent objects of a menu (or any object).
 */
#if 0
PROTECTED
object parent_menubar(object obj)
{
    while (obj) {
	if (obj->kind == MenubarObject)
	    break;
	obj = obj->parent;
    }
    return obj;
}

PROTECTED
object parent_menu(object obj)
{
    while (obj) {
	if (obj->kind == MenuObject)
	    break;
	obj = obj->parent;
    }
    return obj;
}
#endif

/*
 *  The adjust_menu function is called just after the program
 *  receives a WM_INITMENU message.  This message is sent even if
 *  the user is attempting to use the system menu, which is nice.
 *  We use this function to keep menubars and menus up-to-date by
 *  calling their respective action functions.
 */
PROTECTED
void adjust_menu(WPARAM wParam)
{
    object obj;
    obj = find_by_handle((HANDLE)wParam);
    if (obj) {
	activatecontrol(obj);
	if (obj->kind == MenubarObject)
	    DrawMenuBar(obj->parent->handle);
    }
}

/*
 *  Handle the menu selection. wParam is what WM_[SYS]COMMAND sets
 *  it to i.e. the object id number. The menus will already have
 *  received WM_INITMENU messages so they will be up-to-date.
 */
PROTECTED
void handle_menu_id (WPARAM wParam)
{
    object obj;

    obj = find_by_id(wParam);
    if (obj)
	activatecontrol(obj);
}

/*
 *  Handle a menu accelerator key. The key will be a normal char,
 *  in the range of 0..9 or A..Z (not a..z because we use KEYDOWNs).
 *  Uses a recursive function to call the menu adjustor functions
 *  in order from menubar down to popup menu.
 */

static void adjust_menus_top_down(object obj)
{
    /* Recursively step up the list. */
    if (! obj)
	return;
    adjust_menus_top_down(obj->parent);

    /* Adjust menubar then child menus as we descend. */
    if (obj->kind == MenubarObject) {
	activatecontrol(obj);
	DrawMenuBar(obj->parent->handle);
    }
    else if (obj->kind == MenuObject)
	activatecontrol(obj);
}

/* Only search for the key within the menus of the focused window.
   If key is not found fall through to user's key handler. CJ */

PROTECTED
int handle_menu_key(WPARAM wParam)
{
    object win, obj;
    win = find_by_handle(GetFocus());
    if (win) {
	if (win->kind != WindowObject)
	    win = win->parent;
	obj = find_by_key(win, wParam);
	if (obj) {
	    adjust_menus_top_down(obj);
	    if (isenabled(obj)) /* Don't do menu actions which are greyed out. CJ */
		activatecontrol(obj);
	    return 1;
	}
    }
    return 0;
}
