/*-
 * Copyright (c) 1997-2002 The Protein Laboratory, University of Copenhagen
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 * $Id$
 */

/***********************************************************/
/*                                                         */
/*  System dependent menu routines (unix, x11)             */
/*                                                         */
/***********************************************************/

#include "unix/guts.h"
#include "Menu.h"
#include "Image.h"
#include "Window.h"
#include "Application.h"
#define XK_MISCELLANY
#define XK_LATIN1
#define XK_XKB_KEYS
#include <X11/keysymdef.h>

#define TREE            (PAbstractMenu(self)->tree)

static PMenuWindow
get_menu_window( Handle self, XWindow xw)
{
   DEFMM;
   PMenuWindow w = XX-> w;
   while (w && w->w != xw)
      w = w->  next;
   return w;
}

extern Cursor predefined_cursors[];

static PMenuWindow
get_window( Handle self, PMenuItemReg m)
{
   DEFMM;
   PMenuWindow w, wx;
   XSetWindowAttributes attrs;
   
   if ( !( w = malloc( sizeof( MenuWindow)))) return nil;
   bzero(w, sizeof( MenuWindow));
   w-> self = self;
   w-> m = m;
   w-> sz.x = -1;
   w-> sz.y = -1;
   attrs. event_mask = 0
      | KeyPressMask
      | KeyReleaseMask
      | ButtonPressMask
      | ButtonReleaseMask
      | EnterWindowMask
      | LeaveWindowMask
      | PointerMotionMask
      | ButtonMotionMask
      | KeymapStateMask
      | ExposureMask
      | VisibilityChangeMask
      | StructureNotifyMask
      | FocusChangeMask
      | PropertyChangeMask
      | ColormapChangeMask
      | OwnerGrabButtonMask;
   attrs. override_redirect = true;
   attrs. save_under = true;
   attrs. do_not_propagate_mask = attrs. event_mask;
   w->w = XCreateWindow( DISP, guts. root,
                         0, 0, 1, 1, 0, CopyFromParent,
                         InputOutput, CopyFromParent,
                         0
                         | CWOverrideRedirect
                         | CWEventMask
                         | CWSaveUnder
                         , &attrs);
   if (!w->w) {
      free(w);
      return nil;
   }
   XCHECKPOINT;
   XSetTransientForHint( DISP, w->w, None);
   hash_store( guts.menu_windows, &w->w, sizeof(w->w), (void*)self);
   wx = XX-> w;
   if ( predefined_cursors[crArrow] == None) {
      XCreateFontCursor( DISP, XC_left_ptr);
      XCHECKPOINT;
   }
   XDefineCursor( DISP, w-> w, predefined_cursors[crArrow]);
   if ( wx) {
      while ( wx-> next ) wx = wx-> next;
      w-> prev = wx;
      wx-> next = w;
   } else
      XX-> w = w;
   return w;
}

static int
item_count( PMenuWindow w)
{
   int i = 0;
   PMenuItemReg m = w->m;

   while (m) {
      i++; m=m->next;
   }
   return i;
}

static void
free_unix_items( PMenuWindow w)
{
   int i;
   if ( w-> um) {
      if ( w-> first < 0) {
         for ( i = 0; i < w->num; i++) 
            if ( w-> um[i].pixmap)
               XFreePixmap( DISP, w-> um[i].pixmap);
         free( w-> um);
      }
      w-> um = nil;
   }
   w-> num = 0;
}

static void
menu_window_delete_downlinks( PMenuSysData XX, PMenuWindow wx)
{
   PMenuWindow w = wx-> next;
   {
      XRectangle r;
      Region rgn;
      r. x = 0;
      r. y = 0;
      r. width  = guts. displaySize. x; 
      r. height = guts. displaySize. y; 
      rgn = XCreateRegion();
      XUnionRectWithRegion( &r, rgn, rgn);
      XSetRegion( DISP, guts. menugc, rgn);
      XDestroyRegion( rgn);
      XSetForeground( DISP, guts. menugc, XX->c[ciBack]);
   }
   while ( w) {
      PMenuWindow xw = w-> next;
      hash_delete( guts. menu_windows, &w-> w, sizeof( w-> w), false);
      XFillRectangle( DISP, w-> w, guts. menugc, 0, 0, w-> sz. x, w-> sz. y);
      XDestroyWindow( DISP, w-> w);
      XFlush( DISP);
      free_unix_items( w);
      free( w);
      w = xw;
   }
   wx-> next = nil;
   XX-> focused = wx;
}

#define MENU_XOFFSET 5
#define MENU_CHECK_XOFFSET 10

static int
get_text_width( PCachedFont font, const char * text, int byte_length, Bool utf8, uint32_t * xft_map8)
{
   int ret = 0;
   int char_len = utf8 ? utf8_length(( U8*) text, ( U8*) text + byte_length) : byte_length;
#ifdef USE_XFT
   if ( font-> xft)
      return prima_xft_get_text_width( font, text, char_len, false, utf8, xft_map8, nil);
#endif   
   if ( utf8) {
      XChar2b * xc = prima_alloc_utf8_to_wchar( text, char_len);
      if ( xc) {
         ret = XTextWidth16( font-> fs, xc, char_len);
         free( xc);
      }
   } else {
      ret = XTextWidth( font-> fs, text, byte_length);
   }
   return ret;
}

typedef struct {
   void        * xft_drawable;
   uint32_t    * xft_map8;
   Color         rgb;
   unsigned long pixel;
   XWindow       win;
   GC            gc;
} MenuDrawRec;

static void
get_font_abc( PCachedFont font, char * index, Bool utf8, FontABC * rec, MenuDrawRec * data)
{
   char buf[2];
   XCharStruct * cs;
#ifdef USE_XFT
   if ( font-> xft) {
      Point ovx;
      rec-> b = prima_xft_get_text_width( font, index, 1, false, 
                                          utf8, data-> xft_map8, &ovx);
      /* not really abc, but enough for its single invocation */
      rec-> a = -ovx. x;
      rec-> c = -ovx. y;
      return;
   }
#endif   
   if ( utf8)
      prima_utf8_to_wchar( index, ( XChar2b*) buf, 2, 1);
   else
      buf[0] = *index;
   cs = prima_char_struct( font-> fs, ( XChar2b*) buf, utf8);
   rec-> a = cs-> lbearing;
   rec-> b = cs-> rbearing - cs-> lbearing;
   rec-> c = cs-> width - cs-> rbearing;
}

static void
text_out( PCachedFont font, const char * text, int length, int x, int y, 
          Bool utf8, MenuDrawRec * data)
{
#ifdef USE_XFT
   XftColor xftcolor;
   if ( font-> xft) {
      xftcolor.color.red   = COLOR_R16(data-> rgb);
      xftcolor.color.green = COLOR_G16(data-> rgb);
      xftcolor.color.blue  = COLOR_B16(data-> rgb);
      xftcolor.color.alpha = 0xffff;
      xftcolor.pixel       = data-> pixel;
      XftDrawString32(( XftDraw*) data-> xft_drawable, &xftcolor, 
                      font-> xft, x, y, ( FcChar32*)text, length);
      return;
   } 
#endif   
   XSetForeground( DISP, data-> gc, data-> pixel);
   if ( utf8)
      XDrawString16( DISP, data->win, data->gc, x, y, ( XChar2b*) text, length);   
   else
      XDrawString( DISP, data->win, data->gc, x, y, text, length);   
}

