/*
 * Copyright (C) 2000-2023 the xine project
 *
 * This file is part of xine, a unix video player.
 *
 * xine 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.
 *
 * xine 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA
 *
 */
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <stdio.h>
#include <string.h>

#include "_xitk.h"
#include "menu.h"
#include "labelbutton.h"
#include "default_font.h"

#include "utils.h"

#undef DEBUG_MENU
#undef DUMP_MENU

#define _MENU_MAX_OPEN 8 /* xine needs just 3. */

typedef struct _menu_node_s _menu_node_t;
typedef struct _menu_private_s _menu_private_t;

typedef enum {
  _MENU_bevel_plain = 0,
  _MENU_bevel_arrow,
  _MENU_bevel_unchecked,
  _MENU_bevel_checked,
  _MENU_img_LAST
} _menu_img_t;

typedef struct {
  xitk_window_t        *xwin;
  xitk_register_key_t   key;
  _menu_private_t      *wp;
  _menu_node_t         *node;
  xitk_image_t         *img[_MENU_img_LAST];
} _menu_window_t;

#define _MENU_NODE_PLAIN 1
#define _MENU_NODE_SEP 2
#define _MENU_NODE_BRANCH 4
#define _MENU_NODE_CHECK 8
#define _MENU_NODE_CHECKED 16
#define _MENU_NODE_TITLE 32
#define _MENU_NODE_SHORTCUT 64
#define _MENU_NODE_HAS 8

struct _menu_node_s {
  xitk_dnode_t         node;
  _menu_node_t        *parent;
  xitk_dlist_t         branches;
  _menu_private_t     *wp;
  _menu_window_t      *menu_window;
  xitk_widget_t       *button;
  unsigned int         type;
  _menu_img_t          img_type;
  xitk_menu_entry_t    menu_entry;
};

/* A Click outside an open menu shall close it.
 * Unfortunately, we only hear about such clicks when they go into
 * one of our open normal windows. xitk.c still does this as a
 * safety fallback.
 * For most cases, we here try to guess this from a loss of menu
 * window focus that happens while mouse pointer is outside the
 * menu, and we are not switching focus through keyboard action. */

static const uint32_t _menu_mouse_over_flags[_MENU_MAX_OPEN] = {
  0x0001, 0x0002, 0x0004, 0x0008, 0x0010, 0x0020, 0x0040, 0x0080
};
static const uint32_t _menu_focus_flags[_MENU_MAX_OPEN] = {
  0x0100, 0x0200, 0x0400, 0x0800, 0x1000, 0x2000, 0x4000, 0x8000
};
#define _menu_keyboard_move_flag 0x10000

struct _menu_private_s {
  xitk_widget_t        w;
  xitk_t              *xitk;
  xitk_register_key_t  parent, before_cb, after_cb;
  _menu_node_t         root;
  int                  x, y;
  /* count of (sub)menus shown. */
  int                  num_open;
  /* _menu_*_flag* */
  uint32_t             mouse_over;
  xitk_menu_callback_t cb;
  void                *user_data;
  _menu_window_t       open_windows[_MENU_MAX_OPEN];
};

static void _menu_tree_free (_menu_node_t *root) {
  _menu_node_t *this = (_menu_node_t *)root->branches.tail.prev;
  while (this->node.prev) {
    _menu_node_t *prev = (_menu_node_t *)this->node.prev;
    xitk_dnode_remove (&this->node);
    _menu_tree_free (this);
    free (this);
    this = prev;
  }
}

