1 /* Copyright (C) 2000-2012 by George Williams */
2 /*
3  * Redistribution and use in source and binary forms, with or without
4  * modification, are permitted provided that the following conditions are met:
5 
6  * Redistributions of source code must retain the above copyright notice, this
7  * list of conditions and the following disclaimer.
8 
9  * Redistributions in binary form must reproduce the above copyright notice,
10  * this list of conditions and the following disclaimer in the documentation
11  * and/or other materials provided with the distribution.
12 
13  * The name of the author may not be used to endorse or promote products
14  * derived from this software without specific prior written permission.
15 
16  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
17  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
18  * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
19  * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
20  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
22  * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
23  * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
24  * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
25  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26  */
27 
28 #include <fontforge-config.h>
29 
30 #include "gdraw.h"
31 #include "ggadgetP.h"
32 #include "gkeysym.h"
33 #include "gresource.h"
34 #include "gwidget.h"
35 #include "hotkeys.h"
36 #include "prefs.h"
37 #include "ustring.h"
38 #include "utype.h"
39 
40 static GBox menubar_box = GBOX_EMPTY; /* Don't initialize here */
41 static GBox menu_box = GBOX_EMPTY; /* Don't initialize here */
42 static FontInstance *menu_font = NULL, *menubar_font = NULL;
43 static int gmenubar_inited = false;
44 #ifdef __Mac
45 static int mac_menu_icons = true;
46 #else
47 static int mac_menu_icons = false;
48 #endif
49 static int menu_3d_look = 1; // The 3D look is the default/legacy setting.
50 static int mask_set=0;
51 static int menumask = ksm_control|ksm_meta|ksm_shift;		/* These are the modifier masks expected in menus. Will be overridden by what's actually there */
52 #ifndef _Keyboard
53 # define _Keyboard 0
54 #endif
55 static enum { kb_ibm, kb_mac, kb_sun, kb_ppc } keyboard = _Keyboard;
56 /* Sigh. In old XonX the command key is mapped to 0x20 and Option to 0x8 (meta) */
57 /*  the option key conversions (option-c => ccidilla) are not done */
58 /*  In the next X, the command key is mapped to 0x10 and Option to 0x2000 */
59 /*  (again option key conversion are not done) */
60 /*  In 10.3, the command key is mapped to 0x10 and Option to 0x8 */
61 /*  In 10.5 the command key is mapped to 0x10 and Option to 0x8 */
62 /*   (and option conversions are done) */
63 /*  While in Suse PPC X, the command key is 0x8 (meta) and option is 0x2000 */
64 /*  and the standard mac option conversions are done */
65 
66 static GResInfo gmenu_ri;
67 static GResInfo gmenubar_ri = {
68     &gmenu_ri, &ggadget_ri,&gmenu_ri, NULL,
69     &menubar_box,
70     &menubar_font,
71     NULL,
72     NULL,
73     N_("Menu Bar"),
74     N_("Menu Bar"),
75     "GMenuBar",
76     "Gdraw",
77     false,
78     omf_border_shape|omf_border_width|box_foreground_border_outer,
79     NULL,
80     GBOX_EMPTY,
81     NULL,
82     NULL,
83     NULL
84 };
85 static struct resed menu_re[] = {
86     {N_("MacIcons"), "MacIcons", rt_bool, &mac_menu_icons, N_("Whether to use mac-like icons to indicate modifiers (for instance ^ for Control)\nor to use an abbreviation (for instance \"Cnt-\")"), NULL, { 0 }, 0, 0 },
87     RESED_EMPTY
88 };
89 static GResInfo gmenu_ri = {
90     NULL, &ggadget_ri,&gmenubar_ri, NULL,
91     &menu_box,
92     &menu_font,
93     NULL,
94     menu_re,
95     N_("Menu"),
96     N_("Menu"),
97     "GMenu",
98     "Gdraw",
99     false,
100     omf_border_shape|omf_padding|box_foreground_border_outer,
101     NULL,
102     GBOX_EMPTY,
103     NULL,
104     NULL,
105     NULL
106 };
107 
108 char* HKTextInfoToUntranslatedText( char* text_untranslated );
109 char* HKTextInfoToUntranslatedTextFromTextInfo( GTextInfo* ti );
110 
111 static void GMenuBarChangeSelection(GMenuBar *mb, int newsel,GEvent *);
112 static struct gmenu *GMenuCreateSubMenu(struct gmenu *parent,GMenuItem *mi,int disable);
113 static struct gmenu *GMenuCreatePulldownMenu(GMenuBar *mb,GMenuItem *mi, int disabled);
114 
115 static int menu_grabs=true;
116 static struct gmenu *most_recent_popup_menu = NULL;
117 
GMenuInit()118 static void GMenuInit() {
119     FontRequest rq;
120     char *keystr, *end;
121 
122     GGadgetInit();
123     memset(&rq,0,sizeof(rq));
124     GDrawDecomposeFont(_ggadget_default_font,&rq);
125     rq.weight = 400;
126     menu_font = menubar_font = GDrawInstanciateFont(NULL,&rq);
127     _GGadgetCopyDefaultBox(&menubar_box);
128     _GGadgetCopyDefaultBox(&menu_box);
129     menubar_box.border_shape = menu_box.border_shape = bs_rect;
130     menubar_box.border_width = 0;
131     menu_box.padding = 1;
132     menubar_box.flags |= box_foreground_border_outer;
133     menu_box.flags |= box_foreground_border_outer;
134     menubar_font = _GGadgetInitDefaultBox("GMenuBar.",&menubar_box,menubar_font);
135     menu_font = _GGadgetInitDefaultBox("GMenu.",&menu_box,menubar_font);
136     keystr = GResourceFindString("Keyboard");
137     if ( keystr!=NULL ) {
138 	if ( strmatch(keystr,"mac")==0 ) keyboard = kb_mac;
139 	else if ( strmatch(keystr,"sun")==0 ) keyboard = kb_sun;
140 	else if ( strmatch(keystr,"ppc")==0 ) keyboard = kb_ppc;
141 	else if ( strmatch(keystr,"ibm")==0 || strmatch(keystr,"pc")==0 ) keyboard = kb_ibm;
142 	else if ( strtol(keystr,&end,10), *end=='\0' )
143 	    keyboard = strtol(keystr,NULL,10);
144         free(keystr);
145     }
146     menu_grabs = GResourceFindBool("GMenu.Grab",menu_grabs);
147     mac_menu_icons = GResourceFindBool("GMenu.MacIcons",mac_menu_icons);
148     menu_3d_look = GResourceFindBool("GMenu.3DLook", menu_3d_look);
149     gmenubar_inited = true;
150     _GGroup_Init();
151 }
152 
153 typedef struct gmenu {
154     unsigned int hasticks: 1;
155     unsigned int pressed: 1;
156     unsigned int initial_press: 1;
157     unsigned int scrollup: 1;
158     unsigned int freemi: 1;
159     unsigned int disabled: 1;
160     unsigned int dying: 1;
161     unsigned int hidden: 1;
162     unsigned int any_unmasked_shortcuts: 1;		/* Only set for popup menus. Else info in menubar */
163     int bp;
164     int tickoff, tioff, rightedge;
165     int width, height;
166     int line_with_mouse;
167     int offtop, lcnt, mcnt;
168     GMenuItem *mi;
169     int fh, as;
170     GWindow w;
171     GBox *box;
172     struct gmenu *parent, *child;
173     struct gmenubar *menubar;
174     GWindow owner;
175     GTimer *scrollit;		/* No longer in use, see comment below */
176     FontInstance *font;
177     void (*donecallback)(GWindow owner);
178     GIC *gic;
179     char subMenuName[100];
180     /* The code below still contains the old code for using up/down arrows */
181     /*  in the menu instead of a scrollbar. If vsb==NULL and the menu doesn't */
182     /*  fit on the screen, then the old code will be implemented (normally */
183     /*  that won't happen, but if I ever want to re-enable it, this is how */
184     /* The up arrow was placed at the top of the menu IFF there were lines off */
185     /*  the top, similarly for the bottom. Clicking on either one would scroll */
186     /*  in the indicated directions */
187     GGadget *vsb;
188 } GMenu;
189 
190 static char*
translate_shortcut(int i,char * modifier)191 translate_shortcut (int i, char *modifier)
192 {
193   char buffer[32];
194   char *temp;
195   TRACE("translate_shortcut(top) i:%d modifier:%s\n", i, modifier );
196 
197   sprintf (buffer, "Flag0x%02x", 1 << i);
198   temp = dgettext (GMenuGetShortcutDomain (), buffer);
199 
200   if (strcmp (temp, buffer) != 0)
201       modifier = temp;
202   else
203       modifier = dgettext (GMenuGetShortcutDomain (), modifier);
204 
205   TRACE("translate_shortcut(end) i:%d modifier:%s\n", i, modifier );
206 
207   return modifier;
208 }
209 
210 
211 
_shorttext(int shortcut,int short_mask,unichar_t * buf)212 static void _shorttext(int shortcut, int short_mask, unichar_t *buf) {
213     unichar_t *pt = buf;
214     static int initted = false;
215     struct { int mask; char *modifier; } mods[8] = {
216 	{ ksm_shift, H_("Shift+") },
217 	{ ksm_capslock, H_("CapsLk+") },
218 	{ ksm_control, H_("Ctrl+") },
219 	{ ksm_meta, H_("Alt+") },
220 	{ 0x10, H_("Flag0x10+") },
221 	{ 0x20, H_("Flag0x20+") },
222 	{ 0x40, H_("Flag0x40+") },
223 	{ 0x80, H_("Flag0x80+") }
224 	};
225     int i;
226     char buffer[32];
227 
228     uc_strcpy(pt,"xx⎇");
229     pt += u_strlen(pt);
230     *pt = '\0';
231     return;
232 
233     if ( !initted )
234     {
235 	/* char *temp; */
236 	for ( i=0; i<8; ++i )
237 	{
238 	    /* sprintf( buffer,"Flag0x%02x", 1<<i ); */
239 	    /* temp = dgettext(GMenuGetShortcutDomain(),buffer); */
240 	    /* if ( strcmp(temp,buffer)!=0 ) */
241 	    /* 	mods[i].modifier = temp; */
242 	    /* else */
243 	    /* 	mods[i].modifier = dgettext(GMenuGetShortcutDomain(),mods[i].modifier); */
244 
245           if (mac_menu_icons)
246 	  {
247 	      TRACE("mods[i].mask: %s\n", mods[i].modifier );
248 
249               if (mods[i].mask == ksm_cmdmacosx)
250 		  mods[i].modifier = "⌘";
251               else if (mods[i].mask == ksm_control)
252 		  mods[i].modifier = "⌃";
253               else if (mods[i].mask == ksm_meta)
254 		  mods[i].modifier = "⎇";
255               else if (mods[i].mask == ksm_shift)
256 		  mods[i].modifier = "⇧";
257               else
258 		  mods[i].modifier = translate_shortcut (i, mods[i].modifier);
259 	  }
260 	  else
261 	  {
262               translate_shortcut (i, mods[i].modifier);
263 	  }
264 
265 
266 
267 
268 	}
269 	/* It used to be that the Command key was available to X on the mac */
270 	/*  but no longer. So we used to use it, but we can't now */
271 	/* It's sort of available. X11->Preferences->Input->Enable Keyboard shortcuts under X11 needs to be OFF */
272 	/* if ( strcmp(mods[2].modifier,"Ctl+")==0 ) */
273 	    /* mods[2].modifier = keyboard!=kb_mac?"Ctl+":"Cmd+"; */
274 	if ( strcmp(mods[3].modifier,"Alt+")==0 )
275 	    mods[3].modifier = keyboard==kb_ibm?"Alt+":keyboard==kb_mac?"Opt+":keyboard==kb_ppc?"Cmd+":"Meta+";
276     }
277 
278 
279     if ( shortcut==0 ) {
280 	*pt = '\0';
281 return;
282     }
283 
284     for ( i=7; i>=0 ; --i ) {
285 	if ( short_mask&(1<<i) ) {
286 	    uc_strcpy(pt,mods[i].modifier);
287 	    pt += u_strlen(pt);
288 	}
289     }
290 
291 
292     if ( shortcut>=0xff00 && GDrawKeysyms[shortcut-0xff00] ) {
293     	cu_strcpy(buffer,GDrawKeysyms[shortcut-0xff00]);
294     	utf82u_strcpy(pt,dgettext(GMenuGetShortcutDomain(),buffer));
295     } else {
296     	*pt++ = islower(shortcut)?toupper(shortcut):shortcut;
297     	*pt = '\0';
298     }
299 }
300 
301 
302 /*
303  * Unused
304 static void shorttext(GMenuItem *gi,unichar_t *buf) {
305     _shorttext(gi->shortcut,gi->short_mask,buf);
306 }
307 */
308 
GMenuGetMenuPathRecurse(GMenuItem ** stack,GMenuItem * basemi,GMenuItem * targetmi)309 static int GMenuGetMenuPathRecurse( GMenuItem** stack,
310 				    GMenuItem *basemi,
311 				    GMenuItem *targetmi ) {
312     GMenuItem *mi = basemi;
313     int i;
314     for ( i=0; mi[i].ti.text || mi[i].ti.text_untranslated || mi[i].ti.image || mi[i].ti.line; ++i ) {
315 //	TRACE("text_untranslated: %s\n", mi[i].ti.text_untranslated );
316 	if ( mi[i].sub ) {
317 //	    TRACE("GMenuGetMenuPathRecurse() going down on %s\n", u_to_c(mi[i].ti.text));
318 	    stack[0] = &mi[i];
319 	    int rc = GMenuGetMenuPathRecurse( &stack[1], mi[i].sub, targetmi );
320 	    if( rc == 1 )
321 		return rc;
322 	    stack[0] = 0;
323 	}
324 	if( mi[i].ti.text ) {
325 //	    TRACE("GMenuGetMenuPathRecurse() inspecting %s %p %p\n", u_to_c(mi[i].ti.text),mi[i],targetmi);
326 	    GMenuItem* cur = &mi[i];
327 	    if( cur == targetmi ) {
328 		stack[0] = cur;
329 		return 1;
330 	    }
331 	}
332     }
333     return 0;
334 }
335 
336 /**
337  * Get a string describing the path from basemi to the targetmi.
338  * For example, if the basemi is the first mi in the whole menu structure
339  * and targetmi is to the edit / select all menu item then the return
340  * value is:
341  * edit.select all
342  *
343  * If you can't get to targetmi from basemi then return 0.
344  * If something doesn't make sense then return 0.
345  *
346  * Do not free the return value, it is a pointer into a buffer owned
347  * by this function.
348  */
GMenuGetMenuPath(GMenuItem * basemi,GMenuItem * targetmi)349 static char* GMenuGetMenuPath( GMenuItem *basemi, GMenuItem *targetmi ) {
350     GMenuItem* stack[1024];
351     memset(stack, 0, sizeof(stack));
352     if( !targetmi->ti.text )
353 	return 0;
354 
355 //    TRACE("GMenuGetMenuPath() base   %s\n", u_to_c(basemi->ti.text));
356 //    TRACE("GMenuGetMenuPath() target %s\n", u_to_c(targetmi->ti.text));
357 
358     /* { */
359     /* 	int i=0; */
360     /* 	GMenuItem *mi = basemi; */
361     /* 	for ( i=0; mi[i].ti.text!=NULL || mi[i].ti.image!=NULL || mi[i].ti.line; ++i ) { */
362     /* 	    if( mi[i].ti.text ) { */
363     /* 		TRACE("GMenuGetMenuPath() xbase   %s\n", u_to_c(mi[i].ti.text)); */
364     /* 	    } */
365     /* 	} */
366     /* } */
367     /* TRACE("GMenuGetMenuPath() starting...\n"); */
368 
369     GMenuItem *mi = basemi;
370     int i;
371     for ( i=0; mi[i].ti.text!=NULL || mi[i].ti.image!=NULL || mi[i].ti.line; ++i ) {
372 	if( mi[i].ti.text ) {
373 	    memset(stack, 0, sizeof(stack));
374 //	    TRACE("GMenuGetMenuPath() xbase   %s\n", u_to_c(mi[i].ti.text));
375 //	    TRACE("GMenuGetMenuPath() untrans %s\n", mi[i].ti.text_untranslated);
376 	    GMenuGetMenuPathRecurse( stack, &mi[i], targetmi );
377 	    if( stack[0] != 0 ) {
378 //		TRACE("GMenuGetMenuPath() have stack[0]...\n");
379 		break;
380 	    }
381 	}
382     }
383     if( stack[0] == 0 ) {
384 	return 0;
385     }
386 
387     static char buffer[PATH_MAX];
388     buffer[0] = '\0';
389     for( i=0; stack[i]; i++ ) {
390 	if( i )
391 	    strcat(buffer,".");
392 	if( stack[i]->ti.text_untranslated )
393 	{
394             char* tmp = HKTextInfoToUntranslatedTextFromTextInfo( &stack[i]->ti );
395 //	    TRACE("adding %s\n", HKTextInfoToUntranslatedTextFromTextInfo( &stack[i]->ti ));
396             strcat( buffer, tmp);
397             free(tmp);
398 	}
399 	else if( stack[i]->ti.text )
400 	{
401 	    cu_strcat(buffer,stack[i]->ti.text);
402 	}
403 //	TRACE("GMenuGetMenuPath() stack at i:%d  mi %s\n", i, u_to_c(stack[i]->ti.text));
404     }
405     return buffer;
406 }
407 
GMenuDrawCheckMark(struct gmenu * m,Color fg,int ybase,int r2l)408 static void GMenuDrawCheckMark(struct gmenu *m, Color fg, int ybase, int r2l) {
409     int as = m->as;
410     int pt = GDrawPointsToPixels(m->w,1);
411     int x = r2l ? m->width-m->tioff+2*pt : m->tickoff;
412 
413     while ( pt>1 && 2*pt>=as/3 ) --pt;
414     GDrawSetLineWidth(m->w,pt);
415     GDrawDrawLine(m->w,x+2*pt,ybase-as/3,x+as/3,ybase-2*pt,fg);
416     GDrawDrawLine(m->w,x+2*pt,ybase-as/3-pt,x+as/3,ybase-3*pt,fg);
417     GDrawDrawLine(m->w,x+as/3,ybase-2*pt,x+as/3+as/5,ybase-2*pt-as/4,fg);
418     GDrawDrawLine(m->w,x+as/3+as/5,ybase-2*pt-as/4,x+as/3+2*as/5,ybase-2*pt-as/3-as/7,fg);
419     GDrawDrawLine(m->w,x+as/3+2*as/5,ybase-2*pt-as/3-as/7,x+as/3+3*as/5,ybase-2*pt-as/3-as/7-as/8,fg);
420 }
421 
GMenuDrawUncheckMark(struct gmenu * m,Color fg,int ybase,int r2l)422 static void GMenuDrawUncheckMark(struct gmenu *m, Color fg, int ybase, int r2l) {
423 }
424 
GMenuDrawArrow(struct gmenu * m,int ybase,int r2l)425 static void GMenuDrawArrow(struct gmenu *m, int ybase, int r2l) {
426     int pt = GDrawPointsToPixels(m->w,1);
427     int as = 2*(m->as/2);
428     int x = r2l ? m->bp+2*pt : m->rightedge-2*pt;
429     GPoint p[3];
430 
431     GDrawSetLineWidth(m->w,pt);
432     if ( r2l ) {
433 	p[0].x = x;			p[0].y = ybase-as/2;
434 	p[1].x = x+1*(as/2);		p[1].y = ybase;
435 	p[2].x = p[1].x;		p[2].y = ybase-as;
436 
437 	// If rendering menus in standard (3-dimensional) look, use the shadow colors for fake relief.
438 	// Otherwise, use foreground colors.
439 	if (menu_3d_look) {
440 		GDrawDrawLine(m->w,p[0].x,p[0].y,p[2].x,p[2].y,m->box->border_brighter);
441 		GDrawDrawLine(m->w,p[0].x+pt,p[0].y,p[2].x+pt,p[2].y+pt,m->box->border_brighter);
442 		GDrawDrawLine(m->w,p[1].x,p[1].y,p[0].x,p[0].y,m->box->border_darkest);
443 		GDrawDrawLine(m->w,p[1].x+pt,p[1].y-pt,p[0].x-pt,p[0].y,m->box->border_darkest);
444 	} else {
445 		GDrawDrawLine(m->w,p[0].x,p[0].y,p[2].x,p[2].y,m->box->main_foreground);
446 		GDrawDrawLine(m->w,p[0].x+pt,p[0].y,p[2].x+pt,p[2].y+pt,m->box->main_foreground);
447 		GDrawDrawLine(m->w,p[1].x,p[1].y,p[0].x,p[0].y,m->box->main_foreground);
448 		GDrawDrawLine(m->w,p[1].x+pt,p[1].y-pt,p[0].x-pt,p[0].y,m->box->main_foreground);
449 	}
450     } else {
451 	p[0].x = x;			p[0].y = ybase-as/2;
452 	p[1].x = x-1*(as/2);		p[1].y = ybase;
453 	p[2].x = p[1].x;		p[2].y = ybase-as;
454 
455 	if (menu_3d_look) {
456 		GDrawDrawLine(m->w,p[0].x,p[0].y,p[2].x,p[2].y,m->box->border_brighter);
457 		GDrawDrawLine(m->w,p[0].x-pt,p[0].y,p[2].x+pt,p[2].y+pt,m->box->border_brighter);
458 		GDrawDrawLine(m->w,p[1].x,p[1].y,p[0].x,p[0].y,m->box->border_darkest);
459 		GDrawDrawLine(m->w,p[1].x+pt,p[1].y-pt,p[0].x-pt,p[0].y,m->box->border_darkest);
460 	} else {
461 		GDrawDrawLine(m->w,p[0].x,p[0].y,p[2].x,p[2].y,m->box->main_foreground);
462 		GDrawDrawLine(m->w,p[0].x-pt,p[0].y,p[2].x+pt,p[2].y+pt,m->box->main_foreground);
463 		GDrawDrawLine(m->w,p[1].x,p[1].y,p[0].x,p[0].y,m->box->main_foreground);
464 		GDrawDrawLine(m->w,p[1].x+pt,p[1].y-pt,p[0].x-pt,p[0].y,m->box->main_foreground);
465 	}
466     }
467 }
468 
GMenuDrawUpArrow(struct gmenu * m,int ybase)469 static void GMenuDrawUpArrow(struct gmenu *m, int ybase) {
470     int pt = GDrawPointsToPixels(m->w,1);
471     int x = (m->rightedge+m->tickoff)/2;
472     int as = 2*(m->as/2);
473     GPoint p[3];
474 
475     p[0].x = x;			p[0].y = ybase - as;
476     p[1].x = x-as;		p[1].y = ybase;
477     p[2].x = x+as;		p[2].y = ybase;
478 
479     GDrawSetLineWidth(m->w,pt);
480 
481     // If rendering menus in standard (3-dimensional) look, use the shadow colors for fake relief.
482     // Otherwise, use foreground colors.
483     if (menu_3d_look) {
484         GDrawDrawLine(m->w,p[0].x,p[0].y,p[1].x,p[1].y,m->box->border_brightest);
485         GDrawDrawLine(m->w,p[0].x,p[0].y+pt,p[1].x+pt,p[1].y,m->box->border_brightest);
486         GDrawDrawLine(m->w,p[1].x,p[1].y,p[2].x,p[2].y,m->box->border_darker);
487         GDrawDrawLine(m->w,p[1].x+pt,p[1].y,p[2].x-pt,p[2].y,m->box->border_darker);
488         GDrawDrawLine(m->w,p[2].x,p[2].y,p[0].x,p[0].y,m->box->border_darkest);
489         GDrawDrawLine(m->w,p[2].x-pt,p[2].y,p[0].x,p[0].y+pt,m->box->border_darkest);
490     } else {
491         GDrawDrawLine(m->w,p[0].x,p[0].y,p[1].x,p[1].y,m->box->main_foreground);
492         GDrawDrawLine(m->w,p[0].x,p[0].y+pt,p[1].x+pt,p[1].y,m->box->main_foreground);
493         GDrawDrawLine(m->w,p[1].x,p[1].y,p[2].x,p[2].y,m->box->main_foreground);
494         GDrawDrawLine(m->w,p[1].x+pt,p[1].y,p[2].x-pt,p[2].y,m->box->main_foreground);
495         GDrawDrawLine(m->w,p[2].x,p[2].y,p[0].x,p[0].y,m->box->main_foreground);
496         GDrawDrawLine(m->w,p[2].x-pt,p[2].y,p[0].x,p[0].y+pt,m->box->main_foreground);
497     }
498 }
499 
GMenuDrawDownArrow(struct gmenu * m,int ybase)500 static void GMenuDrawDownArrow(struct gmenu *m, int ybase) {
501     int pt = GDrawPointsToPixels(m->w,1);
502     int x = (m->rightedge+m->tickoff)/2;
503     int as = 2*(m->as/2);
504     GPoint p[3];
505 
506     p[0].x = x;			p[0].y = ybase;
507     p[1].x = x-as;		p[1].y = ybase - as;
508     p[2].x = x+as;		p[2].y = ybase - as;
509 
510     GDrawSetLineWidth(m->w,pt);
511 
512     // If rendering menus in standard (3-dimensional) look, use the shadow colors for fake relief.
513     // Otherwise, use foreground colors.
514     if (menu_3d_look) {
515         GDrawDrawLine(m->w,p[0].x,p[0].y,p[1].x,p[1].y,m->box->border_darker);
516         GDrawDrawLine(m->w,p[0].x,p[0].y+pt,p[1].x+pt,p[1].y,m->box->border_darker);
517         GDrawDrawLine(m->w,p[1].x,p[1].y,p[2].x,p[2].y,m->box->border_brightest);
518         GDrawDrawLine(m->w,p[1].x+pt,p[1].y,p[2].x-pt,p[2].y,m->box->border_brightest);
519         GDrawDrawLine(m->w,p[2].x,p[2].y,p[0].x,p[0].y,m->box->border_darkest);
520         GDrawDrawLine(m->w,p[2].x-pt,p[2].y,p[0].x,p[0].y+pt,m->box->border_darkest);
521     } else {
522         GDrawDrawLine(m->w,p[0].x,p[0].y,p[1].x,p[1].y,m->box->main_foreground);
523         GDrawDrawLine(m->w,p[0].x,p[0].y+pt,p[1].x+pt,p[1].y,m->box->main_foreground);
524         GDrawDrawLine(m->w,p[1].x,p[1].y,p[2].x,p[2].y,m->box->main_foreground);
525         GDrawDrawLine(m->w,p[1].x+pt,p[1].y,p[2].x-pt,p[2].y,m->box->main_foreground);
526         GDrawDrawLine(m->w,p[2].x,p[2].y,p[0].x,p[0].y,m->box->main_foreground);
527         GDrawDrawLine(m->w,p[2].x-pt,p[2].y,p[0].x,p[0].y+pt,m->box->main_foreground);
528     }
529 }
530 
531 /**
532  * Return the menu bar at the top of this menu list
533  * */
getTopLevelMenubar(struct gmenu * m)534 static GMenuBar * getTopLevelMenubar( struct gmenu *m ) {
535     while( m->parent ) {
536 	m = m->parent;
537     }
538     return m->menubar;
539 }
540 
541 
GMenuDrawMenuLine(struct gmenu * m,GMenuItem * mi,int y,GWindow pixmap)542 static int GMenuDrawMenuLine(struct gmenu *m, GMenuItem *mi, int y,GWindow pixmap) {
543     unichar_t shortbuf[300];
544     int as = GTextInfoGetAs(m->w,&mi->ti,m->font);
545     int h, width;
546     Color fg = m->box->main_foreground;
547     GRect old, new;
548     int ybase = y+as;
549     int r2l = false;
550     int x;
551 
552     //printf("GMenuDrawMenuLine(top)\n");
553     /* if(mi->ti.text) */
554     /* 	TRACE("GMenuDrawMenuLine() mi:%s\n",u_to_c(mi->ti.text)); */
555 
556     new.x = m->tickoff; new.width = m->rightedge-m->tickoff;
557     new.y = y; new.height = GTextInfoGetHeight(pixmap,&mi->ti,m->font);
558     GDrawPushClip(pixmap,&new,&old);
559 
560     if ( mi->ti.fg!=COLOR_DEFAULT && mi->ti.fg!=COLOR_UNKNOWN )
561 	fg = mi->ti.fg;
562     if ( mi->ti.disabled || m->disabled )
563 	fg = m->box->disabled_foreground;
564     if ( fg==COLOR_DEFAULT )
565 	fg = GDrawGetDefaultForeground(GDrawGetDisplayOfWindow(pixmap));
566     if ( mi->ti.text!=NULL && isrighttoleft(mi->ti.text[0]) )
567 	r2l = true;
568 
569     if ( r2l )
570 	x = m->width-m->tioff-GTextInfoGetWidth(pixmap,&mi->ti,m->font);
571     else
572 	x = m->tioff;
573     h = GTextInfoDraw(pixmap,x,y,&mi->ti,m->font,
574 	    (mi->ti.disabled || m->disabled )?m->box->disabled_foreground:fg,
575 	    m->box->active_border,new.y+new.height);
576     if ( mi->ti.checkable ) {
577 	if ( mi->ti.checked )
578 	    GMenuDrawCheckMark(m,fg,ybase,r2l);
579 	else
580 	    GMenuDrawUncheckMark(m,fg,ybase,r2l);
581     }
582 
583     if ( mi->sub!=NULL )
584 	GMenuDrawArrow(m,ybase,r2l);
585     else
586     {
587 	_shorttext(mi->shortcut,0,shortbuf);
588 	uint16 short_mask = mi->short_mask;
589 
590 	/* TRACE("m->menubar: %p\n", m->menubar ); */
591 	/* TRACE("m->parent: %p\n", m->parent ); */
592 	/* TRACE("m->toplevel: %p\n", getTopLevelMenubar(m)); */
593 
594 	/**
595 	 * Grab the hotkey if there is one. First we work out the
596 	 * menubar for this menu item, and then get the path from the
597 	 * menubar to the menuitem and pass that to the hotkey system
598 	 * to get the hotkey if there is one for that menu item.
599 	 */
600 	GMenuBar* toplevel = getTopLevelMenubar(m);
601 	Hotkey* hk = 0;
602 	if( toplevel )
603 	{
604 	    hk = hotkeyFindByMenuPath( toplevel->g.base,
605 				       GMenuGetMenuPath( toplevel->mi, mi ));
606 	}
607 	else if( m->owner && strlen(m->subMenuName) )
608 	{
609 	    hk = hotkeyFindByMenuPathInSubMenu( m->owner, m->subMenuName,
610 						GMenuGetMenuPath( m->mi, mi ));
611 	}
612 
613 	short_mask = 0;
614 	uc_strcpy(shortbuf,"");
615 
616 	if( hk )
617 	{
618 	    /* TRACE("m->menubar->mi: %p\n", toplevel->mi ); */
619 	    /* TRACE("m->menubar->window: %p\n", toplevel->g.base ); */
620 	    /* TRACE("drawline... hk: %p\n", hk ); */
621 	    short_mask = hk->state;
622 	    char* keydesc = hk->text;
623 	    if( mac_menu_icons )
624 	    {
625 		keydesc = hotkeyTextToMacModifiers( keydesc );
626 	    }
627 	    utf82u_strcpy( shortbuf, keydesc );
628 	    if( keydesc != hk->text )
629 		free( keydesc );
630 	}
631 
632 	width = GDrawGetTextWidth(pixmap,shortbuf,-1);
633 	if ( r2l )
634 	{
635 	    GDrawDrawText(pixmap,m->bp,ybase,shortbuf,-1,fg);
636 	}
637 	else
638 	{
639 	    int x = m->rightedge-width;
640 	    GDrawDrawText(pixmap,x,ybase,shortbuf,-1,fg);
641 	}
642     }
643 
644     GDrawPopClip(pixmap,&old);
645 return( y + h );
646 }
647 
gmenu_expose(struct gmenu * m,GEvent * event,GWindow pixmap)648 static int gmenu_expose(struct gmenu *m, GEvent *event,GWindow pixmap) {
649     GRect old1, old2;
650     GRect r;
651     int i;
652 
653     /* TRACE("gmenu_expose() m:%p ev:%p\n",m,event); */
654     GDrawPushClip(pixmap,&event->u.expose.rect,&old1);
655     r.x = 0; r.width = m->width; r.y = 0; r.height = m->height;
656     GBoxDrawBackground(pixmap,&r,m->box,gs_active,false);
657     GBoxDrawBorder(pixmap,&r,m->box,gs_active,false);
658     r.x = m->tickoff; r.width = m->rightedge-m->tickoff;
659     r.y = m->bp; r.height = m->height - 2*m->bp;
660     GDrawPushClip(pixmap,&r,&old2);
661     for ( i = event->u.expose.rect.y/m->fh+m->offtop; i<m->mcnt &&
662 	    i<=(event->u.expose.rect.y+event->u.expose.rect.height)/m->fh+m->offtop;
663 	    ++i ) {
664 	if ( i==m->offtop && m->offtop!=0 && m->vsb==NULL )
665 	    GMenuDrawUpArrow(m, m->bp+m->as);
666 	else if ( m->lcnt!=m->mcnt && i==m->lcnt+m->offtop-1 && i!=m->mcnt-1 ) {
667 	    if ( m->vsb==NULL )
668 		GMenuDrawDownArrow(m, m->bp+(i-m->offtop)*m->fh+m->as);
669 	    else
670 		GMenuDrawMenuLine(m, &m->mi[i], m->bp+(i-m->offtop)*m->fh, pixmap);
671     break;	/* Otherwise we get bits of the line after the last */
672 	} else
673 	    GMenuDrawMenuLine(m, &m->mi[i], m->bp+(i-m->offtop)*m->fh, pixmap);
674     }
675     GDrawPopClip(pixmap,&old2);
676     GDrawPopClip(pixmap,&old1);
677 return( true );
678 }
679 
GMenuDrawLines(struct gmenu * m,int ln,int cnt)680 static void GMenuDrawLines(struct gmenu *m, int ln, int cnt) {
681     GRect r, old1, old2, winrect;
682 
683     winrect.x = 0; winrect.width = m->width; winrect.y = 0; winrect.height = m->height;
684     r = winrect; r.height = cnt*m->fh;
685     r.y = (ln-m->offtop)*m->fh+m->bp;
686     GDrawPushClip(m->w,&r,&old1);
687     GBoxDrawBackground(m->w,&winrect,m->box,gs_active,false);
688     GBoxDrawBorder(m->w,&winrect,m->box,gs_active,false);
689     r.x = m->tickoff; r.width = m->rightedge-r.x;
690     GDrawPushClip(m->w,&r,&old2);
691     cnt += ln;
692     for ( ; ln<cnt; ++ln )
693 	GMenuDrawMenuLine(m, &m->mi[ln], m->bp+(ln-m->offtop)*m->fh,m->w);
694     GDrawPopClip(m->w,&old2);
695     GDrawPopClip(m->w,&old1);
696 }
697 
GMenuSetPressed(struct gmenu * m,int pressed)698 static void GMenuSetPressed(struct gmenu *m, int pressed) {
699     while ( m->child!=NULL ) m = m->child;
700     while ( m->parent!=NULL ) {
701 	m->pressed = pressed;
702 	m = m->parent;
703     }
704     m->pressed = pressed;
705     if ( m->menubar!=NULL )
706 	m->menubar->pressed = pressed;
707 }
708 
_GMenuDestroy(struct gmenu * m)709 static void _GMenuDestroy(struct gmenu *m) {
710     if ( m->dying )
711 return;
712     m->dying = true;
713     if ( m->line_with_mouse!=-1 )
714 	m->mi[m->line_with_mouse].ti.selected = false;
715     if ( m->child!=NULL )
716 	_GMenuDestroy(m->child);
717     if ( m->parent!=NULL )
718 	m->parent->child = NULL;
719     else if ( m->menubar!=NULL ) {
720 	m->menubar->child = NULL;
721 	m->menubar->pressed = false;
722 	_GWidget_ClearPopupOwner((GGadget *) (m->menubar));
723 	_GWidget_ClearGrabGadget((GGadget *) (m->menubar));
724 	GMenuBarChangeSelection(m->menubar,-1,NULL);
725     }
726     GDrawDestroyWindow(m->w);
727     /* data are freed when we get the destroy event !!!! */
728 }
729 
GMenuDestroy(struct gmenu * m)730 static void GMenuDestroy(struct gmenu *m) {
731     GDrawPointerUngrab(GDrawGetDisplayOfWindow(m->w));
732     if ( menu_grabs && m->parent!=NULL )
733 	GDrawPointerGrab(m->parent->w);
734     _GMenuDestroy(m);
735 }
736 
GMenuHideAll(struct gmenu * m)737 static void GMenuHideAll(struct gmenu *m) {
738     if ( m!=NULL ) {
739 	struct gmenu *s = m;
740 	GDrawPointerUngrab(GDrawGetDisplayOfWindow(m->w));
741 	while ( m->parent ) m = m->parent;
742 	while ( m ) {
743 	    m->hidden = true;
744 	    GDrawSetVisible(m->w,false);
745 	    m=m->child;
746 	}
747 	GDrawSync(GDrawGetDisplayOfWindow(s->w));
748 	GDrawProcessPendingEvents(GDrawGetDisplayOfWindow(s->w));
749     }
750 }
751 
GMenuDismissAll(struct gmenu * m)752 static void GMenuDismissAll(struct gmenu *m) {
753     if ( m!=NULL ) {
754 	while ( m->parent ) m = m->parent;
755 	GMenuDestroy(m);
756     }
757 }
758 
UnsetInitialPress(struct gmenu * m)759 static void UnsetInitialPress(struct gmenu *m) {
760     while ( m!=NULL ) {
761 	m->initial_press = false;
762 	if ( m->menubar!=NULL ) m->menubar->initial_press = false;
763 	m = m->parent;
764     }
765 }
766 
GMenuChangeSelection(struct gmenu * m,int newsel,GEvent * event)767 static void GMenuChangeSelection(struct gmenu *m, int newsel,GEvent *event) {
768     int old=m->line_with_mouse;
769 
770     if ( old==newsel )
771 return;
772     if ( newsel==m->mcnt )
773 return;
774 
775     if ( m->child!=NULL ) {
776 	GMenuDestroy(m->child);
777 	m->child = NULL;
778     }
779     UnsetInitialPress(m);
780     m->line_with_mouse = newsel;
781     if ( newsel!=-1 )
782 	m->mi[newsel].ti.selected = true;
783     if ( old!=-1 )
784 	m->mi[old].ti.selected = false;
785 
786     if ( newsel==old+1 && old!=-1 ) {
787 	GMenuDrawLines(m,old,2);
788     } else if ( old==newsel+1 && newsel!=-1 ) {
789 	GMenuDrawLines(m,newsel,2);
790     } else {
791 	if ( newsel!=-1 )
792 	    GMenuDrawLines(m,newsel,1);
793 	if ( old!=-1 )
794 	    GMenuDrawLines(m,old,1);
795     }
796     if ( newsel!=-1 ) {
797 	if ( m->mi[newsel].moveto!=NULL )
798 	    (m->mi[newsel].moveto)(m->owner,&m->mi[newsel],event);
799 	if ( m->mi[newsel].sub!=NULL )
800 	    m->child = GMenuCreateSubMenu(m,m->mi[newsel].sub,
801 		    m->disabled || m->mi[newsel].ti.disabled);
802     }
803 }
804 
GMenuBarChangeSelection(GMenuBar * mb,int newsel,GEvent * event)805 static void GMenuBarChangeSelection(GMenuBar *mb, int newsel,GEvent *event) {
806     int old=mb->entry_with_mouse;
807     GMenuItem *mi;
808 
809     if ( old==newsel )
810 return;
811     if ( mb->child!=NULL ) {
812 	int waspressed = mb->pressed;
813 	GMenuDestroy(mb->child);
814 	mb->child = NULL;
815 	mb->pressed = waspressed;
816     }
817     mb->entry_with_mouse = newsel;
818     if ( newsel!=-1 )
819 	mb->mi[newsel].ti.selected = true;
820     if ( old!=-1 )
821 	mb->mi[old].ti.selected = false;
822 
823     _ggadget_redraw(&mb->g);
824     if ( newsel!=-1 ) {
825 	mi = newsel==mb->lastmi ? mb->fake : &mb->mi[newsel];
826 	if ( mi->moveto!=NULL )
827 	    (mi->moveto)(mb->g.base,mi,event);
828 	if ( mi->sub!=NULL )
829 	    mb->child = GMenuCreatePulldownMenu(mb,mi->sub,mi->ti.disabled);
830     }
831 }
832 
MParentInitialPress(struct gmenu * m)833 static int MParentInitialPress(struct gmenu *m) {
834     if ( m->parent!=NULL )
835 return( m->parent->initial_press );
836     else if ( m->menubar!=NULL )
837 return( m->menubar->initial_press );
838 
839 return( false );
840 }
841 
gmenu_mouse(struct gmenu * m,GEvent * event)842 static int gmenu_mouse(struct gmenu *m, GEvent *event) {
843     GPoint p;
844     struct gmenu *testm;
845 
846     if ( m->hidden || (m->child!=NULL && m->child->hidden))
847 return( true );
848 
849     if ( event->type == et_crossing ) {
850 	if ( !event->u.crossing.entered )
851 	    UnsetInitialPress(m);
852 return( true );
853     } else if ((event->type==et_mouseup || event->type==et_mousedown) &&
854                (event->u.mouse.button>=4 && event->u.mouse.button<=7) ) {
855         //From scrollbar.c: X treats a scroll as a mousedown/mouseup event
856         return GGadgetDispatchEvent(m->vsb,event);
857     }
858 
859     p.x = event->u.mouse.x; p.y = event->u.mouse.y;
860 
861     for ( testm=m; testm->child!=NULL; testm = testm->child );
862     if ( testm->scrollit && testm!=m ) {
863 	GDrawCancelTimer(testm->scrollit);
864 	testm->scrollit = NULL;
865     }
866     for ( ; testm!=NULL; testm=testm->parent )
867 	if ( GDrawEventInWindow(testm->w,event) )
868     break;
869 
870     if ( testm!=m && testm!=NULL ) {
871 	GDrawPointerGrab(testm->w);
872 	GDrawTranslateCoordinates(m->w,testm->w,&p);
873 	m = testm;
874     } else if ( testm==NULL /*&& event->u.mouse.y<0*/ ) {/* menubars can be below the menu if no room on screen */
875 	for ( testm=m; testm->parent!=NULL; testm=testm->parent );
876 	if ( testm->menubar!=NULL ) {
877 	    GDrawTranslateCoordinates(m->w,testm->menubar->g.base,&p);
878 	    if ( p.x>=0 && p.y>=0 &&
879 		    p.x<testm->menubar->g.inner.x+testm->menubar->g.inner.width &&
880 		    p.y<testm->menubar->g.inner.y+testm->menubar->g.inner.height ) {
881 		/*GDrawPointerGrab(testm->menubar->g.base);*/	/* Don't do this */
882 		event->u.mouse.x = p.x; event->u.mouse.y = p.y;
883 return( (GDrawGetEH(testm->menubar->g.base))(testm->menubar->g.base,event));
884 	    }
885 	}
886 	testm = NULL;
887     }
888     if ( testm==NULL ) {
889 	if ( event->type==et_mousedown )
890 	    GMenuDismissAll(m);
891 	else if ( event->type==et_mouseup )
892 	    GMenuSetPressed(m,false);
893 	else if ( m->pressed )
894 	    GMenuChangeSelection(m,-1,event);
895 return( true );
896     }
897 
898     event->u.mouse.x = p.x; event->u.mouse.y = p.y; event->w = m->w;
899     if (( m->pressed && event->type==et_mousemove ) ||
900 	    event->type == et_mousedown ) {
901 	int l = (event->u.mouse.y-m->bp)/m->fh;
902 	int i = l + m->offtop;
903 	if ( m->scrollit!=NULL )
904 	    ;
905 	else if ( event->u.mouse.y<m->bp && event->type==et_mousedown )
906 	    GMenuDismissAll(m);
907 	else if ( l==0 && m->offtop!=0 && m->vsb==NULL ) {
908 	    GMenuChangeSelection(m,-1,event);
909 	    if ( m->scrollit==NULL )
910 		m->scrollit = GDrawRequestTimer(m->w,1,_GScrollBar_RepeatTime,m);
911 	    m->scrollup = true;
912 	} else if ( l>=m->lcnt-1 && m->offtop+m->lcnt<m->mcnt && m->vsb==NULL ) {
913 	    GMenuChangeSelection(m,-1,event);
914 	    if ( m->scrollit==NULL )
915 		m->scrollit = GDrawRequestTimer(m->w,1,_GScrollBar_RepeatTime,m);
916 	    m->scrollup = false;
917 	} else if ( event->type == et_mousedown && m->child!=NULL &&
918 		i == m->line_with_mouse ) {
919 	    GMenuChangeSelection(m,-1,event);
920 	} else if ( i >= m->mcnt ){
921 	    GMenuChangeSelection(m,-1,event);
922 	} else
923 	    GMenuChangeSelection(m,i,event);
924 	if ( event->type == et_mousedown ) {
925 	    GMenuSetPressed(m,true);
926 	    if ( m->child!=NULL )
927 		m->initial_press = true;
928 	}
929     } else if ( event->type == et_mouseup && m->child==NULL ) {
930 	if ( m->scrollit!=NULL ) {
931 	    GDrawCancelTimer(m->scrollit);
932 	    m->scrollit = NULL;
933 	} else if ( event->u.mouse.y>=m->bp && event->u.mouse.x>=0 &&
934 		event->u.mouse.y<m->height-m->bp &&
935 		event->u.mouse.x < m->width &&
936 		!MParentInitialPress(m)) {
937 	    int l = (event->u.mouse.y-m->bp)/m->fh;
938 	    int i = l + m->offtop;
939 	    if ( !( l==0 && m->offtop!=0 && m->vsb==NULL ) &&
940 		    !( l==m->lcnt-1 && m->offtop+m->lcnt<m->mcnt && m->vsb==NULL ) &&
941 		    !m->disabled &&
942 		    !m->mi[i].ti.disabled && !m->mi[i].ti.line ) {
943 		if ( m->mi[i].ti.checkable )
944 		    m->mi[i].ti.checked = !m->mi[i].ti.checked;
945 		GMenuHideAll(m);
946 		GMenuDismissAll(m);
947 		if ( m->mi[i].invoke!=NULL )
948 		    (m->mi[i].invoke)(m->owner,&m->mi[i],event);
949 	    }
950 	}
951     } else if ( event->type == et_mouseup ) {
952 	UnsetInitialPress(m);
953 	GMenuSetPressed(m,false);
954     } else
955 return( false );
956 
957 return( true );
958 }
959 
gmenu_timer(struct gmenu * m,GEvent * event)960 static int gmenu_timer(struct gmenu *m, GEvent *event) {
961     if ( m->scrollup ) {
962 	if ( m->offtop==0 )
963 return(true);
964 	if ( --m->offtop<0 ) m->offtop = 0;
965     } else {
966 	if ( m->offtop == m->mcnt-m->lcnt )
967 return( true );
968 	++m->offtop;
969 	if ( m->offtop + m->lcnt > m->mcnt )
970 	    m->offtop = m->mcnt-m->lcnt;
971     }
972     GDrawRequestExpose(m->w, NULL, false);
973 return( true );
974 }
975 
GMenuKeyInvoke(struct gmenu * m,int i)976 static int GMenuKeyInvoke(struct gmenu *m, int i) {
977     GMenuChangeSelection(m,i,NULL);
978     if ( m->mi[i].ti.checkable )
979 	m->mi[i].ti.checked = !m->mi[i].ti.checked;
980     if ( m->mi[i].sub==NULL ) {
981 	GMenuHideAll(m);
982     }
983     if ( m->mi[i].invoke!=NULL )
984 	(m->mi[i].invoke)(m->owner,&m->mi[i],NULL);
985     if ( m->mi[i].sub==NULL ) {
986 	GMenuDismissAll(m);
987     }
988 return( true );
989 }
990 
GMenuBarKeyInvoke(struct gmenubar * mb,int i)991 static int GMenuBarKeyInvoke(struct gmenubar *mb, int i) {
992     GMenuBarChangeSelection(mb,i,NULL);
993     if ( mb->mi[i].invoke!=NULL )
994 	(mb->mi[i].invoke)(mb->g.base,&mb->mi[i],NULL);
995 return( true );
996 }
997 
998 /**
999  * Remove any instances of 'ch' from 'ret' and return 'ret'
1000  *
1001  * When ch is encountered it is removed from the string, with all the
1002  * characters following it moved left to cover over the space ch used
1003  * to occupy.
1004  */
str_remove_all_single_char(char * ret,char ch)1005 static char* str_remove_all_single_char( char * ret, char ch )
1006 {
1007     char* src = ret;
1008     char* dst = ret;
1009     for( ; *src; src++ )
1010     {
1011 	if( *src == ch )
1012 	    continue;
1013 
1014 	*dst = *src;
1015 	dst++;
1016     }
1017     *dst = '\0';
1018     return ret;
1019 }
1020 
1021 /**
1022  * Given a text_untranslated which may be either the hotkey of format:
1023  *
1024  * Foo|ShortCutKey
1025  * Win*Foo|ShortCutKey
1026  * _Foo
1027  *
1028  * return just the english text for the entry, ie, Foo
1029  *
1030  * The return value is owned by this function, do not free it.
1031  */
HKTextInfoToUntranslatedText(char * text_untranslated)1032 char* HKTextInfoToUntranslatedText(char *text_untranslated) {
1033     char ret[PATH_MAX+1];
1034     char* pt;
1035     int i;
1036 
1037     strncpy(ret,text_untranslated,PATH_MAX);
1038 
1039     if( (pt=strchr(ret,'*')) )
1040         for (i=0; pt[i]!='\0'; i++)
1041             ret[i]=pt[i+1];
1042     if( (pt=strchr(ret,'|')) )
1043 	*pt = '\0';
1044     str_remove_all_single_char( ret, '_' );
1045     return copy(ret);
1046 }
1047 
1048 /**
1049  * Call HKTextInfoToUntranslatedText on ti->text_untranslated
1050  * guarding against the chance of null for ti, and ti->text_untranslated
1051  */
HKTextInfoToUntranslatedTextFromTextInfo(GTextInfo * ti)1052 char* HKTextInfoToUntranslatedTextFromTextInfo( GTextInfo* ti )
1053 {
1054     if( !ti )
1055 	return 0;
1056     if( !ti->text_untranslated )
1057 	return 0;
1058     return HKTextInfoToUntranslatedText( ti->text_untranslated );
1059 }
1060 
1061 
1062 /**
1063  * return true if the prefix matches the first segment of the given action.
1064  * For example,
1065  * return will be 0 when action = foo.bar.baz prefix = bar
1066  * return will be 0 when action = foo.bar.baz prefix = fooo
1067  * return will be 1 when action = foo.bar.baz prefix = foo
1068  * return will be 1 when action = baz prefix = baz
1069  */
HKActionMatchesFirstPartOf(char * action,char * prefix_const,int munge)1070 static int HKActionMatchesFirstPartOf( char* action, char* prefix_const, int munge )
1071 {
1072     char prefix[PATH_MAX+1];
1073     char* pt = 0;
1074     strncpy( prefix, prefix_const, PATH_MAX );
1075     if( munge ) {
1076 	char *tofree = HKTextInfoToUntranslatedText(prefix_const);
1077 	strncpy( prefix, tofree,PATH_MAX );
1078 	free(tofree);
1079     }
1080 //    TRACE("munge:%d prefix2:%s\n", munge, prefix );
1081 
1082     pt = strchr(action,'.');
1083     if( !pt )
1084 	return( 0==strcmp(action,prefix) );
1085 
1086     int l = pt - action;
1087     if( strlen(prefix) < l )
1088 	return 0;
1089     int rc = strncmp( action, prefix, l );
1090     return rc == 0;
1091 }
1092 
1093 /**
1094  * Call HKActionMatchesFirstPartOf on the given ti.
1095  */
HKActionMatchesFirstPartOfTextInfo(char * action,GTextInfo * ti)1096 static int HKActionMatchesFirstPartOfTextInfo( char* action, GTextInfo* ti )
1097 {
1098     if( ti->text_untranslated )
1099 	return HKActionMatchesFirstPartOf( action, ti->text_untranslated, 1 );
1100     if( ti->text )
1101 	return HKActionMatchesFirstPartOf( action, u_to_c(ti->text), 0 );
1102     return 0;
1103 }
1104 
HKActionPointerPastLeftmostKey(char * action)1105 static char* HKActionPointerPastLeftmostKey( char* action ) {
1106     char* pt = strchr(action,'.');
1107     if( !pt )
1108 	return 0;
1109     return pt + 1;
1110 }
1111 
1112 
1113 
GMenuSearchActionRecursive(GWindow gw,GMenuItem * mi,char * action,GEvent * event,int call_moveto)1114 static GMenuItem *GMenuSearchActionRecursive( GWindow gw,
1115 					      GMenuItem *mi,
1116 					      char* action,
1117 					      GEvent *event,
1118 					      int call_moveto) {
1119 
1120 //    TRACE("GMenuSearchAction() action:%s\n", action );
1121     int i;
1122     for ( i=0; mi[i].ti.text || mi[i].ti.text_untranslated || mi[i].ti.image || mi[i].ti.line; ++i ) {
1123 //	if( mi[i].ti.text )
1124 //	    TRACE("GMenuSearchActionRecursive() text             : %s\n", u_to_c(mi[i].ti.text) );
1125 //	TRACE("GMenuSearchActionRecursive() text_untranslated: %s\n", mi[i].ti.text_untranslated );
1126 	if ( call_moveto && mi[i].moveto != NULL)
1127 	    (mi[i].moveto)(gw,&(mi[i]),event);
1128 
1129 	if ( mi[i].sub ) {
1130 	    if( HKActionMatchesFirstPartOfTextInfo( action, &mi[i].ti )) {
1131 		char* subaction = HKActionPointerPastLeftmostKey(action);
1132 
1133 //		TRACE("GMenuSearchAction() action:%s decending menu:%s\n", action, u_to_c(mi[i].ti.text) );
1134 		GMenuItem *ret = GMenuSearchActionRecursive(gw,mi[i].sub,subaction,event,call_moveto);
1135 		if ( ret!=NULL )
1136 		    return( ret );
1137 	    }
1138 	} else {
1139 //	    TRACE("GMenuSearchAction() action:%s testing menu:%s\n", action, u_to_c(mi[i].ti.text) );
1140 	    if( HKActionMatchesFirstPartOfTextInfo( action, &mi[i].ti )) {
1141 //		TRACE("GMenuSearchAction() matching final menu part! action:%s\n", action );
1142 		return &mi[i];
1143 	    }
1144 	}
1145 
1146     }
1147 return( NULL );
1148 }
GMenuSearchAction(GWindow gw,GMenuItem * mi,char * action,GEvent * event,int call_moveto)1149 static GMenuItem *GMenuSearchAction( GWindow gw,
1150 				     GMenuItem *mi,
1151 				     char* action,
1152 				     GEvent *event,
1153 				     int call_moveto) {
1154     char* windowType = GDrawGetWindowTypeName( gw );
1155     if( !windowType )
1156 	return 0;
1157 //    TRACE("GMenuSearchAction() windowtype:%s\n", windowType );
1158     int actionlen = strlen(action);
1159     int prefixlen = strlen(windowType) + 1 + strlen("Menu.");
1160     if( actionlen < prefixlen ) {
1161 	return 0;
1162     }
1163     action += prefixlen;
1164     GMenuItem * ret = GMenuSearchActionRecursive( gw, mi, action,
1165 						  event, call_moveto );
1166     return ret;
1167 }
1168 
1169 
1170 
GMenuSearchShortcut(GWindow gw,GMenuItem * mi,GEvent * event,int call_moveto)1171 static GMenuItem *GMenuSearchShortcut(GWindow gw, GMenuItem *mi, GEvent *event,
1172 	int call_moveto) {
1173     int i;
1174     unichar_t keysym = event->u.chr.keysym;
1175 
1176     if ( keysym<GK_Special && islower(keysym))
1177 	keysym = toupper(keysym); /*getkey(keysym,event->u.chr.state&0x2000 );*/
1178     for ( i=0; mi[i].ti.text!=NULL || mi[i].ti.image!=NULL || mi[i].ti.line; ++i ) {
1179 	if ( call_moveto && mi[i].moveto != NULL)
1180 	    (mi[i].moveto)(gw,&(mi[i]),event);
1181 	if ( mi[i].sub==NULL && mi[i].shortcut == keysym &&
1182 		(menumask&event->u.chr.state)==mi[i].short_mask )
1183 return( &mi[i]);
1184 	else if ( mi[i].sub!=NULL ) {
1185 	    GMenuItem *ret = GMenuSearchShortcut(gw,mi[i].sub,event,call_moveto);
1186 	    if ( ret!=NULL )
1187 return( ret );
1188 	}
1189     }
1190 return( NULL );
1191 }
1192 
GMenuSpecialKeys(struct gmenu * m,unichar_t keysym,GEvent * event)1193 static int GMenuSpecialKeys(struct gmenu *m, unichar_t keysym, GEvent *event) {
1194     switch ( keysym ) {
1195       case GK_Escape:
1196 	GMenuDestroy(m);
1197 return( true );
1198       case GK_Return:
1199 	if ( m->line_with_mouse==-1 ) {
1200 	    int ns=0;
1201 	    while ( ns<m->mcnt && (m->mi[ns].ti.disabled || m->mi[ns].ti.line)) ++ns;
1202 	    if ( ns<m->mcnt )
1203 		GMenuChangeSelection(m,ns,event);
1204 	} else if ( m->mi[m->line_with_mouse].sub!=NULL &&
1205 		m->child==NULL ) {
1206 	    m->child = GMenuCreateSubMenu(m,m->mi[m->line_with_mouse].sub,
1207 		    m->disabled || m->mi[m->line_with_mouse].ti.disabled);
1208 	} else {
1209 	    int i = m->line_with_mouse;
1210 	    if ( !m->disabled && !m->mi[i].ti.disabled && !m->mi[i].ti.line ) {
1211 		if ( m->mi[i].ti.checkable )
1212 		    m->mi[i].ti.checked = !m->mi[i].ti.checked;
1213 		GMenuDismissAll(m);
1214 		if ( m->mi[i].invoke!=NULL )
1215 		    (m->mi[i].invoke)(m->owner,&m->mi[i],event);
1216 	    } else
1217 		GMenuDismissAll(m);
1218 	}
1219 return( true );
1220       case GK_Left: case GK_KP_Left:
1221 	if ( m->parent!=NULL ) {
1222 	    GMenuDestroy(m);
1223 return( true );
1224 	} else if ( m->menubar!=NULL ) {
1225 	    GMenuBar *mb = m->menubar;
1226 	    int en = mb->entry_with_mouse;
1227 	    int lastmi = mb->fake[0].sub!=NULL ? mb->lastmi+1 : mb->lastmi;
1228 	    if ( en>0 ) {
1229 		GMenuBarChangeSelection(mb,en-1,event);
1230 	    } else
1231 		GMenuBarChangeSelection(mb,lastmi-1,event);
1232 return( true );
1233 	}
1234 	/* Else fall into the "Up" case */
1235       case GK_Up: case GK_KP_Up: case GK_Page_Up: case GK_KP_Page_Up: {
1236 	int ns;
1237 	if ( keysym!=GK_Left && keysym!=GK_KP_Left ) {
1238 	    while ( m->line_with_mouse==-1 && m->parent!=NULL ) {
1239 		GMenu *p = m->parent;
1240 		GMenuDestroy(m);
1241 		m = p;
1242 	    }
1243 	}
1244 	ns = m->line_with_mouse-1;
1245 	while ( ns>=0 && (m->mi[ns].ti.disabled || m->mi[ns].ti.line)) --ns;
1246 	if ( ns<0 ) {
1247 	    ns = m->mcnt-1;
1248 	    while ( ns>=0 && (m->mi[ns].ti.disabled || m->mi[ns].ti.line)) --ns;
1249 	}
1250 	if ( ns<0 && m->line_with_mouse==-1 ) {	/* Nothing selectable? get rid of menu */
1251 	    GMenuDestroy(m);
1252 return( true );
1253 	}
1254 	if ( ns<0 ) ns = -1;
1255 	GMenuChangeSelection(m,ns,NULL);
1256 return( true );
1257       }
1258       case GK_Right: case GK_KP_Right:
1259 	if ( m->line_with_mouse!=-1 &&
1260 		m->mi[m->line_with_mouse].sub!=NULL && m->child==NULL ) {
1261 	    m->child = GMenuCreateSubMenu(m,m->mi[m->line_with_mouse].sub,
1262 		    m->disabled || m->mi[m->line_with_mouse].ti.disabled);
1263 return( true );
1264 	} else if ( m->parent==NULL && m->menubar!=NULL ) {
1265 	    GMenuBar *mb = m->menubar;
1266 	    int en = mb->entry_with_mouse;
1267 	    int lastmi = mb->fake[0].sub!=NULL ? mb->lastmi+1 : mb->lastmi;
1268 	    if ( en+1<lastmi ) {
1269 		GMenuBarChangeSelection(mb,en+1,event);
1270 	    } else
1271 		GMenuBarChangeSelection(mb,0,event);
1272 return( true );
1273 	}
1274       /* Fall through into the "Down" case */
1275       case GK_Down: case GK_KP_Down: case GK_Page_Down: case GK_KP_Page_Down: {
1276 	int ns;
1277 	if ( keysym!=GK_Right && keysym!=GK_KP_Right ) {
1278 	    while ( m->line_with_mouse==-1 && m->parent!=NULL ) {
1279 		GMenu *p = m->parent;
1280 		GMenuDestroy(m);
1281 		m = p;
1282 	    }
1283 	}
1284 	ns = m->line_with_mouse+1;
1285 	while ( ns<m->mcnt && (m->mi[ns].ti.disabled || m->mi[ns].ti.line)) ++ns;
1286 	if ( ns>=m->mcnt ) {
1287 	    ns = 0;
1288 	    while ( ns<m->mcnt && (m->mi[ns].ti.disabled || m->mi[ns].ti.line)) ++ns;
1289 	}
1290 	if ( ns>=m->mcnt && m->line_with_mouse==-1 ) {	/* Nothing selectable? get rid of menu */
1291 	    GMenuDestroy(m);
1292 return( true );
1293 	}
1294 	GMenuChangeSelection(m,ns,event);
1295 return( true );
1296       }
1297       case GK_Home: case GK_KP_Home: {
1298 	int ns=0;
1299 	while ( ns<m->mcnt && (m->mi[ns].ti.disabled || m->mi[ns].ti.line)) ++ns;
1300 	if ( ns!=m->mcnt )
1301 	    GMenuChangeSelection(m,ns,event);
1302 return( true );
1303       }
1304       case GK_End: case GK_KP_End: {
1305 	int ns=m->mcnt-1;
1306 	while ( ns>=0 && (m->mi[ns].ti.disabled || m->mi[ns].ti.line)) --ns;
1307 	if ( ns>=0 )
1308 	    GMenuChangeSelection(m,ns,event);
1309 return( true );
1310       }
1311     }
1312 return( false );
1313 }
1314 
gmenu_key(struct gmenu * m,GEvent * event)1315 static int gmenu_key(struct gmenu *m, GEvent *event) {
1316     int i;
1317     GMenuItem *mi;
1318     GMenu *top;
1319     unichar_t keysym = event->u.chr.keysym;
1320 
1321     if ( m->dying )
1322 	return( false );
1323 
1324     if ( islower(keysym)) keysym = toupper(keysym);
1325     if ( event->u.chr.state&ksm_meta && !(event->u.chr.state&(menumask&~(ksm_meta|ksm_shift)))) {
1326 	/* Only look for mneumonics in the child */
1327 	while ( m->child!=NULL )
1328 	    m = m->child;
1329 	for ( i=0; i<m->mcnt; ++i ) {
1330 	    if ( m->mi[i].ti.mnemonic == keysym &&
1331 			!m->disabled &&
1332 			!m->mi[i].ti.disabled ) {
1333 		GMenuKeyInvoke(m,i);
1334 return( true );
1335 	    }
1336 	}
1337     }
1338 
1339     /* then look for shortcuts everywhere */
1340     if ( (event->u.chr.state&(menumask&~ksm_shift)) ||
1341 	    event->u.chr.keysym>=GK_Special ) {
1342 	for ( top = m; top->parent!=NULL ; top = top->parent );
1343 	if ( top->menubar!=NULL )
1344 	    mi = GMenuSearchShortcut(top->owner,top->menubar->mi,event,false);
1345 	else
1346 	    mi = GMenuSearchShortcut(top->owner,top->mi,event,false);
1347 	if ( mi!=NULL ) {
1348 	    if ( mi->ti.checkable )
1349 		mi->ti.checked = !mi->ti.checked;
1350 	    GMenuHideAll(top);
1351 	    if ( mi->invoke!=NULL )
1352 		(mi->invoke)(m->owner,mi,event);
1353 	    GMenuDestroy(m);
1354 return( true );
1355 	}
1356 	for ( ; m->child!=NULL ; m = m->child );
1357 return( GMenuSpecialKeys(m,event->u.chr.keysym,event));
1358     }
1359 
1360 return( false );
1361 }
1362 
gmenu_destroy(struct gmenu * m)1363 static int gmenu_destroy(struct gmenu *m) {
1364     if ( most_recent_popup_menu==m )
1365 	most_recent_popup_menu = NULL;
1366     if ( m->donecallback )
1367 	(m->donecallback)(m->owner);
1368     if ( m->freemi )
1369 	GMenuItemArrayFree(m->mi);
1370     free(m);
1371 return( true );
1372 }
1373 
gmenu_eh(GWindow w,GEvent * ge)1374 static int gmenu_eh(GWindow w,GEvent *ge) {
1375     GMenu *m = (GMenu *) GDrawGetUserData(w);
1376 
1377     switch ( ge->type ) {
1378       case et_map:
1379 	/* I need to initialize the input context, but I can't do that until */
1380 	/*  the menu pops up */
1381 	if ( ge->u.map.is_visible && m->gic!=NULL )
1382 	    GDrawSetGIC(w,m->gic,0,20);
1383 return( true );
1384       case et_expose:
1385 return( gmenu_expose(m,ge,w));
1386       case et_char:
1387 return( gmenu_key(m,ge));
1388       case et_mousemove: case et_mousedown: case et_mouseup: case et_crossing:
1389 return( gmenu_mouse(m,ge));
1390       case et_timer:
1391 return( gmenu_timer(m,ge));
1392       case et_destroy:
1393 return( gmenu_destroy(m));
1394       case et_close:
1395 	GMenuDestroy(m);
1396 return( true );
1397     }
1398 return( false );
1399 }
1400 
gmenu_scroll(GGadget * g,GEvent * event)1401 static int gmenu_scroll(GGadget *g, GEvent *event) {
1402     enum sb sbt = event->u.control.u.sb.type;
1403     GMenu *m = (GMenu *) (g->data);
1404     int newpos = m->offtop;
1405 
1406     if ( sbt==et_sb_top )
1407 	newpos = 0;
1408     else if ( sbt==et_sb_bottom )
1409 	newpos = m->mcnt-m->lcnt;
1410     else if ( sbt==et_sb_up ) {
1411 	--newpos;
1412     } else if ( sbt==et_sb_down ) {
1413 	++newpos;
1414     } else if ( sbt==et_sb_uppage ) {
1415 	if ( m->lcnt!=1 )		/* Normally we leave one line in window from before, except if only one line fits */
1416 	    newpos -= m->lcnt-1;
1417 	else
1418 	    newpos -= 1;
1419     } else if ( sbt==et_sb_downpage ) {
1420 	if ( m->lcnt!=1 )		/* Normally we leave one line in window from before, except if only one line fits */
1421 	    newpos += m->lcnt-1;
1422 	else
1423 	    newpos += 1;
1424     } else /* if ( sbt==et_sb_thumb || sbt==et_sb_thumbrelease ) */ {
1425 	newpos = event->u.control.u.sb.pos;
1426     }
1427     if ( newpos+m->lcnt > m->mcnt )
1428 	newpos = m->mcnt-m->lcnt;
1429     if ( newpos<0 )
1430 	newpos = 0;
1431     if ( newpos!= m->offtop ) {
1432 	m->offtop = newpos;
1433 	GScrollBarSetPos(m->vsb,newpos);
1434 	GDrawRequestExpose(m->w,NULL,false);
1435     }
1436 return( true );
1437 }
1438 
_GMenu_Create(GMenuBar * toplevel,GWindow owner,GMenuItem * mi,GPoint * where,int awidth,int aheight,GFont * font,int disable,char * subMenuName)1439 static GMenu *_GMenu_Create( GMenuBar* toplevel,
1440 			     GWindow owner,
1441 			     GMenuItem *mi,
1442 			     GPoint *where,
1443 			     int awidth, int aheight,
1444 			     GFont *font, int disable,
1445 			     char* subMenuName )
1446 {
1447     GMenu *m = calloc(1,sizeof(GMenu));
1448     GRect pos;
1449     GDisplay *disp = GDrawGetDisplayOfWindow(owner);
1450     GWindowAttrs pattrs;
1451     int i, width, max_iwidth = 0, max_hkwidth = 0;
1452     unichar_t buffer[300];
1453     extern int _GScrollBar_Width;
1454     int ds, ld, temp, lh;
1455     int sbwidth = 0;
1456     GRect screen;
1457 
1458     m->owner = owner;
1459     m->mi = mi;
1460     m->disabled = disable;
1461     m->font = font;
1462     m->box = &menu_box;
1463     m->tickoff = m->tioff = m->bp = GBoxBorderWidth(owner,m->box);
1464     m->line_with_mouse = -1;
1465     if( subMenuName )
1466 	strncpy(m->subMenuName,subMenuName,sizeof(m->subMenuName)-1);
1467 /* Mnemonics in menus don't work under gnome. Turning off nodecor makes them */
1468 /*  work, but that seems a high price to pay */
1469     pattrs.mask = wam_events|wam_nodecor|wam_positioned|wam_cursor|wam_transient|wam_verytransient;
1470     pattrs.event_masks = -1;
1471     pattrs.nodecoration = true;
1472     pattrs.positioned = true;
1473     pattrs.cursor = ct_pointer;
1474     pattrs.transient = GWidgetGetTopWidget(owner);
1475 
1476     pos.x = pos.y = 0; pos.width = pos.height = 100;
1477 
1478     m->w = GDrawCreateTopWindow(disp,&pos,gmenu_eh,m,&pattrs);
1479     m->gic = GDrawCreateInputContext(m->w,gic_root|gic_orlesser);
1480     GDrawWindowFontMetrics(m->w,m->font,&m->as, &ds, &ld);
1481     m->fh = m->as + ds + 1;		/* I need some extra space, else mneumonic underlines look bad */
1482     lh = m->fh;
1483 
1484     GDrawSetFont(m->w,m->font);
1485     m->hasticks = false;
1486     for ( i=0; mi[i].ti.text!=NULL || mi[i].ti.image!=NULL || mi[i].ti.line; ++i ) {
1487 	if ( mi[i].ti.checkable )
1488 	    m->hasticks = true;
1489 	temp = GTextInfoGetWidth(m->w,&mi[i].ti,m->font);
1490 	if (temp > max_iwidth)
1491 	    max_iwidth = temp;
1492 
1493 	uc_strcpy(buffer,"");
1494 	uint16 short_mask = 0;
1495 	/**
1496 	 * Grab the hotkey if there is one. First we work out the
1497 	 * menubar for this menu item, and then get the path from the
1498 	 * menubar to the menuitem and pass that to the hotkey system
1499 	 * to get the hotkey if there is one for that menu item.
1500 	 *
1501 	 * If we have a hotkey then set the width to the text porition
1502 	 * first and then add the modifier icons if there are to be
1503 	 * any for the key combination.
1504 	 */
1505 //	TRACE("_gmenu_create() toplevel:%p owner:%p\n", toplevel, owner );
1506 
1507 	Hotkey* hk = 0;
1508 	if( toplevel ) {
1509 	    hk = hotkeyFindByMenuPath( toplevel->g.base,
1510 				       GMenuGetMenuPath( toplevel->mi, &mi[i] ));
1511 	}
1512 	else if( owner && strlen(m->subMenuName) )
1513 	{
1514 	    hk = hotkeyFindByMenuPathInSubMenu( owner, m->subMenuName,
1515 						GMenuGetMenuPath( mi, &mi[i] ));
1516 	}
1517 
1518 //	TRACE("hk:%p\n", hk);
1519 	if( hk )
1520 	{
1521 	    short_mask = hk->state;
1522 	    char* keydesc = hk->text;
1523 	    if( mac_menu_icons )
1524 	    {
1525 //		keydesc = hotkeyTextWithoutModifiers( keydesc );
1526 	    }
1527 	    uc_strcpy( buffer, keydesc );
1528 
1529 	    temp = GDrawGetTextWidth(m->w,buffer,-1);
1530 	    if (temp > max_hkwidth)
1531 	        max_hkwidth = temp;
1532 	    /* if( short_mask && mac_menu_icons ) { */
1533 	    /* 	temp += GMenuMacIconsWidth( m, short_mask ); */
1534 	    /* } */
1535 	}
1536 
1537 	temp = GTextInfoGetHeight(m->w,&mi[i].ti,m->font);
1538 	if ( temp>lh ) {
1539 	    if ( temp>3*m->fh/2 )
1540 		temp = 3*m->fh/2;
1541 	    lh = temp;
1542 	}
1543     }
1544     m->fh = lh;
1545     m->mcnt = m->lcnt = i;
1546 
1547     //Width: Max item length + max length of hotkey text + padding
1548     width = max_iwidth + max_hkwidth + GDrawPointsToPixels(m->w, 10);
1549     if ( m->hasticks ) {
1550 	int ticklen = m->as + GDrawPointsToPixels(m->w,5);
1551 	width += ticklen;
1552 	m->tioff += ticklen;
1553     }
1554     m->width = pos.width = width + 2*m->bp;
1555     m->rightedge = m->width - m->bp;
1556     m->height = pos.height = i*m->fh + 2*m->bp;
1557     GDrawGetSize(GDrawGetRoot(disp),&screen);
1558 
1559     sbwidth = 0;
1560 /* On the mac, the menu bar takes up the top twenty pixels or so of screen */
1561 /*  so never put a menu that high */
1562 #define MAC_MENUBAR	20
1563     if ( pos.height > screen.height-MAC_MENUBAR-m->fh ) {
1564 	GGadgetData gd;
1565 
1566 	m->lcnt = (screen.height-MAC_MENUBAR-m->fh-2*m->bp)/m->fh;
1567 	m->height = pos.height = m->lcnt*m->fh + 2*m->bp;
1568 
1569 	/* It's too long, so add a scrollbar */
1570 	sbwidth = GDrawPointsToPixels(owner,_GScrollBar_Width);
1571 	pos.width += sbwidth;
1572 	memset(&gd,'\0',sizeof(gd));
1573 	gd.pos.y = 0; gd.pos.height = pos.height;
1574 	gd.pos.width = sbwidth;
1575 	gd.pos.x = m->width;
1576 	gd.flags = gg_visible|gg_enabled|gg_pos_in_pixels|gg_sb_vert|gg_pos_use0;
1577 	gd.handle_controlevent = gmenu_scroll;
1578 	m->vsb = GScrollBarCreate(m->w,&gd,m);
1579 	GScrollBarSetBounds(m->vsb,0,m->mcnt,m->lcnt);
1580     }
1581 
1582     pos.x = where->x; pos.y = where->y;
1583     if ( pos.y + pos.height > screen.height-MAC_MENUBAR ) {
1584 	if ( where->y+aheight-pos.height >= MAC_MENUBAR )
1585 	    pos.y = where->y+aheight-pos.height;
1586 	else {
1587 	    pos.y = MAC_MENUBAR;
1588 	    /* Ok, it's going to overlap the press point if we got here */
1589 	    /*  let's see if we can shift it left/right a bit so it won't */
1590 	    if ( awidth<0 )
1591 		/* Oh, well, I guess it won't. It's a submenu and we've already */
1592 		/*  moved off to the left */;
1593 	    else if ( pos.x+awidth+pos.width+3<screen.width )
1594 		pos.x += awidth+3;
1595 	    else if ( pos.x-pos.width-3>=0 )
1596 		pos.x -= pos.width+3;
1597 	    else {
1598 		/* There doesn't seem much we can do in this case */
1599 		;
1600             }
1601 	}
1602     }
1603     if ( pos.x+pos.width > screen.width ) {
1604 	if ( where->x+awidth-pos.width >= 0 )
1605 	    pos.x = where->x+awidth-pos.width-3;
1606 	else
1607 	    pos.x = 0;
1608     }
1609     GDrawResize(m->w,pos.width,pos.height);
1610     GDrawMove(m->w,pos.x,pos.y);
1611 
1612     GDrawSetVisible(m->w,true);
1613     if ( menu_grabs )
1614 	GDrawPointerGrab(m->w);
1615 return( m );
1616 }
1617 
GMenuCreateSubMenu(GMenu * parent,GMenuItem * mi,int disable)1618 static GMenu *GMenuCreateSubMenu(GMenu *parent,GMenuItem *mi,int disable) {
1619     GPoint p;
1620     GMenu *m;
1621     char *subMenuName = 0;
1622 
1623     p.x = parent->width;
1624     p.y = (parent->line_with_mouse-parent->offtop)*parent->fh + parent->bp;
1625     GDrawTranslateCoordinates(parent->w,GDrawGetRoot(GDrawGetDisplayOfWindow(parent->w)),&p);
1626     m = _GMenu_Create(getTopLevelMenubar(parent),parent->owner,mi,&p,-parent->width,parent->fh,
1627 		      parent->font, disable, subMenuName );
1628     m->parent = parent;
1629     m->pressed = parent->pressed;
1630 return( m );
1631 }
1632 
GMenuCreatePulldownMenu(GMenuBar * mb,GMenuItem * mi,int disabled)1633 static GMenu *GMenuCreatePulldownMenu(GMenuBar *mb,GMenuItem *mi,int disabled) {
1634     GPoint p;
1635     GMenu *m;
1636     char *subMenuName = 0;
1637 
1638     p.x = mb->g.inner.x + mb->xs[mb->entry_with_mouse]-
1639 	    GBoxDrawnWidth(mb->g.base,&menu_box );
1640     p.y = mb->g.r.y + mb->g.r.height;
1641     GDrawTranslateCoordinates(mb->g.base,GDrawGetRoot(GDrawGetDisplayOfWindow(mb->g.base)),&p);
1642     m = _GMenu_Create( mb, mb->g.base, mi, &p,
1643 		       mb->xs[mb->entry_with_mouse+1]-mb->xs[mb->entry_with_mouse],
1644 		       -mb->g.r.height,
1645 		       mb->font, disabled, subMenuName );
1646     m->menubar = mb;
1647     m->pressed = mb->pressed;
1648     _GWidget_SetPopupOwner((GGadget *) mb);
1649 return( m );
1650 }
1651 
_GMenuCreatePopupMenu(GWindow owner,GEvent * event,GMenuItem * mi,void (* donecallback)(GWindow))1652 GWindow _GMenuCreatePopupMenu( GWindow owner,GEvent *event, GMenuItem *mi,
1653 			       void (*donecallback)(GWindow) )
1654 {
1655     char *subMenuName = 0;
1656     return _GMenuCreatePopupMenuWithName( owner, event, mi, subMenuName, donecallback );
1657 }
1658 
1659 
_GMenuCreatePopupMenuWithName(GWindow owner,GEvent * event,GMenuItem * mi,char * subMenuName,void (* donecallback)(GWindow))1660 GWindow _GMenuCreatePopupMenuWithName( GWindow owner,GEvent *event, GMenuItem *mi,
1661 				       char *subMenuName,
1662 				       void (*donecallback)(GWindow) )
1663 {
1664     GPoint p;
1665     GMenu *m;
1666     GEvent e;
1667 
1668     if ( !gmenubar_inited )
1669 	GMenuInit();
1670 
1671     p.x = event->u.mouse.x;
1672     p.y = event->u.mouse.y;
1673     GDrawTranslateCoordinates(owner,GDrawGetRoot(GDrawGetDisplayOfWindow(owner)),&p);
1674     m = _GMenu_Create( 0, owner, GMenuItemArrayCopy(mi,NULL), &p,
1675 		       0, 0, menu_font,false, subMenuName );
1676     m->any_unmasked_shortcuts = GMenuItemArrayAnyUnmasked(m->mi);
1677     GDrawPointerUngrab(GDrawGetDisplayOfWindow(owner));
1678     GDrawPointerGrab(m->w);
1679     GDrawGetPointerPosition(m->w,&e);
1680     if ( e.u.mouse.state & (ksm_button1|ksm_button2|ksm_button3) )
1681 	m->pressed = m->initial_press = true;
1682     m->donecallback = donecallback;
1683     m->freemi = true;
1684     most_recent_popup_menu = m;
1685 return( m->w );
1686 }
1687 
GMenuCreatePopupMenu(GWindow owner,GEvent * event,GMenuItem * mi)1688 GWindow GMenuCreatePopupMenu(GWindow owner,GEvent *event, GMenuItem *mi)
1689 {
1690     char* subMenuName = 0;
1691     return( _GMenuCreatePopupMenuWithName(owner,event,mi,subMenuName,NULL));
1692 }
1693 
GMenuCreatePopupMenuWithName(GWindow owner,GEvent * event,char * subMenuName,GMenuItem * mi)1694 GWindow GMenuCreatePopupMenuWithName(GWindow owner,GEvent *event,
1695 				     char* subMenuName, GMenuItem *mi)
1696 {
1697     return( _GMenuCreatePopupMenuWithName(owner,event,mi,subMenuName,NULL));
1698 }
1699 
1700 
GMenuPopupCheckKey(GEvent * event)1701 int GMenuPopupCheckKey(GEvent *event) {
1702 
1703     if ( most_recent_popup_menu==NULL ) return( false );
1704 
1705     return( gmenu_key(most_recent_popup_menu,event) );
1706 }
1707 
1708 /* ************************************************************************** */
1709 
GGadgetUndoMacEnglishOptionCombinations(GEvent * event)1710 int GGadgetUndoMacEnglishOptionCombinations(GEvent *event) {
1711     int keysym = event->u.chr.keysym;
1712 
1713     switch ( keysym ) {
1714     /* translate special mac keys to ordinary keys */
1715       case 0xba:
1716 	keysym = '0'; break;
1717       case 0xa1:
1718 	keysym = '1'; break;
1719       case 0x2122:
1720 	keysym = '2'; break;
1721       case 0xa3:
1722 	keysym = '3'; break;
1723       case 0xa2:
1724 	keysym = '4'; break;
1725       case 0x221e:
1726 	keysym = '5'; break;
1727       case 0xa7:
1728 	keysym = '6'; break;
1729       case 0xb6:
1730 	keysym = '7'; break;
1731       case 0x2022:
1732 	keysym = '8'; break;
1733       case 0xaa:
1734 	keysym = '9'; break;
1735       case 0xe5:
1736 	keysym = 'a'; break;
1737       case 0x222b:
1738 	keysym = 'b'; break;
1739       case 0xe7:
1740 	keysym = 'c'; break;
1741       case 0x2202:
1742 	keysym = 'd'; break;
1743       /* e is a modifier */
1744       case 0x192:
1745 	keysym = 'f'; break;
1746       case 0xa9:
1747 	keysym = 'g'; break;
1748       case 0x2d9:
1749 	keysym = 'h'; break;
1750       /* i is a modifier */
1751       case 0x2206:
1752 	keysym = 'j'; break;
1753       case 0x2da:
1754 	keysym = 'k'; break;
1755       case 0xac:
1756 	keysym = 'l'; break;
1757       case 0xb5:
1758 	keysym = 'm'; break;
1759       /* n is a modifier */
1760       case 0xf8:
1761 	keysym = 'o'; break;
1762       case 0x3c0:
1763 	keysym = 'p'; break;
1764       case 0x153:
1765 	keysym = 'q'; break;
1766       case 0xae:
1767 	keysym = 'r'; break;
1768       case 0x2020:
1769 	keysym = 's'; break;
1770       case 0xee:
1771 	keysym = 't'; break;
1772       /* u is a modifier */
1773       case 0x221a:
1774 	keysym = 'v'; break;
1775       case 0x2211:
1776 	keysym = 'w'; break;
1777       case 0x2248:
1778 	keysym = 'x'; break;
1779       case 0xa5:
1780 	keysym = 'y'; break;
1781       case 0x3a9:
1782 	keysym = 'z'; break;
1783     }
1784     return( keysym );
1785 }
1786 
1787 /**
1788  * On OSX the XEvents have some extra translation performed to try to be handier.
1789  * For example, in xev you might notice that alt+- gives a keysym of endash.
1790  *
1791  * Under Linux this translation doesn't happen and you get the alt
1792  * modifier and the minus keysym. The hotkey code is expecting
1793  * modifier(s) + base keysym not what osx gives (modifier(s) +
1794  * alternate-keysym). So this little function is designed to convert
1795  * the osx "enhanced" keysym back to their basic keysym.
1796  */
1797 #ifdef __Mac
osx_handle_keysyms(int st,int k)1798 static int osx_handle_keysyms( int st, int k )
1799 {
1800 //    TRACE("osx_handle_keysyms() st:%d k:%d\n", st, k );
1801 
1802     if( (st & ksm_control) && (st & ksm_meta) )
1803 	switch( k )
1804 	{
1805 	case 8211:  return 45; // Command + Alt + -
1806 	case 8804:  return 44; // Command + Alt + ,
1807 	}
1808 
1809     if( (st & ksm_control) && (st & ksm_meta) && (st & ksm_shift) )
1810 	switch( k )
1811 	{
1812 	case 177:   return 43; // Command + Alt + Shift + =
1813 	case 197:   return 65; // Command + Alt + Shift + A
1814 	case 305:   return 66; // Command + Alt + Shift + B
1815 	case 199:   return 67; // Command + Alt + Shift + C
1816 	case 206:   return 68; // Command + Alt + Shift + D
1817 	case 180:   return 69; // Command + Alt + Shift + E
1818 	case 207:   return 70; // Command + Alt + Shift + F
1819 	case 733:   return 71; // Command + Alt + Shift + G
1820 	case 211:   return 72; // Command + Alt + Shift + H
1821 	case 710:   return 73; // Command + Alt + Shift + I
1822 	case 212:   return 74; // Command + Alt + Shift + J
1823 	case 63743: return 75; // Command + Alt + Shift + K
1824 	case 210:   return 76; // Command + Alt + Shift + L
1825 	case 194:   return 77; // Command + Alt + Shift + M
1826 	case 732:   return 78; // Command + Alt + Shift + N
1827 	case 216:   return 79; // Command + Alt + Shift + O
1828 	case 8719:  return 80; // Command + Alt + Shift + P
1829 	case 65505: return 81; // Command + Alt + Shift + Q
1830 	case 8240:  return 82; // Command + Alt + Shift + R
1831 	case 205:   return 83; // Command + Alt + Shift + S
1832 	case 711:   return 84; // Command + Alt + Shift + T
1833 	case 168:   return 85; // Command + Alt + Shift + U
1834 	case 9674:  return 86; // Command + Alt + Shift + V
1835 	case 8222:  return 87; // Command + Alt + Shift + W
1836 	case 731:   return 88; // Command + Alt + Shift + X
1837 	case 193:   return 89; // Command + Alt + Shift + Y
1838 	case 184:   return 90; // Command + Alt + Shift + Z
1839 
1840 	case 8260:  return 33; // Command + Alt + Shift + 1
1841 	case 8360:  return 64; // Command + Alt + Shift + 2
1842 	case 8249:  return 35; // Command + Alt + Shift + 3
1843 	case 8250:  return 36; // Command + Alt + Shift + 4
1844 	case 64257: return 37; // Command + Alt + Shift + 5
1845 	case 64258: return 94; // Command + Alt + Shift + 6
1846 	case 8225:  return 38; // Command + Alt + Shift + 7
1847 	case 176:   return 42; // Command + Alt + Shift + 8
1848 	case 183:   return 40; // Command + Alt + Shift + 9
1849 	case 8218:  return 41; // Command + Alt + Shift + 0
1850 	}
1851 
1852     if( st & ksm_meta )
1853 	switch( k )
1854 	{
1855 	case 2730:  return 45; // Alt + -
1856 	case 2237:  return 61; // Alt + = (can avoid shift on this one for simpler up/down)
1857 	case 8800:  return 61; // Alt + = (can avoid shift on this one for simpler up/down)
1858 	}
1859 
1860     return k;
1861 }
1862 #endif
1863 
1864 int osx_fontview_copy_cut_counter = 0;
1865 
1866 
GMenuBarCheckHotkey(GWindow top,GGadget * g,GEvent * event)1867 static int GMenuBarCheckHotkey(GWindow top, GGadget *g, GEvent *event) {
1868 //    TRACE("GMenuBarCheckKey(top) keysym:%d upper:%d lower:%d\n",keysym,toupper(keysym),tolower(keysym));
1869     // see if we should skip processing (e.g. no modifier key pressed)
1870     GMenuBar *mb = (GMenuBar *) g;
1871 	GWindow w = GGadgetGetWindow(g);
1872 	GGadget* focus = GWindowGetFocusGadgetOfWindow(w);
1873 	if (GGadgetGetSkipHotkeyProcessing(focus))
1874 	    return 0;
1875 
1876 //    TRACE("GMenuBarCheckKey(2) keysym:%d upper:%d lower:%d\n",keysym,toupper(keysym),tolower(keysym));
1877 
1878     /* then look for hotkeys everywhere */
1879 
1880     if( hotkeySystemGetCanUseMacCommand() )
1881     {
1882 	// If Mac command key was pressed, swap with the control key.
1883 	if ((event->u.chr.state & (ksm_cmdmacosx|ksm_control)) == ksm_cmdmacosx) {
1884 	    event->u.chr.state ^= (ksm_cmdmacosx|ksm_control);
1885 	}
1886     }
1887 #ifdef __Mac
1888 
1889     //
1890     // Command + Alt + Shift + F on OSX doesn't give the keysym one
1891     // might expect if they have been testing on a Linux Machine.
1892     //
1893     TRACE("looking2 for hotkey in new system...state:%d keysym:%d\n", event->u.chr.state, event->u.chr.keysym );
1894     TRACE("     has ksm_control:%d\n", (event->u.chr.state & ksm_control ));
1895     TRACE("     has ksm_cmdmacosx:%d\n", (event->u.chr.state & ksm_cmdmacosx ));
1896     TRACE("     has ksm_cmdmacosx|control:%d\n", ((event->u.chr.state & (ksm_cmdmacosx|ksm_control)) == ksm_cmdmacosx) );
1897     TRACE("     has ksm_meta:%d\n",    (event->u.chr.state & ksm_meta ));
1898     TRACE("     has ksm_shift:%d\n",   (event->u.chr.state & ksm_shift ));
1899     TRACE("     has ksm_option:%d\n",   (event->u.chr.state & ksm_option ));
1900 
1901     if( event->u.chr.state & ksm_option )
1902 	event->u.chr.state ^= ksm_option;
1903 
1904     event->u.chr.keysym = osx_handle_keysyms( event->u.chr.state, event->u.chr.keysym );
1905     TRACE(" 3   has ksm_option:%d\n",   (event->u.chr.state & ksm_option ));
1906 
1907     // Command-c or Command-x
1908     if( event->u.chr.state == ksm_control
1909 	&& (event->u.chr.keysym == 99 || event->u.chr.keysym == 120 ))
1910     {
1911 	osx_fontview_copy_cut_counter++;
1912     }
1913 #endif
1914 
1915 //    TRACE("about to look for hotkey in new system...state:%d keysym:%d\n", event->u.chr.state, event->u.chr.keysym );
1916 //    TRACE("     has ksm_control:%d\n", (event->u.chr.state & ksm_control ));
1917 //    TRACE("     has ksm_meta:%d\n",    (event->u.chr.state & ksm_meta ));
1918 //    TRACE("     has ksm_shift:%d\n",   (event->u.chr.state & ksm_shift ));
1919 
1920     event->u.chr.state |= ksm_numlock;
1921 //    TRACE("about2 to look for hotkey in new system...state:%d keysym:%d\n", event->u.chr.state, event->u.chr.keysym );
1922 
1923     /**
1924      * Mask off the parts we don't explicitly care about
1925      */
1926     event->u.chr.state &= ( ksm_control | ksm_meta | ksm_shift | ksm_option );
1927 
1928 //    TRACE("about3 to look for hotkey in new system...state:%d keysym:%d\n", event->u.chr.state, event->u.chr.keysym );
1929 //    TRACE("     has ksm_control:%d\n", (event->u.chr.state & ksm_control ));
1930 //    TRACE("     has ksm_meta:%d\n",    (event->u.chr.state & ksm_meta ));
1931 //    TRACE("     has ksm_shift:%d\n",   (event->u.chr.state & ksm_shift ));
1932 
1933     if( GGadgetGetSkipUnQualifiedHotkeyProcessing(focus) && !event->u.chr.state )
1934     {
1935 	TRACE("skipping unqualified hotkey for widget g:%p\n", g);
1936 	return 0;
1937     }
1938 
1939 
1940     struct dlistnodeExternal* node= hotkeyFindAllByEvent( top, event );
1941     struct dlistnode* hklist = (struct dlistnode*)node;
1942     for( ; node; node=(struct dlistnodeExternal*)(node->next) ) {
1943 	Hotkey* hk = (Hotkey*)node->ptr;
1944 //	TRACE("hotkey found by event! hk:%p\n", hk );
1945 //	TRACE("hotkey found by event! action:%s\n", hk->action );
1946 
1947 	int skipkey = false;
1948 
1949 	if( cv_auto_goto )
1950 	{
1951 	    if( !hk->state )
1952 		skipkey = true;
1953 //		TRACE("hotkey state:%d skip:%d\n", hk->state, skipkey );
1954 	}
1955 
1956 	if( !skipkey )
1957 	{
1958 	    GMenuItem *mi = GMenuSearchAction(mb->g.base,mb->mi,hk->action,event,mb->child==NULL);
1959 	    if ( mi )
1960 	    {
1961 //		TRACE("GMenuBarCheckKey(x) have mi... :%p\n", mi );
1962 //		TRACE("GMenuBarCheckKey(x) have mitext:%s\n", u_to_c(mi->ti.text) );
1963 		if ( mi->ti.checkable && !mi->ti.disabled )
1964 		    mi->ti.checked = !mi->ti.checked;
1965 		if ( mi->invoke!=NULL && !mi->ti.disabled )
1966 		    (mi->invoke)(mb->g.base,mi,NULL);
1967 		if ( mb->child != NULL )
1968 		    GMenuDestroy(mb->child);
1969 		return( true );
1970 	    }
1971 	    else
1972 	    {
1973 //		    TRACE("hotkey found for event must be a non menu action... action:%s\n", hk->action );
1974 
1975 	    }
1976 	}
1977 
1978 //	    TRACE("END hotkey found by event! hk:%p\n", hk );
1979     }
1980     dlist_free_external(&hklist);
1981 
1982 //    TRACE("menubarcheckkey(e1)\n");
1983     return false;
1984 }
1985 
1986 
GMenuBarCheckKey(GWindow top,GGadget * g,GEvent * event)1987 int GMenuBarCheckKey(GWindow top, GGadget *g, GEvent *event) {
1988     int i;
1989     GMenuBar *mb = (GMenuBar *) g;
1990     unichar_t keysym = event->u.chr.keysym;
1991 
1992     if ( g==NULL || keysym==0 ) return( false ); /* exit if no gadget or key */
1993 
1994     if ( (menumask&ksm_cmdmacosx) && keysym>0x7f &&
1995 	    (event->u.chr.state&ksm_meta) &&
1996 	    !(event->u.chr.state&menumask&(ksm_control|ksm_cmdmacosx)) )
1997 	keysym = GGadgetUndoMacEnglishOptionCombinations(event);
1998 
1999     if ( keysym<GK_Special && islower(keysym))
2000 	keysym = toupper(keysym);
2001     if ( event->u.chr.state&ksm_meta && !(event->u.chr.state&(menumask&~(ksm_meta|ksm_shift)))) {
2002 	/* Only look for mneumonics in the leaf of the displayed menu structure */
2003 	if ( mb->child!=NULL )
2004 	    return( gmenu_key(mb->child,event)); /* this routine will do shortcuts too */
2005 
2006 	for ( i=0; i<mb->mtot; ++i ) {
2007 	    if ( mb->mi[i].ti.mnemonic == keysym && !mb->mi[i].ti.disabled ) {
2008 		GMenuBarKeyInvoke(mb,i);
2009 		return( true );
2010 	    }
2011 	}
2012     }
2013 
2014     // See if it matches a hotkey
2015     if (GMenuBarCheckHotkey(top, g, event)) {
2016         return true;
2017     }
2018 
2019     if ( mb->child )
2020     {
2021 	GMenu *m;
2022 	for ( m=mb->child; m->child!=NULL; m = m->child )
2023 	{
2024 	}
2025 	return( GMenuSpecialKeys(m,event->u.chr.keysym,event));
2026     }
2027 
2028 //    TRACE("menubarcheckkey(e2)\n");
2029     if ( event->u.chr.keysym==GK_Menu && most_recent_popup_menu==NULL )
2030 	GMenuCreatePopupMenu(event->w,event, mb->mi);
2031 
2032     return( false );
2033 }
2034 
GMenuBarDrawDownArrow(GWindow pixmap,GMenuBar * mb,int x)2035 static void GMenuBarDrawDownArrow(GWindow pixmap, GMenuBar *mb, int x) {
2036     int pt = GDrawPointsToPixels(pixmap,1);
2037     int size = 2*(mb->g.inner.height/3);
2038     int ybase = mb->g.inner.y + size + (mb->g.inner.height-size)/2;
2039     GPoint p[3];
2040 
2041     p[0].x = x+size;		p[0].y = ybase;
2042     p[1].x = x;			p[1].y = ybase - size;
2043     p[2].x = x+2*size;		p[2].y = ybase - size;
2044 
2045     GDrawSetLineWidth(pixmap,pt);
2046 
2047     // If rendering menus in standard (3-dimensional) look, use the shadow colors for fake relief.
2048     // Otherwise, use foreground colors.
2049     if (menu_3d_look) {
2050         GDrawDrawLine(pixmap,p[0].x,p[0].y,p[1].x,p[1].y,mb->g.box->border_darker);
2051         GDrawDrawLine(pixmap,p[0].x,p[0].y+pt,p[1].x+pt,p[1].y,mb->g.box->border_darker);
2052         GDrawDrawLine(pixmap,p[1].x,p[1].y,p[2].x,p[2].y,mb->g.box->border_brightest);
2053         GDrawDrawLine(pixmap,p[1].x+pt,p[1].y,p[2].x-pt,p[2].y,mb->g.box->border_brightest);
2054         GDrawDrawLine(pixmap,p[2].x,p[2].y,p[0].x,p[0].y,mb->g.box->border_darkest);
2055         GDrawDrawLine(pixmap,p[2].x-pt,p[2].y,p[0].x,p[0].y+pt,mb->g.box->border_darkest);
2056     } else {
2057         GDrawDrawLine(pixmap,p[0].x,p[0].y,p[1].x,p[1].y,mb->g.box->main_foreground);
2058         GDrawDrawLine(pixmap,p[0].x,p[0].y+pt,p[1].x+pt,p[1].y,mb->g.box->main_foreground);
2059         GDrawDrawLine(pixmap,p[1].x,p[1].y,p[2].x,p[2].y,mb->g.box->main_foreground);
2060         GDrawDrawLine(pixmap,p[1].x+pt,p[1].y,p[2].x-pt,p[2].y,mb->g.box->main_foreground);
2061         GDrawDrawLine(pixmap,p[2].x,p[2].y,p[0].x,p[0].y,mb->g.box->main_foreground);
2062         GDrawDrawLine(pixmap,p[2].x-pt,p[2].y,p[0].x,p[0].y+pt,mb->g.box->main_foreground);
2063     }
2064 }
2065 
gmenubar_expose(GWindow pixmap,GGadget * g,GEvent * expose)2066 static int gmenubar_expose(GWindow pixmap, GGadget *g, GEvent *expose) {
2067     GMenuBar *mb = (GMenuBar *) g;
2068     GRect r,old1,old2, old3;
2069     Color fg = g->state==gs_disabled?g->box->disabled_foreground:
2070 		    g->box->main_foreground==COLOR_DEFAULT?GDrawGetDefaultForeground(GDrawGetDisplayOfWindow(pixmap)):
2071 		    g->box->main_foreground;
2072     int i;
2073 
2074     if ( fg==COLOR_DEFAULT )
2075 	fg = GDrawGetDefaultForeground(GDrawGetDisplayOfWindow(mb->g.base));
2076 
2077     GDrawPushClip(pixmap,&g->r,&old1);
2078 
2079     GBoxDrawBackground(pixmap,&g->r,g->box, g->state,false);
2080     GBoxDrawBorder(pixmap,&g->r,g->box,g->state,false);
2081     GDrawPushClip(pixmap,&g->inner,&old2);
2082     GDrawSetFont(pixmap,mb->font);
2083 
2084     r = g->inner;
2085     for ( i=0; i<mb->lastmi; ++i ) {
2086 	r.x = mb->xs[i]+mb->g.inner.x; r.width = mb->xs[i+1]-mb->xs[i];
2087 	GDrawPushClip(pixmap,&r,&old3);
2088 	GTextInfoDraw(pixmap,r.x,r.y,&mb->mi[i].ti,mb->font,
2089 		mb->mi[i].ti.disabled?mb->g.box->disabled_foreground:fg,
2090 		mb->g.box->active_border,r.y+r.height);
2091 	GDrawPopClip(pixmap,&old3);
2092     }
2093     if ( i<mb->mtot ) {
2094 	GMenuBarDrawDownArrow(pixmap,mb,mb->xs[i]+mb->g.inner.x);
2095     }
2096 
2097     GDrawPopClip(pixmap,&old2);
2098     GDrawPopClip(pixmap,&old1);
2099 return( true );
2100 }
2101 
GMenuBarIndex(GMenuBar * mb,int x)2102 static int GMenuBarIndex(GMenuBar *mb, int x ) {
2103     int i;
2104 
2105     if ( x<0 )
2106 return( -1 );
2107     for ( i=0; i< mb->lastmi; ++i )
2108 	if ( x<mb->g.inner.x+mb->xs[i+1] )
2109 return( i );
2110     if ( mb->lastmi!=mb->mtot )
2111 return( mb->lastmi );
2112 
2113 return( -1 );
2114 }
2115 
gmenubar_mouse(GGadget * g,GEvent * event)2116 static int gmenubar_mouse(GGadget *g, GEvent *event) {
2117     GMenuBar *mb = (GMenuBar *) g;
2118     int which;
2119 
2120     if ( mb->child!=NULL && mb->child->hidden )
2121 return( true );
2122 
2123     if ( event->type == et_mousedown ) {
2124 	mb->pressed = true;
2125 	if ( mb->child!=NULL )
2126 	    GMenuSetPressed(mb->child,true);
2127 	which = GMenuBarIndex(mb,event->u.mouse.x);
2128 	if ( which==mb->entry_with_mouse && mb->child!=NULL )
2129 	    GMenuDestroy(mb->child);
2130 	else {
2131 	    mb->initial_press = true;
2132 	    GMenuBarChangeSelection(mb,which,event);
2133 	}
2134     } else if ( event->type == et_mousemove && mb->pressed ) {
2135 	if ( GGadgetWithin(g,event->u.mouse.x,event->u.mouse.y))
2136 	    GMenuBarChangeSelection(mb,GMenuBarIndex(mb,event->u.mouse.x),event);
2137 	else if ( mb->child!=NULL ) {
2138 	    GPoint p;
2139 
2140 	    p.x = event->u.mouse.x; p.y = event->u.mouse.y;
2141 	    GDrawTranslateCoordinates(mb->g.base,mb->child->w,&p);
2142 	    if ( p.x>=0 && p.y>=0 && p.x<mb->child->width && p.y<mb->child->height ) {
2143 		GDrawPointerUngrab(GDrawGetDisplayOfWindow(mb->g.base));
2144 		GDrawPointerGrab(mb->child->w);
2145 		event->u.mouse.x = p.x; event->u.mouse.y = p.y;
2146 		event->w = mb->child->w;
2147 		gmenu_mouse(mb->child,event);
2148 	    }
2149 	}
2150     } else if ( event->type == et_mouseup &&
2151 	    (!mb->initial_press ||
2152 	      !GGadgetWithin(g,event->u.mouse.x,event->u.mouse.y))) {
2153 	GMenuBarChangeSelection(mb,-1,event);
2154 	mb->pressed = false;
2155     } else if ( event->type == et_mouseup ) {
2156 	mb->initial_press = mb->pressed = false;
2157 	if ( mb->child!=NULL )
2158 	    GMenuSetPressed(mb->child,false);
2159     }
2160 return( true );
2161 }
2162 
gmenubar_destroy(GGadget * g)2163 static void gmenubar_destroy(GGadget *g) {
2164     GMenuBar *mb = (GMenuBar *) g;
2165     if ( g==NULL )
2166 return;
2167     if ( mb->child!=NULL ) {
2168 	GMenuDestroy(mb->child);
2169 	GDrawSync(NULL);
2170 	GDrawProcessPendingEvents(NULL);	/* popup's destroy routine must execute before we die */
2171     }
2172     GMenuItemArrayFree(mb->mi);
2173     free(mb->xs);
2174     _ggadget_destroy(g);
2175 }
2176 
GMenuBarSetFont(GGadget * g,FontInstance * new)2177 static void GMenuBarSetFont(GGadget *g,FontInstance *new) {
2178     GMenuBar *b = (GMenuBar *) g;
2179     b->font = new;
2180 }
2181 
GMenuBarGetFont(GGadget * g)2182 static FontInstance *GMenuBarGetFont(GGadget *g) {
2183     GMenuBar *b = (GMenuBar *) g;
2184 return( b->font );
2185 }
2186 
GMenuBarTestSize(GMenuBar * mb)2187 static void GMenuBarTestSize(GMenuBar *mb) {
2188     int arrow_size = mb->g.inner.height;
2189     int i;
2190 
2191     if ( mb->xs[mb->mtot]<=mb->g.inner.width+4 ) {
2192 	mb->lastmi = mb->mtot;
2193     } else {
2194 	for ( i=mb->mtot-1; i>0 && mb->xs[i]>mb->g.inner.width-arrow_size; --i );
2195 	mb->lastmi = i;
2196 	memset(&mb->fake,0,sizeof(GMenuItem));
2197 	mb->fake[0].sub = mb->mi+mb->lastmi;
2198     }
2199 }
2200 
GMenuBarResize(GGadget * g,int32 width,int32 height)2201 static void GMenuBarResize(GGadget *g, int32 width, int32 height) {
2202     _ggadget_resize(g,width,height);
2203     GMenuBarTestSize((GMenuBar *) g);
2204 }
2205 
2206 struct gfuncs gmenubar_funcs = {
2207     0,
2208     sizeof(struct gfuncs),
2209 
2210     gmenubar_expose,
2211     gmenubar_mouse,
2212     NULL,
2213     NULL,
2214     NULL,
2215     NULL,
2216     NULL,
2217 
2218     _ggadget_redraw,
2219     _ggadget_move,
2220     GMenuBarResize,
2221     _ggadget_setvisible,
2222     _ggadget_setenabled,
2223     _ggadget_getsize,
2224     _ggadget_getinnersize,
2225 
2226     gmenubar_destroy,
2227 
2228     NULL,
2229     NULL,
2230     NULL,
2231     NULL,
2232     NULL,
2233     GMenuBarSetFont,
2234     GMenuBarGetFont,
2235 
2236     NULL,
2237     NULL,
2238     NULL,
2239     NULL,
2240     NULL,
2241     NULL,
2242     NULL,
2243     NULL,
2244     NULL,
2245     NULL,
2246     NULL,
2247 
2248     NULL,
2249     NULL,
2250     NULL,
2251     NULL
2252 };
2253 
GMenuBarFit(GMenuBar * mb,GGadgetData * gd)2254 static void GMenuBarFit(GMenuBar *mb,GGadgetData *gd) {
2255     int bp = GBoxBorderWidth(mb->g.base,mb->g.box );
2256     GRect r;
2257 
2258     if ( gd->pos.x <= 0 )
2259 	mb->g.r.x = 0;
2260     if ( gd->pos.y <= 0 )
2261 	mb->g.r.y = 0;
2262     if ( mb->g.r.width == 0 ) {
2263 	GDrawGetSize(mb->g.base,&r);
2264 	mb->g.r.width = r.width-mb->g.r.x;
2265     }
2266     if ( mb->g.r.height == 0 ) {
2267 	int as,ds,ld;
2268 	GDrawWindowFontMetrics(mb->g.base,mb->font,&as, &ds, &ld);
2269 	mb->g.r.height = as+ds+2*bp;
2270     }
2271     mb->g.inner.x = mb->g.r.x + bp;
2272     mb->g.inner.y = mb->g.r.y + bp;
2273     mb->g.inner.width = mb->g.r.width - 2*bp;
2274     mb->g.inner.height = mb->g.r.height - 2*bp;
2275 }
2276 
GMenuBarFindXs(GMenuBar * mb)2277 static void GMenuBarFindXs(GMenuBar *mb) {
2278     int i, wid;
2279 
2280     GDrawSetFont(mb->g.base,mb->font);
2281     wid = GDrawPointsToPixels(mb->g.base,8);
2282     mb->xs[0] = GDrawPointsToPixels(mb->g.base,2);
2283     for ( i=0; i<mb->mtot; ++i )
2284 	mb->xs[i+1] = mb->xs[i]+wid+GTextInfoGetWidth(mb->g.base,&mb->mi[i].ti,NULL);
2285     GMenuBarTestSize(mb);
2286 }
2287 
MenuMaskInit(GMenuItem * mi)2288 static void MenuMaskInit(GMenuItem *mi) {
2289     int mask = GMenuItemArrayMask(mi);
2290     if ( mask_set )
2291 	menumask |= mask;
2292     else {
2293 	menumask = mask;
2294 	mask_set = true;
2295     }
2296 }
2297 
GMenuBarCreate(struct gwindow * base,GGadgetData * gd,void * data)2298 GGadget *GMenuBarCreate(struct gwindow *base, GGadgetData *gd,void *data) {
2299     GMenuBar *mb = calloc(1,sizeof(GMenuBar));
2300 
2301     if ( !gmenubar_inited )
2302 	GMenuInit();
2303     mb->g.funcs = &gmenubar_funcs;
2304     _GGadget_Create(&mb->g,base,gd,data,&menubar_box);
2305 
2306     mb->mi = GMenuItemArrayCopy(gd->u.menu,&mb->mtot);
2307     mb->xs = malloc((mb->mtot+1)*sizeof(uint16));
2308     mb->entry_with_mouse = -1;
2309     mb->font = menubar_font;
2310 
2311     GMenuBarFit(mb,gd);
2312     GMenuBarFindXs(mb);
2313 
2314     MenuMaskInit(mb->mi);
2315     mb->any_unmasked_shortcuts = GMenuItemArrayAnyUnmasked(mb->mi);
2316 
2317     if ( gd->flags & gg_group_end )
2318 	_GGadgetCloseGroup(&mb->g);
2319     _GWidget_SetMenuBar(&mb->g);
2320 
2321     mb->g.takes_input = true;
2322 return( &mb->g );
2323 }
2324 
GMenu2BarCreate(struct gwindow * base,GGadgetData * gd,void * data)2325 GGadget *GMenu2BarCreate(struct gwindow *base, GGadgetData *gd,void *data) {
2326     GMenuBar *mb = calloc(1,sizeof(GMenuBar));
2327 
2328     if ( !gmenubar_inited )
2329 	GMenuInit();
2330     mb->g.funcs = &gmenubar_funcs;
2331     _GGadget_Create(&mb->g,base,gd,data,&menubar_box);
2332 
2333     mb->mi = GMenuItem2ArrayCopy(gd->u.menu2,&mb->mtot);
2334     mb->xs = malloc((mb->mtot+1)*sizeof(uint16));
2335     mb->entry_with_mouse = -1;
2336     mb->font = menubar_font;
2337 
2338     GMenuBarFit(mb,gd);
2339     GMenuBarFindXs(mb);
2340 
2341     MenuMaskInit(mb->mi);
2342     mb->any_unmasked_shortcuts = GMenuItemArrayAnyUnmasked(mb->mi);
2343 
2344     if ( gd->flags & gg_group_end )
2345 	_GGadgetCloseGroup(&mb->g);
2346     _GWidget_SetMenuBar(&mb->g);
2347 
2348     mb->g.takes_input = true;
2349 return( &mb->g );
2350 }
2351 
2352 /* ************************************************************************** */
GMenuBarFindMid(GMenuItem * mi,int mid)2353 static GMenuItem *GMenuBarFindMid(GMenuItem *mi, int mid) {
2354     int i;
2355     GMenuItem *ret;
2356 
2357     for ( i=0; mi[i].ti.text!=NULL || mi[i].ti.image!=NULL || mi[i].ti.line; ++i ) {
2358 	if ( mi[i].mid == mid )
2359 return( &mi[i]);
2360 	if ( mi[i].sub!=NULL ) {
2361 	    ret = GMenuBarFindMid( mi[i].sub, mid );
2362 	    if ( ret!=NULL )
2363 return( ret );
2364 	}
2365     }
2366 return( NULL );
2367 }
2368 
GMenuBarSetItemChecked(GGadget * g,int mid,int check)2369 void GMenuBarSetItemChecked(GGadget *g, int mid, int check) {
2370     GMenuBar *mb = (GMenuBar *) g;
2371     GMenuItem *item;
2372 
2373     item = GMenuBarFindMid(mb->mi,mid);
2374     if ( item!=NULL )
2375 	item->ti.checked = check;
2376 }
2377 
GMenuBarSetItemEnabled(GGadget * g,int mid,int enabled)2378 void GMenuBarSetItemEnabled(GGadget *g, int mid, int enabled) {
2379     GMenuBar *mb = (GMenuBar *) g;
2380     GMenuItem *item;
2381 
2382     item = GMenuBarFindMid(mb->mi,mid);
2383     if ( item!=NULL )
2384 	item->ti.disabled = !enabled;
2385 }
2386 
GMenuBarSetItemName(GGadget * g,int mid,const unichar_t * name)2387 void GMenuBarSetItemName(GGadget *g, int mid, const unichar_t *name) {
2388     GMenuBar *mb = (GMenuBar *) g;
2389     GMenuItem *item;
2390 
2391     item = GMenuBarFindMid(mb->mi,mid);
2392     if ( item!=NULL ) {
2393 	free( item->ti.text );
2394 	item->ti.text = u_copy(name);
2395     }
2396 }
2397 
2398 /* Check to see if event matches the given shortcut, expressed in our standard*/
2399 /*  syntax and subject to gettext translation */
GMenuIsCommand(GEvent * event,char * shortcut)2400 int GMenuIsCommand(GEvent *event,char *shortcut) {
2401     GMenuItem foo;
2402     unichar_t keysym = event->u.chr.keysym;
2403 
2404     if ( event->type!=et_char )
2405 return( false );
2406 
2407     if ( keysym<GK_Special && islower(keysym))
2408 	keysym = toupper(keysym);
2409 
2410     memset(&foo,0,sizeof(foo));
2411 
2412     GMenuItemParseShortCut(&foo,shortcut);
2413 
2414 return( (menumask&event->u.chr.state)==foo.short_mask && foo.shortcut == keysym );
2415 }
2416 
GMenuMask(void)2417 int GMenuMask(void) {
2418 return( menumask );
2419 }
2420 
_GMenuRIHead(void)2421 GResInfo *_GMenuRIHead(void) {
2422     if ( !gmenubar_inited )
2423 	GMenuInit();
2424 return( &gmenubar_ri );
2425 }
2426 
GMenuAnyUnmaskedShortcuts(GGadget * mb1,GGadget * mb2)2427 int GMenuAnyUnmaskedShortcuts(GGadget *mb1, GGadget *mb2) {
2428 
2429     if ( most_recent_popup_menu!=NULL && most_recent_popup_menu->any_unmasked_shortcuts )
2430 return( true );
2431 
2432     if ( mb1!=NULL && ((GMenuBar *) mb1)->any_unmasked_shortcuts )
2433 return( true );
2434 
2435     if ( mb2!=NULL && ((GMenuBar *) mb2)->any_unmasked_shortcuts )
2436 return( true );
2437 
2438 return( false );
2439 }
2440