static void
update_menu_window( PMenuSysData XX, PMenuWindow w)
{
   int x, y = 2 + 2, startx;
   Bool vertical = w != &XX-> wstatic;
   PMenuItemReg m = w->m;
   PUnixMenuItem ix;
   int lastIncOk = 1;
   PCachedFont kf = XX-> font;
   uint32_t *xft_map8 = nil;

#ifdef USE_XFT
   {
      PWindow owner = ( PWindow)(PComponent( w-> self)-> owner);
      const char * encoding = ( XX-> type. popup) ? 
         owner-> popupFont. encoding :
         owner-> menuFont. encoding;
      xft_map8 = prima_xft_map8( encoding);
   }
#endif      
      
   
   free_unix_items( w);
   w-> num = item_count( w);
   ix = w-> um = malloc( sizeof( UnixMenuItem) * w-> num);
   if ( !ix) return;
   bzero( w-> um, sizeof( UnixMenuItem) * w-> num);

   startx = x = vertical ? MENU_XOFFSET * 4 + MENU_CHECK_XOFFSET * 2 : 0; 
   
   if ( vertical) w-> last = -1;
   w-> selected = -100;
   while ( m) {
      if ( m-> flags. divider) {
         ix-> height = vertical ? MENU_ITEM_GAP * 2 : 0;
      } else {
         int l = 0;
         if ( m-> text) {
            int i, ntildas = 0;
            char * t = m-> text;
            ix-> height = MENU_ITEM_GAP * 2 + kf-> font. height;
            for ( i = 0; t[i]; i++) {
               if ( t[i] == '~' && t[i+1]) {
                  ntildas++;
                  if ( t[i+1] == '~') 
                     i++;
               }   
            }  
            ix-> width += startx + get_text_width( kf, m-> text, i, 
               m-> flags. utf8_text, xft_map8);
            if ( ntildas) 
               ix-> width -= ntildas * get_text_width( kf, "~", 1, false, xft_map8);
         } else if ( m-> bitmap && PObject( m-> bitmap)-> stage < csDead) {
            Pixmap px = prima_std_pixmap( m-> bitmap, CACHE_LOW_RES);
            if ( px) {
               PImage i = ( PImage) m-> bitmap;
               ix-> height += ( i-> h < kf-> font. height) ?  kf-> font. height : i-> h +
                  MENU_ITEM_GAP * 2;
               if ( ix-> height > guts. displaySize. y - kf-> font. height - MENU_ITEM_GAP * 3 - 4) 
                 ix-> height = guts. displaySize. y - kf-> font. height - MENU_ITEM_GAP * 3 - 4;
               ix-> width  += i-> w + startx;
               ix-> pixmap = px;
            }
         }
         if ( m-> accel && ( l = strlen( m-> accel))) {
            ix-> accel_width = get_text_width( kf, m-> accel, l, 
               m-> flags. utf8_accel, xft_map8);
         }
         if ( ix-> accel_width + ix-> width > x) x = ix-> accel_width + ix-> width;
      }

      if ( vertical && lastIncOk && 
           y + ix-> height + MENU_ITEM_GAP * 2 + kf-> font. height > guts. displaySize. y) {
         lastIncOk = 0;
         y += MENU_ITEM_GAP * 2 + kf-> font. height;
      }
      m = m-> next;
      if ( lastIncOk) {
         y += ix-> height;
         w-> last++;
      }
      ix++;
      if ( !lastIncOk) break;
   }
   
   if ( vertical) {
      if ( x > guts. displaySize. x - 64) x = guts. displaySize. x - 64;
      w-> sz.x = x;
      w-> sz.y = y;
      XResizeWindow( DISP, w-> w, x, y);
   }
}

static int
menu_point2item( PMenuSysData XX, PMenuWindow w, int x, int y, PMenuItemReg * m_ptr)
{
   int l = 0, r = 0, index = 0;
   PMenuItemReg m;
   PUnixMenuItem ix;
   if ( !w) return -1;
   m = w-> m;
   ix = w-> um;
   if ( !ix) return -1;
   if ( w == &XX-> wstatic) {
      int right = w-> right;
      l = r = 0;
      if ( x < l) return -1;
      while ( m) {
         if ( m-> flags. divider) {
            if ( right > 0) {
               r += right;
               right = 0;
            }
            if ( x < r) return -1;
         } else {
            if ( index <= w-> last) {
               r += MENU_XOFFSET * 2 + ix-> width;
               if ( m-> accel) r += MENU_XOFFSET/2 + ix-> accel_width;
            } else
               r += MENU_XOFFSET * 2 + XX-> guillemots;
            if (x >= l && x <= r) {
               if ( m_ptr) *m_ptr = m;
               return index;
            }
            if ( index > w-> last) return -1;
         }
         l = r;
         index++;
         ix++;
         m = m-> next;
      }
   } else {
      l = r = 2;
      if ( y < l) return -1;
      while ( m) {
         if ( index > w-> last) {
            r += MENU_ITEM_GAP * 2 + XX-> font-> font. height;
            goto CHECK;
         } else if ( m-> flags. divider) {
            r += MENU_ITEM_GAP * 2;
            if ( y < r) return -1;
         } else {
            r += ix-> height;
         CHECK:   
            if ( y >= l && y <= r) {
               if ( m_ptr) *m_ptr = m;
               return index;
            }
            if ( index > w-> last) return -1;
         }
         l = r;
         index++;
         ix++;
         m = m-> next;
      }
   }
   return -1;
}

static Point
menu_item_offset( PMenuSysData XX, PMenuWindow w, int index)
{
   Point ret = {0,0};
   PMenuItemReg m = w-> m;
   PUnixMenuItem ix = w-> um;
   if ( index < 0 || !ix || !m) return ret;
   if ( w == &XX-> wstatic) {
      int right = w-> right;
      while ( m && index--) {
         if ( m-> flags. divider) {
            if ( right > 0) {
               ret. x += right;
               right = 0;
            }
         } else {
            ret. x += MENU_XOFFSET * 2 + ix-> width;
            if ( m-> accel) ret. x += MENU_XOFFSET / 2 + ix-> accel_width;
         }
         ix++;
         m = m-> next;
      }
   } else {
      ret. y = 2;
      ret. x = 2;
      while ( m && index--) {
         ret. y += ix-> height;
         ix++;
         m = m-> next;
      }
   }
   return ret;
}

static Point
menu_item_size( PMenuSysData XX, PMenuWindow w, int index)
{
   PMenuItemReg m = w-> m;
   PUnixMenuItem ix;
   Point ret = {0,0};
   if ( index < 0 || !w-> um || !m) return ret;
   if ( w == &XX-> wstatic) {
      if ( index >= 0 && index <= w-> last) {
         ix = w-> um + index; 
         while ( index--) m = m-> next; 
         if ( m-> flags. divider) return ret;
         ret. x = MENU_XOFFSET * 2 + ix-> width;
         if ( m-> accel) ret. x += MENU_XOFFSET / 2+ ix-> accel_width;
      } else if ( index == w-> last + 1) {
         ret. x = MENU_XOFFSET * 2 + XX-> guillemots;
      } else
         return ret;
      ret. y = XX-> font-> font. height + MENU_ITEM_GAP * 2; 
   } else {
      if ( index >= 0 && index <= w-> last) {
         ix = w-> um + index; 
         ret. y = ix-> height;
      } else if ( index == w-> last + 1) {
         ret. y = 2 * MENU_ITEM_GAP + XX-> font-> font. height;
      } else
         return ret;
      ret. x = w-> sz. x - 4;
   }
   return ret;
}

static void
menu_select_item( PMenuSysData XX, PMenuWindow w, int index)
{
   if ( index != w-> selected) {
      XRectangle r;
      Point p1 = menu_item_offset( XX, w, index);
      Point p2 = menu_item_offset( XX, w, w-> selected );
      Point s1 = menu_item_size( XX, w, index);
      Point s2 = menu_item_size( XX, w, w-> selected );
      if ( s1.x == 0 && s1.y == 0) {
         if ( s2.x == 0 && s2.y == 0) return;
         r.x = p2.x; r.y = p2.y;
         r.width = s2.x; r.height = s2.y;
      } else if ( s2.x == 0 && s2.y == 0) {
         r.x = p1.x; r.y = p1.y;
         r.width = s1.x; r.height = s1.y;
      } else {
         r. x = ( p1. x < p2. x) ? p1. x : p2. x;
         r. y = ( p1. y < p2. y) ? p1. y : p2. y;
         r. width  = ( p1.x + s1.x > p2.x + s2.x) ? p1.x + s1.x - r.x : p2.x + s2.x - r.x;
         r. height = ( p1.y + s1.y > p2.y + s2.y) ? p1.y + s1.y - r.y : p2.y + s2.y - r.y;
      }
      w-> selected = ( index < 0) ? -100 : index;
      XClearArea( DISP, w-> w, r.x, r.y, r.width, r.height, true);
      XX-> paint_pending = true;
   }
}

