Merged from the latest developing branch.
[MacVim.git] / src / gui_athena.c
blobdf1367f8343d0dca2a8f2fd6f9a813eac9eaf985
1 /* vi:set ts=8 sts=4 sw=4:
3 * VIM - Vi IMproved by Bram Moolenaar
4 * GUI/Motif support by Robert Webb
5 * Athena port by Bill Foster
7 * Do ":help uganda" in Vim to read copying and usage conditions.
8 * Do ":help credits" in Vim to see a list of people who contributed.
9 * See README.txt for an overview of the Vim source code.
12 #include <X11/StringDefs.h>
13 #include <X11/Intrinsic.h>
14 #ifdef FEAT_GUI_NEXTAW
15 # include <X11/neXtaw/Form.h>
16 # include <X11/neXtaw/SimpleMenu.h>
17 # include <X11/neXtaw/MenuButton.h>
18 # include <X11/neXtaw/SmeBSB.h>
19 # include <X11/neXtaw/SmeLine.h>
20 # include <X11/neXtaw/Box.h>
21 # include <X11/neXtaw/Dialog.h>
22 # include <X11/neXtaw/Text.h>
23 # include <X11/neXtaw/AsciiText.h>
24 # include <X11/neXtaw/Scrollbar.h>
25 #else
26 # include <X11/Xaw/Form.h>
27 # include <X11/Xaw/SimpleMenu.h>
28 # include <X11/Xaw/MenuButton.h>
29 # include <X11/Xaw/SmeBSB.h>
30 # include <X11/Xaw/SmeLine.h>
31 # include <X11/Xaw/Box.h>
32 # include <X11/Xaw/Dialog.h>
33 # include <X11/Xaw/Text.h>
34 # include <X11/Xaw/AsciiText.h>
35 #endif /* FEAT_GUI_NEXTAW */
37 #include "vim.h"
38 #ifndef FEAT_GUI_NEXTAW
39 # include "gui_at_sb.h"
40 #endif
42 extern Widget vimShell;
44 static Widget vimForm = (Widget)0;
45 Widget textArea = (Widget)0;
46 #ifdef FEAT_MENU
47 static Widget menuBar = (Widget)0;
48 static XtIntervalId timer = 0; /* 0 = expired, otherwise active */
50 /* Used to figure out menu ordering */
51 static vimmenu_T *a_cur_menu = NULL;
52 static Cardinal athena_calculate_ins_pos __ARGS((Widget));
54 static Pixmap gui_athena_create_pullright_pixmap __ARGS((Widget));
55 static void gui_athena_menu_timeout __ARGS((XtPointer, XtIntervalId *));
56 static void gui_athena_popup_callback __ARGS((Widget, XtPointer, XtPointer));
57 static void gui_athena_delayed_arm_action __ARGS((Widget, XEvent *, String *,
58 Cardinal *));
59 static void gui_athena_popdown_submenus_action __ARGS((Widget, XEvent *,
60 String *, Cardinal *));
61 static XtActionsRec pullAction[2] = {
62 { "menu-delayedpopup", (XtActionProc)gui_athena_delayed_arm_action},
63 { "menu-popdownsubmenus", (XtActionProc)gui_athena_popdown_submenus_action}
65 #endif
67 #ifdef FEAT_TOOLBAR
68 static void gui_mch_reset_focus __ARGS((void));
69 static Widget toolBar = (Widget)0;
70 #endif
72 static void gui_athena_scroll_cb_jump __ARGS((Widget, XtPointer, XtPointer));
73 static void gui_athena_scroll_cb_scroll __ARGS((Widget, XtPointer, XtPointer));
74 #if defined(FEAT_GUI_DIALOG) || defined(FEAT_MENU)
75 static void gui_athena_menu_colors __ARGS((Widget id));
76 #endif
77 static void gui_athena_scroll_colors __ARGS((Widget id));
79 #ifdef FEAT_MENU
80 static XtTranslations popupTrans, parentTrans, menuTrans, supermenuTrans;
81 static Pixmap pullerBitmap = None;
82 static int puller_width = 0;
83 #endif
86 * Scrollbar callback (XtNjumpProc) for when the scrollbar is dragged with the
87 * left or middle mouse button.
89 /* ARGSUSED */
90 static void
91 gui_athena_scroll_cb_jump(w, client_data, call_data)
92 Widget w;
93 XtPointer client_data, call_data;
95 scrollbar_T *sb, *sb_info;
96 long value;
98 sb = gui_find_scrollbar((long)client_data);
100 if (sb == NULL)
101 return;
102 else if (sb->wp != NULL) /* Left or right scrollbar */
105 * Careful: need to get scrollbar info out of first (left) scrollbar
106 * for window, but keep real scrollbar too because we must pass it to
107 * gui_drag_scrollbar().
109 sb_info = &sb->wp->w_scrollbars[0];
111 else /* Bottom scrollbar */
112 sb_info = sb;
114 value = (long)(*((float *)call_data) * (float)(sb_info->max + 1) + 0.001);
115 if (value > sb_info->max)
116 value = sb_info->max;
118 gui_drag_scrollbar(sb, value, TRUE);
122 * Scrollbar callback (XtNscrollProc) for paging up or down with the left or
123 * right mouse buttons.
125 /* ARGSUSED */
126 static void
127 gui_athena_scroll_cb_scroll(w, client_data, call_data)
128 Widget w;
129 XtPointer client_data, call_data;
131 scrollbar_T *sb, *sb_info;
132 long value;
133 int data = (int)(long)call_data;
134 int page;
136 sb = gui_find_scrollbar((long)client_data);
138 if (sb == NULL)
139 return;
140 if (sb->wp != NULL) /* Left or right scrollbar */
143 * Careful: need to get scrollbar info out of first (left) scrollbar
144 * for window, but keep real scrollbar too because we must pass it to
145 * gui_drag_scrollbar().
147 sb_info = &sb->wp->w_scrollbars[0];
149 if (sb_info->size > 5)
150 page = sb_info->size - 2; /* use two lines of context */
151 else
152 page = sb_info->size;
153 #ifdef FEAT_GUI_NEXTAW
154 if (data < 0)
156 data = (data - gui.char_height + 1) / gui.char_height;
157 if (data > -sb_info->size)
158 data = -1;
159 else
160 data = -page;
162 else if (data > 0)
164 data = (data + gui.char_height - 1) / gui.char_height;
165 if (data < sb_info->size)
166 data = 1;
167 else
168 data = page;
170 #else
171 switch (data)
173 case ONE_LINE_DATA: data = 1; break;
174 case -ONE_LINE_DATA: data = -1; break;
175 case ONE_PAGE_DATA: data = page; break;
176 case -ONE_PAGE_DATA: data = -page; break;
177 case END_PAGE_DATA: data = sb_info->max; break;
178 case -END_PAGE_DATA: data = -sb_info->max; break;
179 default: data = 0; break;
181 #endif
183 else /* Bottom scrollbar */
185 sb_info = sb;
186 #ifdef FEAT_GUI_NEXTAW
187 if (data < 0)
189 data = (data - gui.char_width + 1) / gui.char_width;
190 if (data > -sb->size)
191 data = -1;
193 else if (data > 0)
195 data = (data + gui.char_width - 1) / gui.char_width;
196 if (data < sb->size)
197 data = 1;
199 #endif
200 if (data < -1) /* page-width left */
202 if (sb->size > 8)
203 data = -(sb->size - 5);
204 else
205 data = -sb->size;
207 else if (data > 1) /* page-width right */
209 if (sb->size > 8)
210 data = (sb->size - 5);
211 else
212 data = sb->size;
216 value = sb_info->value + data;
217 if (value > sb_info->max)
218 value = sb_info->max;
219 else if (value < 0)
220 value = 0;
222 /* Update the bottom scrollbar an extra time (why is this needed?? */
223 if (sb->wp == NULL) /* Bottom scrollbar */
224 gui_mch_set_scrollbar_thumb(sb, value, sb->size, sb->max);
226 gui_drag_scrollbar(sb, value, FALSE);
230 * Create all the Athena widgets necessary.
232 void
233 gui_x11_create_widgets()
236 * We don't have any borders handled internally by the textArea to worry
237 * about so only skip over the configured border width.
239 gui.border_offset = gui.border_width;
241 #if 0 /* not needed? */
242 XtInitializeWidgetClass(formWidgetClass);
243 XtInitializeWidgetClass(boxWidgetClass);
244 XtInitializeWidgetClass(coreWidgetClass);
245 #ifdef FEAT_MENU
246 XtInitializeWidgetClass(menuButtonWidgetClass);
247 #endif
248 XtInitializeWidgetClass(simpleMenuWidgetClass);
249 #ifdef FEAT_GUI_NEXTAW
250 XtInitializeWidgetClass(scrollbarWidgetClass);
251 #else
252 XtInitializeWidgetClass(vim_scrollbarWidgetClass);
253 #endif
254 #endif
256 /* The form containing all the other widgets */
257 vimForm = XtVaCreateManagedWidget("vimForm",
258 formWidgetClass, vimShell,
259 XtNborderWidth, 0,
260 NULL);
261 gui_athena_scroll_colors(vimForm);
263 #ifdef FEAT_MENU
264 /* The top menu bar */
265 menuBar = XtVaCreateManagedWidget("menuBar",
266 boxWidgetClass, vimForm,
267 XtNresizable, True,
268 XtNtop, XtChainTop,
269 XtNbottom, XtChainTop,
270 XtNleft, XtChainLeft,
271 XtNright, XtChainRight,
272 XtNinsertPosition, athena_calculate_ins_pos,
273 NULL);
274 gui_athena_menu_colors(menuBar);
275 if (gui.menu_fg_pixel != INVALCOLOR)
276 XtVaSetValues(menuBar, XtNborderColor, gui.menu_fg_pixel, NULL);
277 #endif
279 #ifdef FEAT_TOOLBAR
280 /* Don't create it Managed, it will be managed when creating the first
281 * item. Otherwise an empty toolbar shows up. */
282 toolBar = XtVaCreateWidget("toolBar",
283 boxWidgetClass, vimForm,
284 XtNresizable, True,
285 XtNtop, XtChainTop,
286 XtNbottom, XtChainTop,
287 XtNleft, XtChainLeft,
288 XtNright, XtChainRight,
289 XtNorientation, XtorientHorizontal,
290 XtNhSpace, 1,
291 XtNvSpace, 3,
292 XtNinsertPosition, athena_calculate_ins_pos,
293 NULL);
294 gui_athena_menu_colors(toolBar);
295 #endif
297 /* The text area. */
298 textArea = XtVaCreateManagedWidget("textArea",
299 coreWidgetClass, vimForm,
300 XtNresizable, True,
301 XtNtop, XtChainTop,
302 XtNbottom, XtChainTop,
303 XtNleft, XtChainLeft,
304 XtNright, XtChainLeft,
305 XtNbackground, gui.back_pixel,
306 XtNborderWidth, 0,
307 NULL);
310 * Install the callbacks.
312 gui_x11_callbacks(textArea, vimForm);
314 #ifdef FEAT_MENU
315 popupTrans = XtParseTranslationTable(
316 "<EnterWindow>: menu-popdownsubmenus() highlight() menu-delayedpopup()\n"
317 "<LeaveWindow>: unhighlight()\n"
318 "<BtnUp>: menu-popdownsubmenus() XtMenuPopdown() notify() unhighlight()\n"
319 "<Motion>: highlight() menu-delayedpopup()");
320 parentTrans = XtParseTranslationTable("<LeaveWindow>: unhighlight()");
321 menuTrans = XtParseTranslationTable(
322 "<EnterWindow>: menu-popdownsubmenus() highlight() menu-delayedpopup()\n"
323 "<LeaveWindow>: menu-popdownsubmenus() XtMenuPopdown() unhighlight()\n"
324 "<BtnUp>: notify() unhighlight()\n"
325 "<BtnMotion>: highlight() menu-delayedpopup()");
326 supermenuTrans = XtParseTranslationTable(
327 "<EnterWindow>: menu-popdownsubmenus() highlight() menu-delayedpopup()\n"
328 "<LeaveWindow>: unhighlight()\n"
329 "<BtnUp>: menu-popdownsubmenus() XtMenuPopdown() notify() unhighlight()\n"
330 "<BtnMotion>: highlight() menu-delayedpopup()");
332 XtAppAddActions(XtWidgetToApplicationContext(vimForm), pullAction,
333 XtNumber(pullAction));
334 #endif
336 /* Pretend we don't have input focus, we will get an event if we do. */
337 gui.in_focus = FALSE;
340 #ifdef FEAT_MENU
342 * Calculates the Pixmap based on the size of the current menu font.
344 static Pixmap
345 gui_athena_create_pullright_pixmap(w)
346 Widget w;
348 Pixmap retval;
349 #ifdef FONTSET_ALWAYS
350 XFontSet font = None;
351 #else
352 XFontStruct *font = NULL;
353 #endif
355 #ifdef FONTSET_ALWAYS
356 if (gui.menu_fontset == NOFONTSET)
357 #else
358 if (gui.menu_font == NOFONT)
359 #endif
361 XrmValue from, to;
362 WidgetList children;
363 Cardinal num_children;
365 #ifdef FONTSET_ALWAYS
366 from.size = strlen(from.addr = XtDefaultFontSet);
367 to.addr = (XtPointer)&font;
368 to.size = sizeof(XFontSet);
369 #else
370 from.size = strlen(from.addr = XtDefaultFont);
371 to.addr = (XtPointer)&font;
372 to.size = sizeof(XFontStruct *);
373 #endif
374 /* Assumption: The menuBar children will use the same font as the
375 * pulldown menu items AND they will all be of type
376 * XtNfont.
378 XtVaGetValues(menuBar, XtNchildren, &children,
379 XtNnumChildren, &num_children,
380 NULL);
381 if (XtConvertAndStore(w ? w :
382 (num_children > 0) ? children[0] : menuBar,
383 XtRString, &from,
384 #ifdef FONTSET_ALWAYS
385 XtRFontSet, &to
386 #else
387 XtRFontStruct, &to
388 #endif
389 ) == False)
390 return None;
391 /* "font" should now contain data */
393 else
394 #ifdef FONTSET_ALWAYS
395 font = (XFontSet)gui.menu_fontset;
396 #else
397 font = (XFontStruct *)gui.menu_font;
398 #endif
401 int width, height;
402 GC draw_gc, undraw_gc;
403 XGCValues gc_values;
404 XPoint points[3];
406 #ifdef FONTSET_ALWAYS
407 height = fontset_height2(font);
408 #else
409 height = font->max_bounds.ascent + font->max_bounds.descent;
410 #endif
411 width = height - 2;
412 puller_width = width + 4;
413 retval = XCreatePixmap(gui.dpy,DefaultRootWindow(gui.dpy),width,
414 height, 1);
415 gc_values.foreground = 1;
416 gc_values.background = 0;
417 draw_gc = XCreateGC(gui.dpy, retval,
418 GCForeground | GCBackground,
419 &gc_values);
420 gc_values.foreground = 0;
421 gc_values.background = 1;
422 undraw_gc = XCreateGC(gui.dpy, retval,
423 GCForeground | GCBackground,
424 &gc_values);
425 points[0].x = 0;
426 points[0].y = 0;
427 points[1].x = width - 1;
428 points[1].y = (height - 1) / 2;
429 points[2].x = 0;
430 points[2].y = height - 1;
431 XFillRectangle(gui.dpy, retval, undraw_gc, 0, 0, height, height);
432 XFillPolygon(gui.dpy, retval, draw_gc, points, XtNumber(points),
433 Convex, CoordModeOrigin);
434 XFreeGC(gui.dpy, draw_gc);
435 XFreeGC(gui.dpy, undraw_gc);
437 return retval;
439 #endif
442 * Called when the GUI is not going to start after all.
444 void
445 gui_x11_destroy_widgets()
447 textArea = NULL;
448 #ifdef FEAT_MENU
449 menuBar = NULL;
450 #endif
451 #ifdef FEAT_TOOLBAR
452 toolBar = NULL;
453 #endif
456 #if defined(FEAT_TOOLBAR) || defined(PROTO)
457 # include "gui_x11_pm.h"
458 # ifdef HAVE_X11_XPM_H
459 # include <X11/xpm.h>
460 # endif
462 static void createXpmImages __ARGS((char_u *path, char **xpm, Pixmap *sen));
463 static void get_toolbar_pixmap __ARGS((vimmenu_T *menu, Pixmap *sen));
466 * Allocated a pixmap for toolbar menu "menu".
467 * Return in "sen".
469 static void
470 get_toolbar_pixmap(menu, sen)
471 vimmenu_T *menu;
472 Pixmap *sen;
474 char_u buf[MAXPATHL]; /* buffer storing expanded pathname */
475 char **xpm = NULL; /* xpm array */
477 buf[0] = NUL; /* start with NULL path */
479 if (menu->iconfile != NULL)
481 /* Use the "icon=" argument. */
482 gui_find_iconfile(menu->iconfile, buf, "xpm");
483 createXpmImages(buf, NULL, sen);
485 /* If it failed, try using the menu name. */
486 if (*sen == (Pixmap)0 && gui_find_bitmap(menu->name, buf, "xpm") == OK)
487 createXpmImages(buf, NULL, sen);
488 if (*sen != (Pixmap)0)
489 return;
492 if (menu->icon_builtin || gui_find_bitmap(menu->name, buf, "xpm") == FAIL)
494 if (menu->iconidx >= 0 && menu->iconidx
495 < (sizeof(built_in_pixmaps) / sizeof(built_in_pixmaps[0])))
496 xpm = built_in_pixmaps[menu->iconidx];
497 else
498 xpm = tb_blank_xpm;
501 if (xpm != NULL || buf[0] != NUL)
502 createXpmImages(buf, xpm, sen);
506 * Read an Xpm file, doing color substitutions for the foreground and
507 * background colors. If there is an error reading a color xpm file,
508 * drop back and read the monochrome file. If successful, create the
509 * insensitive Pixmap too.
511 static void
512 createXpmImages(path, xpm, sen)
513 char_u *path;
514 char **xpm;
515 Pixmap *sen;
517 Window rootWindow;
518 XpmAttributes attrs;
519 XpmColorSymbol color[5] =
521 {"none", "none", 0},
522 {"iconColor1", NULL, 0},
523 {"bottomShadowColor", NULL, 0},
524 {"topShadowColor", NULL, 0},
525 {"selectColor", NULL, 0}
527 int screenNum;
528 int status;
529 Pixmap mask;
530 Pixmap map;
532 gui_mch_get_toolbar_colors(
533 &color[BACKGROUND].pixel,
534 &color[FOREGROUND].pixel,
535 &color[BOTTOM_SHADOW].pixel,
536 &color[TOP_SHADOW].pixel,
537 &color[HIGHLIGHT].pixel);
539 /* Setup the color subsititution table */
540 attrs.valuemask = XpmColorSymbols;
541 attrs.colorsymbols = color;
542 attrs.numsymbols = 5;
544 screenNum = DefaultScreen(gui.dpy);
545 rootWindow = RootWindow(gui.dpy, screenNum);
547 /* Create the "sensitive" pixmap */
548 if (xpm != NULL)
549 status = XpmCreatePixmapFromData(gui.dpy, rootWindow, xpm,
550 &map, &mask, &attrs);
551 else
552 status = XpmReadFileToPixmap(gui.dpy, rootWindow, (char *)path,
553 &map, &mask, &attrs);
554 if (status == XpmSuccess && map != 0)
556 XGCValues gcvalues;
557 GC back_gc;
558 GC mask_gc;
560 /* Need to create new Pixmaps with the mask applied. */
561 gcvalues.foreground = color[BACKGROUND].pixel;
562 back_gc = XCreateGC(gui.dpy, map, GCForeground, &gcvalues);
563 mask_gc = XCreateGC(gui.dpy, map, GCForeground, &gcvalues);
564 XSetClipMask(gui.dpy, mask_gc, mask);
566 /* Create the "sensitive" pixmap. */
567 *sen = XCreatePixmap(gui.dpy, rootWindow,
568 attrs.width, attrs.height,
569 DefaultDepth(gui.dpy, screenNum));
570 XFillRectangle(gui.dpy, *sen, back_gc, 0, 0,
571 attrs.width, attrs.height);
572 XCopyArea(gui.dpy, map, *sen, mask_gc, 0, 0,
573 attrs.width, attrs.height, 0, 0);
575 XFreeGC(gui.dpy, back_gc);
576 XFreeGC(gui.dpy, mask_gc);
577 XFreePixmap(gui.dpy, map);
579 else
580 *sen = 0;
582 XpmFreeAttributes(&attrs);
585 void
586 gui_mch_set_toolbar_pos(x, y, w, h)
587 int x;
588 int y;
589 int w;
590 int h;
592 Dimension border;
593 int height;
595 if (!XtIsManaged(toolBar)) /* nothing to do */
596 return;
597 XtUnmanageChild(toolBar);
598 XtVaGetValues(toolBar,
599 XtNborderWidth, &border,
600 NULL);
601 height = h - 2 * border;
602 if (height < 0)
603 height = 1;
604 XtVaSetValues(toolBar,
605 XtNhorizDistance, x,
606 XtNvertDistance, y,
607 XtNwidth, w - 2 * border,
608 XtNheight, height,
609 NULL);
610 XtManageChild(toolBar);
612 #endif
614 void
615 gui_mch_set_text_area_pos(x, y, w, h)
616 int x;
617 int y;
618 int w;
619 int h;
621 XtUnmanageChild(textArea);
622 XtVaSetValues(textArea,
623 XtNhorizDistance, x,
624 XtNvertDistance, y,
625 XtNwidth, w,
626 XtNheight, h,
627 NULL);
628 XtManageChild(textArea);
629 #ifdef FEAT_TOOLBAR
630 /* Give keyboard focus to the textArea instead of the toolbar. */
631 gui_mch_reset_focus();
632 #endif
635 #ifdef FEAT_TOOLBAR
637 * A toolbar button has been pushed; now reset the input focus
638 * such that the user can type page up/down etc. and have the
639 * input go to the editor window, not the button
641 static void
642 gui_mch_reset_focus()
644 XtSetKeyboardFocus(vimForm, textArea);
646 #endif
649 void
650 gui_x11_set_back_color()
652 if (textArea != NULL)
653 XtVaSetValues(textArea,
654 XtNbackground, gui.back_pixel,
655 NULL);
658 #if defined(FEAT_MENU) || defined(PROTO)
660 * Menu stuff.
663 static char_u *make_pull_name __ARGS((char_u * name));
664 static Widget get_popup_entry __ARGS((Widget w));
665 static Widget submenu_widget __ARGS((Widget));
666 static Boolean has_submenu __ARGS((Widget));
667 static void gui_mch_submenu_change __ARGS((vimmenu_T *mp, int colors));
668 static void gui_athena_menu_font __ARGS((Widget id));
669 static Boolean gui_athena_menu_has_submenus __ARGS((Widget, Widget));
671 void
672 gui_mch_enable_menu(flag)
673 int flag;
675 if (flag)
677 XtManageChild(menuBar);
678 # ifdef FEAT_TOOLBAR
679 if (XtIsManaged(toolBar))
681 XtVaSetValues(toolBar,
682 XtNvertDistance, gui.menu_height,
683 NULL);
684 XtVaSetValues(textArea,
685 XtNvertDistance, gui.menu_height + gui.toolbar_height,
686 NULL);
688 # endif
690 else
692 XtUnmanageChild(menuBar);
693 # ifdef FEAT_TOOLBAR
694 if (XtIsManaged(toolBar))
696 XtVaSetValues(toolBar,
697 XtNvertDistance, 0,
698 NULL);
700 # endif
704 void
705 gui_mch_set_menu_pos(x, y, w, h)
706 int x;
707 int y;
708 int w;
709 int h;
711 Dimension border;
712 int height;
714 XtUnmanageChild(menuBar);
715 XtVaGetValues(menuBar, XtNborderWidth, &border, NULL);
716 /* avoid trouble when there are no menu items, and h is 1 */
717 height = h - 2 * border;
718 if (height < 0)
719 height = 1;
720 XtVaSetValues(menuBar,
721 XtNhorizDistance, x,
722 XtNvertDistance, y,
723 XtNwidth, w - 2 * border,
724 XtNheight, height,
725 NULL);
726 XtManageChild(menuBar);
730 * Used to calculate the insertion position of a widget with respect to its
731 * neighbors.
733 * Valid range of return values is: 0 (beginning of children) to
734 * numChildren (end of children).
736 static Cardinal
737 athena_calculate_ins_pos(widget)
738 Widget widget;
740 /* Assume that if the parent of the vimmenu_T is NULL, then we can get
741 * to this menu by traversing "next", starting at "root_menu".
743 * This holds true for popup menus, toolbar, and toplevel menu items.
746 /* Popup menus: "id" is NULL. Only submenu_id is valid */
748 /* Menus that are not toplevel: "parent" will be non-NULL, "id" &
749 * "submenu_id" will be non-NULL.
752 /* Toplevel menus: "parent" is NULL, id is the widget of the menu item */
754 WidgetList children;
755 Cardinal num_children = 0;
756 int retval;
757 Arg args[2];
758 int n = 0;
759 int i;
761 XtSetArg(args[n], XtNchildren, &children); n++;
762 XtSetArg(args[n], XtNnumChildren, &num_children); n++;
763 XtGetValues(XtParent(widget), args, n);
765 retval = num_children;
766 for (i = 0; i < num_children; ++i)
768 Widget current = children[i];
769 vimmenu_T *menu = NULL;
771 for (menu = (a_cur_menu->parent == NULL)
772 ? root_menu : a_cur_menu->parent->children;
773 menu != NULL;
774 menu = menu->next)
775 if (current == menu->id
776 && a_cur_menu->priority < menu->priority
777 && i < retval)
778 retval = i;
780 return retval;
783 /* ARGSUSED */
784 void
785 gui_mch_add_menu(menu, idx)
786 vimmenu_T *menu;
787 int idx;
789 char_u *pullright_name;
790 Dimension height, space, border;
791 vimmenu_T *parent = menu->parent;
793 a_cur_menu = menu;
794 if (parent == NULL)
796 if (menu_is_popup(menu->dname))
798 menu->submenu_id = XtVaCreatePopupShell((char *)menu->dname,
799 simpleMenuWidgetClass, vimShell,
800 XtNinsertPosition, athena_calculate_ins_pos,
801 XtNtranslations, popupTrans,
802 NULL);
803 gui_athena_menu_colors(menu->submenu_id);
805 else if (menu_is_menubar(menu->dname))
807 menu->id = XtVaCreateManagedWidget((char *)menu->dname,
808 menuButtonWidgetClass, menuBar,
809 XtNmenuName, menu->dname,
810 #ifdef FONTSET_ALWAYS
811 XtNinternational, True,
812 #endif
813 NULL);
814 if (menu->id == (Widget)0)
815 return;
816 gui_athena_menu_colors(menu->id);
817 gui_athena_menu_font(menu->id);
819 menu->submenu_id = XtVaCreatePopupShell((char *)menu->dname,
820 simpleMenuWidgetClass, menu->id,
821 XtNinsertPosition, athena_calculate_ins_pos,
822 XtNtranslations, supermenuTrans,
823 NULL);
824 gui_athena_menu_colors(menu->submenu_id);
825 gui_athena_menu_font(menu->submenu_id);
827 /* Don't update the menu height when it was set at a fixed value */
828 if (!gui.menu_height_fixed)
831 * When we add a top-level item to the menu bar, we can figure
832 * out how high the menu bar should be.
834 XtVaGetValues(menuBar,
835 XtNvSpace, &space,
836 XtNborderWidth, &border,
837 NULL);
838 XtVaGetValues(menu->id,
839 XtNheight, &height,
840 NULL);
841 gui.menu_height = height + 2 * (space + border);
845 else if (parent->submenu_id != (Widget)0)
847 menu->id = XtVaCreateManagedWidget((char *)menu->dname,
848 smeBSBObjectClass, parent->submenu_id,
849 XtNlabel, menu->dname,
850 #ifdef FONTSET_ALWAYS
851 XtNinternational, True,
852 #endif
853 NULL);
854 if (menu->id == (Widget)0)
855 return;
856 if (pullerBitmap == None)
857 pullerBitmap = gui_athena_create_pullright_pixmap(menu->id);
859 XtVaSetValues(menu->id, XtNrightBitmap, pullerBitmap,
860 NULL);
861 /* If there are other menu items that are not pulldown menus,
862 * we need to adjust the right margins of those, too.
865 WidgetList children;
866 Cardinal num_children;
867 int i;
869 XtVaGetValues(parent->submenu_id, XtNchildren, &children,
870 XtNnumChildren, &num_children,
871 NULL);
872 for (i = 0; i < num_children; ++i)
874 XtVaSetValues(children[i],
875 XtNrightMargin, puller_width,
876 NULL);
879 gui_athena_menu_colors(menu->id);
880 gui_athena_menu_font(menu->id);
882 pullright_name = make_pull_name(menu->dname);
883 menu->submenu_id = XtVaCreatePopupShell((char *)pullright_name,
884 simpleMenuWidgetClass, parent->submenu_id,
885 XtNtranslations, menuTrans,
886 NULL);
887 gui_athena_menu_colors(menu->submenu_id);
888 gui_athena_menu_font(menu->submenu_id);
889 vim_free(pullright_name);
890 XtAddCallback(menu->submenu_id, XtNpopupCallback,
891 gui_athena_popup_callback, (XtPointer)menu);
893 if (parent->parent != NULL)
894 XtOverrideTranslations(parent->submenu_id, parentTrans);
896 a_cur_menu = NULL;
899 /* Used to determine whether a SimpleMenu has pulldown entries.
901 * "id" is the parent of the menu items.
902 * Ignore widget "ignore" in the pane.
904 static Boolean
905 gui_athena_menu_has_submenus(id, ignore)
906 Widget id;
907 Widget ignore;
909 WidgetList children;
910 Cardinal num_children;
911 int i;
913 XtVaGetValues(id, XtNchildren, &children,
914 XtNnumChildren, &num_children,
915 NULL);
916 for (i = 0; i < num_children; ++i)
918 if (children[i] == ignore)
919 continue;
920 if (has_submenu(children[i]))
921 return True;
923 return False;
926 static void
927 gui_athena_menu_font(id)
928 Widget id;
930 #ifdef FONTSET_ALWAYS
931 if (gui.menu_fontset != NOFONTSET)
933 if (XtIsManaged(id))
935 XtUnmanageChild(id);
936 XtVaSetValues(id, XtNfontSet, gui.menu_fontset, NULL);
937 /* We should force the widget to recalculate it's
938 * geometry now. */
939 XtManageChild(id);
941 else
942 XtVaSetValues(id, XtNfontSet, gui.menu_fontset, NULL);
943 if (has_submenu(id))
944 XtVaSetValues(id, XtNrightBitmap, pullerBitmap, NULL);
946 #else
947 int managed = FALSE;
949 if (gui.menu_font != NOFONT)
951 if (XtIsManaged(id))
953 XtUnmanageChild(id);
954 managed = TRUE;
957 # ifdef FEAT_XFONTSET
958 if (gui.fontset != NOFONTSET)
959 XtVaSetValues(id, XtNfontSet, gui.menu_font, NULL);
960 else
961 # endif
962 XtVaSetValues(id, XtNfont, gui.menu_font, NULL);
963 if (has_submenu(id))
964 XtVaSetValues(id, XtNrightBitmap, pullerBitmap, NULL);
966 /* Force the widget to recalculate it's geometry now. */
967 if (managed)
968 XtManageChild(id);
970 #endif
974 void
975 gui_mch_new_menu_font()
977 Pixmap oldpuller = None;
979 if (menuBar == (Widget)0)
980 return;
982 if (pullerBitmap != None)
984 oldpuller = pullerBitmap;
985 pullerBitmap = gui_athena_create_pullright_pixmap(NULL);
987 gui_mch_submenu_change(root_menu, FALSE);
990 /* Iterate through the menubar menu items and get the height of
991 * each one. The menu bar height is set to the maximum of all
992 * the heights.
994 vimmenu_T *mp;
995 int max_height = 9999;
997 for (mp = root_menu; mp != NULL; mp = mp->next)
999 if (menu_is_menubar(mp->dname))
1001 Dimension height;
1003 XtVaGetValues(mp->id,
1004 XtNheight, &height,
1005 NULL);
1006 if (height < max_height)
1007 max_height = height;
1010 if (max_height != 9999)
1012 /* Don't update the menu height when it was set at a fixed value */
1013 if (!gui.menu_height_fixed)
1015 Dimension space, border;
1017 XtVaGetValues(menuBar,
1018 XtNvSpace, &space,
1019 XtNborderWidth, &border,
1020 NULL);
1021 gui.menu_height = max_height + 2 * (space + border);
1025 /* Now, to simulate the window being resized. Only, this
1026 * will resize the window to it's current state.
1028 * There has to be a better way, but I do not see one at this time.
1029 * (David Harrison)
1032 Position w, h;
1034 XtVaGetValues(vimShell,
1035 XtNwidth, &w,
1036 XtNheight, &h,
1037 NULL);
1038 gui_resize_shell(w, h
1039 #ifdef FEAT_XIM
1040 - xim_get_status_area_height()
1041 #endif
1044 gui_set_shellsize(FALSE, TRUE, RESIZE_VERT);
1045 ui_new_shellsize();
1046 if (oldpuller != None)
1047 XFreePixmap(gui.dpy, oldpuller);
1050 #if defined(FEAT_BEVAL) || defined(PROTO)
1051 void
1052 gui_mch_new_tooltip_font()
1054 # ifdef FEAT_TOOLBAR
1055 vimmenu_T *menu;
1057 if (toolBar == (Widget)0)
1058 return;
1060 menu = gui_find_menu((char_u *)"ToolBar");
1061 if (menu != NULL)
1062 gui_mch_submenu_change(menu, FALSE);
1063 # endif
1066 void
1067 gui_mch_new_tooltip_colors()
1069 # ifdef FEAT_TOOLBAR
1070 vimmenu_T *menu;
1072 if (toolBar == (Widget)0)
1073 return;
1075 menu = gui_find_menu((char_u *)"ToolBar");
1076 if (menu != NULL)
1077 gui_mch_submenu_change(menu, TRUE);
1078 # endif
1080 #endif
1082 static void
1083 gui_mch_submenu_change(menu, colors)
1084 vimmenu_T *menu;
1085 int colors; /* TRUE for colors, FALSE for font */
1087 vimmenu_T *mp;
1089 for (mp = menu; mp != NULL; mp = mp->next)
1091 if (mp->id != (Widget)0)
1093 if (colors)
1095 gui_athena_menu_colors(mp->id);
1096 #ifdef FEAT_TOOLBAR
1097 /* For a toolbar item: Free the pixmap and allocate a new one,
1098 * so that the background color is right. */
1099 if (mp->image != (Pixmap)0)
1101 XFreePixmap(gui.dpy, mp->image);
1102 get_toolbar_pixmap(mp, &mp->image);
1103 if (mp->image != (Pixmap)0)
1104 XtVaSetValues(mp->id, XtNbitmap, mp->image, NULL);
1107 # ifdef FEAT_BEVAL
1108 /* If we have a tooltip, then we need to change it's colors */
1109 if (mp->tip != NULL)
1111 Arg args[2];
1113 args[0].name = XtNbackground;
1114 args[0].value = gui.tooltip_bg_pixel;
1115 args[1].name = XtNforeground;
1116 args[1].value = gui.tooltip_fg_pixel;
1117 XtSetValues(mp->tip->balloonLabel, &args[0], XtNumber(args));
1119 # endif
1120 #endif
1122 else
1124 gui_athena_menu_font(mp->id);
1125 #ifdef FEAT_BEVAL
1126 /* If we have a tooltip, then we need to change it's font */
1127 /* Assume XtNinternational == True (in createBalloonEvalWindow)
1129 if (mp->tip != NULL)
1131 Arg args[1];
1133 args[0].name = XtNfontSet;
1134 args[0].value = (XtArgVal)gui.tooltip_fontset;
1135 XtSetValues(mp->tip->balloonLabel, &args[0], XtNumber(args));
1137 #endif
1141 if (mp->children != NULL)
1143 /* Set the colors/font for the tear off widget */
1144 if (mp->submenu_id != (Widget)0)
1146 if (colors)
1147 gui_athena_menu_colors(mp->submenu_id);
1148 else
1149 gui_athena_menu_font(mp->submenu_id);
1151 /* Set the colors for the children */
1152 gui_mch_submenu_change(mp->children, colors);
1158 * Make a submenu name into a pullright name.
1159 * Replace '.' by '_', can't include '.' in the submenu name.
1161 static char_u *
1162 make_pull_name(name)
1163 char_u * name;
1165 char_u *pname;
1166 char_u *p;
1168 pname = vim_strnsave(name, STRLEN(name) + strlen("-pullright"));
1169 if (pname != NULL)
1171 strcat((char *)pname, "-pullright");
1172 while ((p = vim_strchr(pname, '.')) != NULL)
1173 *p = '_';
1175 return pname;
1178 /* ARGSUSED */
1179 void
1180 gui_mch_add_menu_item(menu, idx)
1181 vimmenu_T *menu;
1182 int idx;
1184 vimmenu_T *parent = menu->parent;
1186 a_cur_menu = menu;
1187 # ifdef FEAT_TOOLBAR
1188 if (menu_is_toolbar(parent->name))
1190 WidgetClass type;
1191 int n;
1192 Arg args[21];
1194 n = 0;
1195 if (menu_is_separator(menu->name))
1197 XtSetArg(args[n], XtNlabel, ""); n++;
1198 XtSetArg(args[n], XtNborderWidth, 0); n++;
1200 else
1202 get_toolbar_pixmap(menu, &menu->image);
1203 XtSetArg(args[n], XtNlabel, menu->dname); n++;
1204 XtSetArg(args[n], XtNinternalHeight, 1); n++;
1205 XtSetArg(args[n], XtNinternalWidth, 1); n++;
1206 XtSetArg(args[n], XtNborderWidth, 1); n++;
1207 if (menu->image != 0)
1208 XtSetArg(args[n], XtNbitmap, menu->image); n++;
1210 XtSetArg(args[n], XtNhighlightThickness, 0); n++;
1211 type = commandWidgetClass;
1212 /* TODO: figure out the position in the toolbar?
1213 * This currently works fine for the default toolbar, but
1214 * what if we add/remove items during later runtime?
1217 /* NOTE: "idx" isn't used here. The position is calculated by
1218 * athena_calculate_ins_pos(). The position it calculates
1219 * should be equal to "idx".
1221 /* TODO: Could we just store "idx" and use that as the child
1222 * placement?
1225 if (menu->id == NULL)
1227 menu->id = XtCreateManagedWidget((char *)menu->dname,
1228 type, toolBar, args, n);
1229 XtAddCallback(menu->id,
1230 XtNcallback, gui_x11_menu_cb, menu);
1232 else
1233 XtSetValues(menu->id, args, n);
1234 gui_athena_menu_colors(menu->id);
1236 #ifdef FEAT_BEVAL
1237 gui_mch_menu_set_tip(menu);
1238 #endif
1240 menu->parent = parent;
1241 menu->submenu_id = NULL;
1242 if (!XtIsManaged(toolBar)
1243 && vim_strchr(p_go, GO_TOOLBAR) != NULL)
1244 gui_mch_show_toolbar(TRUE);
1245 gui.toolbar_height = gui_mch_compute_toolbar_height();
1246 return;
1247 } /* toolbar menu item */
1248 # endif
1250 /* Add menu separator */
1251 if (menu_is_separator(menu->name))
1253 menu->submenu_id = (Widget)0;
1254 menu->id = XtVaCreateManagedWidget((char *)menu->dname,
1255 smeLineObjectClass, parent->submenu_id,
1256 NULL);
1257 if (menu->id == (Widget)0)
1258 return;
1259 gui_athena_menu_colors(menu->id);
1261 else
1263 if (parent != NULL && parent->submenu_id != (Widget)0)
1265 menu->submenu_id = (Widget)0;
1266 menu->id = XtVaCreateManagedWidget((char *)menu->dname,
1267 smeBSBObjectClass, parent->submenu_id,
1268 XtNlabel, menu->dname,
1269 #ifdef FONTSET_ALWAYS
1270 XtNinternational, True,
1271 #endif
1272 NULL);
1273 if (menu->id == (Widget)0)
1274 return;
1276 /* If there are other "pulldown" items in this pane, then adjust
1277 * the right margin to accomodate the arrow pixmap, otherwise
1278 * the right margin will be the same as the left margin.
1281 Dimension left_margin;
1283 XtVaGetValues(menu->id, XtNleftMargin, &left_margin, NULL);
1284 XtVaSetValues(menu->id, XtNrightMargin,
1285 gui_athena_menu_has_submenus(parent->submenu_id, NULL) ?
1286 puller_width :
1287 left_margin,
1288 NULL);
1291 gui_athena_menu_colors(menu->id);
1292 gui_athena_menu_font(menu->id);
1293 XtAddCallback(menu->id, XtNcallback, gui_x11_menu_cb,
1294 (XtPointer)menu);
1297 a_cur_menu = NULL;
1300 #if defined(FEAT_TOOLBAR) || defined(PROTO)
1301 void
1302 gui_mch_show_toolbar(int showit)
1304 Cardinal numChildren; /* how many children toolBar has */
1306 if (toolBar == (Widget)0)
1307 return;
1308 XtVaGetValues(toolBar, XtNnumChildren, &numChildren, NULL);
1309 if (showit && numChildren > 0)
1311 /* Assume that we want to show the toolbar if p_toolbar contains valid
1312 * option settings, therefore p_toolbar must not be NULL.
1314 WidgetList children;
1316 XtVaGetValues(toolBar, XtNchildren, &children, NULL);
1318 void (*action)(BalloonEval *);
1319 int text = 0;
1321 if (strstr((const char *)p_toolbar, "tooltips"))
1322 action = &gui_mch_enable_beval_area;
1323 else
1324 action = &gui_mch_disable_beval_area;
1325 if (strstr((const char *)p_toolbar, "text"))
1326 text = 1;
1327 else if (strstr((const char *)p_toolbar, "icons"))
1328 text = -1;
1329 if (text != 0)
1331 vimmenu_T *toolbar;
1332 vimmenu_T *cur;
1334 for (toolbar = root_menu; toolbar; toolbar = toolbar->next)
1335 if (menu_is_toolbar(toolbar->dname))
1336 break;
1337 /* Assumption: toolbar is NULL if there is no toolbar,
1338 * otherwise it contains the toolbar menu structure.
1340 * Assumption: "numChildren" == the number of items in the list
1341 * of items beginning with toolbar->children.
1343 if (toolbar)
1345 for (cur = toolbar->children; cur; cur = cur->next)
1347 Arg args[2];
1348 int n = 0;
1350 /* Enable/Disable tooltip (OK to enable while currently
1351 * enabled)
1353 if (cur->tip != NULL)
1354 (*action)(cur->tip);
1355 if (text == 1)
1357 XtSetArg(args[n], XtNbitmap, None);
1358 n++;
1359 XtSetArg(args[n], XtNlabel,
1360 menu_is_separator(cur->name) ? "" :
1361 (char *)cur->dname);
1362 n++;
1364 else
1366 XtSetArg(args[n], XtNbitmap, cur->image);
1367 n++;
1368 XtSetArg(args[n], XtNlabel, (cur->image == None) ?
1369 menu_is_separator(cur->name) ?
1370 "" :
1371 (char *)cur->dname
1373 (char *)None);
1374 n++;
1376 if (cur->id != NULL)
1378 XtUnmanageChild(cur->id);
1379 XtSetValues(cur->id, args, n);
1380 XtManageChild(cur->id);
1386 gui.toolbar_height = gui_mch_compute_toolbar_height();
1387 XtManageChild(toolBar);
1388 if (XtIsManaged(menuBar))
1390 XtVaSetValues(textArea,
1391 XtNvertDistance, gui.toolbar_height + gui.menu_height,
1392 NULL);
1393 XtVaSetValues(toolBar,
1394 XtNvertDistance, gui.menu_height,
1395 NULL);
1397 else
1399 XtVaSetValues(textArea,
1400 XtNvertDistance, gui.toolbar_height,
1401 NULL);
1402 XtVaSetValues(toolBar,
1403 XtNvertDistance, 0,
1404 NULL);
1407 else
1409 gui.toolbar_height = 0;
1410 if (XtIsManaged(menuBar))
1411 XtVaSetValues(textArea,
1412 XtNvertDistance, gui.menu_height,
1413 NULL);
1414 else
1415 XtVaSetValues(textArea,
1416 XtNvertDistance, 0,
1417 NULL);
1419 XtUnmanageChild(toolBar);
1421 gui_set_shellsize(FALSE, FALSE, RESIZE_VERT);
1426 gui_mch_compute_toolbar_height()
1428 Dimension height; /* total Toolbar height */
1429 Dimension whgt; /* height of each widget */
1430 Dimension marginHeight; /* XmNmarginHeight of toolBar */
1431 Dimension shadowThickness; /* thickness of Xtparent(toolBar) */
1432 WidgetList children; /* list of toolBar's children */
1433 Cardinal numChildren; /* how many children toolBar has */
1434 int i;
1436 height = 0;
1437 shadowThickness = 0;
1438 marginHeight = 0;
1439 if (toolBar != (Widget)0)
1441 XtVaGetValues(toolBar,
1442 XtNborderWidth, &shadowThickness,
1443 XtNvSpace, &marginHeight,
1444 XtNchildren, &children,
1445 XtNnumChildren, &numChildren,
1446 NULL);
1447 for (i = 0; i < numChildren; i++)
1449 whgt = 0;
1451 XtVaGetValues(children[i], XtNheight, &whgt, NULL);
1452 if (height < whgt)
1453 height = whgt;
1457 return (int)(height + (marginHeight << 1) + (shadowThickness << 1));
1460 void
1461 gui_mch_get_toolbar_colors(bgp, fgp, bsp, tsp, hsp)
1462 Pixel *bgp;
1463 Pixel *fgp;
1464 Pixel *bsp;
1465 Pixel *tsp;
1466 Pixel *hsp;
1468 XtVaGetValues(toolBar, XtNbackground, bgp, XtNborderColor, fgp, NULL);
1469 *bsp = *bgp;
1470 *tsp = *fgp;
1471 *hsp = *tsp;
1473 #endif
1476 /* ARGSUSED */
1477 void
1478 gui_mch_toggle_tearoffs(enable)
1479 int enable;
1481 /* no tearoff menus */
1484 void
1485 gui_mch_new_menu_colors()
1487 if (menuBar == (Widget)0)
1488 return;
1489 if (gui.menu_fg_pixel != INVALCOLOR)
1490 XtVaSetValues(menuBar, XtNborderColor, gui.menu_fg_pixel, NULL);
1491 gui_athena_menu_colors(menuBar);
1492 #ifdef FEAT_TOOLBAR
1493 gui_athena_menu_colors(toolBar);
1494 #endif
1496 gui_mch_submenu_change(root_menu, TRUE);
1500 * Destroy the machine specific menu widget.
1502 void
1503 gui_mch_destroy_menu(menu)
1504 vimmenu_T *menu;
1506 Widget parent;
1508 /* There is no item for the toolbar. */
1509 if (menu->id == (Widget)0)
1510 return;
1512 parent = XtParent(menu->id);
1514 /* When removing the last "pulldown" menu item from a pane, adjust the
1515 * right margins of the remaining widgets.
1517 if (menu->submenu_id != (Widget)0)
1519 /* Go through the menu items in the parent of this item and
1520 * adjust their margins, if necessary.
1521 * This takes care of the case when we delete the last menu item in a
1522 * pane that has a submenu. In this case, there will be no arrow
1523 * pixmaps shown anymore.
1526 WidgetList children;
1527 Cardinal num_children;
1528 int i;
1529 Dimension right_margin = 0;
1530 Boolean get_left_margin = False;
1532 XtVaGetValues(parent, XtNchildren, &children,
1533 XtNnumChildren, &num_children,
1534 NULL);
1535 if (gui_athena_menu_has_submenus(parent, menu->id))
1536 right_margin = puller_width;
1537 else
1538 get_left_margin = True;
1540 for (i = 0; i < num_children; ++i)
1542 if (children[i] == menu->id)
1543 continue;
1544 if (get_left_margin == True)
1546 Dimension left_margin;
1548 XtVaGetValues(children[i], XtNleftMargin, &left_margin,
1549 NULL);
1550 XtVaSetValues(children[i], XtNrightMargin, left_margin,
1551 NULL);
1553 else
1554 XtVaSetValues(children[i], XtNrightMargin, right_margin,
1555 NULL);
1559 /* Please be sure to destroy the parent widget first (i.e. menu->id).
1561 * This code should be basically identical to that in the file gui_motif.c
1562 * because they are both Xt based.
1564 if (menu->id != (Widget)0)
1566 Cardinal num_children;
1567 Dimension height, space, border;
1569 XtVaGetValues(menuBar,
1570 XtNvSpace, &space,
1571 XtNborderWidth, &border,
1572 NULL);
1573 XtVaGetValues(menu->id,
1574 XtNheight, &height,
1575 NULL);
1576 #if defined(FEAT_TOOLBAR) && defined(FEAT_BEVAL)
1577 if (parent == toolBar && menu->tip != NULL)
1579 /* We try to destroy this before the actual menu, because there are
1580 * callbacks, etc. that will be unregistered during the tooltip
1581 * destruction.
1583 * If you call "gui_mch_destroy_beval_area()" after destroying
1584 * menu->id, then the tooltip's window will have already been
1585 * deallocated by Xt, and unknown behaviour will ensue (probably
1586 * a core dump).
1588 gui_mch_destroy_beval_area(menu->tip);
1589 menu->tip = NULL;
1591 #endif
1593 * This is a hack to stop the Athena simpleMenuWidget from getting a
1594 * BadValue error when a menu's last child is destroyed. We check to
1595 * see if this is the last child and if so, don't delete it. The parent
1596 * will be deleted soon anyway, and it will delete it's children like
1597 * all good widgets do.
1599 /* NOTE: The cause of the BadValue X Protocol Error is because when the
1600 * last child is destroyed, it is first unmanaged, thus causing a
1601 * geometry resize request from the parent Shell widget.
1602 * Since the Shell widget has no more children, it is resized to have
1603 * width/height of 0. XConfigureWindow() is then called with the
1604 * width/height of 0, which generates the BadValue.
1606 * This happens in phase two of the widget destruction process.
1609 if (parent != menuBar
1610 #ifdef FEAT_TOOLBAR
1611 && parent != toolBar
1612 #endif
1615 XtVaGetValues(parent, XtNnumChildren, &num_children, NULL);
1616 if (num_children > 1)
1617 XtDestroyWidget(menu->id);
1619 else
1620 XtDestroyWidget(menu->id);
1621 menu->id = (Widget)0;
1624 if (parent == menuBar)
1626 if (!gui.menu_height_fixed)
1627 gui.menu_height = height + 2 * (space + border);
1629 #ifdef FEAT_TOOLBAR
1630 else if (parent == toolBar)
1632 /* When removing last toolbar item, don't display the toolbar. */
1633 XtVaGetValues(toolBar, XtNnumChildren, &num_children, NULL);
1634 if (num_children == 0)
1635 gui_mch_show_toolbar(FALSE);
1636 else
1637 gui.toolbar_height = gui_mch_compute_toolbar_height();
1639 #endif
1641 if (menu->submenu_id != (Widget)0)
1643 XtDestroyWidget(menu->submenu_id);
1644 menu->submenu_id = (Widget)0;
1648 /*ARGSUSED*/
1649 static void
1650 gui_athena_menu_timeout(client_data, id)
1651 XtPointer client_data;
1652 XtIntervalId *id;
1654 Widget w = (Widget)client_data;
1655 Widget popup;
1657 timer = 0;
1658 if (XtIsSubclass(w,smeBSBObjectClass))
1660 Pixmap p;
1662 XtVaGetValues(w, XtNrightBitmap, &p, NULL);
1663 if ((p != None) && (p != XtUnspecifiedPixmap))
1665 /* We are dealing with an item that has a submenu */
1666 popup = get_popup_entry(XtParent(w));
1667 if (popup == (Widget)0)
1668 return;
1669 XtPopup(popup, XtGrabNonexclusive);
1674 /* This routine is used to calculate the position (in screen coordinates)
1675 * where a submenu should appear relative to the menu entry that popped it
1676 * up. It should appear even with and just slightly to the left of the
1677 * rightmost end of the menu entry that caused the popup.
1679 * This is called when XtPopup() is called.
1681 /*ARGSUSED*/
1682 static void
1683 gui_athena_popup_callback(w, client_data, call_data)
1684 Widget w;
1685 XtPointer client_data;
1686 XtPointer call_data;
1688 /* Assumption: XtIsSubclass(XtParent(w),simpleMenuWidgetClass) */
1689 vimmenu_T *menu = (vimmenu_T *)client_data;
1690 Dimension width;
1691 Position root_x, root_y;
1693 /* First, popdown any siblings that may have menus popped up */
1695 vimmenu_T *i;
1697 for (i = menu->parent->children; i != NULL; i = i->next)
1699 if (i->submenu_id != NULL && XtIsManaged(i->submenu_id))
1700 XtPopdown(i->submenu_id);
1703 XtVaGetValues(XtParent(w),
1704 XtNwidth, &width,
1705 NULL);
1706 /* Assumption: XawSimpleMenuGetActiveEntry(XtParent(w)) == menu->id */
1707 /* i.e. This IS the active entry */
1708 XtTranslateCoords(menu->id,width - 5, 0, &root_x, &root_y);
1709 XtVaSetValues(w, XtNx, root_x,
1710 XtNy, root_y,
1711 NULL);
1714 /* ARGSUSED */
1715 static void
1716 gui_athena_popdown_submenus_action(w, event, args, nargs)
1717 Widget w;
1718 XEvent *event;
1719 String *args;
1720 Cardinal *nargs;
1722 WidgetList children;
1723 Cardinal num_children;
1725 XtVaGetValues(w, XtNchildren, &children,
1726 XtNnumChildren, &num_children,
1727 NULL);
1728 for (; num_children > 0; --num_children)
1730 Widget child = children[num_children - 1];
1732 if (has_submenu(child))
1734 Widget temp_w;
1736 temp_w = submenu_widget(child);
1737 gui_athena_popdown_submenus_action(temp_w,event,args,nargs);
1738 XtPopdown(temp_w);
1743 /* Used to determine if the given widget has a submenu that can be popped up. */
1744 static Boolean
1745 has_submenu(widget)
1746 Widget widget;
1748 if ((widget != NULL) && XtIsSubclass(widget,smeBSBObjectClass))
1750 Pixmap p;
1752 XtVaGetValues(widget, XtNrightBitmap, &p, NULL);
1753 if ((p != None) && (p != XtUnspecifiedPixmap))
1754 return True;
1756 return False;
1759 /* ARGSUSED */
1760 static void
1761 gui_athena_delayed_arm_action(w, event, args, nargs)
1762 Widget w;
1763 XEvent *event;
1764 String *args;
1765 Cardinal *nargs;
1767 Dimension width, height;
1769 if (event->type != MotionNotify)
1770 return;
1772 XtVaGetValues(w,
1773 XtNwidth, &width,
1774 XtNheight, &height,
1775 NULL);
1777 if (event->xmotion.x >= (int)width || event->xmotion.y >= (int)height)
1778 return;
1781 static Widget previous_active_widget = NULL;
1782 Widget current;
1784 current = XawSimpleMenuGetActiveEntry(w);
1785 if (current != previous_active_widget)
1787 if (timer)
1789 /* If the timeout hasn't been triggered, remove it */
1790 XtRemoveTimeOut(timer);
1792 gui_athena_popdown_submenus_action(w,event,args,nargs);
1793 if (has_submenu(current))
1795 XtAppAddTimeOut(XtWidgetToApplicationContext(w), 600L,
1796 gui_athena_menu_timeout,
1797 (XtPointer)current);
1799 previous_active_widget = current;
1804 static Widget
1805 get_popup_entry(w)
1806 Widget w;
1808 Widget menuw;
1810 /* Get the active entry for the current menu */
1811 if ((menuw = XawSimpleMenuGetActiveEntry(w)) == (Widget)0)
1812 return NULL;
1814 return submenu_widget(menuw);
1817 /* Given the widget that has been determined to have a submenu, return the submenu widget
1818 * that is to be popped up.
1820 static Widget
1821 submenu_widget(widget)
1822 Widget widget;
1824 /* Precondition: has_submenu(widget) == True
1825 * XtIsSubclass(XtParent(widget),simpleMenuWidgetClass) == True
1828 char_u *pullright_name;
1829 Widget popup;
1831 pullright_name = make_pull_name((char_u *)XtName(widget));
1832 popup = XtNameToWidget(XtParent(widget), (char *)pullright_name);
1833 vim_free(pullright_name);
1835 return popup;
1836 /* Postcondition: (popup != NULL) implies
1837 * (XtIsSubclass(popup,simpleMenuWidgetClass) == True) */
1840 /* ARGSUSED */
1841 void
1842 gui_mch_show_popupmenu(menu)
1843 vimmenu_T *menu;
1845 int rootx, rooty, winx, winy;
1846 Window root, child;
1847 unsigned int mask;
1849 if (menu->submenu_id == (Widget)0)
1850 return;
1852 /* Position the popup menu at the pointer */
1853 if (XQueryPointer(gui.dpy, XtWindow(vimShell), &root, &child,
1854 &rootx, &rooty, &winx, &winy, &mask))
1856 rootx -= 30;
1857 if (rootx < 0)
1858 rootx = 0;
1859 rooty -= 5;
1860 if (rooty < 0)
1861 rooty = 0;
1862 XtVaSetValues(menu->submenu_id,
1863 XtNx, rootx,
1864 XtNy, rooty,
1865 NULL);
1868 XtOverrideTranslations(menu->submenu_id, popupTrans);
1869 XtPopupSpringLoaded(menu->submenu_id);
1872 #endif /* FEAT_MENU */
1875 * Set the menu and scrollbar colors to their default values.
1877 void
1878 gui_mch_def_colors()
1881 * Get the colors ourselves. Using the automatic conversion doesn't
1882 * handle looking for approximate colors.
1884 if (gui.in_use)
1886 gui.menu_fg_pixel = gui_get_color((char_u *)gui.rsrc_menu_fg_name);
1887 gui.menu_bg_pixel = gui_get_color((char_u *)gui.rsrc_menu_bg_name);
1888 gui.scroll_fg_pixel = gui_get_color((char_u *)gui.rsrc_scroll_fg_name);
1889 gui.scroll_bg_pixel = gui_get_color((char_u *)gui.rsrc_scroll_bg_name);
1890 #ifdef FEAT_BEVAL
1891 gui.tooltip_fg_pixel = gui_get_color((char_u *)gui.rsrc_tooltip_fg_name);
1892 gui.tooltip_bg_pixel = gui_get_color((char_u *)gui.rsrc_tooltip_bg_name);
1893 #endif
1899 * Scrollbar stuff.
1902 void
1903 gui_mch_set_scrollbar_thumb(sb, val, size, max)
1904 scrollbar_T *sb;
1905 long val;
1906 long size;
1907 long max;
1909 double v, s;
1911 if (sb->id == (Widget)0)
1912 return;
1915 * Athena scrollbar must go from 0.0 to 1.0.
1917 if (max == 0)
1919 /* So you can't scroll it at all (normally it scrolls past end) */
1920 #ifdef FEAT_GUI_NEXTAW
1921 XawScrollbarSetThumb(sb->id, 0.0, 1.0);
1922 #else
1923 vim_XawScrollbarSetThumb(sb->id, 0.0, 1.0, 0.0);
1924 #endif
1926 else
1928 v = (double)val / (double)(max + 1);
1929 s = (double)size / (double)(max + 1);
1930 #ifdef FEAT_GUI_NEXTAW
1931 XawScrollbarSetThumb(sb->id, v, s);
1932 #else
1933 vim_XawScrollbarSetThumb(sb->id, v, s, 1.0);
1934 #endif
1938 void
1939 gui_mch_set_scrollbar_pos(sb, x, y, w, h)
1940 scrollbar_T *sb;
1941 int x;
1942 int y;
1943 int w;
1944 int h;
1946 if (sb->id == (Widget)0)
1947 return;
1949 XtUnmanageChild(sb->id);
1950 XtVaSetValues(sb->id,
1951 XtNhorizDistance, x,
1952 XtNvertDistance, y,
1953 XtNwidth, w,
1954 XtNheight, h,
1955 NULL);
1956 XtManageChild(sb->id);
1959 void
1960 gui_mch_enable_scrollbar(sb, flag)
1961 scrollbar_T *sb;
1962 int flag;
1964 if (sb->id != (Widget)0)
1966 if (flag)
1967 XtManageChild(sb->id);
1968 else
1969 XtUnmanageChild(sb->id);
1973 void
1974 gui_mch_create_scrollbar(sb, orient)
1975 scrollbar_T *sb;
1976 int orient; /* SBAR_VERT or SBAR_HORIZ */
1978 sb->id = XtVaCreateWidget("scrollBar",
1979 #ifdef FEAT_GUI_NEXTAW
1980 scrollbarWidgetClass, vimForm,
1981 #else
1982 vim_scrollbarWidgetClass, vimForm,
1983 #endif
1984 XtNresizable, True,
1985 XtNtop, XtChainTop,
1986 XtNbottom, XtChainTop,
1987 XtNleft, XtChainLeft,
1988 XtNright, XtChainLeft,
1989 XtNborderWidth, 0,
1990 XtNorientation, (orient == SBAR_VERT) ? XtorientVertical
1991 : XtorientHorizontal,
1992 XtNforeground, gui.scroll_fg_pixel,
1993 XtNbackground, gui.scroll_bg_pixel,
1994 NULL);
1995 if (sb->id == (Widget)0)
1996 return;
1998 XtAddCallback(sb->id, XtNjumpProc,
1999 gui_athena_scroll_cb_jump, (XtPointer)sb->ident);
2000 XtAddCallback(sb->id, XtNscrollProc,
2001 gui_athena_scroll_cb_scroll, (XtPointer)sb->ident);
2003 #ifdef FEAT_GUI_NEXTAW
2004 XawScrollbarSetThumb(sb->id, 0.0, 1.0);
2005 #else
2006 vim_XawScrollbarSetThumb(sb->id, 0.0, 1.0, 0.0);
2007 #endif
2010 #if defined(FEAT_WINDOWS) || defined(PROTO)
2011 void
2012 gui_mch_destroy_scrollbar(sb)
2013 scrollbar_T *sb;
2015 if (sb->id != (Widget)0)
2016 XtDestroyWidget(sb->id);
2018 #endif
2020 void
2021 gui_mch_set_scrollbar_colors(sb)
2022 scrollbar_T *sb;
2024 if (sb->id != (Widget)0)
2025 XtVaSetValues(sb->id,
2026 XtNforeground, gui.scroll_fg_pixel,
2027 XtNbackground, gui.scroll_bg_pixel,
2028 NULL);
2030 /* This is needed for the rectangle below the vertical scrollbars. */
2031 if (sb == &gui.bottom_sbar && vimForm != (Widget)0)
2032 gui_athena_scroll_colors(vimForm);
2036 * Miscellaneous stuff:
2038 Window
2039 gui_x11_get_wid()
2041 return XtWindow(textArea);
2044 #if defined(FEAT_BROWSE) || defined(PROTO)
2046 * Put up a file requester.
2047 * Returns the selected name in allocated memory, or NULL for Cancel.
2049 /* ARGSUSED */
2050 char_u *
2051 gui_mch_browse(saving, title, dflt, ext, initdir, filter)
2052 int saving; /* select file to write */
2053 char_u *title; /* not used (title for the window) */
2054 char_u *dflt; /* not used (default name) */
2055 char_u *ext; /* not used (extension added) */
2056 char_u *initdir; /* initial directory, NULL for current dir */
2057 char_u *filter; /* not used (file name filter) */
2059 Position x, y;
2060 char_u dirbuf[MAXPATHL];
2062 /* Concatenate "initdir" and "dflt". */
2063 if (initdir == NULL || *initdir == NUL)
2064 mch_dirname(dirbuf, MAXPATHL);
2065 else if (STRLEN(initdir) + 2 < MAXPATHL)
2066 STRCPY(dirbuf, initdir);
2067 else
2068 dirbuf[0] = NUL;
2069 if (dflt != NULL && *dflt != NUL
2070 && STRLEN(dirbuf) + 2 + STRLEN(dflt) < MAXPATHL)
2072 add_pathsep(dirbuf);
2073 STRCAT(dirbuf, dflt);
2076 /* Position the file selector just below the menubar */
2077 XtTranslateCoords(vimShell, (Position)0, (Position)
2078 #ifdef FEAT_MENU
2079 gui.menu_height
2080 #else
2082 #endif
2083 , &x, &y);
2084 return (char_u *)vim_SelFile(vimShell, (char *)title, (char *)dirbuf,
2085 NULL, (int)x, (int)y, gui.menu_fg_pixel, gui.menu_bg_pixel,
2086 gui.scroll_fg_pixel, gui.scroll_bg_pixel);
2088 #endif
2090 #if defined(FEAT_GUI_DIALOG) || defined(PROTO)
2092 static int dialogStatus;
2093 static Atom dialogatom;
2095 static void keyhit_callback __ARGS((Widget w, XtPointer client_data, XEvent *event, Boolean *cont));
2096 static void butproc __ARGS((Widget w, XtPointer client_data, XtPointer call_data));
2097 static void dialog_wm_handler __ARGS((Widget w, XtPointer client_data, XEvent *event, Boolean *dum));
2100 * Callback function for the textfield. When CR is hit this works like
2101 * hitting the "OK" button, ESC like "Cancel".
2103 /* ARGSUSED */
2104 static void
2105 keyhit_callback(w, client_data, event, cont)
2106 Widget w;
2107 XtPointer client_data;
2108 XEvent *event;
2109 Boolean *cont;
2111 char buf[2];
2113 if (XLookupString(&(event->xkey), buf, 2, NULL, NULL) == 1)
2115 if (*buf == CAR)
2116 dialogStatus = 1;
2117 else if (*buf == ESC)
2118 dialogStatus = 0;
2122 /* ARGSUSED */
2123 static void
2124 butproc(w, client_data, call_data)
2125 Widget w;
2126 XtPointer client_data;
2127 XtPointer call_data;
2129 dialogStatus = (int)(long)client_data + 1;
2133 * Function called when dialog window closed.
2135 /*ARGSUSED*/
2136 static void
2137 dialog_wm_handler(w, client_data, event, dum)
2138 Widget w;
2139 XtPointer client_data;
2140 XEvent *event;
2141 Boolean *dum;
2143 if (event->type == ClientMessage
2144 && ((XClientMessageEvent *)event)->data.l[0] == dialogatom)
2145 dialogStatus = 0;
2148 /* ARGSUSED */
2150 gui_mch_dialog(type, title, message, buttons, dfltbutton, textfield)
2151 int type;
2152 char_u *title;
2153 char_u *message;
2154 char_u *buttons;
2155 int dfltbutton;
2156 char_u *textfield;
2158 char_u *buts;
2159 char_u *p, *next;
2160 XtAppContext app;
2161 XEvent event;
2162 Position wd, hd;
2163 Position wv, hv;
2164 Position x, y;
2165 Widget dialog;
2166 Widget dialogshell;
2167 Widget dialogmessage;
2168 Widget dialogtextfield = 0;
2169 Widget dialogButton;
2170 Widget prev_dialogButton = NULL;
2171 int butcount;
2172 int vertical;
2174 if (title == NULL)
2175 title = (char_u *)_("Vim dialog");
2176 dialogStatus = -1;
2178 /* if our pointer is currently hidden, then we should show it. */
2179 gui_mch_mousehide(FALSE);
2181 /* Check 'v' flag in 'guioptions': vertical button placement. */
2182 vertical = (vim_strchr(p_go, GO_VERTICAL) != NULL);
2184 /* The shell is created each time, to make sure it is resized properly */
2185 dialogshell = XtVaCreatePopupShell("dialogShell",
2186 transientShellWidgetClass, vimShell,
2187 XtNtitle, title,
2188 NULL);
2189 if (dialogshell == (Widget)0)
2190 goto error;
2192 dialog = XtVaCreateManagedWidget("dialog",
2193 formWidgetClass, dialogshell,
2194 XtNdefaultDistance, 20,
2195 NULL);
2196 if (dialog == (Widget)0)
2197 goto error;
2198 gui_athena_menu_colors(dialog);
2199 dialogmessage = XtVaCreateManagedWidget("dialogMessage",
2200 labelWidgetClass, dialog,
2201 XtNlabel, message,
2202 XtNtop, XtChainTop,
2203 XtNbottom, XtChainTop,
2204 XtNleft, XtChainLeft,
2205 XtNright, XtChainLeft,
2206 XtNresizable, True,
2207 XtNborderWidth, 0,
2208 NULL);
2209 gui_athena_menu_colors(dialogmessage);
2211 if (textfield != NULL)
2213 dialogtextfield = XtVaCreateManagedWidget("textfield",
2214 asciiTextWidgetClass, dialog,
2215 XtNwidth, 400,
2216 XtNtop, XtChainTop,
2217 XtNbottom, XtChainTop,
2218 XtNleft, XtChainLeft,
2219 XtNright, XtChainRight,
2220 XtNfromVert, dialogmessage,
2221 XtNresizable, True,
2222 XtNstring, textfield,
2223 XtNlength, IOSIZE,
2224 XtNuseStringInPlace, True,
2225 XtNeditType, XawtextEdit,
2226 XtNwrap, XawtextWrapNever,
2227 XtNresize, XawtextResizeHeight,
2228 NULL);
2229 XtManageChild(dialogtextfield);
2230 XtAddEventHandler(dialogtextfield, KeyPressMask, False,
2231 (XtEventHandler)keyhit_callback, (XtPointer)NULL);
2232 XawTextSetInsertionPoint(dialogtextfield,
2233 (XawTextPosition)STRLEN(textfield));
2234 XtSetKeyboardFocus(dialog, dialogtextfield);
2237 /* make a copy, so that we can insert NULs */
2238 buts = vim_strsave(buttons);
2239 if (buts == NULL)
2240 return -1;
2242 p = buts;
2243 for (butcount = 0; *p; ++butcount)
2245 for (next = p; *next; ++next)
2247 if (*next == DLG_HOTKEY_CHAR)
2248 mch_memmove(next, next + 1, STRLEN(next));
2249 if (*next == DLG_BUTTON_SEP)
2251 *next++ = NUL;
2252 break;
2255 dialogButton = XtVaCreateManagedWidget("button",
2256 commandWidgetClass, dialog,
2257 XtNlabel, p,
2258 XtNtop, XtChainBottom,
2259 XtNbottom, XtChainBottom,
2260 XtNleft, XtChainLeft,
2261 XtNright, XtChainLeft,
2262 XtNfromVert, textfield == NULL ? dialogmessage : dialogtextfield,
2263 XtNvertDistance, vertical ? 4 : 20,
2264 XtNresizable, False,
2265 NULL);
2266 gui_athena_menu_colors(dialogButton);
2267 if (butcount > 0)
2268 XtVaSetValues(dialogButton,
2269 vertical ? XtNfromVert : XtNfromHoriz, prev_dialogButton,
2270 NULL);
2272 XtAddCallback(dialogButton, XtNcallback, butproc, (XtPointer)butcount);
2273 p = next;
2274 prev_dialogButton = dialogButton;
2276 vim_free(buts);
2278 XtRealizeWidget(dialogshell);
2280 /* Setup for catching the close-window event, don't let it close Vim! */
2281 dialogatom = XInternAtom(gui.dpy, "WM_DELETE_WINDOW", False);
2282 XSetWMProtocols(gui.dpy, XtWindow(dialogshell), &dialogatom, 1);
2283 XtAddEventHandler(dialogshell, NoEventMask, True, dialog_wm_handler, NULL);
2285 XtVaGetValues(dialogshell,
2286 XtNwidth, &wd,
2287 XtNheight, &hd,
2288 NULL);
2289 XtVaGetValues(vimShell,
2290 XtNwidth, &wv,
2291 XtNheight, &hv,
2292 NULL);
2293 XtTranslateCoords(vimShell,
2294 (Position)((wv - wd) / 2),
2295 (Position)((hv - hd) / 2),
2296 &x, &y);
2297 if (x < 0)
2298 x = 0;
2299 if (y < 0)
2300 y = 0;
2301 XtVaSetValues(dialogshell, XtNx, x, XtNy, y, NULL);
2303 /* Position the mouse pointer in the dialog, required for when focus
2304 * follows mouse. */
2305 XWarpPointer(gui.dpy, (Window)0, XtWindow(dialogshell), 0, 0, 0, 0, 20, 40);
2308 app = XtWidgetToApplicationContext(dialogshell);
2310 XtPopup(dialogshell, XtGrabNonexclusive);
2312 for (;;)
2314 XtAppNextEvent(app, &event);
2315 XtDispatchEvent(&event);
2316 if (dialogStatus >= 0)
2317 break;
2320 XtPopdown(dialogshell);
2322 if (textfield != NULL && dialogStatus < 0)
2323 *textfield = NUL;
2325 error:
2326 XtDestroyWidget(dialogshell);
2328 return dialogStatus;
2330 #endif
2332 #if defined(FEAT_GUI_DIALOG) || defined(FEAT_MENU)
2334 * Set the colors of Widget "id" to the menu colors.
2336 static void
2337 gui_athena_menu_colors(id)
2338 Widget id;
2340 if (gui.menu_bg_pixel != INVALCOLOR)
2341 XtVaSetValues(id, XtNbackground, gui.menu_bg_pixel, NULL);
2342 if (gui.menu_fg_pixel != INVALCOLOR)
2343 XtVaSetValues(id, XtNforeground, gui.menu_fg_pixel, NULL);
2345 #endif
2348 * Set the colors of Widget "id" to the scroll colors.
2350 static void
2351 gui_athena_scroll_colors(id)
2352 Widget id;
2354 if (gui.scroll_bg_pixel != INVALCOLOR)
2355 XtVaSetValues(id, XtNbackground, gui.scroll_bg_pixel, NULL);
2356 if (gui.scroll_fg_pixel != INVALCOLOR)
2357 XtVaSetValues(id, XtNforeground, gui.scroll_fg_pixel, NULL);