/* Utilities for GTK 4(.0) or later 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 . */ #define __GI_SCANNER__ #include "config.h" #ifdef HAVE_GTK4 #include "lisp.h" #include "atimer.h" #include "blockinput.h" #include "coding.h" #include "gtkinter.h" #include "keyboard.h" #include "pgtksubr.h" #include "buffer.h" #include "float.h" #include "dynlib.h" #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_GDK_X11 #include #include #endif #define EGTK_TEXT_CANCEL "Cancel" #define EGTK_TEXT_OK "OK" #define EGTK_TEXT_OPEN "Open" #define ID_TO_WIDGET_INCR 32 #define TB_INFO_KEY "xg_frame_tb_info" #define GTK_FOREGROUND_COLOR "gtk-style-foreground" #define GTK_BACKGROUND_COLOR "gtk-style-background" #define GTK_MFOREGROUND_COLOR "gtk-style-modeline-foreground" #define GTK_MBACKGROUND_COLOR "gtk-style-modeline-background" #define GTK_LFOREGROUND_COLOR "gtk-style-link-foreground" #define GTK_SLFOREGROUND_COLOR "gtk-style-slink-foreground" #define GTK_TBACKGROUND_COLOR "gtk-style-tb-background" #if ! (CAIRO_DEBUG_ARGB_THEMING) #define CAIRO_HAVE_RGBA128F CAIRO_VERSION_MAJOR > 1 \ || (CAIRO_VERSION_MINOR >= 17 && CAIRO_VERSION_MICRO >= 2) #else #define CAIRO_HAVE_RGBA128F 0 #endif #define ZTYPE_MAXIMUM(t) \ ((t) ((t) 0 < (t) -1 \ ? (t) -1 \ : ((((t) 1 << (sizeof (t) * CHAR_BIT - 2)) - 1) * 2 + 1))) #define EG_WEIGHT_TO_SYMBOL(w) \ (w <= PANGO_WEIGHT_THIN \ ? Qextra_light \ : w <= PANGO_WEIGHT_ULTRALIGHT \ ? Qlight \ : w <= PANGO_WEIGHT_LIGHT \ ? Qsemi_light \ : w < PANGO_WEIGHT_MEDIUM \ ? Qnormal \ : w <= PANGO_WEIGHT_SEMIBOLD \ ? Qsemi_bold \ : w <= PANGO_WEIGHT_BOLD \ ? Qbold \ : w <= PANGO_WEIGHT_HEAVY ? Qextra_bold \ : Qultra_bold) #define EG_STYLE_TO_SYMBOL(s) \ (s == PANGO_STYLE_OBLIQUE ? Qoblique \ : s == PANGO_STYLE_ITALIC ? Qitalic : Qnormal) #ifdef __GNUC__ #define TODO \ do \ { \ printf ("%s:%d: not implemented: %s\n", __FILE__, __LINE__, __func__); \ } \ while (0) #else #define TODO #endif struct popover_cb_data { widget_value *root_wv; GtkWidget **popover; GCallback select_cb; GCallback highlight_cb; GCallback deactivate_cb; GdkRectangle *pos_rect; egtk_menu_cb_data *cb_data; struct frame *frame; }; struct egtk_frame_tb_info { Lisp_Object last_tool_bar; Lisp_Object style; int n_last_items; int hmargin, vmargin; GtkTextDirection dir; }; static char *x_last_font_name; static GdkDisplay *default_display; static struct { GtkWidget **widgets; ptrdiff_t max_size; ptrdiff_t used; } id_to_widget; /* Linked list of all allocated struct xg_menu_cb_data. Used for marking during GC. The next member points to the items. */ static egtk_list_node egtk_menu_cb_list; /* Linked list of all allocated struct xg_menu_item_cb_data. Used for marking during GC. The next member points to the items. */ static egtk_list_node egtk_menu_item_cb_list; static int scroll_bar_width_for_theme; static int scroll_bar_height_for_theme; typedef struct _EmacsGtkAccessible { GtkAccessible parent_instance; struct frame *frame; } EmacsGtkAccessible; typedef struct _EmacsGtkAccessibleClass { GtkAccessibleClass parent_class; } EmacsGtkAccessibleClass; typedef struct _EmacsWindow { GtkApplicationWindow parent; } EmacsWindow; typedef struct _EmacsWindowClass { GtkApplicationWindowClass parent_class; } EmacsWindowClass; typedef struct _LwMenuItem { GtkWidget parent; GtkLabel *label; GtkPopover *popover; widget_value *wv; GtkEventControllerMotion *motion_controller; GtkWidget *menu_bar; } LwMenuItem; typedef struct _LwMenuBar { GtkWidget parent; widget_value **wvs; ptrdiff_t nused; LwMenuItem *selected_item; egtk_menu_cb_data *cl_data; struct frame *frame; GCallback select_cb, highlight_cb, deactivate_cb; } LwMenuBar; typedef struct _XegTtipPopover { GtkPopover parent; } XegTtipPopover; typedef struct _XegTtipPopoverClass { GtkPopoverClass parent_class; } XegTtipPopoverClass; typedef struct _XegBox { GtkBox parent; } XegBox; typedef struct _XegBoxClass { GtkBoxClass parent_class; } XegBoxClass; typedef struct _LwMenuBarClass { GtkWidgetClass parent_class; } LwMenuBarClass; typedef struct _LwMenuItemClass { GtkWidgetClass parent; } LwMenuItemClass; typedef struct _XegChildFrameBin { GtkBin parent; } XegChildFrameBin; typedef struct _XegChildFrameBinClass { GtkBinClass parent_class; } XegChildFrameBinClass; static GHashTable *atab; typedef struct _EmacsResizableDrawingAreaClass EmacsResizableDrawingAreaClass; #pragma GCC diagnostic ignored "-Wmissing-prototypes" #define G_STATIC_ASSERT(f) // FIXME kludge to fix GObject bug static void emacs_resizable_drawing_area_atk_text_init (AtkTextIface *iface); static void emacs_resizable_drawing_area_action_map_init (GActionMapInterface *iface); static void emacs_resizable_drawing_area_action (GtkWidget *widget, const char *action_name, GVariant *parameter); static void boolean_action_activate (GSimpleAction *simple, GVariant *parameter, gpointer user_data); static LwMenuBar * lw_menu_bar_new (void); static LwMenuItem * lw_menu_item_new (widget_value *wv); G_DEFINE_TYPE_WITH_CODE (EmacsGtkAccessible, emacs_gtk_accessible, GTK_TYPE_ACCESSIBLE, G_IMPLEMENT_INTERFACE (ATK_TYPE_TEXT, emacs_resizable_drawing_area_atk_text_init)) G_DEFINE_TYPE (LwMenuBar, lw_menu_bar, GTK_TYPE_WIDGET) G_DEFINE_TYPE (LwMenuItem, lw_menu_item, GTK_TYPE_WIDGET) G_DEFINE_TYPE (XegTtipPopover, xeg_ttip_popover, GTK_TYPE_POPOVER) G_DEFINE_TYPE (XegBox, xeg_box, GTK_TYPE_BOX) G_DEFINE_TYPE (XegChildFrameBin, xeg_child_frame_bin, GTK_TYPE_BIN); static void lw_menu_item_popover_unmap (GtkPopover *popover, LwMenuBar *mbar); static GtkWidget * xeg_ttip_popover_new (void) { return g_object_new (xeg_ttip_popover_get_type (), NULL); } static void xeg_ttip_popover_init (XegTtipPopover *po) { } GtkWidget * xeg_child_frame_bin_new (void) { return g_object_new (xeg_child_frame_bin_get_type (), NULL); } static void xeg_child_frame_bin_init (XegChildFrameBin *bin) { } static void xeg_child_frame_bin_snapshot (GtkWidget *w, GdkSnapshot *snap) { gtk_snapshot_render_background (snap, gtk_widget_get_style_context (GTK_WIDGET (gtk_widget_get_root (w))), 0, 0, gtk_widget_get_width (w), gtk_widget_get_height (w)); GTK_WIDGET_CLASS (xeg_child_frame_bin_parent_class)->snapshot (w, snap); } static void xeg_child_frame_bin_class_init (XegChildFrameBinClass *clazz) { gtk_widget_class_set_css_name (GTK_WIDGET_CLASS (clazz), "window"); gtk_widget_class_set_layout_manager_type (GTK_WIDGET_CLASS (clazz), GTK_TYPE_BIN_LAYOUT); GTK_WIDGET_CLASS (clazz)->snapshot = xeg_child_frame_bin_snapshot; } static GtkWidget * xeg_box_new (void) { return g_object_new (xeg_box_get_type (), "orientation", GTK_ORIENTATION_HORIZONTAL, "spacing", 2, NULL); } static void xeg_box_measure (GtkWidget *widget, GtkOrientation orientation, int for_size, int *minimum, int *natural, int *minimum_baseline, int *natural_baseline) { GtkWidgetClass *wc = GTK_WIDGET_CLASS (xeg_box_parent_class); wc->measure (widget, orientation, for_size, minimum, natural, minimum_baseline, natural_baseline); *minimum = 0; *minimum_baseline = 0; } static void xeg_box_init (XegBox *box) { } static void xeg_box_class_init (XegBoxClass *box_class) { GtkWidgetClass *wc = GTK_WIDGET_CLASS (box_class); wc->measure = xeg_box_measure; } static void xeg_ttip_popover_class_init (XegTtipPopoverClass *clazz) { GtkWidgetClass *wc = GTK_WIDGET_CLASS (clazz); gtk_widget_class_set_css_name (wc, "tooltip"); wc->focus = NULL; wc->grab_focus = NULL; wc->move_focus = NULL; wc->activate_signal = 0; GTK_POPOVER_CLASS (clazz)->activate_default = NULL; gtk_widget_class_set_accessible_role (wc, ATK_ROLE_TOOL_TIP); } static GtkPopover * build_menu_from_widget_value (widget_value *wv, egtk_menu_cb_data *cl_data, GCallback select_cb, GCallback deactivate_cb, GCallback highlight_cb, struct frame *f, bool mb, bool fc); static void lw_menu_item_popup (LwMenuItem *item) { LwMenuBar *mb = (gpointer) item->menu_bar; item->popover = build_menu_from_widget_value (item->wv, mb->cl_data, mb->select_cb, mb->deactivate_cb, mb->highlight_cb, mb->frame, true, true); g_signal_connect (G_OBJECT (item->popover), "unmap", G_CALLBACK (lw_menu_item_popover_unmap), mb); GtkAllocation alloc; gtk_widget_get_allocation (GTK_WIDGET (item), &alloc); gtk_widget_set_parent (GTK_WIDGET (item->popover), GTK_WIDGET (item)); gtk_popover_set_has_arrow (item->popover, false); gtk_popover_set_position (item->popover, GTK_POS_BOTTOM); gtk_widget_set_halign (GTK_WIDGET (item->popover), GTK_ALIGN_START); gtk_popover_popup (item->popover); } static ptrdiff_t lw_menu_bar_extend_used (LwMenuBar *mbar) { block_input (); ptrdiff_t oldsize = mbar->nused * (sizeof (widget_value *)); ++mbar->nused; ptrdiff_t size = mbar->nused * (sizeof (widget_value *)); widget_value **old = mbar->wvs; mbar->wvs = xmalloc (size); memcpy (mbar->wvs, old, oldsize); xfree (old); unblock_input (); return mbar->nused; } static widget_value * lw_menu_bar_append_widget_value (LwMenuBar *bar, widget_value *wv) { ptrdiff_t idx = lw_menu_bar_extend_used (bar); LwMenuItem *item = lw_menu_item_new (wv); item->menu_bar = GTK_WIDGET (bar); gtk_widget_set_parent (GTK_WIDGET (item), GTK_WIDGET (bar)); bar->wvs[idx - 1] = item->wv; return item->wv; } static void lw_menu_bar_remove_item_wv (LwMenuBar *bar, widget_value *wv) { ptrdiff_t ids = 0; for (ptrdiff_t q = 0; q < bar->nused; ++q) if (bar->wvs[q] != wv) ++ids; bar->nused = ids; ptrdiff_t counter = 0; for (ptrdiff_t q = 0; q < ids; ++q) { while (bar->wvs[counter] == wv) ++counter; bar->wvs[q] = bar->wvs[counter]; ++counter; } widget_value **old = bar->wvs; bar->wvs = xmalloc (ids * (sizeof *bar->wvs)); memcpy (bar->wvs, old, ids * (sizeof *bar->wvs)); xfree (old); GSList *widgets_to_remove = NULL; for (LwMenuItem *item = (LwMenuItem *) gtk_widget_get_first_child (GTK_WIDGET (bar)); item; item = (LwMenuItem *) gtk_widget_get_next_sibling (GTK_WIDGET (item))) widgets_to_remove = g_slist_append (widgets_to_remove, item); for (GSList *l = widgets_to_remove; l; l = l->next) gtk_widget_destroy (l->data); g_slist_free (widgets_to_remove); } static void lw_menu_item_popdown (LwMenuItem *item) { if (item->popover) gtk_popover_popdown (item->popover); } static void lw_menu_bar_size_allocate (GtkWidget *widget, gint width, gint height, gint baseline) { LwMenuBar *bar = (gpointer) widget; FRAME_X_OUTPUT (bar->frame)->menubar_height = height; } static void lw_menu_item_size_allocate (GtkWidget *widget, gint foo, gint bar, gint baz) { for (GtkWidget *c = gtk_widget_get_first_child (widget); c; c = gtk_widget_get_next_sibling (c)) { if (GTK_IS_NATIVE (c) && gtk_widget_is_visible (c)) gtk_native_check_resize (GTK_NATIVE (c)); } } static void lw_menu_bar_select_item (LwMenuBar *bar, LwMenuItem *item) { bool_bf was_mapped; bool_bf changed = item != bar->selected_item; if (bar->selected_item) was_mapped = gtk_widget_get_mapped (GTK_WIDGET (bar->selected_item->popover)); else was_mapped = false; if (!changed) return; if (was_mapped) lw_menu_item_popdown (bar->selected_item); if (bar->selected_item) gtk_widget_unset_state_flags (GTK_WIDGET (bar->selected_item), GTK_STATE_FLAG_SELECTED); bar->selected_item = item; if (bar->selected_item) gtk_widget_set_state_flags (GTK_WIDGET (bar->selected_item), GTK_STATE_FLAG_SELECTED, FALSE); if (bar->selected_item) lw_menu_item_popup (bar->selected_item); if (!item) FRAME_X_OUTPUT (bar->frame)->menubar_or_popup_count -= 1; else FRAME_X_OUTPUT (bar->frame)->menubar_or_popup_count += 1; } static void lw_menu_item_popover_unmap (GtkPopover *popover, LwMenuBar *mbar) { if (mbar->selected_item && mbar->selected_item->popover == popover) lw_menu_bar_select_item (mbar, NULL); } static void lw_menu_item_click_gesture_pressed (GtkGestureClick *gesture, gint n_press, gdouble x, gdouble y, gpointer user_data) { LwMenuItem *item = user_data; if (item->wv->contents) lw_menu_bar_select_item ((LwMenuBar *) item->menu_bar, item); else { LwMenuBar *bar = (LwMenuBar *) item->menu_bar; egtk_menu_item_cb_data *cb_data = xmalloc (sizeof *cb_data); #define I item->wv cb_data->select_id = 0; cb_data->help = I->help; cb_data->cl_data = bar->cl_data; cb_data->call_data = I->call_data; ((void (*) (GtkWidget *, gpointer)) bar->select_cb) (GTK_WIDGET (item), cb_data); #undef I xfree (cb_data); } } static void item_enter_cb (GtkEventController *controller, double x, double y, GdkCrossingMode mode, gpointer data) { GtkWidget *target; LwMenuBar *bar; target = gtk_event_controller_get_widget (controller); bar = (LwMenuBar *) (gtk_widget_get_ancestor (target, lw_menu_bar_get_type ())); if (!bar->selected_item) gtk_widget_set_state_flags (target, GTK_STATE_FLAG_SELECTED, FALSE); else lw_menu_bar_select_item (bar, (LwMenuItem *) target); } static void item_leave (GtkEventController *controller, GdkCrossingMode mode, gpointer data) { GtkWidget *target; LwMenuBar *bar; target = gtk_event_controller_get_widget (controller); bar = (LwMenuBar *) (gtk_widget_get_ancestor (target, lw_menu_bar_get_type ())); if (target != (gpointer) bar->selected_item) gtk_widget_unset_state_flags (target, GTK_STATE_FLAG_SELECTED); } static void lw_menu_item_init (LwMenuItem *item) { item->motion_controller = GTK_EVENT_CONTROLLER_MOTION (gtk_event_controller_motion_new ()); gtk_widget_add_controller (GTK_WIDGET (item), GTK_EVENT_CONTROLLER (item->motion_controller)); item->label = g_object_new (GTK_TYPE_LABEL, "use-underline", TRUE, NULL); gtk_widget_set_parent (GTK_WIDGET (item->label), GTK_WIDGET (item)); gtk_event_controller_set_propagation_limit (GTK_EVENT_CONTROLLER (item->motion_controller), GTK_LIMIT_NONE); g_signal_connect (GTK_EVENT_CONTROLLER (item->motion_controller), "enter", G_CALLBACK (item_enter_cb), NULL); g_signal_connect (GTK_EVENT_CONTROLLER (item->motion_controller), "leave", G_CALLBACK (item_leave), NULL); item->popover = NULL; GtkGesture *gesture = gtk_gesture_click_new (); g_signal_connect (G_OBJECT (gesture), "pressed", G_CALLBACK (lw_menu_item_click_gesture_pressed), item); gtk_widget_add_controller (GTK_WIDGET (item), GTK_EVENT_CONTROLLER (gesture)); } static void lw_menu_item_dispose (GObject *o) { LwMenuItem *it = (LwMenuItem *) o; #pragma GCC diagnostic ignored "-Wstrict-aliasing" g_clear_pointer ((GtkWidget **) (gpointer) &it->label, gtk_widget_unparent); g_clear_pointer ((GtkWidget **) (gpointer) &it->popover, gtk_widget_unparent); if (((LwMenuBar *) it->menu_bar)->selected_item == it) ((LwMenuBar *) it->menu_bar)->selected_item = NULL; if (it->popover) gtk_widget_unparent (GTK_WIDGET (it->popover)); GSList *widgets_to_remove = NULL; for (GtkWidget *item = gtk_widget_get_first_child (GTK_WIDGET (it)); item; item = gtk_widget_get_next_sibling (GTK_WIDGET (item))) widgets_to_remove = g_slist_append (widgets_to_remove, item); for (GSList *l = widgets_to_remove; l; l = l->next) gtk_widget_destroy (l->data); g_slist_free (widgets_to_remove); #pragma GCC diagnostic push G_OBJECT_CLASS (lw_menu_item_parent_class)->dispose (o); } static void lw_menu_item_class_init (LwMenuItemClass *klass) { GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); GObjectClass *class = G_OBJECT_CLASS (klass); gtk_widget_class_set_css_name (widget_class, "item"); class->dispose = lw_menu_item_dispose; gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BOX_LAYOUT); widget_class->size_allocate = lw_menu_item_size_allocate; } static LwMenuItem * lw_menu_item_new (widget_value *wv) { block_input (); LwMenuItem *item = g_object_new (lw_menu_item_get_type (), NULL); item->wv = wv; gtk_label_set_text (item->label, item->wv->name); item->popover = NULL; if (!NILP (item->wv->help)) gtk_widget_set_tooltip_text (GTK_WIDGET (item), SSDATA (ENCODE_UTF_8 (item->wv->help))); unblock_input (); return item; } static void lw_menu_bar_dispose (GObject *o) { LwMenuBar *bar = (LwMenuBar *) o; GSList *widgets_to_remove = NULL; for (LwMenuItem *item = (LwMenuItem *) gtk_widget_get_first_child (GTK_WIDGET (bar)); item; item = (LwMenuItem *) gtk_widget_get_next_sibling (GTK_WIDGET (item))) widgets_to_remove = g_slist_append (widgets_to_remove, item); for (GSList *l = widgets_to_remove; l; l = l->next) gtk_widget_destroy (l->data); g_slist_free (widgets_to_remove); } static void lw_menu_bar_finalize (GObject *o) { LwMenuBar *bar = (LwMenuBar *) o; xfree (bar->wvs); bar->nused = 0; G_OBJECT_CLASS (lw_menu_bar_parent_class)->finalize (o); } static void lw_menu_bar_init (LwMenuBar *bar) { bar->nused = 0; bar->selected_item = NULL; bar->wvs = xmalloc (sizeof *bar->wvs); } static LwMenuBar * lw_menu_bar_new (void) { return g_object_new (lw_menu_bar_get_type (), NULL); } static void lw_menu_bar_class_init (LwMenuBarClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->finalize = lw_menu_bar_finalize; object_class->dispose = lw_menu_bar_dispose; GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BOX_LAYOUT); gtk_widget_class_set_css_name (widget_class, "menubar"); gtk_widget_class_set_accessible_role (widget_class, ATK_ROLE_MENU_BAR); widget_class->size_allocate = lw_menu_bar_size_allocate; } static void lw_menu_bar_set_root_wv (LwMenuBar *mbar, widget_value *wv) { for (ptrdiff_t z = 0; z < mbar->nused; ++z) lw_menu_bar_remove_item_wv (mbar, mbar->wvs[z]); for (widget_value *i = wv->contents; i; i = i->next) lw_menu_bar_append_widget_value (mbar, i); } static GtkWidget * emacs_window_new (void); G_DEFINE_TYPE (EmacsWindow, emacs_window, GTK_TYPE_APPLICATION_WINDOW); static void emacs_window_init (EmacsWindow *w) { static GActionEntry we[] = {{"menu.bef_enabled", boolean_action_activate, "b", "true", boolean_action_activate}, {"mv.bef_disabled", boolean_action_activate, "b", "false", boolean_action_activate}}; g_action_map_add_action_entries (G_ACTION_MAP (w), we, 2, w); } static void emacs_window_class_init (EmacsWindowClass *klass) { gtk_widget_class_install_action (GTK_WIDGET_CLASS (klass), "menu.select", (gpointer) G_VARIANT_TYPE_ANY, emacs_resizable_drawing_area_action); } static GtkWidget * emacs_window_new (void) { EmacsWindow *window = g_object_new (emacs_window_get_type (), NULL); return GTK_WIDGET (window); } static EmacsGtkAccessible * emacs_gtk_accessible_new (EmacsResizableDrawingArea *area) { EmacsGtkAccessible *accessible = (EmacsGtkAccessible *) g_object_new (emacs_gtk_accessible_get_type (), NULL); accessible->frame = area->f; gtk_accessible_set_widget (GTK_ACCESSIBLE (accessible), GTK_WIDGET (area)); return accessible; } static void zz_add_action (GActionMap *map, GAction *action) { EmacsResizableDrawingArea *area = (gpointer) map; g_hash_table_insert (area->action_map, (gpointer) g_action_get_name (action), (gpointer) action); g_simple_action_group_insert (G_SIMPLE_ACTION_GROUP (area->action_group), action); } static void zz_remove_action (GActionMap *map, const char *action_name) { EmacsResizableDrawingArea *area = (gpointer) map; g_hash_table_remove (area->action_map, action_name); g_simple_action_group_remove (G_SIMPLE_ACTION_GROUP (area->action_group), action_name); } static GAction * zz_lookup_action (GActionMap *map, const char *action_name) { EmacsResizableDrawingArea *area = (gpointer) map; return g_hash_table_lookup (area->action_map, action_name); } static void emacs_resizable_drawing_area_action_map_init (GActionMapInterface *iface) { iface->add_action = zz_add_action; iface->remove_action = zz_remove_action; iface->lookup_action = zz_lookup_action; } static char * get_utf8_string (const char *str); static GtkWindow * locate_nonchild_window (struct frame *child); static void zz_action_update (EmacsResizableDrawingArea *a); static GMenuModel * zz_build_menu_model (EmacsResizableDrawingArea *area) { GMenu *menu, *section; GMenuItem *item; menu = g_menu_new (); section = g_menu_new (); item = g_menu_item_new ("Kill", "clipboard.cut"); g_menu_item_set_attribute (item, "verb-icon", "s", "edit-cut-symbolic"); g_menu_item_set_attribute (item, "display-hint", "s", "horizontal-buttons"); g_menu_append_item (section, item); g_object_unref (item); item = g_menu_item_new ("Save", "clipboard.copy"); g_menu_item_set_attribute (item, "verb-icon", "s", "edit-copy-symbolic"); g_menu_item_set_attribute (item, "display-hint", "s", "horizontal-buttons"); g_menu_append_item (section, item); g_object_unref (item); item = g_menu_item_new ("Yank", "clipboard.paste"); g_menu_item_set_attribute (item, "verb-icon", "s", "edit-paste-symbolic"); g_menu_item_set_attribute (item, "display-hint", "s", "horizontal-buttons"); g_menu_append_item (section, item); g_object_unref (item); item = g_menu_item_new ("Delete", "selection.delete"); g_menu_item_set_attribute (item, "verb-icon", "s", "edit-delete-symbolic"); g_menu_item_set_attribute (item, "display-hint", "s", "horizontal-buttons"); g_menu_append_item (section, item); g_object_unref (item); item = g_menu_item_new ("Select all", "selection.select-all"); g_menu_item_set_attribute (item, "verb-icon", "s", "edit-select-all-symbolic"); g_menu_item_set_attribute (item, "display-hint", "s", "horizontal-buttons"); g_menu_append_item (section, item); GMenuItem *sec = g_menu_item_new_section (NULL, G_MENU_MODEL (section)); g_menu_item_set_attribute (sec, "display-hint", "s", "horizontal-buttons"); g_menu_append_item (menu, sec); g_object_unref (section); g_object_unref (sec); g_object_unref (item); return G_MENU_MODEL (menu); } static void l_change_cb (struct atimer *atimer) { if (!NILP (Ffeaturep (intern_c_string ("custom"), Qnil))) if (!NILP (call1 (Qcustom_theme_enabled_p, Qgtk))) call1 (Qload_theme, Qgtk); } static void style_change_cb (GtkStyleProvider *provider, GParamSpec *spec, struct frame *data) { start_atimer (ATIMER_RELATIVE, make_timespec (0, 100), l_change_cb, 0); } static GVariant * variant_from_item_cb_data (struct popover_cb_data *data, GSList **ptab) { GVariant *variant = g_variant_new_boolean (true); *ptab = g_slist_append (*ptab, data); g_hash_table_insert (atab, variant, data); return variant; } static struct popover_cb_data * cbd_from_item_variant (GVariant *variant) { return g_hash_table_lookup (atab, variant); } static void pop_closed (GtkPopover *popover, gpointer user_data) { gtk_window_set_focus (locate_nonchild_window (((EmacsResizableDrawingArea *) gtk_widget_get_parent (user_data))->f), FRAME_GTK_WIDGET (((EmacsResizableDrawingArea *) gtk_widget_get_parent (user_data))->f)); } void frame_activate_menu_bar (struct frame *f) { block_input (); eassert (FRAME_MENU_BAR (f)); LwMenuItem *item = (LwMenuItem *) gtk_widget_get_first_child (GTK_WIDGET (FRAME_MENU_BAR (f))); if (item) lw_menu_bar_select_item ((LwMenuBar *) FRAME_MENU_BAR (f), item); unblock_input (); } void emacs_resizable_drawing_area_show_selection_options ( EmacsResizableDrawingArea *area) { int x = XWINDOW (FRAME_SELECTED_WINDOW (area->f))->cursor.x; x += XWINDOW (FRAME_SELECTED_WINDOW (area->f))->pixel_left; int y = XWINDOW (FRAME_SELECTED_WINDOW (area->f))->cursor.y; y += XWINDOW (FRAME_SELECTED_WINDOW (area->f))->pixel_top + FRAME_LINE_HEIGHT (area->f); zz_action_update (area); GMenuModel *mm = zz_build_menu_model (area); GtkPopover *popover = GTK_POPOVER ( gtk_popover_menu_new_from_model_full (mm, GTK_POPOVER_MENU_NESTED)); if (area->nrc_popover) gtk_widget_destroy (GTK_WIDGET (area->nrc_popover)); area->nrc_popover = popover; g_object_ref (area->nrc_popover); gtk_popover_set_position (popover, GTK_POS_BOTTOM); gtk_popover_set_pointing_to (popover, &(GdkRectangle){x, y, 1, 1}); gtk_widget_set_parent (GTK_WIDGET (popover), GTK_WIDGET (area)); gtk_popover_set_has_arrow (popover, false); gtk_widget_set_halign (GTK_WIDGET (popover), GTK_ALIGN_START); g_signal_connect (G_OBJECT (popover), "closed", G_CALLBACK (pop_closed), popover); g_object_unref (mm); gtk_popover_popup (popover); } static void emacs_resizable_drawing_area_action (GtkWidget *widget, const char *action_name, GVariant *parameter) { block_input (); if (EQ (intern_c_string (action_name), intern_c_string ("clipboard.copy"))) { CALLN (Ffuncall_interactively, intern_c_string ("kill-ring-save"), CALLN (Ffuncall, intern_c_string ("mark")), Fpoint ()); } else if (EQ (intern_c_string (action_name), intern_c_string ("clipboard.cut"))) { Ffuncall (1, (Lisp_Object[]){(intern_c_string ("touch-screen--safe-kill"))}); } else if (EQ (intern_c_string (action_name), intern_c_string ("selection.select-all"))) { CALLN (Ffuncall, intern_c_string ("mark-whole-buffer")); } else if (EQ (intern_c_string (action_name), intern_c_string ("selection.delete"))) { if (!NILP (call0 (intern_c_string ("region-active-p")))) call0 (intern_c_string ("delete-active-region")); } else if (EQ (intern_c_string (action_name), intern_c_string ("clipboard.paste"))) { CALLN (Ffuncall_interactively, intern_c_string ("yank")); } else if (EQ (intern_c_string (action_name), intern_c_string ("menu.select"))) { struct popover_cb_data *data = cbd_from_item_variant (parameter); egtk_menu_item_cb_data *cb_data = xmalloc (sizeof *cb_data); #define I data->root_wv cb_data->select_id = 0; cb_data->help = I->help; cb_data->cl_data = data->cb_data; cb_data->call_data = I->call_data; ((void (*) (GtkWidget *, gpointer)) data->select_cb) (GTK_WIDGET ( *(data->popover)), cb_data); #undef I xfree (cb_data); } unblock_input (); } static GMenu * build_menu_model_from_widget_value (GtkWidget **po, widget_value *wv, egtk_menu_cb_data *cl_data, GCallback select_cb, GCallback deactivate_cb, GCallback highlight_cb, GSList **ptab, struct frame *f, GSList **pcd) { GMenu *model = g_menu_new (); GMenu *cs = g_menu_new (); const char *fsn_name = NULL; GSList *cns = 0; int idx = 0; for (widget_value *item = wv->contents; item; item = item->next) { ++idx; if (!item->call_data && !item->contents && !menu_separator_name_p (item->name)) { if (idx != 1) { GMenuItem *label = g_menu_item_new (item->name, NULL); g_menu_item_set_label (label, item->name); g_menu_append_item (cs, label); g_object_unref (label); } else { fsn_name = item->name; } } else if (menu_separator_name_p (item->name)) { g_menu_append_section (model, fsn_name, G_MENU_MODEL (cs)); cs = g_menu_new (); idx = 0; fsn_name = NULL; } else { char *utf8_label; char *utf8_key; utf8_label = get_utf8_string (item->name); utf8_key = get_utf8_string (item->key); if (!item->contents) { struct popover_cb_data *cbd = xmalloc (sizeof (struct popover_cb_data)); cbd->cb_data = cl_data; cbd->frame = f; cbd->highlight_cb = highlight_cb; cbd->deactivate_cb = deactivate_cb; cbd->root_wv = item; cbd->select_cb = select_cb; cbd->popover = po; *pcd = g_slist_append (*pcd, cbd); const char *bef = item->enabled ? (item->button_type == BUTTON_TYPE_TOGGLE || item->button_type == BUTTON_TYPE_RADIO ? item->selected ? "win.menu.bef_enabled" : "win.mv.bef_disabled" : "menu.select") : "menu.select.disabled"; GMenuItem *v = g_menu_item_new (utf8_label, bef); cns = g_slist_append (cns, cbd); g_menu_item_set_action_and_target_value ( v, bef, variant_from_item_cb_data (cbd, ptab)); if (utf8_key) g_menu_item_set_attribute (v, "accel", "s", utf8_key, NULL); g_menu_append_item (cs, v); g_object_unref (v); } else { GMenu *menu = build_menu_model_from_widget_value (po, item, cl_data, select_cb, deactivate_cb, highlight_cb, ptab, f, pcd); g_menu_append_submenu (cs, item->name, G_MENU_MODEL (menu)); g_object_unref (menu); } xfree (utf8_label); if (utf8_key) xfree (utf8_key); } } g_menu_append_section (model, fsn_name, G_MENU_MODEL (cs)); g_object_unref (cs); g_slist_free (cns); return model; } static void free_ptab_gs1 (gpointer gsl) { xfree (gsl); } static void free_ptab_gsl (gpointer gsl) { g_slist_free_full (gsl, free_ptab_gs1); } static void free_pzt_dat1 (gpointer pzt) { g_hash_table_remove (atab, pzt); } static void free_pzt_dat (gpointer pzt) { g_slist_free_full (pzt, free_pzt_dat1); } static void menu_widget_popped_down (GtkPopover *popover, gpointer user_data) { gtk_window_set_focus (locate_nonchild_window (user_data), FRAME_GTK_WIDGET ((struct frame *) user_data)); } static GtkPopover * build_menu_from_widget_value (widget_value *wv, egtk_menu_cb_data *cl_data, GCallback select_cb, GCallback deactivate_cb, GCallback highlight_cb, struct frame *f, bool mb, bool fc) { zz_action_update ((gpointer) FRAME_GTK_WIDGET (f)); GtkPopover **po = xmalloc (sizeof *po); GSList *ptab = NULL; GSList *pcd = NULL; GMenu *model = build_menu_model_from_widget_value ((GtkWidget **) po, wv, cl_data, select_cb, deactivate_cb, highlight_cb, &ptab, f, &pcd); g_menu_freeze (model); *po = GTK_POPOVER (gtk_popover_menu_new_from_model_full ( G_MENU_MODEL (model), (!mb || fc) && NILP (Vpgtk_last_event_from_touchscreen) ? GTK_POPOVER_MENU_NESTED : 0)); if (!mb) gtk_widget_set_parent (GTK_WIDGET (*po), FRAME_GTK_WIDGET (f)); gtk_widget_set_halign (GTK_WIDGET (*po), GTK_ALIGN_START); gtk_popover_set_has_arrow (*po, mb); g_object_set_data_full (G_OBJECT (*po), "eg-ptab-data", ptab, free_pzt_dat); g_object_set_data_full (G_OBJECT (*po), "eg-pcd-data", pcd, free_ptab_gsl); g_object_set_data_full (G_OBJECT (*po), "eg-pot", po, xfree); g_signal_connect (*po, "closed", deactivate_cb, cl_data); g_signal_connect (*po, "closed", G_CALLBACK (menu_widget_popped_down), f); g_object_set_data_full (G_OBJECT (*po), "eg-model", model, g_object_unref); gtk_popover_set_autohide (*po, true); return *po; } GtkWidget * build_popover_menu_bar (widget_value *wv, egtk_menu_cb_data *cl_data, GCallback select_cb, GCallback deactivate_cb, GCallback highlight_cb, struct frame *f, GtkWidget *mbar) { zz_action_update ((gpointer) FRAME_GTK_WIDGET (f)); LwMenuBar **po = xmalloc (sizeof *po); *po = mbar ? (LwMenuBar *) mbar : lw_menu_bar_new (); (*po)->frame = f; lw_menu_bar_set_root_wv (*po, wv); (*po)->cl_data = cl_data; (*po)->deactivate_cb = deactivate_cb; (*po)->highlight_cb = highlight_cb; (*po)->select_cb = select_cb; g_object_set_data_full (G_OBJECT (*po), "eg-pot", po, xfree); g_object_set_data_full (G_OBJECT (*po), "eg-cb-data", cl_data, (GDestroyNotify) unref_cl_data); if (!gtk_widget_get_parent (GTK_WIDGET (*po)) && FRAME_EXTERNAL_MENU_BAR (f)) gtk_container_add (GTK_CONTAINER (FRAME_MB_BOX (f)), GTK_WIDGET (*po)); return GTK_WIDGET (*po); } void free_frame_menubar (struct frame *f) { if (FRAME_EXTERNAL_MENU_BAR (f) && FRAME_MENU_BAR (f)) gtk_widget_unparent (FRAME_MENU_BAR (f)); FRAME_MENU_BAR (f) = NULL; } static void emacs_gtk_accessible_class_init (EmacsGtkAccessibleClass *klass) { } void emacs_resizable_drawing_area_set_background (EmacsResizableDrawingArea *area, unsigned long cl) { GdkRGBA color; Emacs_Color rcolor; rcolor.pixel = cl; pgtk_query_color (area->f, &rcolor); color.alpha = 1.0; color.blue = rcolor.blue / 65535.0; color.red = rcolor.red / 65535.0; color.green = rcolor.red / 65535.0; if (area->background_color && gdk_rgba_equal (&color, area->background_color)) return; GdkRGBA *clon = malloc (sizeof color); memcpy (clon, &color, sizeof color); free (area->background_color); area->background_color = clon; gtk_widget_queue_allocate (GTK_WIDGET (area)); } static void emacs_gtk_accessible_init (EmacsGtkAccessible *area) { atk_object_set_role (ATK_OBJECT (area), ATK_ROLE_TEXT); atk_object_set_name (ATK_OBJECT (area), "Emacs"); } static AtkObject * atk_get_accessible (GtkWidget *widget) { GtkAccessible *accessible = ((EmacsResizableDrawingArea *) widget)->accessible; return ATK_OBJECT (accessible); } G_DEFINE_TYPE_WITH_CODE ( EmacsResizableDrawingArea, emacs_resizable_drawing_area, GTK_TYPE_WIDGET, G_IMPLEMENT_INTERFACE (G_TYPE_ACTION_MAP, emacs_resizable_drawing_area_action_map_init)) #undef G_STATIC_ASSERT #pragma GCC diagnostic push void run_main_loop_iteration (void) { g_main_context_iteration (NULL, false); } static void zz_action_update (EmacsResizableDrawingArea *a) { gtk_widget_action_set_enabled (GTK_WIDGET (a), "clipboard.copy", !NILP (call0 ( intern_c_string ("region-active-p")))); gtk_widget_action_set_enabled (GTK_WIDGET (a), "clipboard.cut", !NILP (call0 ( intern_c_string ("region-active-p")))); gtk_widget_action_set_enabled (GTK_WIDGET (a), "selection.delete", !NILP (call0 ( intern_c_string ("region-active-p")))); gtk_widget_action_set_enabled (GTK_WIDGET (a), "clipboard.paste", true); gtk_widget_action_set_enabled (GTK_WIDGET (a), "selection.select-all", true); gtk_widget_action_set_enabled (GTK_WIDGET (a), "menu.select", true); } static void boolean_action_activate (GSimpleAction *simple, GVariant *parameter, gpointer user_data) { emacs_resizable_drawing_area_action (user_data, "menu.select", parameter); } void emacs_resizable_drawing_area_init (EmacsResizableDrawingArea *a) { a->accessible = GTK_ACCESSIBLE (emacs_gtk_accessible_new (a)); gtk_accessible_set_widget (a->accessible, GTK_WIDGET (a)); gtk_widget_set_name (GTK_WIDGET (a), "Emacs entry"); zz_action_update (a); a->nrc_popover = NULL; a->action_map = g_hash_table_new (g_str_hash, g_str_equal); a->action_group = G_ACTION_GROUP (g_simple_action_group_new ()); gtk_widget_insert_action_group (GTK_WIDGET (a), "win", a->action_group); static GActionEntry we[] = {{"menu.bef_enabled", boolean_action_activate, "b", "true", boolean_action_activate}, {"mv.bef_disabled", boolean_action_activate, "b", "false", boolean_action_activate}}; g_action_map_add_action_entries (G_ACTION_MAP (a), we, 2, a); } static gchar * atk_get_text (AtkText *text, gint start_offset, gint end_offset) { block_input (); EmacsGtkAccessible *accessible = (EmacsGtkAccessible *) text; Lisp_Object window = FRAME_SELECTED_WINDOW (accessible->frame); Lisp_Object buffer = WINDOW_BUFFER (XWINDOW (window)); if (NILP (buffer) || !BUFFERP (buffer)) return NULL; ptrdiff_t specbind_down = SPECPDL_INDEX (); record_unwind_current_buffer (); Fset_buffer (buffer); Lisp_Object bufstr = make_buffer_string (start_offset, end_offset, 0); Lisp_Object utf = ENCODE_UTF_8 (bufstr); const gchar *khar = g_strdup (SSDATA (utf)); unbind_to (specbind_down, Qnil); unblock_input (); return (gchar *) khar; } static gunichar atk_get_character_at_offset (AtkText *text, gint offset) { EmacsGtkAccessible *accessible = (EmacsGtkAccessible *) text; Lisp_Object window = FRAME_SELECTED_WINDOW (accessible->frame); Lisp_Object buffer = WINDOW_BUFFER (XWINDOW (window)); if (NILP (buffer) || !BUFFERP (buffer)) return 0; ptrdiff_t specbind_down = SPECPDL_INDEX (); record_unwind_current_buffer (); Fset_buffer (buffer); Lisp_Object khar = Fchar_after (make_fixnum (offset)); gunichar zchar = (gunichar) XFIXNUM (khar); unbind_to (specbind_down, Qnil); return zchar; } static gint atk_get_caret_offset (AtkText *text) { EmacsGtkAccessible *accessible = (EmacsGtkAccessible *) text; Lisp_Object window = FRAME_SELECTED_WINDOW (accessible->frame); Lisp_Object buffer = WINDOW_BUFFER (XWINDOW (window)); if (NILP (buffer) || !BUFFERP (buffer)) return 0; return XBUFFER (buffer)->pt; } static AtkAttributeSet * atk_get_run_attributes (AtkText *text, gint offset, gint *start_offset, gint *end_offset) { ptrdiff_t pdl = SPECPDL_INDEX (); record_unwind_protect_excursion (); EmacsGtkAccessible *accessible = (EmacsGtkAccessible *) text; Lisp_Object window = FRAME_SELECTED_WINDOW (accessible->frame); Lisp_Object buffer = WINDOW_BUFFER (XWINDOW (window)); set_buffer_internal (XBUFFER (buffer)); Fset_window_point (window, make_fixnum (offset)); Fforward_word (window); int point = XFIXNUM (Fpoint ()); unbind_to (pdl, Qnil); *start_offset = offset; *end_offset = point; return NULL; } static AtkAttributeSet * atk_get_default_attributes (AtkText *text) { TODO; return NULL; } static void zz_size_allocate (GtkWidget *widget, int width, int height, int baseline) { EmacsResizableDrawingArea *area = (gpointer) widget; if (area->nrc_popover && gtk_widget_is_visible (GTK_WIDGET (area->nrc_popover))) gtk_native_check_resize (GTK_NATIVE (area->nrc_popover)); for (GtkWidget *c = gtk_widget_get_first_child (GTK_WIDGET (area)); c; c = gtk_widget_get_next_sibling (c)) { if (GTK_IS_NATIVE (c) && gtk_widget_is_visible (c)) gtk_native_check_resize (GTK_NATIVE (c)); } } static void atk_get_character_extents (AtkText *text, gint offset, gint *x, gint *y, gint *width, gint *height, AtkCoordType coords) { block_input (); EmacsGtkAccessible *accessible = (EmacsGtkAccessible *) text; Lisp_Object window = FRAME_SELECTED_WINDOW (accessible->frame); Lisp_Object buffer = WINDOW_BUFFER (XWINDOW (window)); if (NILP (buffer) || !BUFFERP (buffer)) return; ptrdiff_t binding = SPECPDL_INDEX (); record_unwind_current_buffer (); Fset_buffer (buffer); Lisp_Object bcount = call2 (intern_c_string ("window-absolute-pixel-position"), make_fixnum (offset), Qnil); if (NILP (bcount)) return; *x = XFIXNUM (XCAR (bcount)); *y = XFIXNUM (XCDR (bcount)); *height = FRAME_LINE_HEIGHT (accessible->frame); *width = FRAME_COLUMN_WIDTH (accessible->frame); unbind_to (binding, Qnil); unblock_input (); } static gint atk_get_character_count (AtkText *text) { EmacsGtkAccessible *accessible = (EmacsGtkAccessible *) text; Lisp_Object window = FRAME_SELECTED_WINDOW (accessible->frame); Lisp_Object buffer = WINDOW_BUFFER (XWINDOW (window)); if (NILP (buffer) || !BUFFERP (buffer)) return 0; return XFIXNUM (Fbuffer_size (buffer)); } static void emacs_resizable_drawing_area_atk_text_init (AtkTextIface *iface) { iface->get_text = atk_get_text; iface->get_character_at_offset = atk_get_character_at_offset; iface->get_caret_offset = atk_get_caret_offset; iface->get_run_attributes = atk_get_run_attributes; iface->get_default_attributes = atk_get_default_attributes; iface->get_character_extents = atk_get_character_extents; iface->get_character_count = atk_get_character_count; } GtkWidget * emacs_resizable_drawing_area_new (struct frame *f) { EmacsResizableDrawingArea *area = (EmacsResizableDrawingArea *) g_object_new (emacs_resizable_drawing_area_get_type (), NULL); area->f = f; emacs_resizable_drawing_area_set_background (area, FRAME_BACKGROUND_COLOR (f)); return GTK_WIDGET (area); } static void update_theme_scrollbar_width (void) { GtkWidget *widget = gtk_scrollbar_new (GTK_ORIENTATION_VERTICAL, gtk_adjustment_new (0, 0, 0, 0, 0, 0)); int u, z; gtk_widget_measure (widget, GTK_ORIENTATION_HORIZONTAL, true, &u, &z, &u, &u); scroll_bar_width_for_theme = z; gtk_widget_destroy (widget); } static void update_theme_scrollbar_height (void) { GtkWidget *widget = gtk_scrollbar_new (GTK_ORIENTATION_HORIZONTAL, gtk_adjustment_new (0, 0, 0, 0, 0, 0)); int u, z; gtk_widget_measure (widget, GTK_ORIENTATION_VERTICAL, true, &u, &z, &u, &u); scroll_bar_height_for_theme = z; gtk_widget_destroy (widget); } static void emacs_resizable_drawing_area_snapshot (GtkWidget *widget, GtkSnapshot *snapshot) { block_input (); EmacsResizableDrawingArea *area = (gpointer) widget; gtk_snapshot_append_node ( snapshot, gsk_color_node_new (area->background_color, (&GRAPHENE_RECT_INIT (0, 0, gtk_widget_get_width (widget), gtk_widget_get_height (widget))))); cairo_t *cr = gtk_snapshot_append_cairo (snapshot, (&GRAPHENE_RECT_INIT (0, 0, gtk_widget_get_width (widget), gtk_widget_get_height (widget)))); pgtk_cr_draw_frame (cr, area->f); cairo_destroy (cr); if (FRAME_X_OUTPUT (area->f)->visible_bell_end_time) { GdkRGBA rgba; rgba.red = area->background_color->red; rgba.blue = area->background_color->blue; rgba.green = area->background_color->green; rgba.alpha = area->background_color->alpha / 2; gtk_snapshot_append_color (snapshot, &rgba, (&GRAPHENE_RECT_INIT (0, 0, gtk_widget_get_width (widget), gtk_widget_get_height (widget)))); } GtkWidgetClass *class = emacs_resizable_drawing_area_parent_class; class->snapshot (widget, snapshot); unblock_input (); } static void emacs_resizable_drawing_area_measure (GtkWidget *widget, GtkOrientation orientation, int for_size, int *minimum, int *natural, int *minimum_baseline, int *natural_baseline) { GtkWidgetClass *parent_class = g_type_class_peek_parent ( G_TYPE_INSTANCE_GET_CLASS (widget, emacs_resizable_drawing_area_get_type (), EmacsResizableDrawingAreaClass)); parent_class->measure (widget, orientation, for_size, minimum, natural, minimum_baseline, natural_baseline); *natural = orientation == GTK_ORIENTATION_VERTICAL ? FRAME_PIXEL_HEIGHT (((EmacsResizableDrawingArea *) widget)->f) : FRAME_PIXEL_WIDTH (((EmacsResizableDrawingArea *) widget)->f); if (orientation == GTK_ORIENTATION_HORIZONTAL) if (*natural > gtk_widget_get_width (GTK_WIDGET (FRAME_GTK_OUTER_WIDGET (((EmacsResizableDrawingArea *) widget)->f)))) *natural = gtk_widget_get_width (GTK_WIDGET (FRAME_GTK_OUTER_WIDGET (((EmacsResizableDrawingArea *) widget)->f))); *minimum = 0; } static ptrdiff_t eg_store_widget_in_map (GtkWidget *w); static gboolean emacs_resizable_drawing_area_focus_self (GtkWidget *widget) { EmacsResizableDrawingArea *dr = (gpointer) widget; if (!FRAME_NO_ACCEPT_FOCUS (dr->f)) gtk_root_set_focus (gtk_widget_get_root (widget), widget); return TRUE; } void emacs_resizable_drawing_area_class_init (EmacsResizableDrawingAreaClass *claz) { GTK_WIDGET_CLASS (claz)->measure = emacs_resizable_drawing_area_measure; GTK_WIDGET_CLASS (claz)->get_accessible = atk_get_accessible; GTK_WIDGET_CLASS (claz)->snapshot = emacs_resizable_drawing_area_snapshot; GTK_WIDGET_CLASS (claz)->size_allocate = zz_size_allocate; gtk_widget_class_install_action (GTK_WIDGET_CLASS (claz), "clipboard.cut", NULL, emacs_resizable_drawing_area_action); gtk_widget_class_install_action (GTK_WIDGET_CLASS (claz), "clipboard.copy", NULL, emacs_resizable_drawing_area_action); gtk_widget_class_install_action (GTK_WIDGET_CLASS (claz), "clipboard.paste", NULL, emacs_resizable_drawing_area_action); gtk_widget_class_install_action (GTK_WIDGET_CLASS (claz), "selection.delete", NULL, emacs_resizable_drawing_area_action); gtk_widget_class_install_action (GTK_WIDGET_CLASS (claz), "selection.select-all", NULL, emacs_resizable_drawing_area_action); gtk_widget_class_install_action (GTK_WIDGET_CLASS (claz), "menu.select", (gpointer) G_VARIANT_TYPE_ANY, emacs_resizable_drawing_area_action); GTK_WIDGET_CLASS (claz)->grab_focus = emacs_resizable_drawing_area_focus_self; } void egtk_list_insert (egtk_list_node *list, egtk_list_node *node) { egtk_list_node *list_start = list->next; if (list_start) list_start->prev = node; node->next = list_start; node->prev = 0; list->next = node; } gboolean overlay_allocate_cb (GtkOverlay *overlay, GtkWidget *widget, GdkRectangle *allocation, gpointer user_data) { GdkRectangle *rect = g_object_get_data (G_OBJECT (widget), EG_OVERLAY_ALLOC); if (!rect) return false; allocation->x = rect->x; allocation->y = rect->y; int height, width, mw, mh, r, b; gtk_widget_measure (widget, GTK_ORIENTATION_VERTICAL, true, &mh, &height, &r, &b); gtk_widget_measure (widget, GTK_ORIENTATION_HORIZONTAL, true, &mw, &width, &r, &b); if (GTK_IS_SCROLLBAR (widget)) { bool vis = g_object_get_data (G_OBJECT (widget), "eg-old-vis") || gtk_widget_is_visible (widget); if (rect->height < mh || rect->width < mw) { gtk_widget_set_opacity (gtk_widget_get_first_child (widget), 0.0); g_object_set_data (G_OBJECT (widget), "eg-old-vis", (gpointer) vis); } else { gtk_widget_set_opacity (gtk_widget_get_first_child (widget), !vis ? 0.0 : 1.0); g_object_set_data (G_OBJECT (widget), "eg-old-vis", 0); } } allocation->height = rect->height != -1 ? rect->height : height; allocation->width = rect->width != -1 ? rect->width : width; return true; } bool egtk_create_frame_widgets (struct frame *f) { GtkWindow *wtop = GTK_WINDOW (emacs_window_new ()); GtkSettings *gs = gtk_settings_get_for_display (FRAME_X_DISPLAY (f)); g_signal_connect (G_OBJECT (gs), "notify::gtk-theme-name", G_CALLBACK (style_change_cb), f); g_signal_connect (G_OBJECT (gs), "notify::gtk-application-prefer-dark-theme", G_CALLBACK (style_change_cb), f); g_signal_connect (G_OBJECT (gs), "notify::gtk-font-name", G_CALLBACK (style_change_cb), f); gtk_window_set_display (wtop, FRAME_X_OUTPUT (f)->display_info->gdpy); gtk_window_set_decorated (wtop, true); gtk_widget_set_name (GTK_WIDGET (wtop), EMACS_CLASS); GtkHeaderBar *header_bar = GTK_HEADER_BAR (gtk_header_bar_new ()); gtk_header_bar_set_show_title_buttons (header_bar, true); gtk_window_set_titlebar (wtop, GTK_WIDGET (header_bar)); gtk_header_bar_set_subtitle (header_bar, SSDATA (f->name)); GtkWidget *drawing_area = GTK_WIDGET (emacs_resizable_drawing_area_new (f)); gtk_window_set_default_widget (wtop, drawing_area); gtk_window_set_focus (wtop, drawing_area); gtk_widget_set_name (drawing_area, SSDATA (Vx_resource_name)); GtkBox *box = GTK_BOX (gtk_box_new (GTK_ORIENTATION_VERTICAL, 0)); GtkBox *mbb = GTK_BOX (gtk_box_new (GTK_ORIENTATION_VERTICAL, 0)); GtkBox *tbb = GTK_BOX (gtk_box_new (GTK_ORIENTATION_VERTICAL, 0)); FRAME_BOX (f) = box; FRAME_MB_BOX (f) = mbb; FRAME_TB_BOX (f) = tbb; gtk_container_add (GTK_CONTAINER (box), GTK_WIDGET (mbb)); gtk_container_add (GTK_CONTAINER (box), GTK_WIDGET (tbb)); GtkOverlay *decor_overlay = GTK_OVERLAY (gtk_overlay_new ()); GtkOverlay *overlay = GTK_OVERLAY (gtk_overlay_new ()); gtk_container_add (GTK_CONTAINER (wtop), GTK_WIDGET (box)); gtk_container_add (GTK_CONTAINER (box), GTK_WIDGET (overlay)); gtk_container_add (GTK_CONTAINER (decor_overlay), GTK_WIDGET (drawing_area)); gtk_container_add (GTK_CONTAINER (overlay), GTK_WIDGET (decor_overlay)); g_signal_connect (G_OBJECT (overlay), "get-child-position", G_CALLBACK (overlay_allocate_cb), NULL); g_signal_connect (G_OBJECT (decor_overlay), "get-child-position", G_CALLBACK (overlay_allocate_cb), NULL); gtk_widget_set_size_request (GTK_WIDGET (drawing_area), 0, 0); FRAME_GTK_OUTER_WIDGET (f) = GTK_WIDGET (wtop); FRAME_GTK_WIDGET (f) = GTK_WIDGET (drawing_area); FRAME_GTK_HEADER_BAR (f) = header_bar; FRAME_TOOL_BAR (f) = NULL; FRAME_DECOR_OVERLAY (f) = decor_overlay; FRAME_OVERLAY (f) = overlay; xg_notify_header_bar_menu_state_changed (f); gtk_window_resize (GTK_WINDOW (wtop), f->pixel_width, f->pixel_height); gchar *title = NULL; 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 (wtop, title); if (FRAME_UNDECORATED (f)) { gtk_window_set_decorated (wtop, false); store_frame_param (f, Qundecorated, Qt); } gtk_widget_set_hexpand (GTK_WIDGET (drawing_area), true); gtk_widget_set_vexpand (GTK_WIDGET (drawing_area), true); gtk_window_set_resizable (GTK_WINDOW (wtop), true); gtk_widget_set_focus_on_click (GTK_WIDGET (drawing_area), true); GListModel *conts = gtk_widget_observe_controllers (GTK_WIDGET (wtop)); GtkEventController *mba = NULL; block_input (); for (int i = 0; i < g_list_model_get_n_items (conts); ++i) if (gtk_event_controller_get_name (g_list_model_get_item (conts, i)) && !strcmp (gtk_event_controller_get_name (g_list_model_get_item (conts, i)), "gtk-window-menubar-accel")) mba = g_list_model_get_item (conts, i); unblock_input (); if (mba) gtk_widget_remove_controller (GTK_WIDGET (wtop), mba); gtk_widget_grab_focus (GTK_WIDGET (drawing_area)); return true; } static const char * get_dialog_title (char key) { const char *title = ""; switch (key) { case 'E': case 'e': title = "Error"; break; case 'I': case 'i': title = "Information"; break; case 'L': case 'l': title = "Prompt"; break; case 'P': case 'p': title = "Prompt"; break; case 'Q': case 'q': title = "Question"; break; } return title; } GtkDialog * egtk_build_dialog (struct frame *f, widget_value *wv, GCallback select_cb, GCallback deactivate_cb) { const char *title = get_dialog_title (wv->name[0]); GtkDialog *d = build_dialog_n_items (wv->contents->value, wv->contents->next, select_cb, deactivate_cb); gtk_window_set_title (GTK_WINDOW (d), title); gtk_widget_hide (GTK_WIDGET (d)); gtk_window_set_transient_for (GTK_WINDOW (d), locate_nonchild_window (f)); return d; } double egtk_get_monitor_hdpi (GdkMonitor *monitor) { int pixheight; GdkRectangle rect; gdk_monitor_get_geometry (monitor, &rect); pixheight = rect.height * gdk_monitor_get_scale_factor (monitor); int realheight = gdk_monitor_get_height_mm (monitor); double pixels_per_mm = ((double) pixheight) / ((double) realheight); return (pixels_per_mm * 25.4) * 0.8; } double egtk_get_monitor_wdpi (GdkMonitor *monitor) { int pixwidth; GdkRectangle rect; gdk_monitor_get_geometry (monitor, &rect); pixwidth = rect.width * gdk_monitor_get_scale_factor (monitor); int realwidth = gdk_monitor_get_width_mm (monitor); double pixels_per_mm = ((double) pixwidth) / ((double) realwidth); return (pixels_per_mm * 25.4) * 0.8; } typedef char *(*xg_get_file_func) (GtkWidget *); static char * xg_get_file_name_from_chooser (GtkWidget *w) { return g_file_get_path (gtk_file_chooser_get_file (GTK_FILE_CHOOSER (w))); } struct xg_dialog_data { GMainLoop *loop; int response; GtkWidget *w; guint timerid; }; static void pop_down_dialog (void *arg) { struct xg_dialog_data *dd = arg; block_input (); if (dd->w) gtk_widget_destroy (dd->w); if (dd->timerid != 0) g_source_remove (dd->timerid); g_main_loop_quit (dd->loop); g_main_loop_unref (dd->loop); unblock_input (); } static gboolean xg_maybe_add_timer (gpointer data) { struct xg_dialog_data *dd = data; struct timespec next_time = timer_check (); dd->timerid = 0; if (timespec_valid_p (next_time)) { time_t s = next_time.tv_sec; int per_ms = TIMESPEC_HZ / 1000; int ms = (next_time.tv_nsec + per_ms - 1) / per_ms; if (s <= ((guint) -1 - ms) / 1000) dd->timerid = g_timeout_add (s * 1000 + ms, xg_maybe_add_timer, dd); } return FALSE; } /* Function that is called when the file or font dialogs pop down. W is the dialog widget, RESPONSE is the response code. USER_DATA is what we passed in to g_signal_connect. */ static void eg_dialog_response_cb (GtkDialog *w, gint response, gpointer user_data) { struct xg_dialog_data *dd = user_data; dd->response = response; g_main_loop_quit (dd->loop); } static int xg_dialog_run (struct frame *f, GtkWidget *w) { ptrdiff_t count = SPECPDL_INDEX (); struct xg_dialog_data dd; gtk_window_set_transient_for (GTK_WINDOW (w), locate_nonchild_window (f)); gtk_window_set_destroy_with_parent (GTK_WINDOW (w), TRUE); gtk_window_set_modal (GTK_WINDOW (w), TRUE); dd.loop = g_main_loop_new (NULL, FALSE); dd.response = GTK_RESPONSE_CANCEL; dd.w = w; dd.timerid = 0; g_signal_connect (G_OBJECT (w), "response", G_CALLBACK (eg_dialog_response_cb), &dd); gtk_widget_show (w); record_unwind_protect_ptr (pop_down_dialog, &dd); (void) xg_maybe_add_timer (&dd); g_main_loop_run (dd.loop); dd.w = 0; unbind_to (count, Qnil); return dd.response; } static GtkWidget * xg_get_file_with_chooser (struct frame *f, char *prompt, char *default_filename, bool mustmatch_p, bool only_dir_p, xg_get_file_func *func) { GtkWidget *filewin; GtkWindow *gwin = GTK_WINDOW (FRAME_GTK_OUTER_WIDGET (f)); GtkFileChooserAction action = (mustmatch_p ? GTK_FILE_CHOOSER_ACTION_OPEN : GTK_FILE_CHOOSER_ACTION_SAVE); if (only_dir_p) action = GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER; filewin = gtk_file_chooser_dialog_new (prompt, gwin, action, EGTK_TEXT_CANCEL, GTK_RESPONSE_CANCEL, (mustmatch_p || only_dir_p ? EGTK_TEXT_OPEN : EGTK_TEXT_OK), GTK_RESPONSE_OK, NULL); { Lisp_Object file; char *utf8_filename; 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), g_file_new_for_path (utf8_filename), NULL); } *func = xg_get_file_name_from_chooser; return filewin; } char * egtk_get_file_name (struct frame *f, char *prompt, char *default_filename, bool mustmatch_p, bool only_dir_p) { GtkWidget *w = 0; char *fn = 0; int filesel_done = 0; xg_get_file_func func; w = xg_get_file_with_chooser (f, prompt, default_filename, mustmatch_p, only_dir_p, &func); filesel_done = xg_dialog_run (f, w); if (filesel_done == GTK_RESPONSE_OK) fn = (*func) (w); return fn; } static gboolean eg_font_filter (const PangoFontFamily *family, const PangoFontFace *face, gpointer data) { const char *name = pango_font_family_get_name ((PangoFontFamily *) family); ptrdiff_t namelen = strlen (name); if (font_is_ignored (name, namelen)) return FALSE; return TRUE; } Lisp_Object egtk_get_font (struct frame *f, const char *default_name) { GtkWidget *w; int done = 0; Lisp_Object font = Qnil; w = gtk_font_chooser_dialog_new ("Pick a font", GTK_WINDOW (FRAME_GTK_OUTER_WIDGET (f))); gtk_font_chooser_set_filter_func (GTK_FONT_CHOOSER (w), eg_font_filter, NULL, NULL); if (default_name) { /* Convert fontconfig names to Gtk names, i.e. remove - before number */ char *p = strrchr (default_name, '-'); if (p) { char *ep = p + 1; while (c_isdigit (*ep)) ++ep; if (*ep == '\0') *p = ' '; } } else if (x_last_font_name) default_name = x_last_font_name; if (default_name) { PangoFontDescription *desc = pango_font_description_from_string (default_name); gtk_font_chooser_set_font_desc (GTK_FONT_CHOOSER (w), desc); pango_font_description_free (desc); } gtk_widget_set_name (w, "emacs-fontdialog"); done = xg_dialog_run (f, w); if (done == GTK_RESPONSE_OK) { PangoFontDescription *desc = gtk_font_chooser_get_font_desc (GTK_FONT_CHOOSER (w)); if (desc) { const char *family = pango_font_description_get_family (desc); gint size = pango_font_description_get_size (desc); PangoWeight weight = pango_font_description_get_weight (desc); PangoStyle style = pango_font_description_get_style (desc); font = CALLN (Ffont_spec, QCfamily, build_string (family), QCsize, make_float (pango_units_to_double (size)), QCweight, EG_WEIGHT_TO_SYMBOL (weight), QCslant, EG_STYLE_TO_SYMBOL (style)); char *font_desc_str = pango_font_description_to_string (desc); dupstring (&x_last_font_name, font_desc_str); g_free (font_desc_str); pango_font_description_free (desc); } } gtk_widget_destroy (w); return font; } bool egtk_ignore_gtk_scrollbar; bool egtk_gtk_initialized; static void eg_finish_scroll_bar_creation (struct frame *f, GtkWidget *wscroll, struct scroll_bar *bar, GCallback scroll_callback, GCallback end_callback, const char *scroll_bar_name) { gtk_widget_set_name (wscroll, scroll_bar_name); g_object_set_data (G_OBJECT (wscroll), XG_FRAME_DATA, (gpointer) f); ptrdiff_t scroll_id = eg_store_widget_in_map (wscroll); GtkRange *gr = GTK_RANGE (gtk_widget_get_first_child (wscroll)); if (!gr) emacs_abort (); g_signal_connect (G_OBJECT (gr), "change-value", scroll_callback, (gpointer) bar); GtkEventController *c = GTK_EVENT_CONTROLLER (gtk_gesture_click_new ()); gtk_widget_add_controller (GTK_WIDGET (gr), c); g_signal_connect (G_OBJECT (c), "released", end_callback, (gpointer) bar); gtk_overlay_add_overlay (FRAME_DECOR_OVERLAY (f), wscroll); GdkRectangle *rect = g_malloc (sizeof (GdkRectangle)); rect->x = 0; rect->y = 0; rect->height = 0; rect->width = 0; g_object_set_data_full (G_OBJECT (wscroll), EG_OVERLAY_ALLOC, rect, g_free); gtk_widget_realize (wscroll); bar->x_window = scroll_id; } void egtk_create_scroll_bar (struct frame *f, struct scroll_bar *bar, GCallback scroll_callback, GCallback end_callback, const char *scroll_bar_name) { GtkWidget *wscroll; GtkAdjustment *widget_adjustment = gtk_adjustment_new (XG_SB_MIN, XG_SB_MIN, XG_SB_MAX, 1.0, 1.0, 1.0); wscroll = gtk_scrollbar_new (GTK_ORIENTATION_VERTICAL, GTK_ADJUSTMENT (widget_adjustment)); bar->horizontal = false; eg_finish_scroll_bar_creation (f, wscroll, bar, scroll_callback, end_callback, scroll_bar_name); } void egtk_create_horizontal_scroll_bar (struct frame *f, struct scroll_bar *bar, GCallback scroll_callback, GCallback end_callback, const char *scroll_bar_name) { GtkWidget *wscroll; GtkAdjustment *widget_adjustment = gtk_adjustment_new (XG_SB_MIN, XG_SB_MIN, XG_SB_MAX, 1.0, 1.0, 1.0); wscroll = gtk_scrollbar_new (GTK_ORIENTATION_HORIZONTAL, GTK_ADJUSTMENT (widget_adjustment)); bar->horizontal = true; eg_finish_scroll_bar_creation (f, wscroll, bar, scroll_callback, end_callback, scroll_bar_name); } void egtk_set_toolkit_horizontal_scroll_bar_thumb (struct scroll_bar *bar, int portion, int position, int whole) { GtkWidget *wscroll = eg_get_widget_from_map (bar->x_window); if (wscroll && bar->dragging == -1) { GtkAdjustment *adj; int lower = 0; int upper = max (whole - 1, 0); int pagesize = min (upper, max (portion, 0)); int value = max (0, min (position, upper - pagesize)); int page_increment = 4; int step_increment = 1; block_input (); adj = gtk_scrollbar_get_adjustment (GTK_SCROLLBAR (wscroll)); gtk_adjustment_configure (adj, (gdouble) value, (gdouble) lower, (gdouble) upper, (gdouble) step_increment, (gdouble) page_increment, (gdouble) pagesize); unblock_input (); } } int egtk_get_default_scrollbar_width (struct frame *f) { return scroll_bar_width_for_theme; } int egtk_get_default_scrollbar_height (struct frame *f) { return scroll_bar_height_for_theme; } static void g_func_ref (gpointer o, gpointer ignored) { g_object_ref (o); } static void g_func_force_floating (gpointer o, gpointer ignored) { g_object_force_floating (o); } static void g_func_widget_unparent (gpointer o, gpointer ignored) { gtk_widget_unparent (o); } static void eg_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 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; } 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; } void update_frame_tool_bar (struct frame *f) { update_frame_header_bar (f); if (!FRAME_EXTERNAL_TOOL_BAR (f)) { if (FRAME_TB_BOX (f) && gtk_widget_get_first_child (GTK_WIDGET (FRAME_TB_BOX (f)))) gtk_widget_unparent (gtk_widget_get_first_child (GTK_WIDGET (FRAME_TB_BOX (f)))); return; } if (!FRAME_TOOL_BAR (f) && FRAME_TB_BOX (f)) { FRAME_TOOL_BAR (f) = xeg_box_new (); gtk_container_add (GTK_CONTAINER (FRAME_TB_BOX (f)), FRAME_TOOL_BAR (f)); } else if (!FRAME_TB_BOX (f)) return; block_input (); #define TB FRAME_TOOL_BAR (f) GSList *reusable_buttons = NULL; GSList *reusable_separators = NULL; for (GtkWidget *widget = gtk_widget_get_first_child (TB); widget; widget = gtk_widget_get_next_sibling (widget)) if (GTK_IS_BUTTON (widget)) reusable_buttons = g_slist_append (reusable_buttons, widget); else if (GTK_IS_SEPARATOR (widget)) reusable_separators = g_slist_append (reusable_separators, widget); else emacs_abort (); g_slist_foreach (reusable_buttons, (GFunc) g_func_ref, NULL); g_slist_foreach (reusable_separators, (GFunc) g_func_ref, NULL); g_slist_foreach (reusable_buttons, (GFunc) g_func_widget_unparent, NULL); g_slist_foreach (reusable_separators, (GFunc) g_func_widget_unparent, NULL); g_slist_foreach (reusable_buttons, (GFunc) g_func_force_floating, NULL); g_slist_foreach (reusable_separators, (GFunc) g_func_force_floating, NULL); int toolbar_items = f->n_tool_bar_items; int sepc = 0; int entc = 0; GSList *items = NULL; #define PROP(IDX) AREF (f->tool_bar_items, i *TOOL_BAR_ITEM_NSLOTS + (IDX)) for (int i = 0; i < toolbar_items; ++i) { if (!Fequal (PROP (TOOL_BAR_ITEM_TYPE), Qt)) { ++entc; 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); if (! NILP (PROP (TOOL_BAR_ITEM_VERT_ONLY))) tbe->title = Qnil; 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); items = g_slist_append (items, tbe); } else { ++sepc; items = g_slist_append (items, NULL); } } while (g_slist_length (reusable_buttons) > entc) { gtk_widget_destroy (g_slist_last (reusable_buttons)->data); reusable_buttons = g_slist_remove (reusable_buttons, g_slist_last (reusable_buttons)->data); } while (g_slist_length (reusable_separators) > sepc) { gtk_widget_destroy (g_slist_last (reusable_separators)->data); reusable_separators = g_slist_remove (reusable_separators, g_slist_last (reusable_separators)->data); } int sepi = 0; int btni = 0; #define NEWSEP \ (g_slist_nth (reusable_separators, sepi++) \ ? GTK_SEPARATOR (g_slist_nth_data (reusable_separators, sepi - 1)) \ : GTK_SEPARATOR (gtk_separator_new (GTK_ORIENTATION_VERTICAL))) #define NEWBTN \ (g_slist_nth (reusable_buttons, btni++) \ ? GTK_BUTTON (g_slist_nth_data (reusable_buttons, btni - 1)) \ : GTK_BUTTON (gtk_button_new ())) for (GSList *i = items; i; i = i->next) { struct xg_tool_bar_entry *ent = i->data; if (!ent) gtk_container_add (GTK_CONTAINER (TB), GTK_WIDGET (NEWSEP)); else { GtkButton *btn = NEWBTN; gulong ptr; if ((ptr = (gulong) g_object_get_data (G_OBJECT (btn), "click-handler"))) g_signal_handler_disconnect (G_OBJECT (btn), ptr); if (gtk_bin_get_child (GTK_BIN (btn))) gtk_container_remove (GTK_CONTAINER (btn), GTK_WIDGET (gtk_bin_get_child (GTK_BIN (btn)))); GtkLabel *label = GTK_LABEL (gtk_label_new (NULL)); gtk_widget_add_css_class (GTK_WIDGET (btn), "flat"); gtk_widget_set_margin_end (GTK_WIDGET (btn), 2); gtk_widget_set_margin_start (GTK_WIDGET (btn), 2); if (!NILP (ent->title)) gtk_label_set_text (label, SSDATA (ENCODE_UTF_8 (ent->title))); else gtk_label_set_text (label, NULL); if (NILP (ent->title)) gtk_widget_hide (GTK_WIDGET (label)); gtk_widget_set_sensitive (GTK_WIDGET (btn), ent->enabled); GtkImage *gimage = NULL; if (!NILP (ent->help)) gtk_widget_set_tooltip_text (GTK_WIDGET (btn), SSDATA (ENCODE_UTF_8 (ent->help))); else gtk_widget_set_tooltip_text (GTK_WIDGET (btn), NULL); { Lisp_Object image = ent->icon; Lisp_Object stock = Qnil; char *icon_name = NULL; char *stock_name = NULL; int idx; ptrdiff_t img_id; struct image *img = NULL; Lisp_Object specfile = file_for_image (image); if (!CONSP (image) && !valid_image_p (image)) continue; if (!NILP (specfile)) stock = call1 (Qx_gtk_map_stock, specfile); if (CONSP (stock)) { Lisp_Object itr; for (itr = stock; CONSP (itr); itr = XCDR (itr)) { stock_name = find_icon_from_name (SSDATA (XCAR (itr)), gtk_icon_theme_get_for_display (FRAME_X_DISPLAY (f)), &icon_name); if (icon_name || stock_name) break; } } else if (STRINGP (stock)) { stock_name = find_icon_from_name (SSDATA (stock), gtk_icon_theme_get_for_display (FRAME_X_DISPLAY (f)), &icon_name); } if (stock_name == NULL && icon_name == NULL) { if (VECTORP (image)) { if (ent->enabled) idx = (ent->selected ? TOOL_BAR_IMAGE_ENABLED_SELECTED : TOOL_BAR_IMAGE_ENABLED_DESELECTED); else idx = (ent->selected ? 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); } if (stock_name || icon_name) gimage = GTK_IMAGE (gtk_image_new_from_icon_name (icon_name ? icon_name : stock_name)); else if (img && !img->load_failed_p) gimage = GTK_IMAGE (egtk_get_image_for_pixmap (f, img, TB, NULL)); if (stock_name) xfree (stock_name); if (icon_name) xfree (icon_name); } GtkWidget *box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 4); if (gimage) gtk_widget_set_parent (GTK_WIDGET (gimage), box); gtk_widget_set_parent (GTK_WIDGET (label), box); gtk_container_add (GTK_CONTAINER (btn), box); g_object_set_data (G_OBJECT (btn), XG_FRAME_DATA, f); gulong l = g_signal_connect (G_OBJECT (btn), "clicked", G_CALLBACK (eg_tool_bar_callback), (gpointer) (intptr_t) ent->idx); g_object_set_data (G_OBJECT (btn), "click-handler", (gpointer) l); gtk_container_add (GTK_CONTAINER (TB), GTK_WIDGET (btn)); } if (ent) xfree (ent); } g_slist_free (items); g_slist_free (reusable_buttons); g_slist_free (reusable_separators); unblock_input (); #undef NEWSEP #undef NEWBTN #undef PROP #undef TB } void free_frame_tool_bar (struct frame *f) { if (FRAME_TB_BOX (f) && FRAME_TOOL_BAR (f)) gtk_container_remove (GTK_CONTAINER (FRAME_TB_BOX (f)), FRAME_TOOL_BAR (f)); FRAME_TOOL_BAR (f) = NULL; FRAME_EXTERNAL_TOOL_BAR (f) = NULL; } void egtk_change_toolbar_position (struct frame *f, Lisp_Object pos) { if (!EQ (pos, Qtop)) error ("Toolbar position cannot be changed on GTK 4"); } void egtk_set_undecorated (struct frame *f, Lisp_Object undecorated) { if (!FRAME_PARENT_FRAME (f)) gtk_window_set_decorated (GTK_WINDOW (FRAME_GTK_OUTER_WIDGET (f)), NILP (undecorated)); } void egtk_frame_resized (struct frame *f, int pixelwidth, int pixelheight) { int width, height; if (pixelwidth == -1 && pixelheight == -1) { if (FRAME_GTK_WIDGET (f) && gtk_widget_get_mapped (FRAME_GTK_WIDGET (f))) { if (!FRAME_PARENT_FRAME (f)) { pixelwidth = gdk_surface_get_width ( EGTK_WIDGET_GET_WINDOW (FRAME_GTK_WIDGET (f))); pixelheight = gdk_surface_get_height ( EGTK_WIDGET_GET_WINDOW (FRAME_GTK_WIDGET (f))); } else { pixelwidth = gtk_widget_get_width (FRAME_GTK_WIDGET (f)); pixelheight = gtk_widget_get_height (FRAME_GTK_WIDGET (f)); } } else return; } block_input (); width = FRAME_PIXEL_TO_TEXT_WIDTH (f, pixelwidth); height = FRAME_PIXEL_TO_TEXT_HEIGHT (f, pixelheight); frame_size_history_add (f, Qxg_frame_resized, width, height, Qnil); FRAME_RIF (f)->clear_under_internal_border (f); change_frame_size (f, width, height, 0, 1, 0, 1); SET_FRAME_GARBAGED (f); cancel_mouse_face (f); FRAME_X_OUTPUT (f)->want_flip = false; unblock_input (); } void egtk_frame_set_char_size (struct frame *f, int width, int height) { int pixelwidth = FRAME_TEXT_TO_PIXEL_WIDTH (f, width); int pixelheight = FRAME_TEXT_TO_PIXEL_HEIGHT (f, height); Lisp_Object fullscreen = get_frame_param (f, Qfullscreen); gint gwidth, gheight; int totalheight = pixelheight + FRAME_TOOLBAR_HEIGHT (f) + FRAME_MENUBAR_HEIGHT (f); int totalwidth = pixelwidth + FRAME_TOOLBAR_WIDTH (f); if (FRAME_PIXEL_HEIGHT (f) == 0) return; if (FRAME_PARENT_FRAME (f)) { GtkAllocation alloc; gtk_widget_get_allocation (FRAME_GTK_OUTER_WIDGET (f), &alloc); gwidth = alloc.width; gheight = alloc.height; } else gtk_window_get_size (GTK_WINDOW (FRAME_GTK_OUTER_WIDGET (f)), &gwidth, &gheight); /* Do this before resize, as we don't know yet if we will be resized. */ FRAME_RIF (f)->clear_under_internal_border (f); totalheight /= egtk_get_scale (f); totalwidth /= egtk_get_scale (f); x_wm_set_size_hint (f, 0, 0); /* Resize the top level widget so rows and columns remain constant. When the frame is fullheight and we only want to change the width or it is fullwidth and we only want to change the height we should be able to preserve the fullscreen property. However, due to the fact that we have to send a resize request anyway, the window manager will abolish it. At least the respective size should remain unchanged but giving the frame back its normal size will be broken ... */ if (EQ (fullscreen, Qfullwidth) && width == FRAME_TEXT_WIDTH (f)) { frame_size_history_add (f, Qxg_frame_set_char_size_1, width, height, list2i (gheight, totalheight)); if (!FRAME_PARENT_FRAME (f)) { gtk_window_resize (GTK_WINDOW (FRAME_GTK_OUTER_WIDGET (f)), gwidth, totalheight); } else { GdkRectangle *rect; if (FRAME_PARENT_FRAME (f) && (rect = g_object_get_data (G_OBJECT (FRAME_GTK_OUTER_WIDGET (f)), EG_OVERLAY_ALLOC))) { rect->height = totalheight; rect->width = gwidth; } } } else if (EQ (fullscreen, Qfullheight) && height == FRAME_TEXT_HEIGHT (f)) { frame_size_history_add (f, Qxg_frame_set_char_size_2, width, height, list2i (gwidth, totalwidth)); if (!FRAME_PARENT_FRAME (f)) { gtk_window_resize (GTK_WINDOW (FRAME_GTK_OUTER_WIDGET (f)), totalwidth, gheight); } else { GdkRectangle *rect; if (FRAME_PARENT_FRAME (f) && (rect = g_object_get_data (G_OBJECT (FRAME_GTK_OUTER_WIDGET (f)), EG_OVERLAY_ALLOC))) { rect->width = totalwidth; rect->height = gheight; } } } else { frame_size_history_add (f, Qxg_frame_set_char_size_3, width, height, list2i (totalwidth, totalheight)); if (!FRAME_PARENT_FRAME (f)) { gtk_window_resize (GTK_WINDOW (FRAME_GTK_OUTER_WIDGET (f)), totalwidth, totalheight); } else { GdkRectangle *rect; if (FRAME_PARENT_FRAME (f) && (rect = g_object_get_data (G_OBJECT (FRAME_GTK_OUTER_WIDGET (f)), EG_OVERLAY_ALLOC))) { rect->width = totalwidth; rect->height = totalheight; } } fullscreen = Qnil; } SET_FRAME_GARBAGED (f); cancel_mouse_face (f); /* We can not call change_frame_size for a mapped frame, we can not set pixel width/height either. The window manager may override our resize request, XMonad does this all the time. The best we can do is try to sync, so lisp code sees the updated size as fast as possible. For unmapped windows, we can set rows/cols. When the frame is mapped again we will (hopefully) get the correct size. */ if (FRAME_VISIBLE_P (f)) { if (!NILP (fullscreen)) /* Try to restore fullscreen state. */ { store_frame_param (f, Qfullscreen, fullscreen); gui_set_fullscreen (f, fullscreen, fullscreen); } } else adjust_frame_size (f, width, height, 5, 0, Qxg_frame_set_char_size); } void x_wm_set_size_hint (struct frame *f, long int flags, bool user_position) { } void egtk_free_frame_widgets (struct frame *f) { if (FRAME_GTK_OUTER_WIDGET (f)) { struct egtk_frame_tb_info *tbinfo = g_object_get_data (G_OBJECT (FRAME_GTK_OUTER_WIDGET (f)), TB_INFO_KEY); if (tbinfo) xfree (tbinfo); FRAME_GTK_OUTER_WIDGET (f) = 0; FRAME_GTK_WIDGET (f) = 0; gtk_widget_destroy (FRAME_GTK_OUTER_WIDGET (f)); FRAME_X_WINDOW (f) = 0; } } static int egtk_get_gdk_scale (void) { GSettings *set = g_settings_new ("org.gnome.desktop.interface"); gdouble x = g_settings_get_double (set, "text-scaling-factor"); g_object_unref (set); return (int) round (x); } int egtk_get_scale (struct frame *f) { if (FRAME_GTK_WIDGET (f)) return gtk_widget_get_scale_factor (FRAME_GTK_WIDGET (f)); return egtk_get_gdk_scale (); } void egtk_display_open (char *display_name, GdkDisplay **dpy) { GdkDisplay *display; unrequest_sigio (); display = gdk_display_open (strlen (display_name) ? display_name : NULL); request_sigio (); if (!default_display && display) { default_display = display; gdk_display_manager_set_default_display (gdk_display_manager_get (), default_display); } *dpy = display; } void egtk_display_close (GdkDisplay *display) { /* If this is the default display, try to change it before closing. If there is no other display to use, gdpy_def is set to NULL, and the next call to xg_display_open resets the default display. */ if (gdk_display_get_default () == display) { struct pgtk_display_info *display_info; GdkDisplay *display_new = NULL; /* Find another display. */ for (display_info = x_display_list; display_info; display_info = display_info->next) if (display_info->gdpy != display) { display_new = display_info->gdpy; gdk_display_manager_set_default_display (gdk_display_manager_get (), display_new); break; } default_display = display_new; } gdk_display_close (display); } GdkCursor * egtk_create_default_cursor (GdkDisplay *gdpy) { return gdk_cursor_new_from_name ("default", NULL); } static void eg_set_widget_bg (struct frame *f, GtkWidget *w, unsigned long pixel) { Emacs_Color xbg; xbg.pixel = pixel; xbg.red = (pixel >> 16) & 0xff; xbg.green = (pixel >> 8) & 0xff; xbg.blue = (pixel >> 0) & 0xff; xbg.red |= xbg.red << 8; xbg.green |= xbg.green << 8; xbg.blue |= xbg.blue << 8; { const char format[] = "* { background-color: #%02x%02x%02x; }"; /* The format is always longer than the resulting string. */ char buffer[sizeof format]; int n = snprintf (buffer, sizeof buffer, format, xbg.red >> 8, xbg.green >> 8, xbg.blue >> 8); eassert (n > 0); eassert (n < sizeof buffer); GtkCssProvider *provider = gtk_css_provider_new (); gtk_css_provider_load_from_data (provider, buffer, -1); gtk_style_context_add_provider (gtk_widget_get_style_context (w), GTK_STYLE_PROVIDER (provider), GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); #define G_STATIC_ASSERT(f) // FIXME kludge to fix GTK bugs g_clear_object (&provider); #undef G_STATIC_ASSERT } } static ptrdiff_t eg_store_widget_in_map (GtkWidget *w) { ptrdiff_t i; if (id_to_widget.max_size == id_to_widget.used) { ptrdiff_t new_size; if (ZTYPE_MAXIMUM (GWindow) - ID_TO_WIDGET_INCR < id_to_widget.max_size) memory_full (SIZE_MAX); new_size = id_to_widget.max_size + ID_TO_WIDGET_INCR; id_to_widget.widgets = xnrealloc (id_to_widget.widgets, new_size, sizeof (GtkWidget *)); for (i = id_to_widget.max_size; i < new_size; ++i) id_to_widget.widgets[i] = 0; id_to_widget.max_size = new_size; } /* Just loop over the array and find a free place. After all, how many scroll bars are we creating? Should be a small number. The check above guarantees we will find a free place. */ for (i = 0; i < id_to_widget.max_size; ++i) { if (!id_to_widget.widgets[i]) { id_to_widget.widgets[i] = w; ++id_to_widget.used; return i; } } /* Should never end up here */ emacs_abort (); } GtkWidget * eg_get_widget_from_map (ptrdiff_t idx) { if (id_to_widget.widgets && idx < id_to_widget.max_size && id_to_widget.widgets[idx] != 0) return id_to_widget.widgets[idx]; return 0; } void egtk_set_background_color (struct frame *frame, unsigned long bg) { if (FRAME_GTK_WIDGET (frame)) { block_input (); eg_set_widget_bg (frame, FRAME_GTK_WIDGET (frame), FRAME_BACKGROUND_PIXEL (frame)); unblock_input (); } } void egtk_mark_data (void) { egtk_list_node *iter; Lisp_Object rest, frame; for (iter = egtk_menu_cb_list.next; iter; iter = iter->next) mark_object (((egtk_menu_cb_data *) iter)->menu_bar_vector); for (iter = egtk_menu_item_cb_list.next; iter; iter = iter->next) { egtk_menu_item_cb_data *cb_data = (egtk_menu_item_cb_data *) iter; if (!NILP (cb_data->help)) mark_object (cb_data->help); } FOR_EACH_FRAME (rest, frame) { struct frame *f = XFRAME (frame); if ((FRAME_X_P (f) || FRAME_PGTK_P (f)) && FRAME_GTK_OUTER_WIDGET (f)) { struct egtk_frame_tb_info *tbinfo = g_object_get_data (G_OBJECT (FRAME_GTK_OUTER_WIDGET (f)), TB_INFO_KEY); if (tbinfo) { mark_object (tbinfo->last_tool_bar); mark_object (tbinfo->style); } } } } GtkWidget * egtk_get_image_for_pixmap (struct frame *f, struct image *img, GtkWidget *widget, GtkImage *old_widget) { cairo_surface_t *surface; /* 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 in all cases. */ Lisp_Object specified_file = file_for_image (img->spec); Lisp_Object file; /* 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 (STRINGP (specified_file) && STRINGP (file = image_find_image_file (specified_file))) { char *encoded_file = SSDATA (ENCODE_FILE (file)); if (!old_widget) old_widget = GTK_IMAGE (gtk_image_new_from_file (encoded_file)); else gtk_image_set_from_file (old_widget, encoded_file); 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. */ if (cairo_pattern_get_type (img->cr_data) == CAIRO_PATTERN_TYPE_SURFACE) cairo_pattern_get_surface (img->cr_data, &surface); else surface = NULL; if (surface) { if (!old_widget) old_widget = GTK_IMAGE (gtk_image_new_from_pixbuf ( gdk_pixbuf_get_from_surface (surface, 0, 0, cairo_image_surface_get_width (surface), cairo_image_surface_get_height ( surface)))); else gtk_image_set_from_pixbuf ( old_widget, (gdk_pixbuf_get_from_surface (surface, 0, 0, cairo_image_surface_get_width (surface), cairo_image_surface_get_height ( surface)))); } return GTK_WIDGET (old_widget); } static void eg_remove_widget_from_map (ptrdiff_t idx) { if (idx < id_to_widget.max_size && id_to_widget.widgets[idx] != 0) { id_to_widget.widgets[idx] = 0; --id_to_widget.used; } } Lisp_Object eg_get_font_family (bool mono) { GtkWidget *test_widget = gtk_text_view_new (); gtk_text_view_set_monospace (GTK_TEXT_VIEW (test_widget), mono); PangoContext *ctx = gtk_widget_create_pango_context (test_widget); PangoFontDescription *desc = pango_context_get_font_description (ctx); Lisp_Object name = build_string_from_utf8 (pango_font_description_get_family (desc)); g_object_unref (ctx); gtk_widget_destroy (test_widget); return name; } #pragma GCC diagnostic ignored "-Wdouble-promotion" bool egtk_check_special_colors (struct frame *f, const char *color_name, Emacs_Color *color) { if (!strcmp (GTK_FOREGROUND_COLOR, color_name)) { GtkWidget *tsc = gtk_text_view_new (); GtkStyleContext *gsc = gtk_widget_get_style_context (tsc); if (!gsc) return false; GdkRGBA col; gtk_style_context_get_color (gsc, &col); char buf[sizeof "rgb(255,255,255)"]; unsigned char r = round (col.red * 255.0), g = round (col.green * 255.0), b = round (col.blue * 255.0); #pragma GCC diagnostic ignored "-Wformat-overflow" sprintf (buf, "rgb(%03d,%03d,%03d)", r, g, b); #pragma GCC diagnostic push gtk_widget_destroy (tsc); return pgtk_parse_color (buf, color) != 0; } else if (!strcmp (GTK_BACKGROUND_COLOR, color_name)) { GtkWidget *tsc = gtk_text_view_new (); GtkStyleContext *gsc = gtk_widget_get_style_context (tsc); if (!gsc) return false; #if CAIRO_HAVE_RGBA128F cairo_surface_t *sf = cairo_image_surface_create (CAIRO_FORMAT_RGBA128F, 10, 10); #else cairo_surface_t *sf = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, 10, 10); #endif cairo_t *cr = cairo_create (sf); gtk_render_background (gsc, cr, 0, 0, 10, 10); cairo_surface_flush (sf); #if CAIRO_HAVE_RGBA128F float *rgb = (float *) cairo_image_surface_get_data (sf); #else guint8 *rgb = (guint8 *) cairo_image_surface_get_data (sf); #endif char buf[sizeof "rgb(255,255,255)"]; #if CAIRO_HAVE_RGBA128F guint8 r = 255 * rgb[0], g = 255 * rgb[1], b = 255 * rgb[2], a = 255 * rgb[3]; #else #if BYTE_ORDER != G_LITTLE_ENDIAN guint8 a = rgb[16 + 0], r = rgb[16 + 1], g = rgb[8 + 2], b = rgb[8 + 3]; #else guint8 a = rgb[16 + 3], r = rgb[16 + 2], g = rgb[16 + 1], b = rgb[16 + 0]; #endif #endif if (!a) { r = 255; g = 255; b = 255; } #pragma GCC diagnostic ignored "-Wformat-overflow" sprintf (buf, "rgb(%03d,%03d,%03d)", r, g, b); #pragma GCC diagnostic push cairo_destroy (cr); cairo_surface_destroy (sf); gtk_widget_destroy (tsc); return pgtk_parse_color (buf, color) != 0; } else if (!strcmp (GTK_MFOREGROUND_COLOR, color_name)) { GtkWidget *tsc = gtk_text_view_new (); GtkStyleContext *gsc = gtk_widget_get_style_context (tsc); if (!gsc) return false; GdkRGBA col; gtk_style_context_get_color (gsc, &col); char buf[sizeof "rgb(255,255,255)"]; unsigned char r = round (col.red * 255.0), g = round (col.green * 255.0), b = round (col.blue * 255.0); #pragma GCC diagnostic ignored "-Wformat-overflow" sprintf (buf, "rgb(%03d,%03d,%03d)", r, g, b); #pragma GCC diagnostic push gtk_widget_destroy (tsc); return pgtk_parse_color (buf, color) != 0; } else if (!strcmp (GTK_MBACKGROUND_COLOR, color_name)) { GtkWidget *tsc = gtk_header_bar_new (); GtkStyleContext *gsc = gtk_widget_get_style_context (tsc); if (!gsc) return false; #if CAIRO_HAVE_RGBA128F cairo_surface_t *sf = cairo_image_surface_create (CAIRO_FORMAT_RGBA128F, 10, 10); #else cairo_surface_t *sf = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, 10, 10); #endif cairo_t *cr = cairo_create (sf); gtk_render_background (gsc, cr, 0, 0, 10, 10); cairo_surface_flush (sf); #if CAIRO_HAVE_RGBA128F float *rgb = (float *) cairo_image_surface_get_data (sf); #else guint8 *rgb = (guint8 *) cairo_image_surface_get_data (sf); #endif char buf[sizeof "rgb(255,255,255)"]; #if CAIRO_HAVE_RGBA128F guint8 r = 255 * rgb[4 + 0], g = 255 * rgb[4 + 1], b = 255 * rgb[4 + 2], a = 255 * rgb[4 + 3]; #else #if BYTE_ORDER != G_LITTLE_ENDIAN guint8 a = rgb[16 + 0], r = rgb[16 + 1], g = rgb[8 + 2], b = rgb[8 + 3]; #else guint8 a = rgb[16 + 3], r = rgb[16 + 2], g = rgb[16 + 1], b = rgb[16 + 0]; #endif #endif if (!a) { r = 255; g = 255; b = 255; } #pragma GCC diagnostic ignored "-Wformat-overflow" sprintf (buf, "rgb(%03d,%03d,%03d)", r, g, b); #pragma GCC diagnostic push cairo_destroy (cr); cairo_surface_destroy (sf); gtk_widget_destroy (tsc); return pgtk_parse_color (buf, color) != 0; } else if (!strcmp (GTK_LFOREGROUND_COLOR, color_name)) { GtkWidget *tsc = gtk_link_button_new ("https://foo.bar"); GtkStyleContext *gsc = gtk_widget_get_style_context (tsc); gtk_style_context_set_state (gsc, gtk_style_context_get_state (gsc) | GTK_STATE_FLAG_LINK | GTK_STATE_FLAG_FOCUSED | GTK_STATE_FLAG_FOCUS_VISIBLE); if (!gsc) return false; GdkRGBA col; gtk_style_context_get_color (gsc, &col); char buf[sizeof "rgb(255,255,255)"]; unsigned char r = round (col.red * 255.0), g = round (col.green * 255.0), b = round (col.blue * 255.0); #pragma GCC diagnostic ignored "-Wformat-overflow" sprintf (buf, "rgb(%03d,%03d,%03d)", r, g, b); #pragma GCC diagnostic push gtk_widget_destroy (tsc); return pgtk_parse_color (buf, color) != 0; } else if (!strcmp (GTK_SLFOREGROUND_COLOR, color_name)) { GtkWidget *tsc = gtk_link_button_new ("https://foo.bar"); GtkStyleContext *gsc = gtk_widget_get_style_context (tsc); gtk_style_context_set_state (gsc, gtk_style_context_get_state (gsc) | GTK_STATE_FLAG_LINK | GTK_STATE_FLAG_FOCUSED | GTK_STATE_FLAG_VISITED | GTK_STATE_FLAG_FOCUS_VISIBLE); if (!gsc) return false; GdkRGBA col; gtk_style_context_get_color (gsc, &col); char buf[sizeof "rgb(255,255,255)"]; unsigned char r = round (col.red * 255.0), g = round (col.green * 255.0), b = round (col.blue * 255.0); #pragma GCC diagnostic ignored "-Wformat-overflow" sprintf (buf, "rgb(%03d,%03d,%03d)", r, g, b); #pragma GCC diagnostic push gtk_widget_destroy (tsc); return pgtk_parse_color (buf, color) != 0; } else if (!strcmp (GTK_TBACKGROUND_COLOR, color_name)) { GtkWidget *tsc = gtk_notebook_new (); gtk_widget_add_css_class (tsc, "tab"); GtkStyleContext *gsc = gtk_widget_get_style_context (tsc); gtk_style_context_set_state (gsc, GTK_STATE_FLAG_INSENSITIVE); #if CAIRO_HAVE_RGBA128F cairo_surface_t *sf = cairo_image_surface_create (CAIRO_FORMAT_RGBA128F, 10, 10); #else cairo_surface_t *sf = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, 10, 10); #endif cairo_t *cr = cairo_create (sf); gtk_render_background (gsc, cr, 0, 0, 10, 10); cairo_surface_flush (sf); #if CAIRO_HAVE_RGBA128F float *rgb = (float *) cairo_image_surface_get_data (sf); #else guint8 *rgb = (guint8 *) cairo_image_surface_get_data (sf); #endif char buf[sizeof "rgb(255,255,255)"]; #if CAIRO_HAVE_RGBA128F guint8 r = 255 * rgb[0], g = 255 * rgb[1], b = 255 * rgb[2], a = 255 * rgb[3]; #else #if BYTE_ORDER != G_LITTLE_ENDIAN guint8 a = rgb[4 + 0], r = rgb[4 + 1], g = rgb[4 + 2], b = rgb[4 + 3]; #else guint8 a = rgb[4 + 3], r = rgb[4 + 2], g = rgb[4 + 1], b = rgb[4 + 0]; #endif #endif if (!a) { r = 255; g = 255; b = 255; } #pragma GCC diagnostic ignored "-Wformat-overflow" sprintf (buf, "rgb(%03d,%03d,%03d)", r, g, b); #pragma GCC diagnostic push cairo_destroy (cr); cairo_surface_destroy (sf); gtk_widget_destroy (tsc); return pgtk_parse_color (buf, color) != 0; } bool success_p = 0; bool get_bg = strcmp ("gtk_selection_bg_color", color_name) == 0; bool get_fg = !get_bg && strcmp ("gtk_selection_fg_color", color_name) == 0; bool_bf qn = 0; if (!(get_bg || get_fg)) return success_p; block_input (); { GtkWidget *gcs = gtk_text_view_new (); GtkStyleContext *gsty = gtk_widget_get_style_context (gcs); GdkRGBA col; char buf[sizeof "rgb(255,255,255)"]; if (get_fg) { qn = 1; int os = gtk_style_context_get_state (gsty); gtk_style_context_set_state (gsty, os | GTK_STATE_FLAG_SELECTED | GTK_STATE_FLAG_FOCUSED); gtk_style_context_get_color (gsty, &col); gtk_style_context_set_state (gsty, os); } else { block_input (); int os = gtk_style_context_get_state (gsty); gtk_style_context_set_state (gsty, os | GTK_STATE_FLAG_SELECTED | GTK_STATE_FLAG_FOCUSED); cairo_surface_t *sf = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, 10, 10); cairo_t *cr = cairo_create (sf); gtk_render_background (gsty, cr, 0, 0, 10, 10); cairo_surface_flush (sf); guint8 *rgb = (guint8 *) cairo_image_surface_get_data (sf); #if BYTE_ORDER != G_LITTLE_ENDIAN guint8 a = rgb[16 + 0], r = rgb[16 + 1], g = rgb[16 + 2], b = rgb[16 + 3]; #else guint8 a = rgb[16 + 3], r = rgb[16 + 2], g = rgb[16 + 1], b = rgb[16 + 0]; #endif col.red = r; col.blue = b; col.green = g; col.alpha = a; gtk_style_context_set_state (gsty, os); cairo_destroy (cr); cairo_surface_destroy (sf); unblock_input (); } unsigned short r = round (!qn ? col.red : col.red * 255.0), g = round (!qn ? col.green : col.green * 255.0), b = round (!qn ? col.blue : col.blue * 255.0); #pragma GCC diagnostic ignored "-Wformat-overflow" sprintf (buf, "rgb(%03d,%03d,%03d)", r, g, b); #pragma GCC diagnostic push success_p = pgtk_parse_color (buf, color) != 0; gtk_widget_destroy (gcs); } unblock_input (); return success_p; } #pragma GCC diagnostic push void egtk_frame_restack (struct frame *f1, struct frame *f2, bool above) { block_input (); unblock_input (); } void egtk_set_skip_taskbar (struct frame *f, Lisp_Object skip_taskbar) { #ifdef HAVE_GDK_X11 if (FRAME_PARENT_FRAME (f)) return; if (GDK_IS_X11_DISPLAY (FRAME_X_DISPLAY (f))) gdk_x11_surface_set_skip_taskbar_hint (GDK_X11_SURFACE (EGTK_WIDGET_GET_WINDOW (FRAME_GTK_OUTER_WIDGET (f))), !NILP (skip_taskbar)); #endif } void egtk_set_no_focus_on_map (struct frame *f, Lisp_Object no_focus_on_map) { block_input (); TODO; unblock_input (); } void egtk_set_no_accept_focus (struct frame *f, Lisp_Object no_accept_focus) { block_input (); FRAME_NO_ACCEPT_FOCUS (f) = !NILP (no_accept_focus); unblock_input (); } static GtkPrintSettings *print_settings = NULL; static GtkPageSetup *page_setup = NULL; void egtk_page_setup_dialog (void) { GtkPageSetup *new_page_setup = NULL; if (print_settings == NULL) print_settings = gtk_print_settings_new (); new_page_setup = gtk_print_run_page_setup_dialog (NULL, page_setup, print_settings); if (page_setup) g_object_unref (page_setup); page_setup = new_page_setup; } void egtk_set_override_redirect (struct frame *f, Lisp_Object override_redirect) { } bool egtk_hide_tooltip (struct frame *f) { block_input (); if (FRAME_X_OUTPUT (f)->ttip_popover) { gtk_widget_destroy (GTK_WIDGET (FRAME_X_OUTPUT (f)->ttip_popover)); FRAME_X_OUTPUT (f)->ttip_popover = NULL; } unblock_input (); return true; } void egtk_remove_scroll_bar (struct frame *f, ptrdiff_t scrollbar_id) { GtkWidget *w = eg_get_widget_from_map (scrollbar_id); if (w) { gtk_widget_destroy (w); } eg_remove_widget_from_map (scrollbar_id); } void egtk_update_scrollbar_pos (struct frame *f, ptrdiff_t scrollbar_id, int top, int left, int width, int height) { GtkWidget *sb = eg_get_widget_from_map (scrollbar_id); GdkRectangle *rect = g_object_get_data (G_OBJECT (sb), EG_OVERLAY_ALLOC); rect->y = top; rect->x = left; rect->width = width; rect->height = height; gtk_widget_queue_resize (gtk_widget_get_parent (sb)); } void egtk_update_horizontal_scrollbar_pos (struct frame *f, ptrdiff_t scrollbar_id, int top, int left, int width, int height) { GtkWidget *sb = eg_get_widget_from_map (scrollbar_id); GdkRectangle *rect = g_object_get_data (G_OBJECT (sb), EG_OVERLAY_ALLOC); rect->y = top; rect->x = left; rect->width = width; rect->height = height; gtk_widget_queue_resize (gtk_widget_get_parent (sb)); } static int int_gtk_range_get_value (GtkRange *range) { return gtk_range_get_value (range); } void egtk_set_toolkit_scroll_bar_thumb (struct scroll_bar *bar, int portion, int position, int whole) { GtkWidget *wscroll = eg_get_widget_from_map (bar->x_window); wscroll = gtk_widget_get_first_child (wscroll); if (!wscroll) emacs_abort (); struct frame *f = XFRAME (WINDOW_FRAME (XWINDOW (bar->window))); if (wscroll && bar->dragging == -1) { GtkAdjustment *adj; gdouble shown; gdouble top; int size, value; int old_size; int new_step; bool changed = 0; adj = gtk_range_get_adjustment (GTK_RANGE (wscroll)); if (scroll_bar_adjust_thumb_portion_p) { /* We do the same as for MOTIF in xterm.c, use 30 chars per line rather than the real portion value. This makes the thumb less likely to resize and that looks better. */ portion = WINDOW_TOTAL_LINES (XWINDOW (bar->window)) * 30; /* When the thumb is at the bottom, position == whole. So we need to increase `whole' to make space for the thumb. */ whole += portion; } if (whole <= 0) top = 0, shown = 1; else { top = (gdouble) position / whole; shown = (gdouble) portion / whole; } size = clip_to_bounds (1, shown * XG_SB_RANGE, XG_SB_RANGE); value = clip_to_bounds (XG_SB_MIN, top * XG_SB_RANGE, XG_SB_MAX - size); /* Assume all lines are of equal size. */ new_step = size / max (1, FRAME_LINES (f)); old_size = gtk_adjustment_get_page_size (adj); if (old_size != size) { int old_step = gtk_adjustment_get_step_increment (adj); if (old_step != new_step) { gtk_adjustment_set_page_size (adj, size); gtk_adjustment_set_step_increment (adj, new_step); /* Assume a page increment is about 95% of the page size */ gtk_adjustment_set_page_increment (adj, size - size / 20); changed = 1; } } if (changed || int_gtk_range_get_value (GTK_RANGE (wscroll)) != value) { block_input (); /* gtk_range_set_value invokes the callback. Set ignore_gtk_scrollbar to make the callback do nothing */ egtk_ignore_gtk_scrollbar = 1; if (int_gtk_range_get_value (GTK_RANGE (wscroll)) != value) gtk_adjustment_set_value (adj, value); egtk_ignore_gtk_scrollbar = 0; unblock_input (); } } } void egtk_initialize (void) { default_display = NULL; egtk_ignore_gtk_scrollbar = 0; egtk_menu_cb_list.prev = egtk_menu_cb_list.next = egtk_menu_item_cb_list.prev = egtk_menu_item_cb_list.next = 0; id_to_widget.max_size = id_to_widget.used = 0; id_to_widget.widgets = 0; update_theme_scrollbar_width (); update_theme_scrollbar_height (); atab = g_hash_table_new (NULL, NULL); #ifdef HAVE_FREETYPE x_last_font_name = NULL; #endif } Lisp_Object egtk_get_page_setup (void) { Lisp_Object orientation_symbol; if (page_setup == NULL) page_setup = gtk_page_setup_new (); switch (gtk_page_setup_get_orientation (page_setup)) { case GTK_PAGE_ORIENTATION_PORTRAIT: orientation_symbol = Qportrait; break; case GTK_PAGE_ORIENTATION_LANDSCAPE: orientation_symbol = Qlandscape; break; case GTK_PAGE_ORIENTATION_REVERSE_PORTRAIT: orientation_symbol = Qreverse_portrait; break; case GTK_PAGE_ORIENTATION_REVERSE_LANDSCAPE: orientation_symbol = Qreverse_landscape; break; default: eassume (false); } #define GETSETUP(f) make_float (f (page_setup, GTK_UNIT_POINTS)) return list (Fcons (Qorientation, orientation_symbol), Fcons (Qwidth, GETSETUP (gtk_page_setup_get_page_width)), Fcons (Qheight, GETSETUP (gtk_page_setup_get_page_height)), Fcons (Qleft_margin, GETSETUP (gtk_page_setup_get_left_margin)), Fcons (Qright_margin, GETSETUP (gtk_page_setup_get_right_margin)), Fcons (Qtop_margin, GETSETUP (gtk_page_setup_get_top_margin)), Fcons (Qbottom_margin, GETSETUP (gtk_page_setup_get_bottom_margin))); #undef GETSETUP } static void draw_page (GtkPrintOperation *operation, GtkPrintContext *context, gint page_nr, gpointer user_data) { Lisp_Object frames = *((Lisp_Object *) user_data); struct frame *f = XFRAME (Fnth (make_fixnum (page_nr), frames)); cairo_t *cr = gtk_print_context_get_cairo_context (context); pgtk_cr_draw_frame (cr, f); cairo_save (cr); } static GtkWindow * locate_nonchild_window (struct frame *child) { while (FRAME_PARENT_FRAME (child)) child = FRAME_PARENT_FRAME (child); return GTK_WINDOW (FRAME_GTK_OUTER_WIDGET (child)); } void egtk_print_frames_dialog (Lisp_Object frames) { GtkPrintOperation *print; GtkPrintOperationResult res; print = gtk_print_operation_new (); if (print_settings != NULL) gtk_print_operation_set_print_settings (print, print_settings); if (page_setup != NULL) gtk_print_operation_set_default_page_setup (print, page_setup); gtk_print_operation_set_n_pages (print, list_length (frames)); g_signal_connect (print, "draw-page", G_CALLBACK (draw_page), &frames); res = gtk_print_operation_run (print, GTK_PRINT_OPERATION_ACTION_PRINT_DIALOG, locate_nonchild_window (XFRAME (XCAR (frames))), NULL); if (res == GTK_PRINT_OPERATION_RESULT_APPLY) { if (print_settings != NULL) g_object_unref (print_settings); print_settings = g_object_ref (gtk_print_operation_get_print_settings (print)); } g_object_unref (print); } bool egtk_prepare_tooltip (struct frame *f, Lisp_Object string, int *width, int *height) { FRAME_X_OUTPUT (f)->ttip_label = string; return TRUE; } static GtkWidget * create_popover (GtkWidget *parent, GtkWidget *child) { GtkWidget *popover; popover = xeg_ttip_popover_new (); gtk_widget_set_parent (popover, parent); gtk_container_add (GTK_CONTAINER (popover), child); gtk_widget_set_margin_start (child, 6); gtk_widget_set_margin_end (child, 6); gtk_widget_set_margin_top (child, 6); gtk_widget_set_margin_bottom (child, 6); gtk_widget_show (child); gtk_popover_set_autohide (GTK_POPOVER (popover), false); gtk_widget_set_can_focus (popover, false); return popover; } void egtk_show_tooltip (struct frame *f, int root_x, int root_y) { block_input (); GtkWidget *wg = GTK_WIDGET (gtk_label_new (SSDATA (ENCODE_UTF_8 (FRAME_X_OUTPUT (f)->ttip_label)))); gtk_widget_add_css_class (wg, "monospace"); #define PO FRAME_X_OUTPUT (f)->ttip_popover gtk_window_set_focus (locate_nonchild_window (f), FRAME_GTK_WIDGET (f)); PO = GTK_POPOVER (create_popover (FRAME_GTK_WIDGET (f), wg)); GdkRectangle rect; rect.x = root_x; rect.y = root_y; rect.width = 0; rect.height = 0; gtk_popover_set_pointing_to (PO, &rect); gtk_popover_set_has_arrow (PO, false); gtk_widget_show (GTK_WIDGET (PO)); gtk_window_set_focus (locate_nonchild_window (f), FRAME_GTK_WIDGET (f)); #undef PO unblock_input (); } static char * get_utf8_string (const char *str) { char *utf8_str; if (!str) return NULL; /* If not UTF-8, try current locale. */ if (!g_utf8_validate (str, -1, NULL)) utf8_str = g_locale_to_utf8 (str, -1, 0, 0, 0); else return g_strdup (str); if (!utf8_str) { /* Probably some control characters in str. Escape them. */ ptrdiff_t len; ptrdiff_t nr_bad = 0; size_t bytes_read; size_t bytes_written; unsigned char *p = (unsigned char *) str; char *cp, *up; GError *err = NULL; while (!(cp = g_locale_to_utf8 ((char *) p, -1, &bytes_read, &bytes_written, &err)) && err->code == G_CONVERT_ERROR_ILLEGAL_SEQUENCE) { ++nr_bad; p += bytes_written + 1; g_error_free (err); err = NULL; } if (err) { g_error_free (err); err = NULL; } if (cp) g_free (cp); len = strlen (str); ptrdiff_t alloc; if (INT_MULTIPLY_WRAPV (nr_bad, 4, &alloc) || INT_ADD_WRAPV (len + 1, alloc, &alloc) || SIZE_MAX < alloc) memory_full (SIZE_MAX); up = utf8_str = xmalloc (alloc); p = (unsigned char *) str; while (!(cp = g_locale_to_utf8 ((char *) p, -1, &bytes_read, &bytes_written, &err)) && err->code == G_CONVERT_ERROR_ILLEGAL_SEQUENCE) { memcpy (up, p, bytes_written); up += bytes_written; up += sprintf (up, "\\%03o", p[bytes_written]); p += bytes_written + 1; g_error_free (err); err = NULL; } if (cp) { strcpy (up, cp); g_free (cp); } if (err) { g_error_free (err); err = NULL; } } return utf8_str; } egtk_menu_cb_data * make_cl_data (egtk_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; egtk_list_insert (&egtk_menu_cb_list, &cl_data->ptrs); } cl_data->ref_count++; return cl_data; } static void egtk_list_remove (egtk_list_node *list, egtk_list_node *node) { egtk_list_node *list_start = list->next; if (node == list_start) { list->next = node->next; if (list->next) list->next->prev = 0; } else { node->prev->next = node->next; if (node->next) node->next->prev = node->prev; } } void unref_cl_data (egtk_menu_cb_data *cl_data) { if (cl_data && cl_data->ref_count > 0) { cl_data->ref_count--; if (cl_data->ref_count < 1) { egtk_list_remove (&egtk_menu_cb_list, &cl_data->ptrs); xfree (cl_data); } } } GtkPopover * build_mm_from_wv (widget_value *wv, struct frame *f, GCallback select_cb, GCallback deactivate_cb, GCallback highlight_cb, bool pop_up_p, bool menu_bar_p, egtk_menu_cb_data *cl_data, const char *name, GtkContainer *box, GtkPopover *popover, GdkRectangle *posrect) { return build_menu_from_widget_value (wv, cl_data, select_cb, deactivate_cb, highlight_cb, f, menu_bar_p, false); } int calculate_child_frame_distance_y (struct frame *f) { GtkWidget *ncw = GTK_WIDGET (locate_nonchild_window (f)); gint y; gtk_widget_translate_coordinates (FRAME_GTK_WIDGET (f), ncw, 0, 0, NULL, &y); return y; } int calculate_child_frame_distance_x (struct frame *f) { GtkWidget *ncw = GTK_WIDGET (locate_nonchild_window (f)); gint x; gtk_widget_translate_coordinates (FRAME_GTK_WIDGET (f), ncw, 0, 0, &x, NULL); return x; } void eg_notify_frame_header_bar_state_changed (struct frame *f) { if (FRAME_PGTK_P (f) && !FRAME_PARENT_FRAME (f)) { if (FRAME_DISPLAY_HEADER_BAR (f)) { gtk_window_set_titlebar (GTK_WINDOW (FRAME_GTK_OUTER_WIDGET (f)), GTK_WIDGET (FRAME_GTK_HEADER_BAR (f))); g_object_unref (FRAME_GTK_HEADER_BAR (f)); } else { g_object_ref (G_OBJECT (FRAME_GTK_HEADER_BAR (f))); gtk_window_set_titlebar (GTK_WINDOW (FRAME_GTK_OUTER_WIDGET (f)), NULL); } } } Lisp_Object eg_get_divider_width (void) { GtkSeparator *sep = GTK_SEPARATOR (gtk_separator_new (GTK_ORIENTATION_VERTICAL)); int foo, bar, quux, width; gtk_widget_measure (GTK_WIDGET (sep), GTK_ORIENTATION_HORIZONTAL, 0, &foo, &width, &bar, &quux); gtk_widget_destroy (GTK_WIDGET (sep)); return make_fixnum (width); } Lisp_Object eg_get_caret_px_width (void) { GtkLabel *tv = GTK_LABEL (gtk_label_new ("abcdefghijk")); gtk_label_select_region (tv, 3, 2); PangoRectangle sp, wp; PangoLayout *layout = gtk_widget_create_pango_layout (GTK_WIDGET (tv), "abcdefghijk"); pango_layout_get_cursor_pos (layout, 2, &sp, &wp); double wd = PANGO_PIXELS (sp.height) * 0.04 + 1; g_object_unref (layout); gtk_widget_destroy (GTK_WIDGET (tv)); return make_fixnum (wd); } void eg_unparent_frame (struct frame *f) { if (GTK_IS_WINDOW (FRAME_GTK_OUTER_WIDGET (f))) return; block_input (); GtkWidget *wzchild = gtk_widget_get_first_child (FRAME_GTK_OUTER_WIDGET (f)); g_object_ref (wzchild); gtk_container_remove (GTK_CONTAINER (FRAME_GTK_OUTER_WIDGET (f)), wzchild); gtk_widget_destroy (FRAME_GTK_OUTER_WIDGET (f)); GtkWindow *window = GTK_WINDOW (emacs_window_new ()); g_object_unref (FRAME_GTK_HEADER_BAR (f)); GtkHeaderBar *header_bar = GTK_HEADER_BAR (gtk_header_bar_new ()); gtk_container_add (GTK_CONTAINER (window), wzchild); gtk_window_resize (window, FRAME_PIXEL_WIDTH (f), FRAME_PIXEL_HEIGHT (f)); gtk_widget_set_visible (GTK_WIDGET (window), true); gtk_widget_map (GTK_WIDGET (window)); gtk_widget_realize (GTK_WIDGET (window)); FRAME_GTK_OUTER_WIDGET (f) = GTK_WIDGET (window); FRAME_GTK_HEADER_BAR (f) = header_bar; gtk_header_bar_set_show_title_buttons (header_bar, true); gtk_window_set_titlebar (window, GTK_WIDGET (header_bar)); eg_notify_frame_header_bar_state_changed (f); gtk_header_bar_set_subtitle (header_bar, SSDATA (f->name)); gtk_window_set_focus (window, FRAME_GTK_WIDGET (f)); GListModel *conts = gtk_widget_observe_controllers (GTK_WIDGET (window)); GtkEventController *mba = NULL; for (int i = 0; i < g_list_model_get_n_items (conts); ++i) if (gtk_event_controller_get_name (g_list_model_get_item (conts, i)) && !strcmp (gtk_event_controller_get_name (g_list_model_get_item (conts, i)), "gtk-window-menubar-accel")) mba = g_list_model_get_item (conts, i); if (mba) gtk_widget_remove_controller (GTK_WIDGET (window), mba); unblock_input (); } #endif #undef __GI_SCANNER__ #undef TODO