static Bool
send_cmMenu( Handle self, PMenuItemReg m)
{
    Event ev;
    Handle owner = PComponent( self)-> owner;
    bzero( &ev, sizeof( ev));
    ev. cmd = cmMenu;
    ev. gen. H = self;
    ev. gen. i = m ? m-> id : 0; 
    CComponent(owner)-> message( owner, &ev);
    if ( PComponent( owner)-> stage == csDead ||
         PComponent( self)->  stage == csDead) return false;
    if ( self != guts. currentMenu) return false;
    return true;
}

static Bool
menu_enter_item( PMenuSysData XX, PMenuWindow w, int index, int type)
{
   PMenuItemReg m = w-> m;
   int index2 = index, div = 0;

   XX-> focused = w;
   
   if ( index < 0 || index > w-> last + 1 || !w-> um || !m) return false;
   while ( index2--) {
      if ( m-> flags. divider) div = 1;
      m = m-> next;
   }
   if ( index == w-> last + 1) div = 0;
   
   if ( m-> flags. disabled && index <= w-> last) return false;
        
   if ( m-> down || index == w-> last + 1) {
      PMenuWindow w2;
      Point p, s, n = w-> pos;

      if ( w-> next && w-> next-> m == m-> down) {
         XX-> focused = w-> next;
         return true;
      }
      
      if ( index != w-> last + 1) {
         if ( !send_cmMenu( w-> self, m)) return false;
         m = m-> down;
      }

      menu_window_delete_downlinks( XX, w);
      w2 = get_window( w-> self, m); 
      if ( !w2) return false;
      
      update_menu_window( XX, w2);
      p = menu_item_offset( XX, w, index);
      s = menu_item_size( XX, w, index);
      
      if ( &XX-> wstatic == w) {
         XWindow dummy;
         XTranslateCoordinates( DISP, w->w, guts. root, 0, 0, &n.x, &n.y, &dummy);
         w-> pos = n;
      }
      
      n. x += p. x;
      n. y += p. y;
      p. x += w-> pos. x;
      p. y += w-> pos. y;
      if ( &XX-> wstatic == w) {
         if ( div) n. x -= w2-> sz. x - s. x;
         if ( p.y + s.y + w2-> sz.y <= guts. displaySize.y)
            n. y = p. y + s. y;
         else if ( w2-> sz.y <= p. y)
            n. y = p. y - w2-> sz. y;
         else 
            n. y = 0;
         if ( n. x + w2-> sz. x > guts. displaySize. x)
            n. x = guts. displaySize. x - w2-> sz. x;
         else if ( n. x < 0)
            n. x = 0;
      } else {
         div = 0;
         if ( p.y + w2-> sz.y <= guts. displaySize.y)
            n. y = p. y;
         else if ( w2-> sz.y <= p. y + s. y)
            n. y = p. y + s. y - w2-> sz. y;
         else 
            n. y = 0;
         if ( p.x + s. x + w2-> sz.x <= guts. displaySize.x)
            n. x = p. x + s. x;
         else if ( w2-> sz.x <= p.x)
            n. x = p. x - w2-> sz. x;
         else {
            n. x = 0;
            if ( p.y + w2-> sz.y + s.y <= guts. displaySize.y)
               n. y += s.y;
            else if ( w2-> sz.y <= p. y)
               n. y -= s.y;
         }
      }
      XMoveWindow( DISP, w2-> w, n. x, n. y);
      XMapRaised( DISP, w2-> w);
      w2-> pos = n;
      XX-> focused = w2;
   } else {
      Handle self = w-> self;
      if (( &XX-> wstatic == w) && ( type == 0)) {
         menu_window_delete_downlinks( XX, w);
         return true;
      }
      prima_end_menu();
      CAbstractMenu( self)-> sub_call( self, m);
      return false;
   }
   return true;
}


static void
store_char( char * src, int srclen, int * srcptr, char * dst, int * dstptr, Bool utf8, MenuDrawRec * data)
{
   if ( *srcptr >= srclen ) return;

   if ( utf8) {
      STRLEN char_len;
      UV uv = 
#if PERL_PATCHLEVEL >= 16
         utf8_to_uvchr_buf(( U8*) src + *srcptr, ( U8*) src + srclen, &char_len)
#else
         utf8_to_uvchr(( U8*) src + *srcptr, &char_len)
#endif
      ;
      *srcptr += char_len;
      if ( data-> xft_map8) {
         *(( uint32_t*)(dst + *dstptr)) = (uint32_t) uv;
         *dstptr += 4;
      } else {
         (( XChar2b*)(dst + *dstptr))-> byte1 = uv >> 8;
         (( XChar2b*)(dst + *dstptr))-> byte2 = uv & 0xff;
         *dstptr += 2;
      }
   } else {
      if ( data-> xft_map8) {
         uint32_t c = (( U8*) src)[ *srcptr];
         if ( c > 127)
            c = data-> xft_map8[ c - 128];
         *(( uint32_t*)(dst + *dstptr)) = c;
         (*dstptr) += 4;
         (*srcptr)++;
      } else {
         dst[(*dstptr)++] = src[(*srcptr)++];
      }
   }
}   

