X-Git-Url: http://git.hcoop.net/bpt/emacs.git/blobdiff_plain/724cde0d1d4e455b72a33689c56d86d3edf8faa6..59961aa44f8b28b097285537b9d97e8cdaaa724e:/src/gtkutil.c diff --git a/src/gtkutil.c b/src/gtkutil.c index 25dd88d544..86a4703d0b 100644 --- a/src/gtkutil.c +++ b/src/gtkutil.c @@ -1,12 +1,11 @@ /* Functions for creating and updating GTK widgets. - Copyright (C) 2003 - Free Software Foundation, Inc. + Copyright (C) 2003, 2004, 2005, 2006, 2007 Free Software Foundation, Inc. This file is part of GNU Emacs. GNU Emacs is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by -the Free Software Foundation; either version 2, or (at your option) +the Free Software Foundation; either version 3, or (at your option) any later version. GNU Emacs is distributed in the hope that it will be useful, @@ -16,17 +15,19 @@ GNU General Public License for more details. You should have received a copy of the GNU General Public License along with GNU Emacs; see the file COPYING. If not, write to -the Free Software Foundation, Inc., 59 Temple Place - Suite 330, -Boston, MA 02111-1307, USA. */ +the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, +Boston, MA 02110-1301, USA. */ #include "config.h" #ifdef USE_GTK #include +#include #include #include "lisp.h" #include "xterm.h" #include "blockinput.h" +#include "syssignal.h" #include "window.h" #include "atimer.h" #include "gtkutil.h" @@ -40,6 +41,9 @@ Boston, MA 02111-1307, USA. */ #define FRAME_TOTAL_PIXEL_HEIGHT(f) \ (FRAME_PIXEL_HEIGHT (f) + FRAME_MENUBAR_HEIGHT (f) + FRAME_TOOLBAR_HEIGHT (f)) +/* Avoid "differ in sign" warnings */ +#define SSDATA(x) ((char *) SDATA (x)) + /*********************************************************************** Display handling functions @@ -127,14 +131,8 @@ xg_display_close (Display *dpy) #ifdef HAVE_GTK_MULTIDISPLAY GdkDisplay *gdpy = gdk_x11_lookup_xdisplay (dpy); - /* GTK 2.2 has a bug that makes gdk_display_close crash (bug - http://bugzilla.gnome.org/show_bug.cgi?id=85715). This way - we can continue running, but there will be memory leaks. */ - -#if GTK_MAJOR_VERSION == 2 && GTK_MINOR_VERSION < 4 - /* If this is the default display, we must change it before calling - dispose, otherwise it will crash. */ + dispose, otherwise it will crash on some Gtk+ versions. */ if (gdk_display_get_default () == gdpy) { struct x_display_info *dpyinfo; @@ -156,10 +154,14 @@ xg_display_close (Display *dpy) gdpy_new); } - g_object_run_dispose (G_OBJECT (gdpy)); + /* GTK 2.2-2.8 has a bug that makes gdk_display_close crash (bug + http://bugzilla.gnome.org/show_bug.cgi?id=85715). This way + we can continue running, but there will be memory leaks. */ +#if GTK_MAJOR_VERSION == 2 && GTK_MINOR_VERSION < 10 + g_object_run_dispose (G_OBJECT (gdpy)); #else - /* I hope this will be fixed in GTK 2.4. It is what bug 85715 says. */ + /* This seems to be fixed in GTK 2.10. */ gdk_display_close (gdpy); #endif #endif /* HAVE_GTK_MULTIDISPLAY */ @@ -195,7 +197,7 @@ malloc_widget_value () } else { - wv = (widget_value *) malloc (sizeof (widget_value)); + wv = (widget_value *) xmalloc (sizeof (widget_value)); malloc_cpt++; } memset (wv, 0, sizeof (widget_value)); @@ -238,6 +240,81 @@ xg_create_default_cursor (dpy) return gdk_cursor_new_for_display (gdpy, GDK_LEFT_PTR); } +/* Apply GMASK to GPIX and return a GdkPixbuf with an alpha channel. */ + +static GdkPixbuf * +xg_get_pixbuf_from_pix_and_mask (gpix, gmask, cmap) + GdkPixmap *gpix; + GdkPixmap *gmask; + GdkColormap *cmap; +{ + int x, y, width, height, rowstride, mask_rowstride; + GdkPixbuf *icon_buf, *tmp_buf; + guchar *pixels; + guchar *mask_pixels; + + gdk_drawable_get_size (gpix, &width, &height); + tmp_buf = gdk_pixbuf_get_from_drawable (NULL, gpix, cmap, + 0, 0, 0, 0, width, height); + icon_buf = gdk_pixbuf_add_alpha (tmp_buf, FALSE, 0, 0, 0); + g_object_unref (G_OBJECT (tmp_buf)); + + if (gmask) + { + GdkPixbuf *mask_buf = gdk_pixbuf_get_from_drawable (NULL, + gmask, + NULL, + 0, 0, 0, 0, + width, height); + guchar *pixels = gdk_pixbuf_get_pixels (icon_buf); + guchar *mask_pixels = gdk_pixbuf_get_pixels (mask_buf); + int rowstride = gdk_pixbuf_get_rowstride (icon_buf); + int mask_rowstride = gdk_pixbuf_get_rowstride (mask_buf); + int y; + + for (y = 0; y < height; ++y) + { + guchar *iconptr, *maskptr; + int x; + + iconptr = pixels + y * rowstride; + maskptr = mask_pixels + y * mask_rowstride; + + for (x = 0; x < width; ++x) + { + /* In a bitmap, RGB is either 255/255/255 or 0/0/0. Checking + just R is sufficient. */ + if (maskptr[0] == 0) + iconptr[3] = 0; /* 0, 1, 2 is R, G, B. 3 is alpha. */ + + iconptr += rowstride/width; + maskptr += mask_rowstride/width; + } + } + + g_object_unref (G_OBJECT (mask_buf)); + } + + return icon_buf; +} + +static Lisp_Object +file_for_image(image) + Lisp_Object image; +{ + Lisp_Object specified_file = Qnil; + Lisp_Object tail; + extern Lisp_Object QCfile; + + for (tail = XCDR (image); + NILP (specified_file) && CONSP (tail) && CONSP (XCDR (tail)); + tail = XCDR (XCDR (tail))) + if (EQ (XCAR (tail), QCfile)) + specified_file = XCAR (XCDR (tail)); + + return specified_file; +} + /* For the image defined in IMG, make and return a GtkImage. For displays with 8 planes or less we must make a GdkPixbuf and apply the mask manually. Otherwise the highlightning and dimming the tool bar code in GTK does @@ -259,119 +336,56 @@ xg_get_image_for_pixmap (f, img, widget, old_widget) GdkPixmap *gpix; GdkPixmap *gmask; GdkDisplay *gdpy; + GdkColormap *cmap; + GdkPixbuf *icon_buf; - /* If we are on a one bit display, let GTK do all the image handling. + /* If we have a file, let GTK do all the image handling. This seems to be the only way to make insensitive and activated icons - look good. */ - if (x_screen_planes (f) == 1) - { - Lisp_Object specified_file = Qnil; - Lisp_Object tail; - extern Lisp_Object QCfile; - - for (tail = XCDR (img->spec); - NILP (specified_file) && CONSP (tail) && CONSP (XCDR (tail)); - tail = XCDR (XCDR (tail))) - if (EQ (XCAR (tail), QCfile)) - specified_file = XCAR (XCDR (tail)); - - if (STRINGP (specified_file)) - { - - Lisp_Object file = Qnil; - struct gcpro gcpro1; - GCPRO1 (file); + look good in all cases. */ + Lisp_Object specified_file = file_for_image (img->spec); + Lisp_Object file; - file = x_find_image_file (specified_file); - /* We already loaded the image once before calling this - function, so this should not fail. */ - xassert (STRINGP (file) != 0); + /* We already loaded the image once before calling this + function, so this only fails if the image file has been removed. + In that case, use the pixmap already loaded. */ - if (! old_widget) - old_widget = GTK_IMAGE (gtk_image_new_from_file (SDATA (file))); - else - gtk_image_set_from_file (old_widget, SDATA (file)); + if (STRINGP (specified_file) + && STRINGP (file = x_find_image_file (specified_file))) + { + if (! old_widget) + old_widget = GTK_IMAGE (gtk_image_new_from_file (SSDATA (file))); + else + gtk_image_set_from_file (old_widget, SSDATA (file)); - UNGCPRO; - return GTK_WIDGET (old_widget); - } + return GTK_WIDGET (old_widget); } + /* No file, do the image handling ourselves. This will look very bad + on a monochrome display, and sometimes bad on all displays with + certain themes. */ + gdpy = gdk_x11_lookup_xdisplay (FRAME_X_DISPLAY (f)); gpix = gdk_pixmap_foreign_new_for_display (gdpy, img->pixmap); gmask = img->mask ? gdk_pixmap_foreign_new_for_display (gdpy, img->mask) : 0; - if (x_screen_planes (f) > 8 || x_screen_planes (f) == 1) - { - if (! old_widget) - old_widget = GTK_IMAGE (gtk_image_new_from_pixmap (gpix, gmask)); - else - gtk_image_set_from_pixmap (old_widget, gpix, gmask); - } + /* This is a workaround to make icons look good on pseudo color + displays. Apparently GTK expects the images to have an alpha + channel. If they don't, insensitive and activated icons will + look bad. This workaround does not work on monochrome displays, + and is strictly not needed on true color/static color displays (i.e. + 16 bits and higher). But we do it anyway so we get a pixbuf that is + not associated with the img->pixmap. The img->pixmap may be removed + by clearing the image cache and then the tool bar redraw fails, since + Gtk+ assumes the pixmap is always there. */ + cmap = gtk_widget_get_colormap (widget); + icon_buf = xg_get_pixbuf_from_pix_and_mask (gpix, gmask, cmap); + + if (! old_widget) + old_widget = GTK_IMAGE (gtk_image_new_from_pixbuf (icon_buf)); else - { - /* This is a workaround to make icons look good on pseudo color - displays. Apparently GTK expects the images to have an alpha - channel. If they don't, insensitive and activated icons will - look bad. This workaround does not work on monochrome displays, - and is not needed on true color/static color displays (i.e. - 16 bits and higher). */ - int x, y, width, height, rowstride, mask_rowstride; - GdkPixbuf *icon_buf, *tmp_buf; - guchar *pixels; - guchar *mask_pixels; - - gdk_drawable_get_size (gpix, &width, &height); - tmp_buf = gdk_pixbuf_get_from_drawable (NULL, - gpix, - gtk_widget_get_colormap (widget), - 0, 0, 0, 0, width, height); - icon_buf = gdk_pixbuf_add_alpha (tmp_buf, FALSE, 0, 0, 0); - g_object_unref (G_OBJECT (tmp_buf)); - - if (gmask) - { - GdkPixbuf *mask_buf = gdk_pixbuf_get_from_drawable (NULL, - gmask, - NULL, - 0, 0, 0, 0, - width, height); - guchar *pixels = gdk_pixbuf_get_pixels (icon_buf); - guchar *mask_pixels = gdk_pixbuf_get_pixels (mask_buf); - int rowstride = gdk_pixbuf_get_rowstride (icon_buf); - int mask_rowstride = gdk_pixbuf_get_rowstride (mask_buf); - int y; - - for (y = 0; y < height; ++y) - { - guchar *iconptr, *maskptr; - int x; + gtk_image_set_from_pixbuf (old_widget, icon_buf); - iconptr = pixels + y * rowstride; - maskptr = mask_pixels + y * mask_rowstride; - - for (x = 0; x < width; ++x) - { - /* In a bitmap, RGB is either 255/255/255 or 0/0/0. Checking - just R is sufficient. */ - if (maskptr[0] == 0) - iconptr[3] = 0; /* 0, 1, 2 is R, G, B. 3 is alpha. */ - - iconptr += rowstride/width; - maskptr += mask_rowstride/width; - } - } - - g_object_unref (G_OBJECT (mask_buf)); - } - - if (! old_widget) - old_widget = GTK_IMAGE (gtk_image_new_from_pixbuf (icon_buf)); - else - gtk_image_set_from_pixbuf (old_widget, icon_buf); - - g_object_unref (G_OBJECT (icon_buf)); - } + g_object_unref (G_OBJECT (icon_buf)); g_object_unref (G_OBJECT (gpix)); if (gmask) g_object_unref (G_OBJECT (gmask)); @@ -496,10 +510,66 @@ get_utf8_string (str) { char *utf8_str = str; + if (!str) return NULL; + /* If not UTF-8, try current locale. */ - if (str && !g_utf8_validate (str, -1, NULL)) + if (!g_utf8_validate (str, -1, NULL)) utf8_str = g_locale_to_utf8 (str, -1, 0, 0, 0); + if (!utf8_str) + { + /* Probably some control characters in str. Escape them. */ + size_t nr_bad = 0; + gsize bytes_read; + gsize bytes_written; + unsigned char *p = (unsigned char *)str; + char *cp, *up; + GError *error = NULL; + + while (! (cp = g_locale_to_utf8 ((char *)p, -1, &bytes_read, + &bytes_written, &error)) + && error->code == G_CONVERT_ERROR_ILLEGAL_SEQUENCE) + { + ++nr_bad; + p += bytes_written+1; + g_error_free (error); + error = NULL; + } + + if (error) + { + g_error_free (error); + error = NULL; + } + if (cp) g_free (cp); + + up = utf8_str = xmalloc (strlen (str) + nr_bad * 4 + 1); + p = (unsigned char *)str; + + while (! (cp = g_locale_to_utf8 ((char *)p, -1, &bytes_read, + &bytes_written, &error)) + && error->code == G_CONVERT_ERROR_ILLEGAL_SEQUENCE) + { + strncpy (up, (char *)p, bytes_written); + sprintf (up + bytes_written, "\\%03o", p[bytes_written]); + up[bytes_written+4] = '\0'; + up += bytes_written+4; + p += bytes_written+1; + g_error_free (error); + error = NULL; + } + + if (cp) + { + strcat (utf8_str, cp); + g_free (cp); + } + if (error) + { + g_error_free (error); + error = NULL; + } + } return utf8_str; } @@ -540,6 +610,9 @@ xg_set_geometry (f) if (!gtk_window_parse_geometry (GTK_WINDOW (FRAME_GTK_OUTER_WIDGET (f)), geom_str)) fprintf (stderr, "Failed to parse: '%s'\n", geom_str); + } else if (f->size_hint_flags & PPosition) { + gtk_window_move (GTK_WINDOW (FRAME_GTK_OUTER_WIDGET (f)), + f->left_pos, f->top_pos); } } @@ -554,12 +627,6 @@ xg_resize_outer_widget (f, columns, rows) int columns; int rows; { - gtk_window_resize (GTK_WINDOW (FRAME_GTK_OUTER_WIDGET (f)), - FRAME_PIXEL_WIDTH (f), FRAME_TOTAL_PIXEL_HEIGHT (f)); - - /* base_height is now changed. */ - x_wm_set_size_hint (f, 0, 0); - /* If we are not mapped yet, set geometry once again, as window height now have changed. */ if (! GTK_WIDGET_MAPPED (FRAME_GTK_OUTER_WIDGET (f))) @@ -582,14 +649,14 @@ xg_resize_widgets (f, pixelwidth, pixelheight) { int mbheight = FRAME_MENUBAR_HEIGHT (f); int tbheight = FRAME_TOOLBAR_HEIGHT (f); - int rows = FRAME_PIXEL_HEIGHT_TO_TEXT_LINES (f, (pixelheight + int rows = FRAME_PIXEL_HEIGHT_TO_TEXT_LINES (f, (pixelheight - mbheight - tbheight)); int columns = FRAME_PIXEL_WIDTH_TO_TEXT_COLS (f, pixelwidth); if (FRAME_GTK_WIDGET (f) - && (columns != FRAME_COLS (f) + && (columns != FRAME_COLS (f) || rows != FRAME_LINES (f) - || pixelwidth != FRAME_PIXEL_WIDTH (f) + || pixelwidth != FRAME_PIXEL_WIDTH (f) || pixelheight != FRAME_PIXEL_HEIGHT (f))) { struct x_output *x = f->output_data.x; @@ -717,17 +784,18 @@ xg_create_frame_widgets (f) if (wvbox) gtk_widget_destroy (wvbox); if (wfixed) gtk_widget_destroy (wfixed); + UNBLOCK_INPUT; return 0; } /* Use same names as the Xt port does. I.e. Emacs.pane.emacs by default */ gtk_widget_set_name (wtop, EMACS_CLASS); gtk_widget_set_name (wvbox, "pane"); - gtk_widget_set_name (wfixed, SDATA (Vx_resource_name)); + gtk_widget_set_name (wfixed, SSDATA (Vx_resource_name)); /* If this frame has a title or name, set it in the title bar. */ - if (! NILP (f->title)) title = SDATA (ENCODE_UTF_8 (f->title)); - else if (! NILP (f->name)) title = SDATA (ENCODE_UTF_8 (f->name)); + if (! NILP (f->title)) title = SSDATA (ENCODE_UTF_8 (f->title)); + else if (! NILP (f->name)) title = SSDATA (ENCODE_UTF_8 (f->name)); if (title) gtk_window_set_title (GTK_WINDOW (wtop), title); @@ -754,7 +822,7 @@ xg_create_frame_widgets (f) So we cheat a bit by setting a height that is what it will have later on when tool bar items are added. */ if (FRAME_EXTERNAL_TOOL_BAR (f) && f->n_tool_bar_items == 0) - FRAME_TOOLBAR_HEIGHT (f) = 34; + FRAME_TOOLBAR_HEIGHT (f) = 38; /* We don't want this widget double buffered, because we draw on it @@ -768,8 +836,8 @@ xg_create_frame_widgets (f) can't shrink the window from its starting size. */ gtk_window_set_policy (GTK_WINDOW (wtop), TRUE, TRUE, TRUE); gtk_window_set_wmclass (GTK_WINDOW (wtop), - SDATA (Vx_resource_name), - SDATA (Vx_resource_class)); + SSDATA (Vx_resource_name), + SSDATA (Vx_resource_class)); /* Add callback to do nothing on WM_DELETE_WINDOW. The default in GTK is to destroy the widget. We want Emacs to do that instead. */ @@ -800,7 +868,7 @@ xg_create_frame_widgets (f) /* Since GTK clears its window by filling with the background color, we must keep X and GTK background in sync. */ - xg_pix_to_gcolor (wfixed, f->output_data.x->background_pixel, &bg); + xg_pix_to_gcolor (wfixed, FRAME_BACKGROUND_PIXEL (f), &bg); gtk_widget_modify_bg (wfixed, GTK_STATE_NORMAL, &bg); /* Also, do not let any background pixmap to be set, this looks very @@ -943,6 +1011,24 @@ xg_set_background_color (f, bg) } +/* Set the frame icon to ICON_PIXMAP/MASK. This must be done with GTK + functions so GTK does not overwrite the icon. */ + +void +xg_set_frame_icon (f, icon_pixmap, icon_mask) + FRAME_PTR f; + Pixmap icon_pixmap; + Pixmap icon_mask; +{ + GdkDisplay *gdpy = gdk_x11_lookup_xdisplay (FRAME_X_DISPLAY (f)); + GdkPixmap *gpix = gdk_pixmap_foreign_new_for_display (gdpy, icon_pixmap); + GdkPixmap *gmask = gdk_pixmap_foreign_new_for_display (gdpy, icon_mask); + GdkPixbuf *gp = xg_get_pixbuf_from_pix_and_mask (gpix, gmask, NULL); + + gtk_window_set_icon (GTK_WINDOW (FRAME_GTK_OUTER_WIDGET (f)), gp); +} + + /*********************************************************************** Dialog functions @@ -1122,10 +1208,27 @@ create_dialog (wv, select_cb, deactivate_cb) /*********************************************************************** File dialog functions ***********************************************************************/ +/* Return non-zero if the old file selection dialog is being used. + Return zero if not. */ + +int +xg_uses_old_file_dialog () +{ #ifdef HAVE_GTK_FILE_BOTH -int use_old_gtk_file_dialog; + extern int x_gtk_use_old_file_dialog; + return x_gtk_use_old_file_dialog; +#else /* ! HAVE_GTK_FILE_BOTH */ + +#ifdef HAVE_GTK_FILE_SELECTION_NEW + return 1; +#else + return 0; #endif +#endif /* ! HAVE_GTK_FILE_BOTH */ +} + + /* Function that is called when the file dialog pops down. W is the dialog widget, RESPONSE is the response code. USER_DATA is what we passed in to g_signal_connect (pointer to int). */ @@ -1170,6 +1273,59 @@ xg_get_file_name_from_chooser (w) return gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (w)); } +/* Callback called when the "Show hidden files" toggle is pressed. + WIDGET is the toggle widget, DATA is the file chooser dialog. */ + +static void +xg_toggle_visibility_cb (widget, data) + GtkWidget *widget; + gpointer data; +{ + GtkFileChooser *dialog = GTK_FILE_CHOOSER (data); + gboolean visible; + g_object_get (G_OBJECT (dialog), "show-hidden", &visible, NULL); + g_object_set (G_OBJECT (dialog), "show-hidden", !visible, NULL); +} + + +/* Callback called when a property changes in a file chooser. + GOBJECT is the file chooser dialog, ARG1 describes the property. + USER_DATA is the toggle widget in the file chooser dialog. + We use this to update the "Show hidden files" toggle when the user + changes that property by right clicking in the file list. */ + +static void +xg_toggle_notify_cb (gobject, arg1, user_data) + GObject *gobject; + GParamSpec *arg1; + gpointer user_data; +{ + extern int x_gtk_show_hidden_files; + + if (strcmp (arg1->name, "show-hidden") == 0) + { + GtkFileChooser *dialog = GTK_FILE_CHOOSER (gobject); + GtkWidget *wtoggle = GTK_WIDGET (user_data); + gboolean visible, toggle_on; + + g_object_get (G_OBJECT (gobject), "show-hidden", &visible, NULL); + toggle_on = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (wtoggle)); + + if (!!visible != !!toggle_on) + { + g_signal_handlers_block_by_func (G_OBJECT (wtoggle), + G_CALLBACK (xg_toggle_visibility_cb), + gobject); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (wtoggle), visible); + g_signal_handlers_unblock_by_func + (G_OBJECT (wtoggle), + G_CALLBACK (xg_toggle_visibility_cb), + gobject); + } + x_gtk_show_hidden_files = visible; + } +} + /* Read a file name from the user using a file chooser dialog. F is the current frame. PROMPT is a prompt to show to the user. May not be NULL. @@ -1189,11 +1345,16 @@ xg_get_file_with_chooser (f, prompt, default_filename, int mustmatch_p, only_dir_p; xg_get_file_func *func; { - GtkWidget *filewin; + char message[1024]; + + GtkWidget *filewin, *wtoggle, *wbox, *wmessage; GtkWindow *gwin = GTK_WINDOW (FRAME_GTK_OUTER_WIDGET (f)); GtkFileChooserAction action = (mustmatch_p ? GTK_FILE_CHOOSER_ACTION_OPEN : GTK_FILE_CHOOSER_ACTION_SAVE); + extern int x_gtk_show_hidden_files; + extern int x_gtk_file_dialog_help_text; + if (only_dir_p) action = GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER; @@ -1204,24 +1365,74 @@ xg_get_file_with_chooser (f, prompt, default_filename, GTK_STOCK_OPEN : GTK_STOCK_OK), GTK_RESPONSE_OK, NULL); + gtk_file_chooser_set_local_only (GTK_FILE_CHOOSER (filewin), TRUE); + + wbox = gtk_vbox_new (FALSE, 0); + gtk_widget_show (wbox); + wtoggle = gtk_check_button_new_with_label ("Show hidden files."); + + if (x_gtk_show_hidden_files) + { + g_object_set (G_OBJECT (filewin), "show-hidden", TRUE, NULL); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (wtoggle), TRUE); + } + gtk_widget_show (wtoggle); + g_signal_connect (G_OBJECT (wtoggle), "clicked", + G_CALLBACK (xg_toggle_visibility_cb), filewin); + g_signal_connect (G_OBJECT (filewin), "notify", + G_CALLBACK (xg_toggle_notify_cb), wtoggle); + + if (x_gtk_file_dialog_help_text) + { + message[0] = '\0'; + /* Gtk+ 2.10 has the file name text entry box integrated in the dialog. + Show the C-l help text only for versions < 2.10. */ + if (gtk_check_version (2, 10, 0) && action != GTK_FILE_CHOOSER_ACTION_SAVE) + strcat (message, "\nType C-l to display a file name text entry box.\n"); + strcat (message, "\nIf you don't like this file selector, use the " + "corresponding\nkey binding or customize " + "use-file-dialog to turn it off."); + + wmessage = gtk_label_new (message); + gtk_widget_show (wmessage); + } + + gtk_box_pack_start (GTK_BOX (wbox), wtoggle, FALSE, FALSE, 0); + if (x_gtk_file_dialog_help_text) + gtk_box_pack_start (GTK_BOX (wbox), wmessage, FALSE, FALSE, 0); + gtk_file_chooser_set_extra_widget (GTK_FILE_CHOOSER (filewin), wbox); if (default_filename) { Lisp_Object file; struct gcpro gcpro1; + char *utf8_filename; GCPRO1 (file); + file = build_string (default_filename); + /* File chooser does not understand ~/... in the file name. It must be an absolute name starting with /. */ if (default_filename[0] != '/') + file = Fexpand_file_name (file, Qnil); + + utf8_filename = SSDATA (ENCODE_UTF_8 (file)); + if (! NILP (Ffile_directory_p (file))) + gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (filewin), + utf8_filename); + else { - file = Fexpand_file_name (build_string (default_filename), Qnil); - default_filename = SDATA (file); + gtk_file_chooser_set_filename (GTK_FILE_CHOOSER (filewin), + utf8_filename); + if (action == GTK_FILE_CHOOSER_ACTION_SAVE) + { + char *cp = strrchr (utf8_filename, '/'); + if (cp) ++cp; + else cp = utf8_filename; + gtk_file_chooser_set_current_name (GTK_FILE_CHOOSER (filewin), cp); + } } - gtk_file_chooser_set_filename (GTK_FILE_CHOOSER (filewin), - default_filename); - UNGCPRO; } @@ -1310,8 +1521,16 @@ xg_get_file_name (f, prompt, default_filename, mustmatch_p, only_dir_p) int filesel_done = 0; xg_get_file_func func; +#if defined (HAVE_GTK_AND_PTHREAD) && defined (__SIGRTMIN) + /* I really don't know why this is needed, but without this the GLIBC add on + library linuxthreads hangs when the Gnome file chooser backend creates + threads. */ + sigblock (sigmask (__SIGRTMIN)); +#endif /* HAVE_GTK_AND_PTHREAD */ + #ifdef HAVE_GTK_FILE_BOTH - if (use_old_gtk_file_dialog) + + if (xg_uses_old_file_dialog ()) w = xg_get_file_with_selection (f, prompt, default_filename, mustmatch_p, only_dir_p, &func); else @@ -1355,6 +1574,10 @@ xg_get_file_name (f, prompt, default_filename, mustmatch_p, only_dir_p) gtk_main_iteration (); } +#if defined (HAVE_GTK_AND_PTHREAD) && defined (__SIGRTMIN) + sigunblock (sigmask (__SIGRTMIN)); +#endif + if (filesel_done == GTK_RESPONSE_OK) fn = (*func) (w); @@ -1498,9 +1721,9 @@ menuitem_destroy_callback (w, client_data) } /* Callback called when the pointer enters/leaves a menu item. - W is the menu item. + W is the parent of the menu item. EVENT is either an enter event or leave event. - CLIENT_DATA points to the xg_menu_item_cb_data associated with the W. + CLIENT_DATA is not used. Returns FALSE to tell GTK to keep processing this event. */ @@ -1510,15 +1733,21 @@ menuitem_highlight_callback (w, event, client_data) GdkEventCrossing *event; gpointer client_data; { - if (client_data) + GdkEvent ev; + GtkWidget *subwidget; + xg_menu_item_cb_data *data; + + ev.crossing = *event; + subwidget = gtk_get_event_widget (&ev); + data = (xg_menu_item_cb_data *) g_object_get_data (G_OBJECT (subwidget), + XG_ITEM_DATA); + if (data) { - xg_menu_item_cb_data *data = (xg_menu_item_cb_data*) client_data; - gpointer call_data = event->type == GDK_LEAVE_NOTIFY ? 0 : client_data; - if (! NILP (data->help) && data->cl_data->highlight_cb) { + gpointer call_data = event->type == GDK_LEAVE_NOTIFY ? 0 : data; GtkCallback func = (GtkCallback) data->cl_data->highlight_cb; - (*func) (w, call_data); + (*func) (subwidget, call_data); } } @@ -1545,19 +1774,19 @@ menu_destroy_callback (w, client_data) UNGRAB_P is TRUE if this is an ungrab, FALSE if it is a grab. CLIENT_DATA is NULL (not used). */ +/* Keep track of total number of grabs. */ +static int menu_grab_callback_cnt; + static void menu_grab_callback (GtkWidget *widget, gboolean ungrab_p, gpointer client_data) { - /* Keep track of total number of grabs. */ - static int cnt; - - if (ungrab_p) cnt--; - else cnt++; + if (ungrab_p) menu_grab_callback_cnt--; + else menu_grab_callback_cnt++; - if (cnt > 0 && ! xg_timer) xg_start_timer (); - else if (cnt == 0 && xg_timer) xg_stop_timer (); + if (menu_grab_callback_cnt > 0 && ! xg_timer) xg_start_timer (); + else if (menu_grab_callback_cnt == 0 && xg_timer) xg_stop_timer (); } /* Make a GTK widget that contains both UTF8_LABEL and UTF8_KEY (both @@ -1655,6 +1884,24 @@ make_menu_item (utf8_label, utf8_key, item, group) /* Return non-zero if LABEL specifies a separator (GTK only has one separator type) */ +static char* separator_names[] = { + "space", + "no-line", + "single-line", + "double-line", + "single-dashed-line", + "double-dashed-line", + "shadow-etched-in", + "shadow-etched-out", + "shadow-etched-in-dash", + "shadow-etched-out-dash", + "shadow-double-etched-in", + "shadow-double-etched-out", + "shadow-double-etched-in-dash", + "shadow-double-etched-out-dash", + 0, +}; + static int xg_separator_p (char *label) { @@ -1663,24 +1910,6 @@ xg_separator_p (char *label) && strncmp (label, "--", 2) == 0 && label[2] != '-') { - static char* separator_names[] = { - "space", - "no-line", - "single-line", - "double-line", - "single-dashed-line", - "double-dashed-line", - "shadow-etched-in", - "shadow-etched-out", - "shadow-etched-in-dash", - "shadow-etched-out-dash", - "shadow-double-etched-in", - "shadow-double-etched-out", - "shadow-double-etched-in-dash", - "shadow-double-etched-out-dash", - 0, - }; - int i; label += 2; @@ -1784,7 +2013,7 @@ xg_create_one_menuitem (item, f, select_cb, highlight_cb, cl_data, group) xg_list_insert (&xg_menu_item_cb_list, &cb_data->ptrs); - cb_data->unhighlight_id = cb_data->highlight_id = cb_data->select_id = 0; + cb_data->select_id = 0; cb_data->help = item->help; cb_data->cl_data = cl_data; cb_data->call_data = item->call_data; @@ -1805,25 +2034,37 @@ xg_create_one_menuitem (item, f, select_cb, highlight_cb, cl_data, group) = g_signal_connect (G_OBJECT (w), "activate", select_cb, cb_data); } - if (! NILP (item->help) && highlight_cb) + return w; +} + +/* Callback called when keyboard traversal (started by x-menu-bar-open) ends. + WMENU is the menu for which traversal has been done. DATA points to the + frame for WMENU. We must release grabs, some bad interaction between GTK + and Emacs makes the menus keep the grabs. */ + +static void +menu_nav_ended (wmenu, data) + GtkMenuShell *wmenu; + gpointer data; +{ + FRAME_PTR f = (FRAME_PTR) data; + + if (FRAME_X_OUTPUT (f)->menubar_widget) { - /* We use enter/leave notify instead of select/deselect because - select/deselect doesn't go well with detached menus. */ - cb_data->highlight_id - = g_signal_connect (G_OBJECT (w), - "enter-notify-event", - G_CALLBACK (menuitem_highlight_callback), - cb_data); - cb_data->unhighlight_id - = g_signal_connect (G_OBJECT (w), - "leave-notify-event", - G_CALLBACK (menuitem_highlight_callback), - cb_data); - } + GtkMenuShell *w = GTK_MENU_SHELL (FRAME_X_OUTPUT (f)->menubar_widget); + Display *dpy = FRAME_X_DISPLAY (f); - return w; + BLOCK_INPUT; + gtk_menu_shell_deactivate (w); + gtk_menu_shell_deselect (w); + + XUngrabKeyboard (dpy, CurrentTime); + XUngrabPointer (dpy, CurrentTime); + UNBLOCK_INPUT; + } } + static GtkWidget *create_menus P_ ((widget_value *, FRAME_PTR, GCallback, GCallback, GCallback, int, int, int, GtkWidget *, xg_menu_cb_data *, char *)); @@ -1875,9 +2116,26 @@ create_menus (data, f, select_cb, deactivate_cb, highlight_cb, { wmenu = gtk_menu_new (); xg_set_screen (wmenu, f); + /* Connect this to the menu instead of items so we get enter/leave for + disabled items also. TODO: Still does not get enter/leave for + disabled items in detached menus. */ + g_signal_connect (G_OBJECT (wmenu), + "enter-notify-event", + G_CALLBACK (menuitem_highlight_callback), + NULL); + g_signal_connect (G_OBJECT (wmenu), + "leave-notify-event", + G_CALLBACK (menuitem_highlight_callback), + NULL); } else wmenu = gtk_menu_bar_new (); + /* Fix up grabs after keyboard traversal ends. */ + g_signal_connect (G_OBJECT (wmenu), + "selection-done", + G_CALLBACK (menu_nav_ended), + f); + /* Put cl_data on the top menu for easier access. */ cl_data = make_cl_data (cl_data, f, highlight_cb); g_object_set_data (G_OBJECT (wmenu), XG_FRAME_DATA, (gpointer)cl_data); @@ -1889,7 +2147,7 @@ create_menus (data, f, select_cb, deactivate_cb, highlight_cb, if (deactivate_cb) g_signal_connect (G_OBJECT (wmenu), - "deactivate", deactivate_cb, 0); + "selection-done", deactivate_cb, 0); g_signal_connect (G_OBJECT (wmenu), "grab-notify", G_CALLBACK (menu_grab_callback), 0); @@ -1937,7 +2195,9 @@ create_menus (data, f, select_cb, deactivate_cb, highlight_cb, cl_data, &group); - if (item->contents) + /* Create a possibly empty submenu for menu bar items, since some + themes don't highlight items correctly without it. */ + if (item->contents || menu_bar_p) { GtkWidget *submenu = create_menus (item->contents, f, @@ -2224,8 +2484,14 @@ xg_update_menubar (menubar, f, list, iter, pos, val, cl_data, &group); + /* Create a possibly empty submenu for menu bar items, since some + themes don't highlight items correctly without it. */ + GtkWidget *submenu = create_menus (NULL, f, + select_cb, NULL, highlight_cb, + 0, 0, 0, 0, cl_data, 0); gtk_widget_set_name (w, MENU_ITEM_NAME); gtk_menu_shell_insert (GTK_MENU_SHELL (menubar), w, pos); + gtk_menu_item_set_submenu (GTK_MENU_ITEM (w), submenu); g_list_free (*list); *list = iter = gtk_container_get_children (GTK_CONTAINER (menubar)); @@ -2251,6 +2517,7 @@ xg_update_menubar (menubar, f, list, iter, pos, val, g_list_free (*list); *list = iter = gtk_container_get_children (GTK_CONTAINER (menubar)); while (nr-- > 0) iter = g_list_next (iter); + if (iter) iter = g_list_next (iter); val = val->next; ++pos; } @@ -2365,37 +2632,6 @@ xg_update_menu_item (val, w, select_cb, highlight_cb, cl_data) g_signal_handler_disconnect (w, cb_data->select_id); cb_data->select_id = 0; } - - if (NILP (cb_data->help)) - { - /* Shall not have help. Remove if any existed previously. */ - if (cb_data->highlight_id) - { - g_signal_handler_disconnect (G_OBJECT (w), - cb_data->highlight_id); - cb_data->highlight_id = 0; - } - if (cb_data->unhighlight_id) - { - g_signal_handler_disconnect (G_OBJECT (w), - cb_data->unhighlight_id); - cb_data->unhighlight_id = 0; - } - } - else if (! cb_data->highlight_id && highlight_cb) - { - /* Have help now, but didn't previously. Add callback. */ - cb_data->highlight_id - = g_signal_connect (G_OBJECT (w), - "enter-notify-event", - G_CALLBACK (menuitem_highlight_callback), - cb_data); - cb_data->unhighlight_id - = g_signal_connect (G_OBJECT (w), - "leave-notify-event", - G_CALLBACK (menuitem_highlight_callback), - cb_data); - } } } @@ -2596,7 +2832,7 @@ xg_modify_menubar_widgets (menubar, f, val, deep_p, xg_update_menubar (menubar, f, &list, list, 0, val->contents, select_cb, highlight_cb, cl_data); - if (deep_p); + if (deep_p) { widget_value *cur; @@ -2834,7 +3070,7 @@ xg_gtk_scroll_destroy (widget, data) gpointer data; { gpointer p; - int id = (int)data; + int id = (int) (EMACS_INT) data; /* The EMACS_INT cast avoids a warning. */ p = g_object_get_data (G_OBJECT (widget), XG_LAST_SB_DATA); if (p) xfree (p); @@ -2865,7 +3101,7 @@ scroll_bar_button_cb (widget, event, user_data) if (xg_timer) xg_stop_timer (); bar->dragging = Qnil; } - + return FALSE; } @@ -2904,10 +3140,11 @@ xg_create_scroll_bar (f, bar, scroll_callback, scroll_bar_name) "value-changed", scroll_callback, (gpointer) bar); + /* The EMACS_INT cast avoids a warning. */ g_signal_connect (G_OBJECT (wscroll), "destroy", G_CALLBACK (xg_gtk_scroll_destroy), - (gpointer) scroll_id); + (gpointer) (EMACS_INT) scroll_id); /* Connect to button press and button release to detect if any scroll bar has the pointer. */ @@ -2928,7 +3165,7 @@ xg_create_scroll_bar (f, bar, scroll_callback, scroll_bar_name) event box window. */ gtk_fixed_put (GTK_FIXED (f->output_data.x->edit_widget), webox, -1, -1); gtk_container_add (GTK_CONTAINER (webox), wscroll); - + /* Set the cursor to an arrow. */ xg_set_cursor (webox, FRAME_X_DISPLAY_INFO (f)->xg_cursor); @@ -2987,9 +3224,14 @@ xg_update_scrollbar_pos (f, scrollbar_id, top, left, width, height) GtkWidget *wparent = gtk_widget_get_parent (wscroll); /* Move and resize to new values. */ - gtk_widget_set_size_request (wscroll, width, height); gtk_fixed_move (GTK_FIXED (wfixed), wparent, left, top); - + gtk_widget_set_size_request (wscroll, width, height); + gtk_widget_queue_draw (wparent); + gdk_window_process_all_updates (); + /* GTK does not redraw until the main loop is entered again, but + if there are no X events pending we will not enter it. So we sync + here to get some events. */ + x_sync (f); SET_FRAME_GARBAGED (f); cancel_mouse_face (f); } @@ -3086,6 +3328,40 @@ xg_set_toolkit_scroll_bar_thumb (bar, portion, position, whole) the GtkImage with a new image. */ #define XG_TOOL_BAR_IMAGE_DATA "emacs-tool-bar-image" +/* The key for storing the latest modifiers so the activate callback can + get them. */ +#define XG_TOOL_BAR_LAST_MODIFIER "emacs-tool-bar-modifier" + +/* The key for storing the button widget in its proxy menu item. */ +#define XG_TOOL_BAR_PROXY_BUTTON "emacs-tool-bar-proxy-button" + +/* The key for the data we put in the GtkImage widgets. The data is + the stock name used by Emacs. We use this to see if we need to update + the GtkImage with a new image. */ +#define XG_TOOL_BAR_STOCK_NAME "emacs-tool-bar-stock-name" + +/* As above, but this is used for named theme widgets, as opposed to + stock items. */ +#define XG_TOOL_BAR_ICON_NAME "emacs-tool-bar-icon-name" + +/* Callback function invoked when a tool bar item is pressed. + W is the button widget in the tool bar that got pressed, + CLIENT_DATA is an integer that is the index of the button in the + tool bar. 0 is the first button. */ + +static gboolean +xg_tool_bar_button_cb (widget, event, user_data) + GtkWidget *widget; + GdkEventButton *event; + gpointer user_data; +{ + /* Casts to avoid warnings when gpointer is 64 bits and int is 32 bits */ + gpointer ptr = (gpointer) (EMACS_INT) event->state; + g_object_set_data (G_OBJECT (widget), XG_TOOL_BAR_LAST_MODIFIER, ptr); + return FALSE; +} + + /* Callback function invoked when a tool bar item is pressed. W is the button widget in the tool bar that got pressed, CLIENT_DATA is an integer that is the index of the button in the @@ -3096,7 +3372,11 @@ xg_tool_bar_callback (w, client_data) GtkWidget *w; gpointer client_data; { - int idx = (int)client_data; + /* The EMACS_INT cast avoids a warning. */ + int idx = (int) (EMACS_INT) client_data; + int mod = (int) (EMACS_INT) g_object_get_data (G_OBJECT (w), + XG_TOOL_BAR_LAST_MODIFIER); + FRAME_PTR f = (FRAME_PTR) g_object_get_data (G_OBJECT (w), XG_FRAME_DATA); Lisp_Object key, frame; struct input_event event; @@ -3109,18 +3389,129 @@ xg_tool_bar_callback (w, client_data) key = AREF (f->tool_bar_items, idx + TOOL_BAR_ITEM_KEY); XSETFRAME (frame, f); - event.kind = TOOL_BAR_EVENT; - event.frame_or_window = frame; - event.arg = frame; - kbd_buffer_store_event (&event); + /* We generate two events here. The first one is to set the prefix + to `(tool_bar)', see keyboard.c. */ event.kind = TOOL_BAR_EVENT; event.frame_or_window = frame; + event.arg = frame; + kbd_buffer_store_event (&event); + + event.kind = TOOL_BAR_EVENT; + event.frame_or_window = frame; event.arg = key; - event.modifiers = 0; /* These are not available. */ + /* Convert between the modifier bits GDK uses and the modifier bits + Emacs uses. This assumes GDK an X masks are the same, which they are when + this is written. */ + event.modifiers = x_x_to_emacs_modifiers (FRAME_X_DISPLAY_INFO (f), mod); kbd_buffer_store_event (&event); } +/* Callback function invoked when a tool bar item is pressed in a detached + tool bar or the overflow drop down menu. + We just call xg_tool_bar_callback. + W is the menu item widget that got pressed, + CLIENT_DATA is an integer that is the index of the button in the + tool bar. 0 is the first button. */ + +static void +xg_tool_bar_proxy_callback (w, client_data) + GtkWidget *w; + gpointer client_data; +{ + GtkWidget *wbutton = GTK_WIDGET (g_object_get_data (G_OBJECT (w), + XG_TOOL_BAR_PROXY_BUTTON)); + xg_tool_bar_callback (wbutton, client_data); +} + +/* This callback is called when a tool item should create a proxy item, + such as for the overflow menu. Also called when the tool bar is detached. + If we don't create a proxy menu item, the detached tool bar will be + blank. */ + +static gboolean +xg_tool_bar_menu_proxy (toolitem, user_data) + GtkToolItem *toolitem; + gpointer user_data; +{ + GtkWidget *weventbox = gtk_bin_get_child (GTK_BIN (toolitem)); + GtkButton *wbutton = GTK_BUTTON (gtk_bin_get_child (GTK_BIN (weventbox))); + GtkWidget *wmenuitem = gtk_image_menu_item_new (); + GtkWidget *wmenuimage; + + if (gtk_button_get_use_stock (wbutton)) + wmenuimage = gtk_image_new_from_stock (gtk_button_get_label (wbutton), + GTK_ICON_SIZE_MENU); + else + { + GtkImage *wimage = GTK_IMAGE (gtk_bin_get_child (GTK_BIN (wbutton))); + GtkSettings *settings = gtk_widget_get_settings (GTK_WIDGET (wbutton)); + GtkImageType store_type = gtk_image_get_storage_type (wimage); + if (store_type == GTK_IMAGE_STOCK) + { + gchar *stock_id; + gtk_image_get_stock (wimage, &stock_id, NULL); + wmenuimage = gtk_image_new_from_stock (stock_id, GTK_ICON_SIZE_MENU); + } + else if (store_type == GTK_IMAGE_ICON_SET) + { + GtkIconSet *icon_set; + gtk_image_get_icon_set (wimage, &icon_set, NULL); + wmenuimage = gtk_image_new_from_icon_set (icon_set, + GTK_ICON_SIZE_MENU); + } + else if (store_type == GTK_IMAGE_PIXBUF) + { + gint width, height; + + if (settings && + gtk_icon_size_lookup_for_settings (settings, GTK_ICON_SIZE_MENU, + &width, &height)) + { + GdkPixbuf *src_pixbuf, *dest_pixbuf; + + src_pixbuf = gtk_image_get_pixbuf (wimage); + dest_pixbuf = gdk_pixbuf_scale_simple (src_pixbuf, width, height, + GDK_INTERP_BILINEAR); + + wmenuimage = gtk_image_new_from_pixbuf (dest_pixbuf); + } + else + { + fprintf (stderr, "internal error: GTK_IMAGE_PIXBUF failed\n"); + abort (); + } + } + else if (store_type == GTK_IMAGE_ICON_NAME) + { + const gchar *icon_name; + GtkIconSize icon_size; + + gtk_image_get_icon_name (wimage, &icon_name, &icon_size); + wmenuimage = gtk_image_new_from_icon_name (icon_name, + GTK_ICON_SIZE_MENU); + } + else + { + fprintf (stderr, "internal error: store_type is %d\n", store_type); + abort (); + } + } + if (wmenuimage) + gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (wmenuitem), wmenuimage); + + g_signal_connect (G_OBJECT (wmenuitem), + "activate", + GTK_SIGNAL_FUNC (xg_tool_bar_proxy_callback), + user_data); + + g_object_set_data (G_OBJECT (wmenuitem), XG_TOOL_BAR_PROXY_BUTTON, + (gpointer) wbutton); + gtk_tool_item_set_proxy_menu_item (toolitem, "Emacs toolbar item", wmenuitem); + + return TRUE; +} + /* This callback is called when a tool bar is detached. We must set the height of the tool bar to zero when this happens so frame sizes are correctly calculated. @@ -3135,13 +3526,18 @@ xg_tool_bar_detach_callback (wbox, w, client_data) gpointer client_data; { FRAME_PTR f = (FRAME_PTR) client_data; + extern int x_gtk_whole_detached_tool_bar; + + g_object_set (G_OBJECT (w), "show-arrow", !x_gtk_whole_detached_tool_bar, + NULL); if (f) { + FRAME_X_OUTPUT (f)->toolbar_detached = 1; + /* When detaching a tool bar, not everything dissapear. There are a few pixels left that are used to drop the tool bar back into place. */ - int bw = gtk_container_get_border_width (GTK_CONTAINER (wbox)); FRAME_TOOLBAR_HEIGHT (f) = 2; /* The height has changed, resize outer widget and set columns @@ -3164,16 +3560,19 @@ xg_tool_bar_attach_callback (wbox, w, client_data) gpointer client_data; { FRAME_PTR f = (FRAME_PTR) client_data; + g_object_set (G_OBJECT (w), "show-arrow", TRUE, NULL); if (f) { GtkRequisition req; + FRAME_X_OUTPUT (f)->toolbar_detached = 0; + gtk_widget_size_request (w, &req); FRAME_TOOLBAR_HEIGHT (f) = req.height; /* The height has changed, resize outer widget and set columns - rows to what we had before detaching the tool bar. */ + rows to what we had before attaching the tool bar. */ xg_resize_outer_widget (f, FRAME_COLS (f), FRAME_LINES (f)); } } @@ -3193,15 +3592,11 @@ xg_tool_bar_help_callback (w, event, client_data) GdkEventCrossing *event; gpointer client_data; { - int idx = (int)client_data; + /* The EMACS_INT cast avoids a warning. */ + int idx = (int) (EMACS_INT) client_data; FRAME_PTR f = (FRAME_PTR) g_object_get_data (G_OBJECT (w), XG_FRAME_DATA); Lisp_Object help, frame; - if (! GTK_IS_BUTTON (w)) - { - return FALSE; - } - if (! f || ! f->n_tool_bar_items || NILP (f->tool_bar_items)) return FALSE; @@ -3248,31 +3643,15 @@ xg_tool_bar_item_expose_callback (w, event, client_data) event->area.x = max (0, event->area.x); event->area.y = max (0, event->area.y); - + event->area.width = max (width, event->area.width); event->area.height = max (height, event->area.height); - + return FALSE; } -/* This callback is called when a tool bar shall be redrawn. - We need to update the tool bar from here in case the image cache - has deleted the pixmaps used in the tool bar. - W is the GtkToolbar to be redrawn. - EVENT is the expose event for W. - CLIENT_DATA is pointing to the frame for this tool bar. +#define PROP(IDX) AREF (f->tool_bar_items, i * TOOL_BAR_ITEM_NSLOTS + (IDX)) - Returns FALSE to tell GTK to keep processing this event. */ - -static gboolean -xg_tool_bar_expose_callback (w, event, client_data) - GtkWidget *w; - GdkEventExpose *event; - gpointer client_data; -{ - update_frame_tool_bar ((FRAME_PTR) client_data); - return FALSE; -} /* Create a tool bar for frame F. */ @@ -3286,6 +3665,8 @@ xg_create_tool_bar (f) x->toolbar_widget = gtk_toolbar_new (); x->handlebox_widget = gtk_handle_box_new (); + x->toolbar_detached = 0; + gtk_container_add (GTK_CONTAINER (x->handlebox_widget), x->toolbar_widget); @@ -3312,10 +3693,6 @@ xg_create_tool_bar (f) G_CALLBACK (xg_tool_bar_detach_callback), f); g_signal_connect (G_OBJECT (x->handlebox_widget), "child-attached", G_CALLBACK (xg_tool_bar_attach_callback), f); - g_signal_connect (G_OBJECT (x->toolbar_widget), - "expose-event", - G_CALLBACK (xg_tool_bar_expose_callback), - f); gtk_widget_show_all (x->handlebox_widget); @@ -3329,6 +3706,40 @@ xg_create_tool_bar (f) SET_FRAME_GARBAGED (f); } +/* Find the right-to-left image named by RTL in the tool bar images for F. + Returns IMAGE if RTL is not found. */ + +static Lisp_Object +find_rtl_image (f, image, rtl) + FRAME_PTR f; + Lisp_Object image; + Lisp_Object rtl; +{ + int i; + Lisp_Object file, rtl_name; + struct gcpro gcpro1, gcpro2; + GCPRO2 (file, rtl_name); + + rtl_name = Ffile_name_nondirectory (rtl); + + for (i = 0; i < f->n_tool_bar_items; ++i) + { + Lisp_Object rtl_image = PROP (TOOL_BAR_ITEM_IMAGES); + if (!NILP (file = file_for_image (rtl_image))) + { + file = call1 (intern ("file-name-sans-extension"), + Ffile_name_nondirectory (file)); + if (EQ (Fequal (file, rtl_name), Qt)) + { + image = rtl_image; + break; + } + } + } + + return image; +} + /* Update the tool bar for frame F. Add new buttons and remove old. */ void @@ -3337,164 +3748,323 @@ update_frame_tool_bar (f) { int i; GtkRequisition old_req, new_req; - GList *icon_list; - GList *iter; struct x_output *x = f->output_data.x; + int hmargin = 0, vmargin = 0; + GtkToolbar *wtoolbar; + GtkToolItem *ti; + GtkTextDirection dir; if (! FRAME_GTK_WIDGET (f)) return; BLOCK_INPUT; + if (INTEGERP (Vtool_bar_button_margin) + && XINT (Vtool_bar_button_margin) > 0) + { + hmargin = XFASTINT (Vtool_bar_button_margin); + vmargin = XFASTINT (Vtool_bar_button_margin); + } + else if (CONSP (Vtool_bar_button_margin)) + { + if (INTEGERP (XCAR (Vtool_bar_button_margin)) + && XINT (XCAR (Vtool_bar_button_margin)) > 0) + hmargin = XFASTINT (XCAR (Vtool_bar_button_margin)); + + if (INTEGERP (XCDR (Vtool_bar_button_margin)) + && XINT (XCDR (Vtool_bar_button_margin)) > 0) + vmargin = XFASTINT (XCDR (Vtool_bar_button_margin)); + } + + /* The natural size (i.e. when GTK uses 0 as margin) looks best, + so take DEFAULT_TOOL_BAR_BUTTON_MARGIN to mean "default for GTK", + i.e. zero. This means that margins less than + DEFAULT_TOOL_BAR_BUTTON_MARGIN has no effect. */ + hmargin = max (0, hmargin - DEFAULT_TOOL_BAR_BUTTON_MARGIN); + vmargin = max (0, vmargin - DEFAULT_TOOL_BAR_BUTTON_MARGIN); + if (! x->toolbar_widget) xg_create_tool_bar (f); - gtk_widget_size_request (x->toolbar_widget, &old_req); - - icon_list = gtk_container_get_children (GTK_CONTAINER (x->toolbar_widget)); - iter = icon_list; + wtoolbar = GTK_TOOLBAR (x->toolbar_widget); + gtk_widget_size_request (GTK_WIDGET (wtoolbar), &old_req); + dir = gtk_widget_get_direction (x->toolbar_widget); for (i = 0; i < f->n_tool_bar_items; ++i) { -#define PROP(IDX) AREF (f->tool_bar_items, i * TOOL_BAR_ITEM_NSLOTS + (IDX)) int enabled_p = !NILP (PROP (TOOL_BAR_ITEM_ENABLED_P)); int selected_p = !NILP (PROP (TOOL_BAR_ITEM_SELECTED_P)); int idx; int img_id; - struct image *img; + int icon_size = 0; + struct image *img = NULL; Lisp_Object image; - GtkWidget *wicon = iter ? GTK_WIDGET (iter->data) : 0; - - if (iter) iter = g_list_next (iter); + Lisp_Object stock; + GtkStockItem stock_item; + char *stock_name = NULL; + char *icon_name = NULL; + Lisp_Object rtl; + GtkWidget *wbutton = NULL; + GtkWidget *weventbox; + Lisp_Object func = intern ("x-gtk-map-stock"); + + ti = gtk_toolbar_get_nth_item (GTK_TOOLBAR (x->toolbar_widget), i); + + if (ti) + { + weventbox = gtk_bin_get_child (GTK_BIN (ti)); + wbutton = gtk_bin_get_child (GTK_BIN (weventbox)); + } - /* If image is a vector, choose the image according to the - button state. */ image = PROP (TOOL_BAR_ITEM_IMAGES); - if (VECTORP (image)) - { - if (enabled_p) - idx = (selected_p - ? TOOL_BAR_IMAGE_ENABLED_SELECTED - : TOOL_BAR_IMAGE_ENABLED_DESELECTED); - else - idx = (selected_p - ? TOOL_BAR_IMAGE_DISABLED_SELECTED - : TOOL_BAR_IMAGE_DISABLED_DESELECTED); - - xassert (ASIZE (image) >= idx); - image = AREF (image, idx); - } - else - idx = -1; /* Ignore invalid image specifications. */ if (!valid_image_p (image)) { - if (wicon) gtk_widget_hide (wicon); + if (wbutton) gtk_widget_hide (wbutton); continue; } - img_id = lookup_image (f, image); - img = IMAGE_FROM_ID (f, img_id); - prepare_image_for_display (f, img); + if (EQ (Qt, Ffboundp (func))) + stock = call1 (func, file_for_image (image)); - if (img->load_failed_p || img->pixmap == None) + if (! NILP (stock) && STRINGP (stock)) { - if (wicon) gtk_widget_hide (wicon); - continue; + stock_name = SSDATA (stock); + if (stock_name[0] == 'n' && stock_name[1] == ':') + { + GdkScreen *screen = gtk_widget_get_screen (GTK_WIDGET (wtoolbar)); + GtkIconTheme *icon_theme = gtk_icon_theme_get_for_screen (screen); + + icon_name = stock_name + 2; + stock_name = NULL; + stock = Qnil; + + if (! gtk_icon_theme_has_icon (icon_theme, icon_name)) + icon_name = NULL; + else + icon_size = gtk_toolbar_get_icon_size (wtoolbar); + } + else if (gtk_stock_lookup (SSDATA (stock), &stock_item)) + icon_size = gtk_toolbar_get_icon_size (wtoolbar); + else + { + stock = Qnil; + stock_name = NULL; + } + } + + if (stock_name == NULL && icon_name == NULL) + { + /* No stock image, or stock item not known. Try regular image. */ + + /* If image is a vector, choose the image according to the + button state. */ + if (dir == GTK_TEXT_DIR_RTL + && !NILP (rtl = PROP (TOOL_BAR_ITEM_RTL_IMAGE)) + && STRINGP (rtl)) + { + image = find_rtl_image (f, image, rtl); + } + + if (VECTORP (image)) + { + if (enabled_p) + idx = (selected_p + ? TOOL_BAR_IMAGE_ENABLED_SELECTED + : TOOL_BAR_IMAGE_ENABLED_DESELECTED); + else + idx = (selected_p + ? TOOL_BAR_IMAGE_DISABLED_SELECTED + : TOOL_BAR_IMAGE_DISABLED_DESELECTED); + + xassert (ASIZE (image) >= idx); + image = AREF (image, idx); + } + else + idx = -1; + + img_id = lookup_image (f, image); + img = IMAGE_FROM_ID (f, img_id); + prepare_image_for_display (f, img); + + if (img->load_failed_p || img->pixmap == None) + { + if (ti) + gtk_widget_hide_all (GTK_WIDGET (ti)); + else + { + /* Insert an empty (non-image) button */ + weventbox = gtk_event_box_new (); + wbutton = gtk_button_new (); + gtk_button_set_focus_on_click (GTK_BUTTON (wbutton), FALSE); + gtk_button_set_relief (GTK_BUTTON (wbutton), + GTK_RELIEF_NONE); + gtk_container_add (GTK_CONTAINER (weventbox), wbutton); + ti = gtk_tool_item_new (); + gtk_container_add (GTK_CONTAINER (ti), weventbox); + gtk_toolbar_insert (GTK_TOOLBAR (x->toolbar_widget), ti, -1); + } + continue; + } } - if (! wicon) + if (ti == NULL) { - GtkWidget *w = xg_get_image_for_pixmap (f, img, x->widget, NULL); + GtkWidget *w; + if (stock_name) + { + w = gtk_image_new_from_stock (stock_name, icon_size); + g_object_set_data_full (G_OBJECT (w), XG_TOOL_BAR_STOCK_NAME, + (gpointer) xstrdup (stock_name), + (GDestroyNotify) xfree); + } + else if (icon_name) + { + w = gtk_image_new_from_icon_name (icon_name, icon_size); + g_object_set_data_full (G_OBJECT (w), XG_TOOL_BAR_ICON_NAME, + (gpointer) xstrdup (icon_name), + (GDestroyNotify) xfree); + } + else + { + w = xg_get_image_for_pixmap (f, img, x->widget, NULL); + /* Save the image so we can see if an update is needed when + this function is called again. */ + g_object_set_data (G_OBJECT (w), XG_TOOL_BAR_IMAGE_DATA, + (gpointer)img->pixmap); + } + + gtk_misc_set_padding (GTK_MISC (w), hmargin, vmargin); + wbutton = gtk_button_new (); + gtk_button_set_focus_on_click (GTK_BUTTON (wbutton), FALSE); + gtk_button_set_relief (GTK_BUTTON (wbutton), GTK_RELIEF_NONE); + gtk_container_add (GTK_CONTAINER (wbutton), w); + weventbox = gtk_event_box_new (); + gtk_container_add (GTK_CONTAINER (weventbox), wbutton); + ti = gtk_tool_item_new (); + gtk_container_add (GTK_CONTAINER (ti), weventbox); + gtk_toolbar_insert (GTK_TOOLBAR (x->toolbar_widget), ti, -1); + + + /* The EMACS_INT cast avoids a warning. */ + g_signal_connect (G_OBJECT (ti), "create-menu-proxy", + GTK_SIGNAL_FUNC (xg_tool_bar_menu_proxy), + (gpointer) (EMACS_INT) i); - gtk_toolbar_append_item (GTK_TOOLBAR (x->toolbar_widget), - 0, 0, 0, - w, - GTK_SIGNAL_FUNC (xg_tool_bar_callback), - (gpointer)i); + g_signal_connect (G_OBJECT (wbutton), "clicked", + GTK_SIGNAL_FUNC (xg_tool_bar_callback), + (gpointer) (EMACS_INT) i); - /* Save the image so we can see if an update is needed when - this function is called again. */ - g_object_set_data (G_OBJECT (w), XG_TOOL_BAR_IMAGE_DATA, - (gpointer)img->pixmap); + gtk_widget_show_all (GTK_WIDGET (ti)); + + + g_object_set_data (G_OBJECT (weventbox), XG_FRAME_DATA, (gpointer)f); /* Catch expose events to overcome an annoying redraw bug, see comment for xg_tool_bar_item_expose_callback. */ - g_signal_connect (G_OBJECT (w), + g_signal_connect (G_OBJECT (ti), "expose-event", G_CALLBACK (xg_tool_bar_item_expose_callback), 0); - /* We must set sensitive on the button that is the parent - of the GtkImage parent. Go upwards until we find the button. */ - while (! GTK_IS_BUTTON (w)) - w = gtk_widget_get_parent (w); - - if (w) - { - /* Save the frame in the button so the xg_tool_bar_callback - can get at it. */ - g_object_set_data (G_OBJECT (w), XG_FRAME_DATA, (gpointer)f); - gtk_widget_set_sensitive (w, enabled_p); - - /* Use enter/leave notify to show help. We use the events - rather than the GtkButton specific signals "enter" and - "leave", so we can have only one callback. The event - will tell us what kind of event it is. */ - g_signal_connect (G_OBJECT (w), - "enter-notify-event", - G_CALLBACK (xg_tool_bar_help_callback), - (gpointer)i); - g_signal_connect (G_OBJECT (w), - "leave-notify-event", - G_CALLBACK (xg_tool_bar_help_callback), - (gpointer)i); - } + gtk_widget_set_sensitive (wbutton, enabled_p); + gtk_tool_item_set_homogeneous (ti, FALSE); + + /* Callback to save modifyer mask (Shift/Control, etc). GTK makes + no distinction based on modifiers in the activate callback, + so we have to do it ourselves. */ + g_signal_connect (wbutton, "button-release-event", + GTK_SIGNAL_FUNC (xg_tool_bar_button_cb), + NULL); + + g_object_set_data (G_OBJECT (wbutton), XG_FRAME_DATA, (gpointer)f); + + /* Use enter/leave notify to show help. We use the events + rather than the GtkButton specific signals "enter" and + "leave", so we can have only one callback. The event + will tell us what kind of event it is. */ + /* The EMACS_INT cast avoids a warning. */ + g_signal_connect (G_OBJECT (weventbox), + "enter-notify-event", + G_CALLBACK (xg_tool_bar_help_callback), + (gpointer) (EMACS_INT) i); + g_signal_connect (G_OBJECT (weventbox), + "leave-notify-event", + G_CALLBACK (xg_tool_bar_help_callback), + (gpointer) (EMACS_INT) i); } else { - /* The child of the tool bar is a button. Inside that button - is a vbox. Inside that vbox is the GtkImage. */ - GtkWidget *wvbox = gtk_bin_get_child (GTK_BIN (wicon)); - GList *chlist = gtk_container_get_children (GTK_CONTAINER (wvbox)); - GtkImage *wimage = GTK_IMAGE (chlist->data); + GtkWidget *wimage = gtk_bin_get_child (GTK_BIN (wbutton)); Pixmap old_img = (Pixmap)g_object_get_data (G_OBJECT (wimage), XG_TOOL_BAR_IMAGE_DATA); - g_list_free (chlist); + gpointer old_stock_name = g_object_get_data (G_OBJECT (wimage), + XG_TOOL_BAR_STOCK_NAME); + gpointer old_icon_name = g_object_get_data (G_OBJECT (wimage), + XG_TOOL_BAR_ICON_NAME); + if (stock_name && + (! old_stock_name || strcmp (old_stock_name, stock_name) != 0)) + { + gtk_image_set_from_stock (GTK_IMAGE (wimage), + stock_name, icon_size); + g_object_set_data_full (G_OBJECT (wimage), XG_TOOL_BAR_STOCK_NAME, + (gpointer) xstrdup (stock_name), + (GDestroyNotify) xfree); + g_object_set_data (G_OBJECT (wimage), XG_TOOL_BAR_IMAGE_DATA, + NULL); + g_object_set_data (G_OBJECT (wimage), XG_TOOL_BAR_ICON_NAME, NULL); + } + else if (icon_name && + (! old_icon_name || strcmp (old_icon_name, icon_name) != 0)) + { + gtk_image_set_from_icon_name (GTK_IMAGE (wimage), + icon_name, icon_size); + g_object_set_data_full (G_OBJECT (wimage), XG_TOOL_BAR_ICON_NAME, + (gpointer) xstrdup (icon_name), + (GDestroyNotify) xfree); + g_object_set_data (G_OBJECT (wimage), XG_TOOL_BAR_IMAGE_DATA, + NULL); + g_object_set_data (G_OBJECT (wimage), XG_TOOL_BAR_STOCK_NAME, + NULL); + } + else if (img && old_img != img->pixmap) + { + (void) xg_get_image_for_pixmap (f, img, x->widget, wimage); + g_object_set_data (G_OBJECT (wimage), XG_TOOL_BAR_IMAGE_DATA, + (gpointer)img->pixmap); - if (old_img != img->pixmap) - (void) xg_get_image_for_pixmap (f, img, x->widget, wimage); + g_object_set_data (G_OBJECT (wimage), XG_TOOL_BAR_STOCK_NAME, + NULL); + g_object_set_data (G_OBJECT (wimage), XG_TOOL_BAR_ICON_NAME, NULL); + } - g_object_set_data (G_OBJECT (wimage), XG_TOOL_BAR_IMAGE_DATA, - (gpointer)img->pixmap); + gtk_misc_set_padding (GTK_MISC (wimage), hmargin, vmargin); - gtk_widget_set_sensitive (wicon, enabled_p); - gtk_widget_show (wicon); - } + gtk_widget_set_sensitive (wbutton, enabled_p); + gtk_widget_show_all (GTK_WIDGET (ti)); + } #undef PROP } /* Remove buttons not longer needed. We just hide them so they can be reused later on. */ - while (iter) + do { - GtkWidget *w = GTK_WIDGET (iter->data); - gtk_widget_hide (w); - iter = g_list_next (iter); - } + ti = gtk_toolbar_get_nth_item (GTK_TOOLBAR (x->toolbar_widget), i++); + if (ti) gtk_widget_hide_all (GTK_WIDGET (ti)); + } while (ti != NULL); - gtk_widget_size_request (x->toolbar_widget, &new_req); - if (old_req.height != new_req.height) + gtk_widget_size_request (GTK_WIDGET (wtoolbar), &new_req); + if (old_req.height != new_req.height + && ! FRAME_X_OUTPUT (f)->toolbar_detached) { FRAME_TOOLBAR_HEIGHT (f) = new_req.height; xg_resize_outer_widget (f, FRAME_COLS (f), FRAME_LINES (f)); } - if (icon_list) g_list_free (icon_list); - UNBLOCK_INPUT; } @@ -3533,6 +4103,13 @@ free_frame_tool_bar (f) void xg_initialize () { + GtkBindingSet *binding_set; + +#if HAVE_XFT + /* Work around a bug with corrupted data if libXft gets unloaded. This way + we keep it permanently linked in. */ + XftInit (0); +#endif xg_ignore_gtk_scrollbar = 0; xg_detached_menus = 0; xg_menu_cb_list.prev = xg_menu_cb_list.next = @@ -3555,6 +4132,17 @@ xg_initialize () "gtk-key-theme-name", "Emacs", EMACS_CLASS); + + /* Make dialogs close on C-g. Since file dialog inherits from + dialog, this works for them also. */ + binding_set = gtk_binding_set_by_class (gtk_type_class (GTK_TYPE_DIALOG)); + gtk_binding_entry_add_signal (binding_set, GDK_g, GDK_CONTROL_MASK, + "close", 0); + + /* Make menus close on C-g. */ + binding_set = gtk_binding_set_by_class (gtk_type_class (GTK_TYPE_MENU_SHELL)); + gtk_binding_entry_add_signal (binding_set, GDK_g, GDK_CONTROL_MASK, + "cancel", 0); } #endif /* USE_GTK */