/* 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