void
prima_handle_menu_event( XEvent *ev, XWindow win, Handle self)
{
   switch ( ev-> type) {
   case Expose: {
      DEFMM;
      PMenuWindow w;
      PUnixMenuItem ix;
      PMenuItemReg m;
      GC gc = guts. menugc;
      int mx, my;
      Bool vertical;
      int sz = 1024, l, i, slen, x, y;
      char *s;
      char *t;
      int right, last = 0;
      int xtoffs, descent;
      PCachedFont kf;
      MenuDrawRec draw;
      unsigned long clr;
      Color rgb;
     
      kf = XX-> font;
      XX-> paint_pending = false;
      if ( XX-> wstatic. w == win) {
         w = XX-> w;
         vertical = false;
      } else {
         if ( !( w = get_menu_window( self, win))) return;
         vertical = true;
      }
      right  = vertical ? 0 : w-> right;
      xtoffs = vertical ? MENU_CHECK_XOFFSET : 0;
      m  = w-> m;
      mx = w-> sz.x - 1;
      my = w-> sz.y - 1;
      ix = w-> um;
      if ( !ix) return;

      bzero( &draw, sizeof( draw));
      draw. win = win;
      draw. gc = gc;
#ifdef USE_XFT
      if ( kf-> xft) {
         PWindow owner = ( PWindow)(PComponent( w-> self)-> owner);
         char * encoding = ( XX-> type. popup) ? 
            owner-> popupFont. encoding :
            owner-> menuFont. encoding;
         draw. xft_map8 = prima_xft_map8( encoding);
         draw. xft_drawable = XftDrawCreate( DISP, win, 
            guts. visual. visual, guts. defaultColormap);
         descent = kf-> xft-> descent;
      } else
#endif
      descent = kf-> fs->max_bounds.descent;
       
      {
         XRectangle r;
         Region rgn;
         r. x = ev-> xexpose. x;
	 r. y = ev-> xexpose. y;
	 r. width = ev-> xexpose. width;
	 r. height = ev-> xexpose. height;
         rgn = XCreateRegion();
	 XUnionRectWithRegion( &r, rgn, rgn);
         XSetRegion( DISP, gc, rgn);
#ifdef USE_XFT
         if ( draw. xft_drawable) XftDrawSetClip(( XftDraw*) draw.xft_drawable, rgn);
#endif         
         XDestroyRegion( rgn);
      }

#ifdef USE_XFT
      if ( !kf-> xft)
#endif      
         XSetFont( DISP, gc, kf-> id);
      XSetForeground( DISP, gc, XX->c[ciBack]);
      XSetBackground( DISP, gc, XX->c[ciBack]);
      if ( vertical) {
         XFillRectangle( DISP, win, gc, 2, 2, mx-1, my-1);
         XSetForeground( DISP, gc, XX->c[ciDark3DColor]);
         XDrawLine( DISP, win, gc, 0, 0, 0, my);
         XDrawLine( DISP, win, gc, 0, 0, mx-1, 0);
         XDrawLine( DISP, win, gc, mx-1, my-1, 2, my-1);
         XDrawLine( DISP, win, gc, mx-1, my-1, mx-1, 1);
         XSetForeground( DISP, gc, guts. monochromeMap[0]);
         XDrawLine( DISP, win, gc, mx, my, 1, my);
         XDrawLine( DISP, win, gc, mx, my, mx, 0);
         XSetForeground( DISP, gc, XX->c[ciLight3DColor]);
         XDrawLine( DISP, win, gc, 1, 1, 1, my-1);
         XDrawLine( DISP, win, gc, 1, 1, mx-2, 1);
      } else
         XFillRectangle( DISP, win, gc, 0, 0, w-> sz.x, w-> sz.y);
      y = vertical ? 2 : 0;
      x = 0;
      if ( !( s = malloc( sz))) goto EXIT;
      while ( m) {
         Bool selected = false;

         /* printf("%d %d %d %s\n", last, w-> selected, w-> last, m-> text); */
         if ( last == w-> selected) { 
            Point sz = menu_item_size( XX, w, last);
            Point p = menu_item_offset( XX, w, last);
            XSetForeground( DISP, gc, XX-> c[ciHilite]);
            XFillRectangle( DISP, win, gc, p.x, p.y, sz. x, sz.y);
            clr = XX-> c[ciHiliteText];
            rgb = XX-> rgb[ciHiliteText];
            selected = true;
         } else {
            clr = XX-> c[ciFore];
            rgb = XX-> rgb[ciFore];
         }

         if ( last > w-> last) {
            char buf[8];
            int s = 0, d = 0;
            draw. pixel = clr;
            draw. rgb   = rgb;
            store_char( ">", strlen(">"), &s, buf, &d, 0, &draw);
            s = 0;
            store_char( ">", strlen(">"), &s, buf, &d, 0, &draw);
            text_out( kf, buf, 2,
                  x + MENU_XOFFSET + xtoffs, 
                  y + MENU_ITEM_GAP + kf-> font. height - kf-> font. descent, 
                  false, &draw);
            break;
         }

         if ( m-> flags. divider) {
            if ( vertical) {
               y += MENU_ITEM_GAP - 1;
               XSetForeground( DISP, gc, XX->c[ciDark3DColor]);
               XDrawLine( DISP, win, gc, 1, y, mx-1, y);
               y++;
               XSetForeground( DISP, gc, XX->c[ciLight3DColor]);
               XDrawLine( DISP, win, gc, 1, y, mx-1, y);
               y += MENU_ITEM_GAP;
            } else if ( right > 0) {
               x += right;
               right = 0;
            }
         } else {
            int deltaY = 0;

            if ( m-> flags. disabled) {
               clr = XX-> c[ciDisabledText];
               rgb = XX-> rgb[ ciDisabledText];
            }

            deltaY = ix-> height;
            if ( m-> text) {
               int lineStart, lineEnd = 0, haveDash = 0;
               int ay = y + deltaY - MENU_ITEM_GAP - kf-> font. descent;
	       int text_len = strlen(m-> text);

               t = m-> text;
               for (;;) {
                  l = i = slen = lineEnd = haveDash = 0;
                  lineStart = -1;
                  while ( l < sz - 1 && t[i]) {
                     if (t[i] == '~' && t[i+1]) {
                        if ( t[i+1] == '~') {
                           int dummy = 0;
                           store_char( "~", strlen("~"), &dummy, s, &l, false, &draw);
                           i += 2;
                           slen++;
                        } else {
                           if ( !haveDash) {
                              haveDash = 1;
                              lineEnd = lineStart + 
                                 get_text_width( kf, t + i + 1, 1, 
                                    m-> flags. utf8_text, draw. xft_map8);
                           }
                           i++;
                        }   
                     } else {
                        if ( !haveDash) {
                           FontABC abc;
                           get_font_abc( kf, t+i, m-> flags. utf8_text, &abc, &draw);
                           if ( lineStart < 0)
                              lineStart = ( abc.a < 0) ? -abc.a : 0;
                           lineStart += abc.a + abc.b + abc.c;
                        }
                        store_char( t, text_len, &i, s, &l, m-> flags. utf8_text, &draw);
                        slen++;
                     }
                  }
                  if ( t[i]) {
                     free(s); 
                     if ( !( s = malloc( sz *= 2))) goto EXIT;
                  } else
                     break;
               }
               if ( m-> flags. disabled && !selected) {
                  draw. pixel = XX->c[ciLight3DColor]; 
                  draw. rgb   = XX->rgb[ciLight3DColor]; 
                  text_out( kf, s, slen, x+MENU_XOFFSET+xtoffs+1, ay+1, 
                     m-> flags. utf8_text, &draw);
               } 
               draw. pixel = clr;
               draw. rgb   = rgb;
               text_out( kf, s, slen, x+MENU_XOFFSET+xtoffs, ay, m-> flags. utf8_text, &draw);
               if ( haveDash) {
                  if ( m-> flags. disabled && !selected) {
                      XSetForeground( DISP, gc, XX->c[ciLight3DColor]); 
                      XDrawLine( DISP, win, gc, x+MENU_XOFFSET+xtoffs+lineStart+1, ay+descent-1+1, 
                         x+MENU_XOFFSET+xtoffs+lineEnd+1, ay+descent-1+1);
                  } 
                  XSetForeground( DISP, gc, clr);
                  XDrawLine( DISP, win, gc, x+MENU_XOFFSET+xtoffs+lineStart, ay+descent-1, 
                     x+MENU_XOFFSET+xtoffs+lineEnd, ay+descent-1);
               }  
            } else if ( m-> bitmap && ix-> pixmap) {
               if ( selected) XSetFunction( DISP, gc, GXcopyInverted);
               XCopyArea( DISP, ix-> pixmap, win, gc, 0, 0, ix-> width, ix-> height, x+MENU_XOFFSET+xtoffs, y + MENU_ITEM_GAP);
               if ( selected) XSetFunction( DISP, gc, GXcopy);
            }
            if ( !vertical) x += ix-> width + MENU_XOFFSET;

            if ( m-> accel) {
               int i, ul = 0, sl = 0, dl = 0;
               int zx = vertical ? 
                  mx - MENU_XOFFSET - MENU_CHECK_XOFFSET - ix-> accel_width : 
                  x + MENU_XOFFSET/2;
               int zy = vertical ? 
                  y + ( deltaY + kf-> font. height) / 2 - kf-> font. descent: 
                  y + deltaY - MENU_ITEM_GAP - kf-> font. descent;
	       int accel_len = strlen(m-> accel);

               if ( m-> flags. utf8_accel) 
                  ul = prima_utf8_length( m-> accel);
               else
                  ul = accel_len;
               if (( ul * 4 + 4) > sz) {
                  free(s); 
                  if ( !( s = malloc( sz = (ul * 4 + 4)))) goto EXIT;
               }
               for ( i = 0; i < ul; i++)
                  store_char( m-> accel, accel_len, &sl, s, &dl, m-> flags. utf8_accel, &draw);
               if ( m-> flags. disabled && !selected) {
                  draw. pixel = XX->c[ciLight3DColor]; 
                  draw. rgb   = XX->rgb[ciLight3DColor]; 
                  text_out( kf, s, ul, zx + 1, zy + 1,
                     m-> flags. utf8_accel, &draw);
               } 
               draw. pixel = clr;
               draw. rgb   = rgb;
               text_out( kf, s, ul, zx, zy,
                  m-> flags. utf8_accel, &draw);
               if ( !vertical)
                  x += ix-> accel_width + MENU_XOFFSET/2;
            }
            if ( !vertical) x += MENU_XOFFSET;

            if ( vertical && m-> down) {
               int ave    = kf-> font. height * 0.4;
               int center = y + deltaY / 2;
               XPoint p[3];
               p[0].x = mx - MENU_CHECK_XOFFSET/2;
               p[0].y = center;
               p[1].x = mx - ave - MENU_CHECK_XOFFSET/2;
               p[1].y = center - ave * 0.6;
               p[2].x = mx - ave - MENU_CHECK_XOFFSET/2;
               p[2].y = center + ave * 0.6 + 1;
               if ( m-> flags. disabled && !selected) {
                  int i;
                  XSetForeground( DISP, gc, XX->c[ciLight3DColor]); 
                  for ( i = 0; i < 3; i++) { 
                     p[i].x++;
                     p[i].y++;
                  }   
                  XFillPolygon( DISP, win, gc, p, 3, Nonconvex, CoordModeOrigin);   
                  for ( i = 0; i < 3; i++) { 
                     p[i].x--;
                     p[i].y--;
                  }   
               } 
               XSetForeground( DISP, gc, clr);
               XFillPolygon( DISP, win, gc, p, 3, Nonconvex, CoordModeOrigin);
            } 
            if ( m-> flags. checked && vertical) { 
               /* don't draw check marks on horizontal menus - they look ugly */
               int bottom = y + deltaY - MENU_ITEM_GAP - ix-> height * 0.2;
               int ax = x + MENU_XOFFSET / 2;
               XGCValues gcv;
               gcv. line_width = 3;
               XChangeGC( DISP, gc, GCLineWidth, &gcv); 
               if ( m-> flags. disabled && !selected) {
                  XSetForeground( DISP, gc, XX->c[ciLight3DColor]); 
                  XDrawLine( DISP, win, gc, ax + 1 + 1 , y + deltaY / 2 + 1, ax + MENU_XOFFSET - 2 + 1, bottom - 1);
                  XDrawLine( DISP, win, gc, ax + MENU_XOFFSET - 2 + 1, bottom + 1, ax + MENU_CHECK_XOFFSET + 1, y + MENU_ITEM_GAP + ix-> height * 0.2);
               } 
               XSetForeground( DISP, gc, clr);
               XDrawLine( DISP, win, gc, ax + 1, y + deltaY / 2, ax + MENU_XOFFSET - 2, bottom);
               XDrawLine( DISP, win, gc, ax + MENU_XOFFSET - 2, bottom, ax + MENU_CHECK_XOFFSET, y + MENU_ITEM_GAP + ix-> height * 0.2);
               gcv. line_width = 1;
               XChangeGC( DISP, gc, GCLineWidth, &gcv); 
            } 
            if ( vertical) y += deltaY;
         }
         m = m-> next;
         ix++;
         last++;
      }
      free(s);
   EXIT:; 
#ifdef USE_XFT
      if ( draw. xft_drawable) 
         XftDrawDestroy( draw. xft_drawable);
#endif                
   }
   break;
   case ConfigureNotify: {
      DEFMM;
      if ( XX-> wstatic. w == win) {
         PMenuWindow  w = XX-> w;
         PMenuItemReg m;
         PUnixMenuItem ix;
         int x = 0;
         int stage = 0;
         if ( w-> sz. x == ev-> xconfigure. width &&
              w-> sz. y == ev-> xconfigure. height) return;
         if ( guts. currentMenu == self) prima_end_menu();
         w-> sz. x = ev-> xconfigure. width;
         w-> sz. y = ev-> xconfigure. height;
	 XClearArea( DISP, win, 0, 0, 0, 0, true);

AGAIN:             
         w-> last = -1;
         m = w-> m;
         ix = w-> um;
         while ( m) { 
            int dx = 0;
            if ( !m-> flags. divider) {
               dx += MENU_XOFFSET * 2 + ix-> width; 
               if ( m-> accel) dx += MENU_XOFFSET / 2 + ix-> accel_width;
            }
            if ( x + dx >= w-> sz.x) {
               if ( stage == 0) { /* now we are sure that >> should be drawn - check again */
                  x = MENU_XOFFSET * 2 + XX-> guillemots;
                  stage++;
                  goto AGAIN;
               } 
               break;
            }
            x += dx;
            w-> last++;
            m = m-> next;
            ix++; 
         }
         m = w-> m;
         ix = w-> um;
         w-> right = 0;
         if ( w-> last >= w-> num - 1) {
            Bool hit = false;
            x = 0;
            while ( m) {
               if ( m-> flags. divider) {
                  hit = true;
                  break;
               } else {
                  x += MENU_XOFFSET * 2 + ix-> width; 
                  if ( m-> accel) x += MENU_XOFFSET / 2 + ix-> accel_width;
               }
               m = m-> next; 
               ix++;
            }
            if ( hit) {
               w-> right = 0;
               while ( m) {
                  if ( !m-> flags. divider) {
                     w-> right += MENU_XOFFSET * 2 + ix-> width; 
                     if ( m-> accel) w-> right += MENU_XOFFSET / 2 + ix-> accel_width;
                  }
                  m = m-> next;
                  ix++;
               }
               w-> right = w-> sz.x - w-> right - x;
            }
         }
      }
   }
   break;
   case ButtonPress: 
   case ButtonRelease: {
      DEFMM;
      int px, first = 0;
      PMenuWindow w;
      XWindow focus = nilHandle;
      if ( prima_no_input( X(PComponent(self)->owner), false, true)) return;
      if ( ev-> xbutton. button != Button1) return;

      if ( XX-> wstatic. w == win) {
         Handle x = guts. focused, owner = PComponent(self)-> owner;
         while ( x && !X(x)-> type. window) x = PComponent( x)-> owner;
         if ( x != owner) {
            XSetInputFocus( DISP, focus = PComponent( owner)-> handle, 
               RevertToNone, ev-> xbutton. time);
         }
      }
      
      if ( !( w = get_menu_window( self, win))) {
         prima_end_menu();
         return;
      }
      px = menu_point2item( XX, w, ev-> xbutton. x, ev-> xbutton.y, nil);
      if ( px < 0) {
         if ( XX-> wstatic. w == win) 
            prima_end_menu();
         return;
      }
      if ( guts. currentMenu != self) {
         int rev;
         if ( ev-> type == ButtonRelease) return;
         if ( guts. currentMenu) 
            prima_end_menu();
         if ( focus)
            XX-> focus = focus;
         else
            XGetInputFocus( DISP, &XX-> focus, &rev);
         if ( !XX-> type. popup) {
            Handle topl = PComponent( self)-> owner;
            Handle who  = ( Handle) hash_fetch( guts.windows, (void*)&XX-> focus, sizeof(XX-> focus));
            while ( who) {
               if ( XT_IS_WINDOW(X(who))) {
                  if ( who != topl) XX-> focus = PComponent( topl)-> handle;
                  break;
               }
               who = PComponent( who)-> owner;
            }
         } 
         first = 1;
      }
      XSetInputFocus( DISP, XX-> w-> w, RevertToNone, CurrentTime);
      guts. currentMenu = self;
      if ( first && ( ev-> type == ButtonPress) && ( !send_cmMenu( self, nil)))
         return;
      if ( !first && ( ev-> type == ButtonPress)) return;
      apc_timer_stop( MENU_TIMER);
      menu_select_item( XX, w, px);
      if ( !ev-> xbutton. send_event) {
         if ( !menu_enter_item( XX, w, px, ( ev-> type == ButtonPress) ? 0 : 1))
            return;
      } else
         XX-> focused = w;
      
      ev-> xbutton. x += w-> pos. x;
      ev-> xbutton. y += w-> pos. y;
      if ( w-> next && 
           ev-> xbutton. x >= w-> next-> pos.x &&
           ev-> xbutton. y >= w-> next-> pos.y &&
           ev-> xbutton. x <  w-> next-> pos.x + w-> next-> sz.x &&
           ev-> xbutton. y <  w-> next-> pos.y + w-> next-> sz.y)
           { /* simulate mouse move, as X are stupid enough to not do it  */
         int x = ev-> xbutton.x, y = ev-> xbutton. y;
         ev-> xmotion. x = x - w-> next-> pos. x;
         ev-> xmotion. y = y - w-> next-> pos. y;
         win = w-> next-> w;
         goto MOTION_NOTIFY;
      }
   }
   break;       
   MOTION_NOTIFY:
   case MotionNotify: if ( guts. currentMenu == self) {
      DEFMM;
      PMenuItemReg m;
      PMenuWindow w = get_menu_window( self, win);
      int px = menu_point2item( XX, w, ev-> xmotion.x, ev-> xmotion.y, nil);
      menu_select_item( XX, w, px);
      m = w-> m;
      if ( px >= 0) {
         int x = px;
         while ( x--) m = m-> next;
         if ( px != w-> last + 1) m = m-> down;
         if ( !w-> next || w-> next-> m != m) {
            apc_timer_set_timeout( MENU_TIMER, (XX-> wstatic. w == win) ? 2 : guts. menu_timeout);
            XX-> focused = w;
         }
      }
      while ( w-> next) {
         menu_select_item( XX, w-> next, -1);
         w = w-> next;
      }
   }
   break;
   case FocusOut:
      if ( self == guts. currentMenu) {
         switch ( ev-> xfocus. detail) {
         case NotifyVirtual:
         case NotifyPointer:
         case NotifyPointerRoot: 
         case NotifyDetailNone: 
         case NotifyNonlinearVirtual: 
            return;
         }
         apc_timer_stop( MENU_UNFOCUS_TIMER);
         apc_timer_start( MENU_UNFOCUS_TIMER);
         guts. unfocusedMenu = self;
      }
      break;
   case FocusIn:
      if ( guts. unfocusedMenu && self == guts. unfocusedMenu && self == guts. currentMenu) {
         switch ( ev-> xfocus. detail) {
         case NotifyVirtual:
         case NotifyPointer:
         case NotifyPointerRoot: 
         case NotifyDetailNone: 
         case NotifyNonlinearVirtual: 
            return;
         }
         apc_timer_stop( MENU_UNFOCUS_TIMER);
         guts. unfocusedMenu = nilHandle;
      }
      break;
   case KeyPress: {
      DEFMM;                     
      char str_buf[ 256];
      KeySym keysym;
      int str_len, d = 0, piles = 0;
      PMenuWindow w;
      PMenuItemReg m;

      str_len = XLookupString( &ev-> xkey, str_buf, 256, &keysym, nil);
      if ( prima_handle_menu_shortcuts( PComponent(self)-> owner, ev, keysym) != 0)
         return;
      
      if ( self != guts. currentMenu) return;
      apc_timer_stop( MENU_TIMER);
      if ( !XX-> focused) return;
      /* navigation */
      w = XX-> focused;
      m = w-> m;
      switch (keysym) {
      case XK_Left:
      case XK_KP_Left:
         if ( w == &XX-> wstatic) { /* horizontal menu */
            d--; 
         } else if ( w != XX-> w) { /* not a popup root */
            if ( w-> prev) menu_window_delete_downlinks( XX, w-> prev);
            if ( w-> prev == &XX-> wstatic) {
               d--;
               piles = 1;
            } else
               return;
         }
         break;
      case XK_Right:
      case XK_KP_Right:
         if ( w == &XX-> wstatic) { /* horizontal menu */
            d++; 
         } else if ( w-> selected >= 0) {
            int sel;
            sel = w-> selected;
            if ( sel >= 0) {
               while ( sel--) m = m-> next;
            }
            if ( m-> down || w-> selected == w-> last + 1) {
               if ( menu_enter_item( XX, w, w-> selected, 1) && w-> next)
                  menu_select_item( XX, w-> next, 0);
            } else if ( w-> prev == &XX-> wstatic) {
               menu_window_delete_downlinks( XX, XX-> w);
               piles = 1;
               d++;
            } else
               return;
         }
         break;
      case XK_Up:
      case XK_KP_Up:
         if ( w != &XX-> wstatic) d--;
         break;
      case XK_Down:
      case XK_KP_Down:
         if ( w == &XX-> wstatic) {
            int sel = w-> selected;
            if ( sel >= 0) {
               while ( sel--) m = m-> next;
            }
            if ( m-> down || w-> selected == w-> last + 1) {
               if ( menu_enter_item( XX, w, w-> selected, 1) && w-> next) 
                  menu_select_item( XX, w-> next, 0);
            }
         } else
            d++;
         break;
      case XK_KP_Enter:
      case XK_Return:
         menu_enter_item( XX, w, w-> selected, 1);
         return;
      case XK_Escape:
         if ( w-> prev) 
            menu_window_delete_downlinks( XX, w-> prev);
         else
            prima_end_menu();
         return;
      default:
         goto NEXT_STAGE;
      }

      if ( piles) w = XX-> focused = w-> prev;
      
      if ( d != 0) {
         int sel = w-> selected;
         PMenuItemReg m;
         int z, last = w-> last + (( w-> num == w-> last + 1) ? 0 : 1);

         if ( sel < -1) sel = -1;
         while ( 1) {
            if ( d > 0) {
               sel = ( sel >= last) ? 0 : sel + 1;
            } else {
               sel = ( sel <= 0) ? last : sel - 1;
            }
            m = w-> m;
            z = sel;
            while ( z--) m = m-> next;
            if ( sel == w-> last + 1 || !m-> flags. divider) {
               menu_select_item( XX, w, sel);
               menu_window_delete_downlinks( XX, w);
               if ( piles) {
                  if ( menu_enter_item( XX, w, sel, 0) && w-> next)
                     menu_select_item( XX, w-> next, 0);
               }
               break;
            }
         }
        
      }
      return;
NEXT_STAGE:

      if ( str_len == 1) {
         int i;
	 char c = tolower( str_buf[0]);
         for ( i = 0; i <= w-> last; i++) {
            if ( m-> text) {
               int j = 0;
               char * t = m-> text, z = 0;
               while ( t[j]) {
                  if ( t[j] == '~' && t[j+1]) {
                     if ( t[j+1] == '~')
                        j += 2;
                     else {
                        z = tolower(t[j+1]);
                        break;
                     }
                  }
                  j++;
               }
               if ( z == c) {
                  if ( menu_enter_item( XX, w, i, 1) && w-> next)
                     menu_select_item( XX, w, i);
                  return;
               }
            }
            m = m-> next;
         }
      }
   }
   break;
   case MenuTimerMessage: 
   if ( self == guts. currentMenu) {
      DEFMM;
      PMenuWindow w;
      PMenuItemReg m;
      int s;
     
      if ( !( w = XX-> focused)) return;
      m = w-> m;
      s = w-> selected;
      if ( s < 0) return;
      while ( s--) m = m-> next;
      if ( m-> down || w-> selected == w-> last + 1) 
         menu_enter_item( XX, w, w-> selected, 0);
      else
         menu_window_delete_downlinks( XX, w);
   } 
   break;
   }
}

