Replace bcopy, bzero, bcmp by memcpy, memmove, memset, memcmp
[bpt/emacs.git] / lwlib / xlwmenu.c
CommitLineData
5c520e0a 1/* Implements a lightweight menubar widget.
81e32ca3
GM
2Copyright (C) 1992 Lucid, Inc.
3Copyright (C) 1994, 1995, 1997, 1999, 2000, 2001, 2002, 2003, 2004,
114f9c96 4 2005, 2006, 2007, 2008, 2009, 2010 Free Software Foundation, Inc.
07bf635f
RS
5
6This file is part of the Lucid Widget Library.
7
5c520e0a 8The Lucid Widget Library is free software; you can redistribute it and/or
07bf635f
RS
9modify it under the terms of the GNU General Public License as published by
10the Free Software Foundation; either version 2, or (at your option)
11any later version.
12
13The Lucid Widget Library is distributed in the hope that it will be useful,
5c520e0a 14but WITHOUT ANY WARRANTY; without even the implied warranty of
07bf635f
RS
15MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16GNU General Public License for more details.
17
18You should have received a copy of the GNU General Public License
1fb87c77 19along with GNU Emacs; see the file COPYING. If not, write to the
364c38d3
LK
20Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
21Boston, MA 02110-1301, USA. */
07bf635f
RS
22
23/* Created by devin@lucid.com */
24
0f0912e6
PE
25#ifdef HAVE_CONFIG_H
26#include <config.h>
27#endif
28
d7306fe6 29#include <setjmp.h>
404074e2 30#include "lisp.h"
2f96293d 31
07bf635f 32#include <stdio.h>
99852628 33#include <ctype.h>
07bf635f
RS
34
35#include <sys/types.h>
5c520e0a
SS
36#if (defined __sun) && !(defined SUNOS41)
37#define SUNOS41
07bf635f 38#include <X11/Xos.h>
5c520e0a
SS
39#undef SUNOS41
40#else
41#include <X11/Xos.h>
42#endif
07bf635f 43#include <X11/IntrinsicP.h>
2289f3f4 44#include <X11/ObjectP.h>
07bf635f
RS
45#include <X11/StringDefs.h>
46#include <X11/cursorfont.h>
f0bd1598 47#include <X11/Shell.h>
07bf635f 48#include "xlwmenuP.h"
fc8fefed 49
a57a1605 50#ifdef emacs
f1c16db4
GM
51
52/* Defined in xfns.c. When config.h defines `static' as empty, we get
53 redefinition errors when gray_bitmap is included more than once, so
54 we're referring to the one include in xfns.c here. */
55
56extern int gray_bitmap_width;
57extern int gray_bitmap_height;
5916243b 58extern char *gray_bitmap_bits;
f1c16db4 59
1f5df1f7
JD
60#include "xterm.h"
61
f1c16db4
GM
62#else /* not emacs */
63
64#include <X11/bitmaps/gray>
65#define gray_bitmap_width gray_width
66#define gray_bitmap_height gray_height
67#define gray_bitmap_bits gray_bits
68
69#endif /* not emacs */
d398028f
PR
70
71static int pointer_grabbed;
72static XEvent menu_post_event;
07bf635f 73
99852628 74static XFontStruct *xlwmenu_default_font;
47d52240 75
5c520e0a
SS
76static char
77xlwMenuTranslations [] =
ba624d0f
RS
78"<BtnDown>: start()\n\
79<Motion>: drag()\n\
80<BtnUp>: select()\n\
81<Key>Shift_L: nothing()\n\
82<Key>Shift_R: nothing()\n\
83<Key>Meta_L: nothing()\n\
84<Key>Meta_R: nothing()\n\
85<Key>Control_L: nothing()\n\
86<Key>Control_R: nothing()\n\
87<Key>Hyper_L: nothing()\n\
88<Key>Hyper_R: nothing()\n\
89<Key>Super_L: nothing()\n\
90<Key>Super_R: nothing()\n\
91<Key>Alt_L: nothing()\n\
92<Key>Alt_R: nothing()\n\
93<Key>Caps_Lock: nothing()\n\
94<Key>Shift_Lock: nothing()\n\
95<KeyUp>Shift_L: nothing()\n\
96<KeyUp>Shift_R: nothing()\n\
97<KeyUp>Meta_L: nothing()\n\
98<KeyUp>Meta_R: nothing()\n\
99<KeyUp>Control_L: nothing()\n\
100<KeyUp>Control_R: nothing()\n\
101<KeyUp>Hyper_L: nothing()\n\
102<KeyUp>Hyper_R: nothing()\n\
103<KeyUp>Super_L: nothing()\n\
104<KeyUp>Super_R: nothing()\n\
105<KeyUp>Alt_L: nothing()\n\
106<KeyUp>Alt_R: nothing()\n\
107<KeyUp>Caps_Lock: nothing()\n\
108<KeyUp>Shift_Lock:nothing()\n\
a7e19a26
PJ
109<Key>Return: select()\n\
110<Key>Down: down()\n\
111<Key>Up: up()\n\
112<Key>Left: left()\n\
113<Key>Right: right()\n\
ba624d0f
RS
114<Key>: key()\n\
115<KeyUp>: key()\n\
07bf635f
RS
116";
117
a7e19a26
PJ
118/* FIXME: Space should toggle toggleable menu item but not remove the menu
119 so you can toggle the next one without entering the menu again. */
120
121/* FIXME: Should ESC close one level of menu structure or the complete menu? */
122
2376c841 123/* FIXME: F10 should enter the menu, the first one in the menu-bar. */
a7e19a26 124
07bf635f 125#define offset(field) XtOffset(XlwMenuWidget, field)
5c520e0a 126static XtResource
07bf635f 127xlwMenuResources[] =
5c520e0a 128{
423a3f59 129#ifdef HAVE_X_I18N
2015c054
JD
130 {XtNfontSet, XtCFontSet, XtRFontSet, sizeof(XFontSet),
131 offset(menu.fontSet), XtRFontSet, NULL},
99852628
JD
132#endif
133#ifdef HAVE_XFT
134#define DEFAULT_FACENAME "Sans-10"
135 {XtNfaceName, XtCFaceName, XtRString, sizeof(String),
136 offset(menu.faceName), XtRString, DEFAULT_FACENAME},
137 {XtNdefaultFace, XtCDefaultFace, XtRInt, sizeof(int),
138 offset(menu.default_face), XtRImmediate, (XtPointer)1},
2015c054 139#endif
07bf635f 140 {XtNfont, XtCFont, XtRFontStruct, sizeof(XFontStruct *),
423a3f59 141 offset(menu.font), XtRString, "XtDefaultFont"},
07bf635f
RS
142 {XtNforeground, XtCForeground, XtRPixel, sizeof(Pixel),
143 offset(menu.foreground), XtRString, "XtDefaultForeground"},
8b71a9ca
PJ
144 {XtNdisabledForeground, XtCDisabledForeground, XtRPixel, sizeof(Pixel),
145 offset(menu.disabled_foreground), XtRString, (XtPointer)NULL},
07bf635f
RS
146 {XtNbuttonForeground, XtCButtonForeground, XtRPixel, sizeof(Pixel),
147 offset(menu.button_foreground), XtRString, "XtDefaultForeground"},
148 {XtNmargin, XtCMargin, XtRDimension, sizeof(Dimension),
2376c841 149 offset(menu.margin), XtRImmediate, (XtPointer)1},
07bf635f
RS
150 {XtNhorizontalSpacing, XtCMargin, XtRDimension, sizeof(Dimension),
151 offset(menu.horizontal_spacing), XtRImmediate, (XtPointer)3},
152 {XtNverticalSpacing, XtCMargin, XtRDimension, sizeof(Dimension),
2376c841 153 offset(menu.vertical_spacing), XtRImmediate, (XtPointer)2},
07bf635f
RS
154 {XtNarrowSpacing, XtCMargin, XtRDimension, sizeof(Dimension),
155 offset(menu.arrow_spacing), XtRImmediate, (XtPointer)10},
156
d398028f 157 {XmNshadowThickness, XmCShadowThickness, XtRDimension,
07bf635f 158 sizeof (Dimension), offset (menu.shadow_thickness),
2376c841 159 XtRImmediate, (XtPointer)1},
07bf635f
RS
160 {XmNtopShadowColor, XmCTopShadowColor, XtRPixel, sizeof (Pixel),
161 offset (menu.top_shadow_color), XtRImmediate, (XtPointer)-1},
162 {XmNbottomShadowColor, XmCBottomShadowColor, XtRPixel, sizeof (Pixel),
163 offset (menu.bottom_shadow_color), XtRImmediate, (XtPointer)-1},
164 {XmNtopShadowPixmap, XmCTopShadowPixmap, XtRPixmap, sizeof (Pixmap),
165 offset (menu.top_shadow_pixmap), XtRImmediate, (XtPointer)None},
166 {XmNbottomShadowPixmap, XmCBottomShadowPixmap, XtRPixmap, sizeof (Pixmap),
167 offset (menu.bottom_shadow_pixmap), XtRImmediate, (XtPointer)None},
168
5c520e0a 169 {XtNopen, XtCCallback, XtRCallback, sizeof(XtPointer),
07bf635f 170 offset(menu.open), XtRCallback, (XtPointer)NULL},
5c520e0a 171 {XtNselect, XtCCallback, XtRCallback, sizeof(XtPointer),
07bf635f 172 offset(menu.select), XtRCallback, (XtPointer)NULL},
5c520e0a 173 {XtNhighlightCallback, XtCCallback, XtRCallback, sizeof(XtPointer),
d6fd6371 174 offset(menu.highlight), XtRCallback, (XtPointer)NULL},
1ecd5086
JD
175 {XtNenterCallback, XtCCallback, XtRCallback, sizeof(XtPointer),
176 offset(menu.enter), XtRCallback, (XtPointer)NULL},
177 {XtNleaveCallback, XtCCallback, XtRCallback, sizeof(XtPointer),
178 offset(menu.leave), XtRCallback, (XtPointer)NULL},
07bf635f
RS
179 {XtNmenu, XtCMenu, XtRPointer, sizeof(XtPointer),
180 offset(menu.contents), XtRImmediate, (XtPointer)NULL},
181 {XtNcursor, XtCCursor, XtRCursor, sizeof(Cursor),
182 offset(menu.cursor_shape), XtRString, (XtPointer)"right_ptr"},
183 {XtNhorizontal, XtCHorizontal, XtRInt, sizeof(int),
184 offset(menu.horizontal), XtRImmediate, (XtPointer)True},
185};
186#undef offset
187
e4c8d29a
J
188static Boolean XlwMenuSetValues(Widget current, Widget request, Widget new,
189 ArgList args, Cardinal *num_args);
c3174d16
DN
190static void XlwMenuRealize(Widget w, Mask *valueMask, XSetWindowAttributes *attributes);
191static void XlwMenuResize(Widget w);
e4c8d29a 192static void XlwMenuInitialize(Widget request, Widget mw, ArgList args, Cardinal *num_args);
c3174d16
DN
193static void XlwMenuRedisplay(Widget w, XEvent *ev, Region region);
194static void XlwMenuDestroy(Widget w);
195static void XlwMenuClassInitialize(void);
196static void Start(Widget w, XEvent *ev, String *params, Cardinal *num_params);
197static void Drag(Widget w, XEvent *ev, String *params, Cardinal *num_params);
198static void Down(Widget w, XEvent *ev, String *params, Cardinal *num_params);
199static void Up(Widget w, XEvent *ev, String *params, Cardinal *num_params);
200static void Left(Widget w, XEvent *ev, String *params, Cardinal *num_params);
201static void Right(Widget w, XEvent *ev, String *params, Cardinal *num_params);
202static void Select(Widget w, XEvent *ev, String *params, Cardinal *num_params);
203static void Key(Widget w, XEvent *ev, String *params, Cardinal *num_params);
204static void Nothing(Widget w, XEvent *ev, String *params, Cardinal *num_params);
f57e2426
J
205static int separator_height (enum menu_separator);
206static void pop_up_menu (XlwMenuWidget, XButtonPressedEvent *);
244c93fe 207
07bf635f 208
5c520e0a 209static XtActionsRec
07bf635f
RS
210xlwMenuActionsList [] =
211{
212 {"start", Start},
213 {"drag", Drag},
a7e19a26
PJ
214 {"down", Down},
215 {"up", Up},
216 {"left", Left},
217 {"right", Right},
07bf635f 218 {"select", Select},
ba624d0f 219 {"key", Key},
4220b2a5 220 {"MenuGadgetEscape", Key}, /* Compatibility with Lesstif/Motif. */
ba624d0f 221 {"nothing", Nothing},
07bf635f
RS
222};
223
224#define SuperClass ((CoreWidgetClass)&coreClassRec)
225
226XlwMenuClassRec xlwMenuClassRec =
227{
228 { /* CoreClass fields initialization */
5c520e0a 229 (WidgetClass) SuperClass, /* superclass */
07bf635f
RS
230 "XlwMenu", /* class_name */
231 sizeof(XlwMenuRec), /* size */
232 XlwMenuClassInitialize, /* class_initialize */
233 NULL, /* class_part_initialize */
234 FALSE, /* class_inited */
235 XlwMenuInitialize, /* initialize */
236 NULL, /* initialize_hook */
237 XlwMenuRealize, /* realize */
238 xlwMenuActionsList, /* actions */
239 XtNumber(xlwMenuActionsList), /* num_actions */
240 xlwMenuResources, /* resources */
241 XtNumber(xlwMenuResources), /* resource_count */
242 NULLQUARK, /* xrm_class */
243 TRUE, /* compress_motion */
423a3f59 244 XtExposeCompressMaximal, /* compress_exposure */
07bf635f
RS
245 TRUE, /* compress_enterleave */
246 FALSE, /* visible_interest */
247 XlwMenuDestroy, /* destroy */
248 XlwMenuResize, /* resize */
249 XlwMenuRedisplay, /* expose */
250 XlwMenuSetValues, /* set_values */
251 NULL, /* set_values_hook */
252 XtInheritSetValuesAlmost, /* set_values_almost */
253 NULL, /* get_values_hook */
254 NULL, /* accept_focus */
255 XtVersion, /* version */
256 NULL, /* callback_private */
257 xlwMenuTranslations, /* tm_table */
258 XtInheritQueryGeometry, /* query_geometry */
259 XtInheritDisplayAccelerator, /* display_accelerator */
260 NULL /* extension */
261 }, /* XlwMenuClass fields initialization */
262 {
263 0 /* dummy */
264 },
265};
266
267WidgetClass xlwMenuWidgetClass = (WidgetClass) &xlwMenuClassRec;
268
d398028f
PR
269int submenu_destroyed;
270
005e0d57
JD
271/* For debug, if installation-directory is non-nil this is not an installed
272 Emacs. In that case we do not grab the keyboard to make it easier to
273 debug. */
274#define GRAB_KEYBOARD (EQ (Vinstallation_directory, Qnil))
4db7db7d 275
d398028f
PR
276static int next_release_must_exit;
277
07bf635f 278\f/* Utilities */
be06a3df 279
4db7db7d
JD
280/* Ungrab pointer and keyboard */
281static void
c3174d16 282ungrab_all (Widget w, Time ungrabtime)
4db7db7d
JD
283{
284 XtUngrabPointer (w, ungrabtime);
005e0d57 285 if (GRAB_KEYBOARD) XtUngrabKeyboard (w, ungrabtime);
4db7db7d 286}
be06a3df
GM
287
288/* Like abort, but remove grabs from widget W before. */
289
290static void
c3174d16 291abort_gracefully (Widget w)
be06a3df
GM
292{
293 if (XtIsShell (XtParent (w)))
294 XtRemoveGrab (w);
4db7db7d 295 ungrab_all (w, CurrentTime);
be06a3df
GM
296 abort ();
297}
298
07bf635f 299static void
c3174d16 300push_new_stack (XlwMenuWidget mw, widget_value *val)
07bf635f
RS
301{
302 if (!mw->menu.new_stack)
303 {
304 mw->menu.new_stack_length = 10;
305 mw->menu.new_stack =
306 (widget_value**)XtCalloc (mw->menu.new_stack_length,
307 sizeof (widget_value*));
308 }
309 else if (mw->menu.new_depth == mw->menu.new_stack_length)
310 {
311 mw->menu.new_stack_length *= 2;
312 mw->menu.new_stack =
313 (widget_value**)XtRealloc ((char*)mw->menu.new_stack,
314 mw->menu.new_stack_length * sizeof (widget_value*));
315 }
316 mw->menu.new_stack [mw->menu.new_depth++] = val;
317}
318
319static void
c3174d16 320pop_new_stack_if_no_contents (XlwMenuWidget mw)
07bf635f 321{
fa74535d 322 if (mw->menu.new_depth > 1)
07bf635f
RS
323 {
324 if (!mw->menu.new_stack [mw->menu.new_depth - 1]->contents)
325 mw->menu.new_depth -= 1;
326 }
327}
328
329static void
c3174d16 330make_old_stack_space (XlwMenuWidget mw, int n)
07bf635f
RS
331{
332 if (!mw->menu.old_stack)
333 {
334 mw->menu.old_stack_length = 10;
335 mw->menu.old_stack =
336 (widget_value**)XtCalloc (mw->menu.old_stack_length,
337 sizeof (widget_value*));
338 }
339 else if (mw->menu.old_stack_length < n)
340 {
341 mw->menu.old_stack_length *= 2;
342 mw->menu.old_stack =
343 (widget_value**)XtRealloc ((char*)mw->menu.old_stack,
344 mw->menu.old_stack_length * sizeof (widget_value*));
345 }
346}
347
348\f/* Size code */
1f5df1f7 349static int
c3174d16 350string_width (XlwMenuWidget mw, char *s)
07bf635f
RS
351{
352 XCharStruct xcs;
353 int drop;
99852628
JD
354#ifdef HAVE_XFT
355 if (mw->menu.xft_font)
356 {
357 XGlyphInfo gi;
358 XftTextExtentsUtf8 (XtDisplay (mw), mw->menu.xft_font,
359 (FcChar8 *) s,
360 strlen (s), &gi);
361 return gi.width;
362 }
363#endif
2015c054 364#ifdef HAVE_X_I18N
2015c054
JD
365 if (mw->menu.fontSet)
366 {
99852628 367 XRectangle ink, logical;
2015c054
JD
368 XmbTextExtents (mw->menu.fontSet, s, strlen (s), &ink, &logical);
369 return logical.width;
370 }
371#endif
5c520e0a 372
07bf635f
RS
373 XTextExtents (mw->menu.font, s, strlen (s), &drop, &drop, &drop, &xcs);
374 return xcs.width;
2015c054 375
07bf635f
RS
376}
377
99852628
JD
378#ifdef HAVE_XFT
379#define MENU_FONT_HEIGHT(mw) \
380 ((mw)->menu.xft_font != NULL \
381 ? (mw)->menu.xft_font->height \
382 : ((mw)->menu.fontSet != NULL \
383 ? (mw)->menu.font_extents->max_logical_extent.height \
384 : (mw)->menu.font->ascent + (mw)->menu.font->descent))
385#define MENU_FONT_ASCENT(mw) \
386 ((mw)->menu.xft_font != NULL \
387 ? (mw)->menu.xft_font->ascent \
388 : ((mw)->menu.fontSet != NULL \
389 ? - (mw)->menu.font_extents->max_logical_extent.y \
390 : (mw)->menu.font->ascent))
391#else
423a3f59
SM
392#ifdef HAVE_X_I18N
393#define MENU_FONT_HEIGHT(mw) \
2015c054
JD
394 ((mw)->menu.fontSet != NULL \
395 ? (mw)->menu.font_extents->max_logical_extent.height \
396 : (mw)->menu.font->ascent + (mw)->menu.font->descent)
15df8e89 397#define MENU_FONT_ASCENT(mw) \
2015c054
JD
398 ((mw)->menu.fontSet != NULL \
399 ? - (mw)->menu.font_extents->max_logical_extent.y \
400 : (mw)->menu.font->ascent)
423a3f59
SM
401#else
402#define MENU_FONT_HEIGHT(mw) \
403 ((mw)->menu.font->ascent + (mw)->menu.font->descent)
404#define MENU_FONT_ASCENT(mw) ((mw)->menu.font->ascent)
405#endif
99852628 406#endif
423a3f59 407
07bf635f 408static int
c3174d16 409arrow_width (XlwMenuWidget mw)
07bf635f 410{
423a3f59 411 return (MENU_FONT_ASCENT (mw) * 3/4) | 1;
07bf635f
RS
412}
413
be06a3df
GM
414/* Return the width of toggle buttons of widget MW. */
415
416static int
c3174d16 417toggle_button_width (XlwMenuWidget mw)
be06a3df 418{
423a3f59 419 return (MENU_FONT_HEIGHT (mw) * 2 / 3) | 1;
be06a3df
GM
420}
421
422
423/* Return the width of radio buttons of widget MW. */
424
425static int
c3174d16 426radio_button_width (XlwMenuWidget mw)
be06a3df
GM
427{
428 return toggle_button_width (mw) * 1.41;
429}
430
431
07bf635f
RS
432static XtResource
433nameResource[] =
5c520e0a 434{
07bf635f
RS
435 {"labelString", "LabelString", XtRString, sizeof(String),
436 0, XtRImmediate, 0},
437};
438
439static char*
c3174d16 440resource_widget_value (XlwMenuWidget mw, widget_value *val)
07bf635f
RS
441{
442 if (!val->toolkit_data)
443 {
444 char* resourced_name = NULL;
445 char* complete_name;
446 XtGetSubresources ((Widget) mw,
447 (XtPointer) &resourced_name,
448 val->name, val->name,
449 nameResource, 1, NULL, 0);
450 if (!resourced_name)
451 resourced_name = val->name;
452 if (!val->value)
d97342f3
RS
453 {
454 complete_name = (char *) XtMalloc (strlen (resourced_name) + 1);
455 strcpy (complete_name, resourced_name);
456 }
07bf635f
RS
457 else
458 {
459 int complete_length =
460 strlen (resourced_name) + strlen (val->value) + 2;
461 complete_name = XtMalloc (complete_length);
462 *complete_name = 0;
463 strcat (complete_name, resourced_name);
464 strcat (complete_name, " ");
465 strcat (complete_name, val->value);
466 }
467
468 val->toolkit_data = complete_name;
469 val->free_toolkit_data = True;
470 }
471 return (char*)val->toolkit_data;
472}
473
474/* Returns the sizes of an item */
475static void
be06a3df
GM
476size_menu_item (mw, val, horizontal_p, label_width, rest_width, button_width,
477 height)
d398028f
PR
478 XlwMenuWidget mw;
479 widget_value* val;
480 int horizontal_p;
481 int* label_width;
482 int* rest_width;
be06a3df 483 int* button_width;
d398028f 484 int* height;
07bf635f 485{
be06a3df 486 enum menu_separator separator;
5c520e0a 487
be06a3df 488 if (lw_separator_p (val->name, &separator, 0))
07bf635f 489 {
be06a3df 490 *height = separator_height (separator);
07bf635f
RS
491 *label_width = 1;
492 *rest_width = 0;
be06a3df 493 *button_width = 0;
07bf635f
RS
494 }
495 else
496 {
423a3f59
SM
497 *height = MENU_FONT_HEIGHT (mw)
498 + 2 * mw->menu.vertical_spacing + 2 * mw->menu.shadow_thickness;
5c520e0a 499
07bf635f
RS
500 *label_width =
501 string_width (mw, resource_widget_value (mw, val))
502 + mw->menu.horizontal_spacing + mw->menu.shadow_thickness;
5c520e0a 503
07bf635f
RS
504 *rest_width = mw->menu.horizontal_spacing + mw->menu.shadow_thickness;
505 if (!horizontal_p)
506 {
507 if (val->contents)
be06a3df 508 /* Add width of the arrow displayed for submenus. */
07bf635f
RS
509 *rest_width += arrow_width (mw) + mw->menu.arrow_spacing;
510 else if (val->key)
be06a3df
GM
511 /* Add width of key equivalent string. */
512 *rest_width += (string_width (mw, val->key)
513 + mw->menu.arrow_spacing);
514
515 if (val->button_type == BUTTON_TYPE_TOGGLE)
516 *button_width = (toggle_button_width (mw)
517 + mw->menu.horizontal_spacing);
518 else if (val->button_type == BUTTON_TYPE_RADIO)
519 *button_width = (radio_button_width (mw)
520 + mw->menu.horizontal_spacing);
07bf635f
RS
521 }
522 }
523}
524
525static void
c3174d16 526size_menu (XlwMenuWidget mw, int level)
07bf635f 527{
ccf589a6 528 unsigned int label_width = 0;
07bf635f 529 int rest_width = 0;
be06a3df 530 int button_width = 0;
07bf635f 531 int max_rest_width = 0;
be06a3df 532 int max_button_width = 0;
ccf589a6 533 unsigned int height = 0;
07bf635f
RS
534 int horizontal_p = mw->menu.horizontal && (level == 0);
535 widget_value* val;
536 window_state* ws;
537
538 if (level >= mw->menu.old_depth)
be06a3df 539 abort_gracefully ((Widget) mw);
07bf635f 540
5c520e0a 541 ws = &mw->menu.windows [level];
07bf635f
RS
542 ws->width = 0;
543 ws->height = 0;
544 ws->label_width = 0;
be06a3df 545 ws->button_width = 0;
07bf635f
RS
546
547 for (val = mw->menu.old_stack [level]->contents; val; val = val->next)
548 {
549 size_menu_item (mw, val, horizontal_p, &label_width, &rest_width,
be06a3df 550 &button_width, &height);
07bf635f
RS
551 if (horizontal_p)
552 {
553 ws->width += label_width + rest_width;
554 if (height > ws->height)
555 ws->height = height;
556 }
557 else
558 {
559 if (label_width > ws->label_width)
560 ws->label_width = label_width;
561 if (rest_width > max_rest_width)
562 max_rest_width = rest_width;
be06a3df
GM
563 if (button_width > max_button_width)
564 max_button_width = button_width;
07bf635f
RS
565 ws->height += height;
566 }
567 }
5c520e0a 568
07bf635f 569 if (horizontal_p)
be06a3df 570 ws->label_width = ws->button_width = 0;
07bf635f 571 else
be06a3df
GM
572 {
573 ws->width = ws->label_width + max_rest_width + max_button_width;
574 ws->button_width = max_button_width;
575 }
07bf635f
RS
576
577 ws->width += 2 * mw->menu.shadow_thickness;
578 ws->height += 2 * mw->menu.shadow_thickness;
99852628 579 ws->max_rest_width = max_rest_width;
be06a3df
GM
580
581 if (horizontal_p)
582 {
583 ws->width += 2 * mw->menu.margin;
584 ws->height += 2 * mw->menu.margin;
585 }
07bf635f
RS
586}
587
588
589\f/* Display code */
be06a3df 590
07bf635f 591static void
c3174d16 592draw_arrow (XlwMenuWidget mw, Window window, GC gc, int x, int y, int width, int down_p)
07bf635f 593{
be06a3df
GM
594 Display *dpy = XtDisplay (mw);
595 GC top_gc = mw->menu.shadow_top_gc;
596 GC bottom_gc = mw->menu.shadow_bottom_gc;
597 int thickness = mw->menu.shadow_thickness;
598 int height = width;
599 XPoint pt[10];
600 /* alpha = atan (0.5)
601 factor = (1 + sin (alpha)) / cos (alpha) */
602 double factor = 1.62;
603 int thickness2 = thickness * factor;
5c520e0a 604
423a3f59 605 y += (MENU_FONT_HEIGHT (mw) - height) / 2;
5c520e0a 606
be06a3df
GM
607 if (down_p)
608 {
609 GC temp;
610 temp = top_gc;
611 top_gc = bottom_gc;
612 bottom_gc = temp;
613 }
614
615 pt[0].x = x;
616 pt[0].y = y + height;
617 pt[1].x = x + thickness;
618 pt[1].y = y + height - thickness2;
619 pt[2].x = x + thickness2;
620 pt[2].y = y + thickness2;
621 pt[3].x = x;
622 pt[3].y = y;
623 XFillPolygon (dpy, window, top_gc, pt, 4, Convex, CoordModeOrigin);
5c520e0a 624
be06a3df
GM
625 pt[0].x = x;
626 pt[0].y = y;
627 pt[1].x = x + thickness;
628 pt[1].y = y + thickness2;
629 pt[2].x = x + width - thickness2;
630 pt[2].y = y + height / 2;
631 pt[3].x = x + width;
632 pt[3].y = y + height / 2;
633 XFillPolygon (dpy, window, top_gc, pt, 4, Convex, CoordModeOrigin);
5c520e0a 634
be06a3df
GM
635 pt[0].x = x;
636 pt[0].y = y + height;
637 pt[1].x = x + thickness;
638 pt[1].y = y + height - thickness2;
639 pt[2].x = x + width - thickness2;
640 pt[2].y = y + height / 2;
641 pt[3].x = x + width;
642 pt[3].y = y + height / 2;
643 XFillPolygon (dpy, window, bottom_gc, pt, 4, Convex, CoordModeOrigin);
07bf635f
RS
644}
645
be06a3df
GM
646
647
07bf635f 648static void
c3174d16 649draw_shadow_rectangle (XlwMenuWidget mw, Window window, int x, int y, int width, int height, int erase_p, int down_p)
07bf635f
RS
650{
651 Display *dpy = XtDisplay (mw);
652 GC top_gc = !erase_p ? mw->menu.shadow_top_gc : mw->menu.background_gc;
653 GC bottom_gc = !erase_p ? mw->menu.shadow_bottom_gc : mw->menu.background_gc;
654 int thickness = mw->menu.shadow_thickness;
655 XPoint points [4];
be06a3df
GM
656
657 if (!erase_p && down_p)
658 {
659 GC temp;
660 temp = top_gc;
661 top_gc = bottom_gc;
662 bottom_gc = temp;
663 }
5c520e0a 664
07bf635f
RS
665 points [0].x = x;
666 points [0].y = y;
667 points [1].x = x + width;
668 points [1].y = y;
669 points [2].x = x + width - thickness;
670 points [2].y = y + thickness;
671 points [3].x = x;
672 points [3].y = y + thickness;
673 XFillPolygon (dpy, window, top_gc, points, 4, Convex, CoordModeOrigin);
674 points [0].x = x;
675 points [0].y = y + thickness;
676 points [1].x = x;
677 points [1].y = y + height;
678 points [2].x = x + thickness;
679 points [2].y = y + height - thickness;
680 points [3].x = x + thickness;
681 points [3].y = y + thickness;
682 XFillPolygon (dpy, window, top_gc, points, 4, Convex, CoordModeOrigin);
683 points [0].x = x + width;
684 points [0].y = y;
685 points [1].x = x + width - thickness;
686 points [1].y = y + thickness;
687 points [2].x = x + width - thickness;
688 points [2].y = y + height - thickness;
689 points [3].x = x + width;
690 points [3].y = y + height - thickness;
691 XFillPolygon (dpy, window, bottom_gc, points, 4, Convex, CoordModeOrigin);
692 points [0].x = x;
693 points [0].y = y + height;
694 points [1].x = x + width;
695 points [1].y = y + height;
696 points [2].x = x + width;
697 points [2].y = y + height - thickness;
698 points [3].x = x + thickness;
699 points [3].y = y + height - thickness;
700 XFillPolygon (dpy, window, bottom_gc, points, 4, Convex, CoordModeOrigin);
701}
702
703
be06a3df 704static void
c3174d16 705draw_shadow_rhombus (XlwMenuWidget mw, Window window, int x, int y, int width, int height, int erase_p, int down_p)
be06a3df
GM
706{
707 Display *dpy = XtDisplay (mw);
708 GC top_gc = !erase_p ? mw->menu.shadow_top_gc : mw->menu.background_gc;
709 GC bottom_gc = !erase_p ? mw->menu.shadow_bottom_gc : mw->menu.background_gc;
710 int thickness = mw->menu.shadow_thickness;
711 XPoint points [4];
be06a3df
GM
712
713 if (!erase_p && down_p)
714 {
715 GC temp;
716 temp = top_gc;
717 top_gc = bottom_gc;
718 bottom_gc = temp;
719 }
720
721 points [0].x = x;
722 points [0].y = y + height / 2;
723 points [1].x = x + thickness;
724 points [1].y = y + height / 2;
725 points [2].x = x + width / 2;
726 points [2].y = y + thickness;
727 points [3].x = x + width / 2;
728 points [3].y = y;
729 XFillPolygon (dpy, window, top_gc, points, 4, Convex, CoordModeOrigin);
730 points [0].x = x + width / 2;
731 points [0].y = y;
732 points [1].x = x + width / 2;
733 points [1].y = y + thickness;
734 points [2].x = x + width - thickness;
735 points [2].y = y + height / 2;
736 points [3].x = x + width;
737 points [3].y = y + height / 2;
738 XFillPolygon (dpy, window, top_gc, points, 4, Convex, CoordModeOrigin);
739 points [0].x = x;
740 points [0].y = y + height / 2;
741 points [1].x = x + thickness;
742 points [1].y = y + height / 2;
743 points [2].x = x + width / 2;
744 points [2].y = y + height - thickness;
745 points [3].x = x + width / 2;
746 points [3].y = y + height;
747 XFillPolygon (dpy, window, bottom_gc, points, 4, Convex, CoordModeOrigin);
748 points [0].x = x + width / 2;
749 points [0].y = y + height;
750 points [1].x = x + width / 2;
751 points [1].y = y + height - thickness;
752 points [2].x = x + width - thickness;
753 points [2].y = y + height / 2;
754 points [3].x = x + width;
755 points [3].y = y + height / 2;
756 XFillPolygon (dpy, window, bottom_gc, points, 4, Convex, CoordModeOrigin);
757}
758
759
760/* Draw a toggle button on widget MW, X window WINDOW. X/Y is the
761 top-left corner of the menu item. SELECTED_P non-zero means the
762 toggle button is selected. */
763
764static void
c3174d16 765draw_toggle (XlwMenuWidget mw, Window window, int x, int y, int selected_p)
be06a3df
GM
766{
767 int width, height;
768
769 width = toggle_button_width (mw);
770 height = width;
771 x += mw->menu.horizontal_spacing;
423a3f59 772 y += (MENU_FONT_ASCENT (mw) - height) / 2;
be06a3df
GM
773 draw_shadow_rectangle (mw, window, x, y, width, height, False, selected_p);
774}
775
776
777/* Draw a radio button on widget MW, X window WINDOW. X/Y is the
778 top-left corner of the menu item. SELECTED_P non-zero means the
779 toggle button is selected. */
780
781static void
c3174d16 782draw_radio (XlwMenuWidget mw, Window window, int x, int y, int selected_p)
be06a3df
GM
783{
784 int width, height;
785
786 width = radio_button_width (mw);
787 height = width;
788 x += mw->menu.horizontal_spacing;
423a3f59 789 y += (MENU_FONT_ASCENT (mw) - height) / 2;
be06a3df
GM
790 draw_shadow_rhombus (mw, window, x, y, width, height, False, selected_p);
791}
792
793
794/* Draw a menu separator on widget MW, X window WINDOW. X/Y is the
795 top-left corner of the menu item. WIDTH is the width of the
796 separator to draw. TYPE is the separator type. */
797
798static void
c3174d16 799draw_separator (XlwMenuWidget mw, Window window, int x, int y, int width, enum menu_separator type)
be06a3df
GM
800{
801 Display *dpy = XtDisplay (mw);
802 XGCValues xgcv;
803
804 switch (type)
805 {
806 case SEPARATOR_NO_LINE:
807 break;
5c520e0a 808
be06a3df
GM
809 case SEPARATOR_SINGLE_LINE:
810 XDrawLine (dpy, window, mw->menu.foreground_gc,
811 x, y, x + width, y);
812 break;
5c520e0a 813
be06a3df
GM
814 case SEPARATOR_DOUBLE_LINE:
815 draw_separator (mw, window, x, y, width, SEPARATOR_SINGLE_LINE);
816 draw_separator (mw, window, x, y + 2, width, SEPARATOR_SINGLE_LINE);
817 break;
5c520e0a 818
be06a3df
GM
819 case SEPARATOR_SINGLE_DASHED_LINE:
820 xgcv.line_style = LineOnOffDash;
821 XChangeGC (dpy, mw->menu.foreground_gc, GCLineStyle, &xgcv);
822 XDrawLine (dpy, window, mw->menu.foreground_gc,
823 x, y, x + width, y);
824 xgcv.line_style = LineSolid;
825 XChangeGC (dpy, mw->menu.foreground_gc, GCLineStyle, &xgcv);
826 break;
5c520e0a 827
be06a3df
GM
828 case SEPARATOR_DOUBLE_DASHED_LINE:
829 draw_separator (mw, window, x, y, width,
830 SEPARATOR_SINGLE_DASHED_LINE);
831 draw_separator (mw, window, x, y + 2, width,
832 SEPARATOR_SINGLE_DASHED_LINE);
833 break;
5c520e0a 834
be06a3df
GM
835 case SEPARATOR_SHADOW_ETCHED_IN:
836 XDrawLine (dpy, window, mw->menu.shadow_bottom_gc,
837 x, y, x + width, y);
838 XDrawLine (dpy, window, mw->menu.shadow_top_gc,
839 x, y + 1, x + width, y + 1);
840 break;
841
842 case SEPARATOR_SHADOW_ETCHED_OUT:
843 XDrawLine (dpy, window, mw->menu.shadow_top_gc,
844 x, y, x + width, y);
845 XDrawLine (dpy, window, mw->menu.shadow_bottom_gc,
846 x, y + 1, x + width, y + 1);
847 break;
5c520e0a 848
be06a3df
GM
849 case SEPARATOR_SHADOW_ETCHED_IN_DASH:
850 xgcv.line_style = LineOnOffDash;
851 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
852 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
2f90328f 853 draw_separator (mw, window, x, y, width, SEPARATOR_SHADOW_ETCHED_IN);
be06a3df
GM
854 xgcv.line_style = LineSolid;
855 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
856 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
857 break;
5c520e0a 858
be06a3df
GM
859 case SEPARATOR_SHADOW_ETCHED_OUT_DASH:
860 xgcv.line_style = LineOnOffDash;
861 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
862 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
2f90328f 863 draw_separator (mw, window, x, y, width, SEPARATOR_SHADOW_ETCHED_OUT);
be06a3df
GM
864 xgcv.line_style = LineSolid;
865 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
866 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
867 break;
868
869 case SEPARATOR_SHADOW_DOUBLE_ETCHED_IN:
870 draw_separator (mw, window, x, y, width, SEPARATOR_SHADOW_ETCHED_IN);
871 draw_separator (mw, window, x, y + 3, width, SEPARATOR_SHADOW_ETCHED_IN);
872 break;
5c520e0a 873
be06a3df
GM
874 case SEPARATOR_SHADOW_DOUBLE_ETCHED_OUT:
875 draw_separator (mw, window, x, y, width,
876 SEPARATOR_SHADOW_ETCHED_OUT);
877 draw_separator (mw, window, x, y + 3, width,
878 SEPARATOR_SHADOW_ETCHED_OUT);
879 break;
5c520e0a 880
be06a3df
GM
881 case SEPARATOR_SHADOW_DOUBLE_ETCHED_IN_DASH:
882 xgcv.line_style = LineOnOffDash;
883 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
884 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
885 draw_separator (mw, window, x, y, width,
886 SEPARATOR_SHADOW_DOUBLE_ETCHED_IN);
887 xgcv.line_style = LineSolid;
888 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
889 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
890 break;
891
892 case SEPARATOR_SHADOW_DOUBLE_ETCHED_OUT_DASH:
893 xgcv.line_style = LineOnOffDash;
894 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
895 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
896 draw_separator (mw, window, x, y, width,
897 SEPARATOR_SHADOW_DOUBLE_ETCHED_OUT);
898 xgcv.line_style = LineSolid;
899 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
900 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
901 break;
902
903 default:
904 abort ();
905 }
906}
907
908
909/* Return the pixel height of menu separator SEPARATOR. */
910
911static int
c3174d16 912separator_height (enum menu_separator separator)
be06a3df
GM
913{
914 switch (separator)
915 {
916 case SEPARATOR_NO_LINE:
917 return 2;
5c520e0a 918
be06a3df
GM
919 case SEPARATOR_SINGLE_LINE:
920 case SEPARATOR_SINGLE_DASHED_LINE:
921 return 1;
5c520e0a 922
be06a3df
GM
923 case SEPARATOR_DOUBLE_LINE:
924 case SEPARATOR_DOUBLE_DASHED_LINE:
925 return 3;
5c520e0a 926
be06a3df
GM
927 case SEPARATOR_SHADOW_ETCHED_IN:
928 case SEPARATOR_SHADOW_ETCHED_OUT:
929 case SEPARATOR_SHADOW_ETCHED_IN_DASH:
930 case SEPARATOR_SHADOW_ETCHED_OUT_DASH:
931 return 2;
5c520e0a 932
be06a3df
GM
933 case SEPARATOR_SHADOW_DOUBLE_ETCHED_IN:
934 case SEPARATOR_SHADOW_DOUBLE_ETCHED_OUT:
935 case SEPARATOR_SHADOW_DOUBLE_ETCHED_IN_DASH:
936 case SEPARATOR_SHADOW_DOUBLE_ETCHED_OUT_DASH:
937 return 5;
5c520e0a 938
be06a3df
GM
939 default:
940 abort ();
941 }
942}
943
944
07bf635f 945/* Display the menu item and increment where.x and where.y to show how large
be06a3df
GM
946 the menu item was. */
947
07bf635f 948static void
be06a3df
GM
949display_menu_item (mw, val, ws, where, highlighted_p, horizontal_p,
950 just_compute_p)
d398028f
PR
951 XlwMenuWidget mw;
952 widget_value* val;
953 window_state* ws;
954 XPoint* where;
955 Boolean highlighted_p;
956 Boolean horizontal_p;
957 Boolean just_compute_p;
07bf635f
RS
958{
959 GC deco_gc;
960 GC text_gc;
423a3f59
SM
961 int font_height = MENU_FONT_HEIGHT (mw);
962 int font_ascent = MENU_FONT_ASCENT (mw);
07bf635f 963 int shadow = mw->menu.shadow_thickness;
be06a3df 964 int margin = mw->menu.margin;
07bf635f
RS
965 int h_spacing = mw->menu.horizontal_spacing;
966 int v_spacing = mw->menu.vertical_spacing;
967 int label_width;
968 int rest_width;
be06a3df 969 int button_width;
07bf635f
RS
970 int height;
971 int width;
be06a3df
GM
972 enum menu_separator separator;
973 int separator_p = lw_separator_p (val->name, &separator, 0);
99852628
JD
974#ifdef HAVE_XFT
975 XftColor *xftfg;
976#endif
07bf635f
RS
977
978 /* compute the sizes of the item */
be06a3df
GM
979 size_menu_item (mw, val, horizontal_p, &label_width, &rest_width,
980 &button_width, &height);
07bf635f
RS
981
982 if (horizontal_p)
983 width = label_width + rest_width;
984 else
985 {
986 label_width = ws->label_width;
987 width = ws->width - 2 * shadow;
988 }
989
07bf635f
RS
990 /* Only highlight an enabled item that has a callback. */
991 if (highlighted_p)
992 if (!val->enabled || !(val->call_data || val->contents))
993 highlighted_p = 0;
994
995 /* do the drawing. */
996 if (!just_compute_p)
997 {
998 /* Add the shadow border of the containing menu */
999 int x = where->x + shadow;
1000 int y = where->y + shadow;
1001
be06a3df
GM
1002 if (horizontal_p)
1003 {
1004 x += margin;
1005 y += margin;
1006 }
1007
07bf635f
RS
1008 /* pick the foreground and background GC. */
1009 if (val->enabled)
be06a3df 1010 text_gc = mw->menu.foreground_gc;
07bf635f 1011 else
8b71a9ca 1012 text_gc = mw->menu.disabled_gc;
07bf635f 1013 deco_gc = mw->menu.foreground_gc;
99852628
JD
1014#ifdef HAVE_XFT
1015 xftfg = val->enabled ? &mw->menu.xft_fg : &mw->menu.xft_disabled_fg;
1016#endif
07bf635f
RS
1017
1018 if (separator_p)
1019 {
f0bd1598 1020 draw_separator (mw, ws->pixmap, x, y, width, separator);
07bf635f 1021 }
5c520e0a 1022 else
07bf635f 1023 {
84593cae 1024 int x_offset = x + h_spacing + shadow;
07bf635f 1025 char* display_string = resource_widget_value (mw, val);
f0bd1598 1026 draw_shadow_rectangle (mw, ws->pixmap, x, y, width, height, True,
be06a3df 1027 False);
84593cae
PR
1028
1029 /* Deal with centering a menu title. */
1030 if (!horizontal_p && !val->contents && !val->call_data)
1031 {
1032 int l = string_width (mw, display_string);
1033
1034 if (width > l)
1035 x_offset = (width - l) >> 1;
1036 }
be06a3df
GM
1037 else if (!horizontal_p && ws->button_width)
1038 x_offset += ws->button_width;
5c520e0a
SS
1039
1040
99852628
JD
1041#ifdef HAVE_XFT
1042 if (ws->xft_draw)
1043 {
1044 int draw_y = y + v_spacing + shadow;
99852628
JD
1045 XftDrawStringUtf8 (ws->xft_draw, xftfg,
1046 mw->menu.xft_font,
1047 x_offset, draw_y + font_ascent,
1048 (unsigned char *) display_string,
1049 strlen (display_string));
1050 }
1051 else
1052#endif
423a3f59 1053#ifdef HAVE_X_I18N
2015c054 1054 if (mw->menu.fontSet)
f0bd1598 1055 XmbDrawString (XtDisplay (mw), ws->pixmap, mw->menu.fontSet,
2015c054
JD
1056 text_gc, x_offset,
1057 y + v_spacing + shadow + font_ascent,
1058 display_string, strlen (display_string));
1059 else
423a3f59 1060#endif
f0bd1598 1061 XDrawString (XtDisplay (mw), ws->pixmap,
423a3f59 1062 text_gc, x_offset,
07bf635f
RS
1063 y + v_spacing + shadow + font_ascent,
1064 display_string, strlen (display_string));
5c520e0a 1065
07bf635f
RS
1066 if (!horizontal_p)
1067 {
be06a3df 1068 if (val->button_type == BUTTON_TYPE_TOGGLE)
f0bd1598 1069 draw_toggle (mw, ws->pixmap, x, y + v_spacing + shadow,
be06a3df
GM
1070 val->selected);
1071 else if (val->button_type == BUTTON_TYPE_RADIO)
f0bd1598 1072 draw_radio (mw, ws->pixmap, x, y + v_spacing + shadow,
be06a3df 1073 val->selected);
5c520e0a 1074
07bf635f
RS
1075 if (val->contents)
1076 {
1077 int a_w = arrow_width (mw);
f0bd1598 1078 draw_arrow (mw, ws->pixmap, deco_gc,
be06a3df 1079 x + width - a_w
5c520e0a 1080 - mw->menu.horizontal_spacing
d398028f 1081 - mw->menu.shadow_thickness,
be06a3df
GM
1082 y + v_spacing + shadow, a_w,
1083 highlighted_p);
07bf635f
RS
1084 }
1085 else if (val->key)
1086 {
99852628
JD
1087#ifdef HAVE_XFT
1088 if (ws->xft_draw)
1089 {
99852628
JD
1090 int draw_x = ws->width - ws->max_rest_width
1091 + mw->menu.arrow_spacing;
1092 int draw_y = y + v_spacing + shadow + font_ascent;
1093 XftDrawStringUtf8 (ws->xft_draw, xftfg,
1094 mw->menu.xft_font,
1095 draw_x, draw_y,
1096 (unsigned char *) val->key,
1097 strlen (val->key));
1098 }
1099 else
1100#endif
423a3f59 1101#ifdef HAVE_X_I18N
2015c054 1102 if (mw->menu.fontSet)
f0bd1598 1103 XmbDrawString (XtDisplay (mw), ws->pixmap,
2015c054
JD
1104 mw->menu.fontSet,
1105 text_gc,
1106 x + label_width + mw->menu.arrow_spacing,
1107 y + v_spacing + shadow + font_ascent,
1108 val->key, strlen (val->key));
1109 else
423a3f59 1110#endif
f0bd1598 1111 XDrawString (XtDisplay (mw), ws->pixmap,
423a3f59 1112 text_gc,
07bf635f
RS
1113 x + label_width + mw->menu.arrow_spacing,
1114 y + v_spacing + shadow + font_ascent,
1115 val->key, strlen (val->key));
1116 }
1117 }
d398028f
PR
1118 else
1119 {
f0bd1598 1120 XDrawRectangle (XtDisplay (mw), ws->pixmap,
d398028f
PR
1121 mw->menu.background_gc,
1122 x + shadow, y + shadow,
1123 label_width + h_spacing - 1,
423a3f59 1124 font_height + 2 * v_spacing - 1);
f0bd1598 1125 draw_shadow_rectangle (mw, ws->pixmap, x, y, width, height,
be06a3df 1126 True, False);
d398028f 1127 }
07bf635f
RS
1128
1129 if (highlighted_p)
f0bd1598 1130 draw_shadow_rectangle (mw, ws->pixmap, x, y, width, height, False,
be06a3df 1131 False);
07bf635f
RS
1132 }
1133 }
5c520e0a 1134
07bf635f
RS
1135 where->x += width;
1136 where->y += height;
1137}
1138
1139static void
c3174d16 1140display_menu (XlwMenuWidget mw, int level, Boolean just_compute_p, XPoint *highlighted_pos, XPoint *hit, widget_value **hit_return)
07bf635f
RS
1141{
1142 widget_value* val;
1143 widget_value* following_item;
1144 window_state* ws;
1145 XPoint where;
1146 int horizontal_p = mw->menu.horizontal && (level == 0);
1147 int highlighted_p;
ccc1cac0 1148 int no_return = 0;
be06a3df 1149 enum menu_separator separator;
07bf635f
RS
1150
1151 if (level >= mw->menu.old_depth)
be06a3df 1152 abort_gracefully ((Widget) mw);
07bf635f
RS
1153
1154 if (level < mw->menu.old_depth - 1)
1155 following_item = mw->menu.old_stack [level + 1];
5c520e0a 1156 else
07bf635f
RS
1157 following_item = NULL;
1158
1159 if (hit)
1160 *hit_return = NULL;
1161
1162 where.x = 0;
1163 where.y = 0;
1164
1165 ws = &mw->menu.windows [level];
f0bd1598
J
1166
1167 if (!just_compute_p)
1168 XFillRectangle (XtDisplay (mw), ws->pixmap, mw->menu.background_gc,
1169 0, 0, ws->width, ws->height);
1170
07bf635f
RS
1171 for (val = mw->menu.old_stack [level]->contents; val; val = val->next)
1172 {
1173 highlighted_p = val == following_item;
1174 if (highlighted_p && highlighted_pos)
1175 {
1176 if (horizontal_p)
1177 highlighted_pos->x = where.x;
1178 else
1179 highlighted_pos->y = where.y;
1180 }
5c520e0a 1181
07bf635f 1182 display_menu_item (mw, val, ws, &where, highlighted_p, horizontal_p,
f0bd1598 1183 just_compute_p);
07bf635f
RS
1184
1185 if (highlighted_p && highlighted_pos)
1186 {
1187 if (horizontal_p)
1188 highlighted_pos->y = where.y;
1189 else
1190 highlighted_pos->x = where.x;
1191 }
1192
1193 if (hit
1194 && !*hit_return
1195 && (horizontal_p ? hit->x < where.x : hit->y < where.y)
be06a3df 1196 && !lw_separator_p (val->name, &separator, 0)
ccc1cac0
RS
1197 && !no_return)
1198 {
1199 if (val->enabled)
1200 *hit_return = val;
1ecd5086
JD
1201 else
1202 no_return = 1;
1203 if (mw->menu.inside_entry != val)
1204 {
1205 if (mw->menu.inside_entry)
1206 XtCallCallbackList ((Widget)mw, mw->menu.leave,
1207 (XtPointer) mw->menu.inside_entry);
1208 mw->menu.inside_entry = val;
1209 XtCallCallbackList ((Widget)mw, mw->menu.enter,
1210 (XtPointer) mw->menu.inside_entry);
1211 }
ccc1cac0 1212 }
07bf635f
RS
1213
1214 if (horizontal_p)
1215 where.y = 0;
1216 else
1217 where.x = 0;
1218 }
5c520e0a 1219
07bf635f 1220 if (!just_compute_p)
f0bd1598
J
1221 {
1222 draw_shadow_rectangle (mw, ws->pixmap, 0, 0, ws->width, ws->height,
1223 False, False);
1224 XCopyArea (XtDisplay (mw), ws->pixmap, ws->window,
1225 mw->menu.foreground_gc, 0, 0, ws->width, ws->height, 0, 0);
1226 }
07bf635f
RS
1227}
1228
1229\f/* Motion code */
1230static void
c3174d16 1231set_new_state (XlwMenuWidget mw, widget_value *val, int level)
07bf635f
RS
1232{
1233 int i;
5c520e0a 1234
07bf635f
RS
1235 mw->menu.new_depth = 0;
1236 for (i = 0; i < level; i++)
1237 push_new_stack (mw, mw->menu.old_stack [i]);
1238 push_new_stack (mw, val);
1239}
1240
f0bd1598
J
1241static void
1242expose_cb (Widget widget,
1243 XtPointer closure,
1244 XEvent* event,
1245 Boolean* continue_to_dispatch)
1246{
f0bd1598
J
1247 XlwMenuWidget mw = (XlwMenuWidget) closure;
1248 int i;
1249
c632dfda 1250 *continue_to_dispatch = False;
f0bd1598
J
1251 for (i = 0; i < mw->menu.windows_length; ++i)
1252 if (mw->menu.windows [i].w == widget) break;
1253 if (i < mw->menu.windows_length && i < mw->menu.old_depth)
c3174d16 1254 display_menu (mw, i, False, NULL, NULL, NULL);
f0bd1598
J
1255}
1256
7aad87e8
JD
1257static void
1258set_window_type (Widget w, XlwMenuWidget mw)
1259{
1260 int popup_menu_p = mw->menu.top_depth == 1;
1261 Atom type = XInternAtom (XtDisplay (w),
1262 popup_menu_p
1263 ? "_NET_WM_WINDOW_TYPE_POPUP_MENU"
1264 : "_NET_WM_WINDOW_TYPE_DROPDOWN_MENU",
1265 False);
1266
1267 XChangeProperty (XtDisplay (w), XtWindow (w),
1268 XInternAtom (XtDisplay (w), "_NET_WM_WINDOW_TYPE", False),
1269 XA_ATOM, 32, PropModeReplace,
1270 (unsigned char *)&type, 1);
1271}
1272
1273
07bf635f 1274static void
c3174d16 1275make_windows_if_needed (XlwMenuWidget mw, int n)
07bf635f
RS
1276{
1277 int i;
1278 int start_at;
07bf635f 1279 window_state* windows;
5c520e0a 1280
07bf635f
RS
1281 if (mw->menu.windows_length >= n)
1282 return;
1283
07bf635f
RS
1284 if (!mw->menu.windows)
1285 {
1286 mw->menu.windows =
1287 (window_state*)XtMalloc (n * sizeof (window_state));
1288 start_at = 0;
1289 }
1290 else
1291 {
1292 mw->menu.windows =
1293 (window_state*)XtRealloc ((char*)mw->menu.windows,
1294 n * sizeof (window_state));
1295 start_at = mw->menu.windows_length;
1296 }
1297 mw->menu.windows_length = n;
1298
1299 windows = mw->menu.windows;
1300
1301 for (i = start_at; i < n; i++)
1302 {
f0bd1598
J
1303 Arg av[10];
1304 int ac = 0;
07bf635f
RS
1305 windows [i].x = 0;
1306 windows [i].y = 0;
1307 windows [i].width = 1;
1308 windows [i].height = 1;
99852628 1309 windows [i].max_rest_width = 0;
f0bd1598
J
1310 XtSetArg (av[ac], XtNwidth, 1); ++ac;
1311 XtSetArg (av[ac], XtNheight, 1); ++ac;
1312 XtSetArg (av[ac], XtNsaveUnder, True); ++ac;
1313 XtSetArg (av[ac], XtNbackground, mw->core.background_pixel); ++ac;
1314 XtSetArg (av[ac], XtNborderColor, mw->core.border_pixel); ++ac;
1315 XtSetArg (av[ac], XtNborderWidth, mw->core.border_width); ++ac;
1316 XtSetArg (av[ac], XtNcursor, mw->menu.cursor_shape); ++ac;
1317 windows [i].w =
1318 XtCreatePopupShell ("sub", overrideShellWidgetClass,
1319 (Widget) mw, av, ac);
1320 XtRealizeWidget (windows [i].w);
1321 XtAddEventHandler (windows [i].w, ExposureMask, False, expose_cb, mw);
1322 windows [i].window = XtWindow (windows [i].w);
1323 windows [i].pixmap = None;
99852628 1324#ifdef HAVE_XFT
f0bd1598 1325 windows [i].xft_draw = 0;
99852628 1326#endif
7aad87e8 1327 set_window_type (windows [i].w, mw);
99852628 1328 }
07bf635f
RS
1329}
1330
2a692ba4
GM
1331/* Value is non-zero if WINDOW is part of menu bar widget W. */
1332
1333int
c3174d16 1334xlwmenu_window_p (Widget w, Window window)
2a692ba4
GM
1335{
1336 XlwMenuWidget mw = (XlwMenuWidget) w;
1337 int i;
177c0ea7 1338
2a692ba4
GM
1339 for (i = 0; i < mw->menu.windows_length; ++i)
1340 if (window == mw->menu.windows[i].window)
1341 break;
1342
1343 return i < mw->menu.windows_length;
1344}
1345
07bf635f
RS
1346/* Make the window fit in the screen */
1347static void
c3174d16 1348fit_to_screen (XlwMenuWidget mw, window_state *ws, window_state *previous_ws, Boolean horizontal_p)
07bf635f 1349{
ccf589a6
RS
1350 unsigned int screen_width = WidthOfScreen (XtScreen (mw));
1351 unsigned int screen_height = HeightOfScreen (XtScreen (mw));
1ba46f7d
RS
1352 /* 1 if we are unable to avoid an overlap between
1353 this menu and the parent menu in the X dimension. */
1354 int horizontal_overlap = 0;
07bf635f
RS
1355
1356 if (ws->x < 0)
1357 ws->x = 0;
1358 else if (ws->x + ws->width > screen_width)
1359 {
1360 if (!horizontal_p)
b7fa4a06
MB
1361 /* The addition of shadow-thickness for a sub-menu's position is
1362 to reflect a similar adjustment when the menu is displayed to
1363 the right of the invoking menu-item; it makes the sub-menu
1364 look more `attached' to the menu-item. */
1365 ws->x = previous_ws->x - ws->width + mw->menu.shadow_thickness;
07bf635f
RS
1366 else
1367 ws->x = screen_width - ws->width;
525b8232 1368 if (ws->x < 0)
1ba46f7d
RS
1369 {
1370 ws->x = 0;
1371 horizontal_overlap = 1;
1372 }
1373 }
1374 /* If we overlap in X, try to avoid overlap in Y. */
1375 if (horizontal_overlap
1376 && ws->y < previous_ws->y + previous_ws->height
1377 && previous_ws->y < ws->y + ws->height)
1378 {
1379 /* Put this menu right below or right above PREVIOUS_WS
1380 if there's room. */
1381 if (previous_ws->y + previous_ws->height + ws->height < screen_height)
1382 ws->y = previous_ws->y + previous_ws->height;
1383 else if (previous_ws->y - ws->height > 0)
1384 ws->y = previous_ws->y - ws->height;
07bf635f 1385 }
1ba46f7d 1386
07bf635f
RS
1387 if (ws->y < 0)
1388 ws->y = 0;
1389 else if (ws->y + ws->height > screen_height)
1390 {
1391 if (horizontal_p)
1392 ws->y = previous_ws->y - ws->height;
1393 else
1394 ws->y = screen_height - ws->height;
5c520e0a 1395 if (ws->y < 0)
525b8232 1396 ws->y = 0;
07bf635f
RS
1397 }
1398}
1399
f0bd1598
J
1400static void
1401create_pixmap_for_menu (window_state* ws, XlwMenuWidget mw)
1402{
1403 if (ws->pixmap != None)
1404 {
1405 XFreePixmap (XtDisplay (ws->w), ws->pixmap);
1406 ws->pixmap = None;
1407 }
1408 ws->pixmap = XCreatePixmap (XtDisplay (ws->w), ws->window,
1409 ws->width, ws->height,
1410 DefaultDepthOfScreen (XtScreen (ws->w)));
1411#ifdef HAVE_XFT
1412 if (ws->xft_draw)
1413 XftDrawDestroy (ws->xft_draw);
1414 if (mw->menu.xft_font)
1415 {
1416 int screen = XScreenNumberOfScreen (mw->core.screen);
1417 ws->xft_draw = XftDrawCreate (XtDisplay (ws->w),
1418 ws->pixmap,
1419 DefaultVisual (XtDisplay (ws->w), screen),
1420 mw->core.colormap);
1421 }
1422 else
1423 ws->xft_draw = 0;
1424#endif
1425}
1426
07bf635f
RS
1427/* Updates old_stack from new_stack and redisplays. */
1428static void
c3174d16 1429remap_menubar (XlwMenuWidget mw)
07bf635f
RS
1430{
1431 int i;
1432 int last_same;
1433 XPoint selection_position;
1434 int old_depth = mw->menu.old_depth;
1435 int new_depth = mw->menu.new_depth;
1436 widget_value** old_stack;
1437 widget_value** new_stack;
1438 window_state* windows;
1439 widget_value* old_selection;
1440 widget_value* new_selection;
1441
1442 /* Check that enough windows and old_stack are ready. */
1443 make_windows_if_needed (mw, new_depth);
1444 make_old_stack_space (mw, new_depth);
1445 windows = mw->menu.windows;
1446 old_stack = mw->menu.old_stack;
1447 new_stack = mw->menu.new_stack;
1448
1449 /* compute the last identical different entry */
1450 for (i = 1; i < old_depth && i < new_depth; i++)
1451 if (old_stack [i] != new_stack [i])
1452 break;
1453 last_same = i - 1;
1454
1455 /* Memorize the previously selected item to be able to refresh it */
1456 old_selection = last_same + 1 < old_depth ? old_stack [last_same + 1] : NULL;
1457 if (old_selection && !old_selection->enabled)
1458 old_selection = NULL;
1459 new_selection = last_same + 1 < new_depth ? new_stack [last_same + 1] : NULL;
1460 if (new_selection && !new_selection->enabled)
1461 new_selection = NULL;
1462
d6fd6371
GM
1463 /* Call callback when the hightlighted item changes. */
1464 if (old_selection || new_selection)
1465 XtCallCallbackList ((Widget)mw, mw->menu.highlight,
1466 (XtPointer) new_selection);
1467
07bf635f
RS
1468 /* updates old_state from new_state. It has to be done now because
1469 display_menu (called below) uses the old_stack to know what to display. */
1470 for (i = last_same + 1; i < new_depth; i++)
f0bd1598
J
1471 {
1472 XtPopdown (mw->menu.windows [i].w);
1473 old_stack [i] = new_stack [i];
1474 }
07bf635f
RS
1475 mw->menu.old_depth = new_depth;
1476
908ff139 1477 /* refresh the last selection */
07bf635f
RS
1478 selection_position.x = 0;
1479 selection_position.y = 0;
1480 display_menu (mw, last_same, new_selection == old_selection,
f0bd1598 1481 &selection_position, NULL, NULL);
07bf635f 1482
1ba46f7d
RS
1483 /* Now place the new menus. */
1484 for (i = last_same + 1; i < new_depth && new_stack[i]->contents; i++)
07bf635f 1485 {
1ba46f7d
RS
1486 window_state *previous_ws = &windows[i - 1];
1487 window_state *ws = &windows[i];
07bf635f 1488
be06a3df
GM
1489 ws->x = (previous_ws->x + selection_position.x
1490 + mw->menu.shadow_thickness);
b7fa4a06 1491 if (mw->menu.horizontal && i == 1)
be06a3df
GM
1492 ws->x += mw->menu.margin;
1493
1494#if 0
07bf635f
RS
1495 if (!mw->menu.horizontal || i > 1)
1496 ws->x += mw->menu.shadow_thickness;
be06a3df 1497#endif
5c520e0a 1498
be06a3df
GM
1499 ws->y = (previous_ws->y + selection_position.y
1500 + mw->menu.shadow_thickness);
b7fa4a06 1501 if (mw->menu.horizontal && i == 1)
be06a3df 1502 ws->y += mw->menu.margin;
07bf635f
RS
1503
1504 size_menu (mw, i);
1505
1506 fit_to_screen (mw, ws, previous_ws, mw->menu.horizontal && i == 1);
1507
f0bd1598
J
1508 XtVaSetValues (ws->w, XtNwidth, ws->width, XtNheight, ws->height,
1509 XtNx, ws->x, XtNy, ws->y, NULL);
1510 create_pixmap_for_menu (ws, mw);
1511 XtPopup (ws->w, XtGrabNone);
1512 display_menu (mw, i, False, &selection_position, NULL, NULL);
07bf635f
RS
1513 }
1514
1515 /* unmap the menus that popped down */
1516 for (i = new_depth - 1; i < old_depth; i++)
fa74535d 1517 if (i >= new_depth || (i > 0 && !new_stack[i]->contents))
f0bd1598 1518 XtPopdown (windows[i].w);
07bf635f
RS
1519}
1520
1521static Boolean
c3174d16 1522motion_event_is_in_menu (XlwMenuWidget mw, XMotionEvent *ev, int level, XPoint *relative_pos)
07bf635f
RS
1523{
1524 window_state* ws = &mw->menu.windows [level];
4f08464c
RS
1525 int shadow = level == 0 ? 0 : mw->menu.shadow_thickness;
1526 int x = ws->x + shadow;
1527 int y = ws->y + shadow;
07bf635f
RS
1528 relative_pos->x = ev->x_root - x;
1529 relative_pos->y = ev->y_root - y;
4f08464c 1530 return (x - shadow < ev->x_root && ev->x_root < x + ws->width
1ecd5086 1531 && y - shadow < ev->y_root && ev->y_root < y + ws->height);
07bf635f
RS
1532}
1533
1534static Boolean
c3174d16 1535map_event_to_widget_value (XlwMenuWidget mw, XMotionEvent *ev, widget_value **val, int *level)
07bf635f
RS
1536{
1537 int i;
1538 XPoint relative_pos;
1539 window_state* ws;
1ecd5086 1540 int inside = 0;
07bf635f
RS
1541
1542 *val = NULL;
5c520e0a 1543
07bf635f
RS
1544 /* Find the window */
1545 for (i = mw->menu.old_depth - 1; i >= 0; i--)
1546 {
1547 ws = &mw->menu.windows [i];
1548 if (ws && motion_event_is_in_menu (mw, ev, i, &relative_pos))
1549 {
1ecd5086 1550 inside = 1;
f0bd1598 1551 display_menu (mw, i, True, NULL, &relative_pos, val);
07bf635f
RS
1552
1553 if (*val)
1554 {
1555 *level = i + 1;
1556 return True;
1557 }
1558 }
1559 }
1ecd5086
JD
1560
1561 if (!inside)
1562 {
1563 if (mw->menu.inside_entry != NULL)
1564 XtCallCallbackList ((Widget)mw, mw->menu.leave,
1565 (XtPointer) mw->menu.inside_entry);
1566 mw->menu.inside_entry = NULL;
1567 }
1568
07bf635f
RS
1569 return False;
1570}
1571
1572\f/* Procedures */
1573static void
c3174d16 1574make_drawing_gcs (XlwMenuWidget mw)
07bf635f
RS
1575{
1576 XGCValues xgcv;
ef93458b 1577 float scale;
2015c054 1578 XtGCMask mask = GCForeground | GCBackground;
07bf635f 1579
2015c054
JD
1580#ifdef HAVE_X_I18N
1581 if (!mw->menu.fontSet)
1582 {
1583 xgcv.font = mw->menu.font->fid;
1584 mask |= GCFont;
1585 }
1586#else
07bf635f 1587 xgcv.font = mw->menu.font->fid;
2015c054 1588 mask |= GCFont;
423a3f59 1589#endif
07bf635f
RS
1590 xgcv.foreground = mw->menu.foreground;
1591 xgcv.background = mw->core.background_pixel;
2015c054 1592 mw->menu.foreground_gc = XtGetGC ((Widget)mw, mask, &xgcv);
5c520e0a 1593
07bf635f 1594 xgcv.foreground = mw->menu.button_foreground;
2015c054 1595 mw->menu.button_gc = XtGetGC ((Widget)mw, mask, &xgcv);
5c520e0a 1596
07bf635f 1597 xgcv.background = mw->core.background_pixel;
8b71a9ca
PJ
1598
1599#define BRIGHTNESS(color) (((color) & 0xff) + (((color) >> 8) & 0xff) + (((color) >> 16) & 0xff))
1600
1601 /* Allocate color for disabled menu-items. */
ef93458b 1602 mw->menu.disabled_foreground = mw->menu.foreground;
8b71a9ca 1603 if (BRIGHTNESS(mw->menu.foreground) < BRIGHTNESS(mw->core.background_pixel))
ef93458b 1604 scale = 2.3;
8b71a9ca 1605 else
ef93458b 1606 scale = 0.55;
8b71a9ca
PJ
1607
1608 x_alloc_lighter_color_for_widget ((Widget) mw, XtDisplay ((Widget) mw),
1609 mw->core.colormap,
ef93458b
PJ
1610 &mw->menu.disabled_foreground,
1611 scale,
8b71a9ca 1612 0x8000);
8b71a9ca
PJ
1613
1614 if (mw->menu.foreground == mw->menu.disabled_foreground
1615 || mw->core.background_pixel == mw->menu.disabled_foreground)
1616 {
1617 /* Too few colors, use stipple. */
1618 xgcv.foreground = mw->menu.foreground;
1619 xgcv.fill_style = FillStippled;
1620 xgcv.stipple = mw->menu.gray_pixmap;
2015c054 1621 mw->menu.disabled_gc = XtGetGC ((Widget)mw, mask
423a3f59 1622 | GCFillStyle | GCStipple, &xgcv);
8b71a9ca
PJ
1623 }
1624 else
1625 {
1626 /* Many colors available, use disabled pixel. */
1627 xgcv.foreground = mw->menu.disabled_foreground;
2015c054 1628 mw->menu.disabled_gc = XtGetGC ((Widget)mw, mask, &xgcv);
8b71a9ca 1629 }
5c520e0a 1630
07bf635f
RS
1631 xgcv.foreground = mw->menu.button_foreground;
1632 xgcv.background = mw->core.background_pixel;
1633 xgcv.fill_style = FillStippled;
1634 xgcv.stipple = mw->menu.gray_pixmap;
2015c054 1635 mw->menu.inactive_button_gc = XtGetGC ((Widget)mw, mask
423a3f59 1636 | GCFillStyle | GCStipple, &xgcv);
5c520e0a 1637
07bf635f
RS
1638 xgcv.foreground = mw->core.background_pixel;
1639 xgcv.background = mw->menu.foreground;
2015c054 1640 mw->menu.background_gc = XtGetGC ((Widget)mw, mask, &xgcv);
07bf635f
RS
1641}
1642
1643static void
c3174d16 1644release_drawing_gcs (XlwMenuWidget mw)
07bf635f
RS
1645{
1646 XtReleaseGC ((Widget) mw, mw->menu.foreground_gc);
1647 XtReleaseGC ((Widget) mw, mw->menu.button_gc);
8b71a9ca 1648 XtReleaseGC ((Widget) mw, mw->menu.disabled_gc);
07bf635f
RS
1649 XtReleaseGC ((Widget) mw, mw->menu.inactive_button_gc);
1650 XtReleaseGC ((Widget) mw, mw->menu.background_gc);
1651 /* let's get some segvs if we try to use these... */
1652 mw->menu.foreground_gc = (GC) -1;
1653 mw->menu.button_gc = (GC) -1;
8b71a9ca 1654 mw->menu.disabled_gc = (GC) -1;
07bf635f
RS
1655 mw->menu.inactive_button_gc = (GC) -1;
1656 mw->menu.background_gc = (GC) -1;
1657}
1658
1659#define MINL(x,y) ((((unsigned long) (x)) < ((unsigned long) (y))) \
1660 ? ((unsigned long) (x)) : ((unsigned long) (y)))
1661
1662static void
c3174d16 1663make_shadow_gcs (XlwMenuWidget mw)
07bf635f
RS
1664{
1665 XGCValues xgcv;
1666 unsigned long pm = 0;
1667 Display *dpy = XtDisplay ((Widget) mw);
be06a3df 1668 Screen *screen = XtScreen ((Widget) mw);
b0404f9f 1669 Colormap cmap = mw->core.colormap;
07bf635f
RS
1670 XColor topc, botc;
1671 int top_frobbed = 0, bottom_frobbed = 0;
1672
bba2a923
GM
1673 mw->menu.free_top_shadow_color_p = 0;
1674 mw->menu.free_bottom_shadow_color_p = 0;
1675
07bf635f
RS
1676 if (mw->menu.top_shadow_color == -1)
1677 mw->menu.top_shadow_color = mw->core.background_pixel;
bba2a923
GM
1678 else
1679 mw->menu.top_shadow_color = mw->menu.top_shadow_color;
5c520e0a 1680
07bf635f
RS
1681 if (mw->menu.bottom_shadow_color == -1)
1682 mw->menu.bottom_shadow_color = mw->menu.foreground;
bba2a923
GM
1683 else
1684 mw->menu.bottom_shadow_color = mw->menu.bottom_shadow_color;
07bf635f
RS
1685
1686 if (mw->menu.top_shadow_color == mw->core.background_pixel ||
1687 mw->menu.top_shadow_color == mw->menu.foreground)
1688 {
1689 topc.pixel = mw->core.background_pixel;
65c0ae05
MB
1690#ifdef emacs
1691 if (x_alloc_lighter_color_for_widget ((Widget) mw, dpy, cmap,
1692 &topc.pixel,
1693 1.2, 0x8000))
1694#else
07bf635f
RS
1695 XQueryColor (dpy, cmap, &topc);
1696 /* don't overflow/wrap! */
1697 topc.red = MINL (65535, topc.red * 1.2);
1698 topc.green = MINL (65535, topc.green * 1.2);
1699 topc.blue = MINL (65535, topc.blue * 1.2);
1700 if (XAllocColor (dpy, cmap, &topc))
be06a3df 1701#endif
07bf635f
RS
1702 {
1703 mw->menu.top_shadow_color = topc.pixel;
bba2a923 1704 mw->menu.free_top_shadow_color_p = 1;
07bf635f
RS
1705 top_frobbed = 1;
1706 }
1707 }
1708 if (mw->menu.bottom_shadow_color == mw->menu.foreground ||
1709 mw->menu.bottom_shadow_color == mw->core.background_pixel)
1710 {
1711 botc.pixel = mw->core.background_pixel;
65c0ae05
MB
1712#ifdef emacs
1713 if (x_alloc_lighter_color_for_widget ((Widget) mw, dpy, cmap,
1714 &botc.pixel,
1715 0.6, 0x4000))
1716#else
07bf635f
RS
1717 XQueryColor (dpy, cmap, &botc);
1718 botc.red *= 0.6;
1719 botc.green *= 0.6;
1720 botc.blue *= 0.6;
1721 if (XAllocColor (dpy, cmap, &botc))
be06a3df 1722#endif
07bf635f
RS
1723 {
1724 mw->menu.bottom_shadow_color = botc.pixel;
bba2a923 1725 mw->menu.free_bottom_shadow_color_p = 1;
07bf635f
RS
1726 bottom_frobbed = 1;
1727 }
1728 }
1729
1730 if (top_frobbed && bottom_frobbed)
1731 {
65c0ae05 1732 if (topc.pixel == botc.pixel)
07bf635f
RS
1733 {
1734 if (botc.pixel == mw->menu.foreground)
fc8fefed 1735 {
bba2a923
GM
1736 if (mw->menu.free_top_shadow_color_p)
1737 {
1738 x_free_dpy_colors (dpy, screen, cmap,
1739 &mw->menu.top_shadow_color, 1);
1740 mw->menu.free_top_shadow_color_p = 0;
1741 }
1742 mw->menu.top_shadow_color = mw->core.background_pixel;
fc8fefed 1743 }
07bf635f 1744 else
fc8fefed 1745 {
bba2a923
GM
1746 if (mw->menu.free_bottom_shadow_color_p)
1747 {
1748 x_free_dpy_colors (dpy, screen, cmap,
1749 &mw->menu.bottom_shadow_color, 1);
1750 mw->menu.free_bottom_shadow_color_p = 0;
1751 }
1752 mw->menu.bottom_shadow_color = mw->menu.foreground;
fc8fefed 1753 }
07bf635f
RS
1754 }
1755 }
1756
1757 if (!mw->menu.top_shadow_pixmap &&
1758 mw->menu.top_shadow_color == mw->core.background_pixel)
1759 {
1760 mw->menu.top_shadow_pixmap = mw->menu.gray_pixmap;
bba2a923
GM
1761 if (mw->menu.free_top_shadow_color_p)
1762 {
1763 x_free_dpy_colors (dpy, screen, cmap, &mw->menu.top_shadow_color, 1);
1764 mw->menu.free_top_shadow_color_p = 0;
1765 }
1766 mw->menu.top_shadow_color = mw->menu.foreground;
07bf635f
RS
1767 }
1768 if (!mw->menu.bottom_shadow_pixmap &&
1769 mw->menu.bottom_shadow_color == mw->core.background_pixel)
1770 {
1771 mw->menu.bottom_shadow_pixmap = mw->menu.gray_pixmap;
bba2a923
GM
1772 if (mw->menu.free_bottom_shadow_color_p)
1773 {
1774 x_free_dpy_colors (dpy, screen, cmap,
1775 &mw->menu.bottom_shadow_color, 1);
1776 mw->menu.free_bottom_shadow_color_p = 0;
1777 }
1778 mw->menu.bottom_shadow_color = mw->menu.foreground;
07bf635f
RS
1779 }
1780
1781 xgcv.fill_style = FillStippled;
1782 xgcv.foreground = mw->menu.top_shadow_color;
1783 xgcv.stipple = mw->menu.top_shadow_pixmap;
1784 pm = (xgcv.stipple ? GCStipple|GCFillStyle : 0);
1785 mw->menu.shadow_top_gc = XtGetGC ((Widget)mw, GCForeground | pm, &xgcv);
1786
1787 xgcv.foreground = mw->menu.bottom_shadow_color;
1788 xgcv.stipple = mw->menu.bottom_shadow_pixmap;
1789 pm = (xgcv.stipple ? GCStipple|GCFillStyle : 0);
1790 mw->menu.shadow_bottom_gc = XtGetGC ((Widget)mw, GCForeground | pm, &xgcv);
1791}
1792
1793
1794static void
c3174d16 1795release_shadow_gcs (XlwMenuWidget mw)
07bf635f 1796{
fc8fefed
GM
1797 Display *dpy = XtDisplay ((Widget) mw);
1798 Screen *screen = XtScreen ((Widget) mw);
1799 Colormap cmap = mw->core.colormap;
1800 Pixel px[2];
bba2a923
GM
1801 int i = 0;
1802
1803 if (mw->menu.free_top_shadow_color_p)
1804 px[i++] = mw->menu.top_shadow_color;
1805 if (mw->menu.free_bottom_shadow_color_p)
1806 px[i++] = mw->menu.bottom_shadow_color;
1807 if (i > 0)
1808 x_free_dpy_colors (dpy, screen, cmap, px, i);
5c520e0a 1809
07bf635f
RS
1810 XtReleaseGC ((Widget) mw, mw->menu.shadow_top_gc);
1811 XtReleaseGC ((Widget) mw, mw->menu.shadow_bottom_gc);
1812}
1813
99852628
JD
1814#ifdef HAVE_XFT
1815static int
c3174d16 1816openXftFont (XlwMenuWidget mw)
99852628
JD
1817{
1818 char *fname = mw->menu.faceName;
1819
1820 mw->menu.xft_font = 0;
1821 mw->menu.default_face = fname && strcmp (fname, DEFAULT_FACENAME) == 0;
1822
1823 if (fname && strcmp (fname, "none") != 0)
1824 {
1825 int screen = XScreenNumberOfScreen (mw->core.screen);
1826 int len = strlen (fname), i = len-1;
1827 /* Try to convert Gtk-syntax (Sans 9) to Xft syntax Sans-9. */
1828 while (i > 0 && isdigit (fname[i]))
1829 --i;
1830 if (fname[i] == ' ')
1831 {
1832 fname = xstrdup (mw->menu.faceName);
1833 fname[i] = '-';
1834 }
1835
1836 mw->menu.xft_font = XftFontOpenName (XtDisplay (mw), screen, fname);
1837 if (!mw->menu.xft_font)
1838 {
1839 fprintf (stderr, "Can't find font '%s'\n", fname);
1840 mw->menu.xft_font = XftFontOpenName (XtDisplay (mw), screen,
1841 DEFAULT_FACENAME);
1842 }
1843 }
1844
1845 if (fname != mw->menu.faceName) free (fname);
1846
1847 return mw->menu.xft_font != 0;
1848}
1849#endif
1850
07bf635f 1851static void
e4c8d29a 1852XlwMenuInitialize (Widget request, Widget w, ArgList args, Cardinal *num_args)
07bf635f
RS
1853{
1854 /* Get the GCs and the widget size */
e4c8d29a 1855 XlwMenuWidget mw = (XlwMenuWidget) w;
07bf635f
RS
1856 Window window = RootWindowOfScreen (DefaultScreenOfDisplay (XtDisplay (mw)));
1857 Display* display = XtDisplay (mw);
5c520e0a 1858
d398028f
PR
1859#if 0
1860 widget_value *tem = (widget_value *) XtMalloc (sizeof (widget_value));
1861
1862 /* _XtCreate is freeing the object that was passed to us,
1863 so make a copy that we will actually keep. */
72af86bd 1864 memcpy (tem, mw->menu.contents, sizeof (widget_value));
d398028f
PR
1865 mw->menu.contents = tem;
1866#endif
1867
99852628 1868 /* mw->menu.cursor = XCreateFontCursor (display, mw->menu.cursor_shape); */
07bf635f 1869 mw->menu.cursor = mw->menu.cursor_shape;
5c520e0a 1870
a504c8fa 1871 mw->menu.gray_pixmap
f1c16db4
GM
1872 = XCreatePixmapFromBitmapData (display, window, gray_bitmap_bits,
1873 gray_bitmap_width, gray_bitmap_height,
a504c8fa 1874 (unsigned long)1, (unsigned long)0, 1);
5c520e0a 1875
99852628
JD
1876#ifdef HAVE_XFT
1877 if (openXftFont (mw))
1878 ;
1879 else
1880#endif
1881
1882 if (!mw->menu.font)
1883 {
1884 if (!xlwmenu_default_font)
1885 xlwmenu_default_font = XLoadQueryFont (display, "fixed");
1886 mw->menu.font = xlwmenu_default_font;
1887 if (!mw->menu.font)
1888 {
1889 fprintf (stderr, "Menu font fixed not found, can't continue.\n");
1890 abort ();
1891 }
1892 }
1893
2015c054
JD
1894#ifdef HAVE_X_I18N
1895 if (mw->menu.fontSet)
1896 mw->menu.font_extents = XExtentsOfFontSet (mw->menu.fontSet);
423a3f59
SM
1897#endif
1898
07bf635f
RS
1899 make_drawing_gcs (mw);
1900 make_shadow_gcs (mw);
5c520e0a 1901
07bf635f 1902 mw->menu.popped_up = False;
5c520e0a 1903
07bf635f
RS
1904 mw->menu.old_depth = 1;
1905 mw->menu.old_stack = (widget_value**)XtMalloc (sizeof (widget_value*));
1906 mw->menu.old_stack_length = 1;
1907 mw->menu.old_stack [0] = mw->menu.contents;
5c520e0a 1908
07bf635f
RS
1909 mw->menu.new_depth = 0;
1910 mw->menu.new_stack = 0;
1911 mw->menu.new_stack_length = 0;
1912 push_new_stack (mw, mw->menu.contents);
5c520e0a 1913
07bf635f
RS
1914 mw->menu.windows = (window_state*)XtMalloc (sizeof (window_state));
1915 mw->menu.windows_length = 1;
1916 mw->menu.windows [0].x = 0;
1917 mw->menu.windows [0].y = 0;
1918 mw->menu.windows [0].width = 0;
1919 mw->menu.windows [0].height = 0;
99852628 1920 mw->menu.windows [0].max_rest_width = 0;
f0bd1598 1921 mw->menu.windows [0].pixmap = None;
99852628
JD
1922#ifdef HAVE_XFT
1923 mw->menu.windows [0].xft_draw = 0;
1924#endif
07bf635f 1925 size_menu (mw, 0);
5c520e0a 1926
07bf635f
RS
1927 mw->core.width = mw->menu.windows [0].width;
1928 mw->core.height = mw->menu.windows [0].height;
1929}
1930
1931static void
c3174d16 1932XlwMenuClassInitialize (void)
07bf635f 1933{
99852628 1934 xlwmenu_default_font = 0;
07bf635f
RS
1935}
1936
1937static void
c3174d16 1938XlwMenuRealize (Widget w, Mask *valueMask, XSetWindowAttributes *attributes)
07bf635f
RS
1939{
1940 XlwMenuWidget mw = (XlwMenuWidget)w;
1941 XSetWindowAttributes xswa;
1942 int mask;
1943
1944 (*xlwMenuWidgetClass->core_class.superclass->core_class.realize)
1945 (w, valueMask, attributes);
1946
1947 xswa.save_under = True;
1948 xswa.cursor = mw->menu.cursor_shape;
1949 mask = CWSaveUnder | CWCursor;
2dfa0fac
KL
1950 /* I sometimes get random BadCursor errors while creating the first
1951 frame on a display. I can not find their reason, but they are
1952 annoying so for now let's ignore any errors here. -- lorentey */
1953#ifdef emacs
16b55533 1954 x_catch_errors (XtDisplay (w));
2dfa0fac 1955#endif
07bf635f 1956 XChangeWindowAttributes (XtDisplay (w), XtWindow (w), mask, &xswa);
2dfa0fac 1957#ifdef emacs
16b55533 1958 x_uncatch_errors ();
2dfa0fac 1959#endif
07bf635f 1960
f0bd1598 1961 mw->menu.windows [0].w = w;
07bf635f
RS
1962 mw->menu.windows [0].window = XtWindow (w);
1963 mw->menu.windows [0].x = w->core.x;
1964 mw->menu.windows [0].y = w->core.y;
1965 mw->menu.windows [0].width = w->core.width;
1966 mw->menu.windows [0].height = w->core.height;
99852628 1967
7aad87e8 1968 set_window_type (mw->menu.windows [0].w, mw);
f0bd1598
J
1969 create_pixmap_for_menu (&mw->menu.windows [0], mw);
1970
99852628
JD
1971#ifdef HAVE_XFT
1972 if (mw->menu.xft_font)
1973 {
1974 XColor colors[3];
99852628
JD
1975 colors[0].pixel = mw->menu.xft_fg.pixel = mw->menu.foreground;
1976 colors[1].pixel = mw->menu.xft_bg.pixel = mw->core.background_pixel;
1977 colors[2].pixel = mw->menu.xft_disabled_fg.pixel
1978 = mw->menu.disabled_foreground;
1979 XQueryColors (XtDisplay (mw), mw->core.colormap, colors, 3);
1980 mw->menu.xft_fg.color.alpha = 0xFFFF;
1981 mw->menu.xft_fg.color.red = colors[0].red;
1982 mw->menu.xft_fg.color.green = colors[0].green;
1983 mw->menu.xft_fg.color.blue = colors[0].blue;
1984 mw->menu.xft_bg.color.alpha = 0xFFFF;
1985 mw->menu.xft_bg.color.red = colors[1].red;
1986 mw->menu.xft_bg.color.green = colors[1].green;
1987 mw->menu.xft_bg.color.blue = colors[1].blue;
1988 mw->menu.xft_disabled_fg.color.alpha = 0xFFFF;
1989 mw->menu.xft_disabled_fg.color.red = colors[2].red;
1990 mw->menu.xft_disabled_fg.color.green = colors[2].green;
1991 mw->menu.xft_disabled_fg.color.blue = colors[2].blue;
1992 }
99852628 1993#endif
07bf635f
RS
1994}
1995
1996/* Only the toplevel menubar/popup is a widget so it's the only one that
1997 receives expose events through Xt. So we repaint all the other panes
1998 when receiving an Expose event. */
5c520e0a 1999static void
c3174d16 2000XlwMenuRedisplay (Widget w, XEvent *ev, Region region)
07bf635f
RS
2001{
2002 XlwMenuWidget mw = (XlwMenuWidget)w;
07bf635f 2003
d398028f
PR
2004 /* If we have a depth beyond 1, it's because a submenu was displayed.
2005 If the submenu has been destroyed, set the depth back to 1. */
2006 if (submenu_destroyed)
2007 {
2008 mw->menu.old_depth = 1;
2009 submenu_destroyed = 0;
2010 }
2011
f0bd1598 2012 display_menu (mw, 0, False, NULL, NULL, NULL);
07bf635f
RS
2013}
2014
f3c9e544
GM
2015
2016/* Part of a hack to make the menu redisplay when a tooltip frame
2017 over a menu item is unmapped. */
2018
2019void
c3174d16 2020xlwmenu_redisplay (Widget w)
f3c9e544
GM
2021{
2022 XlwMenuRedisplay (w, NULL, None);
2023}
2024
5c520e0a 2025static void
c3174d16 2026XlwMenuDestroy (Widget w)
07bf635f
RS
2027{
2028 int i;
2029 XlwMenuWidget mw = (XlwMenuWidget) w;
2030
d398028f 2031 if (pointer_grabbed)
4db7db7d 2032 ungrab_all ((Widget)w, CurrentTime);
d398028f
PR
2033 pointer_grabbed = 0;
2034
2035 submenu_destroyed = 1;
2036
07bf635f
RS
2037 release_drawing_gcs (mw);
2038 release_shadow_gcs (mw);
2039
2040 /* this doesn't come from the resource db but is created explicitly
2041 so we must free it ourselves. */
2042 XFreePixmap (XtDisplay (mw), mw->menu.gray_pixmap);
2043 mw->menu.gray_pixmap = (Pixmap) -1;
2044
d398028f
PR
2045#if 0
2046 /* Do free mw->menu.contents because nowadays we copy it
2047 during initialization. */
2048 XtFree (mw->menu.contents);
2049#endif
2050
07bf635f
RS
2051 /* Don't free mw->menu.contents because that comes from our creator.
2052 The `*_stack' elements are just pointers into `contents' so leave
2053 that alone too. But free the stacks themselves. */
2054 if (mw->menu.old_stack) XtFree ((char *) mw->menu.old_stack);
2055 if (mw->menu.new_stack) XtFree ((char *) mw->menu.new_stack);
2056
2057 /* Remember, you can't free anything that came from the resource
2058 database. This includes:
2059 mw->menu.cursor
2060 mw->menu.top_shadow_pixmap
2061 mw->menu.bottom_shadow_pixmap
2062 mw->menu.font
2063 Also the color cells of top_shadow_color, bottom_shadow_color,
2064 foreground, and button_foreground will never be freed until this
2065 client exits. Nice, eh?
2066 */
2067
99852628
JD
2068#ifdef HAVE_XFT
2069 if (mw->menu.windows [0].xft_draw)
2070 XftDrawDestroy (mw->menu.windows [0].xft_draw);
2071 if (mw->menu.xft_font)
2072 XftFontClose (XtDisplay (mw), mw->menu.xft_font);
2073#endif
2074
f0bd1598
J
2075 if (mw->menu.windows [0].pixmap != None)
2076 XFreePixmap (XtDisplay (mw), mw->menu.windows [0].pixmap);
07bf635f
RS
2077 /* start from 1 because the one in slot 0 is w->core.window */
2078 for (i = 1; i < mw->menu.windows_length; i++)
99852628 2079 {
f0bd1598
J
2080 if (mw->menu.windows [i].pixmap != None)
2081 XFreePixmap (XtDisplay (mw), mw->menu.windows [i].pixmap);
99852628
JD
2082#ifdef HAVE_XFT
2083 if (mw->menu.windows [i].xft_draw)
2084 XftDrawDestroy (mw->menu.windows [i].xft_draw);
2085#endif
2086 }
2087
07bf635f
RS
2088 if (mw->menu.windows)
2089 XtFree ((char *) mw->menu.windows);
2090}
2091
b117f7cc 2092#ifdef HAVE_XFT
99852628
JD
2093static int
2094facename_changed (XlwMenuWidget newmw,
2095 XlwMenuWidget oldmw)
2096{
f0bd1598 2097 /* This will fore a new XftFont even if the same string is set.
99852628
JD
2098 This is good, as rendering parameters may have changed and
2099 we just want to do a redisplay. */
2100 return newmw->menu.faceName != oldmw->menu.faceName;
2101}
b117f7cc 2102#endif
99852628 2103
5c520e0a 2104static Boolean
e4c8d29a
J
2105XlwMenuSetValues (Widget current, Widget request, Widget new,
2106 ArgList args, Cardinal *num_args)
07bf635f
RS
2107{
2108 XlwMenuWidget oldmw = (XlwMenuWidget)current;
2109 XlwMenuWidget newmw = (XlwMenuWidget)new;
2110 Boolean redisplay = False;
2111 int i;
2112
2113 if (newmw->menu.contents
2114 && newmw->menu.contents->contents
2115 && newmw->menu.contents->contents->change >= VISIBLE_CHANGE)
2116 redisplay = True;
ba624d0f
RS
2117 /* Do redisplay if the contents are entirely eliminated. */
2118 if (newmw->menu.contents
2119 && newmw->menu.contents->contents == 0
2120 && newmw->menu.contents->change >= VISIBLE_CHANGE)
2121 redisplay = True;
07bf635f
RS
2122
2123 if (newmw->core.background_pixel != oldmw->core.background_pixel
d398028f 2124 || newmw->menu.foreground != oldmw->menu.foreground
99852628
JD
2125#ifdef HAVE_XFT
2126 || facename_changed (newmw, oldmw)
2127#endif
2015c054
JD
2128#ifdef HAVE_X_I18N
2129 || newmw->menu.fontSet != oldmw->menu.fontSet
2130 || (newmw->menu.fontSet == NULL && newmw->menu.font != oldmw->menu.font)
2131#else
423a3f59
SM
2132 || newmw->menu.font != oldmw->menu.font
2133#endif
2134 )
07bf635f
RS
2135 {
2136 release_drawing_gcs (newmw);
2137 make_drawing_gcs (newmw);
90c7e9f0
MB
2138
2139 release_shadow_gcs (newmw);
2140 /* Cause the shadow colors to be recalculated. */
2141 newmw->menu.top_shadow_color = -1;
2142 newmw->menu.bottom_shadow_color = -1;
2143 make_shadow_gcs (newmw);
2144
07bf635f 2145 redisplay = True;
5c520e0a 2146
43aa2f1b 2147 if (XtIsRealized (current))
da353f23
MB
2148 /* If the menu is currently displayed, change the display. */
2149 for (i = 0; i < oldmw->menu.windows_length; i++)
2150 {
2151 XSetWindowBackground (XtDisplay (oldmw),
2152 oldmw->menu.windows [i].window,
2153 newmw->core.background_pixel);
2154 /* clear windows and generate expose events */
2155 XClearArea (XtDisplay (oldmw), oldmw->menu.windows[i].window,
2156 0, 0, 0, 0, True);
2157 }
07bf635f
RS
2158 }
2159
99852628
JD
2160#ifdef HAVE_XFT
2161 if (facename_changed (newmw, oldmw))
2162 {
2163 int i;
2164 int screen = XScreenNumberOfScreen (newmw->core.screen);
2165 if (newmw->menu.xft_font)
2166 XftFontClose (XtDisplay (newmw), newmw->menu.xft_font);
2167 openXftFont (newmw);
2168 for (i = 0; i < newmw->menu.windows_length; i++)
2169 {
2170 if (newmw->menu.windows [i].xft_draw)
2171 XftDrawDestroy (newmw->menu.windows [i].xft_draw);
2172 newmw->menu.windows [i].xft_draw = 0;
2173 }
2174 if (newmw->menu.xft_font)
2175 for (i = 0; i < newmw->menu.windows_length; i++)
2176 newmw->menu.windows [i].xft_draw
2177 = XftDrawCreate (XtDisplay (newmw),
2178 newmw->menu.windows [i].window,
2179 DefaultVisual (XtDisplay (newmw), screen),
2180 newmw->core.colormap);
2181 }
2182#endif
423a3f59 2183#ifdef HAVE_X_I18N
2015c054 2184 if (newmw->menu.fontSet != oldmw->menu.fontSet && newmw->menu.fontSet != NULL)
423a3f59
SM
2185 {
2186 redisplay = True;
2015c054 2187 newmw->menu.font_extents = XExtentsOfFontSet (newmw->menu.fontSet);
423a3f59
SM
2188 }
2189#endif
2190
07bf635f
RS
2191 return redisplay;
2192}
2193
5c520e0a 2194static void
c3174d16 2195XlwMenuResize (Widget w)
07bf635f
RS
2196{
2197 XlwMenuWidget mw = (XlwMenuWidget)w;
2198
d398028f
PR
2199 if (mw->menu.popped_up)
2200 {
2201 /* Don't allow the popup menu to resize itself. */
2202 mw->core.width = mw->menu.windows [0].width;
2203 mw->core.height = mw->menu.windows [0].height;
f0bd1598
J
2204 mw->core.parent->core.width = mw->core.width;
2205 mw->core.parent->core.height = mw->core.height;
d398028f
PR
2206 }
2207 else
2208 {
2209 mw->menu.windows [0].width = mw->core.width;
2210 mw->menu.windows [0].height = mw->core.height;
f0bd1598 2211 create_pixmap_for_menu (&mw->menu.windows [0], mw);
d398028f 2212 }
07bf635f
RS
2213}
2214
2215\f/* Action procedures */
2216static void
c3174d16 2217handle_single_motion_event (XlwMenuWidget mw, XMotionEvent *ev)
07bf635f
RS
2218{
2219 widget_value* val;
2220 int level;
2221
2222 if (!map_event_to_widget_value (mw, ev, &val, &level))
2223 pop_new_stack_if_no_contents (mw);
2224 else
2225 set_new_state (mw, val, level);
2226 remap_menubar (mw);
5c520e0a 2227
07bf635f
RS
2228 /* Sync with the display. Makes it feel better on X terms. */
2229 XSync (XtDisplay (mw), False);
2230}
2231
2232static void
c3174d16 2233handle_motion_event (XlwMenuWidget mw, XMotionEvent *ev)
07bf635f
RS
2234{
2235 int x = ev->x_root;
2236 int y = ev->y_root;
2237 int state = ev->state;
f0bd1598 2238 XMotionEvent oldev = *ev;
07bf635f
RS
2239
2240 /* allow motion events to be generated again */
2241 if (ev->is_hint
2242 && XQueryPointer (XtDisplay (mw), ev->window,
2243 &ev->root, &ev->subwindow,
2244 &ev->x_root, &ev->y_root,
2245 &ev->x, &ev->y,
2246 &ev->state)
2247 && ev->state == state
2248 && (ev->x_root != x || ev->y_root != y))
2249 handle_single_motion_event (mw, ev);
f0bd1598
J
2250 else
2251 handle_single_motion_event (mw, &oldev);
07bf635f
RS
2252}
2253
5c520e0a 2254static void
c3174d16 2255Start (Widget w, XEvent *ev, String *params, Cardinal *num_params)
07bf635f
RS
2256{
2257 XlwMenuWidget mw = (XlwMenuWidget)w;
2258
d398028f
PR
2259 if (!mw->menu.popped_up)
2260 {
2261 menu_post_event = *ev;
244c93fe
JD
2262 /* If event is set to CurrentTime, get the last known time stamp.
2263 This is for calculating if (popup) menus should stay up after
2264 a fast click. */
2265 if (menu_post_event.xbutton.time == CurrentTime)
2266 menu_post_event.xbutton.time
2267 = XtLastTimestampProcessed (XtDisplay (w));
2268
a57a1605 2269 pop_up_menu (mw, (XButtonPressedEvent*) ev);
d398028f
PR
2270 }
2271 else
4cc76151
PR
2272 {
2273 /* If we push a button while the menu is posted semipermanently,
2274 releasing the button should always pop the menu down. */
2275 next_release_must_exit = 1;
d398028f 2276
4cc76151
PR
2277 /* notes the absolute position of the menubar window */
2278 mw->menu.windows [0].x = ev->xmotion.x_root - ev->xmotion.x;
2279 mw->menu.windows [0].y = ev->xmotion.y_root - ev->xmotion.y;
07bf635f 2280
4cc76151 2281 /* handles the down like a move, slots are compatible */
1f5df1f7 2282 ev->xmotion.is_hint = 0;
4cc76151
PR
2283 handle_motion_event (mw, &ev->xmotion);
2284 }
07bf635f
RS
2285}
2286
5c520e0a 2287static void
c3174d16 2288Drag (Widget w, XEvent *ev, String *params, Cardinal *num_params)
07bf635f
RS
2289{
2290 XlwMenuWidget mw = (XlwMenuWidget)w;
3c9ce1c4
KH
2291 if (mw->menu.popped_up)
2292 handle_motion_event (mw, &ev->xmotion);
07bf635f
RS
2293}
2294
ba624d0f
RS
2295/* Do nothing.
2296 This is how we handle presses and releases of modifier keys. */
2297static void
c3174d16 2298Nothing (Widget w, XEvent *ev, String *params, Cardinal *num_params)
ba624d0f
RS
2299{
2300}
2301
244c93fe 2302static widget_value *
c3174d16 2303find_first_selectable (XlwMenuWidget mw, widget_value *item, int skip_titles)
1b8d91ab
PJ
2304{
2305 widget_value *current = item;
2306 enum menu_separator separator;
2307
244c93fe 2308 while (lw_separator_p (current->name, &separator, 0) || !current->enabled
03a693b4 2309 || (skip_titles && !current->call_data && !current->contents))
1b8d91ab
PJ
2310 if (current->next)
2311 current=current->next;
2312 else
03a693b4 2313 return NULL;
1b8d91ab
PJ
2314
2315 return current;
2316}
2317
244c93fe 2318static widget_value *
c3174d16 2319find_next_selectable (XlwMenuWidget mw, widget_value *item, int skip_titles)
a7e19a26
PJ
2320{
2321 widget_value *current = item;
2322 enum menu_separator separator;
2323
2324 while (current->next && (current=current->next) &&
244c93fe 2325 (lw_separator_p (current->name, &separator, 0) || !current->enabled
03a693b4 2326 || (skip_titles && !current->call_data && !current->contents)))
a7e19a26
PJ
2327 ;
2328
2329 if (current == item)
2330 {
8b71a9ca
PJ
2331 if (mw->menu.old_depth < 2)
2332 return current;
a7e19a26
PJ
2333 current = mw->menu.old_stack [mw->menu.old_depth - 2]->contents;
2334
244c93fe
JD
2335 while (lw_separator_p (current->name, &separator, 0)
2336 || !current->enabled
03a693b4
JD
2337 || (skip_titles && !current->call_data
2338 && !current->contents))
1b8d91ab
PJ
2339 {
2340 if (current->next)
2341 current=current->next;
2342
2343 if (current == item)
2344 break;
2345 }
2346
a7e19a26
PJ
2347 }
2348
2349 return current;
2350}
2351
244c93fe 2352static widget_value *
c3174d16 2353find_prev_selectable (XlwMenuWidget mw, widget_value *item, int skip_titles)
a7e19a26
PJ
2354{
2355 widget_value *current = item;
2356 widget_value *prev = item;
2357
03a693b4 2358 while ((current=find_next_selectable (mw, current, skip_titles))
244c93fe 2359 != item)
1b8d91ab
PJ
2360 {
2361 if (prev == current)
2362 break;
a7e19a26 2363 prev=current;
1b8d91ab 2364 }
a7e19a26
PJ
2365
2366 return prev;
2367}
2368
2369static void
c3174d16 2370Down (Widget w, XEvent *ev, String *params, Cardinal *num_params)
a7e19a26
PJ
2371{
2372 XlwMenuWidget mw = (XlwMenuWidget) w;
2373 widget_value* selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
244c93fe 2374 int popup_menu_p = mw->menu.top_depth == 1;
a7e19a26
PJ
2375
2376 /* Inside top-level menu-bar? */
244c93fe 2377 if (mw->menu.old_depth == mw->menu.top_depth)
a7e19a26 2378 /* When <down> in the menu-bar is pressed, display the corresponding
244c93fe 2379 sub-menu and select the first selectable menu item there.
03a693b4 2380 If this is a popup menu, skip title item of the popup. */
244c93fe
JD
2381 set_new_state (mw,
2382 find_first_selectable (mw,
2383 selected_item->contents,
2384 popup_menu_p),
2385 mw->menu.old_depth);
a7e19a26
PJ
2386 else
2387 /* Highlight next possible (enabled and not separator) menu item. */
244c93fe
JD
2388 set_new_state (mw, find_next_selectable (mw, selected_item, popup_menu_p),
2389 mw->menu.old_depth - 1);
a7e19a26
PJ
2390
2391 remap_menubar (mw);
2392}
2393
2394static void
c3174d16 2395Up (Widget w, XEvent *ev, String *params, Cardinal *num_params)
a7e19a26
PJ
2396{
2397 XlwMenuWidget mw = (XlwMenuWidget) w;
2398 widget_value* selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
244c93fe 2399 int popup_menu_p = mw->menu.top_depth == 1;
a7e19a26
PJ
2400
2401 /* Inside top-level menu-bar? */
244c93fe 2402 if (mw->menu.old_depth == mw->menu.top_depth)
a7e19a26
PJ
2403 {
2404 /* FIXME: this is tricky. <up> in the menu-bar should select the
1b8d91ab
PJ
2405 last selectable item in the list. So we select the first
2406 selectable one and find the previous selectable item. Is there
2407 a better way? */
03a693b4 2408 /* If this is a popup menu, skip title item of the popup. */
244c93fe
JD
2409 set_new_state (mw,
2410 find_first_selectable (mw,
2411 selected_item->contents,
2412 popup_menu_p),
2413 mw->menu.old_depth);
a7e19a26
PJ
2414 remap_menubar (mw);
2415 selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
244c93fe
JD
2416 set_new_state (mw,
2417 find_prev_selectable (mw,
2418 selected_item,
2419 popup_menu_p),
2420 mw->menu.old_depth - 1);
a7e19a26
PJ
2421 }
2422 else
2423 /* Highlight previous (enabled and not separator) menu item. */
244c93fe
JD
2424 set_new_state (mw, find_prev_selectable (mw, selected_item, popup_menu_p),
2425 mw->menu.old_depth - 1);
a7e19a26
PJ
2426
2427 remap_menubar (mw);
2428}
2429
244c93fe 2430void
c3174d16 2431Left (Widget w, XEvent *ev, String *params, Cardinal *num_params)
a7e19a26
PJ
2432{
2433 XlwMenuWidget mw = (XlwMenuWidget) w;
2434 widget_value* selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2435
2436 /* Inside top-level menu-bar? */
244c93fe 2437 if (mw->menu.old_depth == mw->menu.top_depth)
a7e19a26
PJ
2438 /* When <left> in the menu-bar is pressed, display the previous item on
2439 the menu-bar. If the current item is the first one, highlight the
2440 last item in the menubar (probably Help). */
244c93fe
JD
2441 set_new_state (mw, find_prev_selectable (mw, selected_item, 0),
2442 mw->menu.old_depth - 1);
fa74535d
RS
2443 else if (mw->menu.old_depth == 1
2444 && selected_item->contents) /* Is this menu item expandable? */
2445 {
2446 set_new_state (mw, selected_item->contents, mw->menu.old_depth);
2447 remap_menubar (mw);
2448 selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
244c93fe
JD
2449 if (!selected_item->enabled && find_first_selectable (mw,
2450 selected_item,
2451 0))
2452 set_new_state (mw, find_first_selectable (mw, selected_item, 0),
2453 mw->menu.old_depth - 1);
fa74535d
RS
2454 }
2455
a7e19a26
PJ
2456 else
2457 {
2458 pop_new_stack_if_no_contents (mw);
244c93fe
JD
2459 set_new_state (mw, mw->menu.old_stack [mw->menu.old_depth - 2],
2460 mw->menu.old_depth - 2);
a7e19a26
PJ
2461 }
2462
2463 remap_menubar (mw);
2464}
2465
244c93fe 2466void
c3174d16 2467Right (Widget w, XEvent *ev, String *params, Cardinal *num_params)
a7e19a26
PJ
2468{
2469 XlwMenuWidget mw = (XlwMenuWidget) w;
2470 widget_value* selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2471
2472 /* Inside top-level menu-bar? */
244c93fe 2473 if (mw->menu.old_depth == mw->menu.top_depth)
a7e19a26
PJ
2474 /* When <right> in the menu-bar is pressed, display the next item on
2475 the menu-bar. If the current item is the last one, highlight the
2476 first item (probably File). */
244c93fe
JD
2477 set_new_state (mw, find_next_selectable (mw, selected_item, 0),
2478 mw->menu.old_depth - 1);
a7e19a26 2479 else if (selected_item->contents) /* Is this menu item expandable? */
1b8d91ab
PJ
2480 {
2481 set_new_state (mw, selected_item->contents, mw->menu.old_depth);
2482 remap_menubar (mw);
2483 selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
244c93fe
JD
2484 if (!selected_item->enabled && find_first_selectable (mw,
2485 selected_item,
2486 0))
2487 set_new_state (mw, find_first_selectable (mw, selected_item, 0),
2488 mw->menu.old_depth - 1);
1b8d91ab 2489 }
a7e19a26
PJ
2490 else
2491 {
2492 pop_new_stack_if_no_contents (mw);
244c93fe
JD
2493 set_new_state (mw, mw->menu.old_stack [mw->menu.old_depth - 2],
2494 mw->menu.old_depth - 2);
a7e19a26
PJ
2495 }
2496
2497 remap_menubar (mw);
2498}
2499
ba624d0f
RS
2500/* Handle key press and release events while menu is popped up.
2501 Our action is to get rid of the menu. */
2502static void
c3174d16 2503Key (Widget w, XEvent *ev, String *params, Cardinal *num_params)
ba624d0f
RS
2504{
2505 XlwMenuWidget mw = (XlwMenuWidget)w;
2506
2507 /* Pop down everything. */
2508 mw->menu.new_depth = 1;
2509 remap_menubar (mw);
2510
2511 if (mw->menu.popped_up)
2512 {
2513 mw->menu.popped_up = False;
4db7db7d 2514 ungrab_all ((Widget)mw, ev->xmotion.time);
ba624d0f
RS
2515 if (XtIsShell (XtParent ((Widget) mw)))
2516 XtPopdown (XtParent ((Widget) mw));
2517 else
2518 {
2519 XtRemoveGrab ((Widget) mw);
f0bd1598 2520 display_menu (mw, 0, False, NULL, NULL, NULL);
ba624d0f
RS
2521 }
2522 }
2523
2524 /* callback */
2525 XtCallCallbackList ((Widget)mw, mw->menu.select, (XtPointer)0);
2526}
2527
2528static void
c3174d16 2529Select (Widget w, XEvent *ev, String *params, Cardinal *num_params)
07bf635f
RS
2530{
2531 XlwMenuWidget mw = (XlwMenuWidget)w;
2532 widget_value* selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
5c520e0a 2533
d398028f
PR
2534 /* If user releases the button quickly, without selecting anything,
2535 after the initial down-click that brought the menu up,
2536 do nothing. */
2537 if ((selected_item == 0
2538 || ((widget_value *) selected_item)->call_data == 0)
2539 && !next_release_must_exit
2540 && (ev->xbutton.time - menu_post_event.xbutton.time
2541 < XtGetMultiClickTime (XtDisplay (w))))
2542 return;
2543
2544 /* pop down everything. */
07bf635f
RS
2545 mw->menu.new_depth = 1;
2546 remap_menubar (mw);
2547
2548 if (mw->menu.popped_up)
2549 {
2550 mw->menu.popped_up = False;
4db7db7d 2551 ungrab_all ((Widget)mw, ev->xmotion.time);
2289f3f4
PR
2552 if (XtIsShell (XtParent ((Widget) mw)))
2553 XtPopdown (XtParent ((Widget) mw));
4cc76151
PR
2554 else
2555 {
2556 XtRemoveGrab ((Widget) mw);
f0bd1598 2557 display_menu (mw, 0, False, NULL, NULL, NULL);
4cc76151 2558 }
07bf635f
RS
2559 }
2560
2561 /* callback */
2562 XtCallCallbackList ((Widget)mw, mw->menu.select, (XtPointer)selected_item);
07bf635f
RS
2563}
2564
2565
2566\f/* Special code to pop-up a menu */
244c93fe 2567static void
c3174d16 2568pop_up_menu (XlwMenuWidget mw, XButtonPressedEvent *event)
07bf635f
RS
2569{
2570 int x = event->x_root;
2571 int y = event->y_root;
2572 int w;
2573 int h;
2574 int borderwidth = mw->menu.shadow_thickness;
2575 Screen* screen = XtScreen (mw);
8400b9ed 2576 Display *display = XtDisplay (mw);
07bf635f 2577
d398028f
PR
2578 next_release_must_exit = 0;
2579
1ecd5086 2580 mw->menu.inside_entry = NULL;
07bf635f
RS
2581 XtCallCallbackList ((Widget)mw, mw->menu.open, NULL);
2582
2289f3f4 2583 if (XtIsShell (XtParent ((Widget)mw)))
4cc76151 2584 size_menu (mw, 0);
07bf635f
RS
2585
2586 w = mw->menu.windows [0].width;
2587 h = mw->menu.windows [0].height;
2588
2589 x -= borderwidth;
2590 y -= borderwidth;
2591 if (x < borderwidth)
2592 x = borderwidth;
2593 if (x + w + 2 * borderwidth > WidthOfScreen (screen))
2594 x = WidthOfScreen (screen) - w - 2 * borderwidth;
2595 if (y < borderwidth)
2596 y = borderwidth;
2597 if (y + h + 2 * borderwidth> HeightOfScreen (screen))
2598 y = HeightOfScreen (screen) - h - 2 * borderwidth;
2599
2600 mw->menu.popped_up = True;
2289f3f4 2601 if (XtIsShell (XtParent ((Widget)mw)))
4cc76151 2602 {
2289f3f4
PR
2603 XtConfigureWidget (XtParent ((Widget)mw), x, y, w, h,
2604 XtParent ((Widget)mw)->core.border_width);
2605 XtPopup (XtParent ((Widget)mw), XtGrabExclusive);
f0bd1598 2606 display_menu (mw, 0, False, NULL, NULL, NULL);
4cc76151
PR
2607 mw->menu.windows [0].x = x + borderwidth;
2608 mw->menu.windows [0].y = y + borderwidth;
244c93fe 2609 mw->menu.top_depth = 1; /* Popup menus don't have a bar so top is 1 */
4cc76151
PR
2610 }
2611 else
2612 {
2613 XEvent *ev = (XEvent *) event;
2614
87a559bf 2615 XtAddGrab ((Widget) mw, True, True);
4cc76151
PR
2616
2617 /* notes the absolute position of the menubar window */
2618 mw->menu.windows [0].x = ev->xmotion.x_root - ev->xmotion.x;
2619 mw->menu.windows [0].y = ev->xmotion.y_root - ev->xmotion.y;
244c93fe 2620 mw->menu.top_depth = 2;
4cc76151
PR
2621 }
2622
d398028f 2623#ifdef emacs
1f5df1f7 2624 x_catch_errors (display);
d398028f 2625#endif
4db7db7d
JD
2626 if (XtGrabPointer ((Widget)mw, False,
2627 (PointerMotionMask
2628 | PointerMotionHintMask
2629 | ButtonReleaseMask
2630 | ButtonPressMask),
2631 GrabModeAsync, GrabModeAsync, None,
2632 mw->menu.cursor_shape,
2633 event->time) == Success)
2634 {
005e0d57 2635 if (! GRAB_KEYBOARD
4db7db7d
JD
2636 || XtGrabKeyboard ((Widget)mw, False, GrabModeAsync,
2637 GrabModeAsync, event->time) == Success)
2638 {
2639 XtSetKeyboardFocus((Widget)mw, None);
2640 pointer_grabbed = 1;
2641 }
2642 else
2643 XtUngrabPointer ((Widget)mw, event->time);
2644 }
2645
d398028f 2646#ifdef emacs
8400b9ed 2647 if (x_had_errors_p (display))
d398028f
PR
2648 {
2649 pointer_grabbed = 0;
2650 XtUngrabPointer ((Widget)mw, event->time);
2651 }
1f5df1f7 2652 x_uncatch_errors ();
d398028f 2653#endif
07bf635f 2654
1f5df1f7 2655 ((XMotionEvent*)event)->is_hint = 0;
07bf635f
RS
2656 handle_motion_event (mw, (XMotionEvent*)event);
2657}
ab5796a9
MB
2658
2659/* arch-tag: 657f43dd-dfd0-4cc9-910c-52935f01176e
2660 (do not change this comment) */