static _menu_node_t *_menu_node_new (_menu_private_t *wp, const xitk_menu_entry_t *me, const char *name, size_t lname) {
  static const uint8_t type[XITK_MENU_ENTRY_LAST] = {
    [XITK_MENU_ENTRY_END]       = 0,
    [XITK_MENU_ENTRY_PLAIN]     = _MENU_NODE_PLAIN,
    [XITK_MENU_ENTRY_SEPARATOR] = _MENU_NODE_SEP,
    [XITK_MENU_ENTRY_BRANCH]    = _MENU_NODE_BRANCH,
    [XITK_MENU_ENTRY_CHECK]     = _MENU_NODE_CHECK,
    [XITK_MENU_ENTRY_CHECKED]   = _MENU_NODE_CHECKED,
    [XITK_MENU_ENTRY_TITLE]     = _MENU_NODE_TITLE
  };
  static const uint8_t img_type[XITK_MENU_ENTRY_LAST] = {
    [XITK_MENU_ENTRY_END]       = _MENU_bevel_plain,
    [XITK_MENU_ENTRY_PLAIN]     = _MENU_bevel_plain,
    [XITK_MENU_ENTRY_SEPARATOR] = _MENU_bevel_plain,
    [XITK_MENU_ENTRY_BRANCH]    = _MENU_bevel_arrow,
    [XITK_MENU_ENTRY_CHECK]     = _MENU_bevel_unchecked,
    [XITK_MENU_ENTRY_CHECKED]   = _MENU_bevel_checked,
    [XITK_MENU_ENTRY_TITLE]     = _MENU_bevel_plain
  };
  _menu_node_t *node;
  size_t lshort = (me->shortcut ? xitk_find_byte (me->shortcut, 0) : 0) + 1;
  char *s;

  lname += 1;
  s = (char *)xitk_xmalloc (sizeof (_menu_node_t) + lname + lshort);
  if (!s)
    return NULL;
  node = (_menu_node_t *)s;
  s += sizeof (*node);

  node->menu_entry.menu = s;
  memcpy (s, name, lname);
  s += lname;

  node->menu_entry.type = me->type;
  {
    uint32_t _type = (me->type >= XITK_MENU_ENTRY_LAST) ? XITK_MENU_ENTRY_PLAIN : me->type;
    node->type = type[_type];
    node->img_type = img_type[_type];
  }

  if (me->shortcut) {
    node->menu_entry.shortcut = s;
    memcpy (s, me->shortcut, lshort);
    s += lshort;
    node->type |= _MENU_NODE_SHORTCUT;
  } else {
    node->menu_entry.shortcut = NULL;
  }

  node->menu_entry.user_id   = me->user_id;

  node->node.next = NULL;
  node->node.prev = NULL;
  node->parent = NULL;
  xitk_dlist_init (&node->branches);
  node->menu_window = NULL;
  node->button = NULL;
  node->wp = wp;
  return node;
}

void xitk_menu_add_entry (xitk_widget_t *w, const xitk_menu_entry_t *me) {
  static const uint8_t tab_char[256] = {
    [0]    = 1,
    ['/']  = 2,
    ['\\'] = 4
  };
  _menu_private_t *wp;
  _menu_node_t *here;
  char buf[400], *e = buf + sizeof (buf) - 1;
  const uint8_t *p;

  xitk_container (wp, w, w);
  if (!w || !me)
    return;
  if ((wp->w.type & WIDGET_TYPE_MASK) != WIDGET_TYPE_MENU)
    return;

  here = &wp->root;
  p = (const uint8_t *)(me->menu ? me->menu : "");

  while (p[0]) {
    _menu_node_t *scan;
    char *q = buf;
    int found;

    while (1) {
      while ((q < e) && !tab_char[p[0]])
        *q++ = *p++;
      if ((q >= e) || (p[0] != '\\'))
        break;
      if (*++p)
        *q++ = *p++;
    }
    *q = 0;
    while (!(tab_char[p[0]] & (1 + 2)))
      p += 1;

    found = 0;
    if (p[0] == '/') {
      /* merge branchhes */
      for (scan = (_menu_node_t *)here->branches.tail.prev; scan->node.prev; scan = (_menu_node_t *)scan->node.prev) {
        if (!strcmp (scan->menu_entry.menu, buf))
          break;
      }
      if (scan->node.prev)
        found = 1;
    }
    if (found) {
      here = scan;
    } else { /* new */
      static const xitk_menu_entry_t dummy = {
        .type = XITK_MENU_ENTRY_BRANCH
      };
      const xitk_menu_entry_t *_me = p[0] ? &dummy : me;
      _menu_node_t *add = _menu_node_new (wp, _me, buf, q - buf);
      if (!add)
        break;
      add->parent = here;
      xitk_dlist_add_tail (&here->branches, &add->node);
      here->type |= (add->type << _MENU_NODE_HAS) | _MENU_NODE_BRANCH;
      here = add;
    }
    if (p[0] == '/')
      p += 1;
  }
}