/* local menu access hacks; it's good idea to have
   hot keys changeable through resources, but have no
   idea ( and desire :) how to plough throgh it */
int
prima_handle_menu_shortcuts( Handle self, XEvent * ev, KeySym keysym)
{
   int ret = 0;
   int mod = 
      (( ev-> xkey. state & ShiftMask)	? kmShift : 0) |
      (( ev-> xkey. state & ControlMask)? kmCtrl  : 0) |
      (( ev-> xkey. state & Mod1Mask)	? kmAlt   : 0);

   if ( mod == kmShift && keysym == XK_F9) {
      Event e;
      bzero( &e, sizeof(e));
      e. cmd    = cmPopup; 
      e. gen. B = false;
      e. gen. P = apc_pointer_get_pos( application); 
      e. gen. H = self;
      apc_widget_map_points( self, false, 1, &e. gen. P);
      CComponent( self)-> message( self, &e);
      if ( PObject( self)-> stage == csDead) return -1;
      ret = 1;
   }

   if ( mod == 0 && keysym == XK_F10) {
      Handle ps = self;
      while ( PComponent( self)-> owner) {
         ps = self;
         if ( XT_IS_WINDOW(X(self))) break;
         self = PComponent( self)-> owner;
      }
      self = ps;

      if ( XT_IS_WINDOW(X(self)) && PWindow(self)-> menu) {
         if ( !guts. currentMenu) {
            XEvent ev;
            bzero( &ev, sizeof( ev));
            ev. type = ButtonPress;
            ev. xbutton. button = Button1; 
            ev. xbutton. send_event = true;
            prima_handle_menu_event( &ev, M(PWindow(self)-> menu)-> w-> w, PWindow(self)-> menu);
         } else 
            prima_end_menu();
         ret = 1;
      }
   }
   
   if ( !guts. currentMenu && mod == kmAlt) {   /* handle menu bar keys */
      KeySym keysym;
      char str_buf[ 256];
      Handle ps = self;

      while ( PComponent( self)-> owner) {
         ps = self;
         if ( XT_IS_WINDOW(X(self))) break;
         self = PComponent( self)-> owner;
      }
      self = ps;

      if ( XT_IS_WINDOW(X(self)) && PWindow(self)-> menu && 
            1 == XLookupString( &ev-> xkey, str_buf, 256, &keysym, nil)) {
         int i;
         PMenuSysData selfxx = M(PWindow(self)-> menu);
	 char c = tolower( str_buf[0]);
         PMenuWindow w = XX-> w;
         PMenuItemReg m = w-> m;
         
         for ( i = 0; i <= w-> last; i++) {
            if ( m-> text) {
               int j = 0;
               char * t = m-> text, z = 0;
               while ( t[j]) {
                  if ( t[j] == '~' && t[j+1]) {
                     if ( t[j+1] == '~')
                        j += 2;
                     else {
                        z = tolower(t[j+1]);
                        break;
                     }
                  }
                  j++;
               }
               if ( z == c) {
                  XEvent ev;
                  bzero( &ev, sizeof( ev));
                  ev. type = ButtonPress;
                  ev. xbutton. button = Button1; 
                  ev. xbutton. send_event = true;
                  prima_handle_menu_event( &ev, w-> w, PWindow(self)-> menu);
                  if ( menu_enter_item( XX, w, i, 1) && w-> next)
                     menu_select_item( XX, w, i);
                  return 1;
               }
            }
            m = m-> next;
         }
      }
   }
   return ret;
}

