| /* |
| * Copyright (C) 2019 Igalia S.L. |
| * Copyright (C) 2017 Red Hat, Inc. |
| * |
| * This library is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Library 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 |
| * Library General Public License for more details. |
| * |
| * You should have received a copy of the GNU Library General Public License |
| * along with this library; see the file COPYING.LIB. If not, write to |
| * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, |
| * Boston, MA 02110-1301, USA. |
| */ |
| |
| // GtkEmojiChooser is private in GTK 3, so this is based in the GTK code, just adapted to |
| // WebKit coding style, using some internal types from WTF to simplify the implementation |
| // and not using GtkBuilder for the UI. |
| |
| #include "config.h" |
| #include "WebKitEmojiChooser.h" |
| |
| #if GTK_CHECK_VERSION(3, 24, 0) |
| |
| #include <glib/gi18n-lib.h> |
| #include <wtf/HashSet.h> |
| #include <wtf/Vector.h> |
| #include <wtf/glib/GRefPtr.h> |
| #include <wtf/glib/GUniquePtr.h> |
| #include <wtf/glib/WTFGType.h> |
| #include <wtf/text/CString.h> |
| |
| enum { |
| EMOJI_PICKED, |
| |
| LAST_SIGNAL |
| }; |
| |
| struct EmojiSection { |
| GtkWidget* heading { nullptr }; |
| GtkWidget* box { nullptr }; |
| GtkWidget* button { nullptr }; |
| bool isEmpty { false }; |
| }; |
| |
| using SectionList = Vector<EmojiSection, 9>; |
| |
| struct _WebKitEmojiChooserPrivate { |
| GtkWidget* stack; |
| GtkWidget* swindow; |
| GtkWidget* searchEntry; |
| SectionList sections; |
| GRefPtr<GSettings> settings; |
| HashSet<GRefPtr<GtkGesture>> gestures; |
| int emojiMaxWidth; |
| }; |
| |
| static guint signals[LAST_SIGNAL] = { 0, }; |
| |
| WEBKIT_DEFINE_TYPE(WebKitEmojiChooser, webkit_emoji_chooser, GTK_TYPE_POPOVER) |
| |
| static void emojiPopupMenu(GtkWidget*, WebKitEmojiChooser*); |
| |
| static const unsigned boxSpace = 6; |
| |
| static void emojiHovered(GtkWidget* widget, GdkEvent* event) |
| { |
| if (event->type == GDK_ENTER_NOTIFY) |
| gtk_widget_set_state_flags(widget, GTK_STATE_FLAG_PRELIGHT, FALSE); |
| else |
| gtk_widget_unset_state_flags(widget, GTK_STATE_FLAG_PRELIGHT); |
| } |
| |
| static GtkWidget* webkitEmojiChooserAddEmoji(WebKitEmojiChooser* chooser, GtkFlowBox* parent, GVariant* item, bool prepend = false, gunichar modifier = 0) |
| { |
| char text[64]; |
| char* textPtr = text; |
| GRefPtr<GVariant> codes = adoptGRef(g_variant_get_child_value(item, 0)); |
| for (unsigned i = 0; i < g_variant_n_children(codes.get()); ++i) { |
| gunichar code; |
| g_variant_get_child(codes.get(), i, "u", &code); |
| if (!code) |
| code = modifier; |
| if (code) |
| textPtr += g_unichar_to_utf8(code, textPtr); |
| } |
| // U+FE0F is the Emoji variation selector |
| textPtr += g_unichar_to_utf8(0xFE0F, textPtr); |
| textPtr[0] = '\0'; |
| |
| GtkWidget* label = gtk_label_new(text); |
| PangoAttrList* attributes = pango_attr_list_new(); |
| pango_attr_list_insert(attributes, pango_attr_scale_new(PANGO_SCALE_X_LARGE)); |
| gtk_label_set_attributes(GTK_LABEL(label), attributes); |
| pango_attr_list_unref(attributes); |
| |
| PangoLayout* layout = gtk_label_get_layout(GTK_LABEL(label)); |
| PangoRectangle rect; |
| pango_layout_get_extents(layout, &rect, nullptr); |
| // Check for fallback rendering that generates too wide items. |
| if (pango_layout_get_unknown_glyphs_count(layout) || rect.width >= 1.5 * chooser->priv->emojiMaxWidth) { |
| gtk_widget_destroy(label); |
| return nullptr; |
| } |
| |
| GtkWidget* 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), "emoji-data", g_variant_ref(item), reinterpret_cast<GDestroyNotify>(g_variant_unref)); |
| if (modifier) |
| g_object_set_data(G_OBJECT(child), "modifier", GUINT_TO_POINTER(modifier)); |
| |
| GtkWidget* eventBox = gtk_event_box_new(); |
| gtk_widget_add_events(eventBox, GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK); |
| g_signal_connect(eventBox, "enter-notify-event", G_CALLBACK(emojiHovered), nullptr); |
| g_signal_connect(eventBox, "leave-notify-event", G_CALLBACK(emojiHovered), nullptr); |
| gtk_container_add(GTK_CONTAINER(eventBox), label); |
| gtk_widget_show(label); |
| |
| gtk_container_add(GTK_CONTAINER(child), eventBox); |
| gtk_widget_show(eventBox); |
| |
| gtk_flow_box_insert(parent, child, prepend ? 0 : -1); |
| gtk_widget_show(child); |
| |
| return child; |
| } |
| |
| static void webkitEmojiChooserAddRecentItem(WebKitEmojiChooser* chooser, GVariant* item, gunichar modifier) |
| { |
| GRefPtr<GVariant> protectItem(item); |
| GVariantBuilder builder; |
| g_variant_builder_init(&builder, G_VARIANT_TYPE("a((auss)u)")); |
| g_variant_builder_add(&builder, "(@(auss)u)", item, modifier); |
| |
| auto& section = chooser->priv->sections.first(); |
| |
| static const unsigned maxRecentItems = 7 * 3; |
| |
| GUniquePtr<GList> children(gtk_container_get_children(GTK_CONTAINER(section.box))); |
| unsigned i = 1; |
| for (auto* l = children.get(); l; l = g_list_next(l), ++i) { |
| auto* item2 = static_cast<GVariant*>(g_object_get_data(G_OBJECT(l->data), "emoji-data")); |
| auto modifier2 = static_cast<gunichar>(GPOINTER_TO_UINT(g_object_get_data(G_OBJECT(l->data), "modifier"))); |
| if (modifier == modifier2 && g_variant_equal(item, item2)) { |
| gtk_widget_destroy(GTK_WIDGET(l->data)); |
| --i; |
| continue; |
| } |
| |
| if (i >= maxRecentItems) { |
| gtk_widget_destroy(GTK_WIDGET(l->data)); |
| continue; |
| } |
| |
| g_variant_builder_add(&builder, "(@(auss)u)", item2, modifier2); |
| } |
| |
| auto* child = webkitEmojiChooserAddEmoji(chooser, GTK_FLOW_BOX(section.box), item, true, modifier); |
| if (child) |
| g_signal_connect(child, "popup-menu", G_CALLBACK(emojiPopupMenu), chooser); |
| |
| gtk_widget_show(section.box); |
| gtk_widget_set_sensitive(section.button, TRUE); |
| |
| g_settings_set_value(chooser->priv->settings.get(), "recent-emoji", g_variant_builder_end(&builder)); |
| } |
| |
| static void emojiActivated(GtkFlowBox* box, GtkFlowBoxChild* child, WebKitEmojiChooser* chooser) |
| { |
| GtkWidget* label = gtk_bin_get_child(GTK_BIN(gtk_bin_get_child(GTK_BIN(child)))); |
| GUniquePtr<char> text(g_strdup(gtk_label_get_label(GTK_LABEL(label)))); |
| |
| auto* item = static_cast<GVariant*>(g_object_get_data(G_OBJECT(child), "emoji-data")); |
| auto modifier = static_cast<gunichar>(GPOINTER_TO_UINT(g_object_get_data(G_OBJECT(child), "modifier"))); |
| webkitEmojiChooserAddRecentItem(chooser, item, modifier); |
| g_signal_emit(chooser, signals[EMOJI_PICKED], 0, text.get()); |
| |
| gtk_popover_popdown(GTK_POPOVER(chooser)); |
| } |
| |
| static bool emojiDataHasVariations(GVariant* emojiData) |
| { |
| GRefPtr<GVariant> codes = adoptGRef(g_variant_get_child_value(emojiData, 0)); |
| for (size_t i = 0; i < g_variant_n_children(codes.get()); ++i) { |
| gunichar code; |
| g_variant_get_child(codes.get(), i, "u", &code); |
| if (!code) |
| return true; |
| } |
| return false; |
| } |
| |
| static void webkitEmojiChooserShowVariations(WebKitEmojiChooser* chooser, GtkWidget* child) |
| { |
| if (!child) |
| return; |
| |
| auto* emojiData = static_cast<GVariant*>(g_object_get_data(G_OBJECT(child), "emoji-data")); |
| if (!emojiData) |
| return; |
| |
| if (!emojiDataHasVariations(emojiData)) |
| return; |
| |
| GtkWidget* popover = gtk_popover_new(child); |
| GtkWidget* view = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); |
| gtk_style_context_add_class(gtk_widget_get_style_context(view), "view"); |
| GtkWidget* box = gtk_flow_box_new(); |
| g_signal_connect(box, "child-activated", G_CALLBACK(emojiActivated), chooser); |
| gtk_flow_box_set_homogeneous(GTK_FLOW_BOX(box), TRUE); |
| gtk_flow_box_set_min_children_per_line(GTK_FLOW_BOX(box), 6); |
| gtk_flow_box_set_max_children_per_line(GTK_FLOW_BOX(box), 6); |
| 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); |
| gtk_container_add(GTK_CONTAINER(view), box); |
| gtk_widget_show(box); |
| gtk_container_add(GTK_CONTAINER(popover), view); |
| gtk_widget_show(view); |
| |
| webkitEmojiChooserAddEmoji(chooser, GTK_FLOW_BOX(box), emojiData); |
| for (gunichar modifier = 0x1F3FB; modifier <= 0x1F3FF; ++modifier) |
| webkitEmojiChooserAddEmoji(chooser, GTK_FLOW_BOX(box), emojiData, false, modifier); |
| |
| gtk_popover_popup(GTK_POPOVER(popover)); |
| } |
| |
| static void emojiLongPressed(GtkGesture* gesture, double x, double y, WebKitEmojiChooser* chooser) |
| { |
| auto* box = GTK_FLOW_BOX(gtk_event_controller_get_widget(GTK_EVENT_CONTROLLER(gesture))); |
| webkitEmojiChooserShowVariations(chooser, GTK_WIDGET(gtk_flow_box_get_child_at_pos(box, x, y))); |
| } |
| |
| static void emojiPressed(GtkGesture* gesture, int, double x, double y, WebKitEmojiChooser* chooser) |
| { |
| emojiLongPressed(gesture, x, y, chooser); |
| } |
| |
| static void emojiPopupMenu(GtkWidget* child, WebKitEmojiChooser* chooser) |
| { |
| webkitEmojiChooserShowVariations(chooser, child); |
| } |
| |
| static void verticalAdjustmentChanged(GtkAdjustment* adjustment, WebKitEmojiChooser* chooser) |
| { |
| double value = gtk_adjustment_get_value(adjustment); |
| EmojiSection* sectionToSelect = nullptr; |
| for (auto& section : chooser->priv->sections) { |
| GtkAllocation allocation; |
| if (section.heading) |
| gtk_widget_get_allocation(section.heading, &allocation); |
| else |
| gtk_widget_get_allocation(section.box, &allocation); |
| |
| if (value < allocation.y - boxSpace) |
| break; |
| |
| sectionToSelect = §ion; |
| } |
| |
| if (!sectionToSelect) |
| sectionToSelect = &chooser->priv->sections[0]; |
| |
| for (auto& section : chooser->priv->sections) { |
| if (§ion == sectionToSelect) |
| gtk_widget_set_state_flags(section.button, GTK_STATE_FLAG_CHECKED, FALSE); |
| else |
| gtk_widget_unset_state_flags(section.button, GTK_STATE_FLAG_CHECKED); |
| } |
| } |
| |
| static GtkWidget* webkitEmojiChooserSetupSectionBox(WebKitEmojiChooser* chooser, GtkBox* parent, const char* title, GtkAdjustment* adjustment, gboolean canHaveVariations = FALSE) |
| { |
| EmojiSection section; |
| if (title) { |
| GtkWidget* label = gtk_label_new(title); |
| section.heading = label; |
| gtk_label_set_xalign(GTK_LABEL(label), 0); |
| gtk_box_pack_start(parent, label, FALSE, FALSE, 0); |
| gtk_widget_show(label); |
| } |
| |
| GtkWidget* box = gtk_flow_box_new(); |
| section.box = box; |
| g_signal_connect(box, "child-activated", G_CALLBACK(emojiActivated), chooser); |
| gtk_flow_box_set_homogeneous(GTK_FLOW_BOX(box), TRUE); |
| gtk_flow_box_set_selection_mode(GTK_FLOW_BOX(box), GTK_SELECTION_NONE); |
| gtk_container_set_focus_vadjustment(GTK_CONTAINER(box), adjustment); |
| gtk_box_pack_start(parent, box, FALSE, FALSE, 0); |
| gtk_widget_show(box); |
| |
| if (canHaveVariations) { |
| GRefPtr<GtkGesture> gesture = adoptGRef(gtk_gesture_long_press_new(box)); |
| g_signal_connect(gesture.get(), "pressed", G_CALLBACK(emojiLongPressed), chooser); |
| chooser->priv->gestures.add(WTFMove(gesture)); |
| GRefPtr<GtkGesture> multiGesture = adoptGRef(gtk_gesture_multi_press_new(box)); |
| gtk_gesture_single_set_button(GTK_GESTURE_SINGLE(multiGesture.get()), GDK_BUTTON_SECONDARY); |
| g_signal_connect(multiGesture.get(), "pressed", G_CALLBACK(emojiPressed), chooser); |
| chooser->priv->gestures.add(WTFMove(multiGesture)); |
| } |
| |
| chooser->priv->sections.append(WTFMove(section)); |
| return box; |
| } |
| |
| static void scrollToSection(GtkButton* button, gpointer data) |
| { |
| auto* chooser = WEBKIT_EMOJI_CHOOSER(gtk_widget_get_ancestor(GTK_WIDGET(button), WEBKIT_TYPE_EMOJI_CHOOSER)); |
| auto& section = chooser->priv->sections[GPOINTER_TO_UINT(data)]; |
| GtkAdjustment* adjustment = gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(chooser->priv->swindow)); |
| if (section.heading) { |
| GtkAllocation allocation = { 0, 0, 0, 0 }; |
| gtk_widget_get_allocation(section.heading, &allocation); |
| gtk_adjustment_set_value(adjustment, allocation.y - boxSpace); |
| } else |
| gtk_adjustment_set_value(adjustment, 0); |
| } |
| |
| static void webkitEmojiChooserSetupSectionButton(WebKitEmojiChooser* chooser, GtkBox* parent, const char* iconName, const char* tooltip) |
| { |
| GtkWidget* button = gtk_button_new_from_icon_name(iconName, GTK_ICON_SIZE_BUTTON); |
| chooser->priv->sections.last().button = button; |
| gtk_style_context_add_class(gtk_widget_get_style_context(button), "emoji-section"); |
| gtk_widget_set_tooltip_text(button, tooltip); |
| g_signal_connect(button, "clicked", G_CALLBACK(scrollToSection), GUINT_TO_POINTER(chooser->priv->sections.size() - 1)); |
| gtk_box_pack_start(parent, button, FALSE, FALSE, 0); |
| gtk_widget_show(button); |
| } |
| |
| static void webkitEmojiChooserSetupRecent(WebKitEmojiChooser* chooser, GtkBox* emojiBox, GtkBox* buttonBox, GtkAdjustment* adjustment) |
| { |
| GtkWidget* flowBox = webkitEmojiChooserSetupSectionBox(chooser, emojiBox, nullptr, adjustment, true); |
| webkitEmojiChooserSetupSectionButton(chooser, buttonBox, "emoji-recent-symbolic", _("Recent")); |
| |
| bool isEmpty = true; |
| GRefPtr<GVariant> variant = adoptGRef(g_settings_get_value(chooser->priv->settings.get(), "recent-emoji")); |
| GVariantIter iter; |
| g_variant_iter_init(&iter, variant.get()); |
| while (GRefPtr<GVariant> item = adoptGRef(g_variant_iter_next_value(&iter))) { |
| GRefPtr<GVariant> emojiData = adoptGRef(g_variant_get_child_value(item.get(), 0)); |
| gunichar modifier; |
| g_variant_get_child(item.get(), 1, "u", &modifier); |
| if (auto* child = webkitEmojiChooserAddEmoji(chooser, GTK_FLOW_BOX(flowBox), emojiData.get(), true, modifier)) |
| g_signal_connect(child, "popup-menu", G_CALLBACK(emojiPopupMenu), chooser); |
| isEmpty = false; |
| } |
| |
| if (isEmpty) { |
| gtk_widget_hide(flowBox); |
| gtk_widget_set_sensitive(chooser->priv->sections.first().button, FALSE); |
| } |
| } |
| |
| static void webkitEmojiChooserEnsureEmptyResult(WebKitEmojiChooser* chooser) |
| { |
| if (gtk_stack_get_child_by_name(GTK_STACK(chooser->priv->stack), "empty")) |
| return; |
| |
| GtkWidget* grid = gtk_grid_new(); |
| gtk_grid_set_row_spacing(GTK_GRID(grid), 12); |
| gtk_widget_set_halign(grid, GTK_ALIGN_CENTER); |
| gtk_widget_set_valign(grid, GTK_ALIGN_CENTER); |
| gtk_style_context_add_class(gtk_widget_get_style_context(grid), "dim-label"); |
| |
| GtkWidget* image = gtk_image_new_from_icon_name("edit-find-symbolic", GTK_ICON_SIZE_DIALOG); |
| gtk_image_set_pixel_size(GTK_IMAGE(image), 72); |
| gtk_style_context_add_class(gtk_widget_get_style_context(image), "dim-label"); |
| gtk_grid_attach(GTK_GRID(grid), image, 0, 0, 1, 1); |
| gtk_widget_show(image); |
| |
| GtkWidget* label = gtk_label_new(_("No Results Found")); |
| PangoAttrList* attributes = pango_attr_list_new(); |
| pango_attr_list_insert(attributes, pango_attr_scale_new(1.44)); |
| pango_attr_list_insert(attributes, pango_attr_weight_new(PANGO_WEIGHT_BOLD)); |
| gtk_label_set_attributes(GTK_LABEL(label), attributes); |
| pango_attr_list_unref(attributes); |
| gtk_grid_attach(GTK_GRID(grid), label, 0, 1, 1, 1); |
| gtk_widget_show(label); |
| |
| label = gtk_label_new(_("Try a different search")); |
| gtk_style_context_add_class(gtk_widget_get_style_context(label), "dim-label"); |
| gtk_grid_attach(GTK_GRID(grid), label, 0, 2, 1, 1); |
| gtk_widget_show(label); |
| |
| gtk_stack_add_named(GTK_STACK(chooser->priv->stack), grid, "empty"); |
| gtk_widget_show(grid); |
| } |
| |
| static void webkitEmojiChooserSearchChanged(WebKitEmojiChooser* chooser) |
| { |
| for (auto& section : chooser->priv->sections) { |
| section.isEmpty = true; |
| gtk_flow_box_invalidate_filter(GTK_FLOW_BOX(section.box)); |
| } |
| |
| bool resultsFound = false; |
| for (auto& section : chooser->priv->sections) { |
| if (section.heading) { |
| gtk_widget_set_visible(section.heading, !section.isEmpty); |
| gtk_widget_set_visible(section.box, !section.isEmpty); |
| } |
| resultsFound = resultsFound || !section.isEmpty; |
| } |
| |
| if (!resultsFound) { |
| webkitEmojiChooserEnsureEmptyResult(chooser); |
| gtk_stack_set_visible_child_name(GTK_STACK(chooser->priv->stack), "empty"); |
| } else |
| gtk_stack_set_visible_child_name(GTK_STACK(chooser->priv->stack), "list"); |
| } |
| |
| static void webkitEmojiChooserSetupFilters(WebKitEmojiChooser* chooser) |
| { |
| for (size_t i = 0; i < chooser->priv->sections.size(); ++i) { |
| gtk_flow_box_set_filter_func(GTK_FLOW_BOX(chooser->priv->sections[i].box), [](GtkFlowBoxChild* child, gpointer userData) -> gboolean { |
| auto* chooser = WEBKIT_EMOJI_CHOOSER(gtk_widget_get_ancestor(GTK_WIDGET(child), WEBKIT_TYPE_EMOJI_CHOOSER)); |
| auto& section = chooser->priv->sections[GPOINTER_TO_UINT(userData)]; |
| const char* text = gtk_entry_get_text(GTK_ENTRY(chooser->priv->searchEntry)); |
| if (!text || !*text) { |
| section.isEmpty = false; |
| return TRUE; |
| } |
| |
| auto* emojiData = static_cast<GVariant*>(g_object_get_data(G_OBJECT(child), "emoji-data")); |
| if (!emojiData) { |
| section.isEmpty = false; |
| return TRUE; |
| } |
| |
| const char* name; |
| g_variant_get_child(emojiData, 1, "&s", &name); |
| if (g_str_match_string(text, name, TRUE)) { |
| section.isEmpty = false; |
| return TRUE; |
| } |
| |
| return FALSE; |
| }, GUINT_TO_POINTER(i), nullptr); |
| } |
| } |
| |
| static void webkitEmojiChooserInitializeEmojiMaxWidth(WebKitEmojiChooser* chooser) |
| { |
| // Get a reasonable maximum width for an emoji. We do this to skip overly wide fallback |
| // rendering for certain emojis the font does not contain and therefore end up being |
| // rendered as multiple glyphs. |
| GRefPtr<PangoLayout> layout = adoptGRef(gtk_widget_create_pango_layout(GTK_WIDGET(chooser), "🙂")); |
| auto* attributes = pango_attr_list_new(); |
| pango_attr_list_insert(attributes, pango_attr_scale_new(PANGO_SCALE_X_LARGE)); |
| pango_layout_set_attributes(layout.get(), attributes); |
| pango_attr_list_unref(attributes); |
| |
| PangoRectangle rect; |
| pango_layout_get_extents(layout.get(), &rect, nullptr); |
| chooser->priv->emojiMaxWidth = rect.width; |
| } |
| |
| static void webkitEmojiChooserConstructed(GObject* object) |
| { |
| WebKitEmojiChooser* chooser = WEBKIT_EMOJI_CHOOSER(object); |
| chooser->priv->settings = adoptGRef(g_settings_new("org.gtk.Settings.EmojiChooser")); |
| |
| G_OBJECT_CLASS(webkit_emoji_chooser_parent_class)->constructed(object); |
| |
| webkitEmojiChooserInitializeEmojiMaxWidth(chooser); |
| |
| gtk_style_context_add_class(gtk_widget_get_style_context(GTK_WIDGET(object)), "emoji-picker"); |
| |
| GtkWidget* mainBox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0); |
| GtkWidget* searchEntry = gtk_search_entry_new(); |
| chooser->priv->searchEntry = searchEntry; |
| g_signal_connect_swapped(searchEntry, "search-changed", G_CALLBACK(webkitEmojiChooserSearchChanged), chooser); |
| gtk_entry_set_input_hints(GTK_ENTRY(searchEntry), GTK_INPUT_HINT_NO_EMOJI); |
| gtk_box_pack_start(GTK_BOX(mainBox), searchEntry, TRUE, FALSE, 0); |
| gtk_widget_show(searchEntry); |
| |
| GtkWidget* stack = gtk_stack_new(); |
| chooser->priv->stack = stack; |
| GtkWidget* box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0); |
| GtkWidget* swindow = gtk_scrolled_window_new(nullptr, nullptr); |
| chooser->priv->swindow = swindow; |
| gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(swindow), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC); |
| gtk_scrolled_window_set_min_content_height(GTK_SCROLLED_WINDOW(swindow), 250); |
| gtk_style_context_add_class(gtk_widget_get_style_context(swindow), "view"); |
| gtk_box_pack_start(GTK_BOX(box), swindow, TRUE, TRUE, 0); |
| gtk_widget_show(swindow); |
| |
| GtkWidget* emojiBox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 6); |
| g_object_set(emojiBox, "margin", 6, nullptr); |
| gtk_container_add(GTK_CONTAINER(swindow), emojiBox); |
| gtk_widget_show(emojiBox); |
| |
| GtkWidget* buttonBox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); |
| gtk_box_pack_start(GTK_BOX(box), buttonBox, TRUE, FALSE, 0); |
| gtk_widget_show(buttonBox); |
| |
| GtkAdjustment* vAdjustment = gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(swindow)); |
| g_signal_connect(vAdjustment, "value-changed", G_CALLBACK(verticalAdjustmentChanged), chooser); |
| |
| webkitEmojiChooserSetupRecent(chooser, GTK_BOX(emojiBox), GTK_BOX(buttonBox), vAdjustment); |
| |
| GRefPtr<GBytes> bytes = adoptGRef(g_resources_lookup_data("/org/gtk/libgtk/emoji/emoji.data", G_RESOURCE_LOOKUP_FLAGS_NONE, nullptr)); |
| GRefPtr<GVariant> data = g_variant_new_from_bytes(G_VARIANT_TYPE("a(auss)"), bytes.get(), TRUE); |
| GVariantIter iter; |
| g_variant_iter_init(&iter, data.get()); |
| GtkWidget* flowBox = nullptr; |
| while (GRefPtr<GVariant> item = adoptGRef(g_variant_iter_next_value(&iter))) { |
| const char* name; |
| g_variant_get_child(item.get(), 1, "&s", &name); |
| |
| if (!g_strcmp0(name, "grinning face")) { |
| const char* title = _("Smileys & People"); |
| flowBox = webkitEmojiChooserSetupSectionBox(chooser, GTK_BOX(emojiBox), title, vAdjustment, true); |
| webkitEmojiChooserSetupSectionButton(chooser, GTK_BOX(buttonBox), "emoji-people-symbolic", title); |
| } else if (!g_strcmp0(name, "selfie")) { |
| const char* title = _("Body & Clothing"); |
| flowBox = webkitEmojiChooserSetupSectionBox(chooser, GTK_BOX(emojiBox), title, vAdjustment, true); |
| webkitEmojiChooserSetupSectionButton(chooser, GTK_BOX(buttonBox), "emoji-body-symbolic", title); |
| } else if (!g_strcmp0(name, "monkey")) { |
| const char* title = _("Animals & Nature"); |
| flowBox = webkitEmojiChooserSetupSectionBox(chooser, GTK_BOX(emojiBox), title, vAdjustment); |
| webkitEmojiChooserSetupSectionButton(chooser, GTK_BOX(buttonBox), "emoji-nature-symbolic", title); |
| } else if (!g_strcmp0(name, "grapes")) { |
| const char* title = _("Food & Drink"); |
| flowBox = webkitEmojiChooserSetupSectionBox(chooser, GTK_BOX(emojiBox), title, vAdjustment); |
| webkitEmojiChooserSetupSectionButton(chooser, GTK_BOX(buttonBox), "emoji-food-symbolic", title); |
| } else if (!g_strcmp0(name, "globe showing Europe-Africa")) { |
| const char* title = _("Travel & Places"); |
| flowBox = webkitEmojiChooserSetupSectionBox(chooser, GTK_BOX(emojiBox), title, vAdjustment); |
| webkitEmojiChooserSetupSectionButton(chooser, GTK_BOX(buttonBox), "emoji-travel-symbolic", title); |
| } else if (!g_strcmp0(name, "jack-o-lantern")) { |
| const char* title = _("Activities"); |
| flowBox = webkitEmojiChooserSetupSectionBox(chooser, GTK_BOX(emojiBox), title, vAdjustment); |
| webkitEmojiChooserSetupSectionButton(chooser, GTK_BOX(buttonBox), "emoji-activities-symbolic", title); |
| } else if (!g_strcmp0(name, "muted speaker")) { |
| const char* title = _("Objects"); |
| flowBox = webkitEmojiChooserSetupSectionBox(chooser, GTK_BOX(emojiBox), title, vAdjustment); |
| webkitEmojiChooserSetupSectionButton(chooser, GTK_BOX(buttonBox), "emoji-objects-symbolic", title); |
| } else if (!g_strcmp0(name, "ATM sign")) { |
| const char* title = _("Symbols"); |
| flowBox = webkitEmojiChooserSetupSectionBox(chooser, GTK_BOX(emojiBox), title, vAdjustment); |
| webkitEmojiChooserSetupSectionButton(chooser, GTK_BOX(buttonBox), "emoji-symbols-symbolic", title); |
| } else if (!g_strcmp0(name, "chequered flag")) { |
| const char* title = _("Flags"); |
| flowBox = webkitEmojiChooserSetupSectionBox(chooser, GTK_BOX(emojiBox), title, vAdjustment); |
| webkitEmojiChooserSetupSectionButton(chooser, GTK_BOX(buttonBox), "emoji-flags-symbolic", title); |
| } |
| auto* child = webkitEmojiChooserAddEmoji(chooser, GTK_FLOW_BOX(flowBox), item.get()); |
| if (child) |
| g_signal_connect(child, "popup-menu", G_CALLBACK(emojiPopupMenu), chooser); |
| } |
| |
| gtk_widget_set_state_flags(chooser->priv->sections.first().button, GTK_STATE_FLAG_CHECKED, FALSE); |
| |
| gtk_stack_add_named(GTK_STACK(stack), box, "list"); |
| gtk_widget_show(box); |
| |
| gtk_box_pack_start(GTK_BOX(mainBox), stack, TRUE, TRUE, 0); |
| gtk_widget_show(stack); |
| |
| gtk_container_add(GTK_CONTAINER(object), mainBox); |
| gtk_widget_show(mainBox); |
| |
| webkitEmojiChooserSetupFilters(chooser); |
| } |
| |
| static void webkitEmojiChooserShow(GtkWidget* widget) |
| { |
| GTK_WIDGET_CLASS(webkit_emoji_chooser_parent_class)->show(widget); |
| |
| WebKitEmojiChooser* chooser = WEBKIT_EMOJI_CHOOSER(widget); |
| auto* adjustment = gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(chooser->priv->swindow)); |
| gtk_adjustment_set_value(adjustment, 0); |
| |
| gtk_entry_set_text(GTK_ENTRY(chooser->priv->searchEntry), ""); |
| } |
| |
| static void webkit_emoji_chooser_class_init(WebKitEmojiChooserClass* klass) |
| { |
| GObjectClass* objectClass = G_OBJECT_CLASS(klass); |
| objectClass->constructed = webkitEmojiChooserConstructed; |
| |
| GtkWidgetClass* widgetClass = GTK_WIDGET_CLASS(klass); |
| widgetClass->show = webkitEmojiChooserShow; |
| |
| signals[EMOJI_PICKED] = g_signal_new( |
| "emoji-picked", |
| G_OBJECT_CLASS_TYPE(objectClass), |
| G_SIGNAL_RUN_LAST, |
| 0, |
| nullptr, nullptr, |
| nullptr, |
| G_TYPE_NONE, 1, |
| G_TYPE_STRING | G_SIGNAL_TYPE_STATIC_SCOPE); |
| } |
| |
| GtkWidget* webkitEmojiChooserNew() |
| { |
| WebKitEmojiChooser* authDialog = WEBKIT_EMOJI_CHOOSER(g_object_new(WEBKIT_TYPE_EMOJI_CHOOSER, nullptr)); |
| return GTK_WIDGET(authDialog); |
| } |
| |
| #endif // GTK_CHECK_VERSION(3, 24, 0) |