summaryrefslogtreecommitdiffstats
path: root/src/gui_athena.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-27 13:18:03 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-27 13:18:03 +0000
commitafce081b90c1e2c50c3507758c7558a0dfa1f33e (patch)
tree3fb840f0bd9de41b463443ddf17131a0ad77f226 /src/gui_athena.c
parentInitial commit. (diff)
downloadvim-afce081b90c1e2c50c3507758c7558a0dfa1f33e.tar.xz
vim-afce081b90c1e2c50c3507758c7558a0dfa1f33e.zip
Adding upstream version 2:8.2.2434.upstream/2%8.2.2434upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/gui_athena.c')
-rw-r--r--src/gui_athena.c2291
1 files changed, 2291 insertions, 0 deletions
diff --git a/src/gui_athena.c b/src/gui_athena.c
new file mode 100644
index 0000000..4384740
--- /dev/null
+++ b/src/gui_athena.c
@@ -0,0 +1,2291 @@
+/* vi:set ts=8 sts=4 sw=4 noet:
+ *
+ * VIM - Vi IMproved by Bram Moolenaar
+ * GUI/Motif support by Robert Webb
+ * Athena port by Bill Foster
+ *
+ * Do ":help uganda" in Vim to read copying and usage conditions.
+ * Do ":help credits" in Vim to see a list of people who contributed.
+ * See README.txt for an overview of the Vim source code.
+ */
+
+#include "vim.h"
+
+#include <X11/StringDefs.h>
+#include <X11/Intrinsic.h>
+#ifdef FEAT_GUI_NEXTAW
+# include <X11/neXtaw/Form.h>
+# include <X11/neXtaw/SimpleMenu.h>
+# include <X11/neXtaw/MenuButton.h>
+# include <X11/neXtaw/SmeBSB.h>
+# include <X11/neXtaw/SmeLine.h>
+# include <X11/neXtaw/Box.h>
+# include <X11/neXtaw/Dialog.h>
+# include <X11/neXtaw/Text.h>
+# include <X11/neXtaw/AsciiText.h>
+# include <X11/neXtaw/Scrollbar.h>
+#else
+# include <X11/Xaw/Form.h>
+# include <X11/Xaw/SimpleMenu.h>
+# include <X11/Xaw/MenuButton.h>
+# include <X11/Xaw/SmeBSB.h>
+# include <X11/Xaw/SmeLine.h>
+# include <X11/Xaw/Box.h>
+# include <X11/Xaw/Dialog.h>
+# include <X11/Xaw/Text.h>
+# include <X11/Xaw/AsciiText.h>
+#endif // FEAT_GUI_NEXTAW
+
+#ifndef FEAT_GUI_NEXTAW
+# include "gui_at_sb.h"
+#endif
+
+extern Widget vimShell;
+
+static Widget vimForm = (Widget)0;
+Widget textArea = (Widget)0;
+#ifdef FEAT_MENU
+static Widget menuBar = (Widget)0;
+static XtIntervalId timer = 0; // 0 = expired, otherwise active
+
+// Used to figure out menu ordering
+static vimmenu_T *a_cur_menu = NULL;
+static Cardinal athena_calculate_ins_pos(Widget);
+
+static void gui_athena_popup_callback(Widget, XtPointer, XtPointer);
+static void gui_athena_delayed_arm_action(Widget, XEvent *, String *,
+ Cardinal *);
+static void gui_athena_popdown_submenus_action(Widget, XEvent *,
+ String *, Cardinal *);
+static XtActionsRec pullAction[2] = {
+ { "menu-delayedpopup", (XtActionProc)gui_athena_delayed_arm_action},
+ { "menu-popdownsubmenus", (XtActionProc)gui_athena_popdown_submenus_action}
+};
+#endif
+
+#ifdef FEAT_TOOLBAR
+static void gui_mch_reset_focus(void);
+static Widget toolBar = (Widget)0;
+#endif
+
+#if defined(FEAT_GUI_DIALOG) || defined(FEAT_MENU)
+static void gui_athena_menu_colors(Widget id);
+#endif
+static void gui_athena_scroll_colors(Widget id);
+
+#ifdef FEAT_MENU
+static XtTranslations popupTrans, parentTrans, menuTrans, supermenuTrans;
+static Pixmap pullerBitmap = None;
+static int puller_width = 0;
+#endif
+
+/*
+ * Scrollbar callback (XtNjumpProc) for when the scrollbar is dragged with the
+ * left or middle mouse button.
+ */
+ static void
+gui_athena_scroll_cb_jump(
+ Widget w UNUSED,
+ XtPointer client_data,
+ XtPointer call_data)
+{
+ scrollbar_T *sb, *sb_info;
+ long value;
+
+ sb = gui_find_scrollbar((long)client_data);
+
+ if (sb == NULL)
+ return;
+ else if (sb->wp != NULL) // Left or right scrollbar
+ {
+ /*
+ * Careful: need to get scrollbar info out of first (left) scrollbar
+ * for window, but keep real scrollbar too because we must pass it to
+ * gui_drag_scrollbar().
+ */
+ sb_info = &sb->wp->w_scrollbars[0];
+ }
+ else // Bottom scrollbar
+ sb_info = sb;
+
+ value = (long)(*((float *)call_data) * (float)(sb_info->max + 1) + 0.001);
+ if (value > sb_info->max)
+ value = sb_info->max;
+
+ gui_drag_scrollbar(sb, value, TRUE);
+}
+
+/*
+ * Scrollbar callback (XtNscrollProc) for paging up or down with the left or
+ * right mouse buttons.
+ */
+ static void
+gui_athena_scroll_cb_scroll(
+ Widget w UNUSED,
+ XtPointer client_data,
+ XtPointer call_data)
+{
+ scrollbar_T *sb, *sb_info;
+ long value;
+ int data = (int)(long)call_data;
+ int page;
+
+ sb = gui_find_scrollbar((long)client_data);
+
+ if (sb == NULL)
+ return;
+ if (sb->wp != NULL) // Left or right scrollbar
+ {
+ /*
+ * Careful: need to get scrollbar info out of first (left) scrollbar
+ * for window, but keep real scrollbar too because we must pass it to
+ * gui_drag_scrollbar().
+ */
+ sb_info = &sb->wp->w_scrollbars[0];
+
+ if (sb_info->size > 5)
+ page = sb_info->size - 2; // use two lines of context
+ else
+ page = sb_info->size;
+#ifdef FEAT_GUI_NEXTAW
+ if (data < 0)
+ {
+ data = (data - gui.char_height + 1) / gui.char_height;
+ if (data > -sb_info->size)
+ data = -1;
+ else
+ data = -page;
+ }
+ else if (data > 0)
+ {
+ data = (data + gui.char_height - 1) / gui.char_height;
+ if (data < sb_info->size)
+ data = 1;
+ else
+ data = page;
+ }
+#else
+ switch (data)
+ {
+ case ONE_LINE_DATA: data = 1; break;
+ case -ONE_LINE_DATA: data = -1; break;
+ case ONE_PAGE_DATA: data = page; break;
+ case -ONE_PAGE_DATA: data = -page; break;
+ case END_PAGE_DATA: data = sb_info->max; break;
+ case -END_PAGE_DATA: data = -sb_info->max; break;
+ default: data = 0; break;
+ }
+#endif
+ }
+ else // Bottom scrollbar
+ {
+ sb_info = sb;
+#ifdef FEAT_GUI_NEXTAW
+ if (data < 0)
+ {
+ data = (data - gui.char_width + 1) / gui.char_width;
+ if (data > -sb->size)
+ data = -1;
+ }
+ else if (data > 0)
+ {
+ data = (data + gui.char_width - 1) / gui.char_width;
+ if (data < sb->size)
+ data = 1;
+ }
+#endif
+ if (data < -1) // page-width left
+ {
+ if (sb->size > 8)
+ data = -(sb->size - 5);
+ else
+ data = -sb->size;
+ }
+ else if (data > 1) // page-width right
+ {
+ if (sb->size > 8)
+ data = (sb->size - 5);
+ else
+ data = sb->size;
+ }
+ }
+
+ value = sb_info->value + data;
+ if (value > sb_info->max)
+ value = sb_info->max;
+ else if (value < 0)
+ value = 0;
+
+ // Update the bottom scrollbar an extra time (why is this needed??
+ if (sb->wp == NULL) // Bottom scrollbar
+ gui_mch_set_scrollbar_thumb(sb, value, sb->size, sb->max);
+
+ gui_drag_scrollbar(sb, value, FALSE);
+}
+
+/*
+ * Create all the Athena widgets necessary.
+ */
+ void
+gui_x11_create_widgets(void)
+{
+ /*
+ * We don't have any borders handled internally by the textArea to worry
+ * about so only skip over the configured border width.
+ */
+ gui.border_offset = gui.border_width;
+
+ // The form containing all the other widgets
+ vimForm = XtVaCreateManagedWidget("vimForm",
+ formWidgetClass, vimShell,
+ XtNborderWidth, 0,
+ NULL);
+ gui_athena_scroll_colors(vimForm);
+
+#ifdef FEAT_MENU
+ // The top menu bar
+ menuBar = XtVaCreateManagedWidget("menuBar",
+ boxWidgetClass, vimForm,
+ XtNresizable, True,
+ XtNtop, XtChainTop,
+ XtNbottom, XtChainTop,
+ XtNleft, XtChainLeft,
+ XtNright, XtChainRight,
+ XtNinsertPosition, athena_calculate_ins_pos,
+ NULL);
+ gui_athena_menu_colors(menuBar);
+ if (gui.menu_fg_pixel != INVALCOLOR)
+ XtVaSetValues(menuBar, XtNborderColor, gui.menu_fg_pixel, NULL);
+#endif
+
+#ifdef FEAT_TOOLBAR
+ // Don't create it Managed, it will be managed when creating the first
+ // item. Otherwise an empty toolbar shows up.
+ toolBar = XtVaCreateWidget("toolBar",
+ boxWidgetClass, vimForm,
+ XtNresizable, True,
+ XtNtop, XtChainTop,
+ XtNbottom, XtChainTop,
+ XtNleft, XtChainLeft,
+ XtNright, XtChainRight,
+ XtNorientation, XtorientHorizontal,
+ XtNhSpace, 1,
+ XtNvSpace, 3,
+ XtNinsertPosition, athena_calculate_ins_pos,
+ NULL);
+ gui_athena_menu_colors(toolBar);
+#endif
+
+ // The text area.
+ textArea = XtVaCreateManagedWidget("textArea",
+ coreWidgetClass, vimForm,
+ XtNresizable, True,
+ XtNtop, XtChainTop,
+ XtNbottom, XtChainTop,
+ XtNleft, XtChainLeft,
+ XtNright, XtChainLeft,
+ XtNbackground, gui.back_pixel,
+ XtNborderWidth, 0,
+ NULL);
+
+ /*
+ * Install the callbacks.
+ */
+ gui_x11_callbacks(textArea, vimForm);
+
+#ifdef FEAT_MENU
+ popupTrans = XtParseTranslationTable(
+ "<EnterWindow>: menu-popdownsubmenus() highlight() menu-delayedpopup()\n"
+ "<LeaveWindow>: unhighlight()\n"
+ "<BtnUp>: menu-popdownsubmenus() XtMenuPopdown() notify() unhighlight()\n"
+ "<Motion>: highlight() menu-delayedpopup()");
+ parentTrans = XtParseTranslationTable("<LeaveWindow>: unhighlight()");
+ menuTrans = XtParseTranslationTable(
+ "<EnterWindow>: menu-popdownsubmenus() highlight() menu-delayedpopup()\n"
+ "<LeaveWindow>: menu-popdownsubmenus() XtMenuPopdown() unhighlight()\n"
+ "<BtnUp>: notify() unhighlight()\n"
+ "<BtnMotion>: highlight() menu-delayedpopup()");
+ supermenuTrans = XtParseTranslationTable(
+ "<EnterWindow>: menu-popdownsubmenus() highlight() menu-delayedpopup()\n"
+ "<LeaveWindow>: unhighlight()\n"
+ "<BtnUp>: menu-popdownsubmenus() XtMenuPopdown() notify() unhighlight()\n"
+ "<BtnMotion>: highlight() menu-delayedpopup()");
+
+ XtAppAddActions(XtWidgetToApplicationContext(vimForm), pullAction,
+ XtNumber(pullAction));
+#endif
+
+ // Pretend we don't have input focus, we will get an event if we do.
+ gui.in_focus = FALSE;
+}
+
+#ifdef FEAT_MENU
+/*
+ * Calculates the Pixmap based on the size of the current menu font.
+ */
+ static Pixmap
+gui_athena_create_pullright_pixmap(Widget w)
+{
+ Pixmap retval;
+#ifdef FONTSET_ALWAYS
+ XFontSet font = None;
+#else
+ XFontStruct *font = NULL;
+#endif
+
+#ifdef FONTSET_ALWAYS
+ if (gui.menu_fontset == NOFONTSET)
+#else
+ if (gui.menu_font == NOFONT)
+#endif
+ {
+ XrmValue from, to;
+ WidgetList children;
+ Cardinal num_children;
+
+#ifdef FONTSET_ALWAYS
+ from.size = strlen(from.addr = XtDefaultFontSet);
+ to.addr = (XtPointer)&font;
+ to.size = sizeof(XFontSet);
+#else
+ from.size = strlen(from.addr = XtDefaultFont);
+ to.addr = (XtPointer)&font;
+ to.size = sizeof(XFontStruct *);
+#endif
+ // Assumption: The menuBar children will use the same font as the
+ // pulldown menu items AND they will all be of type
+ // XtNfont.
+ XtVaGetValues(menuBar, XtNchildren, &children,
+ XtNnumChildren, &num_children,
+ NULL);
+ if (XtConvertAndStore(w ? w :
+ (num_children > 0) ? children[0] : menuBar,
+ XtRString, &from,
+#ifdef FONTSET_ALWAYS
+ XtRFontSet, &to
+#else
+ XtRFontStruct, &to
+#endif
+ ) == False)
+ return None;
+ // "font" should now contain data
+ }
+ else
+#ifdef FONTSET_ALWAYS
+ font = (XFontSet)gui.menu_fontset;
+#else
+ font = (XFontStruct *)gui.menu_font;
+#endif
+
+ {
+ int width, height;
+ GC draw_gc, undraw_gc;
+ XGCValues gc_values;
+ XPoint points[3];
+
+#ifdef FONTSET_ALWAYS
+ height = fontset_height2(font);
+#else
+ height = font->max_bounds.ascent + font->max_bounds.descent;
+#endif
+ width = height - 2;
+ puller_width = width + 4;
+ retval = XCreatePixmap(gui.dpy,DefaultRootWindow(gui.dpy),width,
+ height, 1);
+ gc_values.foreground = 1;
+ gc_values.background = 0;
+ draw_gc = XCreateGC(gui.dpy, retval,
+ GCForeground | GCBackground,
+ &gc_values);
+ gc_values.foreground = 0;
+ gc_values.background = 1;
+ undraw_gc = XCreateGC(gui.dpy, retval,
+ GCForeground | GCBackground,
+ &gc_values);
+ points[0].x = 0;
+ points[0].y = 0;
+ points[1].x = width - 1;
+ points[1].y = (height - 1) / 2;
+ points[2].x = 0;
+ points[2].y = height - 1;
+ XFillRectangle(gui.dpy, retval, undraw_gc, 0, 0, height, height);
+ XFillPolygon(gui.dpy, retval, draw_gc, points, XtNumber(points),
+ Convex, CoordModeOrigin);
+ XFreeGC(gui.dpy, draw_gc);
+ XFreeGC(gui.dpy, undraw_gc);
+ }
+ return retval;
+}
+#endif
+
+/*
+ * Called when the GUI is not going to start after all.
+ */
+ void
+gui_x11_destroy_widgets(void)
+{
+ textArea = NULL;
+#ifdef FEAT_MENU
+ menuBar = NULL;
+#endif
+#ifdef FEAT_TOOLBAR
+ toolBar = NULL;
+#endif
+}
+
+#if defined(FEAT_TOOLBAR) || defined(PROTO)
+# include "gui_x11_pm.h"
+# ifdef HAVE_X11_XPM_H
+# include <X11/xpm.h>
+# endif
+
+static void createXpmImages(char_u *path, char **xpm, Pixmap *sen);
+
+/*
+ * Allocated a pixmap for toolbar menu "menu".
+ * Return in "sen".
+ */
+ static void
+get_toolbar_pixmap(vimmenu_T *menu, Pixmap *sen)
+{
+ char_u buf[MAXPATHL]; // buffer storing expanded pathname
+ char **xpm = NULL; // xpm array
+
+ buf[0] = NUL; // start with NULL path
+
+ if (menu->iconfile != NULL)
+ {
+ // Use the "icon=" argument.
+ gui_find_iconfile(menu->iconfile, buf, "xpm");
+ createXpmImages(buf, NULL, sen);
+
+ // If it failed, try using the menu name.
+ if (*sen == (Pixmap)0 && gui_find_bitmap(menu->name, buf, "xpm") == OK)
+ createXpmImages(buf, NULL, sen);
+ if (*sen != (Pixmap)0)
+ return;
+ }
+
+ if (menu->icon_builtin || gui_find_bitmap(menu->name, buf, "xpm") == FAIL)
+ {
+ if (menu->iconidx >= 0 && menu->iconidx
+ < (int)(sizeof(built_in_pixmaps) / sizeof(built_in_pixmaps[0])))
+ xpm = built_in_pixmaps[menu->iconidx];
+ else
+ xpm = tb_blank_xpm;
+ }
+
+ if (xpm != NULL || buf[0] != NUL)
+ createXpmImages(buf, xpm, sen);
+}
+
+/*
+ * Read an Xpm file, doing color substitutions for the foreground and
+ * background colors. If there is an error reading a color xpm file,
+ * drop back and read the monochrome file. If successful, create the
+ * insensitive Pixmap too.
+ */
+ static void
+createXpmImages(char_u *path, char **xpm, Pixmap *sen)
+{
+ Window rootWindow;
+ XpmAttributes attrs;
+ XpmColorSymbol color[5] =
+ {
+ {"none", "none", 0},
+ {"iconColor1", NULL, 0},
+ {"bottomShadowColor", NULL, 0},
+ {"topShadowColor", NULL, 0},
+ {"selectColor", NULL, 0}
+ };
+ int screenNum;
+ int status;
+ Pixmap mask;
+ Pixmap map;
+
+ gui_mch_get_toolbar_colors(
+ &color[BACKGROUND].pixel,
+ &color[FOREGROUND].pixel,
+ &color[BOTTOM_SHADOW].pixel,
+ &color[TOP_SHADOW].pixel,
+ &color[HIGHLIGHT].pixel);
+
+ // Setup the color substitution table
+ attrs.valuemask = XpmColorSymbols;
+ attrs.colorsymbols = color;
+ attrs.numsymbols = 5;
+
+ screenNum = DefaultScreen(gui.dpy);
+ rootWindow = RootWindow(gui.dpy, screenNum);
+
+ // Create the "sensitive" pixmap
+ if (xpm != NULL)
+ status = XpmCreatePixmapFromData(gui.dpy, rootWindow, xpm,
+ &map, &mask, &attrs);
+ else
+ status = XpmReadFileToPixmap(gui.dpy, rootWindow, (char *)path,
+ &map, &mask, &attrs);
+ if (status == XpmSuccess && map != 0)
+ {
+ XGCValues gcvalues;
+ GC back_gc;
+ GC mask_gc;
+
+ // Need to create new Pixmaps with the mask applied.
+ gcvalues.foreground = color[BACKGROUND].pixel;
+ back_gc = XCreateGC(gui.dpy, map, GCForeground, &gcvalues);
+ mask_gc = XCreateGC(gui.dpy, map, GCForeground, &gcvalues);
+ XSetClipMask(gui.dpy, mask_gc, mask);
+
+ // Create the "sensitive" pixmap.
+ *sen = XCreatePixmap(gui.dpy, rootWindow,
+ attrs.width, attrs.height,
+ DefaultDepth(gui.dpy, screenNum));
+ XFillRectangle(gui.dpy, *sen, back_gc, 0, 0,
+ attrs.width, attrs.height);
+ XCopyArea(gui.dpy, map, *sen, mask_gc, 0, 0,
+ attrs.width, attrs.height, 0, 0);
+
+ XFreeGC(gui.dpy, back_gc);
+ XFreeGC(gui.dpy, mask_gc);
+ XFreePixmap(gui.dpy, map);
+ }
+ else
+ *sen = 0;
+
+ XpmFreeAttributes(&attrs);
+}
+
+ void
+gui_mch_set_toolbar_pos(
+ int x,
+ int y,
+ int w,
+ int h)
+{
+ Dimension border;
+ int height;
+
+ if (!XtIsManaged(toolBar)) // nothing to do
+ return;
+ XtUnmanageChild(toolBar);
+ XtVaGetValues(toolBar,
+ XtNborderWidth, &border,
+ NULL);
+ height = h - 2 * border;
+ if (height < 0)
+ height = 1;
+ XtVaSetValues(toolBar,
+ XtNhorizDistance, x,
+ XtNvertDistance, y,
+ XtNwidth, w - 2 * border,
+ XtNheight, height,
+ NULL);
+ XtManageChild(toolBar);
+}
+#endif
+
+ void
+gui_mch_set_text_area_pos(
+ int x,
+ int y,
+ int w,
+ int h)
+{
+ XtUnmanageChild(textArea);
+ XtVaSetValues(textArea,
+ XtNhorizDistance, x,
+ XtNvertDistance, y,
+ XtNwidth, w,
+ XtNheight, h,
+ NULL);
+ XtManageChild(textArea);
+#ifdef FEAT_TOOLBAR
+ // Give keyboard focus to the textArea instead of the toolbar.
+ gui_mch_reset_focus();
+#endif
+}
+
+#ifdef FEAT_TOOLBAR
+/*
+ * A toolbar button has been pushed; now reset the input focus
+ * such that the user can type page up/down etc. and have the
+ * input go to the editor window, not the button
+ */
+ static void
+gui_mch_reset_focus(void)
+{
+ XtSetKeyboardFocus(vimForm, textArea);
+}
+#endif
+
+
+ void
+gui_x11_set_back_color(void)
+{
+ if (textArea != NULL)
+ XtVaSetValues(textArea,
+ XtNbackground, gui.back_pixel,
+ NULL);
+}
+
+#if defined(FEAT_MENU) || defined(PROTO)
+/*
+ * Menu stuff.
+ */
+
+static char_u *make_pull_name(char_u * name);
+static Widget get_popup_entry(Widget w);
+static Widget submenu_widget(Widget);
+static Boolean has_submenu(Widget);
+static void gui_mch_submenu_change(vimmenu_T *mp, int colors);
+static void gui_athena_menu_font(Widget id);
+
+ void
+gui_mch_enable_menu(int flag)
+{
+ if (flag)
+ {
+ XtManageChild(menuBar);
+# ifdef FEAT_TOOLBAR
+ if (XtIsManaged(toolBar))
+ {
+ XtVaSetValues(toolBar,
+ XtNvertDistance, gui.menu_height,
+ NULL);
+ XtVaSetValues(textArea,
+ XtNvertDistance, gui.menu_height + gui.toolbar_height,
+ NULL);
+ }
+# endif
+ }
+ else
+ {
+ XtUnmanageChild(menuBar);
+# ifdef FEAT_TOOLBAR
+ if (XtIsManaged(toolBar))
+ {
+ XtVaSetValues(toolBar,
+ XtNvertDistance, 0,
+ NULL);
+ }
+# endif
+ }
+}
+
+ void
+gui_mch_set_menu_pos(
+ int x,
+ int y,
+ int w,
+ int h)
+{
+ Dimension border;
+ int height;
+
+ XtUnmanageChild(menuBar);
+ XtVaGetValues(menuBar, XtNborderWidth, &border, NULL);
+ // avoid trouble when there are no menu items, and h is 1
+ height = h - 2 * border;
+ if (height < 0)
+ height = 1;
+ XtVaSetValues(menuBar,
+ XtNhorizDistance, x,
+ XtNvertDistance, y,
+ XtNwidth, w - 2 * border,
+ XtNheight, height,
+ NULL);
+ XtManageChild(menuBar);
+}
+
+/*
+ * Used to calculate the insertion position of a widget with respect to its
+ * neighbors.
+ *
+ * Valid range of return values is: 0 (beginning of children) to
+ * numChildren (end of children).
+ */
+ static Cardinal
+athena_calculate_ins_pos(Widget widget)
+{
+ // Assume that if the parent of the vimmenu_T is NULL, then we can get
+ // to this menu by traversing "next", starting at "root_menu".
+ //
+ // This holds true for popup menus, toolbar, and toplevel menu items.
+
+ // Popup menus: "id" is NULL. Only submenu_id is valid
+
+ // Menus that are not toplevel: "parent" will be non-NULL, "id" &
+ // "submenu_id" will be non-NULL.
+
+ // Toplevel menus: "parent" is NULL, id is the widget of the menu item
+
+ WidgetList children;
+ Cardinal num_children = 0;
+ int retval;
+ Arg args[2];
+ int n = 0;
+ int i;
+
+ XtSetArg(args[n], XtNchildren, &children); n++;
+ XtSetArg(args[n], XtNnumChildren, &num_children); n++;
+ XtGetValues(XtParent(widget), args, n);
+
+ retval = num_children;
+ for (i = 0; i < (int)num_children; ++i)
+ {
+ Widget current = children[i];
+ vimmenu_T *menu = NULL;
+
+ for (menu = (a_cur_menu->parent == NULL)
+ ? root_menu : a_cur_menu->parent->children;
+ menu != NULL;
+ menu = menu->next)
+ if (current == menu->id
+ && a_cur_menu->priority < menu->priority
+ && i < retval)
+ retval = i;
+ }
+ return retval;
+}
+
+ void
+gui_mch_add_menu(vimmenu_T *menu, int idx UNUSED)
+{
+ char_u *pullright_name;
+ Dimension height, space, border;
+ vimmenu_T *parent = menu->parent;
+
+ a_cur_menu = menu;
+ if (parent == NULL)
+ {
+ if (menu_is_popup(menu->dname))
+ {
+ menu->submenu_id = XtVaCreatePopupShell((char *)menu->dname,
+ simpleMenuWidgetClass, vimShell,
+ XtNinsertPosition, athena_calculate_ins_pos,
+ XtNtranslations, popupTrans,
+ NULL);
+ gui_athena_menu_colors(menu->submenu_id);
+ }
+ else if (menu_is_menubar(menu->dname))
+ {
+ menu->id = XtVaCreateManagedWidget((char *)menu->dname,
+ menuButtonWidgetClass, menuBar,
+ XtNmenuName, menu->dname,
+#ifdef FONTSET_ALWAYS
+ XtNinternational, True,
+#endif
+ NULL);
+ if (menu->id == (Widget)0)
+ return;
+ gui_athena_menu_colors(menu->id);
+ gui_athena_menu_font(menu->id);
+
+ menu->submenu_id = XtVaCreatePopupShell((char *)menu->dname,
+ simpleMenuWidgetClass, menu->id,
+ XtNinsertPosition, athena_calculate_ins_pos,
+ XtNtranslations, supermenuTrans,
+ NULL);
+ gui_athena_menu_colors(menu->submenu_id);
+ gui_athena_menu_font(menu->submenu_id);
+
+ // Don't update the menu height when it was set at a fixed value
+ if (!gui.menu_height_fixed)
+ {
+ /*
+ * When we add a top-level item to the menu bar, we can figure
+ * out how high the menu bar should be.
+ */
+ XtVaGetValues(menuBar,
+ XtNvSpace, &space,
+ XtNborderWidth, &border,
+ NULL);
+ XtVaGetValues(menu->id,
+ XtNheight, &height,
+ NULL);
+ gui.menu_height = height + 2 * (space + border);
+ }
+ }
+ }
+ else if (parent->submenu_id != (Widget)0)
+ {
+ menu->id = XtVaCreateManagedWidget((char *)menu->dname,
+ smeBSBObjectClass, parent->submenu_id,
+ XtNlabel, menu->dname,
+#ifdef FONTSET_ALWAYS
+ XtNinternational, True,
+#endif
+ NULL);
+ if (menu->id == (Widget)0)
+ return;
+ if (pullerBitmap == None)
+ pullerBitmap = gui_athena_create_pullright_pixmap(menu->id);
+
+ XtVaSetValues(menu->id, XtNrightBitmap, pullerBitmap,
+ NULL);
+ // If there are other menu items that are not pulldown menus,
+ // we need to adjust the right margins of those, too.
+ {
+ WidgetList children;
+ Cardinal num_children;
+ int i;
+
+ XtVaGetValues(parent->submenu_id, XtNchildren, &children,
+ XtNnumChildren, &num_children,
+ NULL);
+ for (i = 0; i < (int)num_children; ++i)
+ {
+ XtVaSetValues(children[i],
+ XtNrightMargin, puller_width,
+ NULL);
+ }
+ }
+ gui_athena_menu_colors(menu->id);
+ gui_athena_menu_font(menu->id);
+
+ pullright_name = make_pull_name(menu->dname);
+ menu->submenu_id = XtVaCreatePopupShell((char *)pullright_name,
+ simpleMenuWidgetClass, parent->submenu_id,
+ XtNtranslations, menuTrans,
+ NULL);
+ gui_athena_menu_colors(menu->submenu_id);
+ gui_athena_menu_font(menu->submenu_id);
+ vim_free(pullright_name);
+ XtAddCallback(menu->submenu_id, XtNpopupCallback,
+ gui_athena_popup_callback, (XtPointer)menu);
+
+ if (parent->parent != NULL)
+ XtOverrideTranslations(parent->submenu_id, parentTrans);
+ }
+ a_cur_menu = NULL;
+}
+
+// Used to determine whether a SimpleMenu has pulldown entries.
+//
+// "id" is the parent of the menu items.
+// Ignore widget "ignore" in the pane.
+ static Boolean
+gui_athena_menu_has_submenus(Widget id, Widget ignore)
+{
+ WidgetList children;
+ Cardinal num_children;
+ int i;
+
+ XtVaGetValues(id, XtNchildren, &children,
+ XtNnumChildren, &num_children,
+ NULL);
+ for (i = 0; i < (int)num_children; ++i)
+ {
+ if (children[i] == ignore)
+ continue;
+ if (has_submenu(children[i]))
+ return True;
+ }
+ return False;
+}
+
+ static void
+gui_athena_menu_font(Widget id)
+{
+#ifdef FONTSET_ALWAYS
+ if (gui.menu_fontset != NOFONTSET)
+ {
+ if (XtIsManaged(id))
+ {
+ XtUnmanageChild(id);
+ XtVaSetValues(id, XtNfontSet, gui.menu_fontset, NULL);
+ // We should force the widget to recalculate its
+ // geometry now.
+ XtManageChild(id);
+ }
+ else
+ XtVaSetValues(id, XtNfontSet, gui.menu_fontset, NULL);
+ if (has_submenu(id))
+ XtVaSetValues(id, XtNrightBitmap, pullerBitmap, NULL);
+ }
+#else
+ int managed = FALSE;
+
+ if (gui.menu_font != NOFONT)
+ {
+ if (XtIsManaged(id))
+ {
+ XtUnmanageChild(id);
+ managed = TRUE;
+ }
+
+# ifdef FEAT_XFONTSET
+ if (gui.fontset != NOFONTSET)
+ XtVaSetValues(id, XtNfontSet, gui.menu_font, NULL);
+ else
+# endif
+ XtVaSetValues(id, XtNfont, gui.menu_font, NULL);
+ if (has_submenu(id))
+ XtVaSetValues(id, XtNrightBitmap, pullerBitmap, NULL);
+
+ // Force the widget to recalculate its geometry now.
+ if (managed)
+ XtManageChild(id);
+ }
+#endif
+}
+
+
+ void
+gui_mch_new_menu_font(void)
+{
+ Pixmap oldpuller = None;
+
+ if (menuBar == (Widget)0)
+ return;
+
+ if (pullerBitmap != None)
+ {
+ oldpuller = pullerBitmap;
+ pullerBitmap = gui_athena_create_pullright_pixmap(NULL);
+ }
+ gui_mch_submenu_change(root_menu, FALSE);
+
+ {
+ // Iterate through the menubar menu items and get the height of
+ // each one. The menu bar height is set to the maximum of all
+ // the heights.
+ vimmenu_T *mp;
+ int max_height = 9999;
+
+ FOR_ALL_MENUS(mp)
+ {
+ if (menu_is_menubar(mp->dname))
+ {
+ Dimension height;
+
+ XtVaGetValues(mp->id,
+ XtNheight, &height,
+ NULL);
+ if (height < max_height)
+ max_height = height;
+ }
+ }
+ if (max_height != 9999)
+ {
+ // Don't update the menu height when it was set at a fixed value
+ if (!gui.menu_height_fixed)
+ {
+ Dimension space, border;
+
+ XtVaGetValues(menuBar,
+ XtNvSpace, &space,
+ XtNborderWidth, &border,
+ NULL);
+ gui.menu_height = max_height + 2 * (space + border);
+ }
+ }
+ }
+ // Now, to simulate the window being resized. Only, this
+ // will resize the window to its current state.
+ //
+ // There has to be a better way, but I do not see one at this time.
+ // (David Harrison)
+ {
+ Position w, h;
+
+ XtVaGetValues(vimShell,
+ XtNwidth, &w,
+ XtNheight, &h,
+ NULL);
+ gui_resize_shell(w, h
+#ifdef FEAT_XIM
+ - xim_get_status_area_height()
+#endif
+ );
+ }
+ gui_set_shellsize(FALSE, TRUE, RESIZE_VERT);
+ ui_new_shellsize();
+ if (oldpuller != None)
+ XFreePixmap(gui.dpy, oldpuller);
+}
+
+#if defined(FEAT_BEVAL_GUI) || defined(PROTO)
+ void
+gui_mch_new_tooltip_font(void)
+{
+# ifdef FEAT_TOOLBAR
+ vimmenu_T *menu;
+
+ if (toolBar == (Widget)0)
+ return;
+
+ menu = gui_find_menu((char_u *)"ToolBar");
+ if (menu != NULL)
+ gui_mch_submenu_change(menu, FALSE);
+# endif
+}
+
+ void
+gui_mch_new_tooltip_colors(void)
+{
+# ifdef FEAT_TOOLBAR
+ vimmenu_T *menu;
+
+ if (toolBar == (Widget)0)
+ return;
+
+ menu = gui_find_menu((char_u *)"ToolBar");
+ if (menu != NULL)
+ gui_mch_submenu_change(menu, TRUE);
+# endif
+}
+#endif
+
+ static void
+gui_mch_submenu_change(
+ vimmenu_T *menu,
+ int colors) // TRUE for colors, FALSE for font
+{
+ vimmenu_T *mp;
+
+ for (mp = menu; mp != NULL; mp = mp->next)
+ {
+ if (mp->id != (Widget)0)
+ {
+ if (colors)
+ {
+ gui_athena_menu_colors(mp->id);
+#ifdef FEAT_TOOLBAR
+ // For a toolbar item: Free the pixmap and allocate a new one,
+ // so that the background color is right.
+ if (mp->image != (Pixmap)0)
+ {
+ XFreePixmap(gui.dpy, mp->image);
+ get_toolbar_pixmap(mp, &mp->image);
+ if (mp->image != (Pixmap)0)
+ XtVaSetValues(mp->id, XtNbitmap, mp->image, NULL);
+ }
+
+# ifdef FEAT_BEVAL_GUI
+ // If we have a tooltip, then we need to change its colors
+ if (mp->tip != NULL)
+ {
+ Arg args[2];
+
+ args[0].name = XtNbackground;
+ args[0].value = gui.tooltip_bg_pixel;
+ args[1].name = XtNforeground;
+ args[1].value = gui.tooltip_fg_pixel;
+ XtSetValues(mp->tip->balloonLabel, &args[0], XtNumber(args));
+ }
+# endif
+#endif
+ }
+ else
+ {
+ gui_athena_menu_font(mp->id);
+#ifdef FEAT_BEVAL_GUI
+ // If we have a tooltip, then we need to change its font
+ // Assume XtNinternational == True (in createBalloonEvalWindow)
+ if (mp->tip != NULL)
+ {
+ Arg args[1];
+
+ args[0].name = XtNfontSet;
+ args[0].value = (XtArgVal)gui.tooltip_fontset;
+ XtSetValues(mp->tip->balloonLabel, &args[0], XtNumber(args));
+ }
+#endif
+ }
+ }
+
+ if (mp->children != NULL)
+ {
+ // Set the colors/font for the tear off widget
+ if (mp->submenu_id != (Widget)0)
+ {
+ if (colors)
+ gui_athena_menu_colors(mp->submenu_id);
+ else
+ gui_athena_menu_font(mp->submenu_id);
+ }
+ // Set the colors for the children
+ gui_mch_submenu_change(mp->children, colors);
+ }
+ }
+}
+
+/*
+ * Make a submenu name into a pullright name.
+ * Replace '.' by '_', can't include '.' in the submenu name.
+ */
+ static char_u *
+make_pull_name(char_u * name)
+{
+ char_u *pname;
+ char_u *p;
+
+ pname = vim_strnsave(name, STRLEN(name) + strlen("-pullright"));
+ if (pname != NULL)
+ {
+ strcat((char *)pname, "-pullright");
+ while ((p = vim_strchr(pname, '.')) != NULL)
+ *p = '_';
+ }
+ return pname;
+}
+
+ void
+gui_mch_add_menu_item(vimmenu_T *menu, int idx UNUSED)
+{
+ vimmenu_T *parent = menu->parent;
+
+ a_cur_menu = menu;
+# ifdef FEAT_TOOLBAR
+ if (menu_is_toolbar(parent->name))
+ {
+ WidgetClass type;
+ int n;
+ Arg args[21];
+
+ n = 0;
+ if (menu_is_separator(menu->name))
+ {
+ XtSetArg(args[n], XtNlabel, ""); n++;
+ XtSetArg(args[n], XtNborderWidth, 0); n++;
+ }
+ else
+ {
+ get_toolbar_pixmap(menu, &menu->image);
+ XtSetArg(args[n], XtNlabel, menu->dname); n++;
+ XtSetArg(args[n], XtNinternalHeight, 1); n++;
+ XtSetArg(args[n], XtNinternalWidth, 1); n++;
+ XtSetArg(args[n], XtNborderWidth, 1); n++;
+ if (menu->image != 0)
+ XtSetArg(args[n], XtNbitmap, menu->image); n++;
+ }
+ XtSetArg(args[n], XtNhighlightThickness, 0); n++;
+ type = commandWidgetClass;
+ // TODO: figure out the position in the toolbar?
+ // This currently works fine for the default toolbar, but
+ // what if we add/remove items during later runtime?
+
+ // NOTE: "idx" isn't used here. The position is calculated by
+ // athena_calculate_ins_pos(). The position it calculates
+ // should be equal to "idx".
+ // TODO: Could we just store "idx" and use that as the child
+ // placement?
+
+ if (menu->id == NULL)
+ {
+ menu->id = XtCreateManagedWidget((char *)menu->dname,
+ type, toolBar, args, n);
+ XtAddCallback(menu->id,
+ XtNcallback, gui_x11_menu_cb, menu);
+ }
+ else
+ XtSetValues(menu->id, args, n);
+ gui_athena_menu_colors(menu->id);
+
+#ifdef FEAT_BEVAL_GUI
+ gui_mch_menu_set_tip(menu);
+#endif
+
+ menu->parent = parent;
+ menu->submenu_id = NULL;
+ if (!XtIsManaged(toolBar)
+ && vim_strchr(p_go, GO_TOOLBAR) != NULL)
+ gui_mch_show_toolbar(TRUE);
+ gui.toolbar_height = gui_mch_compute_toolbar_height();
+ return;
+ } // toolbar menu item
+# endif
+
+ // Add menu separator
+ if (menu_is_separator(menu->name))
+ {
+ menu->submenu_id = (Widget)0;
+ menu->id = XtVaCreateManagedWidget((char *)menu->dname,
+ smeLineObjectClass, parent->submenu_id,
+ NULL);
+ if (menu->id == (Widget)0)
+ return;
+ gui_athena_menu_colors(menu->id);
+ }
+ else
+ {
+ if (parent != NULL && parent->submenu_id != (Widget)0)
+ {
+ menu->submenu_id = (Widget)0;
+ menu->id = XtVaCreateManagedWidget((char *)menu->dname,
+ smeBSBObjectClass, parent->submenu_id,
+ XtNlabel, menu->dname,
+#ifdef FONTSET_ALWAYS
+ XtNinternational, True,
+#endif
+ NULL);
+ if (menu->id == (Widget)0)
+ return;
+
+ // If there are other "pulldown" items in this pane, then adjust
+ // the right margin to accommodate the arrow pixmap, otherwise
+ // the right margin will be the same as the left margin.
+ {
+ Dimension left_margin;
+
+ XtVaGetValues(menu->id, XtNleftMargin, &left_margin, NULL);
+ XtVaSetValues(menu->id, XtNrightMargin,
+ gui_athena_menu_has_submenus(parent->submenu_id, NULL) ?
+ puller_width :
+ left_margin,
+ NULL);
+ }
+
+ gui_athena_menu_colors(menu->id);
+ gui_athena_menu_font(menu->id);
+ XtAddCallback(menu->id, XtNcallback, gui_x11_menu_cb,
+ (XtPointer)menu);
+ }
+ }
+ a_cur_menu = NULL;
+}
+
+#if defined(FEAT_TOOLBAR) || defined(PROTO)
+ void
+gui_mch_show_toolbar(int showit)
+{
+ Cardinal numChildren; // how many children toolBar has
+
+ if (toolBar == (Widget)0)
+ return;
+ XtVaGetValues(toolBar, XtNnumChildren, &numChildren, NULL);
+ if (showit && numChildren > 0)
+ {
+ // Assume that we want to show the toolbar if p_toolbar contains valid
+ // option settings, therefore p_toolbar must not be NULL.
+ WidgetList children;
+
+ XtVaGetValues(toolBar, XtNchildren, &children, NULL);
+ {
+ void (*action)(BalloonEval *);
+ int text = 0;
+
+ if (strstr((const char *)p_toolbar, "tooltips"))
+ action = &gui_mch_enable_beval_area;
+ else
+ action = &gui_mch_disable_beval_area;
+ if (strstr((const char *)p_toolbar, "text"))
+ text = 1;
+ else if (strstr((const char *)p_toolbar, "icons"))
+ text = -1;
+ if (text != 0)
+ {
+ vimmenu_T *toolbar;
+ vimmenu_T *cur;
+
+ FOR_ALL_MENUS(toolbar)
+ if (menu_is_toolbar(toolbar->dname))
+ break;
+ // Assumption: toolbar is NULL if there is no toolbar,
+ // otherwise it contains the toolbar menu structure.
+ //
+ // Assumption: "numChildren" == the number of items in the list
+ // of items beginning with toolbar->children.
+ if (toolbar)
+ {
+ for (cur = toolbar->children; cur; cur = cur->next)
+ {
+ Arg args[2];
+ int n = 0;
+
+ // Enable/Disable tooltip (OK to enable while currently
+ // enabled)
+ if (cur->tip != NULL)
+ (*action)(cur->tip);
+ if (text == 1)
+ {
+ XtSetArg(args[n], XtNbitmap, None);
+ n++;
+ XtSetArg(args[n], XtNlabel,
+ menu_is_separator(cur->name) ? "" :
+ (char *)cur->dname);
+ n++;
+ }
+ else
+ {
+ XtSetArg(args[n], XtNbitmap, cur->image);
+ n++;
+ XtSetArg(args[n], XtNlabel, (cur->image == None) ?
+ menu_is_separator(cur->name) ?
+ "" :
+ (char *)cur->dname
+ :
+ (char *)None);
+ n++;
+ }
+ if (cur->id != NULL)
+ {
+ XtUnmanageChild(cur->id);
+ XtSetValues(cur->id, args, n);
+ XtManageChild(cur->id);
+ }
+ }
+ }
+ }
+ }
+ gui.toolbar_height = gui_mch_compute_toolbar_height();
+ XtManageChild(toolBar);
+ if (XtIsManaged(menuBar))
+ {
+ XtVaSetValues(textArea,
+ XtNvertDistance, gui.toolbar_height + gui.menu_height,
+ NULL);
+ XtVaSetValues(toolBar,
+ XtNvertDistance, gui.menu_height,
+ NULL);
+ }
+ else
+ {
+ XtVaSetValues(textArea,
+ XtNvertDistance, gui.toolbar_height,
+ NULL);
+ XtVaSetValues(toolBar,
+ XtNvertDistance, 0,
+ NULL);
+ }
+ }
+ else
+ {
+ gui.toolbar_height = 0;
+ if (XtIsManaged(menuBar))
+ XtVaSetValues(textArea,
+ XtNvertDistance, gui.menu_height,
+ NULL);
+ else
+ XtVaSetValues(textArea,
+ XtNvertDistance, 0,
+ NULL);
+
+ XtUnmanageChild(toolBar);
+ }
+ gui_set_shellsize(FALSE, FALSE, RESIZE_VERT);
+}
+
+
+ int
+gui_mch_compute_toolbar_height(void)
+{
+ Dimension height; // total Toolbar height
+ Dimension whgt; // height of each widget
+ Dimension marginHeight; // XmNmarginHeight of toolBar
+ Dimension shadowThickness; // thickness of Xtparent(toolBar)
+ WidgetList children; // list of toolBar's children
+ Cardinal numChildren; // how many children toolBar has
+ int i;
+
+ height = 0;
+ shadowThickness = 0;
+ marginHeight = 0;
+ if (toolBar != (Widget)0)
+ {
+ XtVaGetValues(toolBar,
+ XtNborderWidth, &shadowThickness,
+ XtNvSpace, &marginHeight,
+ XtNchildren, &children,
+ XtNnumChildren, &numChildren,
+ NULL);
+ for (i = 0; i < (int)numChildren; i++)
+ {
+ whgt = 0;
+
+ XtVaGetValues(children[i], XtNheight, &whgt, NULL);
+ if (height < whgt)
+ height = whgt;
+ }
+ }
+
+ return (int)(height + (marginHeight << 1) + (shadowThickness << 1));
+}
+
+ void
+gui_mch_get_toolbar_colors(
+ Pixel *bgp,
+ Pixel *fgp,
+ Pixel *bsp,
+ Pixel *tsp,
+ Pixel *hsp)
+{
+ XtVaGetValues(toolBar, XtNbackground, bgp, XtNborderColor, fgp, NULL);
+ *bsp = *bgp;
+ *tsp = *fgp;
+ *hsp = *tsp;
+}
+#endif
+
+
+ void
+gui_mch_toggle_tearoffs(int enable UNUSED)
+{
+ // no tearoff menus
+}
+
+ void
+gui_mch_new_menu_colors(void)
+{
+ if (menuBar == (Widget)0)
+ return;
+ if (gui.menu_fg_pixel != INVALCOLOR)
+ XtVaSetValues(menuBar, XtNborderColor, gui.menu_fg_pixel, NULL);
+ gui_athena_menu_colors(menuBar);
+#ifdef FEAT_TOOLBAR
+ gui_athena_menu_colors(toolBar);
+#endif
+
+ gui_mch_submenu_change(root_menu, TRUE);
+}
+
+/*
+ * Destroy the machine specific menu widget.
+ */
+ void
+gui_mch_destroy_menu(vimmenu_T *menu)
+{
+ Widget parent;
+
+ // There is no item for the toolbar.
+ if (menu->id == (Widget)0)
+ return;
+
+ parent = XtParent(menu->id);
+
+ // When removing the last "pulldown" menu item from a pane, adjust the
+ // right margins of the remaining widgets.
+ if (menu->submenu_id != (Widget)0)
+ {
+ // Go through the menu items in the parent of this item and
+ // adjust their margins, if necessary.
+ // This takes care of the case when we delete the last menu item in a
+ // pane that has a submenu. In this case, there will be no arrow
+ // pixmaps shown anymore.
+ {
+ WidgetList children;
+ Cardinal num_children;
+ int i;
+ Dimension right_margin = 0;
+ Boolean get_left_margin = False;
+
+ XtVaGetValues(parent, XtNchildren, &children,
+ XtNnumChildren, &num_children,
+ NULL);
+ if (gui_athena_menu_has_submenus(parent, menu->id))
+ right_margin = puller_width;
+ else
+ get_left_margin = True;
+
+ for (i = 0; i < (int)num_children; ++i)
+ {
+ if (children[i] == menu->id)
+ continue;
+ if (get_left_margin == True)
+ {
+ Dimension left_margin;
+
+ XtVaGetValues(children[i], XtNleftMargin, &left_margin,
+ NULL);
+ XtVaSetValues(children[i], XtNrightMargin, left_margin,
+ NULL);
+ }
+ else
+ XtVaSetValues(children[i], XtNrightMargin, right_margin,
+ NULL);
+ }
+ }
+ }
+ // Please be sure to destroy the parent widget first (i.e. menu->id).
+ //
+ // This code should be basically identical to that in the file gui_motif.c
+ // because they are both Xt based.
+ if (menu->id != (Widget)0)
+ {
+ Cardinal num_children;
+ Dimension height, space, border;
+
+ XtVaGetValues(menuBar,
+ XtNvSpace, &space,
+ XtNborderWidth, &border,
+ NULL);
+ XtVaGetValues(menu->id,
+ XtNheight, &height,
+ NULL);
+#if defined(FEAT_TOOLBAR) && defined(FEAT_BEVAL_GUI)
+ if (parent == toolBar && menu->tip != NULL)
+ {
+ // We try to destroy this before the actual menu, because there are
+ // callbacks, etc. that will be unregistered during the tooltip
+ // destruction.
+ //
+ // If you call "gui_mch_destroy_beval_area()" after destroying
+ // menu->id, then the tooltip's window will have already been
+ // deallocated by Xt, and unknown behaviour will ensue (probably
+ // a core dump).
+ gui_mch_destroy_beval_area(menu->tip);
+ menu->tip = NULL;
+ }
+#endif
+ /*
+ * This is a hack to stop the Athena simpleMenuWidget from getting a
+ * BadValue error when a menu's last child is destroyed. We check to
+ * see if this is the last child and if so, don't delete it. The parent
+ * will be deleted soon anyway, and it will delete its children like
+ * all good widgets do.
+ */
+ // NOTE: The cause of the BadValue X Protocol Error is because when the
+ // last child is destroyed, it is first unmanaged, thus causing a
+ // geometry resize request from the parent Shell widget.
+ // Since the Shell widget has no more children, it is resized to have
+ // width/height of 0. XConfigureWindow() is then called with the
+ // width/height of 0, which generates the BadValue.
+ //
+ // This happens in phase two of the widget destruction process.
+ {
+ if (parent != menuBar
+#ifdef FEAT_TOOLBAR
+ && parent != toolBar
+#endif
+ )
+ {
+ XtVaGetValues(parent, XtNnumChildren, &num_children, NULL);
+ if (num_children > 1)
+ XtDestroyWidget(menu->id);
+ }
+ else
+ XtDestroyWidget(menu->id);
+ menu->id = (Widget)0;
+ }
+
+ if (parent == menuBar)
+ {
+ if (!gui.menu_height_fixed)
+ gui.menu_height = height + 2 * (space + border);
+ }
+#ifdef FEAT_TOOLBAR
+ else if (parent == toolBar)
+ {
+ // When removing last toolbar item, don't display the toolbar.
+ XtVaGetValues(toolBar, XtNnumChildren, &num_children, NULL);
+ if (num_children == 0)
+ gui_mch_show_toolbar(FALSE);
+ else
+ gui.toolbar_height = gui_mch_compute_toolbar_height();
+ }
+#endif
+ }
+ if (menu->submenu_id != (Widget)0)
+ {
+ XtDestroyWidget(menu->submenu_id);
+ menu->submenu_id = (Widget)0;
+ }
+}
+
+ static void
+gui_athena_menu_timeout(
+ XtPointer client_data,
+ XtIntervalId *id UNUSED)
+{
+ Widget w = (Widget)client_data;
+ Widget popup;
+
+ timer = 0;
+ if (XtIsSubclass(w,smeBSBObjectClass))
+ {
+ Pixmap p;
+
+ XtVaGetValues(w, XtNrightBitmap, &p, NULL);
+ if ((p != None) && (p != XtUnspecifiedPixmap))
+ {
+ // We are dealing with an item that has a submenu
+ popup = get_popup_entry(XtParent(w));
+ if (popup == (Widget)0)
+ return;
+ XtPopup(popup, XtGrabNonexclusive);
+ }
+ }
+}
+
+/*
+ * This routine is used to calculate the position (in screen coordinates)
+ * where a submenu should appear relative to the menu entry that popped it
+ * up. It should appear even with and just slightly to the left of the
+ * rightmost end of the menu entry that caused the popup.
+ *
+ * This is called when XtPopup() is called.
+ */
+ static void
+gui_athena_popup_callback(
+ Widget w,
+ XtPointer client_data,
+ XtPointer call_data UNUSED)
+{
+ // Assumption: XtIsSubclass(XtParent(w),simpleMenuWidgetClass)
+ vimmenu_T *menu = (vimmenu_T *)client_data;
+ Dimension width;
+ Position root_x, root_y;
+
+ // First, popdown any siblings that may have menus popped up
+ {
+ vimmenu_T *i;
+
+ FOR_ALL_CHILD_MENUS(menu->parent, i)
+ {
+ if (i->submenu_id != NULL && XtIsManaged(i->submenu_id))
+ XtPopdown(i->submenu_id);
+ }
+ }
+ XtVaGetValues(XtParent(w),
+ XtNwidth, &width,
+ NULL);
+ // Assumption: XawSimpleMenuGetActiveEntry(XtParent(w)) == menu->id
+ // i.e. This IS the active entry
+ XtTranslateCoords(menu->id,width - 5, 0, &root_x, &root_y);
+ XtVaSetValues(w, XtNx, root_x,
+ XtNy, root_y,
+ NULL);
+}
+
+ static void
+gui_athena_popdown_submenus_action(
+ Widget w,
+ XEvent *event,
+ String *args,
+ Cardinal *nargs)
+{
+ WidgetList children;
+ Cardinal num_children;
+
+ XtVaGetValues(w, XtNchildren, &children,
+ XtNnumChildren, &num_children,
+ NULL);
+ for (; num_children > 0; --num_children)
+ {
+ Widget child = children[num_children - 1];
+
+ if (has_submenu(child))
+ {
+ Widget temp_w;
+
+ temp_w = submenu_widget(child);
+ gui_athena_popdown_submenus_action(temp_w,event,args,nargs);
+ XtPopdown(temp_w);
+ }
+ }
+}
+
+/*
+ * Used to determine if the given widget has a submenu that can be popped up.
+ */
+ static Boolean
+has_submenu(Widget widget)
+{
+ if ((widget != NULL) && XtIsSubclass(widget,smeBSBObjectClass))
+ {
+ Pixmap p;
+
+ XtVaGetValues(widget, XtNrightBitmap, &p, NULL);
+ if ((p != None) && (p != XtUnspecifiedPixmap))
+ return True;
+ }
+ return False;
+}
+
+ static void
+gui_athena_delayed_arm_action(
+ Widget w,
+ XEvent *event,
+ String *args,
+ Cardinal *nargs)
+{
+ Dimension width, height;
+
+ if (event->type != MotionNotify)
+ return;
+
+ XtVaGetValues(w,
+ XtNwidth, &width,
+ XtNheight, &height,
+ NULL);
+
+ if (event->xmotion.x >= (int)width || event->xmotion.y >= (int)height)
+ return;
+
+ {
+ static Widget previous_active_widget = NULL;
+ Widget current;
+
+ current = XawSimpleMenuGetActiveEntry(w);
+ if (current != previous_active_widget)
+ {
+ if (timer)
+ {
+ // If the timeout hasn't been triggered, remove it
+ XtRemoveTimeOut(timer);
+ }
+ gui_athena_popdown_submenus_action(w,event,args,nargs);
+ if (has_submenu(current))
+ {
+ XtAppAddTimeOut(XtWidgetToApplicationContext(w), 600L,
+ gui_athena_menu_timeout,
+ (XtPointer)current);
+ }
+ previous_active_widget = current;
+ }
+ }
+}
+
+ static Widget
+get_popup_entry(Widget w)
+{
+ Widget menuw;
+
+ // Get the active entry for the current menu
+ if ((menuw = XawSimpleMenuGetActiveEntry(w)) == (Widget)0)
+ return NULL;
+
+ return submenu_widget(menuw);
+}
+
+/*
+ * Given the widget that has been determined to have a submenu, return the
+ * submenu widget that is to be popped up.
+ */
+ static Widget
+submenu_widget(Widget widget)
+{
+ // Precondition: has_submenu(widget) == True
+ // XtIsSubclass(XtParent(widget),simpleMenuWidgetClass) == True
+
+ char_u *pullright_name;
+ Widget popup;
+
+ pullright_name = make_pull_name((char_u *)XtName(widget));
+ popup = XtNameToWidget(XtParent(widget), (char *)pullright_name);
+ vim_free(pullright_name);
+
+ return popup;
+ // Postcondition: (popup != NULL) implies
+ // (XtIsSubclass(popup,simpleMenuWidgetClass) == True)
+}
+
+ void
+gui_mch_show_popupmenu(vimmenu_T *menu)
+{
+ int rootx, rooty, winx, winy;
+ Window root, child;
+ unsigned int mask;
+
+ if (menu->submenu_id == (Widget)0)
+ return;
+
+ // Position the popup menu at the pointer
+ if (XQueryPointer(gui.dpy, XtWindow(vimShell), &root, &child,
+ &rootx, &rooty, &winx, &winy, &mask))
+ {
+ rootx -= 30;
+ if (rootx < 0)
+ rootx = 0;
+ rooty -= 5;
+ if (rooty < 0)
+ rooty = 0;
+ XtVaSetValues(menu->submenu_id,
+ XtNx, rootx,
+ XtNy, rooty,
+ NULL);
+ }
+
+ XtOverrideTranslations(menu->submenu_id, popupTrans);
+ XtPopupSpringLoaded(menu->submenu_id);
+}
+
+#endif // FEAT_MENU
+
+/*
+ * Set the menu and scrollbar colors to their default values.
+ */
+ void
+gui_mch_def_colors(void)
+{
+ /*
+ * Get the colors ourselves. Using the automatic conversion doesn't
+ * handle looking for approximate colors.
+ */
+ if (gui.in_use)
+ {
+ gui.menu_fg_pixel = gui_get_color((char_u *)gui.rsrc_menu_fg_name);
+ gui.menu_bg_pixel = gui_get_color((char_u *)gui.rsrc_menu_bg_name);
+ gui.scroll_fg_pixel = gui_get_color((char_u *)gui.rsrc_scroll_fg_name);
+ gui.scroll_bg_pixel = gui_get_color((char_u *)gui.rsrc_scroll_bg_name);
+#ifdef FEAT_BEVAL_GUI
+ gui.tooltip_fg_pixel = gui_get_color((char_u *)gui.rsrc_tooltip_fg_name);
+ gui.tooltip_bg_pixel = gui_get_color((char_u *)gui.rsrc_tooltip_bg_name);
+#endif
+ }
+}
+
+
+/*
+ * Scrollbar stuff.
+ */
+
+ void
+gui_mch_set_scrollbar_thumb(
+ scrollbar_T *sb,
+ long val,
+ long size,
+ long max)
+{
+ double v, s;
+
+ if (sb->id == (Widget)0)
+ return;
+
+ /*
+ * Athena scrollbar must go from 0.0 to 1.0.
+ */
+ if (max == 0)
+ {
+ // So you can't scroll it at all (normally it scrolls past end)
+#ifdef FEAT_GUI_NEXTAW
+ XawScrollbarSetThumb(sb->id, 0.0, 1.0);
+#else
+ vim_XawScrollbarSetThumb(sb->id, 0.0, 1.0, 0.0);
+#endif
+ }
+ else
+ {
+ v = (double)val / (double)(max + 1);
+ s = (double)size / (double)(max + 1);
+#ifdef FEAT_GUI_NEXTAW
+ XawScrollbarSetThumb(sb->id, v, s);
+#else
+ vim_XawScrollbarSetThumb(sb->id, v, s, 1.0);
+#endif
+ }
+}
+
+ void
+gui_mch_set_scrollbar_pos(
+ scrollbar_T *sb,
+ int x,
+ int y,
+ int w,
+ int h)
+{
+ if (sb->id == (Widget)0)
+ return;
+
+ XtUnmanageChild(sb->id);
+ XtVaSetValues(sb->id,
+ XtNhorizDistance, x,
+ XtNvertDistance, y,
+ XtNwidth, w,
+ XtNheight, h,
+ NULL);
+ XtManageChild(sb->id);
+}
+
+ int
+gui_mch_get_scrollbar_xpadding(void)
+{
+ // TODO: Calculate the padding for adjust scrollbar position when the
+ // Window is maximized.
+ return 0;
+}
+
+ int
+gui_mch_get_scrollbar_ypadding(void)
+{
+ // TODO: Calculate the padding for adjust scrollbar position when the
+ // Window is maximized.
+ return 0;
+}
+
+ void
+gui_mch_enable_scrollbar(scrollbar_T *sb, int flag)
+{
+ if (sb->id != (Widget)0)
+ {
+ if (flag)
+ XtManageChild(sb->id);
+ else
+ XtUnmanageChild(sb->id);
+ }
+}
+
+ void
+gui_mch_create_scrollbar(
+ scrollbar_T *sb,
+ int orient) // SBAR_VERT or SBAR_HORIZ
+{
+ sb->id = XtVaCreateWidget("scrollBar",
+#ifdef FEAT_GUI_NEXTAW
+ scrollbarWidgetClass, vimForm,
+#else
+ vim_scrollbarWidgetClass, vimForm,
+#endif
+ XtNresizable, True,
+ XtNtop, XtChainTop,
+ XtNbottom, XtChainTop,
+ XtNleft, XtChainLeft,
+ XtNright, XtChainLeft,
+ XtNborderWidth, 0,
+ XtNorientation, (orient == SBAR_VERT) ? XtorientVertical
+ : XtorientHorizontal,
+ XtNforeground, gui.scroll_fg_pixel,
+ XtNbackground, gui.scroll_bg_pixel,
+ NULL);
+ if (sb->id == (Widget)0)
+ return;
+
+ XtAddCallback(sb->id, XtNjumpProc,
+ gui_athena_scroll_cb_jump, (XtPointer)sb->ident);
+ XtAddCallback(sb->id, XtNscrollProc,
+ gui_athena_scroll_cb_scroll, (XtPointer)sb->ident);
+
+#ifdef FEAT_GUI_NEXTAW
+ XawScrollbarSetThumb(sb->id, 0.0, 1.0);
+#else
+ vim_XawScrollbarSetThumb(sb->id, 0.0, 1.0, 0.0);
+#endif
+}
+
+ void
+gui_mch_destroy_scrollbar(scrollbar_T *sb)
+{
+ if (sb->id != (Widget)0)
+ XtDestroyWidget(sb->id);
+}
+
+ void
+gui_mch_set_scrollbar_colors(scrollbar_T *sb)
+{
+ if (sb->id != (Widget)0)
+ XtVaSetValues(sb->id,
+ XtNforeground, gui.scroll_fg_pixel,
+ XtNbackground, gui.scroll_bg_pixel,
+ NULL);
+
+ // This is needed for the rectangle below the vertical scrollbars.
+ if (sb == &gui.bottom_sbar && vimForm != (Widget)0)
+ gui_athena_scroll_colors(vimForm);
+}
+
+/*
+ * Miscellaneous stuff:
+ */
+ Window
+gui_x11_get_wid(void)
+{
+ return XtWindow(textArea);
+}
+
+#if defined(FEAT_BROWSE) || defined(PROTO)
+/*
+ * Put up a file requester.
+ * Returns the selected name in allocated memory, or NULL for Cancel.
+ */
+ char_u *
+gui_mch_browse(
+ int saving UNUSED, // select file to write
+ char_u *title, // title for the window
+ char_u *dflt, // default name
+ char_u *ext UNUSED, // extension added
+ char_u *initdir, // initial directory, NULL for current dir
+ char_u *filter UNUSED) // file name filter
+{
+ Position x, y;
+ char_u dirbuf[MAXPATHL];
+
+ // Concatenate "initdir" and "dflt".
+ if (initdir == NULL || *initdir == NUL)
+ mch_dirname(dirbuf, MAXPATHL);
+ else if (STRLEN(initdir) + 2 < MAXPATHL)
+ STRCPY(dirbuf, initdir);
+ else
+ dirbuf[0] = NUL;
+ if (dflt != NULL && *dflt != NUL
+ && STRLEN(dirbuf) + 2 + STRLEN(dflt) < MAXPATHL)
+ {
+ add_pathsep(dirbuf);
+ STRCAT(dirbuf, dflt);
+ }
+
+ // Position the file selector just below the menubar
+ XtTranslateCoords(vimShell, (Position)0, (Position)
+#ifdef FEAT_MENU
+ gui.menu_height
+#else
+ 0
+#endif
+ , &x, &y);
+ return (char_u *)vim_SelFile(vimShell, (char *)title, (char *)dirbuf,
+ NULL, (int)x, (int)y, gui.menu_fg_pixel, gui.menu_bg_pixel,
+ gui.scroll_fg_pixel, gui.scroll_bg_pixel);
+}
+#endif
+
+#if defined(FEAT_GUI_DIALOG) || defined(PROTO)
+
+static int dialogStatus;
+static Atom dialogatom;
+
+/*
+ * Callback function for the textfield. When CR is hit this works like
+ * hitting the "OK" button, ESC like "Cancel".
+ */
+ static void
+keyhit_callback(
+ Widget w UNUSED,
+ XtPointer client_data UNUSED,
+ XEvent *event,
+ Boolean *cont UNUSED)
+{
+ char buf[2];
+
+ if (XLookupString(&(event->xkey), buf, 2, NULL, NULL) == 1)
+ {
+ if (*buf == CAR)
+ dialogStatus = 1;
+ else if (*buf == ESC)
+ dialogStatus = 0;
+ }
+}
+
+ static void
+butproc(
+ Widget w UNUSED,
+ XtPointer client_data,
+ XtPointer call_data UNUSED)
+{
+ dialogStatus = (int)(long)client_data + 1;
+}
+
+/*
+ * Function called when dialog window closed.
+ */
+ static void
+dialog_wm_handler(
+ Widget w UNUSED,
+ XtPointer client_data UNUSED,
+ XEvent *event,
+ Boolean *dum UNUSED)
+{
+ if (event->type == ClientMessage
+ && (Atom)((XClientMessageEvent *)event)->data.l[0] == dialogatom)
+ dialogStatus = 0;
+}
+
+ int
+gui_mch_dialog(
+ int type UNUSED,
+ char_u *title,
+ char_u *message,
+ char_u *buttons,
+ int dfltbutton UNUSED,
+ char_u *textfield,
+ int ex_cmd UNUSED)
+{
+ char_u *buts;
+ char_u *p, *next;
+ XtAppContext app;
+ XEvent event;
+ Position wd, hd;
+ Position wv, hv;
+ Position x, y;
+ Widget dialog;
+ Widget dialogshell;
+ Widget dialogmessage;
+ Widget dialogtextfield = 0;
+ Widget dialogButton;
+ Widget prev_dialogButton = NULL;
+ int butcount;
+ int vertical;
+
+ if (title == NULL)
+ title = (char_u *)_("Vim dialog");
+ dialogStatus = -1;
+
+ // if our pointer is currently hidden, then we should show it.
+ gui_mch_mousehide(FALSE);
+
+ // Check 'v' flag in 'guioptions': vertical button placement.
+ vertical = (vim_strchr(p_go, GO_VERTICAL) != NULL);
+
+ // The shell is created each time, to make sure it is resized properly
+ dialogshell = XtVaCreatePopupShell("dialogShell",
+ transientShellWidgetClass, vimShell,
+ XtNtitle, title,
+ NULL);
+ if (dialogshell == (Widget)0)
+ goto error;
+
+ dialog = XtVaCreateManagedWidget("dialog",
+ formWidgetClass, dialogshell,
+ XtNdefaultDistance, 20,
+ NULL);
+ if (dialog == (Widget)0)
+ goto error;
+ gui_athena_menu_colors(dialog);
+ dialogmessage = XtVaCreateManagedWidget("dialogMessage",
+ labelWidgetClass, dialog,
+ XtNlabel, message,
+ XtNtop, XtChainTop,
+ XtNbottom, XtChainTop,
+ XtNleft, XtChainLeft,
+ XtNright, XtChainLeft,
+ XtNresizable, True,
+ XtNborderWidth, 0,
+ NULL);
+ gui_athena_menu_colors(dialogmessage);
+
+ if (textfield != NULL)
+ {
+ dialogtextfield = XtVaCreateManagedWidget("textfield",
+ asciiTextWidgetClass, dialog,
+ XtNwidth, 400,
+ XtNtop, XtChainTop,
+ XtNbottom, XtChainTop,
+ XtNleft, XtChainLeft,
+ XtNright, XtChainRight,
+ XtNfromVert, dialogmessage,
+ XtNresizable, True,
+ XtNstring, textfield,
+ XtNlength, IOSIZE,
+ XtNuseStringInPlace, True,
+ XtNeditType, XawtextEdit,
+ XtNwrap, XawtextWrapNever,
+ XtNresize, XawtextResizeHeight,
+ NULL);
+ XtManageChild(dialogtextfield);
+ XtAddEventHandler(dialogtextfield, KeyPressMask, False,
+ (XtEventHandler)keyhit_callback, (XtPointer)NULL);
+ XawTextSetInsertionPoint(dialogtextfield,
+ (XawTextPosition)STRLEN(textfield));
+ XtSetKeyboardFocus(dialog, dialogtextfield);
+ }
+
+ // make a copy, so that we can insert NULs
+ buts = vim_strsave(buttons);
+ if (buts == NULL)
+ return -1;
+
+ p = buts;
+ for (butcount = 0; *p; ++butcount)
+ {
+ for (next = p; *next; ++next)
+ {
+ if (*next == DLG_HOTKEY_CHAR)
+ STRMOVE(next, next + 1);
+ if (*next == DLG_BUTTON_SEP)
+ {
+ *next++ = NUL;
+ break;
+ }
+ }
+ dialogButton = XtVaCreateManagedWidget("button",
+ commandWidgetClass, dialog,
+ XtNlabel, p,
+ XtNtop, XtChainBottom,
+ XtNbottom, XtChainBottom,
+ XtNleft, XtChainLeft,
+ XtNright, XtChainLeft,
+ XtNfromVert, textfield == NULL ? dialogmessage : dialogtextfield,
+ XtNvertDistance, vertical ? 4 : 20,
+ XtNresizable, False,
+ NULL);
+ gui_athena_menu_colors(dialogButton);
+ if (butcount > 0)
+ XtVaSetValues(dialogButton,
+ vertical ? XtNfromVert : XtNfromHoriz, prev_dialogButton,
+ NULL);
+
+ XtAddCallback(dialogButton, XtNcallback, butproc, (XtPointer)(long_u)butcount);
+ p = next;
+ prev_dialogButton = dialogButton;
+ }
+ vim_free(buts);
+
+ XtRealizeWidget(dialogshell);
+
+ // Setup for catching the close-window event, don't let it close Vim!
+ dialogatom = XInternAtom(gui.dpy, "WM_DELETE_WINDOW", False);
+ XSetWMProtocols(gui.dpy, XtWindow(dialogshell), &dialogatom, 1);
+ XtAddEventHandler(dialogshell, NoEventMask, True, dialog_wm_handler, NULL);
+
+ XtVaGetValues(dialogshell,
+ XtNwidth, &wd,
+ XtNheight, &hd,
+ NULL);
+ XtVaGetValues(vimShell,
+ XtNwidth, &wv,
+ XtNheight, &hv,
+ NULL);
+ XtTranslateCoords(vimShell,
+ (Position)((wv - wd) / 2),
+ (Position)((hv - hd) / 2),
+ &x, &y);
+ if (x < 0)
+ x = 0;
+ if (y < 0)
+ y = 0;
+ XtVaSetValues(dialogshell, XtNx, x, XtNy, y, NULL);
+
+ // Position the mouse pointer in the dialog, required for when focus
+ // follows mouse.
+ XWarpPointer(gui.dpy, (Window)0, XtWindow(dialogshell), 0, 0, 0, 0, 20, 40);
+
+
+ app = XtWidgetToApplicationContext(dialogshell);
+
+ XtPopup(dialogshell, XtGrabNonexclusive);
+
+ for (;;)
+ {
+ XtAppNextEvent(app, &event);
+ XtDispatchEvent(&event);
+ if (dialogStatus >= 0)
+ break;
+ }
+
+ XtPopdown(dialogshell);
+
+ if (textfield != NULL && dialogStatus < 0)
+ *textfield = NUL;
+
+error:
+ XtDestroyWidget(dialogshell);
+
+ return dialogStatus;
+}
+#endif
+
+#if defined(FEAT_GUI_DIALOG) || defined(FEAT_MENU)
+/*
+ * Set the colors of Widget "id" to the menu colors.
+ */
+ static void
+gui_athena_menu_colors(Widget id)
+{
+ if (gui.menu_bg_pixel != INVALCOLOR)
+ XtVaSetValues(id, XtNbackground, gui.menu_bg_pixel, NULL);
+ if (gui.menu_fg_pixel != INVALCOLOR)
+ XtVaSetValues(id, XtNforeground, gui.menu_fg_pixel, NULL);
+}
+#endif
+
+/*
+ * Set the colors of Widget "id" to the scroll colors.
+ */
+ static void
+gui_athena_scroll_colors(Widget id)
+{
+ if (gui.scroll_bg_pixel != INVALCOLOR)
+ XtVaSetValues(id, XtNbackground, gui.scroll_bg_pixel, NULL);
+ if (gui.scroll_fg_pixel != INVALCOLOR)
+ XtVaSetValues(id, XtNforeground, gui.scroll_fg_pixel, NULL);
+}