void
prima_end_menu(void)
{
   PMenuSysData XX;
   PMenuWindow w;
   apc_timer_stop( MENU_TIMER);
   apc_timer_stop( MENU_UNFOCUS_TIMER);
   guts. unfocusedMenu = nilHandle; 
   if ( !guts. currentMenu) return;
   XX = M(guts. currentMenu);
   {
      XRectangle r;
      Region rgn;
      r. x = 0;
      r. y = 0;
      r. width  = guts. displaySize. x; 
      r. height = guts. displaySize. y; 
      rgn = XCreateRegion();
      XUnionRectWithRegion( &r, rgn, rgn);
      XSetRegion( DISP, guts. menugc, rgn);
      XDestroyRegion( rgn);
      XSetForeground( DISP, guts. menugc, XX->c[ciBack]);
   }
   w = XX-> w;
   if ( XX-> focus)
      XSetInputFocus( DISP, XX-> focus, RevertToNone, CurrentTime);
   menu_window_delete_downlinks( XX, XX-> w);
   XX-> focus = nilHandle;
   XX-> focused = nil; 
   if ( XX-> w != &XX-> wstatic) {
      hash_delete( guts. menu_windows, &w-> w, sizeof( w-> w), false);
      XDestroyWindow( DISP, w-> w);
      free_unix_items( w);
      free( w);
      XX-> w = nil;
   } else {
      XX-> w-> next = nil;
      menu_select_item( XX, XX-> w, -100);
   }
   guts. currentMenu = nilHandle;
}

