From e882db2763d2996a0b517448251e75235a4ea8f5 Mon Sep 17 00:00:00 2001 From: Ting-Wei Lan Date: Sun, 22 Apr 2018 20:47:32 +0800 Subject: [PATCH 01/11] build: Make the default setting work on non-Linux Unix-like systems All of the four platform-dependent backends are enabled by default. It is usually a good default because it requires users to explicitly choose backends they want to use. Rules in meson.build also automatically disable unavailable backends for macOS, Windows, Linux, so users on these 3 major platforms don't have to manually disable things when running meson commands. However, meson.build doesn't do the same thing for other Unix-like systems, which is acceptable but not ideal. To make it easier to build GTK+ on these systems, the Linux case, which enables X11 and Wayland and disables Win32 and Quartz, is made the default for all operating systems that are not Windows or macOS. This commit also changes most 'host_machine.system()' calls to os_* variables, which are easier to read and less likely to be used wrongly. --- meson.build | 37 ++++++++++++++++++++++--------------- meson_options.txt | 4 ++-- tests/meson.build | 2 +- 3 files changed, 25 insertions(+), 18 deletions(-) diff --git a/meson.build b/meson.build index baab1b759d..cdd83e7635 100644 --- a/meson.build +++ b/meson.build @@ -102,20 +102,25 @@ os_darwin = false # to ensure they are enabled if host_machine.system() == 'darwin' os_darwin = true - win32_enabled = false elif host_machine.system() == 'windows' os_win32 = true - win32_enabled = true - x11_enabled = false - wayland_enabled = false - quartz_enabled = false elif host_machine.system() == 'linux' os_linux = true - win32_enabled = false +endif +os_unix = not os_win32 + +if os_darwin + wayland_enabled = false +else quartz_enabled = false endif -os_unix = not os_win32 +if os_win32 + wayland_enabled = false + x11_enabled = false +else + win32_enabled = false +endif gtk_prefix = get_option('prefix') gtk_includedir = join_paths(gtk_prefix, get_option('includedir')) @@ -265,7 +270,7 @@ common_cflags = cc.get_supported_arguments(test_cflags) # Symbol visibility if get_option('default_library') != 'static' - if host_machine.system() == 'windows' + if os_win32 cdata.set('DLL_EXPORT', true) cdata.set('_GDK_EXTERN', '__declspec(dllexport) extern') if cc.get_id() != 'msvc' @@ -284,7 +289,7 @@ if host_machine.system() == 'linux' and cc.get_id() == 'gcc' endif # Maintain compatibility with autotools -if host_machine.system() == 'darwin' +if os_darwin common_ldflags += [ '-compatibility_version 1', '-current_version 1.0', ] endif @@ -299,10 +304,11 @@ glib_dep = dependency('glib-2.0', version: glib_req, fallback : ['glib', 'libglib_dep']) gobject_dep = dependency('gobject-2.0', version: glib_req, fallback : ['glib', 'libgobject_dep']) -if host_machine.system() == 'windows' +if os_win32 giowin32_dep = dependency('gio-windows-2.0', version: glib_req, required: win32_enabled, fallback : ['glib', 'libgio_dep']) -else +endif +if os_unix giounix_dep = dependency('gio-unix-2.0', version: glib_req, required: false, fallback : ['glib', 'libgio_dep']) endif @@ -370,9 +376,10 @@ iso_codes_dep = dependency('iso-codes', required: false) fontconfig_dep = [] # only used in x11 backend atkbridge_dep = [] # only used in x11 backend -if host_machine.system() == 'windows' +if os_win32 platform_gio_dep = giowin32_dep -else +endif +if os_unix platform_gio_dep = giounix_dep endif @@ -582,7 +589,7 @@ if cc.has_function('bind_textdomain_codeset', dependencies: libintl_dep) cdata.set('HAVE_BIND_TEXTDOMAIN_CODESET', 1) endif -if host_machine.system() != 'windows' +if os_unix cdata.set('HAVE_GIO_UNIX', giounix_dep.found()) endif @@ -723,7 +730,7 @@ foreach pkg: pkgs install_dir: pkg_install_dir) endforeach -if host_machine.system() != 'windows' +if os_unix configure_file(input: 'gtk+-unix-print-4.0.pc.in', output: 'gtk+-unix-print-4.0.pc', configuration: pkgconf, diff --git a/meson_options.txt b/meson_options.txt index 6e795b0883..a189ee2cba 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -1,8 +1,8 @@ # GDK backends option('x11-backend', type: 'boolean', value: true, - description : 'Enable the X11 gdk backend (only when building on Linux or macOS)') + description : 'Enable the X11 gdk backend (only when building on Unix)') option('wayland-backend', type: 'boolean', value: true, - description : 'Enable the wayland gdk backend (only when building on Linux)') + description : 'Enable the wayland gdk backend (only when building on Unix except for macOS)') option('broadway-backend', type: 'boolean', value: false, description : 'Enable the broadway (HTML5) gdk backend') option('win32-backend', type: 'boolean', value: true, diff --git a/tests/meson.build b/tests/meson.build index 051fbb1588..772dc23e49 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -133,7 +133,7 @@ gtk_tests = [ ['testtexture'], ] -if os_linux +if os_unix gtk_tests += [['testfontchooserdialog']] endif From 48af68894052101f52ed8390efb61ad9cfd8b47b Mon Sep 17 00:00:00 2001 From: Ting-Wei Lan Date: Sun, 22 Apr 2018 21:42:03 +0800 Subject: [PATCH 02/11] build: Use cc.links to check linker arguments Instead of hard-coding linker flags for a specific operating system and a specific compiler, we can should cc.links to test them, so they can be used on more operating systems and compilers. --- meson.build | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/meson.build b/meson.build index cdd83e7635..895cef9fc0 100644 --- a/meson.build +++ b/meson.build @@ -284,8 +284,12 @@ endif common_ldflags = [] -if host_machine.system() == 'linux' and cc.get_id() == 'gcc' - common_ldflags += [ '-Wl,-Bsymbolic', '-Wl,-z,relro', '-Wl,-z,now', ] +if os_unix and not os_darwin + foreach ldflag: [ '-Wl,-Bsymbolic', '-Wl,-z,relro', '-Wl,-z,now', ] + if cc.links('int main () { return 0; }', name: ldflag, args: ldflag) + common_ldflags += [ ldflag ] + endif + endforeach endif # Maintain compatibility with autotools From 1517114d9f5cef852517e97e231bd5166868e790 Mon Sep 17 00:00:00 2001 From: Ting-Wei Lan Date: Sun, 22 Apr 2018 21:45:19 +0800 Subject: [PATCH 03/11] Fix header guards They were found by clang. --- gtk/gtkcssfontfeaturesvalueprivate.h | 2 +- gtk/gtkcssfontvariationsvalueprivate.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/gtk/gtkcssfontfeaturesvalueprivate.h b/gtk/gtkcssfontfeaturesvalueprivate.h index 834a9a09bd..f78a8cd182 100644 --- a/gtk/gtkcssfontfeaturesvalueprivate.h +++ b/gtk/gtkcssfontfeaturesvalueprivate.h @@ -18,7 +18,7 @@ */ #ifndef __GTK_CSS_FONT_FEATURES_VALUE_PRIVATE_H__ -#define __GTK_CSS_FONT_FEATURES_PRIVATE_H__ +#define __GTK_CSS_FONT_FEATURES_VALUE_PRIVATE_H__ #include "gtkcssparserprivate.h" #include "gtkcssvalueprivate.h" diff --git a/gtk/gtkcssfontvariationsvalueprivate.h b/gtk/gtkcssfontvariationsvalueprivate.h index 153cfeb2e1..90e0fd3a84 100644 --- a/gtk/gtkcssfontvariationsvalueprivate.h +++ b/gtk/gtkcssfontvariationsvalueprivate.h @@ -18,7 +18,7 @@ */ #ifndef __GTK_CSS_FONT_VARIATIONS_VALUE_PRIVATE_H__ -#define __GTK_CSS_FONT_VARIATIONS_PRIVATE_H__ +#define __GTK_CSS_FONT_VARIATIONS_VALUE_PRIVATE_H__ #include "gtkcssparserprivate.h" #include "gtkcssvalueprivate.h" From f57c1a29012300b4062321092c14f7735ab04a84 Mon Sep 17 00:00:00 2001 From: Ting-Wei Lan Date: Sun, 22 Apr 2018 21:48:35 +0800 Subject: [PATCH 04/11] imcontextxim: Don't assign a variable to itself This causes clang to show warnings. --- gtk/gtkimcontextxim.c | 1 - 1 file changed, 1 deletion(-) diff --git a/gtk/gtkimcontextxim.c b/gtk/gtkimcontextxim.c index e8b398c304..01629ab054 100644 --- a/gtk/gtkimcontextxim.c +++ b/gtk/gtkimcontextxim.c @@ -418,7 +418,6 @@ get_im (GdkSurface *client_surface, } else { - tmp_info = tmp_info; break; } } From 2a87caf529a76ae8d1a8a7b72801c47e0350b68e Mon Sep 17 00:00:00 2001 From: Ting-Wei Lan Date: Sun, 22 Apr 2018 21:51:36 +0800 Subject: [PATCH 05/11] gtk-demo, testgtk: Don't pass an integer to fabs It looks like a mistake which compilers are likely to warn. --- demos/gtk-demo/spinbutton.c | 6 +++--- tests/testgtk.c | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/demos/gtk-demo/spinbutton.c b/demos/gtk-demo/spinbutton.c index 310715c1f4..17266b3a24 100644 --- a/demos/gtk-demo/spinbutton.c +++ b/demos/gtk-demo/spinbutton.c @@ -33,14 +33,14 @@ hex_spin_output (GtkSpinButton *spin_button) { GtkAdjustment *adjustment; gchar *buf; - gint val; + gdouble val; adjustment = gtk_spin_button_get_adjustment (spin_button); - val = (gint) gtk_adjustment_get_value (adjustment); + val = gtk_adjustment_get_value (adjustment); if (fabs (val) < 1e-5) buf = g_strdup ("0x00"); else - buf = g_strdup_printf ("0x%.2X", val); + buf = g_strdup_printf ("0x%.2X", (gint) val); if (strcmp (buf, gtk_spin_button_get_text (spin_button))) gtk_spin_button_set_text (spin_button, buf); g_free (buf); diff --git a/tests/testgtk.c b/tests/testgtk.c index 626104bce3..7e5d683746 100644 --- a/tests/testgtk.c +++ b/tests/testgtk.c @@ -3240,14 +3240,14 @@ spin_button_hex_output_func (GtkSpinButton *spin_button) { GtkAdjustment *adjustment; static gchar buf[7]; - gint val; + gdouble val; adjustment = gtk_spin_button_get_adjustment (spin_button); - val = (gint) gtk_adjustment_get_value (adjustment); + val = gtk_adjustment_get_value (adjustment); if (fabs (val) < 1e-5) sprintf (buf, "0x00"); else - sprintf (buf, "0x%.2X", val); + sprintf (buf, "0x%.2X", (gint) val); if (strcmp (buf, gtk_spin_button_get_text (spin_button))) gtk_spin_button_set_text (spin_button, buf); From 3f307c59bd0f4e746f1ce30bfb1abf8036c4eeb2 Mon Sep 17 00:00:00 2001 From: Emmanuele Bassi Date: Mon, 23 Apr 2018 10:57:07 +0100 Subject: [PATCH 06/11] wayland: Allow a NULL inhibitors hash table The shortcuts inhibitors hash table is created when we create a GdkWaylandWindow implementation for a GdkWindow, and it's destroyed once we finalize the instance. The fake "root" window we create for the Wayland display does not have a backing native window, so the shortcuts inhibitors hash table is set to NULL; this causes a critical error message when calling g_hash_table_destroy() on it. The finalization of the root window happens when we close a display connection. We should use g_clear_pointer(), instead, as it's NULL safe. Without this change, the displayclose test fails, as all warnings are considered fatal. --- gdk/wayland/gdksurface-wayland.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/gdk/wayland/gdksurface-wayland.c b/gdk/wayland/gdksurface-wayland.c index e64cba366b..7823589938 100644 --- a/gdk/wayland/gdksurface-wayland.c +++ b/gdk/wayland/gdksurface-wayland.c @@ -914,8 +914,7 @@ gdk_surface_impl_wayland_finalize (GObject *object) g_clear_pointer (&impl->opaque_region, cairo_region_destroy); g_clear_pointer (&impl->input_region, cairo_region_destroy); g_clear_pointer (&impl->staged_updates_region, cairo_region_destroy); - - g_hash_table_destroy (impl->shortcuts_inhibitors); + g_clear_pointer (&impl->shortcuts_inhibitors, g_hash_table_unref); G_OBJECT_CLASS (_gdk_surface_impl_wayland_parent_class)->finalize (object); } From e283ed55232fe183ed3f762824364eff96d88f33 Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Sat, 19 Aug 2017 14:07:59 -0400 Subject: [PATCH 07/11] Export some entry functions privately This will be used in the following commits. --- gtk/gtkentry.c | 9 ++------- gtk/gtkentryprivate.h | 7 +++++++ 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/gtk/gtkentry.c b/gtk/gtkentry.c index 76f412d438..4d09db43cc 100644 --- a/gtk/gtkentry.c +++ b/gtk/gtkentry.c @@ -561,11 +561,6 @@ static gboolean gtk_entry_key_controller_key_pressed (GtkEventControllerKey *co /* Internal routines */ -static void gtk_entry_enter_text (GtkEntry *entry, - const gchar *str); -static void gtk_entry_set_positions (GtkEntry *entry, - gint current_pos, - gint selection_bound); static void gtk_entry_draw_text (GtkEntry *entry, GtkSnapshot *snapshot); static void gtk_entry_draw_cursor (GtkEntry *entry, @@ -5269,7 +5264,7 @@ gtk_entry_delete_surrounding_cb (GtkIMContext *slave, */ /* Used for im_commit_cb and inserting Unicode chars */ -static void +void gtk_entry_enter_text (GtkEntry *entry, const gchar *str) { @@ -5304,7 +5299,7 @@ gtk_entry_enter_text (GtkEntry *entry, /* All changes to priv->current_pos and priv->selection_bound * should go through this function. */ -static void +void gtk_entry_set_positions (GtkEntry *entry, gint current_pos, gint selection_bound) diff --git a/gtk/gtkentryprivate.h b/gtk/gtkentryprivate.h index 83d9715b77..527bbf8bff 100644 --- a/gtk/gtkentryprivate.h +++ b/gtk/gtkentryprivate.h @@ -91,6 +91,13 @@ GtkIMContext* _gtk_entry_get_im_context (GtkEntry *entry); void _gtk_entry_grab_focus (GtkEntry *entry, gboolean select_all); +void gtk_entry_enter_text (GtkEntry *entry, + const char *text); +void gtk_entry_set_positions (GtkEntry *entry, + int current_pos, + int selection_bound); + + G_END_DECLS #endif /* __GTK_ENTRY_PRIVATE_H__ */ From 1104a8fdb7c19822faf7cdf737cae43a06615742 Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Sat, 19 Aug 2017 14:06:47 -0400 Subject: [PATCH 08/11] Add an emoji completion popup This widget provides entry completion-like functionality for Emoji codes like :grin: or :kiss:. --- gtk/gtkemojicompletion.c | 665 +++++++++++++++++++++++++++++++++ gtk/gtkemojicompletion.h | 41 ++ gtk/meson.build | 1 + gtk/theme/Adwaita/_common.scss | 16 +- gtk/ui/gtkemojicompletion.ui | 16 + po/POTFILES.in | 2 + 6 files changed, 740 insertions(+), 1 deletion(-) create mode 100644 gtk/gtkemojicompletion.c create mode 100644 gtk/gtkemojicompletion.h create mode 100644 gtk/ui/gtkemojicompletion.ui diff --git a/gtk/gtkemojicompletion.c b/gtk/gtkemojicompletion.c new file mode 100644 index 0000000000..f1868c7e2a --- /dev/null +++ b/gtk/gtkemojicompletion.c @@ -0,0 +1,665 @@ +/* gtkemojicompletion.c: An Emoji picker widget + * Copyright 2017, Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + */ + +#include "config.h" + +#include "gtkemojicompletion.h" + +#include "gtkentryprivate.h" +#include "gtkbox.h" +#include "gtkcssprovider.h" +#include "gtklistbox.h" +#include "gtklabel.h" +#include "gtkpopover.h" +#include "gtkintl.h" +#include "gtkprivate.h" +#include "gtkgesturelongpress.h" +#include "gtkflowbox.h" +#include "gtkstack.h" + +struct _GtkEmojiCompletion +{ + GtkPopover parent_instance; + + GtkEntry *entry; + char *text; + guint length; + guint offset; + gulong changed_id; + guint n_matches; + + GtkWidget *list; + GtkWidget *active; + GtkWidget *active_variation; + + GVariant *data; + + GtkGesture *long_press; +}; + +struct _GtkEmojiCompletionClass { + GtkPopoverClass parent_class; +}; + +static void connect_signals (GtkEmojiCompletion *completion, + GtkEntry *entry); +static void disconnect_signals (GtkEmojiCompletion *completion); +static int populate_completion (GtkEmojiCompletion *completion, + const char *text, + guint offset); + +#define MAX_ROWS 5 + +G_DEFINE_TYPE (GtkEmojiCompletion, gtk_emoji_completion, GTK_TYPE_POPOVER) + +static void +gtk_emoji_completion_finalize (GObject *object) +{ + GtkEmojiCompletion *completion = GTK_EMOJI_COMPLETION (object); + + disconnect_signals (completion); + + g_free (completion->text); + g_variant_unref (completion->data); + + g_clear_object (&completion->long_press); + + G_OBJECT_CLASS (gtk_emoji_completion_parent_class)->finalize (object); +} + +static void +update_completion (GtkEmojiCompletion *completion) +{ + const char *text; + guint length; + guint n_matches; + + n_matches = 0; + + text = gtk_entry_get_text (GTK_ENTRY (completion->entry)); + length = strlen (text); + + if (length > 0) + { + gboolean found_candidate = FALSE; + const char *p; + + p = text + length; + do + { +next: + p = g_utf8_prev_char (p); + if (*p == ':') + { + if (p + 1 == text + length) + goto next; + + if (p == text || !g_unichar_isalnum (g_utf8_get_char (p - 1))) + { + found_candidate = TRUE; + } + + break; + } + } + while (g_unichar_isalnum (g_utf8_get_char (p)) || *p == '_'); + + if (found_candidate) + n_matches = populate_completion (completion, p, 0); + } + + if (n_matches > 0) + gtk_popover_popup (GTK_POPOVER (completion)); + else + gtk_popover_popdown (GTK_POPOVER (completion)); +} + +static void +entry_changed (GtkEntry *entry, GtkEmojiCompletion *completion) +{ + update_completion (completion); +} + +static void +emoji_activated (GtkWidget *row, + GtkEmojiCompletion *completion) +{ + const char *emoji; + guint length; + + gtk_popover_popdown (GTK_POPOVER (completion)); + + emoji = (const char *)g_object_get_data (G_OBJECT (row), "text"); + + g_signal_handler_block (completion->entry, completion->changed_id); + + length = g_utf8_strlen (gtk_entry_get_text (completion->entry), -1); + gtk_entry_set_positions (completion->entry, length - completion->length, length); + gtk_entry_enter_text (completion->entry, emoji); + + g_signal_handler_unblock (completion->entry, completion->changed_id); +} + +static void +row_activated (GtkListBox *list, + GtkListBoxRow *row, + gpointer data) +{ + GtkEmojiCompletion *completion = data; + + emoji_activated (GTK_WIDGET (row), completion); +} + +static void +child_activated (GtkFlowBox *box, + GtkFlowBoxChild *child, + gpointer data) +{ + GtkEmojiCompletion *completion = data; + + g_print ("child activated\n"); + emoji_activated (GTK_WIDGET (child), completion); +} + +static void +move_active_row (GtkEmojiCompletion *completion, + int direction) +{ + GtkWidget *child; + GtkWidget *base; + + for (child = gtk_widget_get_first_child (completion->list); + child != NULL; + child = gtk_widget_get_next_sibling (child)) + { + gtk_widget_unset_state_flags (child, GTK_STATE_FLAG_PRELIGHT); + base = GTK_WIDGET (g_object_get_data (G_OBJECT (child), "base")); + gtk_widget_unset_state_flags (base, GTK_STATE_FLAG_PRELIGHT); + } + + if (completion->active != NULL) + { + if (direction == 1) + completion->active = gtk_widget_get_next_sibling (completion->active); + else + completion->active = gtk_widget_get_prev_sibling (completion->active); + } + + if (completion->active == NULL) + { + if (direction == 1) + completion->active = gtk_widget_get_first_child (completion->list); + else + completion->active = gtk_widget_get_last_child (completion->list); + } + + if (completion->active != NULL) + gtk_widget_set_state_flags (completion->active, GTK_STATE_FLAG_PRELIGHT, FALSE); + + if (completion->active_variation) + { + gtk_widget_unset_state_flags (completion->active_variation, GTK_STATE_FLAG_PRELIGHT); + completion->active_variation = NULL; + } +} + +static void +activate_active_row (GtkEmojiCompletion *completion) +{ + if (GTK_IS_FLOW_BOX_CHILD (completion->active_variation)) + emoji_activated (completion->active_variation, completion); + else if (completion->active != NULL) + emoji_activated (completion->active, completion); +} + +static void +show_variations (GtkEmojiCompletion *completion, + GtkWidget *row, + gboolean visible) +{ + GtkWidget *stack; + GtkWidget *box; + gboolean is_visible; + + if (!row) + return; + + stack = GTK_WIDGET (g_object_get_data (G_OBJECT (row), "stack")); + box = gtk_stack_get_child_by_name (GTK_STACK (stack), "variations"); + if (!box) + return; + + is_visible = gtk_stack_get_visible_child (GTK_STACK (stack)) == box; + if (is_visible == visible) + return; + + if (visible) + gtk_widget_unset_state_flags (row, GTK_STATE_FLAG_PRELIGHT); + else + gtk_widget_set_state_flags (row, GTK_STATE_FLAG_PRELIGHT, FALSE); + + gtk_stack_set_visible_child_name (GTK_STACK (stack), visible ? "variations" : "text"); + if (completion->active_variation) + { + gtk_widget_unset_state_flags (completion->active_variation, GTK_STATE_FLAG_PRELIGHT); + completion->active_variation = NULL; + } +} + +static gboolean +move_active_variation (GtkEmojiCompletion *completion, + int direction) +{ + GtkWidget *base; + GtkWidget *stack; + GtkWidget *box; + GtkWidget *next; + + if (!completion->active) + return FALSE; + + base = GTK_WIDGET (g_object_get_data (G_OBJECT (completion->active), "base")); + stack = GTK_WIDGET (g_object_get_data (G_OBJECT (completion->active), "stack")); + box = gtk_stack_get_child_by_name (GTK_STACK (stack), "variations"); + + if (gtk_stack_get_visible_child (GTK_STACK (stack)) != box) + return FALSE; + + next = NULL; + + if (!completion->active_variation) + next = base; + else if (completion->active_variation == base && direction == 1) + next = gtk_widget_get_first_child (box); + else if (completion->active_variation == gtk_widget_get_first_child (box) && direction == -1) + next = base; + else if (direction == 1) + next = gtk_widget_get_next_sibling (completion->active_variation); + else if (direction == -1) + next = gtk_widget_get_prev_sibling (completion->active_variation); + + if (next) + { + if (completion->active_variation) + gtk_widget_unset_state_flags (completion->active_variation, GTK_STATE_FLAG_PRELIGHT); + completion->active_variation = next; + gtk_widget_set_state_flags (completion->active_variation, GTK_STATE_FLAG_PRELIGHT, FALSE); + } + + return next != NULL; +} + +static gboolean +entry_key_press (GtkEntry *entry, + GdkEventKey *event, + GtkEmojiCompletion *completion) +{ + guint keyval; + + if (!gtk_widget_get_visible (GTK_WIDGET (completion))) + return FALSE; + + gdk_event_get_keyval ((GdkEvent*)event, &keyval); + + if (keyval == GDK_KEY_Escape) + { + gtk_popover_popdown (GTK_POPOVER (completion)); + return TRUE; + } + + if (keyval == GDK_KEY_Tab) + { + show_variations (completion, completion->active, FALSE); + + guint offset = completion->offset + MAX_ROWS; + if (offset >= completion->n_matches) + offset = 0; + populate_completion (completion, completion->text, offset); + return TRUE; + } + + if (keyval == GDK_KEY_Up) + { + show_variations (completion, completion->active, FALSE); + + move_active_row (completion, -1); + return TRUE; + } + + if (keyval == GDK_KEY_Down) + { + show_variations (completion, completion->active, FALSE); + + move_active_row (completion, 1); + return TRUE; + } + + if (keyval == GDK_KEY_Return || + keyval == GDK_KEY_KP_Enter || + keyval == GDK_KEY_ISO_Enter) + { + activate_active_row (completion); + return TRUE; + } + + if (keyval == GDK_KEY_Right) + { + show_variations (completion, completion->active, TRUE); + move_active_variation (completion, 1); + return TRUE; + } + + if (keyval == GDK_KEY_Left) + { + if (!move_active_variation (completion, -1)) + show_variations (completion, completion->active, FALSE); + return TRUE; + } + + return FALSE; +} + +static gboolean +entry_focus_out (GtkWidget *entry, + GParamSpec *pspec, + GtkEmojiCompletion *completion) +{ + if (!gtk_widget_has_focus (entry)) + gtk_popover_popdown (GTK_POPOVER (completion)); + return FALSE; +} + +static void +connect_signals (GtkEmojiCompletion *completion, + GtkEntry *entry) +{ + completion->entry = entry; + + completion->changed_id = g_signal_connect (entry, "changed", G_CALLBACK (entry_changed), completion); + g_signal_connect (entry, "key-press-event", G_CALLBACK (entry_key_press), completion); + g_signal_connect (entry, "notify::has-focus", G_CALLBACK (entry_focus_out), completion); +} + +static void +disconnect_signals (GtkEmojiCompletion *completion) +{ + g_signal_handlers_disconnect_by_func (completion->entry, entry_changed, completion); + g_signal_handlers_disconnect_by_func (completion->entry, entry_key_press, completion); + g_signal_handlers_disconnect_by_func (completion->entry, entry_focus_out, completion); + + completion->entry = NULL; +} + +static gboolean +has_variations (GVariant *emoji_data) +{ + GVariant *codes; + int i; + gboolean has_variations; + + has_variations = FALSE; + codes = g_variant_get_child_value (emoji_data, 0); + for (i = 0; i < g_variant_n_children (codes); i++) + { + gunichar code; + g_variant_get_child (codes, i, "u", &code); + if (code == 0) + { + has_variations = TRUE; + break; + } + } + g_variant_unref (codes); + + return has_variations; +} + +static void +get_text (GVariant *emoji_data, + gunichar modifier, + char *text, + gsize length) +{ + GVariant *codes; + int i; + char *p; + + p = text; + codes = g_variant_get_child_value (emoji_data, 0); + for (i = 0; i < g_variant_n_children (codes); i++) + { + gunichar code; + + g_variant_get_child (codes, i, "u", &code); + if (code == 0) + code = modifier; + if (code != 0) + p += g_unichar_to_utf8 (code, p); + } + g_variant_unref (codes); + p[0] = 0; +} + +static void +add_emoji_variation (GtkWidget *box, + GVariant *emoji_data, + gunichar modifier) +{ + GtkWidget *child; + GtkWidget *label; + PangoAttrList *attrs; + char text[64]; + + get_text (emoji_data, modifier, text, 64); + + label = gtk_label_new (text); + attrs = pango_attr_list_new (); + pango_attr_list_insert (attrs, pango_attr_scale_new (PANGO_SCALE_X_LARGE)); + gtk_label_set_attributes (GTK_LABEL (label), attrs); + pango_attr_list_unref (attrs); + + child = gtk_flow_box_child_new (); + gtk_style_context_add_class (gtk_widget_get_style_context (child), "emoji"); + g_object_set_data_full (G_OBJECT (child), "text", g_strdup (text), g_free); + g_object_set_data_full (G_OBJECT (child), "emoji-data", + g_variant_ref (emoji_data), + (GDestroyNotify)g_variant_unref); + if (modifier != 0) + g_object_set_data (G_OBJECT (child), "modifier", GUINT_TO_POINTER (modifier)); + + gtk_container_add (GTK_CONTAINER (child), label); + gtk_flow_box_insert (GTK_FLOW_BOX (box), child, -1); +} + +static void +add_emoji (GtkWidget *list, + GVariant *emoji_data, + GtkEmojiCompletion *completion) +{ + GtkWidget *child; + GtkWidget *label; + GtkWidget *box; + PangoAttrList *attrs; + char text[64]; + const char *shortname; + GtkWidget *stack; + gunichar modifier; + + get_text (emoji_data, 0, text, 64); + + label = gtk_label_new (text); + attrs = pango_attr_list_new (); + pango_attr_list_insert (attrs, pango_attr_scale_new (PANGO_SCALE_X_LARGE)); + gtk_label_set_attributes (GTK_LABEL (label), attrs); + pango_attr_list_unref (attrs); + gtk_style_context_add_class (gtk_widget_get_style_context (label), "emoji"); + + child = gtk_list_box_row_new (); + gtk_widget_set_focus_on_click (child, FALSE); + box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0); + gtk_container_add (GTK_CONTAINER (child), box); + gtk_box_pack_start (GTK_BOX (box), label); + g_object_set_data (G_OBJECT (child), "base", label); + + stack = gtk_stack_new (); + gtk_stack_set_homogeneous (GTK_STACK (stack), TRUE); + gtk_stack_set_transition_type (GTK_STACK (stack), GTK_STACK_TRANSITION_TYPE_OVER_RIGHT_LEFT); + gtk_box_pack_start (GTK_BOX (box), stack); + g_object_set_data (G_OBJECT (child), "stack", stack); + + g_variant_get_child (emoji_data, 2, "&s", &shortname); + label = gtk_label_new (shortname); + gtk_label_set_xalign (GTK_LABEL (label), 0); + + gtk_stack_add_named (GTK_STACK (stack), label, "text"); + + if (has_variations (emoji_data)) + { + box = gtk_flow_box_new (); + gtk_flow_box_set_homogeneous (GTK_FLOW_BOX (box), TRUE); + gtk_flow_box_set_min_children_per_line (GTK_FLOW_BOX (box), 5); + gtk_flow_box_set_max_children_per_line (GTK_FLOW_BOX (box), 5); + gtk_flow_box_set_activate_on_single_click (GTK_FLOW_BOX (box), TRUE); + gtk_flow_box_set_selection_mode (GTK_FLOW_BOX (box), GTK_SELECTION_NONE); + g_signal_connect (box, "child-activated", G_CALLBACK (child_activated), completion); + for (modifier = 0x1f3fb; modifier <= 0x1f3ff; modifier++) + add_emoji_variation (box, emoji_data, modifier); + + gtk_stack_add_named (GTK_STACK (stack), box, "variations"); + } + + g_object_set_data_full (G_OBJECT (child), "text", g_strdup (text), g_free); + g_object_set_data_full (G_OBJECT (child), "emoji-data", + g_variant_ref (emoji_data), (GDestroyNotify)g_variant_unref); + gtk_style_context_add_class (gtk_widget_get_style_context (child), "emoji-completion-row"); + + gtk_list_box_insert (GTK_LIST_BOX (list), child, -1); +} + +static int +populate_completion (GtkEmojiCompletion *completion, + const char *text, + guint offset) +{ + GList *children, *l; + int n_matches; + int n_added; + GVariantIter iter; + GVariant *item; + + text = g_strdup (text); + g_free (completion->text); + completion->text = g_strdup (text); + completion->length = g_utf8_strlen (text, -1); + completion->offset = offset; + + children = gtk_container_get_children (GTK_CONTAINER (completion->list)); + for (l = children; l; l = l->next) + gtk_widget_destroy (GTK_WIDGET (l->data)); + g_list_free (children); + + completion->active = NULL; + + n_matches = 0; + n_added = 0; + g_variant_iter_init (&iter, completion->data); + while ((item = g_variant_iter_next_value (&iter))) + { + const char *shortname; + + g_variant_get_child (item, 2, "&s", &shortname); + if (g_str_has_prefix (shortname, text)) + { + n_matches++; + + if (n_matches > offset && n_added < MAX_ROWS) + { + add_emoji (completion->list, item, completion); + n_added++; + } + } + } + + completion->n_matches = n_matches; + + if (n_added > 0) + { + completion->active = gtk_widget_get_first_child (completion->list); + gtk_widget_set_state_flags (completion->active, GTK_STATE_FLAG_PRELIGHT, FALSE); + } + + return n_added; +} + +static void +long_pressed_cb (GtkGesture *gesture, + double x, + double y, + gpointer data) +{ + GtkEmojiCompletion *completion = data; + GtkWidget *row; + + row = GTK_WIDGET (gtk_list_box_get_row_at_y (GTK_LIST_BOX (completion->list), y)); + if (!row) + return; + + show_variations (completion, row, TRUE); +} + +static void +gtk_emoji_completion_init (GtkEmojiCompletion *completion) +{ + g_autoptr(GBytes) bytes = NULL; + + gtk_widget_init_template (GTK_WIDGET (completion)); + + bytes = g_resources_lookup_data ("/org/gtk/libgtk/emoji/emoji.data", 0, NULL); + completion->data = g_variant_ref_sink (g_variant_new_from_bytes (G_VARIANT_TYPE ("a(auss)"), bytes, TRUE)); + + completion->long_press = gtk_gesture_long_press_new (completion->list); + g_signal_connect (completion->long_press, "pressed", G_CALLBACK (long_pressed_cb), completion); +} + +static void +gtk_emoji_completion_class_init (GtkEmojiCompletionClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->finalize = gtk_emoji_completion_finalize; + + gtk_widget_class_set_template_from_resource (widget_class, "/org/gtk/libgtk/ui/gtkemojicompletion.ui"); + + gtk_widget_class_bind_template_child (widget_class, GtkEmojiCompletion, list); + + gtk_widget_class_bind_template_callback (widget_class, row_activated); +} + +GtkWidget * +gtk_emoji_completion_new (GtkEntry *entry) +{ + GtkEmojiCompletion *completion; + + completion = GTK_EMOJI_COMPLETION (g_object_new (GTK_TYPE_EMOJI_COMPLETION, + "relative-to", entry, + NULL)); + + connect_signals (completion, entry); + + return GTK_WIDGET (completion); +} diff --git a/gtk/gtkemojicompletion.h b/gtk/gtkemojicompletion.h new file mode 100644 index 0000000000..ff7cb1fa63 --- /dev/null +++ b/gtk/gtkemojicompletion.h @@ -0,0 +1,41 @@ +/* gtkemojicompletion.h: An Emoji picker widget + * Copyright 2017, Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + */ + +#pragma once + +#if !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION) +#error "Only can be included directly." +#endif + +#include + +G_BEGIN_DECLS + +#define GTK_TYPE_EMOJI_COMPLETION (gtk_emoji_completion_get_type ()) +#define GTK_EMOJI_COMPLETION(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_EMOJI_COMPLETION, GtkEmojiCompletion)) +#define GTK_EMOJI_COMPLETION_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_EMOJI_COMPLETION, GtkEmojiCompletionClass)) +#define GTK_IS_EMOJI_COMPLETION(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_EMOJI_COMPLETION)) +#define GTK_IS_EMOJI_COMPLETION_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_EMOJI_COMPLETION)) +#define GTK_EMOJI_COMPLETION_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_EMOJI_COMPLETION, GtkEmojiCompletionClass)) + +typedef struct _GtkEmojiCompletion GtkEmojiCompletion; +typedef struct _GtkEmojiCompletionClass GtkEmojiCompletionClass; + +GType gtk_emoji_completion_get_type (void) G_GNUC_CONST; +GtkWidget *gtk_emoji_completion_new (GtkEntry *entry); + +G_END_DECLS diff --git a/gtk/meson.build b/gtk/meson.build index 075cc9069f..db30e4c1ec 100644 --- a/gtk/meson.build +++ b/gtk/meson.build @@ -197,6 +197,7 @@ gtk_public_sources = files([ 'gtkdrawingarea.c', 'gtkeditable.c', 'gtkemojichooser.c', + 'gtkemojicompletion.c', 'gtkentry.c', 'gtkentrybuffer.c', 'gtkentrycompletion.c', diff --git a/gtk/theme/Adwaita/_common.scss b/gtk/theme/Adwaita/_common.scss index d65f368860..6317fdb930 100644 --- a/gtk/theme/Adwaita/_common.scss +++ b/gtk/theme/Adwaita/_common.scss @@ -4556,7 +4556,7 @@ button.emoji-section { &:checked label { opacity: 1; } } -.emoji { +popover.emoji-picker .emoji { font-size: x-large; padding: 6px; border-radius: 6px; @@ -4565,3 +4565,17 @@ button.emoji-section { background: $selected_bg_color; } } + +popover.emoji-completion arrow { + border: none; + background: none; +} + +popover.emoji-completion contents row box { + border-spacing: 10px; + padding: 2px 10px; +} + +popover.emoji-completion .emoji:hover { + background-color: $popover_hover_color; +} diff --git a/gtk/ui/gtkemojicompletion.ui b/gtk/ui/gtkemojicompletion.ui new file mode 100644 index 0000000000..f7a5e1baea --- /dev/null +++ b/gtk/ui/gtkemojicompletion.ui @@ -0,0 +1,16 @@ + + + + diff --git a/po/POTFILES.in b/po/POTFILES.in index 627262bf47..6ebeaaac51 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -126,6 +126,7 @@ gtk/gtkdragsource.c gtk/gtkdrawingarea.c gtk/gtkeditable.c gtk/gtkemojichooser.c +gtk/gtkemojicompletion.c gtk/gtkentrybuffer.c gtk/gtkentry.c gtk/gtkentrycompletion.c @@ -350,6 +351,7 @@ gtk/ui/gtkcolorchooserdialog.ui gtk/ui/gtkcoloreditor.ui gtk/ui/gtkdialog.ui gtk/ui/gtkemojichooser.ui +gtk/ui/gtkemojicompletion.ui gtk/ui/gtkfilechooserdialog.ui gtk/ui/gtkfilechooserwidget.ui gtk/ui/gtkfontchooserdialog.ui From 5d8008647005454a7d8626a3159f247c08e1c923 Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Sat, 19 Aug 2017 14:08:15 -0400 Subject: [PATCH 09/11] entry: Add emoji completion Pop up completions when the text in the entry matches :word: This functionality has to be enabled using the enable-emoji-completion property. --- gtk/gtkentry.c | 42 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/gtk/gtkentry.c b/gtk/gtkentry.c index 4d09db43cc..6afd90e7b1 100644 --- a/gtk/gtkentry.c +++ b/gtk/gtkentry.c @@ -41,7 +41,9 @@ #include "gtkdnd.h" #include "gtkdndprivate.h" #include "gtkemojichooser.h" +#include "gtkemojicompletion.h" #include "gtkentrybuffer.h" +#include "gtkeventcontrollerkey.h" #include "gtkgesturedrag.h" #include "gtkgesturemultipress.h" #include "gtkgesturesingle.h" @@ -72,7 +74,6 @@ #include "gtktypebuiltins.h" #include "gtkwidgetprivate.h" #include "gtkwindow.h" -#include "gtkeventcontrollerkey.h" #include "a11y/gtkentryaccessible.h" @@ -253,6 +254,7 @@ struct _GtkEntryPrivate guint editable : 1; guint show_emoji_icon : 1; + guint enable_emoji_completion : 1; guint in_drag : 1; guint overwrite_mode : 1; guint visible : 1; @@ -371,6 +373,7 @@ enum { PROP_POPULATE_ALL, PROP_TABS, PROP_SHOW_EMOJI_ICON, + PROP_ENABLE_EMOJI_COMPLETION, PROP_EDITING_CANCELED, NUM_PROPERTIES = PROP_EDITING_CANCELED }; @@ -652,6 +655,8 @@ static void buffer_disconnect_signals (GtkEntry *entry); static GtkEntryBuffer *get_buffer (GtkEntry *entry); static void set_show_emoji_icon (GtkEntry *entry, gboolean value); +static void set_enable_emoji_completion (GtkEntry *entry, + gboolean value); static void gtk_entry_measure (GtkWidget *widget, GtkOrientation orientation, @@ -1404,6 +1409,13 @@ gtk_entry_class_init (GtkEntryClass *class) FALSE, GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY); + entry_props[PROP_ENABLE_EMOJI_COMPLETION] = + g_param_spec_boolean ("enable-emoji-completion", + P_("Enable Emoji completion"), + P_("Whether to suggest Emoji replacements"), + FALSE, + GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY); + g_object_class_install_properties (gobject_class, NUM_PROPERTIES, entry_props); /** @@ -2179,6 +2191,10 @@ gtk_entry_set_property (GObject *object, set_show_emoji_icon (entry, g_value_get_boolean (value)); break; + case PROP_ENABLE_EMOJI_COMPLETION: + set_enable_emoji_completion (entry, g_value_get_boolean (value)); + break; + case PROP_SCROLL_OFFSET: case PROP_CURSOR_POSITION: default: @@ -2411,6 +2427,10 @@ gtk_entry_get_property (GObject *object, g_value_set_boolean (value, priv->show_emoji_icon); break; + case PROP_ENABLE_EMOJI_COMPLETION: + g_value_set_boolean (value, priv->enable_emoji_completion); + break; + default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -9580,3 +9600,23 @@ set_show_emoji_icon (GtkEntry *entry, g_object_notify_by_pspec (G_OBJECT (entry), entry_props[PROP_SHOW_EMOJI_ICON]); gtk_widget_queue_resize (GTK_WIDGET (entry)); } + +static void +set_enable_emoji_completion (GtkEntry *entry, + gboolean value) +{ + GtkEntryPrivate *priv = gtk_entry_get_instance_private (entry); + + if (priv->enable_emoji_completion == value) + return; + + priv->enable_emoji_completion = value; + + if (priv->enable_emoji_completion) + g_object_set_data (G_OBJECT (entry), "emoji-completion-popup", + gtk_emoji_completion_new (entry)); + else + g_object_set_data (G_OBJECT (entry), "emoji-completion-popup", NULL); + + g_object_notify_by_pspec (G_OBJECT (entry), entry_props[PROP_ENABLE_EMOJI_COMPLETION]); +} From f0f2dd743f38b75f0508853d7540cc4b92032814 Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Tue, 15 Aug 2017 17:35:03 -0400 Subject: [PATCH 10/11] widget-factory: add emoji completion to an entry --- demos/widget-factory/widget-factory.ui | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/demos/widget-factory/widget-factory.ui b/demos/widget-factory/widget-factory.ui index 21eb02f361..8d70fc8d05 100644 --- a/demos/widget-factory/widget-factory.ui +++ b/demos/widget-factory/widget-factory.ui @@ -468,7 +468,8 @@ Suspendisse feugiat quam quis dolor accumsan cursus. 1 - + 1 + Click icon to change mode view-refresh-symbolic Change mode From 8be12a2427927cf914c6223c6a2e1267352c40da Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Mon, 23 Apr 2018 00:29:21 -0400 Subject: [PATCH 11/11] Drop Ctrl-Shift-e support Now that we have Emoji completion, drop the rather limited Ctrl-Shift-e support in GtkIMContextSimple, and leave this sequence to input methods. --- gtk/gtkimcontextsimple.c | 265 ++++----------------------------------- 1 file changed, 27 insertions(+), 238 deletions(-) diff --git a/gtk/gtkimcontextsimple.c b/gtk/gtkimcontextsimple.c index 83a7836c00..af41cc5d14 100644 --- a/gtk/gtkimcontextsimple.c +++ b/gtk/gtkimcontextsimple.c @@ -66,57 +66,15 @@ * by typing Ctrl-Shift-u, followed by a hexadecimal Unicode codepoint. * For example, Ctrl-Shift-u 1 2 3 Enter yields U+0123 LATIN SMALL LETTER * G WITH CEDILLA, i.e. ģ. - * - * ## Emoji - * - * GtkIMContextSimple also supports entry of Emoji by their name. - * This works by first typing Ctrl-Shift-e, followed by an Emoji name. - * - * The following names are supported: - * - :-) 🙂 - * - 8-) 😍 - * - <3 ❤ - * - kiss 💋 - * - grin 😁 - * - joy 😂 - * - :-* 😚 - * - xD 😆 - * - like 👍 - * - dislike 👎 - * - up 👆 - * - v ✌ - * - ok 👌 - * - B-) 😎 - * - ;-) 😉 - * - ;-P 😜 - * - :-p 😋 - * - 3( 😔 - * - :-( 😞 - * - :] 😏 - * - :'( 😢 - * - :_( 😭 - * - :(( 😩 - * - :o 😨 - * - :| 😐 - * - 3-) 😌 - * - >( 😠 - * - >(( 😡 - * - O:) 😇 - * - ;o 😰 - * - 8| 😳 - * - 8o 😲 - * - :X 😷 - * - }:) 😈 */ struct _GtkIMContextSimplePrivate { - guint16 compose_buffer[MAX(GTK_MAX_COMPOSE_LEN + 1, 9)]; + guint16 compose_buffer[GTK_MAX_COMPOSE_LEN + 1]; gunichar tentative_match; gint tentative_match_len; guint in_hex_sequence : 1; - guint in_emoji_sequence : 1; guint modifiers_dropped : 1; }; @@ -346,10 +304,9 @@ gtk_im_context_simple_commit_char (GtkIMContext *context, len = g_unichar_to_utf8 (ch, buf); buf[len] = '\0'; - if (priv->tentative_match || priv->in_hex_sequence || priv->in_emoji_sequence) + if (priv->tentative_match || priv->in_hex_sequence) { priv->in_hex_sequence = FALSE; - priv->in_emoji_sequence = FALSE; priv->tentative_match = 0; priv->tentative_match_len = 0; g_signal_emit_by_name (context_simple, "preedit-changed"); @@ -917,110 +874,6 @@ check_hex (GtkIMContextSimple *context_simple, return TRUE; } -typedef struct { - const char *name; - gunichar ch; -} EmojiItem; - -static EmojiItem emoji[] = { - { ":-)", 0x1f642 }, - { "8-)", 0x1f60d }, - { "<3", 0x02764 }, - { "kiss", 0x1f48b }, - { "grin", 0x1f601 }, - { "joy", 0x1f602 }, - { ":-*", 0x1f61a }, - { "xD", 0x1f606 }, - { "like", 0x1f44d }, - { "dislike", 0x1f44e }, - { "up", 0x1f446 }, - { "v", 0x0270c }, - { "ok", 0x1f44c }, - { "B-)", 0x1f60e }, - { ":-D", 0x1f603 }, - { ";-)", 0x1f609 }, - { ";-P", 0x1f61c }, - { ":-p", 0x1f60b }, - { "3(", 0x1f614 }, - { ":-(", 0x1f61e }, - { ":]", 0x1f60f }, - { ":'(", 0x1f622 }, - { ":_(", 0x1f62d }, - { ":((", 0x1f629 }, - { ":o", 0x1f628 }, - { ":|", 0x1f610 }, - { "3-)", 0x1f60c }, - { ">(", 0x1f620 }, - { ">((", 0x1f621 }, - { "O:)", 0x1f607 }, - { ";o", 0x1f630 }, - { "8|", 0x1f633 }, - { "8o", 0x1f632 }, - { ":X", 0x1f637 }, - { "}:)", 0x1f608 }, - { NULL, 0 } -}; - -static gboolean -check_emoji (GtkIMContextSimple *context_simple, - gint n_compose) -{ - GtkIMContextSimplePrivate *priv = context_simple->priv; - GString *str; - gint i; - gchar buf[7]; - char *lower; - gboolean has_completion; - - priv->tentative_match = 0; - priv->tentative_match_len = 0; - - str = g_string_new (NULL); - - i = 0; - while (i < n_compose) - { - gunichar ch; - - ch = gdk_keyval_to_unicode (priv->compose_buffer[i]); - - if (ch == 0) - return FALSE; - - buf[g_unichar_to_utf8 (ch, buf)] = '\0'; - - g_string_append (str, buf); - - ++i; - } - - lower = g_utf8_strdown (str->str, str->len); - - has_completion = FALSE; - for (i = 0; emoji[i].name; i++) - { - if (strcmp (str->str, emoji[i].name) == 0 || - strcmp (lower, emoji[i].name) == 0) - { - priv->tentative_match = emoji[i].ch; - priv->tentative_match_len = n_compose; - break; - } - - if (!has_completion && - (g_str_has_prefix (emoji[i].name, str->str) || - g_str_has_prefix (emoji[i].name, lower))) - { - has_completion = TRUE; - } - } - - g_string_free (str, TRUE); - g_free (lower); - - return priv->tentative_match != 0 || has_completion; -} - static void beep_surface (GdkSurface *surface) { @@ -1147,15 +1000,6 @@ canonical_hex_keyval (GdkEventKey *event) return 0; } -static guint -canonical_emoji_keyval (GdkEventKey *event) -{ - guint keyval; - gdk_event_get_keyval ((GdkEvent *) event, &keyval); - - return keyval; -} - static gboolean gtk_im_context_simple_filter_keypress (GtkIMContext *context, GdkEventKey *event) @@ -1170,12 +1014,10 @@ gtk_im_context_simple_filter_keypress (GtkIMContext *context, GdkModifierType hex_mod_mask; gboolean have_hex_mods; gboolean is_hex_start; - gboolean is_end; - gboolean is_emoji_start; + gboolean is_hex_end; gboolean is_backspace; gboolean is_escape; guint hex_keyval; - guint emoji_keyval; int i; gboolean compose_finish; gboolean compose_match; @@ -1191,11 +1033,11 @@ gtk_im_context_simple_filter_keypress (GtkIMContext *context, if (gdk_event_get_event_type ((GdkEvent *) event) == GDK_KEY_RELEASE) { - if ((keyval == GDK_KEY_Control_L || keyval == GDK_KEY_Control_R || + if (priv->in_hex_sequence && + (keyval == GDK_KEY_Control_L || keyval == GDK_KEY_Control_R || keyval == GDK_KEY_Shift_L || keyval == GDK_KEY_Shift_R)) { - if ((priv->in_hex_sequence || priv->in_emoji_sequence) && - priv->tentative_match && + if (priv->tentative_match && g_unichar_validate (priv->tentative_match)) { gtk_im_context_simple_commit_char (context, priv->tentative_match); @@ -1203,8 +1045,7 @@ gtk_im_context_simple_filter_keypress (GtkIMContext *context, return TRUE; } - else if (priv->in_emoji_sequence || - (priv->in_hex_sequence && n_compose == 0)) + else if (n_compose == 0) { priv->modifiers_dropped = TRUE; @@ -1217,7 +1058,6 @@ gtk_im_context_simple_filter_keypress (GtkIMContext *context, priv->tentative_match = 0; priv->in_hex_sequence = FALSE; - priv->in_emoji_sequence = FALSE; priv->compose_buffer[0] = 0; g_signal_emit_by_name (context_simple, "preedit-changed"); @@ -1238,21 +1078,19 @@ gtk_im_context_simple_filter_keypress (GtkIMContext *context, hex_mod_mask = gdk_keymap_get_modifier_mask (keymap, GDK_MODIFIER_INTENT_PRIMARY_ACCELERATOR); hex_mod_mask |= GDK_SHIFT_MASK; - if ((priv->in_hex_sequence || priv->in_emoji_sequence) && priv->modifiers_dropped) + if (priv->in_hex_sequence && priv->modifiers_dropped) have_hex_mods = TRUE; else have_hex_mods = (state & (hex_mod_mask)) == hex_mod_mask; is_hex_start = keyval == GDK_KEY_U; - is_emoji_start = (keyval == GDK_KEY_E) && !priv->in_hex_sequence; - is_end = (keyval == GDK_KEY_space || - keyval == GDK_KEY_KP_Space || - keyval == GDK_KEY_Return || - keyval == GDK_KEY_ISO_Enter || - keyval == GDK_KEY_KP_Enter); + is_hex_end = (keyval == GDK_KEY_space || + keyval == GDK_KEY_KP_Space || + keyval == GDK_KEY_Return || + keyval == GDK_KEY_ISO_Enter || + keyval == GDK_KEY_KP_Enter); is_backspace = keyval == GDK_KEY_BackSpace; is_escape = keyval == GDK_KEY_Escape; hex_keyval = canonical_hex_keyval (event); - emoji_keyval = canonical_emoji_keyval (event); /* If we are already in a non-hex sequence, or * this keystroke is not hex modifiers + hex digit, don't filter @@ -1262,18 +1100,17 @@ gtk_im_context_simple_filter_keypress (GtkIMContext *context, * ISO_Level3_Switch. */ if (!have_hex_mods || - (n_compose > 0 && !priv->in_hex_sequence && !priv->in_emoji_sequence) || - (n_compose == 0 && !priv->in_hex_sequence && !priv->in_emoji_sequence && - !is_hex_start && !is_emoji_start) || + (n_compose > 0 && !priv->in_hex_sequence) || + (n_compose == 0 && !priv->in_hex_sequence && !is_hex_start) || (priv->in_hex_sequence && !hex_keyval && - !is_hex_start && !is_end && !is_escape && !is_backspace)) + !is_hex_start && !is_hex_end && !is_escape && !is_backspace)) { GdkModifierType no_text_input_mask; no_text_input_mask = gdk_keymap_get_modifier_mask (keymap, GDK_MODIFIER_INTENT_NO_TEXT_INPUT); if (state & no_text_input_mask || - ((priv->in_hex_sequence || priv->in_emoji_sequence) && priv->modifiers_dropped && + (priv->in_hex_sequence && priv->modifiers_dropped && (keyval == GDK_KEY_Return || keyval == GDK_KEY_ISO_Enter || keyval == GDK_KEY_KP_Enter))) @@ -1283,21 +1120,17 @@ gtk_im_context_simple_filter_keypress (GtkIMContext *context, } /* Handle backspace */ - if ((priv->in_hex_sequence || priv->in_emoji_sequence) && have_hex_mods && is_backspace) + if (priv->in_hex_sequence && have_hex_mods && is_backspace) { if (n_compose > 0) { n_compose--; priv->compose_buffer[n_compose] = 0; - if (priv->in_hex_sequence) - check_hex (context_simple, n_compose); - else if (priv->in_emoji_sequence) - check_emoji (context_simple, n_compose); + check_hex (context_simple, n_compose); } else { priv->in_hex_sequence = FALSE; - priv->in_emoji_sequence = FALSE; } g_signal_emit_by_name (context_simple, "preedit-changed"); @@ -1343,20 +1176,6 @@ gtk_im_context_simple_filter_keypress (GtkIMContext *context, return TRUE; } - /* Check for emoji sequence start */ - if (!priv->in_emoji_sequence && have_hex_mods && is_emoji_start) - { - priv->compose_buffer[0] = 0; - priv->in_emoji_sequence = TRUE; - priv->modifiers_dropped = FALSE; - priv->tentative_match = 0; - - g_signal_emit_by_name (context_simple, "preedit-start"); - g_signal_emit_by_name (context_simple, "preedit-changed"); - - return TRUE; - } - /* Then, check for compose sequences */ if (priv->in_hex_sequence) { @@ -1367,52 +1186,25 @@ gtk_im_context_simple_filter_keypress (GtkIMContext *context, gtk_im_context_simple_reset (context); return TRUE; } - else if (!is_end) + else if (!is_hex_end) { /* non-hex character in hex sequence */ beep_surface (surface); return TRUE; } } - else if (priv->in_emoji_sequence) - { - if (emoji_keyval) - priv->compose_buffer[n_compose++] = emoji_keyval; - else if (is_escape) - { - gtk_im_context_simple_reset (context); - return TRUE; - } - else - { - beep_surface (surface); - return TRUE; - } - } else priv->compose_buffer[n_compose++] = keyval; - if (n_compose == MAX(GTK_MAX_COMPOSE_LEN + 1, 9)) - { - beep_surface (surface); - priv->tentative_match = 0; - priv->in_hex_sequence = FALSE; - priv->in_emoji_sequence = FALSE; - priv->compose_buffer[0] = 0; - g_signal_emit_by_name (context_simple, "preedit-changed"); - - return TRUE; - } - priv->compose_buffer[n_compose] = 0; - if (priv->in_hex_sequence || priv->in_emoji_sequence) + if (priv->in_hex_sequence) { /* If the modifiers are still held down, consider the sequence again */ if (have_hex_mods) { /* space or return ends the sequence, and we eat the key */ - if (n_compose > 0 && is_end) + if (n_compose > 0 && is_hex_end) { if (priv->tentative_match && g_unichar_validate (priv->tentative_match)) @@ -1427,17 +1219,15 @@ gtk_im_context_simple_filter_keypress (GtkIMContext *context, priv->tentative_match = 0; priv->in_hex_sequence = FALSE; - priv->in_emoji_sequence = FALSE; priv->compose_buffer[0] = 0; } } - else if ((priv->in_hex_sequence && !check_hex (context_simple, n_compose)) || - (priv->in_emoji_sequence && !check_emoji (context_simple, n_compose))) + else if (!check_hex (context_simple, n_compose)) beep_surface (surface); g_signal_emit_by_name (context_simple, "preedit-changed"); - if (!priv->in_hex_sequence && !priv->in_emoji_sequence) + if (!priv->in_hex_sequence) g_signal_emit_by_name (context_simple, "preedit-end"); return TRUE; @@ -1558,10 +1348,9 @@ gtk_im_context_simple_reset (GtkIMContext *context) priv->compose_buffer[0] = 0; - if (priv->tentative_match || priv->in_hex_sequence || priv->in_emoji_sequence) + if (priv->tentative_match || priv->in_hex_sequence) { priv->in_hex_sequence = FALSE; - priv->in_emoji_sequence = FALSE; priv->tentative_match = 0; priv->tentative_match_len = 0; g_signal_emit_by_name (context_simple, "preedit-changed"); @@ -1580,11 +1369,11 @@ gtk_im_context_simple_get_preedit_string (GtkIMContext *context, char outbuf[37]; /* up to 6 hex digits */ int len = 0; - if (priv->in_hex_sequence || priv->in_emoji_sequence) + if (priv->in_hex_sequence) { int hexchars = 0; - outbuf[0] = priv->in_hex_sequence ? 'u' : 'e'; + outbuf[0] = 'u'; len = 1; while (priv->compose_buffer[hexchars] != 0)