blob: b5da5c35f770572755db9649a4c4d509b10eacd9 [file] [log] [blame]
Bram Moolenaar071d4272004-06-13 20:20:40 +00001/* vi:set ts=8 sts=4 sw=4:
2 *
3 * VIM - Vi IMproved by Bram Moolenaar
4 *
5 * Do ":help uganda" in Vim to read copying and usage conditions.
6 * Do ":help credits" in Vim to see a list of people who contributed.
7 * See README.txt for an overview of the Vim source code.
8 */
9
10/*
11 * Porting to GTK+ was done by:
12 *
Bram Moolenaarfca34d62005-01-04 21:38:36 +000013 * (C) 1998,1999,2000 by Marcin Dalecki <martin@dalecki.de>
Bram Moolenaar071d4272004-06-13 20:20:40 +000014 *
15 * With GREAT support and continuous encouragements by Andy Kahn and of
16 * course Bram Moolenaar!
17 *
18 * Support for GTK+ 2 was added by:
19 *
20 * (C) 2002,2003 Jason Hildebrand <jason@peaceworks.ca>
21 * Daniel Elstner <daniel.elstner@gmx.net>
22 *
23 * Best supporting actor (He helped somewhat, aesthetically speaking):
24 * Maxime Romano <verbophobe@hotmail.com>
25 */
26
27#ifdef FEAT_GUI_GTK
28# include "gui_gtk_f.h"
29#endif
30
31/* GTK defines MAX and MIN, but some system header files as well. Undefine
32 * them and don't use them. */
33#ifdef MIN
34# undef MIN
35#endif
36#ifdef MAX
37# undef MAX
38#endif
39
40#include "vim.h"
41
42#ifdef FEAT_GUI_GNOME
43/* Gnome redefines _() and N_(). Grrr... */
44# ifdef _
45# undef _
46# endif
47# ifdef N_
48# undef N_
49# endif
50# ifdef textdomain
51# undef textdomain
52# endif
53# ifdef bindtextdomain
54# undef bindtextdomain
55# endif
Bram Moolenaara2dd9002007-05-14 17:38:30 +000056# ifdef bind_textdomain_codeset
57# undef bind_textdomain_codeset
Bram Moolenaar79166c42007-05-10 18:29:51 +000058# endif
Bram Moolenaar071d4272004-06-13 20:20:40 +000059# if defined(FEAT_GETTEXT) && !defined(ENABLE_NLS)
60# define ENABLE_NLS /* so the texts in the dialog boxes are translated */
61# endif
62# include <gnome.h>
63#endif
64
Bram Moolenaar071d4272004-06-13 20:20:40 +000065#ifdef FEAT_GUI_GTK
66# include <gdk/gdkkeysyms.h>
67# include <gdk/gdk.h>
68# ifdef WIN3264
69# include <gdk/gdkwin32.h>
70# else
71# include <gdk/gdkx.h>
72# endif
73
74# include <gtk/gtk.h>
75#else
76/* define these items to be able to generate prototypes without GTK */
77typedef int GtkWidget;
78# define gpointer int
79# define guint8 int
80# define GdkPixmap int
81# define GdkBitmap int
82# define GtkIconFactory int
83# define GtkToolbar int
84# define GtkAdjustment int
85# define gboolean int
86# define GdkEventKey int
87# define CancelData int
88#endif
89
Bram Moolenaar071d4272004-06-13 20:20:40 +000090static void entry_activate_cb(GtkWidget *widget, gpointer data);
91static void entry_changed_cb(GtkWidget *entry, GtkWidget *dialog);
92static void find_replace_cb(GtkWidget *widget, gpointer data);
Bram Moolenaar08bc2742012-06-06 16:14:40 +020093#if defined(FEAT_BROWSE) || defined(PROTO)
Bram Moolenaar68fb5dc2012-04-25 17:10:16 +020094static void recent_func_log_func(
95 const gchar *log_domain,
96 GLogLevelFlags log_level,
97 const gchar *message,
98 gpointer user_data);
Bram Moolenaar08bc2742012-06-06 16:14:40 +020099#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +0000100
Bram Moolenaar182c5be2010-06-25 05:37:59 +0200101#if defined(FEAT_TOOLBAR)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000102/*
103 * Table from BuiltIn## icon indices to GTK+ stock IDs. Order must exactly
104 * match toolbar_names[] in menu.c! All stock icons including the "vim-*"
105 * ones can be overridden in your gtkrc file.
106 */
107static const char * const menu_stock_ids[] =
108{
109 /* 00 */ GTK_STOCK_NEW,
110 /* 01 */ GTK_STOCK_OPEN,
111 /* 02 */ GTK_STOCK_SAVE,
112 /* 03 */ GTK_STOCK_UNDO,
113 /* 04 */ GTK_STOCK_REDO,
114 /* 05 */ GTK_STOCK_CUT,
115 /* 06 */ GTK_STOCK_COPY,
116 /* 07 */ GTK_STOCK_PASTE,
117 /* 08 */ GTK_STOCK_PRINT,
118 /* 09 */ GTK_STOCK_HELP,
119 /* 10 */ GTK_STOCK_FIND,
120 /* 11 */ "vim-save-all",
121 /* 12 */ "vim-session-save",
122 /* 13 */ "vim-session-new",
123 /* 14 */ "vim-session-load",
124 /* 15 */ GTK_STOCK_EXECUTE,
125 /* 16 */ GTK_STOCK_FIND_AND_REPLACE,
126 /* 17 */ GTK_STOCK_CLOSE, /* FIXME: fuzzy */
127 /* 18 */ "vim-window-maximize",
128 /* 19 */ "vim-window-minimize",
129 /* 20 */ "vim-window-split",
130 /* 21 */ "vim-shell",
131 /* 22 */ GTK_STOCK_GO_BACK,
132 /* 23 */ GTK_STOCK_GO_FORWARD,
133 /* 24 */ "vim-find-help",
134 /* 25 */ GTK_STOCK_CONVERT,
135 /* 26 */ GTK_STOCK_JUMP_TO,
136 /* 27 */ "vim-build-tags",
137 /* 28 */ "vim-window-split-vertical",
138 /* 29 */ "vim-window-maximize-width",
139 /* 30 */ "vim-window-minimize-width",
140 /* 31 */ GTK_STOCK_QUIT
141};
142
Bram Moolenaar36e294c2015-12-29 18:55:46 +0100143#ifdef USE_GRESOURCE
144typedef struct IconNames {
145 const char *icon_name;
146 const char *file_name;
147} IconNames;
148
149static IconNames stock_vim_icons[] = {
150 { "vim-build-tags", "stock_vim_build_tags.png" },
151 { "vim-find-help", "stock_vim_find_help.png" },
152 { "vim-save-all", "stock_vim_save_all.png" },
153 { "vim-session-load", "stock_vim_session_load.png" },
154 { "vim-session-new", "stock_vim_session_new.png" },
155 { "vim-session-save", "stock_vim_session_save.png" },
156 { "vim-shell", "stock_vim_shell.png" },
157 { "vim-window-maximize", "stock_vim_window_maximize.png" },
158 { "vim-window-maximize-width", "stock_vim_window_maximize_width.png" },
159 { "vim-window-minimize", "stock_vim_window_minimize.png" },
160 { "vim-window-minimize-width", "stock_vim_window_minimize_width.png" },
161 { "vim-window-split", "stock_vim_window_split.png" },
162 { "vim-window-split-vertical", "stock_vim_window_split_vertical.png" },
163 { NULL, NULL }
164};
165#endif
166
167#ifndef USE_GRESOURCE
Bram Moolenaar071d4272004-06-13 20:20:40 +0000168 static void
169add_stock_icon(GtkIconFactory *factory,
170 const char *stock_id,
171 const guint8 *inline_data,
172 int data_length)
173{
174 GdkPixbuf *pixbuf;
175 GtkIconSet *icon_set;
176
177 pixbuf = gdk_pixbuf_new_from_inline(data_length, inline_data, FALSE, NULL);
178 icon_set = gtk_icon_set_new_from_pixbuf(pixbuf);
179
180 gtk_icon_factory_add(factory, stock_id, icon_set);
181
182 gtk_icon_set_unref(icon_set);
183 g_object_unref(pixbuf);
184}
Bram Moolenaar36e294c2015-12-29 18:55:46 +0100185#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +0000186
187 static int
188lookup_menu_iconfile(char_u *iconfile, char_u *dest)
189{
190 expand_env(iconfile, dest, MAXPATHL);
191
192 if (mch_isFullName(dest))
193 {
194 return vim_fexists(dest);
195 }
196 else
197 {
198 static const char suffixes[][4] = {"png", "xpm", "bmp"};
199 char_u buf[MAXPATHL];
200 unsigned int i;
201
202 for (i = 0; i < G_N_ELEMENTS(suffixes); ++i)
203 if (gui_find_bitmap(dest, buf, (char *)suffixes[i]) == OK)
204 {
205 STRCPY(dest, buf);
206 return TRUE;
207 }
208
209 return FALSE;
210 }
211}
212
213 static GtkWidget *
214load_menu_iconfile(char_u *name, GtkIconSize icon_size)
215{
216 GtkWidget *image = NULL;
217 GtkIconSet *icon_set;
218 GtkIconSource *icon_source;
219
220 /*
221 * Rather than loading the icon directly into a GtkImage, create
222 * a new GtkIconSet and put it in there. This way we can easily
223 * scale the toolbar icons on the fly when needed.
224 */
225 icon_set = gtk_icon_set_new();
226 icon_source = gtk_icon_source_new();
227
228 gtk_icon_source_set_filename(icon_source, (const char *)name);
229 gtk_icon_set_add_source(icon_set, icon_source);
230
231 image = gtk_image_new_from_icon_set(icon_set, icon_size);
232
233 gtk_icon_source_free(icon_source);
234 gtk_icon_set_unref(icon_set);
235
236 return image;
237}
238
239 static GtkWidget *
240create_menu_icon(vimmenu_T *menu, GtkIconSize icon_size)
241{
242 GtkWidget *image = NULL;
243 char_u buf[MAXPATHL];
244
245 /* First use a specified "icon=" argument. */
246 if (menu->iconfile != NULL && lookup_menu_iconfile(menu->iconfile, buf))
247 image = load_menu_iconfile(buf, icon_size);
248
249 /* If not found and not builtin specified try using the menu name. */
250 if (image == NULL && !menu->icon_builtin
251 && lookup_menu_iconfile(menu->name, buf))
252 image = load_menu_iconfile(buf, icon_size);
253
254 /* Still not found? Then use a builtin icon, a blank one as fallback. */
255 if (image == NULL)
256 {
257 const char *stock_id;
258 const int n_ids = G_N_ELEMENTS(menu_stock_ids);
259
260 if (menu->iconidx >= 0 && menu->iconidx < n_ids)
261 stock_id = menu_stock_ids[menu->iconidx];
262 else
263 stock_id = GTK_STOCK_MISSING_IMAGE;
264
265 image = gtk_image_new_from_stock(stock_id, icon_size);
266 }
267
268 return image;
269}
270
Bram Moolenaar9b2200a2006-03-20 21:55:45 +0000271 static gint
Bram Moolenaarb85cb212009-05-17 14:24:23 +0000272toolbar_button_focus_in_event(GtkWidget *widget UNUSED,
273 GdkEventFocus *event UNUSED,
274 gpointer data UNUSED)
Bram Moolenaar9b2200a2006-03-20 21:55:45 +0000275{
Bram Moolenaarb85cb212009-05-17 14:24:23 +0000276 /* When we're in a GtkPlug, we don't have window focus events, only widget
277 * focus. To emulate stand-alone gvim, if a button gets focus (e.g.,
278 * <Tab> into GtkPlug) immediately pass it to mainwin. */
Bram Moolenaar9b2200a2006-03-20 21:55:45 +0000279 if (gtk_socket_id != 0)
Bram Moolenaarc9b4b052006-04-30 18:54:39 +0000280 gtk_widget_grab_focus(gui.drawarea);
Bram Moolenaar9b2200a2006-03-20 21:55:45 +0000281
282 return TRUE;
283}
Bram Moolenaar182c5be2010-06-25 05:37:59 +0200284#endif /* FEAT_TOOLBAR */
Bram Moolenaar071d4272004-06-13 20:20:40 +0000285
Bram Moolenaar182c5be2010-06-25 05:37:59 +0200286#if defined(FEAT_TOOLBAR) || defined(PROTO)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000287
288 void
289gui_gtk_register_stock_icons(void)
290{
Bram Moolenaar36e294c2015-12-29 18:55:46 +0100291#ifndef USE_GRESOURCE
292# include "../pixmaps/stock_icons.h"
Bram Moolenaar071d4272004-06-13 20:20:40 +0000293 GtkIconFactory *factory;
294
295 factory = gtk_icon_factory_new();
Bram Moolenaar36e294c2015-12-29 18:55:46 +0100296# define ADD_ICON(Name, Data) add_stock_icon(factory, Name, Data, (int)sizeof(Data))
Bram Moolenaar071d4272004-06-13 20:20:40 +0000297
298 ADD_ICON("vim-build-tags", stock_vim_build_tags);
299 ADD_ICON("vim-find-help", stock_vim_find_help);
300 ADD_ICON("vim-save-all", stock_vim_save_all);
301 ADD_ICON("vim-session-load", stock_vim_session_load);
302 ADD_ICON("vim-session-new", stock_vim_session_new);
303 ADD_ICON("vim-session-save", stock_vim_session_save);
304 ADD_ICON("vim-shell", stock_vim_shell);
305 ADD_ICON("vim-window-maximize", stock_vim_window_maximize);
306 ADD_ICON("vim-window-maximize-width", stock_vim_window_maximize_width);
307 ADD_ICON("vim-window-minimize", stock_vim_window_minimize);
308 ADD_ICON("vim-window-minimize-width", stock_vim_window_minimize_width);
309 ADD_ICON("vim-window-split", stock_vim_window_split);
310 ADD_ICON("vim-window-split-vertical", stock_vim_window_split_vertical);
311
Bram Moolenaar36e294c2015-12-29 18:55:46 +0100312# undef ADD_ICON
313#else
314 GtkIconFactory * const factory = gtk_icon_factory_new();
315 const char * const path_prefix = "/org/vim/gui/icon";
316 IconNames *names;
317
318 for (names = stock_vim_icons; names->icon_name != NULL; names++)
319 {
320 char path[MAXPATHL];
321 GdkPixbuf *pixbuf;
322
323 vim_snprintf(path, MAXPATHL, "%s/%s", path_prefix, names->file_name);
324 pixbuf = gdk_pixbuf_new_from_resource(path, NULL);
325 if (pixbuf != NULL)
326 {
327 GtkIconSet *icon_set = gtk_icon_set_new_from_pixbuf(pixbuf);
328 gtk_icon_factory_add(factory, names->icon_name, icon_set);
329 gtk_icon_set_unref(icon_set);
330 g_object_unref(pixbuf);
331 }
332 }
333#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +0000334 gtk_icon_factory_add_default(factory);
335 g_object_unref(factory);
336}
337
Bram Moolenaar182c5be2010-06-25 05:37:59 +0200338#endif /* FEAT_TOOLBAR */
Bram Moolenaar071d4272004-06-13 20:20:40 +0000339
340
Bram Moolenaar071d4272004-06-13 20:20:40 +0000341#if defined(FEAT_MENU) || defined(PROTO)
342
343/*
344 * Translate Vim's mnemonic tagging to GTK+ style and convert to UTF-8
345 * if necessary. The caller must vim_free() the returned string.
346 *
347 * Input Output
348 * _ __
349 * && &
350 * & _ stripped if use_mnemonic == FALSE
351 * <Tab> end of menu label text
352 */
353 static char_u *
354translate_mnemonic_tag(char_u *name, int use_mnemonic)
355{
356 char_u *buf;
357 char_u *psrc;
358 char_u *pdest;
359 int n_underscores = 0;
360
Bram Moolenaar071d4272004-06-13 20:20:40 +0000361 name = CONVERT_TO_UTF8(name);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000362 if (name == NULL)
363 return NULL;
364
365 for (psrc = name; *psrc != NUL && *psrc != TAB; ++psrc)
366 if (*psrc == '_')
367 ++n_underscores;
368
369 buf = alloc((unsigned)(psrc - name + n_underscores + 1));
370 if (buf != NULL)
371 {
372 pdest = buf;
373 for (psrc = name; *psrc != NUL && *psrc != TAB; ++psrc)
374 {
375 if (*psrc == '_')
376 {
377 *pdest++ = '_';
378 *pdest++ = '_';
379 }
380 else if (*psrc != '&')
381 {
382 *pdest++ = *psrc;
383 }
384 else if (*(psrc + 1) == '&')
385 {
386 *pdest++ = *psrc++;
387 }
388 else if (use_mnemonic)
389 {
390 *pdest++ = '_';
391 }
392 }
393 *pdest = NUL;
394 }
395
Bram Moolenaar071d4272004-06-13 20:20:40 +0000396 CONVERT_TO_UTF8_FREE(name);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000397 return buf;
398}
399
Bram Moolenaar071d4272004-06-13 20:20:40 +0000400 static void
401menu_item_new(vimmenu_T *menu, GtkWidget *parent_widget)
402{
403 GtkWidget *box;
404 char_u *text;
405 int use_mnemonic;
406
407 /* It would be neat to have image menu items, but that would require major
408 * changes to Vim's menu system. Not to mention that all the translations
409 * had to be updated. */
410 menu->id = gtk_menu_item_new();
411 box = gtk_hbox_new(FALSE, 20);
412
413 use_mnemonic = (p_wak[0] != 'n' || !GTK_IS_MENU_BAR(parent_widget));
414 text = translate_mnemonic_tag(menu->name, use_mnemonic);
415
416 menu->label = gtk_label_new_with_mnemonic((const char *)text);
417 vim_free(text);
418
419 gtk_box_pack_start(GTK_BOX(box), menu->label, FALSE, FALSE, 0);
420
421 if (menu->actext != NULL && menu->actext[0] != NUL)
422 {
423 text = CONVERT_TO_UTF8(menu->actext);
424
425 gtk_box_pack_end(GTK_BOX(box),
426 gtk_label_new((const char *)text),
427 FALSE, FALSE, 0);
428
429 CONVERT_TO_UTF8_FREE(text);
430 }
431
432 gtk_container_add(GTK_CONTAINER(menu->id), box);
433 gtk_widget_show_all(menu->id);
434}
435
Bram Moolenaar071d4272004-06-13 20:20:40 +0000436 void
437gui_mch_add_menu(vimmenu_T *menu, int idx)
438{
439 vimmenu_T *parent;
440 GtkWidget *parent_widget;
441
442 if (menu->name[0] == ']' || menu_is_popup(menu->name))
443 {
444 menu->submenu_id = gtk_menu_new();
445 return;
446 }
447
448 parent = menu->parent;
449
450 if ((parent != NULL && parent->submenu_id == NULL)
451 || !menu_is_menubar(menu->name))
452 return;
453
454 parent_widget = (parent != NULL) ? parent->submenu_id : gui.menubar;
455 menu_item_new(menu, parent_widget);
456
457 /* since the tearoff should always appear first, increment idx */
458 if (parent != NULL && !menu_is_popup(parent->name))
459 ++idx;
460
461 gtk_menu_shell_insert(GTK_MENU_SHELL(parent_widget), menu->id, idx);
462
Bram Moolenaar071d4272004-06-13 20:20:40 +0000463 menu->submenu_id = gtk_menu_new();
464
465 gtk_menu_set_accel_group(GTK_MENU(menu->submenu_id), gui.accel_group);
466 gtk_menu_item_set_submenu(GTK_MENU_ITEM(menu->id), menu->submenu_id);
467
468 menu->tearoff_handle = gtk_tearoff_menu_item_new();
469 if (vim_strchr(p_go, GO_TEAROFF) != NULL)
470 gtk_widget_show(menu->tearoff_handle);
471 gtk_menu_prepend(GTK_MENU(menu->submenu_id), menu->tearoff_handle);
472}
473
Bram Moolenaar071d4272004-06-13 20:20:40 +0000474 static void
Bram Moolenaarb85cb212009-05-17 14:24:23 +0000475menu_item_activate(GtkWidget *widget UNUSED, gpointer data)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000476{
477 gui_menu_cb((vimmenu_T *)data);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000478}
479
Bram Moolenaar071d4272004-06-13 20:20:40 +0000480 void
481gui_mch_add_menu_item(vimmenu_T *menu, int idx)
482{
483 vimmenu_T *parent;
484
485 parent = menu->parent;
486
487# ifdef FEAT_TOOLBAR
488 if (menu_is_toolbar(parent->name))
489 {
490 GtkToolbar *toolbar;
491
492 toolbar = GTK_TOOLBAR(gui.toolbar);
493 menu->submenu_id = NULL;
494
495 if (menu_is_separator(menu->name))
496 {
497 gtk_toolbar_insert_space(toolbar, idx);
498 menu->id = NULL;
499 }
500 else
501 {
Bram Moolenaar071d4272004-06-13 20:20:40 +0000502 char_u *text;
503 char_u *tooltip;
504
505 text = CONVERT_TO_UTF8(menu->dname);
506 tooltip = CONVERT_TO_UTF8(menu->strings[MENU_INDEX_TIP]);
Bram Moolenaar3fdfa4a2004-10-07 21:02:47 +0000507 if (tooltip != NULL && !utf_valid_string(tooltip, NULL))
508 /* Invalid text, can happen when 'encoding' is changed. Avoid
509 * a nasty GTK error message, skip the tooltip. */
510 CONVERT_TO_UTF8_FREE(tooltip);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000511
512 menu->id = gtk_toolbar_insert_item(
513 toolbar,
514 (const char *)text,
515 (const char *)tooltip,
516 NULL,
517 create_menu_icon(menu, gtk_toolbar_get_icon_size(toolbar)),
518 G_CALLBACK(&menu_item_activate),
519 menu,
520 idx);
521
Bram Moolenaarc9b4b052006-04-30 18:54:39 +0000522 if (gtk_socket_id != 0)
523 gtk_signal_connect(GTK_OBJECT(menu->id), "focus_in_event",
524 GTK_SIGNAL_FUNC(toolbar_button_focus_in_event), NULL);
Bram Moolenaar9b2200a2006-03-20 21:55:45 +0000525
Bram Moolenaar071d4272004-06-13 20:20:40 +0000526 CONVERT_TO_UTF8_FREE(text);
527 CONVERT_TO_UTF8_FREE(tooltip);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000528 }
529 }
530 else
531# endif /* FEAT_TOOLBAR */
532 {
533 /* No parent, must be a non-menubar menu */
Bram Moolenaareb3593b2006-04-22 22:33:57 +0000534 if (parent == NULL || parent->submenu_id == NULL)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000535 return;
536
537 /* Make place for the possible tearoff handle item. Not in the popup
538 * menu, it doesn't have a tearoff item. */
Bram Moolenaareb3593b2006-04-22 22:33:57 +0000539 if (!menu_is_popup(parent->name))
Bram Moolenaar071d4272004-06-13 20:20:40 +0000540 ++idx;
541
542 if (menu_is_separator(menu->name))
543 {
544 /* Separator: Just add it */
545 menu->id = gtk_menu_item_new();
546 gtk_widget_set_sensitive(menu->id, FALSE);
547 gtk_widget_show(menu->id);
548 gtk_menu_insert(GTK_MENU(parent->submenu_id), menu->id, idx);
549
550 return;
551 }
552
553 /* Add textual menu item. */
554 menu_item_new(menu, parent->submenu_id);
555 gtk_widget_show(menu->id);
556 gtk_menu_insert(GTK_MENU(parent->submenu_id), menu->id, idx);
557
558 if (menu->id != NULL)
559 gtk_signal_connect(GTK_OBJECT(menu->id), "activate",
560 GTK_SIGNAL_FUNC(menu_item_activate), menu);
561 }
562}
563#endif /* FEAT_MENU */
564
565
566 void
567gui_mch_set_text_area_pos(int x, int y, int w, int h)
568{
569 gtk_form_move_resize(GTK_FORM(gui.formwin), gui.drawarea, x, y, w, h);
570}
571
572
573#if defined(FEAT_MENU) || defined(PROTO)
574/*
Bram Moolenaar79166c42007-05-10 18:29:51 +0000575 * Enable or disable accelerators for the toplevel menus.
Bram Moolenaar071d4272004-06-13 20:20:40 +0000576 */
577 void
578gui_gtk_set_mnemonics(int enable)
579{
580 vimmenu_T *menu;
581 char_u *name;
Bram Moolenaar071d4272004-06-13 20:20:40 +0000582
583 for (menu = root_menu; menu != NULL; menu = menu->next)
584 {
585 if (menu->id == NULL)
586 continue;
587
Bram Moolenaar071d4272004-06-13 20:20:40 +0000588 name = translate_mnemonic_tag(menu->name, enable);
589 gtk_label_set_text_with_mnemonic(GTK_LABEL(menu->label),
590 (const char *)name);
591 vim_free(name);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000592 }
593}
594
595 static void
596recurse_tearoffs(vimmenu_T *menu, int val)
597{
598 for (; menu != NULL; menu = menu->next)
599 {
600 if (menu->submenu_id != NULL && menu->tearoff_handle != NULL
601 && menu->name[0] != ']' && !menu_is_popup(menu->name))
602 {
603 if (val)
604 gtk_widget_show(menu->tearoff_handle);
605 else
606 gtk_widget_hide(menu->tearoff_handle);
607 }
608 recurse_tearoffs(menu->children, val);
609 }
610}
611
612 void
613gui_mch_toggle_tearoffs(int enable)
614{
615 recurse_tearoffs(root_menu, enable);
616}
617#endif /* FEAT_MENU */
618
Bram Moolenaar182c5be2010-06-25 05:37:59 +0200619#if defined(FEAT_TOOLBAR)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000620 static int
621get_menu_position(vimmenu_T *menu)
622{
623 vimmenu_T *node;
Bram Moolenaar89d40322006-08-29 15:30:07 +0000624 int idx = 0;
Bram Moolenaar071d4272004-06-13 20:20:40 +0000625
626 for (node = menu->parent->children; node != menu; node = node->next)
627 {
628 g_return_val_if_fail(node != NULL, -1);
Bram Moolenaar89d40322006-08-29 15:30:07 +0000629 ++idx;
Bram Moolenaar071d4272004-06-13 20:20:40 +0000630 }
631
Bram Moolenaar89d40322006-08-29 15:30:07 +0000632 return idx;
Bram Moolenaar071d4272004-06-13 20:20:40 +0000633}
Bram Moolenaar182c5be2010-06-25 05:37:59 +0200634#endif /* FEAT_TOOLBAR */
Bram Moolenaar071d4272004-06-13 20:20:40 +0000635
636
637#if defined(FEAT_TOOLBAR) || defined(PROTO)
638 void
639gui_mch_menu_set_tip(vimmenu_T *menu)
640{
641 if (menu->id != NULL && menu->parent != NULL
642 && gui.toolbar != NULL && menu_is_toolbar(menu->parent->name))
643 {
644 char_u *tooltip;
645
Bram Moolenaar071d4272004-06-13 20:20:40 +0000646 tooltip = CONVERT_TO_UTF8(menu->strings[MENU_INDEX_TIP]);
Bram Moolenaar3fdfa4a2004-10-07 21:02:47 +0000647 if (tooltip == NULL || utf_valid_string(tooltip, NULL))
648 /* Only set the tooltip when it's valid utf-8. */
Bram Moolenaar071d4272004-06-13 20:20:40 +0000649 gtk_tooltips_set_tip(GTK_TOOLBAR(gui.toolbar)->tooltips,
650 menu->id, (const char *)tooltip, NULL);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000651 CONVERT_TO_UTF8_FREE(tooltip);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000652 }
653}
654#endif /* FEAT_TOOLBAR */
655
656
657#if defined(FEAT_MENU) || defined(PROTO)
658/*
659 * Destroy the machine specific menu widget.
660 */
661 void
662gui_mch_destroy_menu(vimmenu_T *menu)
663{
Bram Moolenaarf9da6802013-07-03 13:04:27 +0200664 /* Don't let gtk_container_remove automatically destroy menu->id. */
665 if (menu->id != NULL)
666 g_object_ref(menu->id);
667
668 /* Workaround for a spurious gtk warning in Ubuntu: "Trying to remove
669 * a child that doesn't believe we're it's parent."
670 * Remove widget from gui.menubar before destroying it. */
671 if (menu->id != NULL && gui.menubar != NULL
672 && gtk_widget_get_parent(menu->id) == gui.menubar)
673 gtk_container_remove(GTK_CONTAINER(gui.menubar), menu->id);
674
Bram Moolenaar071d4272004-06-13 20:20:40 +0000675# ifdef FEAT_TOOLBAR
676 if (menu->parent != NULL && menu_is_toolbar(menu->parent->name))
677 {
Bram Moolenaar071d4272004-06-13 20:20:40 +0000678 if (menu_is_separator(menu->name))
679 gtk_toolbar_remove_space(GTK_TOOLBAR(gui.toolbar),
680 get_menu_position(menu));
681 else if (menu->id != NULL)
682 gtk_widget_destroy(menu->id);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000683 }
684 else
685# endif /* FEAT_TOOLBAR */
686 {
687 if (menu->submenu_id != NULL)
688 gtk_widget_destroy(menu->submenu_id);
689
690 if (menu->id != NULL)
691 gtk_widget_destroy(menu->id);
692 }
693
Bram Moolenaarf9da6802013-07-03 13:04:27 +0200694 if (menu->id != NULL)
695 g_object_unref(menu->id);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000696 menu->submenu_id = NULL;
697 menu->id = NULL;
698}
699#endif /* FEAT_MENU */
700
701
702/*
703 * Scrollbar stuff.
704 */
705 void
706gui_mch_set_scrollbar_thumb(scrollbar_T *sb, long val, long size, long max)
707{
708 if (sb->id != NULL)
709 {
710 GtkAdjustment *adjustment;
711
712 adjustment = gtk_range_get_adjustment(GTK_RANGE(sb->id));
713
714 adjustment->lower = 0.0;
715 adjustment->value = val;
716 adjustment->upper = max + 1;
717 adjustment->page_size = size;
718 adjustment->page_increment = size < 3L ? 1L : size - 2L;
719 adjustment->step_increment = 1.0;
720
Bram Moolenaar071d4272004-06-13 20:20:40 +0000721 g_signal_handler_block(GTK_OBJECT(adjustment),
722 (gulong)sb->handler_id);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000723 gtk_adjustment_changed(adjustment);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000724 g_signal_handler_unblock(GTK_OBJECT(adjustment),
725 (gulong)sb->handler_id);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000726 }
727}
728
729 void
730gui_mch_set_scrollbar_pos(scrollbar_T *sb, int x, int y, int w, int h)
731{
732 if (sb->id != NULL)
733 gtk_form_move_resize(GTK_FORM(gui.formwin), sb->id, x, y, w, h);
734}
735
736/*
737 * Take action upon scrollbar dragging.
738 */
739 static void
740adjustment_value_changed(GtkAdjustment *adjustment, gpointer data)
741{
742 scrollbar_T *sb;
743 long value;
744 int dragging = FALSE;
745
746#ifdef FEAT_XIM
747 /* cancel any preediting */
748 if (im_is_preediting())
749 xim_reset();
750#endif
751
752 sb = gui_find_scrollbar((long)data);
753 value = (long)adjustment->value;
754 /*
755 * The dragging argument must be right for the scrollbar to work with
756 * closed folds. This isn't documented, hopefully this will keep on
757 * working in later GTK versions.
758 *
759 * FIXME: Well, it doesn't work in GTK2. :)
760 * HACK: Get the mouse pointer position, if it appears to be on an arrow
761 * button set "dragging" to FALSE. This assumes square buttons!
762 */
763 if (sb != NULL)
764 {
Bram Moolenaar071d4272004-06-13 20:20:40 +0000765 dragging = TRUE;
766
767 if (sb->wp != NULL)
768 {
769 int x;
770 int y;
771 GdkModifierType state;
772 int width;
773 int height;
774
775 /* vertical scrollbar: need to set "dragging" properly in case
776 * there are closed folds. */
777 gdk_window_get_pointer(sb->id->window, &x, &y, &state);
778 gdk_window_get_size(sb->id->window, &width, &height);
779 if (x >= 0 && x < width && y >= 0 && y < height)
780 {
781 if (y < width)
782 {
783 /* up arrow: move one (closed fold) line up */
784 dragging = FALSE;
785 value = sb->wp->w_topline - 2;
786 }
787 else if (y > height - width)
788 {
789 /* down arrow: move one (closed fold) line down */
790 dragging = FALSE;
791 value = sb->wp->w_topline;
792 }
793 }
794 }
Bram Moolenaar071d4272004-06-13 20:20:40 +0000795 }
796
797 gui_drag_scrollbar(sb, value, dragging);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000798}
799
800/* SBAR_VERT or SBAR_HORIZ */
801 void
802gui_mch_create_scrollbar(scrollbar_T *sb, int orient)
803{
804 if (orient == SBAR_HORIZ)
805 sb->id = gtk_hscrollbar_new(NULL);
806 else if (orient == SBAR_VERT)
807 sb->id = gtk_vscrollbar_new(NULL);
808
809 if (sb->id != NULL)
810 {
811 GtkAdjustment *adjustment;
812
813 GTK_WIDGET_UNSET_FLAGS(sb->id, GTK_CAN_FOCUS);
814 gtk_form_put(GTK_FORM(gui.formwin), sb->id, 0, 0);
815
816 adjustment = gtk_range_get_adjustment(GTK_RANGE(sb->id));
817
818 sb->handler_id = gtk_signal_connect(
819 GTK_OBJECT(adjustment), "value_changed",
820 GTK_SIGNAL_FUNC(adjustment_value_changed),
821 GINT_TO_POINTER(sb->ident));
822 gui_mch_update();
823 }
824}
825
826#if defined(FEAT_WINDOWS) || defined(PROTO)
827 void
828gui_mch_destroy_scrollbar(scrollbar_T *sb)
829{
830 if (sb->id != NULL)
831 {
832 gtk_widget_destroy(sb->id);
833 sb->id = NULL;
834 }
835 gui_mch_update();
836}
837#endif
838
839#if defined(FEAT_BROWSE) || defined(PROTO)
840/*
841 * Implementation of the file selector related stuff
842 */
843
Bram Moolenaara7fc0102005-05-18 22:17:12 +0000844#ifndef USE_FILE_CHOOSER
Bram Moolenaar071d4272004-06-13 20:20:40 +0000845 static void
Bram Moolenaarb85cb212009-05-17 14:24:23 +0000846browse_ok_cb(GtkWidget *widget UNUSED, gpointer cbdata)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000847{
848 gui_T *vw = (gui_T *)cbdata;
849
850 if (vw->browse_fname != NULL)
851 g_free(vw->browse_fname);
852
853 vw->browse_fname = (char_u *)g_strdup(gtk_file_selection_get_filename(
854 GTK_FILE_SELECTION(vw->filedlg)));
855 gtk_widget_hide(vw->filedlg);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000856}
857
Bram Moolenaar071d4272004-06-13 20:20:40 +0000858 static void
Bram Moolenaarb85cb212009-05-17 14:24:23 +0000859browse_cancel_cb(GtkWidget *widget UNUSED, gpointer cbdata)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000860{
861 gui_T *vw = (gui_T *)cbdata;
862
863 if (vw->browse_fname != NULL)
864 {
865 g_free(vw->browse_fname);
866 vw->browse_fname = NULL;
867 }
868 gtk_widget_hide(vw->filedlg);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000869}
870
Bram Moolenaar071d4272004-06-13 20:20:40 +0000871 static gboolean
Bram Moolenaarb85cb212009-05-17 14:24:23 +0000872browse_destroy_cb(GtkWidget *widget UNUSED)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000873{
874 if (gui.browse_fname != NULL)
875 {
876 g_free(gui.browse_fname);
877 gui.browse_fname = NULL;
878 }
879 gui.filedlg = NULL;
Bram Moolenaara3f41662010-07-11 19:01:06 +0200880 gtk_main_quit();
Bram Moolenaar071d4272004-06-13 20:20:40 +0000881 return FALSE;
882}
Bram Moolenaara7fc0102005-05-18 22:17:12 +0000883#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +0000884
885/*
886 * Put up a file requester.
887 * Returns the selected name in allocated memory, or NULL for Cancel.
888 * saving, select file to write
889 * title title for the window
890 * dflt default name
891 * ext not used (extension added)
892 * initdir initial directory, NULL for current dir
893 * filter not used (file name filter)
894 */
Bram Moolenaar071d4272004-06-13 20:20:40 +0000895 char_u *
Bram Moolenaarb85cb212009-05-17 14:24:23 +0000896gui_mch_browse(int saving UNUSED,
Bram Moolenaar071d4272004-06-13 20:20:40 +0000897 char_u *title,
898 char_u *dflt,
Bram Moolenaarb85cb212009-05-17 14:24:23 +0000899 char_u *ext UNUSED,
Bram Moolenaar071d4272004-06-13 20:20:40 +0000900 char_u *initdir,
Bram Moolenaar6c4b6462012-07-10 13:12:51 +0200901 char_u *filter)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000902{
Bram Moolenaara7fc0102005-05-18 22:17:12 +0000903#ifdef USE_FILE_CHOOSER
904 GtkWidget *fc;
905#endif
Bram Moolenaar7b0294c2004-10-11 10:16:09 +0000906 char_u dirbuf[MAXPATHL];
Bram Moolenaar68fb5dc2012-04-25 17:10:16 +0200907 guint log_handler;
908 const gchar *domain = "Gtk";
Bram Moolenaar071d4272004-06-13 20:20:40 +0000909
Bram Moolenaar071d4272004-06-13 20:20:40 +0000910 title = CONVERT_TO_UTF8(title);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000911
Bram Moolenaar57ac3a22006-10-10 16:28:30 +0000912 /* GTK has a bug, it only works with an absolute path. */
Bram Moolenaara7fc0102005-05-18 22:17:12 +0000913 if (initdir == NULL || *initdir == NUL)
914 mch_dirname(dirbuf, MAXPATHL);
Bram Moolenaar57ac3a22006-10-10 16:28:30 +0000915 else if (vim_FullName(initdir, dirbuf, MAXPATHL - 2, FALSE) == FAIL)
Bram Moolenaara7fc0102005-05-18 22:17:12 +0000916 dirbuf[0] = NUL;
917 /* Always need a trailing slash for a directory. */
918 add_pathsep(dirbuf);
Bram Moolenaara7fc0102005-05-18 22:17:12 +0000919
920 /* If our pointer is currently hidden, then we should show it. */
921 gui_mch_mousehide(FALSE);
922
Bram Moolenaar68fb5dc2012-04-25 17:10:16 +0200923 /* Hack: The GTK file dialog warns when it can't access a new file, this
924 * makes it shut up. http://bugzilla.gnome.org/show_bug.cgi?id=664587 */
925 log_handler = g_log_set_handler(domain, G_LOG_LEVEL_WARNING,
926 recent_func_log_func, NULL);
927
Bram Moolenaara7fc0102005-05-18 22:17:12 +0000928#ifdef USE_FILE_CHOOSER
929 /* We create the dialog each time, so that the button text can be "Open"
930 * or "Save" according to the action. */
931 fc = gtk_file_chooser_dialog_new((const gchar *)title,
932 GTK_WINDOW(gui.mainwin),
933 saving ? GTK_FILE_CHOOSER_ACTION_SAVE
934 : GTK_FILE_CHOOSER_ACTION_OPEN,
Bram Moolenaara7fc0102005-05-18 22:17:12 +0000935 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
Bram Moolenaard1350622006-10-24 20:01:06 +0000936 saving ? GTK_STOCK_SAVE : GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
Bram Moolenaara7fc0102005-05-18 22:17:12 +0000937 NULL);
938 gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(fc),
939 (const gchar *)dirbuf);
Bram Moolenaar6c4b6462012-07-10 13:12:51 +0200940
941 if (filter != NULL && *filter != NUL)
942 {
943 int i = 0;
944 char_u *patt;
945 char_u *p = filter;
Bram Moolenaar205f9f52012-10-18 05:18:32 +0200946 GtkFileFilter *gfilter;
Bram Moolenaar6c4b6462012-07-10 13:12:51 +0200947
948 gfilter = gtk_file_filter_new();
949 patt = alloc(STRLEN(filter));
950 while (p != NULL && *p != NUL)
951 {
952 if (*p == '\n' || *p == ';' || *p == '\t')
953 {
954 STRNCPY(patt, filter, i);
955 patt[i] = '\0';
956 if (*p == '\t')
957 gtk_file_filter_set_name(gfilter, (gchar *)patt);
958 else
959 {
960 gtk_file_filter_add_pattern(gfilter, (gchar *)patt);
961 if (*p == '\n')
962 {
963 gtk_file_chooser_add_filter((GtkFileChooser *)fc,
964 gfilter);
965 if (*(p + 1) != NUL)
966 gfilter = gtk_file_filter_new();
967 }
968 }
969 filter = ++p;
970 i = 0;
971 }
972 else
973 {
974 p++;
975 i++;
976 }
977 }
978 vim_free(patt);
979 }
Bram Moolenaara3f41662010-07-11 19:01:06 +0200980 if (saving && dflt != NULL && *dflt != NUL)
981 gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(fc), (char *)dflt);
Bram Moolenaara7fc0102005-05-18 22:17:12 +0000982
983 gui.browse_fname = NULL;
984 if (gtk_dialog_run(GTK_DIALOG(fc)) == GTK_RESPONSE_ACCEPT)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000985 {
Bram Moolenaarc9b4b052006-04-30 18:54:39 +0000986 char *filename;
Bram Moolenaara7fc0102005-05-18 22:17:12 +0000987
Bram Moolenaarc9b4b052006-04-30 18:54:39 +0000988 filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(fc));
989 gui.browse_fname = (char_u *)g_strdup(filename);
990 g_free(filename);
Bram Moolenaara7fc0102005-05-18 22:17:12 +0000991 }
992 gtk_widget_destroy(GTK_WIDGET(fc));
993
994#else
995
996 if (gui.filedlg == NULL)
997 {
998 GtkFileSelection *fs; /* shortcut */
999
Bram Moolenaar071d4272004-06-13 20:20:40 +00001000 gui.filedlg = gtk_file_selection_new((const gchar *)title);
1001 gtk_window_set_modal(GTK_WINDOW(gui.filedlg), TRUE);
1002 gtk_window_set_transient_for(GTK_WINDOW(gui.filedlg),
Bram Moolenaara7fc0102005-05-18 22:17:12 +00001003 GTK_WINDOW(gui.mainwin));
Bram Moolenaar071d4272004-06-13 20:20:40 +00001004 fs = GTK_FILE_SELECTION(gui.filedlg);
1005
1006 gtk_container_border_width(GTK_CONTAINER(fs), 4);
1007
1008 gtk_signal_connect(GTK_OBJECT(fs->ok_button),
1009 "clicked", GTK_SIGNAL_FUNC(browse_ok_cb), &gui);
1010 gtk_signal_connect(GTK_OBJECT(fs->cancel_button),
1011 "clicked", GTK_SIGNAL_FUNC(browse_cancel_cb), &gui);
1012 /* gtk_signal_connect() doesn't work for destroy, it causes a hang */
1013 gtk_signal_connect_object(GTK_OBJECT(gui.filedlg),
1014 "destroy", GTK_SIGNAL_FUNC(browse_destroy_cb),
1015 GTK_OBJECT(gui.filedlg));
1016 }
1017 else
1018 gtk_window_set_title(GTK_WINDOW(gui.filedlg), (const gchar *)title);
1019
Bram Moolenaar57ac3a22006-10-10 16:28:30 +00001020 /* Concatenate "initdir" and "dflt". */
1021 if (dflt != NULL && *dflt != NUL
1022 && STRLEN(dirbuf) + 2 + STRLEN(dflt) < MAXPATHL)
1023 STRCAT(dirbuf, dflt);
1024
Bram Moolenaar071d4272004-06-13 20:20:40 +00001025 gtk_file_selection_set_filename(GTK_FILE_SELECTION(gui.filedlg),
1026 (const gchar *)dirbuf);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001027
1028 gtk_widget_show(gui.filedlg);
Bram Moolenaara3f41662010-07-11 19:01:06 +02001029 gtk_main();
Bram Moolenaara7fc0102005-05-18 22:17:12 +00001030#endif
Bram Moolenaar68fb5dc2012-04-25 17:10:16 +02001031 g_log_remove_handler(domain, log_handler);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001032
Bram Moolenaara7fc0102005-05-18 22:17:12 +00001033 CONVERT_TO_UTF8_FREE(title);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001034 if (gui.browse_fname == NULL)
1035 return NULL;
1036
1037 /* shorten the file name if possible */
Bram Moolenaard089d9b2007-09-30 12:02:55 +00001038 return vim_strsave(shorten_fname1(gui.browse_fname));
Bram Moolenaar071d4272004-06-13 20:20:40 +00001039}
1040
Bram Moolenaar7b0294c2004-10-11 10:16:09 +00001041/*
1042 * Put up a directory selector
1043 * Returns the selected name in allocated memory, or NULL for Cancel.
1044 * title title for the window
1045 * dflt default name
1046 * initdir initial directory, NULL for current dir
1047 */
Bram Moolenaar7b0294c2004-10-11 10:16:09 +00001048 char_u *
1049gui_mch_browsedir(
1050 char_u *title,
1051 char_u *initdir)
1052{
1053# if defined(GTK_FILE_CHOOSER) /* Only in GTK 2.4 and later. */
1054 char_u dirbuf[MAXPATHL];
1055 char_u *p;
1056 GtkWidget *dirdlg; /* file selection dialog */
1057 char_u *dirname = NULL;
1058
1059 title = CONVERT_TO_UTF8(title);
1060
1061 dirdlg = gtk_file_chooser_dialog_new(
1062 (const gchar *)title,
1063 GTK_WINDOW(gui.mainwin),
1064 GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER,
1065 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
1066 GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
1067 NULL);
1068
1069 CONVERT_TO_UTF8_FREE(title);
1070
1071 /* if our pointer is currently hidden, then we should show it. */
1072 gui_mch_mousehide(FALSE);
1073
1074 /* GTK appears to insist on an absolute path. */
1075 if (initdir == NULL || *initdir == NUL
1076 || vim_FullName(initdir, dirbuf, MAXPATHL - 10, FALSE) == FAIL)
1077 mch_dirname(dirbuf, MAXPATHL - 10);
1078
1079 /* Always need a trailing slash for a directory.
1080 * Also add a dummy file name, so that we get to the directory. */
1081 add_pathsep(dirbuf);
1082 STRCAT(dirbuf, "@zd(*&1|");
1083 gtk_file_chooser_set_filename(GTK_FILE_CHOOSER(dirdlg),
1084 (const gchar *)dirbuf);
1085
1086 /* Run the dialog. */
1087 if (gtk_dialog_run(GTK_DIALOG(dirdlg)) == GTK_RESPONSE_ACCEPT)
1088 dirname = (char_u *)gtk_file_chooser_get_filename(
1089 GTK_FILE_CHOOSER(dirdlg));
1090 gtk_widget_destroy(dirdlg);
1091 if (dirname == NULL)
1092 return NULL;
1093
1094 /* shorten the file name if possible */
Bram Moolenaard089d9b2007-09-30 12:02:55 +00001095 p = vim_strsave(shorten_fname1(dirname));
Bram Moolenaar7b0294c2004-10-11 10:16:09 +00001096 g_free(dirname);
1097 return p;
1098
1099# else
1100 /* For GTK 2.2 and earlier: fall back to ordinary file selector. */
1101 return gui_mch_browse(0, title, NULL, NULL, initdir, NULL);
1102# endif
1103}
1104
Bram Moolenaara7fc0102005-05-18 22:17:12 +00001105
Bram Moolenaar071d4272004-06-13 20:20:40 +00001106#endif /* FEAT_BROWSE */
1107
Bram Moolenaar182c5be2010-06-25 05:37:59 +02001108#if defined(FEAT_GUI_DIALOG) || defined(PROTO)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001109
1110 static GtkWidget *
1111create_message_dialog(int type, char_u *title, char_u *message)
1112{
1113 GtkWidget *dialog;
1114 GtkMessageType message_type;
1115
1116 switch (type)
1117 {
1118 case VIM_ERROR: message_type = GTK_MESSAGE_ERROR; break;
1119 case VIM_WARNING: message_type = GTK_MESSAGE_WARNING; break;
1120 case VIM_QUESTION: message_type = GTK_MESSAGE_QUESTION; break;
1121 default: message_type = GTK_MESSAGE_INFO; break;
1122 }
1123
1124 message = CONVERT_TO_UTF8(message);
1125 dialog = gtk_message_dialog_new(GTK_WINDOW(gui.mainwin),
1126 GTK_DIALOG_DESTROY_WITH_PARENT,
1127 message_type,
1128 GTK_BUTTONS_NONE,
1129 "%s", (const char *)message);
1130 CONVERT_TO_UTF8_FREE(message);
1131
1132 if (title != NULL)
1133 {
1134 title = CONVERT_TO_UTF8(title);
1135 gtk_window_set_title(GTK_WINDOW(dialog), (const char *)title);
1136 CONVERT_TO_UTF8_FREE(title);
1137 }
1138 else if (type == VIM_GENERIC)
1139 {
1140 gtk_window_set_title(GTK_WINDOW(dialog), "VIM");
1141 }
1142
1143 return dialog;
1144}
1145
1146/*
1147 * Split up button_string into individual button labels by inserting
1148 * NUL bytes. Also replace the Vim-style mnemonic accelerator prefix
1149 * '&' with '_'. button_string must point to allocated memory!
1150 * Return an allocated array of pointers into button_string.
1151 */
1152 static char **
1153split_button_string(char_u *button_string, int *n_buttons)
1154{
1155 char **array;
1156 char_u *p;
1157 unsigned int count = 1;
1158
1159 for (p = button_string; *p != NUL; ++p)
1160 if (*p == DLG_BUTTON_SEP)
1161 ++count;
1162
1163 array = (char **)alloc((count + 1) * sizeof(char *));
1164 count = 0;
1165
1166 if (array != NULL)
1167 {
1168 array[count++] = (char *)button_string;
Bram Moolenaarb5bf5b82004-12-24 14:35:23 +00001169 for (p = button_string; *p != NUL; )
Bram Moolenaar071d4272004-06-13 20:20:40 +00001170 {
1171 if (*p == DLG_BUTTON_SEP)
1172 {
Bram Moolenaarb5bf5b82004-12-24 14:35:23 +00001173 *p++ = NUL;
1174 array[count++] = (char *)p;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001175 }
1176 else if (*p == DLG_HOTKEY_CHAR)
Bram Moolenaarb5bf5b82004-12-24 14:35:23 +00001177 *p++ = '_';
1178 else
1179 mb_ptr_adv(p);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001180 }
1181 array[count] = NULL; /* currently not relied upon, but doesn't hurt */
1182 }
1183
1184 *n_buttons = count;
1185 return array;
1186}
1187
1188 static char **
1189split_button_translation(const char *message)
1190{
1191 char **buttons = NULL;
1192 char_u *str;
1193 int n_buttons = 0;
1194 int n_expected = 1;
1195
1196 for (str = (char_u *)message; *str != NUL; ++str)
1197 if (*str == DLG_BUTTON_SEP)
1198 ++n_expected;
1199
1200 str = (char_u *)_(message);
1201 if (str != NULL)
1202 {
1203 if (output_conv.vc_type != CONV_NONE)
1204 str = string_convert(&output_conv, str, NULL);
1205 else
1206 str = vim_strsave(str);
1207
1208 if (str != NULL)
1209 buttons = split_button_string(str, &n_buttons);
1210 }
1211 /*
1212 * Uh-oh... this should never ever happen. But we don't wanna crash
1213 * if the translation is broken, thus fall back to the untranslated
1214 * buttons string in case of emergency.
1215 */
1216 if (buttons == NULL || n_buttons != n_expected)
1217 {
1218 vim_free(buttons);
1219 vim_free(str);
1220 buttons = NULL;
1221 str = vim_strsave((char_u *)message);
1222
1223 if (str != NULL)
1224 buttons = split_button_string(str, &n_buttons);
1225 if (buttons == NULL)
1226 vim_free(str);
1227 }
1228
1229 return buttons;
1230}
1231
1232 static int
1233button_equal(const char *a, const char *b)
1234{
1235 while (*a != '\0' && *b != '\0')
1236 {
1237 if (*a == '_' && *++a == '\0')
1238 break;
1239 if (*b == '_' && *++b == '\0')
1240 break;
1241
1242 if (g_unichar_tolower(g_utf8_get_char(a))
1243 != g_unichar_tolower(g_utf8_get_char(b)))
1244 return FALSE;
1245
1246 a = g_utf8_next_char(a);
1247 b = g_utf8_next_char(b);
1248 }
1249
1250 return (*a == '\0' && *b == '\0');
1251}
1252
1253 static void
1254dialog_add_buttons(GtkDialog *dialog, char_u *button_string)
1255{
1256 char **ok;
1257 char **ync; /* "yes no cancel" */
1258 char **buttons;
1259 int n_buttons = 0;
Bram Moolenaar89d40322006-08-29 15:30:07 +00001260 int idx;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001261
1262 button_string = vim_strsave(button_string); /* must be writable */
1263 if (button_string == NULL)
1264 return;
1265
1266 /* Check 'v' flag in 'guioptions': vertical button placement. */
1267 if (vim_strchr(p_go, GO_VERTICAL) != NULL)
1268 {
1269 GtkWidget *vbutton_box;
1270
1271 vbutton_box = gtk_vbutton_box_new();
1272 gtk_widget_show(vbutton_box);
1273 gtk_box_pack_end(GTK_BOX(GTK_DIALOG(dialog)->vbox),
1274 vbutton_box, TRUE, FALSE, 0);
1275 /* Overrule the "action_area" value, hopefully this works... */
1276 GTK_DIALOG(dialog)->action_area = vbutton_box;
1277 }
1278
1279 /*
1280 * Yes this is ugly, I don't particularly like it either. But doing it
1281 * this way has the compelling advantage that translations need not to
1282 * be touched at all. See below what 'ok' and 'ync' are used for.
1283 */
1284 ok = split_button_translation(N_("&Ok"));
1285 ync = split_button_translation(N_("&Yes\n&No\n&Cancel"));
1286 buttons = split_button_string(button_string, &n_buttons);
1287
1288 /*
1289 * Yes, the buttons are in reversed order to match the GNOME 2 desktop
1290 * environment. Don't hit me -- it's all about consistency.
1291 * Well, apparently somebody changed his mind: with GTK 2.2.4 it works the
1292 * other way around...
1293 */
Bram Moolenaar89d40322006-08-29 15:30:07 +00001294 for (idx = 1; idx <= n_buttons; ++idx)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001295 {
1296 char *label;
1297 char_u *label8;
1298
Bram Moolenaar89d40322006-08-29 15:30:07 +00001299 label = buttons[idx - 1];
Bram Moolenaar071d4272004-06-13 20:20:40 +00001300 /*
1301 * Perform some guesswork to find appropriate stock items for the
1302 * buttons. We have to compare with a sample of the translated
1303 * button string to get things right. Yes, this is hackish :/
1304 *
1305 * But even the common button labels aren't necessarily translated,
1306 * since anyone can create their own dialogs using Vim functions.
1307 * Thus we have to check for those too.
1308 */
1309 if (ok != NULL && ync != NULL) /* almost impossible to fail */
1310 {
1311 if (button_equal(label, ok[0])) label = GTK_STOCK_OK;
1312 else if (button_equal(label, ync[0])) label = GTK_STOCK_YES;
1313 else if (button_equal(label, ync[1])) label = GTK_STOCK_NO;
1314 else if (button_equal(label, ync[2])) label = GTK_STOCK_CANCEL;
1315 else if (button_equal(label, "Ok")) label = GTK_STOCK_OK;
1316 else if (button_equal(label, "Yes")) label = GTK_STOCK_YES;
1317 else if (button_equal(label, "No")) label = GTK_STOCK_NO;
1318 else if (button_equal(label, "Cancel")) label = GTK_STOCK_CANCEL;
1319 }
1320 label8 = CONVERT_TO_UTF8((char_u *)label);
Bram Moolenaar89d40322006-08-29 15:30:07 +00001321 gtk_dialog_add_button(dialog, (const gchar *)label8, idx);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001322 CONVERT_TO_UTF8_FREE(label8);
1323 }
1324
1325 if (ok != NULL)
1326 vim_free(*ok);
1327 if (ync != NULL)
1328 vim_free(*ync);
1329 vim_free(ok);
1330 vim_free(ync);
1331 vim_free(buttons);
1332 vim_free(button_string);
1333}
1334
1335/*
1336 * Allow mnemonic accelerators to be activated without pressing <Alt>.
1337 * I'm not sure if it's a wise idea to do this. However, the old GTK+ 1.2
1338 * GUI used to work this way, and I consider the impact on UI consistency
1339 * low enough to justify implementing this as a special Vim feature.
1340 */
1341typedef struct _DialogInfo
1342{
1343 int ignore_enter; /* no default button, ignore "Enter" */
1344 int noalt; /* accept accelerators without Alt */
1345 GtkDialog *dialog; /* Widget of the dialog */
1346} DialogInfo;
1347
Bram Moolenaar071d4272004-06-13 20:20:40 +00001348 static gboolean
1349dialog_key_press_event_cb(GtkWidget *widget, GdkEventKey *event, gpointer data)
1350{
1351 DialogInfo *di = (DialogInfo *)data;
1352
Bram Moolenaard2c765e2007-08-14 13:00:40 +00001353 /* Ignore hitting Enter (or Space) when there is no default button. */
1354 if (di->ignore_enter && (event->keyval == GDK_Return
1355 || event->keyval == ' '))
1356 return TRUE;
1357 else /* A different key was pressed, return to normal behavior */
1358 di->ignore_enter = FALSE;
1359
Bram Moolenaar071d4272004-06-13 20:20:40 +00001360 /* Close the dialog when hitting "Esc". */
1361 if (event->keyval == GDK_Escape)
1362 {
1363 gtk_dialog_response(di->dialog, GTK_RESPONSE_REJECT);
1364 return TRUE;
1365 }
1366
1367 if (di->noalt
1368 && (event->state & gtk_accelerator_get_default_mod_mask()) == 0)
1369 {
1370 return gtk_window_mnemonic_activate(
1371 GTK_WINDOW(widget), event->keyval,
1372 gtk_window_get_mnemonic_modifier(GTK_WINDOW(widget)));
1373 }
1374
1375 return FALSE; /* continue emission */
1376}
1377
1378 int
1379gui_mch_dialog(int type, /* type of dialog */
1380 char_u *title, /* title of dialog */
1381 char_u *message, /* message text */
1382 char_u *buttons, /* names of buttons */
1383 int def_but, /* default button */
Bram Moolenaard2c340a2011-01-17 20:08:11 +01001384 char_u *textfield, /* text for textfield or NULL */
1385 int ex_cmd UNUSED)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001386{
1387 GtkWidget *dialog;
1388 GtkWidget *entry = NULL;
1389 char_u *text;
1390 int response;
1391 DialogInfo dialoginfo;
1392
1393 dialog = create_message_dialog(type, title, message);
1394 dialoginfo.dialog = GTK_DIALOG(dialog);
1395 dialog_add_buttons(GTK_DIALOG(dialog), buttons);
1396
1397 if (textfield != NULL)
1398 {
1399 GtkWidget *alignment;
1400
1401 entry = gtk_entry_new();
1402 gtk_widget_show(entry);
1403
Bram Moolenaardf6b11e2010-11-24 18:48:12 +01001404 /* Make Enter work like pressing OK. */
Bram Moolenaar6c4b6462012-07-10 13:12:51 +02001405 gtk_entry_set_activates_default(GTK_ENTRY(entry), TRUE);
Bram Moolenaardf6b11e2010-11-24 18:48:12 +01001406
Bram Moolenaar071d4272004-06-13 20:20:40 +00001407 text = CONVERT_TO_UTF8(textfield);
1408 gtk_entry_set_text(GTK_ENTRY(entry), (const char *)text);
1409 CONVERT_TO_UTF8_FREE(text);
1410
1411 alignment = gtk_alignment_new((float)0.5, (float)0.5,
1412 (float)1.0, (float)1.0);
1413 gtk_container_add(GTK_CONTAINER(alignment), entry);
1414 gtk_container_set_border_width(GTK_CONTAINER(alignment), 5);
1415 gtk_widget_show(alignment);
1416
1417 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox),
1418 alignment, TRUE, FALSE, 0);
1419 dialoginfo.noalt = FALSE;
1420 }
1421 else
1422 dialoginfo.noalt = TRUE;
1423
1424 /* Allow activation of mnemonic accelerators without pressing <Alt> when
Bram Moolenaar14716812006-05-04 21:54:08 +00001425 * there is no textfield. Handle pressing Esc. */
Bram Moolenaar071d4272004-06-13 20:20:40 +00001426 g_signal_connect(G_OBJECT(dialog), "key_press_event",
1427 G_CALLBACK(&dialog_key_press_event_cb), &dialoginfo);
1428
1429 if (def_but > 0)
1430 {
1431 gtk_dialog_set_default_response(GTK_DIALOG(dialog), def_but);
1432 dialoginfo.ignore_enter = FALSE;
1433 }
1434 else
1435 /* No default button, ignore pressing Enter. */
1436 dialoginfo.ignore_enter = TRUE;
1437
1438 /* Show the mouse pointer if it's currently hidden. */
1439 gui_mch_mousehide(FALSE);
1440
1441 response = gtk_dialog_run(GTK_DIALOG(dialog));
1442
1443 /* GTK_RESPONSE_NONE means the dialog was programmatically destroyed. */
1444 if (response != GTK_RESPONSE_NONE)
1445 {
Bram Moolenaar82cf9b62005-06-07 21:09:25 +00001446 if (response == GTK_RESPONSE_ACCEPT) /* Enter pressed */
1447 response = def_but;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001448 if (textfield != NULL)
1449 {
1450 text = (char_u *)gtk_entry_get_text(GTK_ENTRY(entry));
1451 text = CONVERT_FROM_UTF8(text);
1452
Bram Moolenaarce0842a2005-07-18 21:58:11 +00001453 vim_strncpy(textfield, text, IOSIZE - 1);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001454
1455 CONVERT_FROM_UTF8_FREE(text);
1456 }
1457 gtk_widget_destroy(dialog);
1458 }
1459
Bram Moolenaar071d4272004-06-13 20:20:40 +00001460 return response > 0 ? response : 0;
1461}
1462
Bram Moolenaar182c5be2010-06-25 05:37:59 +02001463#endif /* FEAT_GUI_DIALOG */
Bram Moolenaar071d4272004-06-13 20:20:40 +00001464
1465
1466#if defined(FEAT_MENU) || defined(PROTO)
1467
1468 void
1469gui_mch_show_popupmenu(vimmenu_T *menu)
1470{
Bram Moolenaar182c5be2010-06-25 05:37:59 +02001471# if defined(FEAT_XIM)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001472 /*
1473 * Append a submenu for selecting an input method. This is
1474 * currently the only way to switch input methods at runtime.
1475 */
1476 if (xic != NULL && g_object_get_data(G_OBJECT(menu->submenu_id),
1477 "vim-has-im-menu") == NULL)
1478 {
1479 GtkWidget *menuitem;
1480 GtkWidget *submenu;
1481 char_u *name;
1482
1483 menuitem = gtk_separator_menu_item_new();
1484 gtk_widget_show(menuitem);
1485 gtk_menu_shell_append(GTK_MENU_SHELL(menu->submenu_id), menuitem);
1486
1487 name = (char_u *)_("Input _Methods");
1488 name = CONVERT_TO_UTF8(name);
1489 menuitem = gtk_menu_item_new_with_mnemonic((const char *)name);
1490 CONVERT_TO_UTF8_FREE(name);
1491 gtk_widget_show(menuitem);
1492
1493 submenu = gtk_menu_new();
1494 gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), submenu);
1495 gtk_menu_shell_append(GTK_MENU_SHELL(menu->submenu_id), menuitem);
1496
1497 gtk_im_multicontext_append_menuitems(GTK_IM_MULTICONTEXT(xic),
1498 GTK_MENU_SHELL(submenu));
1499 g_object_set_data(G_OBJECT(menu->submenu_id),
1500 "vim-has-im-menu", GINT_TO_POINTER(TRUE));
1501 }
Bram Moolenaar182c5be2010-06-25 05:37:59 +02001502# endif /* FEAT_XIM */
Bram Moolenaar071d4272004-06-13 20:20:40 +00001503
1504 gtk_menu_popup(GTK_MENU(menu->submenu_id),
1505 NULL, NULL,
1506 (GtkMenuPositionFunc)NULL, NULL,
Bram Moolenaar20892c12011-06-26 04:49:00 +02001507 3U, gui.event_time);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001508}
1509
Bram Moolenaar045e82d2005-07-08 22:25:33 +00001510/* Ugly global variable to pass "mouse_pos" flag from gui_make_popup() to
1511 * popup_menu_position_func(). */
1512static int popup_mouse_pos;
1513
Bram Moolenaar071d4272004-06-13 20:20:40 +00001514/*
1515 * Menu position callback; used by gui_make_popup() to place the menu
1516 * at the current text cursor position.
1517 *
1518 * Note: The push_in output argument seems to affect scrolling of huge
1519 * menus that don't fit on the screen. Leave it at the default for now.
1520 */
Bram Moolenaar071d4272004-06-13 20:20:40 +00001521 static void
Bram Moolenaarb85cb212009-05-17 14:24:23 +00001522popup_menu_position_func(GtkMenu *menu UNUSED,
Bram Moolenaar071d4272004-06-13 20:20:40 +00001523 gint *x, gint *y,
Bram Moolenaarb85cb212009-05-17 14:24:23 +00001524 gboolean *push_in UNUSED,
Bram Moolenaarb85cb212009-05-17 14:24:23 +00001525 gpointer user_data UNUSED)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001526{
Bram Moolenaar045e82d2005-07-08 22:25:33 +00001527 gdk_window_get_origin(gui.drawarea->window, x, y);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001528
Bram Moolenaar045e82d2005-07-08 22:25:33 +00001529 if (popup_mouse_pos)
1530 {
1531 int mx, my;
1532
1533 gui_mch_getmouse(&mx, &my);
1534 *x += mx;
1535 *y += my;
1536 }
1537 else if (curwin != NULL && gui.drawarea != NULL && gui.drawarea->window != NULL)
1538 {
Bram Moolenaar071d4272004-06-13 20:20:40 +00001539 /* Find the cursor position in the current window */
1540 *x += FILL_X(W_WINCOL(curwin) + curwin->w_wcol + 1) + 1;
1541 *y += FILL_Y(W_WINROW(curwin) + curwin->w_wrow + 1) + 1;
1542 }
1543}
1544
1545 void
Bram Moolenaar045e82d2005-07-08 22:25:33 +00001546gui_make_popup(char_u *path_name, int mouse_pos)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001547{
1548 vimmenu_T *menu;
1549
Bram Moolenaar045e82d2005-07-08 22:25:33 +00001550 popup_mouse_pos = mouse_pos;
1551
Bram Moolenaar071d4272004-06-13 20:20:40 +00001552 menu = gui_find_menu(path_name);
1553
1554 if (menu != NULL && menu->submenu_id != NULL)
1555 {
1556 gtk_menu_popup(GTK_MENU(menu->submenu_id),
1557 NULL, NULL,
1558 &popup_menu_position_func, NULL,
1559 0U, (guint32)GDK_CURRENT_TIME);
1560 }
1561}
1562
1563#endif /* FEAT_MENU */
1564
1565
1566/*
1567 * We don't create it twice.
1568 */
1569
1570typedef struct _SharedFindReplace
1571{
1572 GtkWidget *dialog; /* the main dialog widget */
1573 GtkWidget *wword; /* 'Whole word only' check button */
1574 GtkWidget *mcase; /* 'Match case' check button */
1575 GtkWidget *up; /* search direction 'Up' radio button */
1576 GtkWidget *down; /* search direction 'Down' radio button */
1577 GtkWidget *what; /* 'Find what' entry text widget */
1578 GtkWidget *with; /* 'Replace with' entry text widget */
1579 GtkWidget *find; /* 'Find Next' action button */
1580 GtkWidget *replace; /* 'Replace With' action button */
1581 GtkWidget *all; /* 'Replace All' action button */
1582} SharedFindReplace;
1583
Bram Moolenaarb85cb212009-05-17 14:24:23 +00001584static SharedFindReplace find_widgets = {NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL};
1585static SharedFindReplace repl_widgets = {NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL};
Bram Moolenaar071d4272004-06-13 20:20:40 +00001586
Bram Moolenaar071d4272004-06-13 20:20:40 +00001587 static int
1588find_key_press_event(
Bram Moolenaarb85cb212009-05-17 14:24:23 +00001589 GtkWidget *widget UNUSED,
Bram Moolenaar071d4272004-06-13 20:20:40 +00001590 GdkEventKey *event,
1591 SharedFindReplace *frdp)
1592{
1593 /* If the user is holding one of the key modifiers we will just bail out,
1594 * thus preserving the possibility of normal focus traversal.
1595 */
1596 if (event->state & (GDK_CONTROL_MASK | GDK_SHIFT_MASK))
1597 return FALSE;
1598
1599 /* the Escape key synthesizes a cancellation action */
1600 if (event->keyval == GDK_Escape)
1601 {
1602 gtk_widget_hide(frdp->dialog);
1603
1604 return TRUE;
1605 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00001606
Bram Moolenaar79166c42007-05-10 18:29:51 +00001607 /* It would be delightful if it where possible to do search history
Bram Moolenaar071d4272004-06-13 20:20:40 +00001608 * operations on the K_UP and K_DOWN keys here.
1609 */
1610
1611 return FALSE;
1612}
1613
Bram Moolenaar071d4272004-06-13 20:20:40 +00001614 static GtkWidget *
1615create_image_button(const char *stock_id, const char *label)
1616{
1617 char_u *text;
1618 GtkWidget *box;
1619 GtkWidget *alignment;
1620 GtkWidget *button;
1621
1622 text = CONVERT_TO_UTF8((char_u *)label);
1623
1624 box = gtk_hbox_new(FALSE, 3);
1625 gtk_box_pack_start(GTK_BOX(box),
1626 gtk_image_new_from_stock(stock_id, GTK_ICON_SIZE_BUTTON),
1627 FALSE, FALSE, 0);
1628 gtk_box_pack_start(GTK_BOX(box),
1629 gtk_label_new((const char *)text),
1630 FALSE, FALSE, 0);
1631
1632 CONVERT_TO_UTF8_FREE(text);
1633
1634 alignment = gtk_alignment_new((float)0.5, (float)0.5,
1635 (float)0.0, (float)0.0);
1636 gtk_container_add(GTK_CONTAINER(alignment), box);
1637 gtk_widget_show_all(alignment);
1638
1639 button = gtk_button_new();
1640 gtk_container_add(GTK_CONTAINER(button), alignment);
1641
1642 return button;
1643}
1644
1645/*
1646 * This is currently only used by find_replace_dialog_create(), and
1647 * I'd really like to keep it at that. In other words: don't spread
1648 * this nasty hack all over the code. Think twice.
1649 */
1650 static const char *
1651convert_localized_message(char_u **buffer, const char *message)
1652{
1653 if (output_conv.vc_type == CONV_NONE)
1654 return message;
1655
1656 vim_free(*buffer);
1657 *buffer = string_convert(&output_conv, (char_u *)message, NULL);
1658
1659 return (const char *)*buffer;
1660}
Bram Moolenaar071d4272004-06-13 20:20:40 +00001661
1662 static void
1663find_replace_dialog_create(char_u *arg, int do_replace)
1664{
Bram Moolenaar071d4272004-06-13 20:20:40 +00001665 GtkWidget *hbox; /* main top down box */
1666 GtkWidget *actionarea;
1667 GtkWidget *table;
1668 GtkWidget *tmp;
1669 GtkWidget *vbox;
1670 gboolean sensitive;
1671 SharedFindReplace *frdp;
1672 char_u *entry_text;
1673 int wword = FALSE;
1674 int mcase = !p_ic;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001675 char_u *conv_buffer = NULL;
1676# define CONV(message) convert_localized_message(&conv_buffer, (message))
Bram Moolenaar071d4272004-06-13 20:20:40 +00001677
1678 frdp = (do_replace) ? (&repl_widgets) : (&find_widgets);
1679
1680 /* Get the search string to use. */
1681 entry_text = get_find_dialog_text(arg, &wword, &mcase);
1682
Bram Moolenaar071d4272004-06-13 20:20:40 +00001683 if (entry_text != NULL && output_conv.vc_type != CONV_NONE)
1684 {
1685 char_u *old_text = entry_text;
1686 entry_text = string_convert(&output_conv, entry_text, NULL);
1687 vim_free(old_text);
1688 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00001689
1690 /*
1691 * If the dialog already exists, just raise it.
1692 */
1693 if (frdp->dialog)
1694 {
Bram Moolenaar071d4272004-06-13 20:20:40 +00001695 if (entry_text != NULL)
1696 {
1697 gtk_entry_set_text(GTK_ENTRY(frdp->what), (char *)entry_text);
1698 gtk_toggle_button_set_state(GTK_TOGGLE_BUTTON(frdp->wword),
1699 (gboolean)wword);
1700 gtk_toggle_button_set_state(GTK_TOGGLE_BUTTON(frdp->mcase),
1701 (gboolean)mcase);
1702 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00001703 gtk_window_present(GTK_WINDOW(frdp->dialog));
Bram Moolenaar071d4272004-06-13 20:20:40 +00001704 vim_free(entry_text);
1705 return;
1706 }
1707
Bram Moolenaar071d4272004-06-13 20:20:40 +00001708 frdp->dialog = gtk_dialog_new();
1709 gtk_dialog_set_has_separator(GTK_DIALOG(frdp->dialog), FALSE);
1710 gtk_window_set_transient_for(GTK_WINDOW(frdp->dialog), GTK_WINDOW(gui.mainwin));
1711 gtk_window_set_destroy_with_parent(GTK_WINDOW(frdp->dialog), TRUE);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001712
1713 if (do_replace)
1714 {
Bram Moolenaar071d4272004-06-13 20:20:40 +00001715 gtk_window_set_title(GTK_WINDOW(frdp->dialog),
1716 CONV(_("VIM - Search and Replace...")));
1717 }
1718 else
1719 {
Bram Moolenaar071d4272004-06-13 20:20:40 +00001720 gtk_window_set_title(GTK_WINDOW(frdp->dialog),
1721 CONV(_("VIM - Search...")));
1722 }
1723
Bram Moolenaar071d4272004-06-13 20:20:40 +00001724 hbox = gtk_hbox_new(FALSE, 0);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001725 gtk_container_set_border_width(GTK_CONTAINER(hbox), 10);
1726 gtk_container_add(GTK_CONTAINER(GTK_DIALOG(frdp->dialog)->vbox), hbox);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001727
1728 if (do_replace)
1729 table = gtk_table_new(1024, 4, FALSE);
1730 else
1731 table = gtk_table_new(1024, 3, FALSE);
1732 gtk_box_pack_start(GTK_BOX(hbox), table, TRUE, TRUE, 0);
1733 gtk_container_border_width(GTK_CONTAINER(table), 4);
1734
1735 tmp = gtk_label_new(CONV(_("Find what:")));
1736 gtk_misc_set_alignment(GTK_MISC(tmp), (gfloat)0.0, (gfloat)0.5);
1737 gtk_table_attach(GTK_TABLE(table), tmp, 0, 1, 0, 1,
1738 GTK_FILL, GTK_EXPAND, 2, 2);
1739 frdp->what = gtk_entry_new();
1740 sensitive = (entry_text != NULL && entry_text[0] != NUL);
1741 if (entry_text != NULL)
1742 gtk_entry_set_text(GTK_ENTRY(frdp->what), (char *)entry_text);
1743 gtk_signal_connect(GTK_OBJECT(frdp->what), "changed",
1744 GTK_SIGNAL_FUNC(entry_changed_cb), frdp->dialog);
1745 gtk_signal_connect_after(GTK_OBJECT(frdp->what), "key_press_event",
1746 GTK_SIGNAL_FUNC(find_key_press_event),
1747 (gpointer) frdp);
1748 gtk_table_attach(GTK_TABLE(table), frdp->what, 1, 1024, 0, 1,
1749 GTK_EXPAND | GTK_FILL, GTK_EXPAND, 2, 2);
1750
1751 if (do_replace)
1752 {
1753 tmp = gtk_label_new(CONV(_("Replace with:")));
1754 gtk_misc_set_alignment(GTK_MISC(tmp), (gfloat)0.0, (gfloat)0.5);
1755 gtk_table_attach(GTK_TABLE(table), tmp, 0, 1, 1, 2,
1756 GTK_FILL, GTK_EXPAND, 2, 2);
1757 frdp->with = gtk_entry_new();
1758 gtk_signal_connect(GTK_OBJECT(frdp->with), "activate",
1759 GTK_SIGNAL_FUNC(find_replace_cb),
1760 GINT_TO_POINTER(FRD_R_FINDNEXT));
1761 gtk_signal_connect_after(GTK_OBJECT(frdp->with), "key_press_event",
1762 GTK_SIGNAL_FUNC(find_key_press_event),
1763 (gpointer) frdp);
1764 gtk_table_attach(GTK_TABLE(table), frdp->with, 1, 1024, 1, 2,
1765 GTK_EXPAND | GTK_FILL, GTK_EXPAND, 2, 2);
1766
1767 /*
1768 * Make the entry activation only change the input focus onto the
1769 * with item.
1770 */
1771 gtk_signal_connect(GTK_OBJECT(frdp->what), "activate",
1772 GTK_SIGNAL_FUNC(entry_activate_cb), frdp->with);
1773 }
1774 else
1775 {
1776 /*
1777 * Make the entry activation do the search.
1778 */
1779 gtk_signal_connect(GTK_OBJECT(frdp->what), "activate",
1780 GTK_SIGNAL_FUNC(find_replace_cb),
1781 GINT_TO_POINTER(FRD_FINDNEXT));
1782 }
1783
1784 /* whole word only button */
1785 frdp->wword = gtk_check_button_new_with_label(CONV(_("Match whole word only")));
1786 gtk_toggle_button_set_state(GTK_TOGGLE_BUTTON(frdp->wword),
1787 (gboolean)wword);
1788 if (do_replace)
1789 gtk_table_attach(GTK_TABLE(table), frdp->wword, 0, 1023, 2, 3,
1790 GTK_FILL, GTK_EXPAND, 2, 2);
1791 else
1792 gtk_table_attach(GTK_TABLE(table), frdp->wword, 0, 1023, 1, 2,
1793 GTK_FILL, GTK_EXPAND, 2, 2);
1794
1795 /* match case button */
1796 frdp->mcase = gtk_check_button_new_with_label(CONV(_("Match case")));
1797 gtk_toggle_button_set_state(GTK_TOGGLE_BUTTON(frdp->mcase),
1798 (gboolean)mcase);
1799 if (do_replace)
1800 gtk_table_attach(GTK_TABLE(table), frdp->mcase, 0, 1023, 3, 4,
1801 GTK_FILL, GTK_EXPAND, 2, 2);
1802 else
1803 gtk_table_attach(GTK_TABLE(table), frdp->mcase, 0, 1023, 2, 3,
1804 GTK_FILL, GTK_EXPAND, 2, 2);
1805
1806 tmp = gtk_frame_new(CONV(_("Direction")));
1807 if (do_replace)
1808 gtk_table_attach(GTK_TABLE(table), tmp, 1023, 1024, 2, 4,
1809 GTK_FILL, GTK_FILL, 2, 2);
1810 else
1811 gtk_table_attach(GTK_TABLE(table), tmp, 1023, 1024, 1, 3,
1812 GTK_FILL, GTK_FILL, 2, 2);
1813 vbox = gtk_vbox_new(FALSE, 0);
1814 gtk_container_border_width(GTK_CONTAINER(vbox), 0);
1815 gtk_container_add(GTK_CONTAINER(tmp), vbox);
1816
1817 /* 'Up' and 'Down' buttons */
1818 frdp->up = gtk_radio_button_new_with_label(NULL, CONV(_("Up")));
1819 gtk_box_pack_start(GTK_BOX(vbox), frdp->up, TRUE, TRUE, 0);
1820 frdp->down = gtk_radio_button_new_with_label(
1821 gtk_radio_button_group(GTK_RADIO_BUTTON(frdp->up)),
1822 CONV(_("Down")));
1823 gtk_toggle_button_set_state(GTK_TOGGLE_BUTTON(frdp->down), TRUE);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001824 gtk_container_set_border_width(GTK_CONTAINER(vbox), 2);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001825 gtk_box_pack_start(GTK_BOX(vbox), frdp->down, TRUE, TRUE, 0);
1826
1827 /* vbox to hold the action buttons */
1828 actionarea = gtk_vbutton_box_new();
1829 gtk_container_border_width(GTK_CONTAINER(actionarea), 2);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001830 gtk_box_pack_end(GTK_BOX(hbox), actionarea, FALSE, FALSE, 0);
1831
1832 /* 'Find Next' button */
Bram Moolenaar071d4272004-06-13 20:20:40 +00001833 frdp->find = create_image_button(GTK_STOCK_FIND, _("Find Next"));
Bram Moolenaar071d4272004-06-13 20:20:40 +00001834 gtk_widget_set_sensitive(frdp->find, sensitive);
1835
1836 gtk_signal_connect(GTK_OBJECT(frdp->find), "clicked",
1837 GTK_SIGNAL_FUNC(find_replace_cb),
1838 (do_replace) ? GINT_TO_POINTER(FRD_R_FINDNEXT)
1839 : GINT_TO_POINTER(FRD_FINDNEXT));
1840
1841 GTK_WIDGET_SET_FLAGS(frdp->find, GTK_CAN_DEFAULT);
1842 gtk_box_pack_start(GTK_BOX(actionarea), frdp->find, FALSE, FALSE, 0);
1843 gtk_widget_grab_default(frdp->find);
1844
1845 if (do_replace)
1846 {
1847 /* 'Replace' button */
Bram Moolenaar071d4272004-06-13 20:20:40 +00001848 frdp->replace = create_image_button(GTK_STOCK_CONVERT, _("Replace"));
Bram Moolenaar071d4272004-06-13 20:20:40 +00001849 gtk_widget_set_sensitive(frdp->replace, sensitive);
1850 GTK_WIDGET_SET_FLAGS(frdp->replace, GTK_CAN_DEFAULT);
1851 gtk_box_pack_start(GTK_BOX(actionarea), frdp->replace, FALSE, FALSE, 0);
1852 gtk_signal_connect(GTK_OBJECT(frdp->replace), "clicked",
1853 GTK_SIGNAL_FUNC(find_replace_cb),
1854 GINT_TO_POINTER(FRD_REPLACE));
1855
1856 /* 'Replace All' button */
Bram Moolenaar071d4272004-06-13 20:20:40 +00001857 frdp->all = create_image_button(GTK_STOCK_CONVERT, _("Replace All"));
Bram Moolenaar071d4272004-06-13 20:20:40 +00001858 gtk_widget_set_sensitive(frdp->all, sensitive);
1859 GTK_WIDGET_SET_FLAGS(frdp->all, GTK_CAN_DEFAULT);
1860 gtk_box_pack_start(GTK_BOX(actionarea), frdp->all, FALSE, FALSE, 0);
1861 gtk_signal_connect(GTK_OBJECT(frdp->all), "clicked",
1862 GTK_SIGNAL_FUNC(find_replace_cb),
1863 GINT_TO_POINTER(FRD_REPLACEALL));
1864 }
1865
1866 /* 'Cancel' button */
Bram Moolenaar071d4272004-06-13 20:20:40 +00001867 tmp = gtk_button_new_from_stock(GTK_STOCK_CLOSE);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001868 GTK_WIDGET_SET_FLAGS(tmp, GTK_CAN_DEFAULT);
1869 gtk_box_pack_end(GTK_BOX(actionarea), tmp, FALSE, FALSE, 0);
1870 gtk_signal_connect_object(GTK_OBJECT(tmp),
1871 "clicked", GTK_SIGNAL_FUNC(gtk_widget_hide),
1872 GTK_OBJECT(frdp->dialog));
1873 gtk_signal_connect_object(GTK_OBJECT(frdp->dialog),
1874 "delete_event", GTK_SIGNAL_FUNC(gtk_widget_hide_on_delete),
1875 GTK_OBJECT(frdp->dialog));
1876
1877 tmp = gtk_vseparator_new();
Bram Moolenaar071d4272004-06-13 20:20:40 +00001878 gtk_box_pack_end(GTK_BOX(hbox), tmp, FALSE, FALSE, 10);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001879
Bram Moolenaar071d4272004-06-13 20:20:40 +00001880 /* Suppress automatic show of the unused action area */
1881 gtk_widget_hide(GTK_DIALOG(frdp->dialog)->action_area);
1882 gtk_widget_show_all(hbox);
1883 gtk_widget_show(frdp->dialog);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001884
1885 vim_free(entry_text);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001886 vim_free(conv_buffer);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001887#undef CONV
1888}
1889
1890 void
1891gui_mch_find_dialog(exarg_T *eap)
1892{
1893 if (gui.in_use)
1894 find_replace_dialog_create(eap->arg, FALSE);
1895}
1896
1897 void
1898gui_mch_replace_dialog(exarg_T *eap)
1899{
1900 if (gui.in_use)
1901 find_replace_dialog_create(eap->arg, TRUE);
1902}
1903
Bram Moolenaar071d4272004-06-13 20:20:40 +00001904/*
1905 * Callback for actions of the find and replace dialogs
1906 */
Bram Moolenaar071d4272004-06-13 20:20:40 +00001907 static void
Bram Moolenaarb85cb212009-05-17 14:24:23 +00001908find_replace_cb(GtkWidget *widget UNUSED, gpointer data)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001909{
1910 int flags;
1911 char_u *find_text;
1912 char_u *repl_text;
1913 gboolean direction_down;
1914 SharedFindReplace *sfr;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001915
1916 flags = (int)(long)data; /* avoid a lint warning here */
1917
1918 /* Get the search/replace strings from the dialog */
1919 if (flags == FRD_FINDNEXT)
1920 {
1921 repl_text = NULL;
1922 sfr = &find_widgets;
1923 }
1924 else
1925 {
1926 repl_text = (char_u *)gtk_entry_get_text(GTK_ENTRY(repl_widgets.with));
1927 sfr = &repl_widgets;
1928 }
1929
1930 find_text = (char_u *)gtk_entry_get_text(GTK_ENTRY(sfr->what));
1931 direction_down = GTK_TOGGLE_BUTTON(sfr->down)->active;
1932
1933 if (GTK_TOGGLE_BUTTON(sfr->wword)->active)
1934 flags |= FRD_WHOLE_WORD;
1935 if (GTK_TOGGLE_BUTTON(sfr->mcase)->active)
1936 flags |= FRD_MATCH_CASE;
1937
Bram Moolenaar071d4272004-06-13 20:20:40 +00001938 repl_text = CONVERT_FROM_UTF8(repl_text);
1939 find_text = CONVERT_FROM_UTF8(find_text);
Bram Moolenaare980d8a2010-12-08 13:11:21 +01001940 gui_do_findrepl(flags, find_text, repl_text, direction_down);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001941 CONVERT_FROM_UTF8_FREE(repl_text);
1942 CONVERT_FROM_UTF8_FREE(find_text);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001943}
1944
1945/* our usual callback function */
Bram Moolenaar071d4272004-06-13 20:20:40 +00001946 static void
Bram Moolenaarb85cb212009-05-17 14:24:23 +00001947entry_activate_cb(GtkWidget *widget UNUSED, gpointer data)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001948{
1949 gtk_widget_grab_focus(GTK_WIDGET(data));
1950}
1951
1952/*
1953 * Syncing the find/replace dialogs on the fly is utterly useless crack,
1954 * and causes nothing but problems. Please tell me a use case for which
1955 * you'd need both a find dialog and a find/replace one at the same time,
1956 * without being able to actually use them separately since they're syncing
1957 * all the time. I don't think it's worthwhile to fix this nonsense,
1958 * particularly evil incarnation of braindeadness, whatever; I'd much rather
1959 * see it extinguished from this planet. Thanks for listening. Sorry.
1960 */
1961 static void
1962entry_changed_cb(GtkWidget * entry, GtkWidget * dialog)
1963{
1964 const gchar *entry_text;
1965 gboolean nonempty;
1966
1967 entry_text = gtk_entry_get_text(GTK_ENTRY(entry));
1968
1969 if (!entry_text)
1970 return; /* shouldn't happen */
1971
1972 nonempty = (entry_text[0] != '\0');
1973
1974 if (dialog == find_widgets.dialog)
1975 {
1976 gtk_widget_set_sensitive(find_widgets.find, nonempty);
1977 }
1978
1979 if (dialog == repl_widgets.dialog)
1980 {
1981 gtk_widget_set_sensitive(repl_widgets.find, nonempty);
1982 gtk_widget_set_sensitive(repl_widgets.replace, nonempty);
1983 gtk_widget_set_sensitive(repl_widgets.all, nonempty);
1984 }
1985}
1986
1987/*
1988 * ":helpfind"
1989 */
Bram Moolenaar071d4272004-06-13 20:20:40 +00001990 void
1991ex_helpfind(eap)
Bram Moolenaarb85cb212009-05-17 14:24:23 +00001992 exarg_T *eap UNUSED;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001993{
1994 /* This will fail when menus are not loaded. Well, it's only for
1995 * backwards compatibility anyway. */
1996 do_cmdline_cmd((char_u *)"emenu ToolBar.FindHelp");
1997}
Bram Moolenaar68fb5dc2012-04-25 17:10:16 +02001998
Bram Moolenaar08bc2742012-06-06 16:14:40 +02001999#if defined(FEAT_BROWSE) || defined(PROTO)
Bram Moolenaar68fb5dc2012-04-25 17:10:16 +02002000 static void
2001recent_func_log_func(const gchar *log_domain UNUSED,
2002 GLogLevelFlags log_level UNUSED,
2003 const gchar *message UNUSED,
2004 gpointer user_data UNUSED)
2005{
2006 /* We just want to suppress the warnings. */
2007 /* http://bugzilla.gnome.org/show_bug.cgi?id=664587 */
2008}
Bram Moolenaar08bc2742012-06-06 16:14:40 +02002009#endif