[gtk+/wip/matthiasc/emoji-picker: 753/756] wip: emoji completion



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]