diff options
Diffstat (limited to 'src/gui_gtk.c')
-rw-r--r-- | src/gui_gtk.c | 3035 |
1 files changed, 3035 insertions, 0 deletions
diff --git a/src/gui_gtk.c b/src/gui_gtk.c new file mode 100644 index 0000000000..0d552c05f5 --- /dev/null +++ b/src/gui_gtk.c @@ -0,0 +1,3035 @@ +/* vi:set ts=8 sts=4 sw=4: + * + * 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 <dalecki@evision.ag> + * + * 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> + */ + +#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 + +#include "vim.h" + +#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 +# if defined(FEAT_GETTEXT) && !defined(ENABLE_NLS) +# define ENABLE_NLS /* so the texts in the dialog boxes are translated */ +# endif +# include <gnome.h> +#endif + +#if defined(FEAT_GUI_DIALOG) && !defined(HAVE_GTK2) +# include "../pixmaps/alert.xpm" +# include "../pixmaps/error.xpm" +# include "../pixmaps/generic.xpm" +# include "../pixmaps/info.xpm" +# include "../pixmaps/quest.xpm" +#endif + +#if defined(FEAT_TOOLBAR) && !defined(HAVE_GTK2) +/* + * Icons used by the toolbar code. + */ +#include "../pixmaps/tb_new.xpm" +#include "../pixmaps/tb_open.xpm" +#include "../pixmaps/tb_close.xpm" +#include "../pixmaps/tb_save.xpm" +#include "../pixmaps/tb_print.xpm" +#include "../pixmaps/tb_cut.xpm" +#include "../pixmaps/tb_copy.xpm" +#include "../pixmaps/tb_paste.xpm" +#include "../pixmaps/tb_find.xpm" +#include "../pixmaps/tb_find_next.xpm" +#include "../pixmaps/tb_find_prev.xpm" +#include "../pixmaps/tb_find_help.xpm" +#include "../pixmaps/tb_exit.xpm" +#include "../pixmaps/tb_undo.xpm" +#include "../pixmaps/tb_redo.xpm" +#include "../pixmaps/tb_help.xpm" +#include "../pixmaps/tb_macro.xpm" +#include "../pixmaps/tb_make.xpm" +#include "../pixmaps/tb_save_all.xpm" +#include "../pixmaps/tb_jump.xpm" +#include "../pixmaps/tb_ctags.xpm" +#include "../pixmaps/tb_load_session.xpm" +#include "../pixmaps/tb_save_session.xpm" +#include "../pixmaps/tb_new_session.xpm" +#include "../pixmaps/tb_blank.xpm" +#include "../pixmaps/tb_maximize.xpm" +#include "../pixmaps/tb_split.xpm" +#include "../pixmaps/tb_minimize.xpm" +#include "../pixmaps/tb_shell.xpm" +#include "../pixmaps/tb_replace.xpm" +#include "../pixmaps/tb_vsplit.xpm" +#include "../pixmaps/tb_maxwidth.xpm" +#include "../pixmaps/tb_minwidth.xpm" +#endif /* FEAT_TOOLBAR && !HAVE_GTK2 */ + +#ifdef FEAT_GUI_GTK +# include <gdk/gdkkeysyms.h> +# include <gdk/gdk.h> +# ifdef WIN3264 +# 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 + +#ifdef HAVE_GTK2 +/* + * Convenience macros to convert from 'encoding' to 'termencoding' and + * vice versa. If no conversion is necessary the passed-in pointer is + * returned as is, without allocating any memory. Thus additional _FREE() + * macros are provided. The _FREE() macros also set the pointer to NULL, + * in order to avoid bugs due to illegal memory access only happening if + * 'encoding' != utf-8... + * + * Defining these macros as pure expressions looks a bit tricky but + * avoids depending on the context of the macro expansion. One of the + * rare occasions where the comma operator comes in handy :) + * + * Note: Do NOT keep the result around when handling control back to + * the main Vim! The user could change 'encoding' at any time. + */ +# define CONVERT_TO_UTF8(String) \ + ((output_conv.vc_type == CONV_NONE || (String) == NULL) \ + ? (String) \ + : string_convert(&output_conv, (String), NULL)) + +# define CONVERT_TO_UTF8_FREE(String) \ + ((String) = ((output_conv.vc_type == CONV_NONE) \ + ? (char_u *)NULL \ + : (vim_free(String), (char_u *)NULL))) + +# define CONVERT_FROM_UTF8(String) \ + ((input_conv.vc_type == CONV_NONE || (String) == NULL) \ + ? (String) \ + : string_convert(&input_conv, (String), NULL)) + +# define CONVERT_FROM_UTF8_FREE(String) \ + ((String) = ((input_conv.vc_type == CONV_NONE) \ + ? (char_u *)NULL \ + : (vim_free(String), (char_u *)NULL))) + +#endif /* HAVE_GTK2 */ + +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_TOOLBAR) && defined(HAVE_GTK2) +/* + * 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. + */ +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 +}; + + 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); +} + + 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; + 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; +} + + 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) + { + 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); + } + + return image; +} + +#endif /* FEAT_TOOLBAR && HAVE_GTK2 */ + +#if (defined(FEAT_TOOLBAR) && defined(HAVE_GTK2)) || defined(PROTO) + + void +gui_gtk_register_stock_icons(void) +{ +# 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); +} + +#endif /* FEAT_TOOLBAR && HAVE_GTK2 */ + + +/* + * Only use accelerators when gtk_menu_ensure_uline_accel_group() is + * available, which is in version 1.2.1. That was the first version where + * accelerators properly worked (according to the change log). + */ +#ifdef GTK_CHECK_VERSION +# if GTK_CHECK_VERSION(1, 2, 1) +# define GTK_USE_ACCEL +# endif +#endif + +#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; + +# ifdef HAVE_GTK2 + name = CONVERT_TO_UTF8(name); +# endif + if (name == NULL) + return NULL; + + for (psrc = name; *psrc != NUL && *psrc != TAB; ++psrc) + if (*psrc == '_') + ++n_underscores; + + buf = alloc((unsigned)(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; + } + +# ifdef HAVE_GTK2 + CONVERT_TO_UTF8_FREE(name); +# endif + return buf; +} + +# ifdef HAVE_GTK2 + + 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(); + box = gtk_hbox_new(FALSE, 20); + + 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); +} + +# else /* !HAVE_GTK2 */ + +/* + * Create a highly customized menu item by hand instead of by using: + * + * gtk_menu_item_new_with_label(menu->dname); + * + * This is neccessary, since there is no other way in GTK+ 1 to get the + * not automatically parsed accellerator stuff right. + */ + static void +menu_item_new(vimmenu_T *menu, GtkWidget *parent_widget) +{ + GtkWidget *widget; + GtkWidget *bin; + GtkWidget *label; + char_u *name; + guint accel_key; + + widget = gtk_widget_new(GTK_TYPE_MENU_ITEM, + "GtkWidget::visible", TRUE, + "GtkWidget::sensitive", TRUE, + /* "GtkWidget::parent", parent->submenu_id, */ + NULL); + bin = gtk_widget_new(GTK_TYPE_HBOX, + "GtkWidget::visible", TRUE, + "GtkWidget::parent", widget, + "GtkBox::spacing", 16, + NULL); + label = gtk_widget_new(GTK_TYPE_ACCEL_LABEL, + "GtkWidget::visible", TRUE, + "GtkWidget::parent", bin, + "GtkAccelLabel::accel_widget", widget, + "GtkMisc::xalign", 0.0, + NULL); + menu->label = label; + + if (menu->actext) + gtk_widget_new(GTK_TYPE_LABEL, + "GtkWidget::visible", TRUE, + "GtkWidget::parent", bin, + "GtkLabel::label", menu->actext, + "GtkMisc::xalign", 1.0, + NULL); + + /* + * Translate VIM accelerator tagging into GTK+'s. Note that since GTK uses + * underscores as the accelerator key, we need to add an additional under- + * score for each understore that appears in the menu name. + */ +# ifdef GTK_USE_ACCEL + name = translate_mnemonic_tag(menu->name, + (p_wak[0] != 'n' || !GTK_IS_MENU_BAR(parent_widget))); +# else + name = translate_mnemonic_tag(menu->name, FALSE); +# endif + + /* let GTK do its thing */ + accel_key = gtk_label_parse_uline(GTK_LABEL(label), (const char *)name); + vim_free(name); + +# ifdef GTK_USE_ACCEL + /* Don't add accelator if 'winaltkeys' is "no". */ + if (accel_key != GDK_VoidSymbol) + { + if (GTK_IS_MENU_BAR(parent_widget)) + { + if (*p_wak != 'n') + gtk_widget_add_accelerator(widget, + "activate_item", + gui.accel_group, + accel_key, GDK_MOD1_MASK, + (GtkAccelFlags)0); + } + else + { + gtk_widget_add_accelerator(widget, + "activate_item", + gtk_menu_ensure_uline_accel_group(GTK_MENU(parent_widget)), + accel_key, 0, + (GtkAccelFlags)0); + } + } +# endif /* GTK_USE_ACCEL */ + + menu->id = widget; +} + +# endif /* !HAVE_GTK2 */ + + 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); + + /* since the tearoff should always appear first, increment idx */ + if (parent != NULL && !menu_is_popup(parent->name)) + ++idx; + + gtk_menu_shell_insert(GTK_MENU_SHELL(parent_widget), menu->id, idx); + +#ifndef HAVE_GTK2 + /* + * The "Help" menu is a special case, and should be placed at the far + * right hand side of the menu-bar. It's detected by its high priority. + * + * Right-aligning "Help" is considered bad UI design nowadays. + * Thus lets disable this for GTK+ 2 to match the environment. + */ + if (parent == NULL && menu->priority >= 9999) + gtk_menu_item_right_justify(GTK_MENU_ITEM(menu->id)); +#endif + + 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); + + menu->tearoff_handle = gtk_tearoff_menu_item_new(); + if (vim_strchr(p_go, GO_TEAROFF) != NULL) + gtk_widget_show(menu->tearoff_handle); + gtk_menu_prepend(GTK_MENU(menu->submenu_id), menu->tearoff_handle); +} + +/*ARGSUSED*/ + static void +menu_item_activate(GtkWidget *widget, gpointer data) +{ + gui_menu_cb((vimmenu_T *)data); + +# ifndef HAVE_GTK2 + /* Work around a bug in GTK+ 1: we don't seem to get a focus-in + * event after clicking a menu item shown via :popup. */ + if (!gui.in_focus) + gui_focus_change(TRUE); +# endif + + /* make sure the menu action is taken immediately */ + if (gtk_main_level() > 0) + gtk_main_quit(); +} + +# if defined(FEAT_TOOLBAR) && !defined(HAVE_GTK2) +/* + * These are the pixmaps used for the default buttons. + * Order must exactly match toolbar_names[] in menu.c! + */ +static char **(built_in_pixmaps[]) = +{ + tb_new_xpm, + tb_open_xpm, + tb_save_xpm, + tb_undo_xpm, + tb_redo_xpm, + tb_cut_xpm, + tb_copy_xpm, + tb_paste_xpm, + tb_print_xpm, + tb_help_xpm, + tb_find_xpm, + tb_save_all_xpm, + tb_save_session_xpm, + tb_new_session_xpm, + tb_load_session_xpm, + tb_macro_xpm, + tb_replace_xpm, + tb_close_xpm, + tb_maximize_xpm, + tb_minimize_xpm, + tb_split_xpm, + tb_shell_xpm, + tb_find_prev_xpm, + tb_find_next_xpm, + tb_find_help_xpm, + tb_make_xpm, + tb_jump_xpm, + tb_ctags_xpm, + tb_vsplit_xpm, + tb_maxwidth_xpm, + tb_minwidth_xpm, + tb_exit_xpm +}; + +/* + * creates a blank pixmap using tb_blank + */ + static void +pixmap_create_from_xpm(char **xpm, GdkPixmap **pixmap, GdkBitmap **mask) +{ + *pixmap = gdk_pixmap_colormap_create_from_xpm_d( + NULL, + gtk_widget_get_colormap(gui.mainwin), + mask, + NULL, + xpm); +} + +/* + * creates a pixmap by using a built-in number + */ + static void +pixmap_create_by_num(int pixmap_num, GdkPixmap **pixmap, GdkBitmap **mask) +{ + if (pixmap_num >= 0 && pixmap_num < (sizeof(built_in_pixmaps) + / sizeof(built_in_pixmaps[0]))) + pixmap_create_from_xpm(built_in_pixmaps[pixmap_num], pixmap, mask); +} + +/* + * Creates a pixmap by using the pixmap "name" found in 'runtimepath'/bitmaps/ + */ + static void +pixmap_create_by_dir(char_u *name, GdkPixmap **pixmap, GdkBitmap **mask) +{ + char_u full_pathname[MAXPATHL + 1]; + + if (gui_find_bitmap(name, full_pathname, "xpm") == OK) + *pixmap = gdk_pixmap_colormap_create_from_xpm( + NULL, + gtk_widget_get_colormap(gui.mainwin), + mask, + &gui.mainwin->style->bg[GTK_STATE_NORMAL], + (const char *)full_pathname); +} + +/* + * Creates a pixmap by using the pixmap "fname". + */ + static void +pixmap_create_from_file(char_u *fname, GdkPixmap **pixmap, GdkBitmap **mask) +{ + *pixmap = gdk_pixmap_colormap_create_from_xpm( + NULL, + gtk_widget_get_colormap(gui.mainwin), + mask, + &gui.mainwin->style->bg[GTK_STATE_NORMAL], + (const char *)fname); +} + +# endif /* FEAT_TOOLBAR && !HAVE_GTK2 */ + + 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)) + { + gtk_toolbar_insert_space(toolbar, idx); + menu->id = NULL; + } + else + { +# ifdef HAVE_GTK2 + char_u *text; + char_u *tooltip; + + text = CONVERT_TO_UTF8(menu->dname); + tooltip = CONVERT_TO_UTF8(menu->strings[MENU_INDEX_TIP]); + + 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); + + CONVERT_TO_UTF8_FREE(text); + CONVERT_TO_UTF8_FREE(tooltip); + +# else /* !HAVE_GTK2 */ + + GdkPixmap *pixmap = NULL; + GdkBitmap *mask = NULL; + + /* First try user specified bitmap, then builtin, the a blank. */ + if (menu->iconfile != NULL) + { + char_u buf[MAXPATHL + 1]; + + gui_find_iconfile(menu->iconfile, buf, "xpm"); + pixmap_create_from_file(buf, &pixmap, &mask); + } + if (pixmap == NULL && !menu->icon_builtin) + pixmap_create_by_dir(menu->name, &pixmap, &mask); + if (pixmap == NULL && menu->iconidx >= 0) + pixmap_create_by_num(menu->iconidx, &pixmap, &mask); + if (pixmap == NULL) + pixmap_create_from_xpm(tb_blank_xpm, &pixmap, &mask); + if (pixmap == NULL) + return; /* should at least have blank pixmap, but if not... */ + + menu->id = gtk_toolbar_insert_item( + toolbar, + (char *)(menu->dname), + (char *)(menu->strings[MENU_INDEX_TIP]), + (char *)(menu->dname), + gtk_pixmap_new(pixmap, mask), + GTK_SIGNAL_FUNC(menu_item_activate), + (gpointer)menu, + idx); +# endif /* !HAVE_GTK2 */ + } + } + else +# endif /* FEAT_TOOLBAR */ + { + /* No parent, must be a non-menubar menu */ + if (parent->submenu_id == NULL) + return; + + /* Make place for the possible tearoff handle item. Not in the popup + * menu, it doesn't have a tearoff item. */ + if (parent != NULL && !menu_is_popup(parent->name)) + ++idx; + + if (menu_is_separator(menu->name)) + { + /* Separator: Just add it */ + menu->id = gtk_menu_item_new(); + gtk_widget_set_sensitive(menu->id, FALSE); + gtk_widget_show(menu->id); + gtk_menu_insert(GTK_MENU(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_insert(GTK_MENU(parent->submenu_id), menu->id, idx); + + if (menu->id != NULL) + gtk_signal_connect(GTK_OBJECT(menu->id), "activate", + GTK_SIGNAL_FUNC(menu_item_activate), menu); + } +} +#endif /* FEAT_MENU */ + + + void +gui_mch_set_text_area_pos(int x, int y, int w, int h) +{ + gtk_form_move_resize(GTK_FORM(gui.formwin), gui.drawarea, x, y, w, h); +} + + +#if defined(FEAT_MENU) || defined(PROTO) +/* + * Enable or disable accelators for the toplevel menus. + */ + void +gui_gtk_set_mnemonics(int enable) +{ + vimmenu_T *menu; + char_u *name; +# if !defined(HAVE_GTK2) && defined(GTK_USE_ACCEL) + guint accel_key; +# endif + + for (menu = root_menu; menu != NULL; menu = menu->next) + { + if (menu->id == NULL) + continue; + +# if defined(HAVE_GTK2) + name = translate_mnemonic_tag(menu->name, enable); + gtk_label_set_text_with_mnemonic(GTK_LABEL(menu->label), + (const char *)name); + vim_free(name); +# elif defined(GTK_USE_ACCEL) + name = translate_mnemonic_tag(menu->name, TRUE); + if (name != NULL) + { + accel_key = gtk_label_parse_uline(GTK_LABEL(menu->label), + (const char *)name); + if (accel_key != GDK_VoidSymbol) + gtk_widget_remove_accelerator(menu->id, gui.accel_group, + accel_key, GDK_MOD1_MASK); + if (enable && accel_key != GDK_VoidSymbol) + gtk_widget_add_accelerator(menu->id, "activate_item", + gui.accel_group, + accel_key, GDK_MOD1_MASK, + (GtkAccelFlags)0); + vim_free(name); + } + if (!enable) + { + name = translate_mnemonic_tag(menu->name, FALSE); + gtk_label_parse_uline(GTK_LABEL(menu->label), (const char *)name); + vim_free(name); + } +# endif + } +} + + 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); + } +} + + void +gui_mch_toggle_tearoffs(int enable) +{ + recurse_tearoffs(root_menu, enable); +} +#endif /* FEAT_MENU */ + + +#if defined(FEAT_TOOLBAR) && !defined(HAVE_GTK2) +/* + * Seems like there's a hole in the GTK Toolbar API: there's no provision for + * removing an item from the toolbar. Therefore I need to resort to going + * really deeply into the internal widget structures. + * + * <danielk> I'm not sure the statement above is true -- at least with + * GTK+ 2 one can just call gtk_widget_destroy() and be done with it. + * It is true though that you couldn't remove space items before GTK+ 2 + * (without digging into the internals that is). But the code below + * doesn't seem to handle those either. Well, it's obsolete anyway. + */ + static void +toolbar_remove_item_by_text(GtkToolbar *tb, const char *text) +{ + GtkContainer *container; + GList *childl; + GtkToolbarChild *gtbc; + + g_return_if_fail(tb != NULL); + g_return_if_fail(GTK_IS_TOOLBAR(tb)); + container = GTK_CONTAINER(&tb->container); + + for (childl = tb->children; childl; childl = childl->next) + { + gtbc = (GtkToolbarChild *)childl->data; + + if (gtbc->type != GTK_TOOLBAR_CHILD_SPACE + && strcmp(GTK_LABEL(gtbc->label)->label, text) == 0) + { + gboolean was_visible; + + was_visible = GTK_WIDGET_VISIBLE(gtbc->widget); + gtk_widget_unparent(gtbc->widget); + + tb->children = g_list_remove_link(tb->children, childl); + g_free(gtbc); + g_list_free(childl); + tb->num_children--; + + if (was_visible && GTK_WIDGET_VISIBLE(container)) + gtk_widget_queue_resize(GTK_WIDGET(container)); + + break; + } + } +} +#endif /* FEAT_TOOLBAR && !HAVE_GTK2 */ + + +#if defined(FEAT_TOOLBAR) && defined(HAVE_GTK2) + static int +get_menu_position(vimmenu_T *menu) +{ + vimmenu_T *node; + int index = 0; + + for (node = menu->parent->children; node != menu; node = node->next) + { + g_return_val_if_fail(node != NULL, -1); + ++index; + } + + return index; +} +#endif /* FEAT_TOOLBAR && HAVE_GTK2 */ + + +#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; + +# ifdef HAVE_GTK2 + tooltip = CONVERT_TO_UTF8(menu->strings[MENU_INDEX_TIP]); +# else + tooltip = menu->strings[MENU_INDEX_TIP]; +# endif + gtk_tooltips_set_tip(GTK_TOOLBAR(gui.toolbar)->tooltips, + menu->id, (const char *)tooltip, NULL); +# ifdef HAVE_GTK2 + CONVERT_TO_UTF8_FREE(tooltip); +# endif + } +} +#endif /* FEAT_TOOLBAR */ + + +#if defined(FEAT_MENU) || defined(PROTO) +/* + * Destroy the machine specific menu widget. + */ + void +gui_mch_destroy_menu(vimmenu_T *menu) +{ +# ifdef FEAT_TOOLBAR + if (menu->parent != NULL && menu_is_toolbar(menu->parent->name)) + { +# ifdef HAVE_GTK2 + if (menu_is_separator(menu->name)) + gtk_toolbar_remove_space(GTK_TOOLBAR(gui.toolbar), + get_menu_position(menu)); + else if (menu->id != NULL) + gtk_widget_destroy(menu->id); +# else + toolbar_remove_item_by_text(GTK_TOOLBAR(gui.toolbar), + (const char *)menu->dname); +# endif + } + else +# endif /* FEAT_TOOLBAR */ + { + if (menu->submenu_id != NULL) + gtk_widget_destroy(menu->submenu_id); + + if (menu->id != NULL) + gtk_widget_destroy(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)); + + adjustment->lower = 0.0; + adjustment->value = val; + adjustment->upper = max + 1; + adjustment->page_size = size; + adjustment->page_increment = size < 3L ? 1L : size - 2L; + adjustment->step_increment = 1.0; + +#ifdef HAVE_GTK2 + g_signal_handler_block(GTK_OBJECT(adjustment), + (gulong)sb->handler_id); +#else + gtk_signal_handler_block(GTK_OBJECT(adjustment), + (guint)sb->handler_id); +#endif + gtk_adjustment_changed(adjustment); +#ifdef HAVE_GTK2 + g_signal_handler_unblock(GTK_OBJECT(adjustment), + (gulong)sb->handler_id); +#else + gtk_signal_handler_unblock(GTK_OBJECT(adjustment), + (guint)sb->handler_id); +#endif + } +} + + void +gui_mch_set_scrollbar_pos(scrollbar_T *sb, int x, int y, int w, int h) +{ + if (sb->id != NULL) + gtk_form_move_resize(GTK_FORM(gui.formwin), sb->id, x, y, w, h); +} + +/* + * 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 = (long)adjustment->value; + /* + * 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) + { +#ifdef HAVE_GTK2 + 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; + } + } + } +#else + dragging = (GTK_RANGE(sb->id)->scroll_type == GTK_SCROLL_NONE); +#endif + } + + gui_drag_scrollbar(sb, value, dragging); + + if (gtk_main_level() > 0) + gtk_main_quit(); +} + +/* SBAR_VERT or SBAR_HORIZ */ + void +gui_mch_create_scrollbar(scrollbar_T *sb, int orient) +{ + if (orient == SBAR_HORIZ) + sb->id = gtk_hscrollbar_new(NULL); + else if (orient == SBAR_VERT) + sb->id = gtk_vscrollbar_new(NULL); + + if (sb->id != NULL) + { + GtkAdjustment *adjustment; + + GTK_WIDGET_UNSET_FLAGS(sb->id, GTK_CAN_FOCUS); + gtk_form_put(GTK_FORM(gui.formwin), sb->id, 0, 0); + + adjustment = gtk_range_get_adjustment(GTK_RANGE(sb->id)); + + sb->handler_id = gtk_signal_connect( + GTK_OBJECT(adjustment), "value_changed", + GTK_SIGNAL_FUNC(adjustment_value_changed), + GINT_TO_POINTER(sb->ident)); + gui_mch_update(); + } +} + +#if defined(FEAT_WINDOWS) || defined(PROTO) + void +gui_mch_destroy_scrollbar(scrollbar_T *sb) +{ + if (sb->id != NULL) + { + gtk_widget_destroy(sb->id); + sb->id = NULL; + } + gui_mch_update(); +} +#endif + +#if defined(FEAT_BROWSE) || defined(PROTO) +/* + * Implementation of the file selector related stuff + */ + |