[gtk+/wip/matthiasc/emoji-picker: 753/756] wip: emoji completion
- From: Bastien Nocera <hadess src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gtk+/wip/matthiasc/emoji-picker: 753/756] wip: emoji completion
- Date: Wed, 18 Oct 2017 16:28:22 +0000 (UTC)
commit 3e9c62229a3b7a52338db69b8da9ca43a5629230
Author: Matthias Clasen <mclasen redhat com>
Date: Sat Aug 12 08:37:28 2017 -0400
wip: emoji completion
gtk/gtkemojicompletions.c | 192 ++++++++++++++++++++++++++++++++++++++++
gtk/gtkemojicompletions.h | 43 +++++++++
gtk/gtkentry.c | 91 +++++++++++++++++++
gtk/theme/Adwaita/_common.scss | 10 ++
gtk/ui/gtkemojicompletions.ui | 16 ++++
5 files changed, 352 insertions(+), 0 deletions(-)
---
diff --git a/gtk/gtkemojicompletions.c b/gtk/gtkemojicompletions.c
new file mode 100644
index 0000000..7b479fa
--- /dev/null
+++ b/gtk/gtkemojicompletions.c
@@ -0,0 +1,192 @@
+/* gtkemojicompletions.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 <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include "gtkemojicompletions.h"
+
+#include "gtkbox.h"
+#include "gtkcssprovider.h"
+#include "gtklistbox.h"
+#include "gtklabel.h"
+#include "gtkpopover.h"
+#include "gtkintl.h"
+#include "gtkprivate.h"
+
+struct _GtkEmojiCompletions
+{
+ GtkPopover parent_instance;
+
+ GtkWidget *list;
+
+ GVariant *data;
+};
+
+struct _GtkEmojiCompletionsClass {
+ GtkPopoverClass parent_class;
+};
+
+enum {
+ EMOJI_PICKED,
+ LAST_SIGNAL
+};
+
+static int signals[LAST_SIGNAL];
+
+G_DEFINE_TYPE (GtkEmojiCompletions, gtk_emoji_completions, GTK_TYPE_POPOVER)
+
+static void
+gtk_emoji_completions_finalize (GObject *object)
+{
+ //GtkEmojiCompletions *completions = GTK_EMOJI_COMPLETIONS (object);
+
+ //g_variant_unref (completions->data);
+
+ G_OBJECT_CLASS (gtk_emoji_completions_parent_class)->finalize (object);
+}
+
+static void
+emoji_activated (GtkListBox *list,
+ GtkListBoxRow *row,
+ gpointer data)
+{
+ GtkEmojiCompletions *completions = data;
+ const char *text;
+
+ gtk_popover_popdown (GTK_POPOVER (completions));
+
+ text = (const char *)g_object_get_data (G_OBJECT (row), "text");
+
+ g_signal_emit (data, signals[EMOJI_PICKED], 0, text);
+}
+
+static void
+add_emoji (GtkWidget *list,
+ GVariant *item)
+{
+ GtkWidget *child;
+ GtkWidget *label;
+ GtkWidget *box;
+ PangoAttrList *attrs;
+ GVariant *codes;
+ char text[64];
+ char *p = text;
+ int i;
+ char *tmp;
+
+ codes = g_variant_get_child_value (item, 0);
+ for (i = 0; i < g_variant_n_children (codes); i++)
+ {
+ gunichar code;
+
+ g_variant_get_child (codes, i, "u", &code);
+ if (code != 0)
+ p += g_unichar_to_utf8 (code, p);
+ }
+ p[0] = 0;
+
+ 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_list_box_row_new ();
+ box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
+ gtk_container_add (GTK_CONTAINER (child), box);
+ gtk_box_pack_start (GTK_BOX (box), label);
+
+ tmp = g_strdup_printf (":%s:", "grinning_face");
+ label = gtk_label_new (tmp);
+ g_free (tmp);
+ gtk_box_pack_start (GTK_BOX (box), label);
+
+ g_object_set_data_full (G_OBJECT (child), "text", g_strdup (text), g_free);
+ gtk_style_context_add_class (gtk_widget_get_style_context (child), "emoji-completion-row");
+ g_object_set_data_full (G_OBJECT (child), "text", g_strdup (text), g_free);
+
+ gtk_list_box_insert (GTK_LIST_BOX (list), child, -1);
+}
+
+static void
+populate_emoji_completions (GtkEmojiCompletions *completions)
+{
+ g_autoptr(GBytes) bytes = NULL;
+ GVariantIter iter;
+ GVariant *item;
+ int i;
+
+ bytes = g_resources_lookup_data ("/org/gtk/libgtk/emoji/emoji.data", 0, NULL);
+ completions->data = g_variant_ref_sink (g_variant_new_from_bytes (G_VARIANT_TYPE ("a(aus)"), bytes, TRUE));
+
+ g_variant_iter_init (&iter, completions->data);
+ i = 0;
+ while ((item = g_variant_iter_next_value (&iter)))
+ {
+ add_emoji (completions->list, item);
+ i++;
+ if (i == 5)
+ break;
+ }
+}
+
+static void
+gtk_emoji_completions_init (GtkEmojiCompletions *completions)
+{
+ gtk_widget_init_template (GTK_WIDGET (completions));
+
+ populate_emoji_completions (completions);
+}
+
+static void
+gtk_emoji_completions_class_init (GtkEmojiCompletionsClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ object_class->finalize = gtk_emoji_completions_finalize;
+
+ signals[EMOJI_PICKED] = g_signal_new ("emoji-picked",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ NULL,
+ G_TYPE_NONE, 1, G_TYPE_STRING|G_SIGNAL_TYPE_STATIC_SCOPE);
+
+ gtk_widget_class_set_template_from_resource (widget_class, "/org/gtk/libgtk/ui/gtkemojicompletions.ui");
+
+ gtk_widget_class_bind_template_child (widget_class, GtkEmojiCompletions, list);
+
+ gtk_widget_class_bind_template_callback (widget_class, emoji_activated);
+}
+
+GtkWidget *
+gtk_emoji_completions_new (GtkWidget *widget)
+{
+ return GTK_WIDGET (g_object_new (GTK_TYPE_EMOJI_COMPLETIONS,
+ "relative-to", widget,
+ NULL));
+}
+
+gboolean
+gtk_emoji_completions_show (GtkEmojiCompletions *completions,
+ const char *text)
+{
+ gtk_popover_popup (GTK_POPOVER (completions));
+ return TRUE;
+}
diff --git a/gtk/gtkemojicompletions.h b/gtk/gtkemojicompletions.h
new file mode 100644
index 0000000..d1eadba
--- /dev/null
+++ b/gtk/gtkemojicompletions.h
@@ -0,0 +1,43 @@
+/* gtkemojicompletions.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 <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#if !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION)
+#error "Only <gtk/gtk.h> can be included directly."
+#endif
+
+#include <gtk/gtkwidget.h>
+
+G_BEGIN_DECLS
+
+#define GTK_TYPE_EMOJI_COMPLETIONS (gtk_emoji_completions_get_type ())
+#define GTK_EMOJI_COMPLETIONS(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj),
GTK_TYPE_EMOJI_COMPLETIONS, GtkEmojiCompletions))
+#define GTK_EMOJI_COMPLETIONS_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass),
GTK_TYPE_EMOJI_COMPLETIONS, GtkEmojiCompletionsClass))
+#define GTK_IS_EMOJI_COMPLETIONS(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj),
GTK_TYPE_EMOJI_COMPLETIONS))
+#define GTK_IS_EMOJI_COMPLETIONS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass),
GTK_TYPE_EMOJI_COMPLETIONS))
+#define GTK_EMOJI_COMPLETIONS_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj),
GTK_TYPE_EMOJI_COMPLETIONS, GtkEmojiCompletionsClass))
+
+typedef struct _GtkEmojiCompletions GtkEmojiCompletions;
+typedef struct _GtkEmojiCompletionsClass GtkEmojiCompletionsClass;
+
+GType gtk_emoji_completions_get_type (void) G_GNUC_CONST;
+GtkWidget *gtk_emoji_completions_new (GtkWidget *widget);
+gboolean gtk_emoji_completions_show (GtkEmojiCompletions *completions,
+ const char *text);
+
+G_END_DECLS
diff --git a/gtk/gtkentry.c b/gtk/gtkentry.c
index 002f5a4..c4884ea 100644
--- a/gtk/gtkentry.c
+++ b/gtk/gtkentry.c
@@ -69,6 +69,7 @@
#include "gtkcssnodeprivate.h"
#include "gtkimageprivate.h"
#include "gtkemojichooser.h"
+#include "gtkemojicompletions.h"
#include "a11y/gtkentryaccessible.h"
@@ -655,6 +656,7 @@ 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 check_emoji_completion (GtkEntry *entry);
static void gtk_entry_measure (GtkWidget *widget,
GtkOrientation orientation,
@@ -5081,6 +5083,7 @@ gtk_entry_backspace (GtkEntry *entry)
}
gtk_entry_pend_cursor_blink (entry);
+ check_emoji_completion (entry);
}
static void
@@ -5360,6 +5363,7 @@ gtk_entry_enter_text (GtkEntry *entry,
gtk_editable_set_position (editable, tmp_pos);
priv->need_im_reset = old_need_im_reset;
+ check_emoji_completion (entry);
}
/* All changes to priv->current_pos and priv->selection_bound
@@ -9961,3 +9965,90 @@ 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
+dismiss_emoji_completions (GtkEntry *entry)
+{
+ GtkWidget *popup;
+
+g_print ("dismiss emoji completions\n");
+ popup = GTK_WIDGET (g_object_get_data (G_OBJECT (entry), "emoji-completion-popup"));
+ if (popup)
+ gtk_popover_popdown (GTK_POPOVER (popup));
+}
+
+static void
+emoji_picked (GtkEmojiCompletions *completions,
+ const char *emoji_text,
+ gpointer data)
+{
+ GtkEntry *entry = data;
+ const char *text;
+ const char *completion_text;
+ guint length;
+
+ text = gtk_entry_buffer_get_text (get_buffer (entry));
+ length = gtk_entry_buffer_get_length (get_buffer (entry));
+
+ completion_text = g_object_get_data (entry, "emoji-completion-text");
+ gtk_entry_set_positions (entry, length - strlen (completion_text), length);
+ gtk_entry_enter_text (entry, emoji_text);
+}
+
+static void
+update_emoji_completions (GtkEntry *entry,
+ const char *text)
+{
+ GtkWidget *popup;
+
+g_print ("update emoji completions\n");
+
+ g_object_set_data_full (G_OBJECT (entry), "emoji-completion-text", g_strdup (text), g_free);
+
+ popup = GTK_WIDGET (g_object_get_data (G_OBJECT (entry), "emoji-completion-popup"));
+ if (popup == NULL)
+ {
+ popup = gtk_emoji_completions_new (entry);
+ g_object_set_data_full (G_OBJECT (entry), "emoji-completion-popup", popup,
(GDestroyNotify)gtk_widget_destroy);
+ g_signal_connect (popup, "emoji-picked", G_CALLBACK (emoji_picked), entry);
+ }
+
+ gtk_emoji_completions_show (GTK_EMOJI_COMPLETIONS (popup), text);
+}
+
+static void
+check_emoji_completion (GtkEntry *entry)
+{
+ const char *text;
+ guint length;
+ const char *p;
+
+ text = gtk_entry_buffer_get_text (get_buffer (entry));
+ length = gtk_entry_buffer_get_length (get_buffer (entry));
+
+ if (length > 0)
+ {
+ gboolean found_candidate = FALSE;
+
+ p = text + length;
+ do
+ {
+ p = g_utf8_prev_char (p);
+ if (*p == ':')
+ {
+ 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)
+ {
+ update_emoji_completions (entry, p);
+ return;
+ }
+ }
+
+ dismiss_emoji_completions (entry);
+}
diff --git a/gtk/theme/Adwaita/_common.scss b/gtk/theme/Adwaita/_common.scss
index a34833c..e423aee 100644
--- a/gtk/theme/Adwaita/_common.scss
+++ b/gtk/theme/Adwaita/_common.scss
@@ -4469,3 +4469,13 @@ button.emoji-section {
background: $selected_bg_color;
}
}
+
+popover.emoji-completions arrow {
+ border: none;
+ background: none;
+}
+
+popover.emoji-completions contents row box {
+ border-spacing: 10px;
+ padding: 2px 10px;
+}
diff --git a/gtk/ui/gtkemojicompletions.ui b/gtk/ui/gtkemojicompletions.ui
new file mode 100644
index 0000000..0af8c47
--- /dev/null
+++ b/gtk/ui/gtkemojicompletions.ui
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface domain="gtk40">
+ <template class="GtkEmojiCompletions" parent="GtkPopover">
+ <property name="modal">0</property>
+ <style>
+ <class name="emoji-completions"/>
+ </style>
+ <child>
+ <object class="GtkListBox" id="list">
+ <property name="selection-mode">none</property>
+ <property name="activate-on-single-click">1</property>
+ <signal name="row-activated" handler="emoji_activated"/>
+ </object>
+ </child>
+ </template>
+</interface>
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]