static void _menu_close_from (_menu_private_t *wp, int from) {
  int i;

  if (from >= wp->num_open)
    return;

  /* revert focus first and prevent annoying window manager flashing. */
  if (from > 0) {
    _menu_window_t *mw = wp->open_windows + from - 1;
    if (xitk_window_flags (mw->xwin, 0, 0) & XITK_WINF_VISIBLE)
      xitk_window_set_input_focus (mw->xwin);
  }

  for (i = wp->num_open - 1; i >= from; i--) {
    _menu_window_t *mw = wp->open_windows + i;
    wp->mouse_over &= ~(_menu_mouse_over_flags[i] + _menu_focus_flags[i]);
    /* now, close the window. */
    xitk_unregister_event_handler (mw->wp->xitk, &mw->key);
    xitk_image_free_image (&mw->img[_MENU_bevel_plain]);
    xitk_image_free_image (&mw->img[_MENU_bevel_arrow]);
    xitk_image_free_image (&mw->img[_MENU_bevel_unchecked]);
    xitk_image_free_image (&mw->img[_MENU_bevel_checked]);
    xitk_window_destroy_window (mw->xwin);
    mw->xwin = NULL;
    mw->node->menu_window = NULL;
  }

  wp->num_open = from;
}

static void _menu_close_subs_in (_menu_private_t *wp, _menu_node_t *node) {
  if (!node)
    node = &wp->root;
  if (node->menu_window
    && (node->menu_window >= wp->open_windows)
    && (node->menu_window < wp->open_windows + wp->num_open))
    _menu_close_from (wp, node->menu_window - wp->open_windows);
}

static void _menu_close_subs_ex (_menu_private_t *wp, _menu_node_t *node) {
  if (!node)
    node = &wp->root;
  if (node->menu_window
    && (node->menu_window >= wp->open_windows)
    && (node->menu_window < wp->open_windows + wp->num_open))
    _menu_close_from (wp, node->menu_window - wp->open_windows + 1);
}

static void _menu_exit (_menu_private_t *wp) {
  /* 핵: this runs inside xitk_destroy_widget ().
   * dont destroy again through xitk_set_current_menu (NULL). */
  xitk_unset_current_menu (wp->xitk);
  _menu_close_subs_in (wp, NULL);
  _menu_tree_free (&wp->root);
  /* revert focus if not a new window. */
  if (wp->before_cb == wp->after_cb)
    xitk_set_focus_key (wp->xitk, wp->parent, 1);
}

static void _menu_open (_menu_node_t *branch, int x, int y);

static void _menu_click_cb (xitk_widget_t *w, void *data, int state) {
  _menu_node_t *me = (_menu_node_t *)data;
  _menu_private_t *wp = me->wp;

  (void)w;
  (void)state;
  if (me->type & _MENU_NODE_BRANCH) {
    if (me->branches.head.next != &me->branches.tail) {
      if (!me->menu_window) {
        xitk_rect_t wr = {0, 0, 0, 0};
        int x = 0, y = 0;

        wp->mouse_over |= _menu_keyboard_move_flag;

        xitk_window_get_window_position (me->parent->menu_window->xwin, &wr);
        xitk_get_widget_pos (me->button, &x, &y);

        x += xitk_get_widget_width (me->button) + wr.x;
        y += wr.y;

        _menu_close_subs_ex (wp, me->parent);
        _menu_open (me, x, y);
      } else {
        if (!(wp->mouse_over & _menu_focus_flags[me->menu_window - wp->open_windows]))
          wp->mouse_over |= _menu_keyboard_move_flag;
        /* xitk_window_raise_window (me->menu_window->xwin); */
        xitk_window_set_input_focus (me->menu_window->xwin);
      }
    } else {
      wp->mouse_over |= _menu_keyboard_move_flag;
      _menu_close_subs_ex (wp, me);
    }
  } else if (!(me->type & (_MENU_NODE_TITLE | _MENU_NODE_SEP))) {
    xitk_widget_t *ww;

    _menu_close_subs_in (wp, NULL);
    /* 핵: detach from parent window. it may go away in user callback,
     * eg when switching fullscreen mode. */
    xitk_dnode_remove (&wp->w.node);
    xitk_unset_current_menu (wp->xitk);
    wp->w.wl = NULL;
    if (me->wp->cb) {
      /* if user callback did open a new window, let it keep its focus in
       * xitk_destroy_widget () -> _menu_exit (). */
      wp->before_cb = xitk_get_focus_key (wp->xitk);
      me->wp->cb (&wp->w, &me->menu_entry, me->wp->user_data);
      wp->after_cb = xitk_get_focus_key (wp->xitk);
    }
    ww = &wp->w;
    xitk_widgets_delete (&ww, 1);
  }
}