Bool
apc_menu_create( Handle self, Handle owner)
{
   DEFMM;
   int i;
   apc_menu_destroy( self);
   XX-> type.menu = true;
   XX-> w         = &XX-> wstatic;
   XX-> w-> self  = self; 
   XX-> w-> m     = TREE;
   XX-> w-> first = 0;
   for ( i = 0; i <= ciMaxId; i++)
      XX-> c[i] = prima_allocate_color( 
          nilHandle, 
          prima_map_color( PWindow(owner)-> menuColor[i], nil), 
          nil);
   apc_menu_set_font( self, &PWindow(owner)-> menuFont);
   return true;
}

Bool
apc_menu_destroy( Handle self)
{
   if ( guts. currentMenu == self) prima_end_menu();
   return true;
}

PFont
apc_menu_default_font( PFont f)
{
   memcpy( f, &guts. default_menu_font, sizeof( Font));
   return f;
}

Color
apc_menu_get_color( Handle self, int index)
{
   Color c;
   if ( index < 0 || index > ciMaxId) return clInvalid;
   c = M(self)-> c[index];
   if ( guts. palSize > 0) 
       return guts. palette[c]. composite;
   return
      ((((c & guts. visual. blue_mask)  >> guts. blue_shift) << 8) >> guts. blue_range) |
     (((((c & guts. visual. green_mask) >> guts. green_shift) << 8) >> guts. green_range) << 8) |
     (((((c & guts. visual. red_mask)   >> guts. red_shift)   << 8) >> guts. red_range) << 16);
}

PFont
apc_menu_get_font( Handle self, PFont font)
{
   DEFMM;
   if ( !XX-> font)
      return apc_menu_default_font( font);
   memcpy( font, &XX-> font-> font, sizeof( Font));
   return font;
}

Bool
apc_menu_set_color( Handle self, Color color, int i)
{
   DEFMM;
   if ( i < 0 || i > ciMaxId) return false;
   XX-> rgb[i] = prima_map_color( color, nil);
   if ( !XX-> type.popup) {
      if ( X(PWidget(self)-> owner)-> menuColorImmunity) {
         X(PWidget(self)-> owner)-> menuColorImmunity--;
         return true;
      }
      if ( X_WINDOW) {
         prima_palette_replace( PWidget(self)-> owner, false);
         if ( !XX-> paint_pending) {
            XClearArea( DISP, X_WINDOW, 0, 0, XX-> w-> sz.x, XX-> w-> sz.y, true);
            XX-> paint_pending = true;
         }
      }
   } else 
      XX-> c[i] = prima_allocate_color( nilHandle, XX-> rgb[i], nil);
   return true;
}

/* apc_menu_set_font is in apc_font.c */

void
menu_touch( Handle self, PMenuItemReg who, Bool kill)
{
   DEFMM;
   PMenuWindow w, lw = nil;

   if ( guts. currentMenu != self) return;

   w = XX-> w;
   while ( w) {
      if ( w-> m == who) {
         if ( kill || lw == nil)
            prima_end_menu(); 
         else
            menu_window_delete_downlinks( M(self), lw);
         return;
      }
      lw = w;
      w = w-> next;
   }
}

static void 
menu_reconfigure( Handle self)
{
   XEvent ev;
   DEFMM;
   ev. type = ConfigureNotify;
   XX-> w-> sz. x =  ev. xconfigure. width - 1; /* force cache flush */
   ev. xconfigure. width  = X(PComponent(self)-> owner)-> size.x;
   ev. xconfigure. height = X(PComponent(self)-> owner)-> size.y;
   prima_handle_menu_event( &ev, PMenu(self)-> handle, self);
}

