/* GTK related routines Copyright (C) 2020 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 3 of the License, or (at your option) any later version. GNU Emacs is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with GNU Emacs. If not, see . */ #include "gtkinter.h" #include "blockinput.h" #ifdef HAVE_GTK4 #include "pgtksubr.h" #else #include "gtkutil.h" #define XG_TOOL_BAR_LAST_MODIFIER "emacs-tool-bar-modifier" #endif #include "keyboard.h" #ifdef HAVE_GTK3 #ifdef HAVE_GTK4 #define xg_list_node egtk_list_node #define xg_menu_cb_data egtk_menu_cb_data #define xg_list_insert egtk_list_insert #endif /* Linked list of all allocated struct xg_menu_cb_data. Used for marking during GC. The next member points to the items. */ static xg_list_node xg_menu_cb_list; #pragma GCC diagnostic ignored "-Wunused-macros" #pragma GCC diagnostic ignored "-Wunused-function" static xg_menu_cb_data * g_make_cl_data (xg_menu_cb_data *cl_data, struct frame *f, GCallback highlight_cb) { if (!cl_data) { cl_data = xmalloc (sizeof *cl_data); cl_data->f = f; cl_data->menu_bar_vector = f->menu_bar_vector; cl_data->menu_bar_items_used = f->menu_bar_items_used; cl_data->highlight_cb = highlight_cb; cl_data->ref_count = 0; xg_list_insert (&xg_menu_cb_list, &cl_data->ptrs); } cl_data->ref_count++; return cl_data; } #pragma GCC diagnostic push #pragma GCC diagnostic push #endif #ifndef HAVE_GTK4 static void find_dialog_buttons (GtkWidget *w, gpointer l) { if (GTK_IS_BUTTON (w)) *(GList **) l = g_list_append (*(GList **) l, w); } #endif GtkAboutDialog * show_about_dialog (gpointer thing, struct frame *f) { GtkAboutDialog *a = GTK_ABOUT_DIALOG (gtk_about_dialog_new ()); GtkWindow *frame_window = GTK_WINDOW (FRAME_GTK_OUTER_WIDGET (f)); gtk_about_dialog_set_program_name (a, PACKAGE_NAME); Lisp_Object ev = call0 (intern ("emacs-version")); CHECK_STRING (ev); gtk_about_dialog_set_version (a, SSDATA (ev)); gtk_about_dialog_set_website (a, PACKAGE_URL); #ifdef HAVE_GTK3 gtk_about_dialog_set_license_type (a, GTK_LICENSE_GPL_3_0); #endif gtk_about_dialog_set_copyright (a, COPYRIGHT); gtk_about_dialog_set_wrap_license (a, true); gtk_window_set_transient_for (GTK_WINDOW (a), frame_window); gtk_dialog_run (GTK_DIALOG (a)); gtk_widget_destroy (GTK_WIDGET (a)); return a; } GtkDialog * #ifndef HAVE_GTK4 build_dialog_n_items (char *fmt, widget_value *wv, GCallback select_cb, GCallback deactivate_cb, GCallback dialog_delete_callback) #else build_dialog_n_items (char *fmt, widget_value *wv, GCallback select_cb, GCallback deactivate_cb) #endif { GtkDialog *dialog = GTK_DIALOG ( gtk_message_dialog_new (NULL, GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_INFO, GTK_BUTTONS_NONE, fmt, NULL)); #ifndef HAVE_GTK4 g_signal_connect (G_OBJECT (dialog), "delete-event", G_CALLBACK (dialog_delete_callback), 0); #endif if (deactivate_cb) { g_signal_connect (G_OBJECT (dialog), "close", deactivate_cb, 0); g_signal_connect (G_OBJECT (dialog), "response", deactivate_cb, 0); } int id = 0; do { g_signal_connect (G_OBJECT ( gtk_dialog_add_button (dialog, wv->value, id)), "clicked", select_cb, wv->call_data); wv = wv->next; ++id; } while (wv); return dialog; } #ifndef HAVE_GTK4 GtkDialog * build_dialog_3_items (Lisp_Object fmt, Lisp_Object name_a, Lisp_Object name_b, Lisp_Object name_c, GCallback select_cb, GCallback deactivate_cb, widget_value *wv_a, widget_value *wv_b, widget_value *wv_c, GCallback dialog_delete_callback) { GtkDialog *dialog = GTK_DIALOG ( gtk_message_dialog_new (NULL, GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_INFO, GTK_BUTTONS_NONE, SSDATA (fmt), NULL)); GtkButton *a = GTK_BUTTON (gtk_dialog_add_button (dialog, SSDATA (name_a), 0)); GtkButton *b = GTK_BUTTON (gtk_dialog_add_button (dialog, SSDATA (name_b), 1)); GtkButton *c = GTK_BUTTON (gtk_dialog_add_button (dialog, SSDATA (name_c), 2)); g_signal_connect (G_OBJECT (dialog), "delete-event", G_CALLBACK (dialog_delete_callback), 0); if (deactivate_cb) { g_signal_connect (G_OBJECT (dialog), "close", deactivate_cb, 0); g_signal_connect (G_OBJECT (dialog), "response", deactivate_cb, 0); } g_signal_connect (G_OBJECT (a), "clicked", select_cb, wv_a->call_data); g_signal_connect (G_OBJECT (b), "clicked", select_cb, wv_b->call_data); g_signal_connect (G_OBJECT (c), "clicked", select_cb, wv_c->call_data); gtk_window_set_modal (GTK_WINDOW (dialog), TRUE); return dialog; } GtkMessageDialog * build_dialog_2_items (Lisp_Object fmt, Lisp_Object name_a, Lisp_Object name_b, GCallback select_cb, GCallback deactivate_cb, widget_value *wv_a, widget_value *wv_b, GCallback dialog_delete_callback) { GtkMessageDialog *dialog = GTK_MESSAGE_DIALOG ( gtk_message_dialog_new (NULL, GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_QUESTION, GTK_BUTTONS_YES_NO, SSDATA (fmt), NULL)); GList *buttons = g_list_alloc (); GtkContainer *action_area = GTK_CONTAINER (gtk_dialog_get_action_area (GTK_DIALOG (dialog))); gtk_container_forall (action_area, find_dialog_buttons, &buttons); GtkButton *a = buttons->next->data; GtkButton *b = buttons->next->next->data; gtk_button_set_label (a, SSDATA (name_a)); gtk_button_set_label (b, SSDATA (name_b)); g_signal_connect (G_OBJECT (dialog), "delete-event", G_CALLBACK (dialog_delete_callback), 0); if (deactivate_cb) { g_signal_connect (G_OBJECT (dialog), "close", deactivate_cb, 0); g_signal_connect (G_OBJECT (dialog), "response", deactivate_cb, 0); } g_signal_connect (G_OBJECT (a), "clicked", select_cb, wv_a->call_data); g_signal_connect (G_OBJECT (b), "clicked", select_cb, wv_b->call_data); g_list_free (buttons); return dialog; } extern GtkMessageDialog * build_dialog_1_item (Lisp_Object fmt, Lisp_Object name_a, GCallback select_cb, GCallback deactivate_cb, widget_value *wv_a, GCallback dialog_delete_callback) { GtkMessageDialog *dialog = GTK_MESSAGE_DIALOG ( gtk_message_dialog_new (NULL, GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_QUESTION, GTK_BUTTONS_OK, SSDATA (fmt), NULL)); GList *buttons = g_list_alloc (); GtkContainer *action_area = GTK_CONTAINER (gtk_dialog_get_action_area (GTK_DIALOG (dialog))); gtk_container_forall (action_area, find_dialog_buttons, &buttons); GtkButton *a = buttons->next->data; gtk_button_set_label (a, SSDATA (name_a)); g_signal_connect (G_OBJECT (dialog), "delete-event", G_CALLBACK (dialog_delete_callback), 0); if (deactivate_cb) { g_signal_connect (G_OBJECT (dialog), "close", deactivate_cb, 0); g_signal_connect (G_OBJECT (dialog), "response", deactivate_cb, 0); } g_signal_connect (G_OBJECT (a), "clicked", select_cb, wv_a->call_data); g_list_free (buttons); return dialog; } #endif #ifdef HAVE_GTK3 static void gtk_find_gtk_menu_button (GtkWidget *w, GtkMenuButton **user_data) { if (GTK_IS_MENU_BUTTON (w)) (*user_data) = GTK_MENU_BUTTON (w); } #endif /* When the GTK widget W is to be created on a display for F that is not the default display, set the display for W. W can be a GtkMenu or a GtkWindow widget. */ #ifndef HAVE_GTK4 static void gtk_set_screen (GtkWidget *w, struct frame *f) { #ifndef HAVE_PGTK if (FRAME_X_DISPLAY (f) != DEFAULT_GDK_DISPLAY ()) { GdkDisplay *gdpy = gdk_x11_lookup_xdisplay (FRAME_X_DISPLAY (f)); GdkScreen *gscreen = gdk_display_get_default_screen (gdpy); if (GTK_IS_MENU (w)) gtk_menu_set_screen (GTK_MENU (w), gscreen); else gtk_window_set_screen (GTK_WINDOW (w), gscreen); } #else if (FRAME_X_DISPLAY (f) != DEFAULT_GDK_DISPLAY ()) { GdkScreen *gscreen = gdk_display_get_default_screen (FRAME_X_DISPLAY (f)); if (GTK_IS_MENU (w)) gtk_menu_set_screen (GTK_MENU (w), gscreen); else gtk_window_set_screen (GTK_WINDOW (w), gscreen); } #endif } #endif #ifdef HAVE_GTK3 static bool xg_get_widget_pack_flag (GtkWidget *w) { /* * nil = start * t = end */ return g_object_get_data (G_OBJECT (w), "xg-widget-pack-flag") != 0; } static void xg_set_widget_pack_flag (GtkWidget *w, bool end) { g_object_set_data (G_OBJECT (w), "xg-widget-pack-flag", (gpointer) (intptr_t) ((gpointer) ((gpointer) end) != 0)); } static void xg_find_gtk_menu_button (GtkWidget *w, GtkMenuButton **user_data) { if (GTK_IS_MENU_BUTTON (w)) (*user_data) = GTK_MENU_BUTTON (w); } void xg_notify_header_bar_menu_state_changed (struct frame *f) { if (FRAME_PARENT_FRAME (f)) return; GtkMenuButton *btn = NULL; GtkHeaderBar *hbawr = FRAME_GTK_HEADER_BAR (f); gtk_container_foreach (GTK_CONTAINER (hbawr), (GtkCallback) gtk_find_gtk_menu_button, &btn); if (!FRAME_DISPLAY_HEADER_BAR_MENU (f)) { if (btn) gtk_widget_destroy (GTK_WIDGET (btn)); return; } else if (!btn) { GtkMenuButton *btn = GTK_MENU_BUTTON (gtk_menu_button_new ()); #ifdef HAVE_GTK4 gtk_menu_button_set_icon_name (btn, "open-menu-symbolic"); #else gtk_button_set_image ( GTK_BUTTON (btn), gtk_image_new_from_icon_name ("open-menu-symbolic", GTK_ICON_SIZE_BUTTON)); #endif gtk_header_bar_pack_end (FRAME_GTK_HEADER_BAR (f), GTK_WIDGET (btn)); } #ifndef HAVE_GTK4 if (!btn) { GtkWidget *menubar = gtk_menu_button_new (); btn = GTK_MENU_BUTTON (menubar); gtk_button_set_image ( GTK_BUTTON (btn), gtk_image_new_from_icon_name ("open-menu-symbolic", GTK_ICON_SIZE_BUTTON)); gtk_menu_button_set_popup (btn, gtk_menu_new ()); gtk_header_bar_pack_end (hbawr, GTK_WIDGET (btn)); gtk_widget_set_visible (GTK_WIDGET (btn), true); } #endif } #ifndef HAVE_GTK4 static void xg_destroy_menu_item_if_empty (GtkWidget *it, gpointer _) { if (GTK_IS_MENU_ITEM (it) && !strlen (gtk_menu_item_get_label (GTK_MENU_ITEM (it)))) gtk_widget_destroy (it); } #endif #ifdef HAVE_GTK4 struct ppw_data { widget_value *popup; GCallback select_cb; GCallback deactivate_cb; GCallback highlight_cb; egtk_menu_cb_data *cl_data; struct frame *f; bool m; }; static void popup_create_func (GtkMenuButton *menu_button, gpointer user_data) { struct ppw_data *data = user_data; #define popup data->popup #define select_cb data->select_cb #define deactivate_cb data->deactivate_cb #define highlight_cb data->highlight_cb if (popup && select_cb && !data->m) { if (gtk_menu_button_get_popover (menu_button)) gtk_menu_button_set_popover (menu_button, NULL); GtkPopover *po = build_mm_from_wv (popup, data->f, select_cb, deactivate_cb, highlight_cb, true, true, data->cl_data, NULL, NULL, NULL, NULL); gtk_menu_button_set_popover (menu_button, GTK_WIDGET (po)); data->m = true; } #undef popup #undef select_cb #undef deactivate_cb #undef highlight_cb } static void free_data (gpointer data) { struct ppw_data *d = data; if (d->popup) { free_menubar_widget_value_tree (d->popup); d->popup = NULL; } unref_cl_data (d->cl_data); xfree (d); } #endif void xg_modify_header_bar_widgets (GtkHeaderBar *hbawr, struct frame *f, widget_value *val, bool deep_p, GCallback select_cb, GCallback deactivate_cb, GCallback highlight_cb) { #ifndef HAVE_GTK4 GtkMenuButton *btn = NULL; GtkMenu *menubar = NULL; if (!gtk_window_has_toplevel_focus (GTK_WINDOW (FRAME_GTK_OUTER_WIDGET (f)))) return; gtk_container_foreach (GTK_CONTAINER (hbawr), (GtkCallback) gtk_find_gtk_menu_button, &btn); if (!FRAME_DISPLAY_HEADER_BAR_MENU (f)) { if (btn) gtk_widget_destroy (GTK_WIDGET (btn)); return; } if (!btn) { btn = GTK_MENU_BUTTON (gtk_menu_button_new ()); gtk_button_set_image ( GTK_BUTTON (btn), gtk_image_new_from_icon_name ("open-menu-symbolic", GTK_ICON_SIZE_BUTTON)); menubar = GTK_MENU (gtk_menu_new ()); gtk_menu_button_set_popup (btn, GTK_WIDGET (menubar)); gtk_header_bar_pack_end (hbawr, GTK_WIDGET (btn)); gtk_widget_set_visible (GTK_WIDGET (btn), true); } else { menubar = gtk_menu_button_get_popup (btn); } xg_menu_cb_data *cl_data; GList *list = gtk_container_get_children (GTK_CONTAINER (menubar)); cl_data = g_make_cl_data (g_object_get_data (G_OBJECT (menubar), XG_FRAME_DATA), f, highlight_cb); update_cl_data (cl_data, f, highlight_cb); g_object_set_data_full (G_OBJECT (menubar), XG_FRAME_DATA, cl_data, NULL); xg_update_menubar (GTK_WIDGET (menubar), f, &list, list, 0, val->contents, select_cb, deactivate_cb, highlight_cb, cl_data); widget_value *cur; /* Update all sub menus. We must keep the submenus (GTK menu item widgets) since the X Window in the XEvent that activates the menu are those widgets. */ /* Update cl_data, menu_item things in F may have changed. */ update_cl_data (cl_data, f, highlight_cb); if (deep_p) for (cur = val->contents; cur; cur = cur->next) { GList *iter; GtkWidget *sub = 0; GtkWidget *newsub; GtkMenuItem *witem = 0; /* Find sub menu that corresponds to val and update it. */ for (iter = list; iter; iter = g_list_next (iter)) { witem = GTK_MENU_ITEM (iter->data); if (xg_item_label_same_p (witem, cur->name)) { sub = gtk_menu_item_get_submenu (witem); break; } } newsub = xg_update_submenu (sub, f, cur->contents, select_cb, deactivate_cb, highlight_cb, cl_data); /* sub may still be NULL. If we just updated non deep and added a new menu bar item, it has no sub menu yet. So we set the newly created sub menu under witem. */ if (newsub != sub && witem != 0) { gtk_set_screen (newsub, f); gtk_menu_item_set_submenu (witem, newsub); } } g_list_free (list); gtk_container_forall (GTK_CONTAINER (menubar), xg_destroy_menu_item_if_empty, NULL); gtk_widget_show_all (GTK_WIDGET (menubar)); #else if (!FRAME_DISPLAY_HEADER_BAR (f) || !gtk_widget_has_visible_focus (FRAME_GTK_WIDGET (f))) return; xg_notify_header_bar_menu_state_changed (f); if (!hbawr) return; GtkMenuButton *btn = NULL; gtk_container_foreach (GTK_CONTAINER (hbawr), (GtkCallback) gtk_find_gtk_menu_button, &btn); if (btn) { if (gtk_menu_button_get_popover (btn)) gtk_menu_button_set_popover (btn, NULL); } else return; struct ppw_data *d = xmalloc (sizeof *d); *d = (struct ppw_data) { val, select_cb, deactivate_cb, highlight_cb, make_cl_data (NULL, f, highlight_cb), f, false }; gtk_menu_button_set_create_popup_func (btn, popup_create_func, d, free_data); #endif } static void process_frame_action_button (GtkWidget *w, gpointer l) { if (!GTK_IS_WIDGET (w)) emacs_abort (); if (GTK_IS_BUTTON (w) && !GTK_IS_MENU_BUTTON (w) && !xg_get_widget_pack_flag (w)) *(GList **) l = g_list_append (*(GList **) l, w); } static void process_frame_action_button_end (GtkWidget *w, gpointer l) { if (!GTK_IS_WIDGET (w)) emacs_abort (); if (GTK_IS_BUTTON (w) && !GTK_IS_MENU_BUTTON (w) && xg_get_widget_pack_flag (w)) *(GList **) l = g_list_append (*(GList **) l, w); } static char * find_icon_from_name (char *name, GtkIconTheme *icon_theme, char **icon_name) { if (name[0] == 'n' && name[1] == ':') { *icon_name = name + 2; name = NULL; if (!gtk_icon_theme_has_icon (icon_theme, *icon_name)) *icon_name = NULL; } else if (gtk_icon_theme_has_icon (icon_theme, name)) { *icon_name = name; name = NULL; } else { name = NULL; *icon_name = NULL; } return 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 void xg_tool_bar_callback (GtkWidget *w, gpointer client_data) { intptr_t idx = (intptr_t) client_data; gpointer gmod = g_object_get_data (G_OBJECT (w), XG_TOOL_BAR_LAST_MODIFIER); intptr_t mod = (intptr_t) gmod; struct frame *f = g_object_get_data (G_OBJECT (w), XG_FRAME_DATA); Lisp_Object key, frame; struct input_event event; EVENT_INIT (event); if (!f || !f->n_tool_bar_items || NILP (f->tool_bar_items)) return; idx *= TOOL_BAR_ITEM_NSLOTS; 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 = key; /* Convert between the modifier bits GDK uses and the modifier bits Emacs uses. This assumes GDK and X masks are the same, which they are when this is written. */ #ifndef HAVE_PGTK event.modifiers = x_x_to_emacs_modifiers (FRAME_DISPLAY_INFO (f), mod); #else event.modifiers = pgtk_gtk_to_emacs_modifiers (FRAME_DISPLAY_INFO (f), mod); #endif kbd_buffer_store_event (&event); /* Return focus to the frame after we have clicked on a detached tool bar button. */ FRAME_TERMINAL (f)->focus_frame_hook (f, false); } static gboolean xg_tool_bar_button_cb (GtkWidget *widget, #ifdef HAVE_GTK4 GdkEvent *event, #else GdkEventButton *event, #endif gpointer user_data) { #ifdef HAVE_GTK4 GdkModifierType #else intptr_t #endif state #ifndef HAVE_GTK4 = event->state; #else = gdk_event_get_modifier_state (event); #endif gpointer ptr = (gpointer) state; g_object_set_data (G_OBJECT (widget), XG_TOOL_BAR_LAST_MODIFIER, ptr); return FALSE; } static Lisp_Object file_for_image (Lisp_Object image) { Lisp_Object specified_file = Qnil; Lisp_Object tail; 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; } #ifdef HAVE_GTK3 void update_frame_header_bar (struct frame *f) { GtkHeaderBar *header_bar = FRAME_GTK_HEADER_BAR (f); if (!header_bar) return; if (FRAME_PARENT_FRAME (f)) return; #ifndef HAVE_GTK4 if (!gtk_window_has_toplevel_focus (GTK_WINDOW (FRAME_GTK_OUTER_WIDGET (f)))) return; #else if (!gtk_widget_is_focus (FRAME_GTK_WIDGET (f))) return; #endif block_input (); int n_tool_bar_items = FRAME_DISPLAY_HEADER_BAR_ICONS (f) ? f->n_tool_bar_items : 0; int n_start_tool_bar_items = n_tool_bar_items; int n_end_tool_bar_items = 0; GList *start_items = NULL; GList *end_items = NULL; if (!f->n_tool_bar_items || NILP (f->tool_bar_items) || f->n_tool_bar_items < 0 || !n_tool_bar_items) return; for (int i = 0; i < f->n_tool_bar_items; ++i) { #define PROP(IDX) AREF (f->tool_bar_items, i *TOOL_BAR_ITEM_NSLOTS + (IDX)) if (!Fequal (PROP (TOOL_BAR_ITEM_TYPE), Qt)) { struct xg_tool_bar_entry *tbe = xmalloc (sizeof (struct xg_tool_bar_entry)); tbe->icon = PROP (TOOL_BAR_ITEM_IMAGES); tbe->idx = i; tbe->title = PROP (TOOL_BAR_ITEM_LABEL); tbe->help = PROP (TOOL_BAR_ITEM_HELP); tbe->selected = !NILP (PROP (TOOL_BAR_ITEM_SELECTED_P)); tbe->enabled = !NILP (PROP (TOOL_BAR_ITEM_ENABLED_P)); tbe->type = PROP (TOOL_BAR_ITEM_TYPE); #undef PROP if (!NILP (Fequal (tbe->title, build_string ("Save"))) || !NILP (Fequal (tbe->title, build_string ("Load init files")))) { end_items = g_list_append (end_items, tbe); } else { start_items = g_list_append (start_items, tbe); } } } n_start_tool_bar_items = g_list_length (start_items); n_end_tool_bar_items = g_list_length (end_items); GList *header_buttons_start = NULL; GList *header_buttons_end = NULL; gtk_container_foreach (GTK_CONTAINER (header_bar), process_frame_action_button, &header_buttons_start); gtk_container_foreach (GTK_CONTAINER (header_bar), process_frame_action_button_end, &header_buttons_end); if (!n_start_tool_bar_items) for (; header_buttons_start; header_buttons_start = header_buttons_start->next) gtk_widget_destroy (header_buttons_start->data); if (!n_end_tool_bar_items) for (; header_buttons_end; header_buttons_end = header_buttons_end->next) gtk_widget_destroy (header_buttons_end->data); if (!n_start_tool_bar_items) { if (header_buttons_end) g_list_free (header_buttons_end); if (header_buttons_start) g_list_free (header_buttons_start); if (end_items) g_list_free (end_items); return; } while (g_list_length (header_buttons_start) > n_start_tool_bar_items) { gpointer d = g_list_last (header_buttons_start)->data; if (!GTK_IS_WIDGET (d)) emacs_abort (); gtk_widget_destroy (GTK_WIDGET (d)); header_buttons_start = g_list_remove (header_buttons_start, d); } while (g_list_length (header_buttons_start) < n_start_tool_bar_items) { GtkWidget *tb = gtk_button_new (); #ifndef HAVE_GTK4 g_signal_connect (tb, "button-release-event", G_CALLBACK (xg_tool_bar_button_cb), NULL); #endif g_object_set_data (G_OBJECT (tb), XG_FRAME_DATA, f); header_buttons_start = g_list_append (header_buttons_start, tb); gtk_header_bar_pack_start (header_bar, tb); xg_set_widget_pack_flag (GTK_WIDGET (tb), 0); #ifndef HAVE_GTK4 gtk_widget_set_visible (GTK_WIDGET (tb), true); #endif } while (g_list_length (header_buttons_end) > n_end_tool_bar_items) { gpointer d = g_list_last (header_buttons_end)->data; if (!GTK_IS_WIDGET (d)) emacs_abort (); gtk_widget_destroy (GTK_WIDGET (d)); header_buttons_end = g_list_remove (header_buttons_end, d); } while (g_list_length (header_buttons_end) < n_end_tool_bar_items) { GtkWidget *tb = gtk_button_new (); #ifndef HAVE_GTK4 g_signal_connect (tb, "button-release-event", G_CALLBACK (xg_tool_bar_button_cb), NULL); #endif g_object_set_data (G_OBJECT (tb), XG_FRAME_DATA, f); header_buttons_end = g_list_append (header_buttons_end, tb); gtk_header_bar_pack_end (header_bar, tb); xg_set_widget_pack_flag (GTK_WIDGET (tb), 1); gtk_widget_set_visible (GTK_WIDGET (tb), true); } bool flag = 0; GList *l = header_buttons_start; GList *r = start_items; loop:; #pragma GCC diagnostic ignored "-Wunused-value" for (; (l && r); (l = l->next) && (r = r->next)) { #pragma GCC diagnostic push GtkButton *btn = l->data; struct xg_tool_bar_entry *e = r->data; bool enabled_p = e->enabled; bool selected_p = e->selected; if (!btn) continue; if (EQ (e->type, Qt)) { gtk_widget_set_visible (GTK_WIDGET (btn), false); } else { gtk_widget_set_visible (GTK_WIDGET (btn), true); gtk_widget_set_sensitive (GTK_WIDGET (btn), enabled_p); Lisp_Object image = e->icon; Lisp_Object stock = Qnil; char *icon_name = NULL; char *stock_name = NULL; int idx; ptrdiff_t img_id; g_object_ref (btn); struct image *img; if (!CONSP (image) && !valid_image_p (image)) { #ifndef HAVE_GTK4 gtk_button_set_image (btn, NULL); #else gtk_container_remove (GTK_CONTAINER (btn), gtk_bin_get_child (GTK_BIN (btn))); #endif continue; } Lisp_Object specified_file = file_for_image (image); if (!NILP (specified_file) && !NILP (Ffboundp (Qx_gtk_map_stock))) stock = call1 (Qx_gtk_map_stock, specified_file); if (CONSP (stock)) { Lisp_Object itr; for (itr = stock; CONSP (itr); itr = XCDR (itr)) { stock_name = find_icon_from_name (SSDATA (XCAR (itr)), #ifdef HAVE_GTK4 gtk_icon_theme_get_for_display ( gtk_widget_get_display ( GTK_WIDGET (header_bar))), #else gtk_icon_theme_get_default (), #endif &icon_name); if (stock_name || icon_name) break; } } else if (STRINGP (stock)) { stock_name = find_icon_from_name (SSDATA (stock), #ifdef HAVE_GTK4 gtk_icon_theme_get_for_display ( gtk_widget_get_display ( GTK_WIDGET (header_bar))), #else gtk_icon_theme_get_default (), #endif &icon_name); } if (stock_name || icon_name) { if (stock_name) stock_name = g_strconcat (stock_name, "-symbolic", NULL); if (icon_name) icon_name = g_strconcat (icon_name, "-symbolic", NULL); } if (stock_name == NULL && icon_name == NULL) { 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); eassert (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); } GtkImage *w = NULL; if (stock_name || icon_name) { if (stock_name) { #ifndef HAVE_GTK4 w = GTK_IMAGE ( gtk_image_new_from_stock (stock_name, GTK_ICON_SIZE_BUTTON)); #endif } else { w = GTK_IMAGE ( #ifndef HAVE_GTK4 gtk_image_new_from_icon_name (icon_name, GTK_ICON_SIZE_BUTTON)); #else gtk_image_new_from_icon_name (icon_name)); #endif } } else { if (!img->load_failed_p) { w = GTK_IMAGE ( #ifndef HAVE_GTK4 xg_get_image_for_pixmap (f, img, GTK_WIDGET (header_bar), NULL) #else egtk_get_image_for_pixmap (f, img, GTK_WIDGET (header_bar), NULL) #endif ); } } if (w) gtk_widget_set_visible (GTK_WIDGET (w), true); #ifndef HAVE_GTK4 gtk_button_set_image (btn, NULL); #else if (GTK_IS_LABEL (gtk_bin_get_child (GTK_BIN (btn)))) gtk_widget_destroy (gtk_bin_get_child (GTK_BIN (btn))); #endif Lisp_Object name = e->title; if (name && (!NILP (Fequal (name, build_string ("Save"))) || !NILP (Fequal (name, build_string ("Help"))) || !NILP (Fequal (name, build_string ("Load init files"))))) { gtk_button_set_label (GTK_BUTTON (btn), SSDATA (name)); } else { gtk_button_set_label (btn, NULL); #ifdef HAVE_GTK4 gtk_widget_remove_css_class (GTK_WIDGET (btn), "text-button"); gtk_widget_add_css_class (GTK_WIDGET (btn), "image-button"); if (w) gtk_container_add (GTK_CONTAINER (btn), GTK_WIDGET (w)); #else gtk_button_set_image (btn, GTK_WIDGET (w)); #endif } gulong ptr; if ((ptr = (gulong) g_object_get_data (G_OBJECT (btn), "click-handler"))) { g_signal_handler_disconnect (G_OBJECT (btn), ptr); } gulong l = g_signal_connect (G_OBJECT (btn), "clicked", G_CALLBACK (xg_tool_bar_callback), (gpointer) (intptr_t) e->idx); g_object_set_data (G_OBJECT (btn), "click-handler", (gpointer) l); if (e->help && !NILP (e->help)) gtk_widget_set_tooltip_text (GTK_WIDGET (btn), SSDATA (e->help)); else gtk_widget_set_tooltip_text (GTK_WIDGET (btn), NULL); if (stock_name) g_free (stock_name); if (icon_name) g_free (icon_name); g_object_unref (btn); } } if (!flag) { l = header_buttons_end; r = end_items; flag = true; goto loop; } g_list_free (header_buttons_start); g_list_free (header_buttons_end); g_list_free_full (end_items, xfree); g_list_free_full (start_items, xfree); unblock_input (); } #endif #endif DEFUN ("display-gtk-about-screen", Fdisplay_gtk_about_screen, Sdisplay_gtk_about_screen, 0, 1, "", doc: /* Display a GTK about screen for FRAME */) (Lisp_Object frame) { if (!frame || NILP (frame)) frame = Fselected_frame (); if (NILP (frame)) error ("No valid frame could be found"); CHECK_LIVE_FRAME (frame); show_about_dialog (0, XFRAME (frame)); return Qnil; } DEFUN ("display-gtk-header-menu", Fdisplay_gtk_header_menu, Sdisplay_gtk_header_menu, 0, 1, "", doc: /* Display the header bar menu */) (Lisp_Object frame) { if (!frame || NILP (frame)) frame = Fselected_frame (); if (NILP (frame)) error ("No valid frame could be found"); CHECK_LIVE_FRAME (frame); struct frame *f = XFRAME (frame); check_pgtk_display_info (frame); #ifndef HAVE_GTK4 #ifdef HAVE_GTK3 if (!FRAME_DISPLAY_HEADER_BAR_MENU (f)) error ("There is no header bar menu for frame %s", SSDATA (f->name)); #endif if (!FRAME_PGTK_P (f) || FRAME_X_P (f)) error ("Must be a pgtk or X frame"); #ifdef HAVE_GTK3 GtkHeaderBar *hbar = FRAME_GTK_HEADER_BAR (f); update_frame_header_bar (f); GtkMenuButton *btn = NULL; gtk_container_foreach (GTK_CONTAINER (hbar), (GtkCallback) gtk_find_gtk_menu_button, &btn); if (!btn) return Qnil; #ifndef HAVE_GTK4 gtk_button_clicked (GTK_BUTTON (btn)); #else gtk_menu_button_popup (btn); #endif #else { if (FRAME_EXTERNAL_MENU_BAR (f)) { GList *children = gtk_container_get_children ( GTK_CONTAINER (FRAME_X_OUTPUT (f)->menubar_widget)); GtkWidget *w = children->data; g_list_free (children); gtk_menu_shell_activate_item (FRAME_X_OUTPUT (f)->menubar_widget, w, true); } } #endif #endif #ifdef HAVE_GTK4 if (FRAME_MENU_BAR (f)) frame_activate_menu_bar (f); else if (FRAME_DISPLAY_HEADER_BAR (f) && FRAME_GTK_HEADER_BAR (f) && gtk_widget_is_visible (GTK_WIDGET (FRAME_GTK_HEADER_BAR (f)))) { if (!FRAME_GTK_HEADER_BAR (f)) return Qnil; GtkMenuButton *btn = NULL; gtk_container_foreach (GTK_CONTAINER (FRAME_GTK_HEADER_BAR (f)), (GtkCallback) gtk_find_gtk_menu_button, &btn); if (!btn) return Qnil; gtk_menu_button_popup (btn); return Qnil; } else #endif call2 (intern_c_string ("mouse-popup-menubar"), Qnil, Qnil); return Qnil; } void syms_of_gtkinter (void) { defsubr (&Sdisplay_gtk_about_screen); defsubr (&Sdisplay_gtk_header_menu); }