static int _menu_event (void *data, const xitk_be_event_t *e) {
  _menu_window_t *mw = data;
  _menu_private_t *wp = mw->wp;
  int level = mw - wp->open_windows;

  switch (e->type) {
    case XITK_EV_DEL_WIN:
      wp->mouse_over &= ~(_menu_mouse_over_flags[level] + _menu_focus_flags[level]);
      break;
    case XITK_EV_ENTER:
      wp->mouse_over |= _menu_mouse_over_flags[level];
      break;
    case XITK_EV_LEAVE:
      wp->mouse_over &= ~_menu_mouse_over_flags[level];
      break;
    case XITK_EV_FOCUS:
      wp->mouse_over |= _menu_focus_flags[level];
      break;
    case XITK_EV_UNFOCUS:
      wp->mouse_over &= ~_menu_focus_flags[level];
      if (!wp->mouse_over) {
        /* click outside all menus, not necessarily into one of our windows. */
        xitk_widget_t *w = &wp->w;
        if (wp->xitk->verbosity >= 2)
          printf ("xitk.menu.click_outside.\n");
        xitk_widgets_delete (&w, 1);
        return 1;
      }
      wp->mouse_over &= ~_menu_keyboard_move_flag;
      break;
    case XITK_EV_KEY_DOWN:
      if (e->utf8[0] != XITK_CTRL_KEY_PREFIX)
        break;
      /* switch to keyboard mode there. */
      switch (e->utf8[1]) {
        case XITK_KEY_LEFT:
          if (!level)
            break;
          /* fall through */
        case XITK_KEY_ESCAPE:
          wp->mouse_over |= _menu_keyboard_move_flag;
          if (level) {
            xitk_set_focus_to_widget (mw->node->button);
            _menu_close_subs_in (wp, mw->node);
          } else {
            xitk_widget_t *w = &wp->w;
            xitk_widgets_delete (&w, 1);
          }
          return 1;
        case XITK_KEY_UP:
          xitk_set_focus_to_next_widget (xitk_window_widget_list (mw->xwin), 1, e->qual);
          return 1;
        case XITK_KEY_DOWN:
          xitk_set_focus_to_next_widget (xitk_window_widget_list (mw->xwin), 0, e->qual);
          return 1;
        default: ;
      }
      break;
    default: ;
  }
  return 0;
}

