diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 13:18:03 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 13:18:03 +0000 |
commit | afce081b90c1e2c50c3507758c7558a0dfa1f33e (patch) | |
tree | 3fb840f0bd9de41b463443ddf17131a0ad77f226 /src/gui_gtk.c | |
parent | Initial commit. (diff) | |
download | vim-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_gtk.c')
-rw-r--r-- | src/gui_gtk.c | 2642 |
1 files changed, 2642 insertions, 0 deletions
diff --git a/src/gui_gtk.c b/src/gui_gtk.c new file mode 100644 index 0000000..48910b2 --- /dev/null +++ b/src/gui_gtk.c @@ -0,0 +1,2642 @@ +/* vi:set ts=8 sts=4 sw=4 noet: + * + * VIM - Vi IMproved by Bram Moolenaar + * + * 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. + */ + +/* + * Porting to GTK+ was done by: + * + * (C) 1998,1999,2000 by Marcin Dalecki <martin@dalecki.de> + * + * With GREAT support and continuous encouragements by Andy Kahn and of + * course Bram Moolenaar! + * + * Support for GTK+ 2 was added by: + * + * (C) 2002,2003 Jason Hildebrand <jason@peaceworks.ca> + * Daniel Elstner <daniel.elstner@gmx.net> + * + * Best supporting actor (He helped somewhat, aesthetically speaking): + * Maxime Romano <verbophobe@hotmail.com> + * + * Support for GTK+ 3 was added by: + * + * 2016 Kazunobu Kuriyama <kazunobu.kuriyama@gmail.com> + * + * With the help of Marius Gedminas and the word of Bram Moolenaar, + * "Let's give this some time to mature." + */ + +#include "vim.h" + +#ifdef FEAT_GUI_GTK +# include "gui_gtk_f.h" +#endif + +// GTK defines MAX and MIN, but some system header files as well. Undefine +// them and don't use them. +#ifdef MIN +# undef MIN +#endif +#ifdef MAX +# undef MAX +#endif + +#ifdef FEAT_GUI_GNOME +// Gnome redefines _() and N_(). Grrr... +# ifdef _ +# undef _ +# endif +# ifdef N_ +# undef N_ +# endif +# ifdef textdomain +# undef textdomain +# endif +# ifdef bindtextdomain +# undef bindtextdomain +# endif +# ifdef bind_textdomain_codeset +# undef bind_textdomain_codeset +# endif +# if defined(FEAT_GETTEXT) && !defined(ENABLE_NLS) +# define ENABLE_NLS // so the texts in the dialog boxes are translated +# endif +# include <gnome.h> +#endif + +#ifdef FEAT_GUI_GTK +# if GTK_CHECK_VERSION(3,0,0) +# include <gdk/gdkkeysyms-compat.h> +# else +# include <gdk/gdkkeysyms.h> +# endif +# include <gdk/gdk.h> +# ifdef MSWIN +# include <gdk/gdkwin32.h> +# else +# include <gdk/gdkx.h> +# endif + +# include <gtk/gtk.h> +#else +// define these items to be able to generate prototypes without GTK +typedef int GtkWidget; +# define gpointer int +# define guint8 int +# define GdkPixmap int +# define GdkBitmap int +# define GtkIconFactory int +# define GtkToolbar int +# define GtkAdjustment int +# define gboolean int +# define GdkEventKey int +# define CancelData int +#endif + +static void entry_activate_cb(GtkWidget *widget, gpointer data); +static void entry_changed_cb(GtkWidget *entry, GtkWidget *dialog); +static void find_replace_cb(GtkWidget *widget, gpointer data); +#if defined(FEAT_BROWSE) || defined(PROTO) +static void recent_func_log_func( + const gchar *log_domain, + GLogLevelFlags log_level, + const gchar *message, + gpointer user_data); +#endif + +#if defined(FEAT_TOOLBAR) +/* + * Table from BuiltIn## icon indices to GTK+ stock IDs. Order must exactly + * match toolbar_names[] in menu.c! All stock icons including the "vim-*" + * ones can be overridden in your gtkrc file. + */ +# if GTK_CHECK_VERSION(3,10,0) +static const char * const menu_themed_names[] = +{ + /* 00 */ "document-new", // sub. GTK_STOCK_NEW + /* 01 */ "document-open", // sub. GTK_STOCK_OPEN + /* 02 */ "document-save", // sub. GTK_STOCK_SAVE + /* 03 */ "edit-undo", // sub. GTK_STOCK_UNDO + /* 04 */ "edit-redo", // sub. GTK_STOCK_REDO + /* 05 */ "edit-cut", // sub. GTK_STOCK_CUT + /* 06 */ "edit-copy", // sub. GTK_STOCK_COPY + /* 07 */ "edit-paste", // sub. GTK_STOCK_PASTE + /* 08 */ "document-print", // sub. GTK_STOCK_PRINT + /* 09 */ "help-browser", // sub. GTK_STOCK_HELP + /* 10 */ "edit-find", // sub. GTK_STOCK_FIND +# if GTK_CHECK_VERSION(3,14,0) + // Use the file names in gui_gtk_res.xml, cutting off the extension. + // Similar changes follow. + /* 11 */ "stock_vim_save_all", + /* 12 */ "stock_vim_session_save", + /* 13 */ "stock_vim_session_new", + /* 14 */ "stock_vim_session_load", +# else + /* 11 */ "vim-save-all", + /* 12 */ "vim-session-save", + /* 13 */ "vim-session-new", + /* 14 */ "vim-session-load", +# endif + /* 15 */ "system-run", // sub. GTK_STOCK_EXECUTE + /* 16 */ "edit-find-replace", // sub. GTK_STOCK_FIND_AND_REPLACE + /* 17 */ "window-close", // sub. GTK_STOCK_CLOSE, FIXME: fuzzy +# if GTK_CHECK_VERSION(3,14,0) + /* 18 */ "stock_vim_window_maximize", + /* 19 */ "stock_vim_window_minimize", + /* 20 */ "stock_vim_window_split", + /* 21 */ "stock_vim_shell", +# else + /* 18 */ "vim-window-maximize", + /* 19 */ "vim-window-minimize", + /* 20 */ "vim-window-split", + /* 21 */ "vim-shell", +# endif + /* 22 */ "go-previous", // sub. GTK_STOCK_GO_BACK + /* 23 */ "go-next", // sub. GTK_STOCK_GO_FORWARD +# if GTK_CHECK_VERSION(3,14,0) + /* 24 */ "stock_vim_find_help", +# else + /* 24 */ "vim-find-help", +# endif + /* 25 */ "gtk-convert", // sub. GTK_STOCK_CONVERT + /* 26 */ "go-jump", // sub. GTK_STOCK_JUMP_TO +# if GTK_CHECK_VERSION(3,14,0) + /* 27 */ "stock_vim_build_tags", + /* 28 */ "stock_vim_window_split_vertical", + /* 29 */ "stock_vim_window_maximize_width", + /* 30 */ "stock_vim_window_minimize_width", +# else + /* 27 */ "vim-build-tags", + /* 28 */ "vim-window-split-vertical", + /* 29 */ "vim-window-maximize-width", + /* 30 */ "vim-window-minimize-width", +# endif + /* 31 */ "application-exit", // GTK_STOCK_QUIT +}; +# else // !GTK_CHECK_VERSION(3,10,0) +static const char * const menu_stock_ids[] = +{ + /* 00 */ GTK_STOCK_NEW, + /* 01 */ GTK_STOCK_OPEN, + /* 02 */ GTK_STOCK_SAVE, + /* 03 */ GTK_STOCK_UNDO, + /* 04 */ GTK_STOCK_REDO, + /* 05 */ GTK_STOCK_CUT, + /* 06 */ GTK_STOCK_COPY, + /* 07 */ GTK_STOCK_PASTE, + /* 08 */ GTK_STOCK_PRINT, + /* 09 */ GTK_STOCK_HELP, + /* 10 */ GTK_STOCK_FIND, + /* 11 */ "vim-save-all", + /* 12 */ "vim-session-save", + /* 13 */ "vim-session-new", + /* 14 */ "vim-session-load", + /* 15 */ GTK_STOCK_EXECUTE, + /* 16 */ GTK_STOCK_FIND_AND_REPLACE, + /* 17 */ GTK_STOCK_CLOSE, // FIXME: fuzzy + /* 18 */ "vim-window-maximize", + /* 19 */ "vim-window-minimize", + /* 20 */ "vim-window-split", + /* 21 */ "vim-shell", + /* 22 */ GTK_STOCK_GO_BACK, + /* 23 */ GTK_STOCK_GO_FORWARD, + /* 24 */ "vim-find-help", + /* 25 */ GTK_STOCK_CONVERT, + /* 26 */ GTK_STOCK_JUMP_TO, + /* 27 */ "vim-build-tags", + /* 28 */ "vim-window-split-vertical", + /* 29 */ "vim-window-maximize-width", + /* 30 */ "vim-window-minimize-width", + /* 31 */ GTK_STOCK_QUIT +}; +# endif // !GTK_CHECK_VERSION(3,10,0) + +# ifdef USE_GRESOURCE +# if !GTK_CHECK_VERSION(3,10,0) +typedef struct IconNames { + const char *icon_name; + const char *file_name; +} IconNames; + +static IconNames stock_vim_icons[] = { + { "vim-build-tags", "stock_vim_build_tags.png" }, + { "vim-find-help", "stock_vim_find_help.png" }, + { "vim-save-all", "stock_vim_save_all.png" }, + { "vim-session-load", "stock_vim_session_load.png" }, + { "vim-session-new", "stock_vim_session_new.png" }, + { "vim-session-save", "stock_vim_session_save.png" }, + { "vim-shell", "stock_vim_shell.png" }, + { "vim-window-maximize", "stock_vim_window_maximize.png" }, + { "vim-window-maximize-width", "stock_vim_window_maximize_width.png" }, + { "vim-window-minimize", "stock_vim_window_minimize.png" }, + { "vim-window-minimize-width", "stock_vim_window_minimize_width.png" }, + { "vim-window-split", "stock_vim_window_split.png" }, + { "vim-window-split-vertical", "stock_vim_window_split_vertical.png" }, + { NULL, NULL } +}; +# endif +# endif // USE_G_RESOURCE + +# ifndef USE_GRESOURCE + static void +add_stock_icon(GtkIconFactory *factory, + const char *stock_id, + const guint8 *inline_data, + int data_length) +{ + GdkPixbuf *pixbuf; + GtkIconSet *icon_set; + + pixbuf = gdk_pixbuf_new_from_inline(data_length, inline_data, FALSE, NULL); + icon_set = gtk_icon_set_new_from_pixbuf(pixbuf); + + gtk_icon_factory_add(factory, stock_id, icon_set); + + gtk_icon_set_unref(icon_set); + g_object_unref(pixbuf); +} +# endif + + static int +lookup_menu_iconfile(char_u *iconfile, char_u *dest) +{ + expand_env(iconfile, dest, MAXPATHL); + + if (mch_isFullName(dest)) + { + return vim_fexists(dest); + } + else + { + static const char suffixes[][4] = {"png", "xpm", "bmp"}; + char_u buf[MAXPATHL]; + unsigned int i; + + for (i = 0; i < G_N_ELEMENTS(suffixes); ++i) + if (gui_find_bitmap(dest, buf, (char *)suffixes[i]) == OK) + { + STRCPY(dest, buf); + return TRUE; + } + + return FALSE; + } +} + + static GtkWidget * +load_menu_iconfile(char_u *name, GtkIconSize icon_size) +{ + GtkWidget *image = NULL; +# if GTK_CHECK_VERSION(3,10,0) + int pixel_size = -1; + + switch (icon_size) + { + case GTK_ICON_SIZE_MENU: + pixel_size = 16; + break; + case GTK_ICON_SIZE_SMALL_TOOLBAR: + pixel_size = 16; + break; + case GTK_ICON_SIZE_LARGE_TOOLBAR: + pixel_size = 24; + break; + case GTK_ICON_SIZE_BUTTON: + pixel_size = 16; + break; + case GTK_ICON_SIZE_DND: + pixel_size = 32; + break; + case GTK_ICON_SIZE_DIALOG: + pixel_size = 48; + break; + case GTK_ICON_SIZE_INVALID: + // FALLTHROUGH + default: + pixel_size = 0; + break; + } + + if (pixel_size > 0 || pixel_size == -1) + { + GdkPixbuf * const pixbuf + = gdk_pixbuf_new_from_file_at_scale((const char *)name, + pixel_size, pixel_size, TRUE, NULL); + if (pixbuf != NULL) + { + image = gtk_image_new_from_pixbuf(pixbuf); + g_object_unref(pixbuf); + } + } + if (image == NULL) + image = gtk_image_new_from_icon_name("image-missing", icon_size); + + return image; +# else // !GTK_CHECK_VERSION(3,10,0) + GtkIconSet *icon_set; + GtkIconSource *icon_source; + + /* + * Rather than loading the icon directly into a GtkImage, create + * a new GtkIconSet and put it in there. This way we can easily + * scale the toolbar icons on the fly when needed. + */ + icon_set = gtk_icon_set_new(); + icon_source = gtk_icon_source_new(); + + gtk_icon_source_set_filename(icon_source, (const char *)name); + gtk_icon_set_add_source(icon_set, icon_source); + + image = gtk_image_new_from_icon_set(icon_set, icon_size); + + gtk_icon_source_free(icon_source); + gtk_icon_set_unref(icon_set); + + return image; +# endif // !GTK_CHECK_VERSION(3,10,0) +} + + static GtkWidget * +create_menu_icon(vimmenu_T *menu, GtkIconSize icon_size) +{ + GtkWidget *image = NULL; + char_u buf[MAXPATHL]; + + // First use a specified "icon=" argument. + if (menu->iconfile != NULL && lookup_menu_iconfile(menu->iconfile, buf)) + image = load_menu_iconfile(buf, icon_size); + + // If not found and not builtin specified try using the menu name. + if (image == NULL && !menu->icon_builtin + && lookup_menu_iconfile(menu->name, buf)) + image = load_menu_iconfile(buf, icon_size); + + // Still not found? Then use a builtin icon, a blank one as fallback. + if (image == NULL) + { +# if GTK_CHECK_VERSION(3,10,0) + const char *icon_name = NULL; + const int n_names = G_N_ELEMENTS(menu_themed_names); + + if (menu->iconidx >= 0 && menu->iconidx < n_names) + icon_name = menu_themed_names[menu->iconidx]; + if (icon_name == NULL) + icon_name = "image-missing"; + + image = gtk_image_new_from_icon_name(icon_name, icon_size); +# else + const char *stock_id; + const int n_ids = G_N_ELEMENTS(menu_stock_ids); + + if (menu->iconidx >= 0 && menu->iconidx < n_ids) + stock_id = menu_stock_ids[menu->iconidx]; + else + stock_id = GTK_STOCK_MISSING_IMAGE; + + image = gtk_image_new_from_stock(stock_id, icon_size); +# endif + } + + return image; +} + + static gint +toolbar_button_focus_in_event(GtkWidget *widget UNUSED, + GdkEventFocus *event UNUSED, + gpointer data UNUSED) +{ + // When we're in a GtkPlug, we don't have window focus events, only widget + // focus. To emulate stand-alone gvim, if a button gets focus (e.g., + // <Tab> into GtkPlug) immediately pass it to mainwin. + if (gtk_socket_id != 0) + gtk_widget_grab_focus(gui.drawarea); + + return TRUE; +} +#endif // FEAT_TOOLBAR + +#if defined(FEAT_TOOLBAR) || defined(PROTO) + + void +gui_gtk_register_stock_icons(void) +{ +# ifndef USE_GRESOURCE +# include "../pixmaps/stock_icons.h" + GtkIconFactory *factory; + + factory = gtk_icon_factory_new(); +# define ADD_ICON(Name, Data) add_stock_icon(factory, Name, Data, (int)sizeof(Data)) + + ADD_ICON("vim-build-tags", stock_vim_build_tags); + ADD_ICON("vim-find-help", stock_vim_find_help); + ADD_ICON("vim-save-all", stock_vim_save_all); + ADD_ICON("vim-session-load", stock_vim_session_load); + ADD_ICON("vim-session-new", stock_vim_session_new); + ADD_ICON("vim-session-save", stock_vim_session_save); + ADD_ICON("vim-shell", stock_vim_shell); + ADD_ICON("vim-window-maximize", stock_vim_window_maximize); + ADD_ICON("vim-window-maximize-width", stock_vim_window_maximize_width); + ADD_ICON("vim-window-minimize", stock_vim_window_minimize); + ADD_ICON("vim-window-minimize-width", stock_vim_window_minimize_width); + ADD_ICON("vim-window-split", stock_vim_window_split); + ADD_ICON("vim-window-split-vertical", stock_vim_window_split_vertical); + +# undef ADD_ICON + + gtk_icon_factory_add_default(factory); + g_object_unref(factory); +# else // defined(USE_GRESOURCE) + const char * const path_prefix = "/org/vim/gui/icon"; +# if GTK_CHECK_VERSION(3,14,0) + GdkScreen *screen = NULL; + GtkIconTheme *icon_theme = NULL; + + if (GTK_IS_WIDGET(gui.mainwin)) + screen = gtk_widget_get_screen(gui.mainwin); + else + screen = gdk_screen_get_default(); + icon_theme = gtk_icon_theme_get_for_screen(screen); + gtk_icon_theme_add_resource_path(icon_theme, path_prefix); +# elif GTK_CHECK_VERSION(3,0,0) + IconNames *names; + + for (names = stock_vim_icons; names->icon_name != NULL; names++) + { + char path[MAXPATHL]; + + vim_snprintf(path, MAXPATHL, "%s/%s", path_prefix, names->file_name); + GdkPixbuf *pixbuf = NULL; + pixbuf = gdk_pixbuf_new_from_resource(path, NULL); + if (pixbuf != NULL) + { + const gint size = MAX(gdk_pixbuf_get_width(pixbuf), + gdk_pixbuf_get_height(pixbuf)); + if (size > 16) + { + // An icon theme is supposed to provide fixed-size + // image files for each size, e.g., 16, 22, 24, ... + // Naturally, in contrast to GtkIconSet, GtkIconTheme + // won't prepare size variants for us out of a single + // fixed-size image. + // + // Currently, Vim provides 24x24 images only while the + // icon size on the menu and the toolbar is set to 16x16 + // by default. + // + // Resize them by ourselves until we have our own fully + // fledged icon theme. + GdkPixbuf *src = pixbuf; + pixbuf = gdk_pixbuf_scale_simple(src, + 16, 16, + GDK_INTERP_BILINEAR); + if (pixbuf == NULL) + pixbuf = src; + else + g_object_unref(src); + } + gtk_icon_theme_add_builtin_icon(names->icon_name, size, pixbuf); + g_object_unref(pixbuf); + } + } +# else // !GTK_CHECK_VERSION(3,0.0) + GtkIconFactory * const factory = gtk_icon_factory_new(); + IconNames *names; + + for (names = stock_vim_icons; names->icon_name != NULL; names++) + { + char path[MAXPATHL]; + GdkPixbuf *pixbuf; + + vim_snprintf(path, MAXPATHL, "%s/%s", path_prefix, names->file_name); + pixbuf = gdk_pixbuf_new_from_resource(path, NULL); + if (pixbuf != NULL) + { + GtkIconSet *icon_set = gtk_icon_set_new_from_pixbuf(pixbuf); + gtk_icon_factory_add(factory, names->icon_name, icon_set); + gtk_icon_set_unref(icon_set); + g_object_unref(pixbuf); + } + } + + gtk_icon_factory_add_default(factory); + g_object_unref(factory); +# endif // !GTK_CHECK_VERSION(3,0,0) +# endif // defined(USE_GRESOURCE) +} + +#endif // FEAT_TOOLBAR + +#if defined(FEAT_MENU) || defined(PROTO) + +/* + * Translate Vim's mnemonic tagging to GTK+ style and convert to UTF-8 + * if necessary. The caller must vim_free() the returned string. + * + * Input Output + * _ __ + * && & + * & _ stripped if use_mnemonic == FALSE + * <Tab> end of menu label text + */ + static char_u * +translate_mnemonic_tag(char_u *name, int use_mnemonic) +{ + char_u *buf; + char_u *psrc; + char_u *pdest; + int n_underscores = 0; + + name = CONVERT_TO_UTF8(name); + if (name == NULL) + return NULL; + + for (psrc = name; *psrc != NUL && *psrc != TAB; ++psrc) + if (*psrc == '_') + ++n_underscores; + + buf = alloc(psrc - name + n_underscores + 1); + if (buf != NULL) + { + pdest = buf; + for (psrc = name; *psrc != NUL && *psrc != TAB; ++psrc) + { + if (*psrc == '_') + { + *pdest++ = '_'; + *pdest++ = '_'; + } + else if (*psrc != '&') + { + *pdest++ = *psrc; + } + else if (*(psrc + 1) == '&') + { + *pdest++ = *psrc++; + } + else if (use_mnemonic) + { + *pdest++ = '_'; + } + } + *pdest = NUL; + } + + CONVERT_TO_UTF8_FREE(name); + return buf; +} + + static void +menu_item_new(vimmenu_T *menu, GtkWidget *parent_widget) +{ + GtkWidget *box; + char_u *text; + int use_mnemonic; + + // It would be neat to have image menu items, but that would require major + // changes to Vim's menu system. Not to mention that all the translations + // had to be updated. + menu->id = gtk_menu_item_new(); +# if GTK_CHECK_VERSION(3,2,0) + box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 20); + gtk_box_set_homogeneous(GTK_BOX(box), FALSE); +# else + box = gtk_hbox_new(FALSE, 20); +# endif + + use_mnemonic = (p_wak[0] != 'n' || !GTK_IS_MENU_BAR(parent_widget)); + text = translate_mnemonic_tag(menu->name, use_mnemonic); + + menu->label = gtk_label_new_with_mnemonic((const char *)text); + vim_free(text); + + gtk_box_pack_start(GTK_BOX(box), menu->label, FALSE, FALSE, 0); + + if (menu->actext != NULL && menu->actext[0] != NUL) + { + text = CONVERT_TO_UTF8(menu->actext); + + gtk_box_pack_end(GTK_BOX(box), + gtk_label_new((const char *)text), + FALSE, FALSE, 0); + + CONVERT_TO_UTF8_FREE(text); + } + + gtk_container_add(GTK_CONTAINER(menu->id), box); + gtk_widget_show_all(menu->id); +} + + void +gui_mch_add_menu(vimmenu_T *menu, int idx) +{ + vimmenu_T *parent; + GtkWidget *parent_widget; + + if (menu->name[0] == ']' || menu_is_popup(menu->name)) + { + menu->submenu_id = gtk_menu_new(); + return; + } + + parent = menu->parent; + + if ((parent != NULL && parent->submenu_id == NULL) + || !menu_is_menubar(menu->name)) + return; + + parent_widget = (parent != NULL) ? parent->submenu_id : gui.menubar; + menu_item_new(menu, parent_widget); + +# if !GTK_CHECK_VERSION(3,4,0) + // since the tearoff should always appear first, increment idx + if (parent != NULL && !menu_is_popup(parent->name)) + ++idx; +# endif + + gtk_menu_shell_insert(GTK_MENU_SHELL(parent_widget), menu->id, idx); + + menu->submenu_id = gtk_menu_new(); + + gtk_menu_set_accel_group(GTK_MENU(menu->submenu_id), gui.accel_group); + gtk_menu_item_set_submenu(GTK_MENU_ITEM(menu->id), menu->submenu_id); + +# if !GTK_CHECK_VERSION(3,4,0) + menu->tearoff_handle = gtk_tearoff_menu_item_new(); + if (vim_strchr(p_go, GO_TEAROFF) != NULL) + gtk_widget_show(menu->tearoff_handle); +# if GTK_CHECK_VERSION(3,0,0) + gtk_menu_shell_prepend(GTK_MENU_SHELL(menu->submenu_id), + menu->tearoff_handle); +# else + gtk_menu_prepend(GTK_MENU(menu->submenu_id), menu->tearoff_handle); +# endif +# endif +} + + static void +menu_item_activate(GtkWidget *widget UNUSED, gpointer data) +{ + gui_menu_cb((vimmenu_T *)data); +} + + void +gui_mch_add_menu_item(vimmenu_T *menu, int idx) +{ + vimmenu_T *parent; + + parent = menu->parent; + +# ifdef FEAT_TOOLBAR + if (menu_is_toolbar(parent->name)) + { + GtkToolbar *toolbar; + + toolbar = GTK_TOOLBAR(gui.toolbar); + menu->submenu_id = NULL; + + if (menu_is_separator(menu->name)) + { +# if GTK_CHECK_VERSION(3,0,0) + GtkToolItem *item = gtk_separator_tool_item_new(); + gtk_separator_tool_item_set_draw(GTK_SEPARATOR_TOOL_ITEM(item), + TRUE); + gtk_tool_item_set_expand(GTK_TOOL_ITEM(item), FALSE); + gtk_widget_show(GTK_WIDGET(item)); + + gtk_toolbar_insert(toolbar, item, idx); +# else + gtk_toolbar_insert_space(toolbar, idx); +# endif + menu->id = NULL; + } + else + { + char_u *text; + char_u *tooltip; + + text = CONVERT_TO_UTF8(menu->dname); + tooltip = CONVERT_TO_UTF8(menu->strings[MENU_INDEX_TIP]); + if (tooltip != NULL && !utf_valid_string(tooltip, NULL)) + // Invalid text, can happen when 'encoding' is changed. Avoid + // a nasty GTK error message, skip the tooltip. + CONVERT_TO_UTF8_FREE(tooltip); + +# if GTK_CHECK_VERSION(3,0,0) + { + GtkWidget *icon; + GtkToolItem *item; + + icon = create_menu_icon(menu, + gtk_toolbar_get_icon_size(toolbar)); + item = gtk_tool_button_new(icon, (const gchar *)text); + gtk_tool_item_set_tooltip_text(item, (const gchar *)tooltip); + g_signal_connect(G_OBJECT(item), "clicked", + G_CALLBACK(&menu_item_activate), menu); + gtk_widget_show_all(GTK_WIDGET(item)); + + gtk_toolbar_insert(toolbar, item, idx); + + menu->id = GTK_WIDGET(item); + } +# else + menu->id = gtk_toolbar_insert_item( + toolbar, + (const char *)text, + (const char *)tooltip, + NULL, + create_menu_icon(menu, gtk_toolbar_get_icon_size(toolbar)), + G_CALLBACK(&menu_item_activate), + menu, + idx); +# endif + + if (gtk_socket_id != 0) + g_signal_connect(G_OBJECT(menu->id), "focus-in-event", + G_CALLBACK(toolbar_button_focus_in_event), NULL); + + CONVERT_TO_UTF8_FREE(text); + CONVERT_TO_UTF8_FREE(tooltip); + } + } + else +# endif // FEAT_TOOLBAR + { + // No parent, must be a non-menubar menu + if (parent == NULL || parent->submenu_id == NULL) + return; + +# if !GTK_CHECK_VERSION(3,4,0) + // Make place for the possible tearoff handle item. Not in the popup + // menu, it doesn't have a tearoff item. + if (!menu_is_popup(parent->name)) + ++idx; +# endif + + if (menu_is_separator(menu->name)) + { + // Separator: Just add it +# if GTK_CHECK_VERSION(3,0,0) + menu->id = gtk_separator_menu_item_new(); +# else + menu->id = gtk_menu_item_new(); + gtk_widget_set_sensitive(menu->id, FALSE); +# endif + gtk_widget_show(menu->id); + gtk_menu_shell_insert(GTK_MENU_SHELL(parent->submenu_id), + menu->id, idx); + + return; + } + + // Add textual menu item. + menu_item_new(menu, parent->submenu_id); + gtk_widget_show(menu->id); + gtk_menu_shell_insert(GTK_MENU_SHELL(parent->submenu_id), + menu->id, idx); + + if (menu->id != NULL) + g_signal_connect(G_OBJECT(menu->id), "activate", + G_CALLBACK(menu_item_activate), menu); + } +} +#endif // FEAT_MENU + + + void +gui_mch_set_text_area_pos(int x, int y, int w, int h) +{ + gui_gtk_form_move_resize(GTK_FORM(gui.formwin), gui.drawarea, x, y, w, h); +} + + +#if defined(FEAT_MENU) || defined(PROTO) +/* + * Enable or disable accelerators for the toplevel menus. + */ + void +gui_gtk_set_mnemonics(int enable) +{ + vimmenu_T *menu; + char_u *name; + + FOR_ALL_MENUS(menu) + { + if (menu->id == NULL) + continue; + + name = translate_mnemonic_tag(menu->name, enable); + gtk_label_set_text_with_mnemonic(GTK_LABEL(menu->label), + (const char *)name); + vim_free(name); + } +} + +# if !GTK_CHECK_VERSION(3,4,0) + static void +recurse_tearoffs(vimmenu_T *menu, int val) +{ + for (; menu != NULL; menu = menu->next) + { + if (menu->submenu_id != NULL && menu->tearoff_handle != NULL + && menu->name[0] != ']' && !menu_is_popup(menu->name)) + { + if (val) + gtk_widget_show(menu->tearoff_handle); + else + gtk_widget_hide(menu->tearoff_handle); + } + recurse_tearoffs(menu->children, val); + } +} +# endif + +# if GTK_CHECK_VERSION(3,4,0) + void +gui_mch_toggle_tearoffs(int enable UNUSED) +{ + // Do nothing +} +# else + void +gui_mch_toggle_tearoffs(int enable) +{ + recurse_tearoffs(root_menu, enable); +} +# endif +#endif // FEAT_MENU + +#if defined(FEAT_TOOLBAR) + static int +get_menu_position(vimmenu_T *menu) +{ + vimmenu_T *node; + int idx = 0; + + for (node = menu->parent->children; node != menu; node = node->next) + { + g_return_val_if_fail(node != NULL, -1); + ++idx; + } + + return idx; +} +#endif // FEAT_TOOLBAR + + +#if defined(FEAT_TOOLBAR) || defined(PROTO) + void +gui_mch_menu_set_tip(vimmenu_T *menu) +{ + if (menu->id != NULL && menu->parent != NULL + && gui.toolbar != NULL && menu_is_toolbar(menu->parent->name)) + { + char_u *tooltip; + + tooltip = CONVERT_TO_UTF8(menu->strings[MENU_INDEX_TIP]); + if (tooltip != NULL && utf_valid_string(tooltip, NULL)) +# if GTK_CHECK_VERSION(3,0,0) + // Only set the tooltip when it's valid utf-8. + gtk_widget_set_tooltip_text(menu->id, (const gchar *)tooltip); +# else + // Only set the tooltip when it's valid utf-8. + gtk_tooltips_set_tip(GTK_TOOLBAR(gui.toolbar)->tooltips, + menu->id, (const char *)tooltip, NULL); +# endif + CONVERT_TO_UTF8_FREE(tooltip); + } +} +#endif // FEAT_TOOLBAR + + +#if defined(FEAT_MENU) || defined(PROTO) +/* + * Destroy the machine specific menu widget. + */ + void +gui_mch_destroy_menu(vimmenu_T *menu) +{ + // Don't let gtk_container_remove automatically destroy menu->id. + if (menu->id != NULL) + g_object_ref(menu->id); + + // Workaround for a spurious gtk warning in Ubuntu: "Trying to remove + // a child that doesn't believe we're its parent." + // Remove widget from gui.menubar before destroying it. + if (menu->id != NULL && gui.menubar != NULL + && gtk_widget_get_parent(menu->id) == gui.menubar) + gtk_container_remove(GTK_CONTAINER(gui.menubar), menu->id); + +# ifdef FEAT_TOOLBAR + if (menu->parent != NULL && menu_is_toolbar(menu->parent->name)) + { + if (menu_is_separator(menu->name)) +# if GTK_CHECK_VERSION(3,0,0) + { + GtkToolItem *item = NULL; + + item = gtk_toolbar_get_nth_item(GTK_TOOLBAR(gui.toolbar), + get_menu_position(menu)); + if (item != NULL) + gtk_container_remove(GTK_CONTAINER(gui.toolbar), + GTK_WIDGET(item)); + } +# else + gtk_toolbar_remove_space(GTK_TOOLBAR(gui.toolbar), + get_menu_position(menu)); +# endif + else if (menu->id != NULL) + gtk_widget_destroy(menu->id); + } + else +# endif // FEAT_TOOLBAR + { + if (menu->submenu_id != NULL) + gtk_widget_destroy(menu->submenu_id); + + if (menu->id != NULL) + gtk_widget_destroy(menu->id); + } + + if (menu->id != NULL) + g_object_unref(menu->id); + menu->submenu_id = NULL; + menu->id = NULL; +} +#endif // FEAT_MENU + + +/* + * Scrollbar stuff. + */ + void +gui_mch_set_scrollbar_thumb(scrollbar_T *sb, long val, long size, long max) +{ + if (sb->id != NULL) + { + GtkAdjustment *adjustment; + + adjustment = gtk_range_get_adjustment(GTK_RANGE(sb->id)); + + gtk_adjustment_set_lower(adjustment, 0.0); + gtk_adjustment_set_value(adjustment, val); + gtk_adjustment_set_upper(adjustment, max + 1); + gtk_adjustment_set_page_size(adjustment, size); + gtk_adjustment_set_page_increment(adjustment, + size < 3L ? 1L : size - 2L); + gtk_adjustment_set_step_increment(adjustment, 1.0); + + g_signal_handler_block(G_OBJECT(adjustment), (gulong)sb->handler_id); + +#if !GTK_CHECK_VERSION(3,18,0) + gtk_adjustment_changed(adjustment); +#endif + + g_signal_handler_unblock(G_OBJECT(adjustment), + (gulong)sb->handler_id); + } +} + + void +gui_mch_set_scrollbar_pos(scrollbar_T *sb, int x, int y, int w, int h) +{ + if (sb->id != NULL) + gui_gtk_form_move_resize(GTK_FORM(gui.formwin), sb->id, x, y, w, h); +} + + 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; +} + +/* + * Take action upon scrollbar dragging. + */ + static void +adjustment_value_changed(GtkAdjustment *adjustment, gpointer data) +{ + scrollbar_T *sb; + long value; + int dragging = FALSE; + +#ifdef FEAT_XIM + // cancel any preediting + if (im_is_preediting()) + xim_reset(); +#endif + + sb = gui_find_scrollbar((long)data); + value = gtk_adjustment_get_value(adjustment); +#if !GTK_CHECK_VERSION(3,0,0) + /* + * The dragging argument must be right for the scrollbar to work with + * closed folds. This isn't documented, hopefully this will keep on + * working in later GTK versions. + * + * FIXME: Well, it doesn't work in GTK2. :) + * HACK: Get the mouse pointer position, if it appears to be on an arrow + * button set "dragging" to FALSE. This assumes square buttons! + */ + if (sb != NULL) + { + dragging = TRUE; + + if (sb->wp != NULL) + { + int x; + int y; + GdkModifierType state; + int width; + int height; + + // vertical scrollbar: need to set "dragging" properly in case + // there are closed folds. + gdk_window_get_pointer(sb->id->window, &x, &y, &state); + gdk_window_get_size(sb->id->window, &width, &height); + if (x >= 0 && x < width && y >= 0 && y < height) + { + if (y < width) + { + // up arrow: move one (closed fold) line up + dragging = FALSE; + value = sb->wp->w_topline - 2; + } + else if (y > height - width) + { + // down arrow: move one (closed fold) line down + dragging = FALSE; + value = sb->wp->w_topline; + } + } + } + } +#endif // !GTK_CHECK_VERSION(3,0,0) + gui_drag_scrollbar(sb, value, dragging); +} + +// SBAR_VERT or SBAR_HORIZ + void +gui_mch_create_scrollbar(scrollbar_T *sb, int orient) +{ + if (orient == SBAR_HORIZ) +#if GTK_CHECK_VERSION(3,2,0) + sb->id = gtk_scrollbar_new(GTK_ORIENTATION_HORIZONTAL, NULL); +#else + sb->id = gtk_hscrollbar_new(NULL); +#endif + else if (orient == SBAR_VERT) +#if GTK_CHECK_VERSION(3,2,0) + sb->id = gtk_scrollbar_new(GTK_ORIENTATION_VERTICAL, NULL); +#else + sb->id = gtk_vscrollbar_new(NULL); +#endif + + if (sb->id != NULL) + { + GtkAdjustment *adjustment; + + gtk_widget_set_can_focus(sb->id, FALSE); + gui_gtk_form_put(GTK_FORM(gui.formwin), sb->id, 0, 0); + + adjustment = gtk_range_get_adjustment(GTK_RANGE(sb->id)); + + sb->handler_id = g_signal_connect( + G_OBJECT(adjustment), "value-changed", + G_CALLBACK(adjustment_value_changed), + GINT_TO_POINTER(sb->ident)); + gui_mch_update(); + } +} + + void +gui_mch_destroy_scrollbar(scrollbar_T *sb) +{ + if (sb->id != NULL) + { + gtk_widget_destroy(sb->id); + sb->id = NULL; + } + gui_mch_update(); +} + +#if defined(FEAT_BROWSE) || defined(PROTO) +/* + * Implementation of the file selector related stuff + */ + +#ifndef USE_FILE_CHOOSER + static void +browse_ok_cb(GtkWidget *widget UNUSED, gpointer cbdata) +{ + gui_T *vw = (gui_T *)cbdata; + + if (vw->browse_fname != NULL) + g_free(vw->browse_fname); + + vw->browse_fname = (char_u *)g_strdup(gtk_file_selection_get_filename( + GTK_FILE_SELECTION(vw->filedlg))); + gtk_widget_hide(vw->filedlg); +} + + static void +browse_cancel_cb(GtkWidget *widget UNUSED, gpointer cbdata) +{ + gui_T *vw = (gui_T *)cbdata; + + if (vw->browse_fname != NULL) + { + g_free(vw->browse_fname); + vw->browse_fname = NULL; + } + gtk_widget_hide(vw->filedlg); +} + + static gboolean +browse_destroy_cb(GtkWidget *widget UNUSED) +{ + if (gui.browse_fname != NULL) + { + g_free(gui.browse_fname); + gui.browse_fname = NULL; + } + gui.filedlg = NULL; + gtk_main_quit(); + return FALSE; +} +#endif + +/* + * Put up a file requester. + * Returns the selected name in allocated memory, or NULL for Cancel. + * saving, select file to write + * title title for the window + * dflt default name + * ext not used (extension added) + * initdir initial directory, NULL for current dir + * filter not used (file name filter) + */ + char_u * +gui_mch_browse(int saving UNUSED, + char_u *title, + char_u *dflt, + char_u *ext UNUSED, + char_u *initdir, + char_u *filter) +{ +#ifdef USE_FILE_CHOOSER +# if GTK_CHECK_VERSION(3,20,0) + GtkFileChooserNative *fc; +# else + GtkWidget *fc; +# endif +#endif + char_u dirbuf[MAXPATHL]; + guint log_handler; + const gchar *domain = "Gtk"; + + title = CONVERT_TO_UTF8(title); + + // GTK has a bug, it only works with an absolute path. + if (initdir == NULL || *initdir == NUL) + mch_dirname(dirbuf, MAXPATHL); + else if (vim_FullName(initdir, dirbuf, MAXPATHL - 2, FALSE) == FAIL) + dirbuf[0] = NUL; + // Always need a trailing slash for a directory. + add_pathsep(dirbuf); + + // If our pointer is currently hidden, then we should show it. + gui_mch_mousehide(FALSE); + + // Hack: The GTK file dialog warns when it can't access a new file, this + // makes it shut up. http://bugzilla.gnome.org/show_bug.cgi?id=664587 + log_handler = g_log_set_handler(domain, G_LOG_LEVEL_WARNING, + recent_func_log_func, NULL); + +#ifdef USE_FILE_CHOOSER + // We create the dialog each time, so that the button text can be "Open" + // or "Save" according to the action. +# if GTK_CHECK_VERSION(3,20,0) + fc = gtk_file_chooser_native_new( +# else + fc = gtk_file_chooser_dialog_new( +# endif + (const gchar *)title, + GTK_WINDOW(gui.mainwin), + saving ? GTK_FILE_CHOOSER_ACTION_SAVE + : GTK_FILE_CHOOSER_ACTION_OPEN, +# if GTK_CHECK_VERSION(3,20,0) + saving ? _("_Save") : _("_Open"), _("_Cancel")); +# else +# if GTK_CHECK_VERSION(3,10,0) + _("_Cancel"), GTK_RESPONSE_CANCEL, + saving ? _("_Save") : _("_Open"), GTK_RESPONSE_ACCEPT, +# else + GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, + saving ? GTK_STOCK_SAVE : GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT, +# endif + NULL); +# endif + gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(fc), + (const gchar *)dirbuf); + + if (filter != NULL && *filter != NUL) + { + int i = 0; + char_u *patt; + char_u *p = filter; + GtkFileFilter *gfilter; + + gfilter = gtk_file_filter_new(); + patt = alloc(STRLEN(filter)); + while (p != NULL && *p != NUL) + { + if (*p == '\n' || *p == ';' || *p == '\t') + { + STRNCPY(patt, filter, i); + patt[i] = '\0'; + if (*p == '\t') + gtk_file_filter_set_name(gfilter, (gchar *)patt); + else + { + gtk_file_filter_add_pattern(gfilter, (gchar *)patt); + if (*p == '\n') + { + gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(fc), + gfilter); + if (*(p + 1) != NUL) + gfilter = gtk_file_filter_new(); + } + } + filter = ++p; + i = 0; + } + else + { + p++; + i++; + } + } + vim_free(patt); + } + if (saving && dflt != NULL && *dflt != NUL) + gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(fc), (char *)dflt); + + gui.browse_fname = NULL; +# if GTK_CHECK_VERSION(3,20,0) + if (gtk_native_dialog_run(GTK_NATIVE_DIALOG(fc)) == GTK_RESPONSE_ACCEPT) +# else + if (gtk_dialog_run(GTK_DIALOG(fc)) == GTK_RESPONSE_ACCEPT) +#endif + { + char *filename; + + filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(fc)); + gui.browse_fname = (char_u *)g_strdup(filename); + g_free(filename); + } +# if GTK_CHECK_VERSION(3,20,0) + g_object_unref(fc); +# else + gtk_widget_destroy(GTK_WIDGET(fc)); +# endif + +#else // !USE_FILE_CHOOSER + + if (gui.filedlg == NULL) + { + GtkFileSelection *fs; // shortcut + + gui.filedlg = gtk_file_selection_new((const gchar *)title); + gtk_window_set_modal(GTK_WINDOW(gui.filedlg), TRUE); + gtk_window_set_transient_for(GTK_WINDOW(gui.filedlg), + GTK_WINDOW(gui.mainwin)); + fs = GTK_FILE_SELECTION(gui.filedlg); + + gtk_container_border_width(GTK_CONTAINER(fs), 4); + + gtk_signal_connect(GTK_OBJECT(fs->ok_button), + "clicked", GTK_SIGNAL_FUNC(browse_ok_cb), &gui); + gtk_signal_connect(GTK_OBJECT(fs->cancel_button), + "clicked", GTK_SIGNAL_FUNC(browse_cancel_cb), &gui); + // gtk_signal_connect() doesn't work for destroy, it causes a hang + gtk_signal_connect_object(GTK_OBJECT(gui.filedlg), + "destroy", GTK_SIGNAL_FUNC(browse_destroy_cb), + GTK_OBJECT(gui.filedlg)); + } + else + gtk_window_set_title(GTK_WINDOW(gui.filedlg), (const gchar *)title); + + // Concatenate "initdir" and "dflt". + if (dflt != NULL && *dflt != NUL + && STRLEN(dirbuf) + 2 + STRLEN(dflt) < MAXPATHL) + STRCAT(dirbuf, dflt); + + gtk_file_selection_set_filename(GTK_FILE_SELECTION(gui.filedlg), + (const gchar *)dirbuf); + + gtk_widget_show(gui.filedlg); + gtk_main(); +#endif // !USE_FILE_CHOOSER + g_log_remove_handler(domain, log_handler); + + CONVERT_TO_UTF8_FREE(title); + if (gui.browse_fname == NULL) + return NULL; + + // shorten the file name if possible + return vim_strsave(shorten_fname1(gui.browse_fname)); +} + +/* + * Put up a directory selector + * Returns the selected name in allocated memory, or NULL for Cancel. + * title title for the window + * dflt default name + * initdir initial directory, NULL for current dir + */ + char_u * +gui_mch_browsedir( + char_u *title, + char_u *initdir) +{ +# if defined(GTK_FILE_CHOOSER) // Only in GTK 2.4 and later. + char_u dirbuf[MAXPATHL]; + char_u *p; + GtkWidget *dirdlg; // file selection dialog + char_u *dirname = NULL; + + title = CONVERT_TO_UTF8(title); + + dirdlg = gtk_file_chooser_dialog_new( + (const gchar *)title, + GTK_WINDOW(gui.mainwin), + GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER, +# if GTK_CHECK_VERSION(3,10,0) + _("_Cancel"), GTK_RESPONSE_CANCEL, + _("_OK"), GTK_RESPONSE_ACCEPT, +# else + GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, + GTK_STOCK_OK, GTK_RESPONSE_ACCEPT, +# endif + NULL); + + CONVERT_TO_UTF8_FREE(title); + + // if our pointer is currently hidden, then we should show it. + gui_mch_mousehide(FALSE); + + // GTK appears to insist on an absolute path. + if (initdir == NULL || *initdir == NUL + || vim_FullName(initdir, dirbuf, MAXPATHL - 10, FALSE) == FAIL) + mch_dirname(dirbuf, MAXPATHL - 10); + + // Always need a trailing slash for a directory. + // Also add a dummy file name, so that we get to the directory. + add_pathsep(dirbuf); + STRCAT(dirbuf, "@zd(*&1|"); + gtk_file_chooser_set_filename(GTK_FILE_CHOOSER(dirdlg), + (const gchar *)dirbuf); + + // Run the dialog. + if (gtk_dialog_run(GTK_DIALOG(dirdlg)) == GTK_RESPONSE_ACCEPT) + dirname = (char_u *)gtk_file_chooser_get_filename( + GTK_FILE_CHOOSER(dirdlg)); + gtk_widget_destroy(dirdlg); + if (dirname == NULL) + return NULL; + + // shorten the file name if possible + p = vim_strsave(shorten_fname1(dirname)); + g_free(dirname); + return p; + +# else // !defined(GTK_FILE_CHOOSER) + // For GTK 2.2 and earlier: fall back to ordinary file selector. + return gui_mch_browse(0, title, NULL, NULL, initdir, NULL); +# endif // !defined(GTK_FILE_CHOOSER) +} + + +#endif // FEAT_BROWSE + +#if defined(FEAT_GUI_DIALOG) || defined(PROTO) + + static GtkWidget * +create_message_dialog(int type, char_u *title, char_u *message) +{ + GtkWidget *dialog; + GtkMessageType message_type; + + switch (type) + { + case VIM_ERROR: message_type = GTK_MESSAGE_ERROR; break; + case VIM_WARNING: message_type = GTK_MESSAGE_WARNING; break; + case VIM_QUESTION: message_type = GTK_MESSAGE_QUESTION; break; + default: message_type = GTK_MESSAGE_INFO; break; + } + + message = CONVERT_TO_UTF8(message); + dialog = gtk_message_dialog_new(GTK_WINDOW(gui.mainwin), + GTK_DIALOG_DESTROY_WITH_PARENT, + message_type, + GTK_BUTTONS_NONE, + "%s", (const char *)message); + CONVERT_TO_UTF8_FREE(message); + + if (title != NULL) + { + title = CONVERT_TO_UTF8(title); + gtk_window_set_title(GTK_WINDOW(dialog), (const char *)title); + CONVERT_TO_UTF8_FREE(title); + } + else if (type == VIM_GENERIC) + { + gtk_window_set_title(GTK_WINDOW(dialog), "VIM"); + } + + return dialog; +} + +/* + * Split up button_string into individual button labels by inserting + * NUL bytes. Also replace the Vim-style mnemonic accelerator prefix + * '&' with '_'. button_string must point to allocated memory! + * Return an allocated array of pointers into button_string. + */ + static char ** +split_button_string(char_u *button_string, int *n_buttons) +{ + char **array; + char_u *p; + unsigned int count = 1; + + for (p = button_string; *p != NUL; ++p) + if (*p == DLG_BUTTON_SEP) + ++count; + + array = ALLOC_MULT(char *, count + 1); + count = 0; + + if (array != NULL) + { + array[count++] = (char *)button_string; + for (p = button_string; *p != NUL; ) + { + if (*p == DLG_BUTTON_SEP) + { + *p++ = NUL; + array[count++] = (char *)p; + } + else if (*p == DLG_HOTKEY_CHAR) + *p++ = '_'; + else + MB_PTR_ADV(p); + } + array[count] = NULL; // currently not relied upon, but doesn't hurt + } + + *n_buttons = count; + return array; +} + + static char ** +split_button_translation(const char *message) +{ + char **buttons = NULL; + char_u *str; + int n_buttons = 0; + int n_expected = 1; + + for (str = (char_u *)message; *str != NUL; ++str) + if (*str == DLG_BUTTON_SEP) + ++n_expected; + + str = (char_u *)_(message); + if (str != NULL) + { + if (output_conv.vc_type != CONV_NONE) + str = string_convert(&output_conv, str, NULL); + else + str = vim_strsave(str); + + if (str != NULL) + buttons = split_button_string(str, &n_buttons); + } + /* + * Uh-oh... this should never ever happen. But we don't wanna crash + * if the translation is broken, thus fall back to the untranslated + * buttons string in case of emergency. + */ + if (buttons == NULL || n_buttons != n_expected) + { + vim_free(buttons); + vim_free(str); + buttons = NULL; + str = vim_strsave((char_u *)message); + + if (str != NULL) + buttons = split_button_string(str, &n_buttons); + if (buttons == NULL) + vim_free(str); + } + + return buttons; +} + + static int +button_equal(const char *a, const char *b) +{ + while (*a != '\0' && *b != '\0') + { + if (*a == '_' && *++a == '\0') + break; + if (*b == '_' && *++b == '\0') + break; + + if (g_unichar_tolower(g_utf8_get_char(a)) + != g_unichar_tolower(g_utf8_get_char(b))) + return FALSE; + + a = g_utf8_next_char(a); + b = g_utf8_next_char(b); + } + + return (*a == '\0' && *b == '\0'); +} + + static void +dialog_add_buttons(GtkDialog *dialog, char_u *button_string) +{ + char **ok; + char **ync; // "yes no cancel" + char **buttons; + int n_buttons = 0; + int idx; + + button_string = vim_strsave(button_string); // must be writable + if (button_string == NULL) + return; + + // Check 'v' flag in 'guioptions': vertical button placement. + if (vim_strchr(p_go, GO_VERTICAL) != NULL) + { +# if GTK_CHECK_VERSION(3,0,0) + // Add GTK+ 3 code if necessary. + // N.B. GTK+ 3 doesn't allow you to access vbox and action_area via + // the C API. +# else + GtkWidget *vbutton_box; + + vbutton_box = gtk_vbutton_box_new(); + gtk_widget_show(vbutton_box); + gtk_box_pack_end(GTK_BOX(GTK_DIALOG(dialog)->vbox), + vbutton_box, TRUE, FALSE, 0); + // Overrule the "action_area" value, hopefully this works... + GTK_DIALOG(dialog)->action_area = vbutton_box; +# endif + } + + /* + * Yes this is ugly, I don't particularly like it either. But doing it + * this way has the compelling advantage that translations need not to + * be touched at all. See below what 'ok' and 'ync' are used for. + */ + ok = split_button_translation(N_("&Ok")); + ync = split_button_translation(N_("&Yes\n&No\n&Cancel")); + buttons = split_button_string(button_string, &n_buttons); + + /* + * Yes, the buttons are in reversed order to match the GNOME 2 desktop + * environment. Don't hit me -- it's all about consistency. + * Well, apparently somebody changed his mind: with GTK 2.2.4 it works the + * other way around... + */ + for (idx = 1; idx <= n_buttons; ++idx) + { + char *label; + char_u *label8; + + label = buttons[idx - 1]; + /* + * Perform some guesswork to find appropriate stock items for the + * buttons. We have to compare with a sample of the translated + * button string to get things right. Yes, this is hackish :/ + * + * But even the common button labels aren't necessarily translated, + * since anyone can create their own dialogs using Vim functions. + * Thus we have to check for those too. + */ + if (ok != NULL && ync != NULL) // almost impossible to fail + { +# if GTK_CHECK_VERSION(3,10,0) + if (button_equal(label, ok[0])) label = _("OK"); + else if (button_equal(label, ync[0])) label = _("Yes"); + else if (button_equal(label, ync[1])) label = _("No"); + else if (button_equal(label, ync[2])) label = _("Cancel"); + else if (button_equal(label, "Ok")) label = _("OK"); + else if (button_equal(label, "Yes")) label = _("Yes"); + else if (button_equal(label, "No")) label = _("No"); + else if (button_equal(label, "Cancel")) label = _("Cancel"); +# else + if (button_equal(label, ok[0])) label = GTK_STOCK_OK; + else if (button_equal(label, ync[0])) label = GTK_STOCK_YES; + else if (button_equal(label, ync[1])) label = GTK_STOCK_NO; + else if (button_equal(label, ync[2])) label = GTK_STOCK_CANCEL; + else if (button_equal(label, "Ok")) label = GTK_STOCK_OK; + else if (button_equal(label, "Yes")) label = GTK_STOCK_YES; + else if (button_equal(label, "No")) label = GTK_STOCK_NO; + else if (button_equal(label, "Cancel")) label = GTK_STOCK_CANCEL; +# endif + } + label8 = CONVERT_TO_UTF8((char_u *)label); + gtk_dialog_add_button(dialog, (const gchar *)label8, idx); + CONVERT_TO_UTF8_FREE(label8); + } + + if (ok != NULL) + vim_free(*ok); + if (ync != NULL) + vim_free(*ync); + vim_free(ok); + vim_free(ync); + vim_free(buttons); + vim_free(button_string); +} + +/* + * Allow mnemonic accelerators to be activated without pressing <Alt>. + * I'm not sure if it's a wise idea to do this. However, the old GTK+ 1.2 + * GUI used to work this way, and I consider the impact on UI consistency + * low enough to justify implementing this as a special Vim feature. + */ +typedef struct _DialogInfo +{ + int ignore_enter; // no default button, ignore "Enter" + int noalt; // accept accelerators without Alt + GtkDialog *dialog; // Widget of the dialog +} DialogInfo; + + static gboolean +dialog_key_press_event_cb(GtkWidget *widget, GdkEventKey *event, gpointer data) +{ + DialogInfo *di = (DialogInfo *)data; + + // Ignore hitting Enter (or Space) when there is no default button. + if (di->ignore_enter && (event->keyval == GDK_Return + || event->keyval == ' ')) + return TRUE; + else // A different key was pressed, return to normal behavior + di->ignore_enter = FALSE; + + // Close the dialog when hitting "Esc". + if (event->keyval == GDK_Escape) + { + gtk_dialog_response(di->dialog, GTK_RESPONSE_REJECT); + return TRUE; + } + + if (di->noalt + && (event->state & gtk_accelerator_get_default_mod_mask()) == 0) + { + return gtk_window_mnemonic_activate( + GTK_WINDOW(widget), event->keyval, + gtk_window_get_mnemonic_modifier(GTK_WINDOW(widget))); + } + + return FALSE; // continue emission +} + + int +gui_mch_dialog(int type, // type of dialog + char_u *title, // title of dialog + char_u *message, // message text + char_u *buttons, // names of buttons + int def_but, // default button + char_u *textfield, // text for textfield or NULL + int ex_cmd UNUSED) +{ + GtkWidget *dialog; + GtkWidget *entry = NULL; + char_u *text; + int response; + DialogInfo dialoginfo; + + dialog = create_message_dialog(type, title, message); + dialoginfo.dialog = GTK_DIALOG(dialog); + dialog_add_buttons(GTK_DIALOG(dialog), buttons); + + if (textfield != NULL) + { + GtkWidget *alignment; + + entry = gtk_entry_new(); + gtk_widget_show(entry); + + // Make Enter work like pressing OK. + gtk_entry_set_activates_default(GTK_ENTRY(entry), TRUE); + + text = CONVERT_TO_UTF8(textfield); + gtk_entry_set_text(GTK_ENTRY(entry), (const char *)text); + CONVERT_TO_UTF8_FREE(text); + +# if GTK_CHECK_VERSION(3,14,0) + gtk_widget_set_halign(GTK_WIDGET(entry), GTK_ALIGN_CENTER); + gtk_widget_set_valign(GTK_WIDGET(entry), GTK_ALIGN_CENTER); + gtk_widget_set_hexpand(GTK_WIDGET(entry), TRUE); + gtk_widget_set_vexpand(GTK_WIDGET(entry), TRUE); + + alignment = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); +# else + alignment = gtk_alignment_new((float)0.5, (float)0.5, + (float)1.0, (float)1.0); +# endif + gtk_container_add(GTK_CONTAINER(alignment), entry); + gtk_container_set_border_width(GTK_CONTAINER(alignment), 5); + gtk_widget_show(alignment); + +# if GTK_CHECK_VERSION(3,0,0) + { + GtkWidget * const vbox + = gtk_dialog_get_content_area(GTK_DIALOG(dialog)); + gtk_box_pack_start(GTK_BOX(vbox), + alignment, TRUE, FALSE, 0); + } +# else + gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), + alignment, TRUE, FALSE, 0); +# endif + dialoginfo.noalt = FALSE; + } + else + dialoginfo.noalt = TRUE; + + // Allow activation of mnemonic accelerators without pressing <Alt> when + // there is no textfield. Handle pressing Esc. + g_signal_connect(G_OBJECT(dialog), "key-press-event", + G_CALLBACK(&dialog_key_press_event_cb), &dialoginfo); + + if (def_but > 0) + { + gtk_dialog_set_default_response(GTK_DIALOG(dialog), def_but); + dialoginfo.ignore_enter = FALSE; + } + else + // No default button, ignore pressing Enter. + dialoginfo.ignore_enter = TRUE; + + // Show the mouse pointer if it's currently hidden. + gui_mch_mousehide(FALSE); + + response = gtk_dialog_run(GTK_DIALOG(dialog)); + + // GTK_RESPONSE_NONE means the dialog was programmatically destroyed. + if (response != GTK_RESPONSE_NONE) + { + if (response == GTK_RESPONSE_ACCEPT) // Enter pressed + response = def_but; + if (textfield != NULL) + { + text = (char_u *)gtk_entry_get_text(GTK_ENTRY(entry)); + text = CONVERT_FROM_UTF8(text); + + vim_strncpy(textfield, text, IOSIZE - 1); + + CONVERT_FROM_UTF8_FREE(text); + } + gtk_widget_destroy(dialog); + } + + return response > 0 ? response : 0; +} + +#endif // FEAT_GUI_DIALOG + + +#if defined(FEAT_MENU) || defined(PROTO) + + void +gui_mch_show_popupmenu(vimmenu_T *menu) +{ +# if defined(FEAT_XIM) + /* + * Append a submenu for selecting an input method. This is + * currently the only way to switch input methods at runtime. + */ +# if !GTK_CHECK_VERSION(3,10,0) + if (xic != NULL && g_object_get_data(G_OBJECT(menu->submenu_id), + "vim-has-im-menu") == NULL) + { + GtkWidget *menuitem; + GtkWidget *submenu; + char_u *name; + + menuitem = gtk_separator_menu_item_new(); + gtk_widget_show(menuitem); + gtk_menu_shell_append(GTK_MENU_SHELL(menu->submenu_id), menuitem); + + name = (char_u *)_("Input _Methods"); + name = CONVERT_TO_UTF8(name); + menuitem = gtk_menu_item_new_with_mnemonic((const char *)name); + CONVERT_TO_UTF8_FREE(name); + gtk_widget_show(menuitem); + + submenu = gtk_menu_new(); + gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), submenu); + gtk_menu_shell_append(GTK_MENU_SHELL(menu->submenu_id), menuitem); + + gtk_im_multicontext_append_menuitems(GTK_IM_MULTICONTEXT(xic), + GTK_MENU_SHELL(submenu)); + g_object_set_data(G_OBJECT(menu->submenu_id), + "vim-has-im-menu", GINT_TO_POINTER(TRUE)); + } +# endif +# endif // FEAT_XIM + +# if GTK_CHECK_VERSION(3,22,2) + { + GdkEventButton trigger; + + // A pseudo event to have gtk_menu_popup_at_pointer() work. Since the + // function calculates the popup menu position on the basis of the + // actual pointer position when it is invoked, the fields x, y, x_root + // and y_root are set to zero for convenience. + trigger.type = GDK_BUTTON_PRESS; + trigger.window = gtk_widget_get_window(gui.drawarea); + trigger.send_event = FALSE; + trigger.time = gui.event_time; + trigger.x = 0.0; + trigger.y = 0.0; + trigger.axes = NULL; + trigger.state = 0; + trigger.button = 3; + trigger.device = NULL; + trigger.x_root = 0.0; + trigger.y_root = 0.0; + + gtk_menu_popup_at_pointer(GTK_MENU(menu->submenu_id), + (GdkEvent *)&trigger); + } +#else + gtk_menu_popup(GTK_MENU(menu->submenu_id), + NULL, NULL, + (GtkMenuPositionFunc)NULL, NULL, + 3U, gui.event_time); +#endif +} + +// Ugly global variable to pass "mouse_pos" flag from gui_make_popup() to +// popup_menu_position_func(). +static int popup_mouse_pos; + +/* + * Menu position callback; used by gui_make_popup() to place the menu + * at the current text cursor position. + * + * Note: The push_in output argument seems to affect scrolling of huge + * menus that don't fit on the screen. Leave it at the default for now. + */ + static void +popup_menu_position_func(GtkMenu *menu UNUSED, + gint *x, gint *y, + gboolean *push_in UNUSED, + gpointer user_data UNUSED) +{ + gdk_window_get_origin(gtk_widget_get_window(gui.drawarea), x, y); + + if (popup_mouse_pos) + { + int mx, my; + + gui_mch_getmouse(&mx, &my); + *x += mx; + *y += my; + } + else if (curwin != NULL && gui.drawarea != NULL && + gtk_widget_get_window(gui.drawarea) != NULL) + { + // Find the cursor position in the current window + *x += FILL_X(curwin->w_wincol + curwin->w_wcol + 1) + 1; + *y += FILL_Y(W_WINROW(curwin) + curwin->w_wrow + 1) + 1; + } +} + + void +gui_make_popup(char_u *path_name, int mouse_pos) +{ + vimmenu_T *menu; + + popup_mouse_pos = mouse_pos; + + menu = gui_find_menu(path_name); + + if (menu != NULL && menu->submenu_id != NULL) + { +# if GTK_CHECK_VERSION(3,22,2) + GdkWindow * const win = gtk_widget_get_window(gui.drawarea); + GdkEventButton trigger; + + // A pseudo event to have gtk_menu_popup_at_*() functions work. Since + // the position where the menu pops up is automatically adjusted by + // the functions, none of the fields x, y, x_root and y_root has to be + // set to a specific value here; therefore, they are set to zero for + // convenience. + trigger.type = GDK_BUTTON_PRESS; + trigger.window = win; + trigger.send_event = FALSE; + trigger.time = GDK_CURRENT_TIME; + trigger.x = 0.0; + trigger.y = 0.0; + trigger.axes = NULL; + trigger.state = 0; + trigger.button = 0; + trigger.device = NULL; + trigger.x_root = 0.0; + trigger.y_root = 0.0; + + if (mouse_pos) + gtk_menu_popup_at_pointer(GTK_MENU(menu->submenu_id), + (GdkEvent *)&trigger); + else + { + gint origin_x, origin_y; + GdkRectangle rect = { 0, 0, 0, 0 }; + + gdk_window_get_origin(win, &origin_x, &origin_y); + popup_menu_position_func(NULL, &rect.x, &rect.y, NULL, NULL); + + rect.x -= origin_x; + rect.y -= origin_y; + + gtk_menu_popup_at_rect(GTK_MENU(menu->submenu_id), + win, + &rect, + GDK_GRAVITY_SOUTH_EAST, + GDK_GRAVITY_NORTH_WEST, + (GdkEvent *)&trigger); + } +# else + gtk_menu_popup(GTK_MENU(menu->submenu_id), + NULL, NULL, + &popup_menu_position_func, NULL, + 0U, (guint32)GDK_CURRENT_TIME); +# endif + } +} + +#endif // FEAT_MENU + + +/* + * We don't create it twice. + */ + +typedef struct _SharedFindReplace +{ + GtkWidget *dialog; // the main dialog widget + GtkWidget *wword; // 'Whole word only' check button + GtkWidget *mcase; // 'Match case' check button + GtkWidget *up; // search direction 'Up' radio button + GtkWidget *down; // search direction 'Down' radio button + GtkWidget *what; // 'Find what' entry text widget + GtkWidget *with; // 'Replace with' entry text widget + GtkWidget *find; // 'Find Next' action button + GtkWidget *replace; // 'Replace With' action button + GtkWidget *all; // 'Replace All' action button +} SharedFindReplace; + +static SharedFindReplace find_widgets = {NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL}; +static SharedFindReplace repl_widgets = {NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL}; + + static int +find_key_press_event( + GtkWidget *widget UNUSED, + GdkEventKey *event, + SharedFindReplace *frdp) +{ + // If the user is holding one of the key modifiers we will just bail out, + // thus preserving the possibility of normal focus traversal. + if (event->state & (GDK_CONTROL_MASK | GDK_SHIFT_MASK)) + return FALSE; + + // the Escape key synthesizes a cancellation action + if (event->keyval == GDK_Escape) + { + gtk_widget_hide(frdp->dialog); + + return TRUE; + } + + // It would be delightful if it where possible to do search history + // operations on the K_UP and K_DOWN keys here. + + return FALSE; +} + + static GtkWidget * +#if GTK_CHECK_VERSION(3,10,0) +create_image_button(const char *stock_id UNUSED, + const char *label) +#else +create_image_button(const char *stock_id, + const char *label) +#endif +{ + char_u *text; + GtkWidget *box; + GtkWidget *alignment; + GtkWidget *button; + + text = CONVERT_TO_UTF8((char_u *)label); + +#if GTK_CHECK_VERSION(3,2,0) + box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 3); + gtk_box_set_homogeneous(GTK_BOX(box), FALSE); +#else + box = gtk_hbox_new(FALSE, 3); +#endif +#if !GTK_CHECK_VERSION(3,10,0) + if (stock_id != NULL) + gtk_box_pack_start(GTK_BOX(box), + gtk_image_new_from_stock(stock_id, GTK_ICON_SIZE_BUTTON), + FALSE, FALSE, 0); +#endif + gtk_box_pack_start(GTK_BOX(box), + gtk_label_new((const char *)text), + FALSE, FALSE, 0); + + CONVERT_TO_UTF8_FREE(text); + +#if GTK_CHECK_VERSION(3,14,0) + gtk_widget_set_halign(GTK_WIDGET(box), GTK_ALIGN_CENTER); + gtk_widget_set_valign(GTK_WIDGET(box), GTK_ALIGN_CENTER); + gtk_widget_set_hexpand(GTK_WIDGET(box), TRUE); + gtk_widget_set_vexpand(GTK_WIDGET(box), TRUE); + + alignment = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); +#else + alignment = gtk_alignment_new((float)0.5, (float)0.5, + (float)0.0, (float)0.0); +#endif + gtk_container_add(GTK_CONTAINER(alignment), box); + gtk_widget_show_all(alignment); + + button = gtk_button_new(); + gtk_container_add(GTK_CONTAINER(button), alignment); + + return button; +} + +/* + * This is currently only used by find_replace_dialog_create(), and + * I'd really like to keep it at that. In other words: don't spread + * this nasty hack all over the code. Think twice. + */ + static const char * +convert_localized_message(char_u **buffer, const char *message) +{ + if (output_conv.vc_type == CONV_NONE) + return message; + + vim_free(*buffer); + *buffer = string_convert(&output_conv, (char_u *)message, NULL); + + return (const char *)*buffer; +} + +/* + * Returns the number of characters in GtkEntry. + */ + static unsigned long +entry_get_text_length(GtkEntry *entry) +{ + g_return_val_if_fail(entry != NULL, 0); + g_return_val_if_fail(GTK_IS_ENTRY(entry) == TRUE, 0); + +#if GTK_CHECK_VERSION(2,18,0) + // 2.18 introduced a new object GtkEntryBuffer to handle text data for + // GtkEntry instead of letting each instance of the latter have its own + // storage for that. The code below is almost identical to the + // implementation of gtk_entry_get_text_length() for the versions >= 2.18. + return gtk_entry_buffer_get_length(gtk_entry_get_buffer(entry)); +#elif GTK_CHECK_VERSION(2,14,0) + // 2.14 introduced a new function to avoid memory management bugs which can + // happen when gtk_entry_get_text() is used without due care and attention. + return gtk_entry_get_text_length(entry); +#else + // gtk_entry_get_text() returns the pointer to the storage allocated + // internally by the widget. Accordingly, use the one with great care: + // Don't free it nor modify the contents it points to; call the function + // every time you need the pointer since its value may have been changed + // by the widget. + return g_utf8_strlen(gtk_entry_get_text(entry), -1); +#endif +} + + static void +find_replace_dialog_create(char_u *arg, int do_replace) +{ + GtkWidget *hbox; // main top down box + GtkWidget *actionarea; + GtkWidget *table; + GtkWidget *tmp; + GtkWidget *vbox; + gboolean sensitive; + SharedFindReplace *frdp; + char_u *entry_text; + int wword = FALSE; + int mcase = !p_ic; + char_u *conv_buffer = NULL; +# define CONV(message) convert_localized_message(&conv_buffer, (message)) + + frdp = (do_replace) ? (&repl_widgets) : (&find_widgets); + + // Get the search string to use. + entry_text = get_find_dialog_text(arg, &wword, &mcase); + + if (entry_text != NULL && output_conv.vc_type != CONV_NONE) + { + char_u *old_text = entry_text; + entry_text = string_convert(&output_conv, entry_text, NULL); + vim_free(old_text); + } + + /* + * If the dialog already exists, just raise it. + */ + if (frdp->dialog) + { + if (entry_text != NULL) + { + gtk_entry_set_text(GTK_ENTRY(frdp->what), (char *)entry_text); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(frdp->wword), + (gboolean)wword); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(frdp->mcase), + (gboolean)mcase); + } + gtk_window_present(GTK_WINDOW(frdp->dialog)); + + // For :promptfind dialog, always give keyboard focus to 'what' entry. + // For :promptrepl dialog, give it to 'with' entry if 'what' has an + // non-empty entry; otherwise, to 'what' entry. + gtk_widget_grab_focus(frdp->what); + if (do_replace && entry_get_text_length(GTK_ENTRY(frdp->what)) > 0) + gtk_widget_grab_focus(frdp->with); + + vim_free(entry_text); + return; + } + + frdp->dialog = gtk_dialog_new(); +#if GTK_CHECK_VERSION(3,0,0) + // Nothing equivalent to gtk_dialog_set_has_separator() in GTK+ 3. +#else + gtk_dialog_set_has_separator(GTK_DIALOG(frdp->dialog), FALSE); +#endif + gtk_window_set_transient_for(GTK_WINDOW(frdp->dialog), GTK_WINDOW(gui.mainwin)); + gtk_window_set_destroy_with_parent(GTK_WINDOW(frdp->dialog), TRUE); + + if (do_replace) + { + gtk_window_set_title(GTK_WINDOW(frdp->dialog), + CONV(_("VIM - Search and Replace..."))); + } + else + { + gtk_window_set_title(GTK_WINDOW(frdp->dialog), + CONV(_("VIM - Search..."))); + } + +#if GTK_CHECK_VERSION(3,2,0) + hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); + gtk_box_set_homogeneous(GTK_BOX(hbox), FALSE); +#else + hbox = gtk_hbox_new(FALSE, 0); +#endif + gtk_container_set_border_width(GTK_CONTAINER(hbox), 10); +#if GTK_CHECK_VERSION(3,0,0) + { + GtkWidget * const dialog_vbox + = gtk_dialog_get_content_area(GTK_DIALOG(frdp->dialog)); + gtk_container_add(GTK_CONTAINER(dialog_vbox), hbox); + } +#else + gtk_container_add(GTK_CONTAINER(GTK_DIALOG(frdp->dialog)->vbox), hbox); +#endif + + if (do_replace) +#if GTK_CHECK_VERSION(3,4,0) + table = gtk_grid_new(); +#else + table = gtk_table_new(1024, 4, FALSE); +#endif + else +#if GTK_CHECK_VERSION(3,4,0) + table = gtk_grid_new(); +#else + table = gtk_table_new(1024, 3, FALSE); +#endif + gtk_box_pack_start(GTK_BOX(hbox), table, TRUE, TRUE, 0); + gtk_container_set_border_width(GTK_CONTAINER(table), 4); + + tmp = gtk_label_new(CONV(_("Find what:"))); +#if GTK_CHECK_VERSION(3,16,0) + gtk_label_set_xalign(GTK_LABEL(tmp), 0.0); + gtk_label_set_yalign(GTK_LABEL(tmp), 0.5); +#elif GTK_CHECK_VERSION(3,14,0) + { + GValue align_val = G_VALUE_INIT; + + g_value_init(&align_val, G_TYPE_FLOAT); + + g_value_set_float(&align_val, 0.0); + g_object_set_property(G_OBJECT(tmp), "xalign", &align_val); + + g_value_set_float(&align_val, 0.5); + g_object_set_property(G_OBJECT(tmp), "yalign", &align_val); + + g_value_unset(&align_val); + } +#else + gtk_misc_set_alignment(GTK_MISC(tmp), (gfloat)0.0, (gfloat)0.5); +#endif +#if GTK_CHECK_VERSION(3,4,0) + gtk_grid_attach(GTK_GRID(table), tmp, 0, 0, 2, 1); +#else + gtk_table_attach(GTK_TABLE(table), tmp, 0, 1, 0, 1, + GTK_FILL, GTK_EXPAND, 2, 2); +#endif + frdp->what = gtk_entry_new(); + sensitive = (entry_text != NULL && entry_text[0] != NUL); + if (entry_text != NULL) + gtk_entry_set_text(GTK_ENTRY(frdp->what), (char *)entry_text); + g_signal_connect(G_OBJECT(frdp->what), "changed", + G_CALLBACK(entry_changed_cb), frdp->dialog); + g_signal_connect_after(G_OBJECT(frdp->what), "key-press-event", + G_CALLBACK(find_key_press_event), + (gpointer) frdp); +#if GTK_CHECK_VERSION(3,4,0) + gtk_grid_attach(GTK_GRID(table), frdp->what, 2, 0, 5, 1); +#else + gtk_table_attach(GTK_TABLE(table), frdp->what, 1, 1024, 0, 1, + GTK_EXPAND | GTK_FILL, GTK_EXPAND, 2, 2); +#endif + + if (do_replace) + { + tmp = gtk_label_new(CONV(_("Replace with:"))); +#if GTK_CHECK_VERSION(3,16,0) + gtk_label_set_xalign(GTK_LABEL(tmp), 0.0); + gtk_label_set_yalign(GTK_LABEL(tmp), 0.5); +#elif GTK_CHECK_VERSION(3,14,0) + { + GValue align_val = G_VALUE_INIT; + + g_value_init(&align_val, G_TYPE_FLOAT); + + g_value_set_float(&align_val, 0.0); + g_object_set_property(G_OBJECT(tmp), "xalign", &align_val); + + g_value_set_float(&align_val, 0.5); + g_object_set_property(G_OBJECT(tmp), "yalign", &align_val); + + g_value_unset(&align_val); + } +#else + gtk_misc_set_alignment(GTK_MISC(tmp), (gfloat)0.0, (gfloat)0.5); +#endif +#if GTK_CHECK_VERSION(3,4,0) + gtk_grid_attach(GTK_GRID(table), tmp, 0, 1, 2, 1); +#else + gtk_table_attach(GTK_TABLE(table), tmp, 0, 1, 1, 2, + GTK_FILL, GTK_EXPAND, 2, 2); +#endif + frdp->with = gtk_entry_new(); + g_signal_connect(G_OBJECT(frdp->with), "activate", + G_CALLBACK(find_replace_cb), + GINT_TO_POINTER(FRD_R_FINDNEXT)); + g_signal_connect_after(G_OBJECT(frdp->with), "key-press-event", + G_CALLBACK(find_key_press_event), + (gpointer) frdp); +#if GTK_CHECK_VERSION(3,4,0) + gtk_grid_attach(GTK_GRID(table), frdp->with, 2, 1, 5, 1); +#else + gtk_table_attach(GTK_TABLE(table), frdp->with, 1, 1024, 1, 2, + GTK_EXPAND | GTK_FILL, GTK_EXPAND, 2, 2); +#endif + + /* + * Make the entry activation only change the input focus onto the + * with item. + */ + g_signal_connect(G_OBJECT(frdp->what), "activate", + G_CALLBACK(entry_activate_cb), frdp->with); + } + else + { + /* + * Make the entry activation do the search. + */ + g_signal_connect(G_OBJECT(frdp->what), "activate", + G_CALLBACK(find_replace_cb), + GINT_TO_POINTER(FRD_FINDNEXT)); + } + + // whole word only button + frdp->wword = gtk_check_button_new_with_label(CONV(_("Match whole word only"))); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(frdp->wword), + (gboolean)wword); + if (do_replace) +#if GTK_CHECK_VERSION(3,4,0) + gtk_grid_attach(GTK_GRID(table), frdp->wword, 0, 2, 5, 1); +#else + gtk_table_attach(GTK_TABLE(table), frdp->wword, 0, 1023, 2, 3, + GTK_FILL, GTK_EXPAND, 2, 2); +#endif + else +#if GTK_CHECK_VERSION(3,4,0) + gtk_grid_attach(GTK_GRID(table), frdp->wword, 0, 3, 5, 1); +#else + gtk_table_attach(GTK_TABLE(table), frdp->wword, 0, 1023, 1, 2, + GTK_FILL, GTK_EXPAND, 2, 2); +#endif + + // match case button + frdp->mcase = gtk_check_button_new_with_label(CONV(_("Match case"))); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(frdp->mcase), + (gboolean)mcase); + if (do_replace) +#if GTK_CHECK_VERSION(3,4,0) + gtk_grid_attach(GTK_GRID(table), frdp->mcase, 0, 3, 5, 1); +#else + gtk_table_attach(GTK_TABLE(table), frdp->mcase, 0, 1023, 3, 4, + GTK_FILL, GTK_EXPAND, 2, 2); +#endif + else +#if GTK_CHECK_VERSION(3,4,0) + gtk_grid_attach(GTK_GRID(table), frdp->mcase, 0, 4, 5, 1); +#else + gtk_table_attach(GTK_TABLE(table), frdp->mcase, 0, 1023, 2, 3, + GTK_FILL, GTK_EXPAND, 2, 2); +#endif + + tmp = gtk_frame_new(CONV(_("Direction"))); + if (do_replace) +#if GTK_CHECK_VERSION(3,4,0) + gtk_grid_attach(GTK_GRID(table), tmp, 5, 2, 2, 4); +#else + gtk_table_attach(GTK_TABLE(table), tmp, 1023, 1024, 2, 4, + GTK_FILL, GTK_FILL, 2, 2); +#endif + else +#if GTK_CHECK_VERSION(3,4,0) + gtk_grid_attach(GTK_GRID(table), tmp, 5, 2, 1, 3); +#else + gtk_table_attach(GTK_TABLE(table), tmp, 1023, 1024, 1, 3, + GTK_FILL, GTK_FILL, 2, 2); +#endif +#if GTK_CHECK_VERSION(3,2,0) + vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0); + gtk_box_set_homogeneous(GTK_BOX(vbox), FALSE); +#else + vbox = gtk_vbox_new(FALSE, 0); +#endif + gtk_container_set_border_width(GTK_CONTAINER(vbox), 0); + gtk_container_add(GTK_CONTAINER(tmp), vbox); + + // 'Up' and 'Down' buttons + frdp->up = gtk_radio_button_new_with_label(NULL, CONV(_("Up"))); + gtk_box_pack_start(GTK_BOX(vbox), frdp->up, TRUE, TRUE, 0); + frdp->down = gtk_radio_button_new_with_label( + gtk_radio_button_get_group(GTK_RADIO_BUTTON(frdp->up)), + CONV(_("Down"))); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(frdp->down), TRUE); + gtk_container_set_border_width(GTK_CONTAINER(vbox), 2); + gtk_box_pack_start(GTK_BOX(vbox), frdp->down, TRUE, TRUE, 0); + + // vbox to hold the action buttons +#if GTK_CHECK_VERSION(3,2,0) + actionarea = gtk_button_box_new(GTK_ORIENTATION_VERTICAL); +#else + actionarea = gtk_vbutton_box_new(); +#endif + gtk_container_set_border_width(GTK_CONTAINER(actionarea), 2); + gtk_box_pack_end(GTK_BOX(hbox), actionarea, FALSE, FALSE, 0); + + // 'Find Next' button +#if GTK_CHECK_VERSION(3,10,0) + frdp->find = create_image_button(NULL, _("Find Next")); +#else + frdp->find = create_image_button(GTK_STOCK_FIND, _("Find Next")); +#endif + gtk_widget_set_sensitive(frdp->find, sensitive); + + g_signal_connect(G_OBJECT(frdp->find), "clicked", + G_CALLBACK(find_replace_cb), + (do_replace) ? GINT_TO_POINTER(FRD_R_FINDNEXT) + : GINT_TO_POINTER(FRD_FINDNEXT)); + + gtk_widget_set_can_default(frdp->find, TRUE); + gtk_box_pack_start(GTK_BOX(actionarea), frdp->find, FALSE, FALSE, 0); + gtk_widget_grab_default(frdp->find); + + if (do_replace) + { + // 'Replace' button +#if GTK_CHECK_VERSION(3,10,0) + frdp->replace = create_image_button(NULL, _("Replace")); +#else + frdp->replace = create_image_button(GTK_STOCK_CONVERT, _("Replace")); +#endif + gtk_widget_set_sensitive(frdp->replace, sensitive); + gtk_widget_set_can_default(frdp->find, TRUE); + gtk_box_pack_start(GTK_BOX(actionarea), frdp->replace, FALSE, FALSE, 0); + g_signal_connect(G_OBJECT(frdp->replace), "clicked", + G_CALLBACK(find_replace_cb), + GINT_TO_POINTER(FRD_REPLACE)); + + // 'Replace All' button +#if GTK_CHECK_VERSION(3,10,0) + frdp->all = create_image_button(NULL, _("Replace All")); +#else + frdp->all = create_image_button(GTK_STOCK_CONVERT, _("Replace All")); +#endif + gtk_widget_set_sensitive(frdp->all, sensitive); + gtk_widget_set_can_default(frdp->all, TRUE); + gtk_box_pack_start(GTK_BOX(actionarea), frdp->all, FALSE, FALSE, 0); + g_signal_connect(G_OBJECT(frdp->all), "clicked", + G_CALLBACK(find_replace_cb), + GINT_TO_POINTER(FRD_REPLACEALL)); + } + + // 'Cancel' button +#if GTK_CHECK_VERSION(3,10,0) + tmp = gtk_button_new_with_mnemonic(_("_Close")); +#else + tmp = gtk_button_new_from_stock(GTK_STOCK_CLOSE); +#endif + gtk_widget_set_can_default(tmp, TRUE); + gtk_box_pack_end(GTK_BOX(actionarea), tmp, FALSE, FALSE, 0); + g_signal_connect_swapped(G_OBJECT(tmp), + "clicked", G_CALLBACK(gtk_widget_hide), + G_OBJECT(frdp->dialog)); + g_signal_connect_swapped(G_OBJECT(frdp->dialog), + "delete-event", G_CALLBACK(gtk_widget_hide_on_delete), + G_OBJECT(frdp->dialog)); + +#if GTK_CHECK_VERSION(3,2,0) + tmp = gtk_separator_new(GTK_ORIENTATION_VERTICAL); +#else + tmp = gtk_vseparator_new(); +#endif + gtk_box_pack_end(GTK_BOX(hbox), tmp, FALSE, FALSE, 10); + + // Suppress automatic show of the unused action area +#if GTK_CHECK_VERSION(3,0,0) +# if !GTK_CHECK_VERSION(3,12,0) + gtk_widget_hide(gtk_dialog_get_action_area(GTK_DIALOG(frdp->dialog))); +# endif +#else + gtk_widget_hide(GTK_DIALOG(frdp->dialog)->action_area); +#endif + gtk_widget_show_all(hbox); + gtk_widget_show(frdp->dialog); + + vim_free(entry_text); + vim_free(conv_buffer); +#undef CONV +} + + void +gui_mch_find_dialog(exarg_T *eap) +{ + if (gui.in_use) + find_replace_dialog_create(eap->arg, FALSE); +} + + void +gui_mch_replace_dialog(exarg_T *eap) +{ + if (gui.in_use) + find_replace_dialog_create(eap->arg, TRUE); +} + +/* + * Callback for actions of the find and replace dialogs + */ + static void +find_replace_cb(GtkWidget *widget UNUSED, gpointer data) +{ + int flags; + char_u *find_text; + char_u *repl_text; + gboolean direction_down; + SharedFindReplace *sfr; + + flags = (int)(long)data; // avoid a lint warning here + + // Get the search/replace strings from the dialog + if (flags == FRD_FINDNEXT) + { + repl_text = NULL; + sfr = &find_widgets; + } + else + { + repl_text = (char_u *)gtk_entry_get_text(GTK_ENTRY(repl_widgets.with)); + sfr = &repl_widgets; + } + + find_text = (char_u *)gtk_entry_get_text(GTK_ENTRY(sfr->what)); + direction_down = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(sfr->down)); + + if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(sfr->wword))) + flags |= FRD_WHOLE_WORD; + if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(sfr->mcase))) + flags |= FRD_MATCH_CASE; + + repl_text = CONVERT_FROM_UTF8(repl_text); + find_text = CONVERT_FROM_UTF8(find_text); + gui_do_findrepl(flags, find_text, repl_text, direction_down); + CONVERT_FROM_UTF8_FREE(repl_text); + CONVERT_FROM_UTF8_FREE(find_text); +} + +/* + * our usual callback function + */ + static void +entry_activate_cb(GtkWidget *widget UNUSED, gpointer data) +{ + gtk_widget_grab_focus(GTK_WIDGET(data)); +} + +/* + * Syncing the find/replace dialogs on the fly is utterly useless crack, + * and causes nothing but problems. Please tell me a use case for which + * you'd need both a find dialog and a find/replace one at the same time, + * without being able to actually use them separately since they're syncing + * all the time. I don't think it's worthwhile to fix this nonsense, + * particularly evil incarnation of braindeadness, whatever; I'd much rather + * see it extinguished from this planet. Thanks for listening. Sorry. + */ + static void +entry_changed_cb(GtkWidget * entry, GtkWidget * dialog) +{ + const gchar *entry_text; + gboolean nonempty; + + entry_text = gtk_entry_get_text(GTK_ENTRY(entry)); + + if (!entry_text) + return; // shouldn't happen + + nonempty = (entry_text[0] != '\0'); + + if (dialog == find_widgets.dialog) + gtk_widget_set_sensitive(find_widgets.find, nonempty); + + if (dialog == repl_widgets.dialog) + { + gtk_widget_set_sensitive(repl_widgets.find, nonempty); + gtk_widget_set_sensitive(repl_widgets.replace, nonempty); + gtk_widget_set_sensitive(repl_widgets.all, nonempty); + } +} + +/* + * ":helpfind" + */ + void +ex_helpfind(exarg_T *eap UNUSED) +{ + // This will fail when menus are not loaded. Well, it's only for + // backwards compatibility anyway. + do_cmdline_cmd((char_u *)"emenu ToolBar.FindHelp"); +} + +#if defined(FEAT_BROWSE) || defined(PROTO) + static void +recent_func_log_func(const gchar *log_domain UNUSED, + GLogLevelFlags log_level UNUSED, + const gchar *message UNUSED, + gpointer user_data UNUSED) +{ + // We just want to suppress the warnings. + // http://bugzilla.gnome.org/show_bug.cgi?id=664587 +} +#endif |