static void
menubar_repaint( Handle self)
{
   DEFMM;
   if ( !XT_IS_POPUP(XX) && XX-> w == &XX-> wstatic && PMenu(self)-> handle) {
      XClearArea( DISP, X_WINDOW, 0, 0, XX-> w-> sz.x, XX-> w-> sz.y, true);
      XX-> paint_pending = true;
   }
}

Bool
apc_menu_update( Handle self, PMenuItemReg oldBranch, PMenuItemReg newBranch)
{
   DEFMM;
   if ( !XT_IS_POPUP(XX) && XX-> w-> m == oldBranch) {
      if ( guts. currentMenu == self) prima_end_menu();
      XX-> w-> m = newBranch;
      if ( PMenu(self)-> handle) {
         update_menu_window( XX, XX-> w);
         menu_reconfigure( self);
         XClearArea( DISP, X_WINDOW, 0, 0, XX-> w-> sz.x, XX-> w-> sz.y, true);
         XX-> paint_pending = true;
      }
   }
   menu_touch( self, oldBranch, true);
   return true;
}

Bool
apc_menu_item_delete( Handle self, PMenuItemReg m)
{
   DEFMM;
   if ( !XT_IS_POPUP(XX) && XX-> w-> m == m) {
      if ( guts. currentMenu == self) prima_end_menu();
      XX-> w-> m = TREE;
      if ( PMenu(self)-> handle) {
         update_menu_window( XX, XX-> w);
         menu_reconfigure( self);
         XClearArea( DISP, X_WINDOW, 0, 0, XX-> w-> sz.x, XX-> w-> sz.y, true);
         XX-> paint_pending = true;
      }
   }
   menu_touch( self, m, true);
   return true;
}

Bool
apc_menu_item_set_accel( Handle self, PMenuItemReg m)
{
   menu_touch( self, m, false);
   return true;
}

Bool
apc_menu_item_set_check( Handle self, PMenuItemReg m)
{
   menu_touch( self, m, false);
   return true;
}

Bool
apc_menu_item_set_enabled( Handle self, PMenuItemReg m)
{
   menu_touch( self, m, false);
   menubar_repaint( self);
   return true;
}

Bool
apc_menu_item_set_image( Handle self, PMenuItemReg m)
{
   menu_touch( self, m, false);
   menubar_repaint( self);
   return true;
}

Bool
apc_menu_item_set_key( Handle self, PMenuItemReg m)
{
   menu_touch( self, m, false);
   return true;
}

Bool
apc_menu_item_set_text( Handle self, PMenuItemReg m)
{
   menu_touch( self, m, false);
   menubar_repaint( self);
   return true;
}

ApiHandle
apc_menu_get_handle( Handle self)
{
   return nilHandle;
}

Bool
apc_popup_create( Handle self, Handle owner)
{
   DEFMM;
   apc_menu_destroy( self);
   XX-> type.menu = true;
   XX-> type.popup = true;
   return true;
}

PFont
apc_popup_default_font( PFont f)
{
   memcpy( f, &guts. default_menu_font, sizeof( Font));
   return f;
}

Bool
apc_popup( Handle self, int x, int y, Rect *anchor)
{
   DEFMM;
   PMenuItemReg m;
   PMenuWindow w;
   XWindow dummy;
   PDrawableSysData owner;
   int dx, dy;

   prima_end_menu();
   if (!(m=TREE)) return false;
   guts. currentMenu = self;
   if ( !send_cmMenu( self, nil)) return false;
   if (!(w = get_window(self,m))) return false;
   update_menu_window(XX, w);
   if ( anchor-> left == 0 && anchor-> right == 0 && anchor-> top == 0 && anchor-> bottom == 0) {
      anchor-> left = anchor-> right = x;
      anchor-> top = anchor-> bottom = y;
   } else {
      if ( x < anchor-> left)   anchor-> left   = x;
      if ( x > anchor-> right)  anchor-> right  = x;
      if ( y < anchor-> bottom) anchor-> bottom = y;
      if ( y > anchor-> top)    anchor-> top    = y;
   }
   owner = X(PComponent(self)->owner);
   y = owner-> size. y - y;
   anchor-> bottom = owner-> size. y - anchor-> bottom;
   anchor-> top = owner-> size. y - anchor-> top;
   dx = dy = 0;
   XTranslateCoordinates( DISP, owner->udrawable, guts. root, dx, dy, &dx, &dy, &dummy);
   x += dx;
   y += dy;
   anchor-> left   += dx;
   anchor-> right  += dx;
   anchor-> top    += dy;
   anchor-> bottom += dy;
   if ( anchor-> bottom + w-> sz.y <= guts. displaySize.y)
      y = anchor-> bottom;
   else if ( w-> sz. y < anchor-> top)
      y = anchor-> top - w-> sz. y;
   else
      y = 0;
   if ( anchor-> right + w-> sz.x <= guts. displaySize.x)
      x = anchor-> right;
   else if ( w-> sz.x < anchor-> left)
      x = anchor-> left - w-> sz. x;
   else
      x = 0;
   w-> pos. x = x;
   w-> pos. y = y;
   XX-> focused = w;
   XGetInputFocus( DISP, &XX-> focus, &dx);
   XMoveWindow( DISP, w->w, x, y);
   XMapRaised( DISP, w->w);
   XSetInputFocus( DISP, w->w, RevertToNone, CurrentTime);
   XFlush( DISP);
   XCHECKPOINT;
   return true;
}

Bool
apc_window_set_menu( Handle self, Handle menu)
{
   DEFXX;
   int y = XX-> menuHeight;
   Bool repal = false;
   
   if ( XX-> menuHeight > 0) {
      PMenu m = ( PMenu) PWindow( self)-> menu;
      PMenuWindow w = M(m)-> w;
      if ( m-> handle == guts. currentMenu) prima_end_menu();
      hash_delete( guts. menu_windows, &w-> w, sizeof( w-> w), false);
      XDestroyWindow( DISP, w-> w);
      free_unix_items( w);
      m-> handle = nilHandle;
      M(m)-> paint_pending = false;
      M(m)-> focused = nil;
      y = 0;
      repal = true;
   }

   if ( menu) {
      PMenu m = ( PMenu) menu;
      XSetWindowAttributes attrs;
      attrs. event_mask =           KeyPressMask | ButtonPressMask  | ButtonReleaseMask
         | EnterWindowMask     | LeaveWindowMask | ButtonMotionMask | ExposureMask
         | StructureNotifyMask | FocusChangeMask | OwnerGrabButtonMask
         | PointerMotionMask;
      attrs. do_not_propagate_mask = attrs. event_mask;
      attrs. win_gravity = NorthWestGravity;
      y = PWindow(self)-> menuFont. height + MENU_ITEM_GAP * 2;
      M(m)-> w-> w = m-> handle = XCreateWindow( DISP, X_WINDOW, 
         0, 0, 1, 1, 0, CopyFromParent, 
         InputOutput, CopyFromParent, CWWinGravity| CWEventMask, &attrs);
      hash_store( guts. menu_windows, &m-> handle, sizeof( m-> handle), m);
      XResizeWindow( DISP, m-> handle, XX-> size.x, y);
      XMapRaised( DISP, m-> handle);
      M(m)-> paint_pending = true;
      M(m)-> focused = nil;
      update_menu_window(M(m), M(m)-> w);
      menu_reconfigure( menu);
      repal = true;
      /* make it immune to necessary color change calls */
      XX-> menuColorImmunity = ciMaxId + 1;
   }
   prima_window_reset_menu( self, y);
   if ( repal) prima_palette_replace( self, false); 
   if ( menu) {
      int i;
      for ( i = 0; i <= ciMaxId; i++) {
         M(menu)-> c[i] = prima_allocate_color( self, 
             prima_map_color( PWindow(self)-> menuColor[i], nil), nil);
      }
   }
   return true;
}