static void _menu_open (_menu_node_t *node, int x, int y) {
  _menu_private_t *wp = node->wp;
  int              bentries, bsep;
  _menu_node_t    *me;
  int              maxlen, wwidth, wheight;
  int              shortcutlen, shortcutpos, shortcuts_enable;
  xitk_window_t   *xwin;
  _menu_window_t  *mw;
  xitk_image_t    *bg;

  if (wp->num_open >= _MENU_MAX_OPEN)
    return;
  if (wp->num_open > 0) {
    if (node->parent != wp->open_windows[wp->num_open - 1].node)
      return;
  } else {
    if (node->parent != NULL)
      return;
  }

  bentries = bsep = 0;
  maxlen = shortcutlen = 0;

  shortcuts_enable = xitk_get_cfg_num (wp->xitk, XITK_MENU_SHORTCUTS_ENABLE);
  if (shortcuts_enable && (node->type & (_MENU_NODE_SHORTCUT << _MENU_NODE_HAS))) {
    _menu_node_t *maxnode = NULL, *smaxnode = NULL;
    xitk_font_t *fs;

    for (maxnode = me = (_menu_node_t *)node->branches.head.next; me->node.next; me = (_menu_node_t *)me->node.next) {
      int len;
      bsep += !!(me->type & (_MENU_NODE_TITLE | _MENU_NODE_SEP));
      if (me->type & _MENU_NODE_SEP)
        continue;
      bentries += 1;
      /* FIXME: non fixed width font? */
      len = xitk_find_byte (me->menu_entry.menu, 0);
      if (len > maxlen) {
        maxlen = len;
        maxnode = me;
      }
      if (me->menu_entry.shortcut) {
        /* FIXME: non fixed width font? */
        len = xitk_find_byte (me->menu_entry.shortcut, 0);
        if (len > shortcutlen) {
          shortcutlen = len;
          smaxnode = me;
        }
      }
    }

    if (maxnode) {
      fs = xitk_font_load_font (wp->xitk, (maxnode->type & _MENU_NODE_TITLE) ? DEFAULT_BOLD_FONT_14 : DEFAULT_BOLD_FONT_12);
      maxlen = xitk_font_get_string_length (fs, maxnode->menu_entry.menu);
      xitk_font_unload_font (fs);
    }
    if (smaxnode) {
      fs = xitk_font_load_font (wp->xitk, DEFAULT_FONT_12);
      shortcutlen = xitk_font_get_string_length (fs, smaxnode->menu_entry.shortcut);
      xitk_font_unload_font (fs);
    }
    maxlen += shortcutlen + 15;

  } else {
    _menu_node_t *maxnode = NULL;
    xitk_font_t *fs;

    for (maxnode = me = (_menu_node_t *)node->branches.head.next; me->node.next; me = (_menu_node_t *)me->node.next) {
      int len;
      bsep += !!(me->type & (_MENU_NODE_TITLE | _MENU_NODE_SEP));
      if (me->type & _MENU_NODE_SEP)
        continue;
      bentries += 1;
      /* FIXME: non fixed width font? */
      len = xitk_find_byte (me->menu_entry.menu, 0);
      if (len > maxlen) {
        maxlen = len;
        maxnode = me;
      }
    }

    fs = xitk_font_load_font (wp->xitk, (maxnode->type & _MENU_NODE_TITLE) ? DEFAULT_BOLD_FONT_14 : DEFAULT_BOLD_FONT_12);
    maxlen = xitk_font_get_string_length (fs, maxnode->menu_entry.menu);
    xitk_font_unload_font (fs);
  }

  /* always reserve leftmost 20px checkmark space, looks better. */
  wwidth = maxlen + 40;
  shortcutpos = wwidth - shortcutlen - 15;
  if (node->type & (_MENU_NODE_BRANCH << _MENU_NODE_HAS))
    wwidth += 10;
  wheight = bentries * 20 + bsep * 2;


  {
    int swidth = 0, sheight = 0;
    xitk_get_display_size (wp->xitk, &swidth, &sheight);

    if (node->parent) {
      x -= 4; /* Overlap parent menu but leave text and symbols visible */
      y -= 1; /* Top item of submenu in line with parent item */
    } else {
      x++; y++; /* Upper left corner 1 pix distance to mouse pointer */
    }

    /* Check if menu fits on screen and adjust position if necessary in a way
     * that it doesn't obscure the parent menu or get under the mouse pointer. */
    if ((x + (wwidth + 2)) > swidth) {
      /* Exceeds right edge of screen */
      if (node->parent) {
        /* Align right edge of submenu to left edge of parent item */
        x -= xitk_get_widget_width (node->button) + (wwidth + 2) - 4;
      } else {
        /* Align right edge of top level menu to right edge of screen */
        x  = swidth - (wwidth + 2);
      }
    }
    if ((y + (wheight + 2)) > sheight) {
      /* Exceeds bottom edge of screen */
      if (node->parent) {
        /* Align bottom edge of submenu for bottom item in line with parent item */
        y -= wheight - xitk_get_widget_height (node->button);
      } else {
        /* Align bottom edge of top level menu to requested (i.e. pointer) pos */
        y -= (wheight + 2) + 1;
      }
    }
  }

  xwin = xitk_window_create_simple_window_ext (wp->xitk,
    x, y, wwidth + 2, wheight + 2, NULL, NULL, NULL, 1, 1, NULL);
  if (!xwin)
    return;

  bg = bsep ? xitk_window_get_background_image (xwin) : NULL;

  mw = wp->open_windows + wp->num_open;
  wp->num_open += 1;

  mw->xwin = xwin;
  mw->wp = wp;
  mw->node = node;
  node->menu_window = mw;

  mw->img[_MENU_bevel_plain] = NULL;
  mw->img[_MENU_bevel_arrow] = NULL;
  mw->img[_MENU_bevel_unchecked] = NULL;
  mw->img[_MENU_bevel_checked] = NULL;

  if (node->type & (_MENU_NODE_PLAIN << _MENU_NODE_HAS)) {
    mw->img[_MENU_bevel_plain] = xitk_image_new (wp->xitk, NULL, 0, wwidth * 3, 20);
    xitk_image_draw_flat_three_state (mw->img[_MENU_bevel_plain]);
  }

  if (node->type & (_MENU_NODE_BRANCH << _MENU_NODE_HAS)) {
    mw->img[_MENU_bevel_arrow] = xitk_image_new (wp->xitk, NULL, 0, wwidth * 3, 20);
    xitk_image_draw_flat_three_state (mw->img[_MENU_bevel_arrow]);
    xitk_image_draw_menu_arrow_branch (mw->img[_MENU_bevel_arrow]);
  }

  if (node->type & (_MENU_NODE_CHECK << _MENU_NODE_HAS)) {
    mw->img[_MENU_bevel_unchecked] = xitk_image_new (wp->xitk, NULL, 0, wwidth * 3, 20);
    xitk_image_draw_flat_three_state (mw->img[_MENU_bevel_unchecked]);
    xitk_image_draw_menu_check (mw->img[_MENU_bevel_unchecked], 0);
  }

  if (node->type & (_MENU_NODE_CHECKED << _MENU_NODE_HAS)) {
    mw->img[_MENU_bevel_checked] = xitk_image_new (wp->xitk, NULL, 0, wwidth * 3, 20);
    xitk_image_draw_flat_three_state (mw->img[_MENU_bevel_checked]);
    xitk_image_draw_menu_check (mw->img[_MENU_bevel_checked], 1);
  }

  {
    xitk_skin_element_info_t  info;
    xitk_labelbutton_widget_t lb;
    xitk_widget_list_t *wl = xitk_window_widget_list (xwin);
    int yy;

    memset (&info, 0, sizeof (info));
    info.x                 = 1;
    info.visibility        = 0;
    info.enability         = 0;
    info.pixmap_name       = NULL;
    info.label_alignment   = ALIGN_LEFT;
    info.label_printable   = 1;
    info.label_staticity   = 0;
    info.label_color       =
    info.label_color_focus = XITK_NOSKIN_TEXT_NORM;
    info.label_color_click = XITK_NOSKIN_TEXT_INV;
    info.label_fontname    = (char *)DEFAULT_BOLD_FONT_12;

    lb.nw.wl                = wl;
    lb.nw.skin_element_name = NULL;
    lb.button_type       = CLICK_BUTTON;
    lb.align             = ALIGN_LEFT;
    lb.callback          = _menu_click_cb;
    lb.state_callback    = NULL;

    wl = xitk_window_widget_list (xwin);

    yy = 1;
    for (me = (_menu_node_t *)node->branches.head.next; me->node.next; me = (_menu_node_t *)me->node.next) {

      me->wp = wp;
      me->button = NULL;

      if (me->type & (_MENU_NODE_TITLE | _MENU_NODE_SEP)) {

        if ((me->type & _MENU_NODE_TITLE) && bg) {
          xitk_font_t *fs;
          int lbear, rbear, width, asc, des;
          unsigned int cfg, cbg;

          fs = xitk_font_load_font (wp->xitk, DEFAULT_BOLD_FONT_14);
          xitk_font_string_extent (fs, me->menu_entry.menu, &lbear, &rbear, &width, &asc, &des);
          cbg = xitk_color_db_get (wp->xitk, (140 << 16) + (140 << 8) + 140);
          cfg = xitk_color_db_get (wp->xitk, (255 << 16) + (255 << 8) + 255);
          xitk_image_fill_rectangle (bg, 1, yy, wwidth, 20, cbg);
          xitk_image_draw_string (bg, fs, 5, yy + ((20 + asc + des) >> 1) - des,
            me->menu_entry.menu, xitk_find_byte (me->menu_entry.menu, 0), cfg);
          xitk_font_unload_font (fs);
          yy += 20;
        }
        if (bg)
          xitk_image_draw_rectangular_box (bg, 3, yy, wwidth - 5, 2, DRAW_INNER | DRAW_LIGHT);
        yy += 2;

      } else {

        xitk_widget_t *btn;
        lb.label    = me->menu_entry.menu;
        lb.nw.userdata = me;
        info.y = yy;
        info.pixmap_img.image = mw->img[me->img_type];
        btn = xitk_info_labelbutton_create (&lb, &info);
        if (!btn)
          continue;
        xitk_dlist_add_tail (&wl->list, &btn->node);
        btn->type |= WIDGET_GROUP_MEMBER | WIDGET_GROUP_MENU | ((me->type & _MENU_NODE_BRANCH) ? WIDGET_KEEP_FOCUS : 0);
        me->button = btn;
        if (shortcuts_enable && me->menu_entry.shortcut)
          xitk_labelbutton_change_shortcut_label (btn, me->menu_entry.shortcut, shortcutpos, DEFAULT_FONT_12);
        xitk_labelbutton_set_label_offset (btn, 20);
        xitk_widgets_state (&btn, 1, XITK_WIDGET_STATE_ENABLE | XITK_WIDGET_STATE_VISIBLE, ~0u);
        yy += 20;

      }
    }
  }

  if (bg)
    xitk_window_set_background_image (xwin, bg);

  /* set up wndow type before showing it to minimize window manager action. */
  if (!(xitk_get_wm_type (wp->xitk) & WM_TYPE_KWIN))
    /* WINDOW_TYPE_MENU seems to be the natural choice. */
    xitk_window_set_wm_window_type (xwin, WINDOW_TYPE_MENU);
  else
    /* However, KWin has unacceptable behaviour for WINDOW_TYPE_MENU in  */
    /* our transient-for scheme: The transient-for window must be mapped */
    /* and the transient-for window or another transient window (incl.   */
    /* the menu itself) must have focus, otherwise it unmaps the menu.   */
    /* This causes menus not to be shown under many several conditions.  */
    /* WINDOW_TYPE_DOCK is definitely the right choice for KWin.         */
    xitk_window_set_wm_window_type (xwin, WINDOW_TYPE_DOCK);

  /* Set transient-for-hint to the immediate predecessor,     */
  /* so window stacking of submenus is kept upon raise/lower. */
  if (!node->parent) {
    xitk_window_set_transient_for_win (xwin, wp->w.wl->xwin);
  } else {
    xitk_window_set_role (xwin, XITK_WR_SUBMENU);
    xitk_window_set_transient_for_win (xwin, node->parent->menu_window->xwin);
  }
  {
    char name[32] = "xitk_menu_0";
    name[10] += wp->num_open;
    mw->key = xitk_be_register_event_handler (name, mw->xwin, _menu_event, mw, NULL, NULL);
  }

  xitk_window_flags (xwin, XITK_WINF_VISIBLE | XITK_WINF_ICONIFIED, XITK_WINF_VISIBLE);
  xitk_window_raise_window (xwin);
  xitk_window_set_input_focus (xwin);

  {
    xitk_widget_t *btn = (xitk_widget_t *) xitk_window_widget_list(xwin)->list.head.next;
    if (btn)
      xitk_set_focus_to_widget (btn);
  }
}

