text: #include "st_menu.h"
  
  #include <ctype.h>
  #include "st_panel.h"
  #include <stdlib.h>
  #include <string.h>
  #include <wchar.h>
  
  #ifdef HAVE_LIBUNISTRING
  
  /* libunistring */
  #include <unicase.h>
  #include <unistr.h>
  #include <uniwidth.h>
  
  #else
  
  /* Alternative minimalistic own unicode support */
  
  #include "unicode.h"
  
  #endif
  
  
  /*
   * This window is main application window. It is used for taking content
   * for shadow drawing.
   */
  static WINDOW *desktop_win = NULL;
  
  typedef struct
  {
  	char	*c;
  	int		length;
  	int		row;
  } ST_MENU_ACCELERATOR;
  
  struct ST_MENU
  {
  	ST_MENU_ITEM	   *menu_items;
  	WINDOW	   *draw_area;
  	WINDOW	   *window;
  	PANEL	   *panel;
  	WINDOW	   *shadow_window;
  	PANEL	   *shadow_panel;
  	int			first_row;						/* first visible row */
  	int			cursor_row;
  	int			mouse_row;						/* mouse row where button1 was pressed */
  	int		   *options;						/* state options, initially copyied from menu */
  	int		  **refvals;						/* referenced values */
  	ST_MENU_ACCELERATOR		*accelerators;
  	int			naccelerators;
  	ST_MENU_CONFIG *config;
  	int			shortcut_x_pos;
  	int			item_x_pos;
  	int			nitems;							/* number of menu items */
  	int		   *bar_fields_x_pos;				/* array of x positions of menubar fields */
  	int			ideal_y_pos;					/* y pos when is enough space */
  	int			ideal_x_pos;					/* x pos when is enough space */
  	int			rows;							/* number of rows */
  	int			cols;							/* number of columns */
  	int			focus;							/* identify possible event filtering */
  	char	   *title;
  	bool		is_menubar;
  	struct ST_MENU	*active_submenu;
  	struct ST_MENU	**submenus;
  };
  
  struct ST_CMDBAR
  {
  	ST_CMDBAR_ITEM	   *cmdbar_items;
  	WINDOW	   *window;
  	PANEL	   *panel;
  	ST_MENU_CONFIG *config;
  	int			nitems;
  	int		   *positions;
  	char	  **labels;
  	ST_CMDBAR_ITEM	   **ordered_items;
  };
  
  static struct ST_CMDBAR   *active_cmdbar = NULL;
  
  static ST_MENU_ITEM		   *selected_item = NULL;
  static ST_CMDBAR_ITEM	   *selected_command = NULL;
  static int					selected_options = 0;
  static int				   *selected_refval = NULL;
  
  static bool			press_accelerator = false;
  static bool			button1_clicked = false;
  static bool			press_enter = false;
  
  static bool			command_was_activated = false;
  
  static inline int char_length(ST_MENU_CONFIG *config, const char *c);
  static inline int char_width(ST_MENU_CONFIG *config, char *c);
  static inline int str_width(ST_MENU_CONFIG *config, char *str);
  static inline char *chr_casexfrm(ST_MENU_CONFIG *config, char *str);
  static inline int wchar_to_utf8(ST_MENU_CONFIG *config, char *str, int n, wchar_t wch);
  
  static bool _st_menu_driver(struct ST_MENU *menu, int c, bool alt, MEVENT *mevent, bool is_top, bool is_nested_pulldown, bool *unpost_submenu);
  static void _st_menu_free(struct ST_MENU *menu);
  
  static int menutext_displaywidth(ST_MENU_CONFIG *config, char *text, char **accelerator, bool *extern_accel);
  static void pulldownmenu_content_size(ST_MENU_CONFIG *config, ST_MENU_ITEM *menu_items,
  										int *rows, int *columns, int *shortcut_x_pos, int *item_x_pos,
  										ST_MENU_ACCELERATOR *accelerators, int *naccelerators, int *first_row);
  
  static void pulldownmenu_draw_shadow(struct ST_MENU *menu);
  static void menubar_draw(struct ST_MENU *menu);
  static void pulldownmenu_draw(struct ST_MENU *menu, bool is_top);
  static void cmdbar_draw(struct ST_CMDBAR *cmdbar);
  static bool cmdbar_driver(struct ST_CMDBAR *cmdbar, int c, bool alt, MEVENT *mevent);
  
  static void subtract_correction(WINDOW *s, int *y, int *x);
  
  /*
   * Generic functions
   */
  static inline int
  max_int(int a, int b)
  {
  	return a > b ? a : b;
  }
  
  static inline int
  min_int(int a, int b)
  {
  	return a < b ? a : b;
  }
  
  static void *
  safe_malloc(size_t size)
  {
  	void *ptr = malloc(size);
  
  	if (!ptr)
  	{
  		endwin();
  		printf("FATAL: Out of memory\n");
  		exit(1);
  	}
  
  	memset(ptr, 0, size);
  
  	return ptr;
  }
  
  #ifdef PDCURSES
  
  /*
   Created a new version of newwin() because PDCurses (unlike ncurses)
   will not allocate a Window if:
   (begin_y + rows > SP->lines || begin_x + cols > SP->cols)
   Therefore we will make an attempt to reduce the rows/cols if needed
   to get the allocation to pass for *most* cases.
  */
  static WINDOW*
  newwin2(int* rows, int* cols, int begin_y, int begin_x)
  {
  	int SPlines, SPcols;
  
  	getmaxyx(stdscr, SPlines, SPcols);
  	if (begin_y + *rows > SPlines)
  	{
  		*rows = SPlines - begin_y;
  	}
  	if (begin_x + *cols > SPcols)
  	{
  		*cols = SPcols - begin_x;
  	}
  
  	return newwin(*rows, *cols, begin_y, begin_x);
  }
  
  #else
  
  #define newwin2(rows, cols, begin_y, begin_x)  newwin(*rows, *cols, begin_y, begin_x)
  
  #endif
  
  /*
   * Returns bytes of multibyte char
   */
  static inline int
  char_length(ST_MENU_CONFIG *config, const char *c)
  {
  	int		result;
  
  	if (!config->force8bit)
  	{
  		/*
  		 * I would not to check validity of UTF8 char. So I pass
  		 * 4 as n (max possible size of UTF8 char in bytes). When
  		 * in this case, u8_mblen should to return possitive number,
  		 * but be sure, and return 1 everytime.
  		 *
  		 * This functionality can be enhanced to check real size
  		 * of utf8 string.
  		 */
  #ifdef HAVE_LIBUNISTRING
  
  		result = u8_mblen((const uint8_t *) c, 4);
  
  #else
  
  		result = utf8charlen(*((char *) c));
  
  #endif
  
  		if (result > 0)
  			return result;
  	}
  
  	return 1;
  }
  
  /*
   * Retuns display width of char
   */
  static inline int
  char_width(ST_MENU_CONFIG *config, char *c)
  {
  	if (!config->force8bit)
  #ifdef HAVE_LIBUNISTRING
  
  		return u8_width((const uint8_t *) c, 1, config->encoding);
  
  #else
  
  		return utf_dsplen((const char *) c);
  
  #endif
  
  	return 1;
  }
  
  /*
   * returns display width of string
   */
  static inline int
  str_width(ST_MENU_CONFIG *config, char *str)
  {
  	if (!config->force8bit)
  #ifdef HAVE_LIBUNISTRING
  
  		return u8_strwidth((const uint8_t *) str, config->encoding);
  
  #else
  
  		return utf_string_dsplen((const char *) str, strlen(str));
  
  #endif
  
  	return strlen(str);
  }
  
  /*
   * Transform string to simply compareable case insensitive string
   */
  static inline char *
  chr_casexfrm(ST_MENU_CONFIG *config, char *str)
  {
  	char	buffer[20];
  	char   *result;
  
  	if (!config->force8bit)
  	{
  #ifdef HAVE_LIBUNISTRING
  
  		size_t	length;
  
  		length = sizeof(buffer);
  		result = u8_casexfrm((const uint8_t *) str,
  								char_length(config, str),
  									config->language, NULL,
  									buffer, &length);
  		if (result == buffer)
  		{
  			result = strdup(buffer);
  			if (!result)
  			{
  				endwin();
  				printf("FATAL: out of memory\n");
  				exit(1);
  			}
  		}
  
  #else
  
  		char buffer2[10];
  		int chrl = utf8charlen(*str);
  		int	fold;
  
  		strncpy(buffer2, str, chrl);
  		buffer2[chrl] = '\0';
  
  		fold  = utf8_tofold((const char *) buffer2);
  
  		*((int *) buffer) = fold;
  		buffer[sizeof(int)] = '\0';
  
  		result = strdup(buffer);
  		if (!result)
  		{
  			endwin();
  			printf("FATAL: out of memory\n");
  			exit(1);
  		}
  
  #endif
  	}
  	else
  	{
  		buffer[0] = tolower(str[0]);
  		buffer[1] = '\0';
  
  		result = strdup(buffer);
  		if (!result)
  		{
  			endwin();
  			printf("FATAL: out of memory\n");
  			exit(1);
  		}
  	}
  
  	return result;
  }
  
  /*
   * Convert wide char to multibyte encoding
   */
  static inline int
  wchar_to_utf8(ST_MENU_CONFIG *config, char *str, int n, wchar_t wch)
  {
  	int		result;
  
  	if (!config->force8bit)
  	{
  #ifdef HAVE_LIBUNISTRING
  
  		result = u8_uctomb((uint8_t *) str, (ucs4_t) wch, n);
  
  #else
  
  		/* be compiler quite */
  		(void) n;
  
  		unicode_to_utf8(wch, (unsigned char *) str, &result);
  
  #endif
  	}
  	else
  	{
  		*str = (char) wch;
  		result = 1;
  	}
  
  	return result;
  }
  
  /*
   * Workhorse for st_menu_save
   */
  static int
  _save_menustate(struct ST_MENU *menu, int *cursor_rows, int max_rows, int write_pos)
  {
  	int		active_row = -1;
  	int		i;
  
  	if (write_pos + 1 + menu->nitems >= max_rows)
  	{
  		endwin();
  		printf("FATAL: Cannot save menu positions, too complex menu.\n");
  		exit(1);
  	}
  
  	cursor_rows[write_pos++] = menu->cursor_row;
  	cursor_rows[write_pos++] = menu->first_row;
  
  	if (menu->submenus)
  	{
  		for (i = 0; i < menu->nitems; i++)
  		{
  			if (menu->submenus[i])
  			{
  				write_pos = _save_menustate(menu->submenus[i], cursor_rows, max_rows, write_pos);
  
  				if (menu->active_submenu == menu->submenus[i])
  					active_row = i + 1;
  			}
  		}
  	}
  
  	for (i = 0; i < menu->nitems; i++)
  		cursor_rows[write_pos++] = menu->options[i];
  
  	cursor_rows[write_pos++] = active_row;
  
  	return write_pos;
  }
  
  static int
  _save_refvals(struct ST_MENU *menu, int **refvals, int max_refvals, int write_pos)
  {
  	int		i;
  
  	if (write_pos + menu->nitems >= max_refvals)
  	{
  		endwin();
  		printf("FATAL: Cannot save menu refvals, too complex menu.\n");
  		exit(1);
  	}
  
  	if (menu->submenus)
  	{
  		for (i = 0; i < menu->nitems; i++)
  		{
  			if (menu->submenus[i])
  				write_pos = _save_refvals(menu->submenus[i], refvals, max_refvals, write_pos);
  		}
  	}
  
  	for (i = 0; i < menu->nitems; i++)
  		refvals[write_pos++] = menu->refvals[i];
  
  	return write_pos;
  }
  
  /*
   * Workhorse for st_menu_load
   */
  static int
  _load_menustate(struct ST_MENU *menu, int *cursor_rows, int read_pos)
  {
  	int		active_row;
  	int		i;
  
  	menu->cursor_row = cursor_rows[read_pos++];
  	menu->first_row = cursor_rows[read_pos++];
  
  	if (menu->submenus)
  	{
  		for (i = 0; i < menu->nitems; i++)
  		{
  			if (menu->submenus[i])
  			{
  				read_pos = _load_menustate(menu->submenus[i], cursor_rows, read_pos);
  			}
  		}
  	}
  
  	for (i = 0; i < menu->nitems; i++)
  		menu->options[i] = cursor_rows[read_pos++];
  
  	active_row = cursor_rows[read_pos++];
  	if (active_row != -1)
  		menu->active_submenu = menu->submenus[active_row - 1];
  
  	return read_pos;
  }
  
  static int
  _load_refvals(struct ST_MENU *menu, int **refvals, int read_pos)
  {
  	int		i;
  
  	if (menu->submenus)
  	{
  		for (i = 0; i < menu->nitems; i++)
  		{
  			if (menu->submenus[i])
  			{
  				read_pos = _load_refvals(menu->submenus[i], refvals, read_pos);
  			}
  		}
  	}
  
  	for (i = 0; i < menu->nitems; i++)
  		menu->refvals[i] = refvals[read_pos++];
  
  	return read_pos;
  }
  
  
  /*
   * Serialize important fields of menustate to cursor_rows array.
   */
  void
  st_menu_save(struct ST_MENU *menu, int *cursor_rows, int **refvals, int max_items)
  {
  	_save_menustate(menu, cursor_rows, max_items, 0);
  	_save_refvals(menu, refvals, max_items, 0);
  }
  
  /*
   * Load cursor positions and active submenu from safe
   */
  void
  st_menu_load(struct ST_MENU *menu, int *cursor_rows, int **refvals)
  {
  	_load_menustate(menu, cursor_rows, 0);
  	_load_refvals(menu, refvals, 0);
  }
  
  
  /*
   * Returns display length of some text. ~ char is ignored.
   * ~~ is used as ~.
   */
  static int
  menutext_displaywidth(ST_MENU_CONFIG *config, char *text, char **accelerator, bool *extern_accel)
  {
  	int		result = 0;
  	bool	_extern_accel = false;
  	char   *_accelerator = NULL;
  	bool	first_char = true;
  	int		bytes;
  
  	while (*text != '\0')
  	{
  		if (*text == '~' || (*text == '_' && first_char))
  		{
  			/*
  			 * ~~ or __ disable effect of special chars ~ and _. ~x~ defines
  			 * internal accelerator (inside menu item text). _x_ defines
  			 * external accelerator (displayed before menu item text) _ has this
  			 * effect only when it is first char of menu item text.
  			 */
  			if (text[1] == *text)
  			{
  				result += 1;
  				text += 2;
  			}
  			else
  			{
  				if (*text == '_')
  					_extern_accel = true;
  
  				text += 1;
  				if (_accelerator == NULL)
  					_accelerator = text;
  
  				/* eat second '_' */
  				if (_extern_accel)
  					text += 1;
  			}
  
  			first_char = false;
  			continue;
  		}
  
  		bytes = char_length(config, text);
  		result += char_width(config, text);
  		text += bytes;
  
  		first_char = false;
  	}
  
  	if (extern_accel)
  		*extern_accel = _extern_accel;
  	if (accelerator)
  		*accelerator = _accelerator;
  
  	return result;
  }
  
  /*
   * Collect display info about pulldown menu
   */
  static void
  pulldownmenu_content_size(ST_MENU_CONFIG *config, ST_MENU_ITEM *menu_items,
  								int *rows, int *columns, int *shortcut_x_pos, int *item_x_pos,
  								ST_MENU_ACCELERATOR *accelerators, int *naccelerators,
  								int *first_row)
  {
  	char	*accelerator;
  	bool	has_extern_accel = false;
  	int	max_text_width = 0;
  	int max_shortcut_width = 0;
  	int		naccel = 0;
  	int		default_row = -1;
  
  	*rows = 0;
  	*columns = 0;
  	*shortcut_x_pos = 0;
  	*first_row = -1;
  
  	*naccelerators = 0;
  
  	while (menu_items->text)
  	{
  		bool	extern_accel;
  
  		*rows += 1;
  		if (*menu_items->text && strncmp(menu_items->text, "--", 2) != 0)
  		{
  			int text_width = 0;
  			int shortcut_width = 0;
  
  			if (*first_row == -1)
  				*first_row = *rows;
  
  			text_width = menutext_displaywidth(config, menu_items->text, &accelerator, &extern_accel);
  
  			if (extern_accel)
  				has_extern_accel = true;
  
  			if (accelerator != NULL)
  			{
  				accelerators[naccel].c = chr_casexfrm(config, accelerator);
  				accelerators[naccel].length = strlen(accelerators[naccel].c);
  				accelerators[naccel++].row = *rows;
  			}
  
  			if (menu_items->shortcut)
  				shortcut_width = str_width(config, menu_items->shortcut);
  
  			if (menu_items->submenu)
  				shortcut_width += shortcut_width > 0 ? 2 : 1;
  
  			if (menu_items->options & ST_MENU_OPTION_DEFAULT && default_row == -1)
  				default_row = *rows;
  
  			/*
  			 * left alligned shortcuts are used by MC style
  			 */
  			if (config->left_alligned_shortcuts)
  			{
  				max_text_width = max_int(max_text_width, 1 + text_width + 2);
  				max_shortcut_width = max_int(max_shortcut_width, shortcut_width);
  			}
  			else
  				*columns = max_int(*columns,
  											1 + text_width + 1
  											  + (config->extra_inner_space ? 2 : 0)
  											  + (shortcut_width > 0 ? shortcut_width + 4 : 0));
  		}
  
  		menu_items += 1;
  	}
  
  	if (config->left_alligned_shortcuts)
  	{
  		*columns = max_text_width + (max_shortcut_width > 0 ? max_shortcut_width + 1 : 0);
  		*shortcut_x_pos = max_text_width;
  	}
  	else
  		*shortcut_x_pos = -1;
  
  	*naccelerators = naccel;
  
  	/*
  	 * When external accelerators are used, move content to right
  	 */
  	if (has_extern_accel)
  	{
  		*columns += config->extern_accel_text_space + 1;
  		if (*shortcut_x_pos != -1)
  			*shortcut_x_pos += config->extern_accel_text_space + 1;
  		*item_x_pos = config->extern_accel_text_space + 1;
  	}
  	else
  		*item_x_pos = 1;
  
  	if (default_row != -1)
  		*first_row = default_row;
  }
  
  /*
   * Draw menubar
   */
  static void
  menubar_draw(struct ST_MENU *menu)
  {
  	ST_MENU_ITEM	   *menu_item = menu->menu_items;
  	ST_MENU_CONFIG	*config = menu->config;
  	bool	has_focus;
  	bool	has_accelerators;
  	int		i;
  
  	selected_item = NULL;
  	selected_options = 0;
  	selected_refval = NULL;
  
  	/* do nothing when content is invisible */
  	if (menu->focus == ST_MENU_FOCUS_NONE)
  		return;
  
  	show_panel(menu->panel);
  	top_panel(menu->panel);
  
  	update_panels();
  
  	has_focus = menu->focus == ST_MENU_FOCUS_FULL;
  	has_accelerators = menu->focus == ST_MENU_FOCUS_FULL || 
  								menu->focus == ST_MENU_FOCUS_ALT_MOUSE;
  
  	if (has_focus)
  		wbkgd(menu->window, COLOR_PAIR(config->menu_background_cpn) | config->menu_background_attr);
  	else
  		wbkgd(menu->window, COLOR_PAIR(config->menu_unfocused_cpn) | config->menu_unfocused_attr);
  
  	werase(menu->window);
  
  	i = 0;
  	while (menu_item->text)
  	{
  		char	*text = menu_item->text;
  		bool	highlight = false;
  		bool	is_cursor_row = menu->cursor_row == i + 1 && has_focus;
  		bool	is_disabled = menu->options[i] & ST_MENU_OPTION_DISABLED;
  		int		current_pos;
  
  		/* bar_fields_x_pos holds x positions of menubar items */
  		current_pos = menu->bar_fields_x_pos[i];
  
  		if (is_cursor_row)
  		{
  			wmove(menu->window, 0, current_pos - 1);
  			wattron(menu->window, COLOR_PAIR(config->cursor_cpn) | config->cursor_attr);
  			waddstr(menu->window, " ");
  
  			selected_item = menu_item;
  		}
  		else
  			wmove(menu->window, 0, current_pos);
  
  		if (is_disabled)
  			wattron(menu->window, COLOR_PAIR(config->disabled_cpn) | config->disabled_attr);
  
  		while (*text)
  		{
  			/* there are not external accelerators */
  			if (*text == '~')
  			{
  				if (text[1] == '~')
  				{
  					waddstr(menu->window, "~");
  					text += 2;
  					continue;
  				}
  
  				if (!is_disabled && has_accelerators)
  				{
  					if (!highlight)
  					{
  						wattron(menu->window,
  							COLOR_PAIR(is_cursor_row ? config->cursor_accel_cpn : config->accelerator_cpn) |
  									   (is_cursor_row ? config->cursor_accel_attr : config->accelerator_attr) );
  					}
  					else
  					{
  						wattroff(menu->window,
  							COLOR_PAIR(is_cursor_row ? config->cursor_accel_cpn : config->accelerator_cpn) |
  									   (is_cursor_row ? config->cursor_accel_attr : config->accelerator_attr));
  						if (is_cursor_row)
  							wattron(menu->window, COLOR_PAIR(config->cursor_cpn) | config->cursor_attr);
  					}
  
  					highlight = !highlight;
  				}
  				text += 1;
  			}
  			else
  			{
  				int chlen = char_length(config, text);
  
  				waddnstr(menu->window, text, chlen);
  				text += chlen;
  			}
  		}
  
  		if (is_cursor_row)
  		{
  			waddstr(menu->window, " ");
  			wattroff(menu->window, COLOR_PAIR(config->cursor_cpn) | config->cursor_attr);
  		}
  
  		if (is_disabled)
  			wattroff(menu->window, COLOR_PAIR(config->disabled_cpn) | config->disabled_attr);
  
  		menu_item += 1;
  		i += 1;
  	}
  
  	wnoutrefresh(menu->window);
  
  
  	if (menu->active_submenu)
  		pulldownmenu_draw(menu->active_submenu, true);
  }
  
  #ifdef DEBUG_PIPE
  
  static void
  debug_print_size(WINDOW *window, char *name)
  {
  	int		rows, cols;
  	int		y, x;
  
  	getbegyx(window, y, x);
  	getmaxyx(window, rows, cols);
  
  	fprintf(debug_pipe, "window \"%s\" y: %d, x: %d, rows: %d. cols: %d\n", name, y, x, rows, cols);
  }
  
  #endif
  
  /*
   * adjust pulldown position - move panels from ideal position to any position
   * where can be fully displayed.
   */
  static void
  pulldownmenu_ajust_position(struct ST_MENU *menu, int maxy, int maxx)
  {
  	ST_MENU_CONFIG	*config = menu->config;
  
  	int		rows, cols;
  	int		new_y, new_x;
  	int		y, x;
  
  	getbegyx(menu->window, y, x);
  	getmaxyx(menu->window, rows, cols);
  	subtract_correction(menu->window, &y, &x);
  
  	/*
  	 * Hypothesis: when panel is moved, then assigned windows is moved
  	 * to and possibly reduced (when terminal is smaller). But when terminal
  	 * grows, then size of assigned window grows too, more than was original
  	 * size. So we should to recheck size of assigned window, and fix it, when
  	 * it is bigger than should be.
  	 */
  	if (rows != menu->rows || cols != menu->cols)
  	{
  		int		new_rows = y + menu->rows <= maxy ? menu->rows : maxy - y + 1;
  		int		new_cols = x + menu->cols <= maxx ? menu->cols : maxx - x + 1;
  
  		if (new_rows != rows || new_cols != cols)
  			wresize(menu->window, new_rows, new_cols);
  	}
  
  #ifdef DEBUG_PIPE
  
  	debug_print_size(menu->window, "menu window");
  
  #endif
  
  	/*
  	 * Previous issue is same for shadow window. But with different timing.
  	 * Visibility of shadow needs one row and one column more. The begin of
  	 * shadow window should be in relation to begin of menu window.
  	 */
  	if (config->shadow_width > 0)
  	{
  		int		srows, scols;
  
  		/* panel move can move shadow window. Force correct position now */
  
  		getmaxyx(menu->shadow_window, srows, scols);
  		mvwin(menu->shadow_window, y+1 , x+config->shadow_width);
  
  		if (srows != menu->rows || scols != menu->cols)
  		{
  			int		new_srows, new_scols;
  
  			new_srows = y + 1 + menu->rows <= maxy ? menu->rows :  maxy - y - 1;
  			new_scols = x + config->shadow_width + menu->cols <= maxx ? menu->cols : maxx - x - config->shadow_width;
  
  			wresize(menu->shadow_window, new_srows, new_scols);
  		}
  	}
  
  #ifdef DEBUG_PIPE
  
  	debug_print_size(menu->shadow_window, "menu shadow window");
  
  #endif
  
  	if (menu->ideal_x_pos + menu->cols > maxx)
  	{
  		new_x = maxx - menu->cols;
  		if (new_x < 0)
  			new_x = 0;
  	}
  	else
  		new_x = menu->ideal_x_pos;
  
  	if (menu->ideal_y_pos + menu->rows > maxy)
  	{
  		new_y = maxy - menu->rows;
  		if (new_y < 1)
  			new_y = 1;
  	}
  	else
  		new_y = menu->ideal_y_pos;
  
  	if (new_y != y || new_x != x)
  	{
  		int result;
  
  		result = move_panel(menu->panel, new_y, new_x);
  
  		/*
  		 * This is maybe ugly hack. move_panel fails when
  		 * attached window cannot be displayed. So we can try
  		 * resize attached window first.
  		 */
  		if (result != OK)
  		{
  			WINDOW *pw = panel_window(menu->panel);
  
  			wresize(pw, maxy - new_y, menu->cols);
  			replace_panel(menu->panel, pw);
  		}
  
  		result = move_panel(menu->panel, new_y, new_x);
  
  		/*
  		 * move_panel fails when it cannot be displayed completly.
  		 * This is problem for shadow window because is n char right,
  		 * over left border. So we have to create new window with
  		 * different sizes.
  		 * Don't try move shadow panel, when a move of menu panel
  		 * failed.
  		 */
  		if (result == OK && menu->shadow_panel)
  		{
  			int		new_rows, new_cols;
  			int		smaxy, smaxx;
  
  			new_cols = menu->cols - (new_x == menu->ideal_x_pos ? 0 : config->shadow_width);
  			new_rows = menu->rows - (maxy >= new_y + menu->rows + 1 ? 0 : 1);
  
  			getmaxyx(menu->shadow_window, smaxy, smaxx);
  
  			if (new_cols <= smaxx || new_rows <= smaxy)
  			{
  				WINDOW   *new_shadow_window;
  
  				new_shadow_window = newwin2(&new_rows, &new_cols, new_y + 1, new_x + config->shadow_width);
  
  				/* There are no other possibility to resize panel */
  				replace_panel(menu->shadow_panel, new_shadow_window);
  
  				delwin(menu->shadow_window);
  				menu->shadow_window = new_shadow_window;
  
  				wbkgd(menu->shadow_window, COLOR_PAIR(config->menu_shadow_cpn) | config->menu_shadow_attr);
  
  				wnoutrefresh(menu->shadow_window);
  			}
  
  			move_panel(menu->shadow_panel, new_y + 1, new_x + config->shadow_width);
  		}
  	}
  
  	if (menu->active_submenu)
  		pulldownmenu_ajust_position(menu->active_submenu, maxy, maxx);
  
  	update_panels();
  }
  
  /*
   * Draw shadow
   */
  static void
  pulldownmenu_draw_shadow(struct ST_MENU *menu)
  {
  	ST_MENU_CONFIG	*config = menu->config;
  
  	if (menu->shadow_window)
  	{
  		int		smaxy, smaxx;
  		int		i, j;
  		int		wmaxy, wmaxx;
  		attr_t	shadow_attr;
  
  		shadow_attr = config->menu_shadow_attr | A_DIM;
  
  		getmaxyx(menu->shadow_window, smaxy, smaxx);
  
  		show_panel(menu->shadow_panel);
  		top_panel(menu->shadow_panel);
  
  		/* desktop_win must be global */
  		werase(menu->shadow_window);
  
  		if (desktop_win)
  			overwrite(desktop_win, menu->shadow_window);
  		if (active_cmdbar)
  			overwrite(active_cmdbar->window, menu->shadow_window);
  
  		wmaxy = smaxy - 1;
  		wmaxx = smaxx - config->shadow_width;
  
  		for (i = 0; i <= smaxy; i++)
  			for (j = 0; j <= smaxx; j++)
  			{
  
  				if (i < wmaxy && j < wmaxx)
  					continue;
  
  				if (mvwinch(menu->shadow_window, i, j) & A_ALTCHARSET)
  					mvwchgat(menu->shadow_window, i, j, 1,
  								shadow_attr | A_ALTCHARSET,
  								config->menu_shadow_cpn,
  								NULL);
  				else
  					mvwchgat(menu->shadow_window, i, j, 1,
  								shadow_attr,
  								config->menu_shadow_cpn,
  								NULL);
  			}
  
  		wnoutrefresh(menu->shadow_window);
  	}
  
  	if (menu->active_submenu)
  		pulldownmenu_draw_shadow(menu->active_submenu);
  }
  
  /*
   * Early search of selected refval items. Is necessary to
   * change state before next drawing.
   */
  static void
  searching_selected_refval_items(struct ST_MENU *menu)
  {
  	ST_MENU_ITEM	   *menu_items = menu->menu_items;
  	int		row = 1;
  
  	while (menu_items->text != NULL)
  	{
  		int		offset = menu_items - menu->menu_items;
  
  		if (IS_REF_OPTION(menu->options[offset]))
  		{
  			if (menu->cursor_row == row)
  			{
  				selected_item = menu_items;
  				selected_options = menu->options[offset];
  				selected_refval= menu->refvals[offset];
  			}
  		}
  
  		menu_items += 1;
  		row += 1;
  	}
  
  	if (menu->active_submenu)
  		searching_selected_refval_items(menu->active_submenu);
  }
  
  /*
   * pulldown menu bar draw
   */
  static void
  pulldownmenu_draw(struct ST_MENU *menu, bool is_top)
  {
  	bool	draw_box = menu->config->draw_box;
  	ST_MENU_ITEM	   *menu_items = menu->menu_items;
  	ST_MENU_CONFIG	*config = menu->config;
  	WINDOW	   *draw_area = menu->draw_area;
  	WINDOW	   *loc_draw_area = NULL;
  	int		row = 1;
  	int		maxy, maxx;
  	int		dmaxy, dmaxx, dy, dx;
  	int		text_min_x, text_max_x;
  	int		*options = menu->options;
  	bool	force_ascii_art = config->force_ascii_art;
  	int		max_draw_rows = menu->rows;
  	int		i;
  
  	selected_item = NULL;
  
  	if (is_top)
  	{
  		int	stdscr_maxy, stdscr_maxx;
  
  		/* adjust positions of pulldown menus */
  		getmaxyx(stdscr, stdscr_maxy, stdscr_maxx);
  		pulldownmenu_ajust_position(menu, stdscr_maxy, stdscr_maxx);
  
  		/* Draw shadows of window and all nested active pull down menu */
  		pulldownmenu_draw_shadow(menu);
  	}
  
  	show_panel(menu->panel);
  	top_panel(menu->panel);
  
  	update_panels();
  
  	/* clean menu background */
  	werase(menu->window);
  
  	/*
  	 * Now, we would to check if is possible to draw complete draw area on
  	 * screen, and if draw area is good enough for all menu's items.
  	 */
  	getmaxyx(stdscr, maxy, maxx);
  	getmaxyx(draw_area, dmaxy, dmaxx);
  	getbegyx(draw_area, dy, dx);
  
  	subtract_correction(draw_area, &dy, &dx);
  
  	if (dy + dmaxy > maxy || dmaxy < menu->rows )
  	{
  		dmaxy = min_int(maxy - dy, dmaxy);
  		max_draw_rows = draw_box ? (dmaxy - 2) : dmaxy;
  
  		loc_draw_area = subwin(menu->window, dmaxy, dmaxx, dy, dx);
  		draw_area = loc_draw_area;
  
  		if (menu->cursor_row < menu->first_row)
  			menu->first_row = menu->cursor_row;
  
  		if (menu->cursor_row > menu->first_row + max_draw_rows - 1)
  			menu->first_row = menu->cursor_row - max_draw_rows + 1;
  	}
  	else
  		menu->first_row = 1;
  
  	getmaxyx(draw_area, maxy, maxx);
  
  	/* be compiler quiet */
  	(void) maxy;
  
  	if (draw_box)
  	{
  		if (!force_ascii_art)
  			box(draw_area, 0, 0);
  		else
  			wborder(draw_area, '|', '|','-','-','+','+','+','+');
  	}
  
  	text_min_x = (draw_box ? 1 : 0) + (config->extra_inner_space ? 1 : 0);
  	text_max_x = maxx - (draw_box ? 1 : 0) - (config->extra_inner_space ? 1 : 0);
  
  	/* skip first firt_row rows from menu */
  	for (i = 1; i < menu->first_row; i++)
  		if (menu_items->text != NULL)
  			menu_items += 1;
  
  	while (menu_items->text != NULL)
  	{
  		int		offset = menu_items - menu->menu_items;
  		bool	has_submenu = menu_items->submenu ? true : false;
  		bool	is_disabled = false;
  		bool	is_marked = false;
  		int		mark_tag;
  
  		if (options)
  		{
  			int		option = options[offset];
  
  			is_disabled = option & ST_MENU_OPTION_DISABLED;
  
  			if (option & ST_MENU_OPTION_MARKED)
  			{
  				mark_tag = config->mark_tag;
  				is_marked = true;
  			}
  			else if (option & ST_MENU_OPTION_MARKED_REF)
  			{
  				int   *refval = menu->refvals[offset];
  
  				if (*refval == menu_items->data)
  				{
  					is_marked = true;
  					mark_tag = config->mark_tag;
  				}
  			}
  			else if (option & ST_MENU_OPTION_SWITCH2_REF)
  			{
  				int   *refval = menu->refvals[offset];
  
  				is_marked = true;
  				mark_tag = (*refval == 1) ? config->switch_tag_1 : config->switch_tag_0;
  			}
  			else if (option & ST_MENU_OPTION_SWITCH3_REF)
  			{
  				int   *refval = menu->refvals[offset];
  
  				is_marked = true;
  				switch (*refval)
  				{
  					case 1:
  						mark_tag = config->switch_tag_1;
  						break;
  					case 0:
  						mark_tag = config->switch_tag_0;
  						break;
  					default:
  						mark_tag = config->switch_tag_n1;
  						break;
  				}
  			}
  		}
  
  		if (*menu_items->text == '\0' || strncmp(menu_items->text, "--", 2) == 0)
  		{
  			if (draw_box)
  			{
  				wmove(draw_area, row, 0);
  				if (!force_ascii_art)
  					waddch(draw_area, ACS_LTEE);
  				else
  					waddch(draw_area, '|');
  			}
  			else
  				wmove(draw_area, row - 1, 0);
  
  			for(i = 0; i < maxx - 1 - (draw_box ? 1 : -1); i++)
  			{
  				if (!force_ascii_art)
  					waddch(draw_area, ACS_HLINE);
  				else
  					waddch(draw_area, '-');
  			}
  
  			if (draw_box)
  			{
  				if (!force_ascii_art)
  					waddch(draw_area, ACS_RTEE);
  				else
  					waddch(draw_area, '|');
  			}
  		}
  		else
  		{
  			char	*text = menu_items->text;
  			bool	highlight = false;
  			bool	is_cursor_row = menu->cursor_row == offset + 1;
  			bool	first_char = true;
  			bool	is_extern_accel;
  			int		text_y = -1;
  			int		text_x = -1;
  
  			if (is_cursor_row)
  			{
  				mvwchgat(draw_area, row - (draw_box ? 0 : 1), text_min_x, text_max_x - text_min_x,
  						config->cursor_attr, config->cursor_cpn, NULL);
  				wattron(draw_area, COLOR_PAIR(config->cursor_cpn) | config->cursor_attr);
  
  				selected_item = menu_items;
  			}
  
  			if (is_disabled)
  				wattron(draw_area, COLOR_PAIR(config->disabled_cpn) | config->disabled_attr);
  
  			is_extern_accel = (*text == '_' && text[1] != '_');
  
  			if (menu->item_x_pos != 1 && !is_extern_accel)
  			{
  				wmove(draw_area, row - (draw_box ? 0 : 1), text_min_x + 1 + menu->item_x_pos);
  			}
  			else
  				wmove(draw_area, row - (draw_box ? 0 : 1), text_min_x + 1);
  
  			while (*text)
  			{
  				if (*text == '~' || (*text == '_' && (first_char || highlight)))
  				{
  					if (text[1] == *text)
  					{
  						waddnstr(draw_area, text, 1);
  						text += 2;
  						first_char = false;
  						continue;
  					}
  
  					if (!is_disabled)
  					{
  						if (!highlight)
  						{
  							wattron(draw_area,
  								COLOR_PAIR(is_cursor_row ? config->cursor_accel_cpn : config->accelerator_cpn) |
  										   (is_cursor_row ? config->cursor_accel_attr : config->accelerator_attr));
  						}
  						else
  						{
  							wattroff(draw_area,
  								COLOR_PAIR(is_cursor_row ? config->cursor_accel_cpn : config->accelerator_cpn) |
  										   (is_cursor_row ? config->cursor_accel_attr : config->accelerator_attr));
  							if (is_cursor_row)
  								wattron(draw_area, COLOR_PAIR(config->cursor_cpn) | config->cursor_attr);
  
  							if (is_extern_accel)
  							{
  								int		y, x;
  
  								getyx(draw_area, y, x);
  								wmove(draw_area, y, x + config->extern_accel_text_space);
  							}
  						}
  
  						highlight = !highlight;
  					}
  					text += 1;
  				}
  				else
  				{
  					int chlen = char_length(config, text);
  
  					/* Save initial position of text. This first char, when is not
  					 * external accelerator used, or first char after highlighted char
  					 * when extern accelerator is used.
  					 */
  					if (text_y == -1 && text_x == -1)
  					{
  						if (!is_extern_accel || !highlight)
  							getyx(draw_area, text_y, text_x);
  					}
  
  					waddnstr(draw_area, text, chlen);
  					text += chlen;
  				}
  
  				first_char = false;
  			}
  
  			if (menu_items->shortcut != NULL)
  			{
  				if (menu->shortcut_x_pos != -1)
  				{
  					wmove(draw_area, row - (draw_box ? 0 : 1), menu->shortcut_x_pos + (draw_box ? 1 : 0));
  				}
  				else
  				{
  					int dspl = str_width(config, menu_items->shortcut);
  
  					wmove(draw_area,
  							  row - (draw_box ? 0 : 1),
  							  text_max_x - dspl - 1 - (has_submenu ? 2 : 0));
  				}
  
  				waddstr(draw_area, menu_items->shortcut);
  			}
  
  			if (has_submenu)
  			{
  				mvwprintw(draw_area,
  								row - (draw_box ? 0 : 1),
  								text_max_x - 2,
  									"%lc", config->submenu_tag);
  			}
  
  			if (is_marked)
  			{
  				mvwprintw(draw_area,
  								row - (draw_box ? 0 : 1),
  								text_x - 1,
  									"%lc", mark_tag);
  			}
  
  			if (is_cursor_row)
  				wattroff(draw_area, COLOR_PAIR(config->cursor_cpn) | config->cursor_attr);
  
  			if (is_disabled)
  				wattroff(draw_area, COLOR_PAIR(config->disabled_cpn) | config->disabled_attr);
  		}
  
  		menu_items += 1;
  		row += 1;
  
  		if (row > max_draw_rows)
  			break;
  	}
  
  	if (draw_box)
  	{
  		if (menu->first_row > 1)
  			mvwprintw(draw_area, 1, maxx - 1, "%lc", config->scroll_up_tag);
  
  		if (menu->first_row + max_draw_rows - 1 < menu->nitems)
  			mvwprintw(draw_area, maxy - 2, maxx - 1, "%lc", config->scroll_down_tag);
  	}
  
  	if (loc_draw_area)
  	{
  		wnoutrefresh(loc_draw_area);
  		delwin(loc_draw_area);
  	}
  
  	wnoutrefresh(menu->window);
  
  	if (menu->active_submenu)
  		pulldownmenu_draw(menu->active_submenu, false);
  }
  
  /*
   * Sets desktop window - it is used to draw shadow. The window
   * should be panelized.
   */
  void
  st_menu_set_desktop_window(WINDOW *win)
  {
  	desktop_win = win;
  }
  
  /*
   * Show menu - pull down or menu bar.
   */
  void
  st_menu_post(struct ST_MENU *menu)
  {
  	curs_set(0);
  	noecho();
  
  	menu->mouse_row = -1;
  
  	/* show menu */
  	if (menu->is_menubar)
  		menubar_draw(menu);
  	else
  		pulldownmenu_draw(menu, true);
  }
  
  /*
   * Hide menu. When close_active_submenu is true, then the path
   * of active submenu is destroyed - it doesn't rememeber opened
   * submenus.
   */
  void
  st_menu_unpost(struct ST_MENU *menu, bool close_active_submenu)
  {
  	/* hide active submenu */
  	if (menu->active_submenu)
  	{
  		st_menu_unpost(menu->active_submenu, close_active_submenu);
  		if (close_active_submenu)
  			menu->active_submenu = NULL;
  	}
  
  	menu->mouse_row = -1;
  
  	hide_panel(menu->panel);
  	if (menu->shadow_panel)
  		hide_panel(menu->shadow_panel);
  
  	update_panels();
  }
  
  /*
   * Allow to set focus level for menu objects. This allow to
   * redirect events to some else where focus is not full.
   */
  void
  st_menu_set_focus(struct ST_MENU *menu, int focus)
  {
  	menu->focus = focus;
  }
  
  /*
   * The coordinates of subwin are not "correctly" refreshed, when
   * parent panel is moved. Maybe it is bug in ncurses, maybe not.
   * The new coordinates are calculated from parent and offset to parent
   * and difference between new and old coordinates is applyed on
   * x, y points.
   */
  static void
  add_correction(WINDOW *s, int *y, int *x)
  {
  	WINDOW *p = wgetparent(s);
  	/*
  	 * Note: function is_subwin is unknown on some
  	 * older ncurses implementations. Don't use it.
  	 */
  	if (p)
  	{
  		int	py, px, sy, sx, oy, ox;
  		int fix_y, fix_x;
  
  		getbegyx(p, py, px);
  		getbegyx(s, sy, sx);
  		getparyx(s, oy, ox);
  
  		fix_y = sy - (py + oy);
  		fix_x = sx - (px + ox);
  
  		*y += fix_y;
  		*x += fix_x;
  	}
  }
  
  /*
   * It is correction for window begxy, begx when panel contained
   * this window was moved.
   */
  static void
  subtract_correction(WINDOW *s, int *y, int *x)
  {
  	WINDOW *p = wgetparent(s);
  	/*
  	 * Note: function is_subwin is unknown on some
  	 * older ncurses implementations. Don't use it.
  	 */
  	if (p)
  	{
  		int	py, px, sy, sx, oy, ox;
  		int fix_y, fix_x;
  
  		getbegyx(p, py, px);
  		getbegyx(s, sy, sx);
  		getparyx(s, oy, ox);
  
  		fix_y = sy - (py + oy);
  		fix_x = sx - (px + ox);
  
  		*y -= fix_y;
  		*x -= fix_x;
  	}
  }
  
  /*
   * Handle any outer event - pressed key, or mouse event. This driver
   * doesn't handle shortcuts - shortcuts are displayed only.
   * is_top is true, when _st_menu_driver is called first time, when
   * it is called recursivly, then it is false. Only in top call, the
   * draw routines can be called.
   * when unpost_submenu is true, then driver should to unpost active
   * subwindow of current menu. This info is propagated back - the nested
   * element tell to owner, close me.
   */
  static bool
  _st_menu_driver(struct ST_MENU *menu, int c, bool alt, MEVENT *mevent,
  					bool is_top, bool is_nested_pulldown,
  					bool *unpost_submenu)
  {
  	ST_MENU_CONFIG	*config;
  
  	int		cursor_row;				/* number of active menu item */
  	bool	is_menubar;				/* true, when processed object is menu bar */
  	int		first_row = -1;			/* number of row of first enabled item */
  	int		last_row = -1;			/* last menu item */
  	int		mouse_row = -1;			/* item number selected by mouse */
  	int		search_row = -1;		/* code menu selected by accelerator */
  	bool	found_row = false;		/* we found new active menu item (row var will be valid) */
  	bool	post_menu = false;			/* when it is true, then assiciated pulldown menu will be posted */
  	int		row;
  	bool	processed = false;
  	ST_MENU_ITEM	   *menu_items;
  
  	/* reset globals */
  	selected_item = NULL;
  	press_accelerator = false;
  	press_enter = false;
  	button1_clicked = false;
  
  	*unpost_submenu = false;
  
  	/* maybe only cmdbar is used */
  	if (!menu)
  		goto post_process;
  
  	config = menu->config;
  	cursor_row = menu->cursor_row;		/* number of active menu item */
  	is_menubar = menu->is_menubar;		/* true, when processed object is menu bar */
  
  	/* Focus filter */
  	if ((menu->focus == ST_MENU_FOCUS_MOUSE_ONLY && c != KEY_MOUSE) ||
  		(menu->focus == ST_MENU_FOCUS_ALT_MOUSE && c != KEY_MOUSE && !alt) ||
  		(menu->focus == ST_MENU_FOCUS_NONE))
  		goto post_process;
  
  #if NCURSES_MOUSE_VERSION > 1
  
  	/* use mouse wheel only with FULL FOCUS */
  	if (c == KEY_MOUSE &&
  		(mevent->bstate & BUTTON4_PRESSED || mevent->bstate & BUTTON5_PRESSED) &&
  		menu->focus != ST_MENU_FOCUS_FULL)
  		goto post_process;
  
  #endif
  
  	/*
  	 * Propagate event to nested active object first. When nested object would be
  	 * closed, close it. When nested object read event, go to end
  	 */
  	if (menu->active_submenu)
  	{
  		bool	_is_nested_pulldown = is_nested_pulldown ? true : (is_menubar ? false : true);
  		bool	unpost_submenu_loc = false;
  
  		/*
  		 * Key right is used in pulldown menu for access to nested menu.
  		 * When nested menu is active already, then there is not any
  		 * work on this level. KEY_RIGHT can buble to menubar and can
  		 * be processed there.
  		 */
  		if (!is_menubar && c == KEY_RIGHT)
  			goto post_process;
  
  		/*
  		 * Submenu cannot be top object. When now is not menu bar, then now should be
  		 * pulldown menu, and nested object should be nested pulldown menu.
  		 */
  		processed = _st_menu_driver(menu->active_submenu, c, alt, mevent,
  												false, _is_nested_pulldown, &unpost_submenu_loc);
  
  		if (unpost_submenu_loc)
  		{
  			st_menu_unpost(menu->active_submenu, false);
  			menu->active_submenu = NULL;
  		}
  
  		/*
  		 * When we close some object, then we did some work on this
  		 * level, and we should not do more work here.
  		 */
  		if (processed)
  			goto post_process;
  	}
  
  	/*
  	 * The checks of events, that can unpost this level menu. For unposting top
  	 * object is responsible the user.
  	 */
  	if (!is_top)
  	{
  		if (c == ST_MENU_ESCAPE)
  		{
  			*unpost_submenu = true;
  
  			/*
  			 * escape should to close all opened objects, so we cannot to
  			 * returns true (processed) due we are in top object.
  			 */
  			return is_top ? true : false;
  		}
  
  		/*
  		 * Unpost menu (close) when mouse click outside menu window. Mouse position
  		 * event is processed specially. We want to close only submenu, or we want
  		 * to swith to other bar menu item.
  		 */
  		if (c == KEY_MOUSE &&
  			mevent->bstate & (BUTTON1_PRESSED | BUTTON1_RELEASED
  
  #if NCURSES_MOUSE_VERSION > 1
  
  							  | REPORT_MOUSE_POSITION
  
  #endif
  		  ))
  		{
  			int		y = mevent->y;
  			int		x = mevent->x;
  
  			/*
  			 * For some styles, the window is different than draw_area. The
  			 * draw_area is subwindow of window. When main window is moved
  			 * due moving panel (see adjust position), then subwindow has not
  			 * expected coordinates. Following routine calculate fix between
  			 * current draw_area coordinates and expected coordinates. Then
  			 * apply this fix on mouse position.
  			 */
  			add_correction(menu->draw_area, &y, &x);
  
  			if (!is_menubar && !wenclose(menu->draw_area, y, x))
  			{
  
  #if NCURSES_MOUSE_VERSION > 1
  
  				if (mevent->bstate & REPORT_MOUSE_POSITION)
  				{
  					/*
  					 * We want to ignore move movement outside menu's window. Without
  					 * it the behaviour is too sensitive, and it is not user friendly.
  					 * But we need to close submenus, and we want to allow usage of
  					 * bar menu. For these case we should to allow sending event to
  					 * upper objects.
  					 */
  					if (is_nested_pulldown)
  						return false;
  					else
  						/* true when we are not in menu bar position */
  						return y != 0;
  				}
  				else
  				{
  					*unpost_submenu = true;
  					return false;
  				}
  
  #else
  
  				*unpost_submenu = true;
  				return false;
  
  #endif
  
  			}
  		}
  
  		/*
  		 * Nested submenu can be unposted by pressing key left
  		 */
  		if (c == KEY_LEFT && is_nested_pulldown)
  		{
  			*unpost_submenu = true;
  			return true;
  		}
  	}
  
  	if (c == KEY_MOUSE)
  	{
  
  #if NCURSES_MOUSE_VERSION > 1
  
  		if (mevent->bstate & BUTTON5_PRESSED)
  		{
  				c = KEY_DOWN;
  		}
  		else if (mevent->bstate & BUTTON4_PRESSED)
  		{
  			c = KEY_UP;
  		}
  		else
  
  #endif
  
  		if (mevent->bstate & (BUTTON1_PRESSED | BUTTON1_RELEASED
  
  #if NCURSES_MOUSE_VERSION > 1
  
  		 | REPORT_MOUSE_POSITION
  
  #endif
  		  ))
  		{
  			if (is_menubar)
  			{
  				/*
  				 * On menubar level we can process mouse event if row is zero, or
  				 * we can check if mouse is positioned inside draw area or active
  				 * submenu. If not, then we should to unpost active submenu.
  				 */
  				if (mevent->y == 0)
  				{
  					int		i = 0;
  					int		chars_before;
  
  					/*
  					 * How much chars before active item and following item
  					 * specify range of menubar item.
  					 */
  					chars_before = (config->text_space != -1) ? (config->text_space / 2) : 1;
  
  					menu_items = menu->menu_items;
  					while (menu_items->text)
  					{
  						int		minx, maxx;
  
  						/* first menubar field get mouse from left corner */
  						minx = i > 0 ? (menu->bar_fields_x_pos[i] - chars_before) : 0;
  
  						/* last menubar field get mouse to right corner */
  						maxx = i + 1 < menu->nitems ? menu->bar_fields_x_pos[i + 1] - chars_before : mevent->x + 1;
  
  						/* transform possitions to target menu code */
  						if (mevent->x >= minx && mevent->x < maxx)
  						{
  							mouse_row = i + 1;
  							break;
  						}
  
  						menu_items += 1;
  						i = i + 1;
  					}
  				}
  			}
  			else
  			{
  				int		row_loc, col_loc;
  
  				row_loc = mevent->y;
  				col_loc = mevent->x;
  
  				/* fix mouse coordinates, if draw_area has "wrong" coordinates */
  				add_correction(menu->draw_area, &row_loc, &col_loc);
  
  				/* calculate row from transformed mouse event */
  				if (wmouse_trafo(menu->draw_area, &row_loc, &col_loc, false))
  						mouse_row = row_loc + 1 - (config->draw_box ? 1:0) + (menu->first_row - 1);
  			}
  		}
  	}
  	else
  		/* there are no mouse event, reset prev mouse row */
  		menu->mouse_row = -1;
  
  	/*
  	 * Try to check if key is accelerator. This check should be on last level.
  	 * So don't do it if submenu is active.
  	 */
  	if (c != KEY_MOUSE
  			&& c != KEY_HOME && c != KEY_END
  			&& c != KEY_UP && c != KEY_DOWN
  			&& c != KEY_LEFT && c != KEY_RIGHT)
  	{
  		char	buffer[20];
  		char   *pressed;
  		int		l_pressed;
  		int		i;
  
  		/*
  		 * accelerator can be alt accelerator for menuber or non alt, and
  		 * the menu should not to have active submenu.
  		 */
  		if ((!alt && !menu->active_submenu) ||
  				(alt && is_menubar))
  		{
  			l_pressed = wchar_to_utf8(config, buffer, 20, (wchar_t) c);
  			buffer[l_pressed] = '\0';
  
  			pressed = chr_casexfrm(config, (char *) buffer);
  			l_pressed = strlen(pressed);
  
  			for (i = 0; i < menu->naccelerators; i++)
  			{
  				if (menu->accelerators[i].length == l_pressed &&
  					memcmp(menu->accelerators[i].c, pressed, l_pressed) == 0)
  				{
  					/* check if row is enabled */
  					search_row = menu->accelerators[i].row;
  					if (menu->options[search_row - 1] & ST_MENU_OPTION_DISABLED)
  						/* revert back, found accelerator is for disabled menu item */
  						search_row = -1;
  					else
  						break;
  				}
  			}
  
  			free(pressed);
  
  			/* Process key in this case only when we found accelerator */
  			if (search_row != -1)
  				processed = true;
  		}
  	}
  
  	/*
  	 * Iterate over menu items, and try to find next or previous row, code, or mouse
  	 * row.
  	 */
  	menu_items = menu->menu_items; row = 1;
  	while (menu_items->text != 0)
  	{
  		if (*menu_items->text != '\0' &&
  				(strncmp(menu_items->text, "--", 2) != 0) &&
  				((menu->options[row - 1] & ST_MENU_OPTION_DISABLED) == 0))
  		{
  			if (first_row == -1)
  			{
  				first_row = row;
  
  				if (c == KEY_HOME && !is_menubar)
  				{
  					menu->cursor_row = first_row;
  					found_row = true;
  					processed = true;
  					break;
  				}
  			}
  
  			if (is_menubar)
  			{
  				if (c == KEY_RIGHT && row > cursor_row) 
  				{
  					menu->cursor_row = row;
  					found_row = true;
  					processed = true;
  					break;
  				}
  				else if (c == KEY_LEFT && row == cursor_row)
  				{
  					if (last_row != -1)
  					{
  						menu->cursor_row = last_row;
  						found_row = true;
  						processed = true;
  						break;
  					}
  				}
  			}
  			else
  			{
  				/* Is not menubar */
  				if (c == KEY_DOWN && row > cursor_row)
  				{
  					menu->cursor_row = row;
  					processed = true;
  					found_row = true;
  					break;
  				}
  				else if (c == KEY_UP && row == cursor_row)
  				{
  					if (last_row != -1)
  					{
  						menu->cursor_row = last_row;
  						found_row = true;
  						processed = true;
  						break;
  					}
  					else
  						c = KEY_END;
  				}
  			}
  
  			if (mouse_row != -1 && row == mouse_row)
  			{
  				menu->cursor_row = row;
  				found_row = true;
  				processed = true;
  				post_menu = true;
  
  				if (mevent->bstate & BUTTON1_PRESSED
  
  #if NCURSES_MOUSE_VERSION > 1
  
  				|| mevent->bstate & REPORT_MOUSE_POSITION
  
  #endif
  
  				  )
  				{
  					menu->mouse_row = mouse_row;
  				}
  				else
  				{
  					/*
  					 * Fully valid release event for transformation to
  					 * clicked event is only event, when PRESSED row
  					 * and released row is same.
  					 */
  					if (mevent->bstate& BUTTON1_RELEASED &&
  							menu->mouse_row == mouse_row)
  					{
  						button1_clicked = true;
  					}
  					menu->mouse_row = -1;
  				}
  				break;
  			}
  			else if (search_row != -1 && row == search_row)
  			{
  				menu->cursor_row = row;
  				press_accelerator = true;
  
  				found_row = true;
  				post_menu = true;
  				processed = true;
  				break;
  			}
  
  			last_row = row;
  		}
  		menu_items += 1;
  		row += 1;
  	}
  
  	/*
  	 * When rows not found, maybe we would to search limit points
  	 * or we would to return back in ring buffer of menu items.
  	 */
  	if (!found_row)
  	{
  		if (is_menubar)
  		{
  			if (c == KEY_RIGHT)
  			{
  				menu->cursor_row = first_row;
  				processed = true;
  			}
  			else if (c == KEY_LEFT)
  			{
  				menu->cursor_row = last_row;
  				processed = true;
  			}
  		}
  		else
  		{
  			if (c == KEY_END)
  			{
  				menu->cursor_row = last_row;
  				processed = true;
  			}
  			else if (c == KEY_DOWN)
  			{
  				menu->cursor_row = first_row;
  				processed = true;
  			}
  		}
  	}
  
  	/* when menubar is changed, unpost active pulldown submenu */
  	if (menu->active_submenu && cursor_row != menu->cursor_row)
  	{
  		st_menu_unpost(menu->active_submenu, false);
  		menu->active_submenu = NULL;
  
  		/* remember, submenu was visible */
  		post_menu = true;
  	}
  
  	/* enter has sense only on selectable menu item */
  	if (c == 10 && menu->cursor_row != -1)
  		press_enter = true;
  
  	/*
  	 * Some actions can activate submenu, check it and open it, if it
  	 * is required.
  	 */
  	if (press_accelerator || 
  			  (c == KEY_DOWN && is_menubar) ||
  			  (c == KEY_RIGHT && !is_menubar) ||
  			  c == 10 || post_menu)
  	{
  		menu->active_submenu = menu->submenus[menu->cursor_row - 1];
  		if (menu->active_submenu)
  		{
  			/* when submenu is active, then reset accelerator and mouse flags */
  			press_accelerator = false;
  			press_enter = false;
  			button1_clicked = false;
  		}
  
  		/*
  		 * When mouse event opens or reopens submenu, then we take
  		 * this event as processed event. Valid accelerator is processed
  		 * always. Enter (c == 10) is processed always too.
  		 */
  		if (press_accelerator || c == 10)
  			processed = true;
  		else
  			processed = menu->active_submenu != NULL;
  	}
  
  	/*
  	 * We can set processed flag, when some mouse row was founded. That means
  	 * so mouse click was somewhere to draw area.
  	 */
  	if (mouse_row != -1)
  		processed = true;
  
  post_process:
  
  	/*
  	 * show content, only top object is can do this - nested objects
  	 * are displayed recursivly.
  	 */
  	if (is_top)
  	{
  		/* when we processed some event, then we usually got a full focus */
  		if (processed)
  			menu->focus = ST_MENU_FOCUS_FULL;
  		else
  		{
  			/*
  			 * When event was not processed by menubar, then we
  			 * we can try to sent it to command bar. But with
  			 * full focus, the menubar is hungry, and we send nothing.
  			 */
  			if (active_cmdbar)
  			{
  				if (!menu || menu->focus != ST_MENU_FOCUS_FULL)
  					processed = cmdbar_driver(active_cmdbar, c, alt, mevent);
  			}
  		}
  
  		if (processed)
  		{
  			/* try to search selected item */
  			if (menu)
  				searching_selected_refval_items(menu);
  		}
  
  		/* postprocess for referenced values */
  		if (selected_item && (press_accelerator || press_enter || button1_clicked))
  		{
  			if (IS_REF_OPTION(selected_options))
  			{
  				if (selected_refval == NULL)
  				{
  					endwin();
  					fprintf(stderr, "detected referenced option without referenced value");
  					exit(1);
  				}
  
  				if (selected_options & ST_MENU_OPTION_MARKED_REF)
  				{
  					*selected_refval = selected_item->data;
  				}
  				else if ((selected_options & ST_MENU_OPTION_SWITCH2_REF) ||
  						 (selected_options & ST_MENU_OPTION_SWITCH3_REF))
  				{
  					*selected_refval = (*selected_refval == 1) ? 0 : 1;
  				}
  			}
  		}
  
  		/*
  		 * command bar should be drawed first - because it is deeper
  		 * than pulldown menu
  		 */
  		if (active_cmdbar)
  			cmdbar_draw(active_cmdbar);
  
  		if (menu)
  		{
  			if (menu->is_menubar)
  				menubar_draw(menu);
  			else
  				pulldownmenu_draw(menu, true);
  
  			/* eat all keyboard input, when focus is full on top level */
  			if (c != KEY_MOUSE && c != KEY_RESIZE &&
  					c != ST_MENU_ESCAPE &&
  					menu->focus == ST_MENU_FOCUS_FULL)
  				processed = true;
  		}
  	}
  
  	return processed;
  }
  
  bool
  st_menu_driver(struct ST_MENU *menu, int c, bool alt, MEVENT *mevent)
  {
  	bool		aux_unpost_submenu = false;
  
  	/*
  	 * We should to complete mouse click based on two
  	 * events. Mouse click is valid if press, release
  	 * was related with same command. Now, when mouse
  	 * is pressed, we don't know a related object, but
  	 * we can reset selected_command variable.
  	 */
  	if (mevent->bstate & BUTTON1_PRESSED)
  		selected_command = NULL;
  
  	/*
  	 * We would to close pulldown menus on F10 key - similar behave
  	 * like SCAPE, so we can translate F10 event to ST_MENU_ESCAPE,
  	 * when menubar can accept these keys (based on focus).
  	 */
  	if (menu && KEY_F(10) == c && menu->focus == ST_MENU_FOCUS_FULL)
  		c = ST_MENU_ESCAPE;
  
  	return _st_menu_driver(menu, c, alt, mevent, true, false, &aux_unpost_submenu);
  }
  
  /*
   * Create state variable for pulldown menu. It based on template - a array of ST_MENU_ITEM fields.
   * The initial position can be specified. The config (specify desplay properties) should be
   * passed. The config can be own or preloaded from preddefined styles by function st_menu_load_style.
   * a title is not supported yet.
   */
  struct ST_MENU *
  st_menu_new(ST_MENU_CONFIG *config, ST_MENU_ITEM *menu_items, int begin_y, int begin_x, char *title)
  {
  	struct ST_MENU *menu;
  	int		rows, cols;
  	ST_MENU_ITEM *menu_item;
  	int		menu_fields = 0;
  	int		i;
  
  	menu = safe_malloc(sizeof(struct ST_MENU));
  
  	menu->menu_items = menu_items;
  	menu->config = config;
  	menu->title = title;
  	menu->naccelerators = 0;
  	menu->is_menubar = false;
  	menu->mouse_row = -1;
  	menu->first_row = 1;
  
  	/* how much items are in template */
  	menu_item = menu_items;
  	while (menu_item->text != NULL)
  	{
  		menu_fields += 1;
  		menu_item += 1;
  	}
  
  	/* preallocate good enough memory */
  	menu->accelerators = safe_malloc(sizeof(ST_MENU_ACCELERATOR) * menu_fields);
  	menu->submenus = safe_malloc(sizeof(struct ST_MENU) * menu_fields);
  	menu->options = safe_malloc(sizeof(int) * menu_fields);
  	menu->refvals = safe_malloc(sizeof(int*) * menu_fields);
  
  	menu->nitems = menu_fields;
  
  	/* get pull down menu dimensions */
  	pulldownmenu_content_size(config, menu_items, &rows, &cols,
  							&menu->shortcut_x_pos, &menu->item_x_pos,
  							menu->accelerators, &menu->naccelerators,
  							&menu->cursor_row);
  
  	if (config->draw_box)
  	{
  		rows += 2;
  		cols += 2;
  	}
  
  	if (config->wide_vborders)
  		cols += 2;
  	if (config->wide_hborders)
  		rows += 2;
  
  	/* Prepare property for menu shadow */
  	if (config->shadow_width > 0)
  	{
  		menu->shadow_window = newwin2(&rows, &cols, begin_y + 1, begin_x + config->shadow_width);
  		menu->shadow_panel = new_panel(menu->shadow_window);
  
  		hide_panel(menu->shadow_panel);
  		wbkgd(menu->shadow_window, COLOR_PAIR(config->menu_shadow_cpn) | config->menu_shadow_attr);
  
  		wnoutrefresh(menu->shadow_window);
  	}
  	else
  	{
  		menu->shadow_window = NULL;
  		menu->shadow_panel = NULL;
  	}
  
  	menu->window = newwin2(&rows, &cols, begin_y, begin_x);
  
  	menu->ideal_y_pos = begin_y;
  	menu->ideal_x_pos = begin_x;
  	menu->rows = rows;
  	menu->cols = cols;
  
  	wbkgd(menu->window, COLOR_PAIR(config->menu_background_cpn) | config->menu_background_attr);
  	wnoutrefresh(menu->window);
  
  	/*
  	 * Initialize submenu states (nested submenus)
  	 */
  	menu_item = menu_items;
  	i = 0;
  	while (menu_item->text)
  	{
  		if (menu_item->submenu)
  		{
  			menu->submenus[i] = 
  					st_menu_new(config, menu_item->submenu,
  										begin_y + i + config->submenu_offset_y
  										+ (config->draw_box ? 1 : 0)
  										+ (config->wide_vborders ? 1 : 0),
  										begin_x + cols + config->submenu_offset_x,
  										NULL);
  		}
  		else
  			menu->submenus[i] = NULL;
  
  		menu->options[i] = menu_item->options;
  		menu->refvals[i] = NULL;
  
  		menu_item += 1;
  		i += 1;
  	}
  
  	/* draw area can be same like window or smaller */
  	if (config->wide_vborders || config->wide_hborders)
  	{
  		menu->draw_area = derwin(menu->window,
  			rows - (config->wide_hborders ? 2 : 0),
  			cols - (config->wide_vborders ? 2 : 0),
  			config->wide_hborders ? 1 : 0,
  			config->wide_vborders ? 1 : 0);
  
  		wbkgd(menu->draw_area, COLOR_PAIR(config->menu_background_cpn) | config->menu_background_attr);
  
  		wnoutrefresh(menu->draw_area);
  	}
  	else
  		menu->draw_area = menu->window;
  
  	menu->panel = new_panel(menu->window);
  	hide_panel(menu->panel);
  
  	return menu;
  }
  
  /*
   * Create state variable for menubar based on template (array) of ST_MENU_ITEM
   */
  struct ST_MENU *
  st_menu_new_menubar2(ST_MENU_CONFIG *barcfg, ST_MENU_CONFIG *pdcfg, ST_MENU_ITEM *menu_items)
  {
  	struct ST_MENU *menu;
  	int		maxy, maxx;
  	ST_MENU_ITEM *menu_item;
  	int		menu_fields = 0;
  	int		aux_width = 0;
  	int		text_space;
  	int		current_pos;
  	int		i = 0;
  	int		naccel = 0;
  
  	if (pdcfg == NULL)
  		pdcfg = barcfg;
  
  	getmaxyx(stdscr, maxy, maxx);
  
  	menu = safe_malloc(sizeof(struct ST_MENU));
  
  	maxy = 1;
  	menu->window = newwin2(&maxy, &maxx, 0, 0);
  	menu->panel = new_panel(menu->window);
  
  	/* there are not shadows */
  	menu->shadow_window = NULL;
  	menu->shadow_panel = NULL;
  
  	menu->config = barcfg;
  	menu->menu_items = menu_items;
  	menu->cursor_row = 1;
  	menu->first_row = 1;
  	menu->active_submenu = NULL;
  
  	menu->is_menubar = true;
  	menu->mouse_row = -1;
  
  	wbkgd(menu->window, COLOR_PAIR(barcfg->menu_background_cpn) | barcfg->menu_background_attr);
  
  	menu_item = menu_items;
  	while (menu_item->text)
  	{
  		menu_fields += 1;
  
  		if (barcfg->text_space == -1)
  			aux_width += menutext_displaywidth(barcfg, menu_item->text, NULL, NULL);
  
  		menu_item += 1;
  	}
  
  	/*
  	 * last bar position is hypotetical - we should not to calculate length of last field
  	 * every time.
  	 */
  	menu->bar_fields_x_pos = safe_malloc(sizeof(int) * (menu_fields + 1));
  	menu->submenus = safe_malloc(sizeof(struct ST_MENU) * menu_fields);
  	menu->accelerators = safe_malloc(sizeof(ST_MENU_ACCELERATOR) * menu_fields);
  	menu->options = safe_malloc(sizeof(int) * menu_fields);
  	menu->refvals = safe_malloc(sizeof(int*) * menu_fields);
  
  	menu->nitems = menu_fields; 
  
  	/*
  	 * When text_space is not defined, then try to vallign menu items
  	 */
  	if (barcfg->text_space == -1)
  	{
  		text_space = (maxx + 1 - aux_width) / (menu_fields + 1);
  		if (text_space < 4)
  			text_space = 4;
  		else if (text_space > 15)
  			text_space = 15;
  		current_pos = text_space;
  	}
  	else
  	{
  		text_space = barcfg->text_space;
  		current_pos = barcfg->init_text_space;
  	}
  
  	/* Initialize submenu */
  	menu_item = menu_items; i = 0;
  	while (menu_item->text)
  	{
  		char	*accelerator;
  
  		menu->bar_fields_x_pos[i] = current_pos;
  		current_pos += menutext_displaywidth(barcfg, menu_item->text, &accelerator, NULL);
  		current_pos += text_space;
  		if (menu_item->submenu)
  		{
  			menu->submenus[i] = 
  					st_menu_new(pdcfg, menu_item->submenu,
  										1, menu->bar_fields_x_pos[i] + 
  										pdcfg->menu_bar_menu_offset
  										- (pdcfg->draw_box ? 1 : 0)
  										- (pdcfg->wide_vborders ? 1 : 0)
  										- (pdcfg->extra_inner_space ? 1 : 0) - 1, NULL);
  		}
  		else
  			menu->submenus[i] = NULL;
  
  		if (accelerator)
  		{
  			menu->accelerators[naccel].c = chr_casexfrm(barcfg, accelerator);
  			menu->accelerators[naccel].length = strlen(menu->accelerators[naccel].c);
  			menu->accelerators[naccel++].row = i + 1;
  		}
  
  		menu->naccelerators = naccel;
  
  		menu->options[i] = menu_item->options;
  		menu->refvals[i] = NULL;
  
  		menu_item += 1;
  		i += 1;
  	}
  
  	/*
  	 * store hypotetical x bar position
  	 */
  	menu->bar_fields_x_pos[i] = current_pos;
  
  	return menu;
  }
  
  struct ST_MENU *
  st_menu_new_menubar(ST_MENU_CONFIG *config, ST_MENU_ITEM *menu_items)
  {
  	return st_menu_new_menubar2(config, NULL, menu_items);
  }
  
  /*
   * Remove all objects allocated by menu and nested objects
   * it is workhorse for st_menu_free
   */
  static void
  _st_menu_free(struct ST_MENU *menu)
  {
  	int		i;
  
  	if (menu)
  	{
  		if (menu->submenus)
  		{
  			for (i = 0; i < menu->nitems; i++)
  				st_menu_free(menu->submenus[i]);
  
  			free(menu->submenus);
  		}
  
  		if (menu->accelerators)
  			free(menu->accelerators);
  
  		if (menu->shadow_panel)
  			del_panel(menu->shadow_panel);
  		if (menu->shadow_window)
  			delwin(menu->shadow_window);
  
  		del_panel(menu->panel);
  		delwin(menu->window);
  
  		free(menu);
  	}
  }
  
  void
  st_menu_free(struct ST_MENU *menu)
  {
  	_st_menu_free(menu);
  
  	update_panels();
  }
  
  /*
   * Returns active item and info about selecting of active item
   */
  ST_MENU_ITEM *
  st_menu_selected_item(bool *activated)
  {
  	/*
  	 * Activated can be true only when selected_item is valid
  	 */
  	if (selected_item)
  		*activated = press_accelerator || press_enter || button1_clicked;
  	else
  		*activated = false;
  
  	return selected_item;
  }
  
  /*
   * Set flag of first menu item specified by code
   */
  bool
  st_menu_enable_option(struct ST_MENU *menu, int code, int option)
  {
  	ST_MENU_ITEM *menu_items = menu->menu_items;
  	int		i = 0;
  
  	while (menu_items->text)
  	{
  		if (menu_items->code == code)
  		{
  			menu->options[i] |= option;
  			return true;
  		}
  
  		if (menu->submenus[i])
  			if (st_menu_enable_option(menu->submenus[i], code, option))
  				return true;
  
  		menu_items += 1;
  		i += 1;
  	}
  
  	return false;
  }
  
  /*
   * Reset flag of first menu item specified by code
   */
  bool
  st_menu_reset_option(struct ST_MENU *menu, int code, int option)
  {
  	ST_MENU_ITEM *menu_items = menu->menu_items;
  	int		i = 0;
  
  	while (menu_items->text)
  	{
  		if (menu_items->code == code)
  		{
  			menu->options[i] &= ~option;
  			return true;
  		}
  
  		if (menu->submenus[i])
  			if (st_menu_reset_option(menu->submenus[i], code, option))
  				return true;
  
  		menu_items += 1;
  		i += 1;
  	}
  
  	return false;
  }
  
  /*
   * Reset flag of first menu item specified by code
   */
  bool
  st_menu_reset_all_options(struct ST_MENU *menu, int option)
  {
  	ST_MENU_ITEM *menu_items = menu->menu_items;
  	int		i = 0;
  
  	while (menu_items->text)
  	{
  		menu->options[i] &= ~option;
  
  		if (menu->submenus[i])
  			st_menu_reset_all_options(menu->submenus[i], option);
  
  		menu_items += 1;
  		i += 1;
  	}
  
  	return false;
  }
  
  
  /*
   * Reset flag of first menu item specified by code
   */
  bool
  st_menu_reset_all_submenu_options(struct ST_MENU *menu, int menu_code, int option)
  {
  	ST_MENU_ITEM *menu_items = menu->menu_items;
  	int		i = 0;
  
  	while (menu_items->text)
  	{
  		if (menu->submenus[i])
  		{
  			if (menu_items->code == menu_code)
  			{
  				st_menu_reset_all_options(menu->submenus[i], option);
  				return true;
  			}
  
  			if (st_menu_reset_all_submenu_options(menu->submenus[i], menu_code, option))
  				return true;
  		}
  
  		menu_items += 1;
  		i += 1;
  	}
  
  	return false;
  }
  
  /*
   * Set flag of first menu item specified by code
   */
  bool
  st_menu_set_option(struct ST_MENU *menu, int code, int option, bool value)
  {
  	ST_MENU_ITEM *menu_items = menu->menu_items;
  	int		i = 0;
  
  	while (menu_items->text)
  	{
  		if (menu_items->code == code)
  		{
  			if (value)
  				menu->options[i] |= option;
  			else
  				menu->options[i] &= ~option;
  
  			return true;
  		}
  
  		if (menu->submenus[i])
  			if (st_menu_set_option(menu->submenus[i], code, option, value))
  				return true;
  
  		menu_items += 1;
  		i += 1;
  	}
  
  	return false;
  }
  
  /*
   * Reduce string to expected display width. The buffer should be
   * preallocated on good enough length - size of src.
   */
  static void
  reduce_string(ST_MENU_CONFIG *config, int display_width, char *dest, char *src)
  {
  	int current_width = str_width(config, src);
  	int		char_count = 0;
  
  	while (src && display_width > 0)
  	{
  		if (current_width <= display_width)
  		{
  			strcpy(dest, src);
  			return;
  		}
  		else
  		{
  			int		chrlen = char_length(config, src);
  			int		dw = char_width(config, src);
  
  			if (char_count < 2)
  			{
  				memcpy(dest, src, chrlen);
  				dest += chrlen;
  				display_width -= dw;
  			}
  			else if (char_count == 2)
  			{
  				*dest++ = '~';
  				display_width -= 1;
  			}
  
  			char_count += 1;
  			current_width -= dw;
  			src += chrlen;
  		}
  	}
  
  	*dest = '\0';
  }
  
  static void
  cmdbar_draw(struct ST_CMDBAR *cmdbar)
  {
  	ST_MENU_CONFIG *config = cmdbar->config;
  	int		i;
  
  	show_panel(cmdbar->panel);
  	top_panel(cmdbar->panel);
  
  	update_panels();
  
  	werase(cmdbar->window);
  
  	if (config->funckey_bar_style)
  	{
  		for (i = 0; i < cmdbar->nitems; i++)
  		{
  			wmove(cmdbar->window, 0, cmdbar->positions[i]);
  			wattron(cmdbar->window,
  					  COLOR_PAIR(config->cursor_cpn) | config->cursor_attr);
  
  			wprintw(cmdbar->window, "%2d", i+1);
  
  			wattroff(cmdbar->window,
  					  COLOR_PAIR(config->cursor_cpn) | config->cursor_attr);
  
  			if (cmdbar->labels[i])
  				waddstr(cmdbar->window, cmdbar->labels[i]);
  		}
  	}
  	else
  	{
  		for (i = 0; i < cmdbar->nitems; i++)
  		{
  			bool	need_sep = false;
  			bool	marked = false;
  			int		accel_prop;
  			int		text_prop;
  
  			marked = &cmdbar->cmdbar_items[i] == selected_command && !command_was_activated;
  
  			if (marked)
  			{
  				mvwchgat(cmdbar->window, 0,
  							cmdbar->positions[i] - 1,
  							cmdbar->positions[i+1] - config->text_space + 1 - cmdbar->positions[i] + 1,
  							config->cursor_attr,
  							config->cursor_cpn, NULL);
  
  				accel_prop = COLOR_PAIR(config->cursor_accel_cpn) | config->cursor_accel_attr;
  				text_prop = COLOR_PAIR(config->cursor_cpn) | config->cursor_attr;
  			}
  			else
  			{
  
  				accel_prop = COLOR_PAIR(config->accelerator_cpn) | config->accelerator_attr;
  				text_prop = COLOR_PAIR(config->menu_unfocused_cpn) | config->menu_unfocused_attr;
  			}
  
  			wmove(cmdbar->window, 0, cmdbar->positions[i]);
  
  			wattron(cmdbar->window, accel_prop);
  
  			if (cmdbar->cmdbar_items[i].alt)
  			{
  				need_sep = true;
  				waddstr(cmdbar->window, "M-");
  			}
  			if (cmdbar->cmdbar_items[i].fkey > 0)
  			{
  				need_sep = true;
  				wprintw(cmdbar->window, "F%d", cmdbar->cmdbar_items[i].fkey);
  			}
  
  			wattroff(cmdbar->window, accel_prop);
  
  			wattron(cmdbar->window, text_prop);
  
  			if (need_sep)
  				waddstr(cmdbar->window, " ");
  
  			waddstr(cmdbar->window, cmdbar->cmdbar_items[i].text);
  
  			wattroff(cmdbar->window, text_prop);
  		}
  	}
  
  	wnoutrefresh(cmdbar->window);
  }
  
  static bool
  cmdbar_driver(struct ST_CMDBAR *cmdbar, int c, bool alt, MEVENT *mevent)
  {
  	ST_CMDBAR_ITEM *cmdbar_item = cmdbar->cmdbar_items;
  	ST_MENU_CONFIG *config = cmdbar->config;
  
  	if (c == KEY_MOUSE &&
  		mevent->bstate & (BUTTON1_PRESSED | BUTTON1_RELEASED
  
  #if NCURSES_MOUSE_VERSION > 1
  
  						  | REPORT_MOUSE_POSITION
  
  #endif
  	  ))
  	{
  		int		y = mevent->y;
  		int		x = mevent->x;
  		int		i;
  
  		if (!wenclose(cmdbar->window, y, x))
  		{
  			command_was_activated = true;
  			return false;
  		}
  
  		for (i = 0; i < cmdbar->nitems; i++)
  		{
  			int		begin_x = i > 0 ? cmdbar->positions[i] : 0;
  			int		next_begin_x = cmdbar->positions[i + 1];
  
  			if (!config->funckey_bar_style)
  			{
  				begin_x -= 1;
  				next_begin_x -= 1;
  			}
  
  			if (begin_x <= x && x < next_begin_x)
  			{
  				if (config->funckey_bar_style)
  				{
  					if (cmdbar->labels[i])
  					{
  						/*
  						 * This design is not exact, but it is good enough.
  						 * The click is valid, when press and release is over same
  						 * object.
  						 */
  						if (mevent->bstate & BUTTON1_PRESSED)
  						{
  							command_was_activated = false;
  							selected_command = cmdbar->ordered_items[i];
  							return true;
  						}
  						else if (mevent->bstate & BUTTON1_RELEASED)
  						{
  							if (selected_command == cmdbar->ordered_items[i])
  							{
  								command_was_activated = true;
  								return true;
  							}
  						}
  					}
  				}
  				else
  				{
  					if (mevent->bstate & BUTTON1_PRESSED)
  					{
  						command_was_activated = false;
  						selected_command = &cmdbar->cmdbar_items[i];
  						return true;
  					}
  					else if (mevent->bstate & BUTTON1_RELEASED)
  					{
  						if (selected_command == &cmdbar->cmdbar_items[i])
  						{
  							command_was_activated = true;
  							return true;
  						}
  					}
  				}
  			}
  		}
  
  		selected_command = NULL;
  		return true;
  	}
  	else
  	{
  		while (cmdbar_item->text)
  		{
  			if (cmdbar_item->alt == alt && KEY_F(cmdbar_item->fkey) == c)
  			{
  				command_was_activated = true;
  				selected_command = cmdbar_item;
  				return true;
  			}
  			cmdbar_item += 1;
  		}
  	}
  
  	return false;
  }
  
  ST_CMDBAR_ITEM *
  st_menu_selected_command(bool *activated)
  {
  	*activated = selected_command != NULL && command_was_activated;
  
  	return selected_command;
  }
  
  /*
   * Create state variable for commandbar. It based on template - a array of ST_CMDBAR_ITEM fields.
   */
  struct ST_CMDBAR *
  st_cmdbar_new(ST_MENU_CONFIG *config, ST_CMDBAR_ITEM *cmdbar_items)
  {
  	struct ST_CMDBAR *cmdbar;
  	ST_CMDBAR_ITEM *cmdbar_item;
  	int		maxy, maxx, tmpy;
  	int		i;
  	int		last_position;
  
  	cmdbar = safe_malloc(sizeof(struct ST_CMDBAR));
  
  	cmdbar->cmdbar_items = cmdbar_items;
  	cmdbar->config = config;
  
  	getmaxyx(stdscr, maxy, maxx);
  
  	tmpy = 1;
  	cmdbar->window = newwin2(&tmpy, &maxx, maxy - 1, 0);
  	cmdbar->panel = new_panel(cmdbar->window);
  
  	wbkgd(cmdbar->window,
  					  COLOR_PAIR(config->menu_unfocused_cpn) |
  					  config->menu_unfocused_attr);
  
  	werase(cmdbar->window);
  
  	cmdbar->nitems = 0;
  
  	cmdbar_item = cmdbar_items;
  
  	if (!config->funckey_bar_style)
  	{
  		while (cmdbar_item->text)
  		{
  			cmdbar->nitems += 1;
  			cmdbar_item += 1;
  		}
  	}
  	else
  		cmdbar->nitems = 10;
  
  	cmdbar->positions = safe_malloc(sizeof(int) * (cmdbar->nitems + 1));
  	cmdbar->labels = safe_malloc(sizeof(char*) * cmdbar->nitems);
  	cmdbar->ordered_items = safe_malloc(sizeof(ST_CMDBAR_ITEM *) * cmdbar->nitems);
  	last_position = 0;
  
  	if (config->funckey_bar_style)
  	{
  		int		width = maxx / 10;
  		double	extra_width = (maxx % 10) / 10.0;
  		double	extra_width_sum = 0;
  
  		if (width < 7)
  		{
  			/* when terminal is too thin, don't show all fields */
  			cmdbar->nitems = maxx / 7;
  
  			width = maxx / cmdbar->nitems;
  			extra_width = (maxx % cmdbar->nitems) / (cmdbar->nitems * 1.0);
  			extra_width_sum = 0;
  		}
  
  		for (i = 0; i < cmdbar->nitems; i++)
  		{
  			cmdbar->positions[i] = last_position;
  			last_position += width;
  			extra_width_sum += extra_width;
  			if (extra_width_sum > 1.0)
  			{
  				last_position += 1;
  				extra_width_sum -= 1;
  			}
  		}
  
  		cmdbar->positions[cmdbar->nitems] = maxx + 1;
  
  		cmdbar_item = cmdbar_items;
  		while (cmdbar_item->text)
  		{
  			int		fkey = cmdbar_item->fkey;
  			int		display_width;
  
  			if (cmdbar_item->alt)
  			{
  				endwin();
  				fprintf(stderr, "Alt is not supported in funckey bar style");
  				exit(1);
  			}
  
  			if (fkey < 1 || fkey > 10)
  			{
  				endwin();
  				fprintf(stderr, "fkey code should be between 1 and 10");
  				exit(1);
  			}
  
  			/* don't display keys in reduced bar */
  			if (fkey > cmdbar->nitems)
  			{
  				cmdbar_item += 1;
  				continue;
  			}
  
  			if (cmdbar->labels[fkey - 1])
  			{
  				endwin();
  				fprintf(stderr, "multiple assigned items inside funckey bar");
  				exit(1);
  			}
  
  			cmdbar->ordered_items[fkey - 1] = cmdbar_item;
  
  			display_width = cmdbar->positions[fkey] - cmdbar->positions[fkey - 1] - 2;
  			cmdbar->labels[fkey - 1] = safe_malloc(strlen(cmdbar_item->text) + 1);
  			reduce_string(config, display_width, cmdbar->labels[fkey - 1], cmdbar_item->text);
  
  			cmdbar_item += 1;
  		}
  	}
  	else
  	{
  		last_position = config->init_text_space;
  
  		for (i = 0; i < cmdbar->nitems; i++)
  		{
  			cmdbar_item = &cmdbar_items[i];
  
  			cmdbar->positions[i] = last_position;
  
  			if (cmdbar_item->alt)
  				last_position += strlen("M-");
  			if (cmdbar_item->fkey > 0)
  				last_position += strlen("Fx");
  			if (cmdbar_item->fkey > 9)
  				last_position += strlen("0");
  
  			if (cmdbar->positions[i] != last_position)
  				last_position += 1;
  
  			last_position += str_width(config, cmdbar_item->text);
  			last_position += config->text_space != -1 ? config->text_space : 3;
  		}
  
  		cmdbar->positions[cmdbar->nitems] = last_position;
  	}
  
  	return cmdbar;
  }
  
  void
  st_cmdbar_post(struct ST_CMDBAR *cmdbar)
  {
  	active_cmdbar = cmdbar;
  	cmdbar_draw(cmdbar);
  }
  
  void
  st_cmdbar_unpost(struct ST_CMDBAR *cmdbar)
  {
  	active_cmdbar = NULL;
  
  	hide_panel(cmdbar->panel);
  	update_panels();
  }
  
  void
  st_cmdbar_free(struct ST_CMDBAR *cmdbar)
  {
  	int		i;
  
  	active_cmdbar = NULL;
  
  	del_panel(cmdbar->panel);
  	delwin(cmdbar->window);
  
  	for (i = 0; i < cmdbar->nitems; i++)
  		if (cmdbar->labels[i])
  			free(cmdbar->labels[i]);
  
  	free(cmdbar->labels);
  	free(cmdbar->positions);
  	free(cmdbar->ordered_items);
  	free(cmdbar);
  
  	update_panels();
  }
  
  /*
   * Set reference to some variable for menu's option
   */
  bool
  st_menu_set_ref_option(struct ST_MENU *menu,
  					   int code,
  					   int option,
  					   int *refvalue)
  {
  	ST_MENU_ITEM *menu_items = menu->menu_items;
  	int		i = 0;
  
  	if (!IS_REF_OPTION(option))
  	{
  		endwin();
  		fprintf(stderr, "cannot assign reference value with not reference option");
  		exit(1);
  	}
  
  	while (menu_items->text)
  	{
  		if (menu_items->code == code)
  		{
  			menu->options[i] |= option;
  			menu->refvals[i] = refvalue;
  
  			return true;
  		}
  
  		if (menu->submenus[i])
  			if (st_menu_set_ref_option(menu->submenus[i], code, option, refvalue))
  				return true;
  
  		menu_items += 1;
  		i += 1;
  	}
  
  	return false;
  }
  
  /*
   * Set shortcut for option specified by code
   */
  bool
  st_menu_set_shortcut(struct ST_MENU *menu, int code, char *shortcut)
  {
  	ST_MENU_ITEM *menu_items = menu->menu_items;
  	int		i = 0;
  
  	while (menu_items->text)
  	{
  		if (menu_items->code == code)
  		{
  			menu_items->shortcut = shortcut;
  			return true;
  		}
  
  		if (menu->submenus[i])
  			if (st_menu_set_shortcut(menu->submenus[i], code, shortcut))
  				return true;
  
  		menu_items += 1;
  		i += 1;
  	}
  
  	return false;
  }
  
  /*
   * Returns type of focus of menu
   */
  int
  st_menu_get_focus(struct ST_MENU *menu)
  {
  	return menu->focus;
  }
  