#ifdef DUMP_MENU
#ifdef	__GNUC__
#define prints(fmt, args...) do { int j; for(j = 0; j < i; j++) printf(" "); printf(fmt, ##args); } while(0)
#else
#define prints(...) do { int j; for(j = 0; j < i; j++) printf(" "); printf(__VA_ARGS__); } while(0)
#endif
#endif

static int menu_notify_event (xitk_widget_t *w, const widget_event_t *event) {
  _menu_private_t *wp;

  xitk_container (wp, w, w);
  if (!wp)
    return 0;
  if ((wp->w.type & WIDGET_TYPE_MASK) != WIDGET_TYPE_MENU)
    return 0;

  switch (event->type) {
    case WIDGET_EVENT_CLICK:
    case WIDGET_EVENT_INSIDE:
      return 1;
    case WIDGET_EVENT_DESTROY:
      _menu_exit (wp);
      return 1;
    default:
      return 0;
  }
  return 0;
}

void xitk_menu_auto_pop (xitk_widget_t *w) {
  _menu_node_t *me;

  if (!w)
    return;
  if ((w->type & (WIDGET_GROUP_MENU | WIDGET_TYPE_MASK)) != (WIDGET_GROUP_MENU | WIDGET_TYPE_LABELBUTTON))
    return;

  me = labelbutton_get_user_data (w);
  if (me->type & _MENU_NODE_BRANCH) {
    _menu_click_cb (w, me, 0);
  } else {
    _menu_close_subs_ex (me->wp, me->parent);
  }
}

void xitk_menu_show_menu (xitk_widget_t *w) {
  _menu_private_t *wp;

  xitk_container (wp, w, w);
  if (!wp)
    return;
  if ((wp->w.type & WIDGET_TYPE_MASK) != WIDGET_TYPE_MENU)
    return;

  wp->parent = xitk_get_focus_key (wp->xitk);
  wp->w.state |= XITK_WIDGET_STATE_VISIBLE;
  _menu_open (&wp->root, wp->x, wp->y);
  xitk_set_current_menu (wp->xitk, &wp->w);
}

xitk_widget_t *xitk_noskin_menu_create (const xitk_menu_widget_t *m, int x, int y) {
  _menu_private_t *wp;

  _xitk_new_widget_check (&m->nw);

  wp = (_menu_private_t *)xitk_widget_new (m->nw.wl, sizeof (*wp));
  if (!wp)
    return NULL;

  wp->xitk           = m->nw.wl->xitk;
  wp->w.type         = WIDGET_GROUP | WIDGET_TYPE_MENU | WIDGET_FOCUSABLE;

  wp->x              = x;
  wp->y              = y;
  wp->num_open       = 0;
  wp->mouse_over     = 0;

  wp->root.node.next   = NULL;
  wp->root.node.prev   = NULL;
  wp->root.parent      = NULL;
  wp->root.type        = 0;
  xitk_dlist_init (&wp->root.branches);
  wp->root.menu_window = NULL;
  wp->root.wp          = wp;
  wp->root.button      = NULL;
  wp->before_cb        =
  wp->after_cb         =
  wp->parent           = 0;
  wp->cb               = m->cb;
  wp->user_data        = m->nw.userdata;

  if (!m->menu_tree) {
    printf ("Empty menu entries. You will not .\n");
    abort ();
  }

  {
    const xitk_menu_entry_t *me;
    for (me = m->menu_tree; me->type != XITK_MENU_ENTRY_END; me++)
      if (me->menu)
        xitk_menu_add_entry (&wp->w, me);
  }

  wp->w.state &= ~XITK_WIDGET_STATE_VISIBLE;
  wp->w.event         = menu_notify_event;

  return &wp->w;
}

