mirror of
https://gitlab.gnome.org/GNOME/gtk.git
synced 2024-12-27 14:10:30 +00:00
Add an Emoji chooser widget
Add a popover that shows color Emoji, with a search entry. The recently-used Emoji are stored in a GSetting.
This commit is contained in:
parent
44999fa239
commit
c6c1619646
@ -451,6 +451,7 @@ gtk_private_h_sources = \
|
||||
gtkdebugupdatesprivate.h \
|
||||
gtkdialogprivate.h \
|
||||
gtkdndprivate.h \
|
||||
gtkemojichooser.h \
|
||||
gtkentryprivate.h \
|
||||
gtkeventcontrollerprivate.h \
|
||||
gtkfilechooserembed.h \
|
||||
@ -722,6 +723,7 @@ gtk_base_c_sources = \
|
||||
gtkdragsource.c \
|
||||
gtkdrawingarea.c \
|
||||
gtkeditable.c \
|
||||
gtkemojichooser.c \
|
||||
gtkentry.c \
|
||||
gtkentrybuffer.c \
|
||||
gtkentrycompletion.c \
|
||||
@ -1148,6 +1150,7 @@ templates = \
|
||||
ui/gtkcoloreditor.ui \
|
||||
ui/gtkcombobox.ui \
|
||||
ui/gtkdialog.ui \
|
||||
ui/gtkemojichooser.ui \
|
||||
ui/gtkfilechooserwidget.ui \
|
||||
ui/gtkfilechooserdialog.ui \
|
||||
ui/gtkfontchooserdialog.ui \
|
||||
@ -1690,6 +1693,7 @@ files:
|
||||
gsettings_SCHEMAS = \
|
||||
org.gtk.Settings.FileChooser.gschema.xml \
|
||||
org.gtk.Settings.ColorChooser.gschema.xml \
|
||||
org.gtk.Settings.EmojiChooser.gschema.xml \
|
||||
org.gtk.Settings.Debug.gschema.xml
|
||||
|
||||
@GSETTINGS_RULES@
|
||||
|
611
gtk/gtkemojichooser.c
Normal file
611
gtk/gtkemojichooser.c
Normal file
@ -0,0 +1,611 @@
|
||||
/* gtkemojichooser.c: An Emoji chooser 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 "gtkemojichooser.h"
|
||||
|
||||
#include "gtkadjustment.h"
|
||||
#include "gtkbox.h"
|
||||
#include "gtkbutton.h"
|
||||
#include "gtkcssprovider.h"
|
||||
#include "gtkentry.h"
|
||||
#include "gtkflowbox.h"
|
||||
#include "gtkstack.h"
|
||||
#include "gtklabel.h"
|
||||
#include "gtkgesturelongpress.h"
|
||||
#include "gtkpopover.h"
|
||||
#include "gtkscrolledwindow.h"
|
||||
#include "gtkintl.h"
|
||||
#include "gtkprivate.h"
|
||||
|
||||
typedef struct {
|
||||
GtkWidget *box;
|
||||
GtkWidget *heading;
|
||||
GtkWidget *button;
|
||||
const char *first;
|
||||
gunichar label;
|
||||
gboolean empty;
|
||||
} EmojiSection;
|
||||
|
||||
struct _GtkEmojiChooser
|
||||
{
|
||||
GtkPopover parent_instance;
|
||||
|
||||
GtkWidget *search_entry;
|
||||
GtkWidget *stack;
|
||||
GtkWidget *scrolled_window;
|
||||
|
||||
EmojiSection recent;
|
||||
EmojiSection people;
|
||||
EmojiSection body;
|
||||
EmojiSection nature;
|
||||
EmojiSection food;
|
||||
EmojiSection travel;
|
||||
EmojiSection activities;
|
||||
EmojiSection objects;
|
||||
EmojiSection symbols;
|
||||
EmojiSection flags;
|
||||
|
||||
GtkGesture *recent_press;
|
||||
GtkGesture *people_press;
|
||||
GtkGesture *body_press;
|
||||
|
||||
GVariant *data;
|
||||
|
||||
GSettings *settings;
|
||||
};
|
||||
|
||||
struct _GtkEmojiChooserClass {
|
||||
GtkPopoverClass parent_class;
|
||||
};
|
||||
|
||||
enum {
|
||||
EMOJI_PICKED,
|
||||
LAST_SIGNAL
|
||||
};
|
||||
|
||||
static int signals[LAST_SIGNAL];
|
||||
|
||||
G_DEFINE_TYPE (GtkEmojiChooser, gtk_emoji_chooser, GTK_TYPE_POPOVER)
|
||||
|
||||
static void
|
||||
gtk_emoji_chooser_finalize (GObject *object)
|
||||
{
|
||||
GtkEmojiChooser *chooser = GTK_EMOJI_CHOOSER (object);
|
||||
|
||||
g_variant_unref (chooser->data);
|
||||
g_object_unref (chooser->settings);
|
||||
|
||||
G_OBJECT_CLASS (gtk_emoji_chooser_parent_class)->finalize (object);
|
||||
}
|
||||
|
||||
static void
|
||||
scroll_to_section (GtkButton *button,
|
||||
gpointer data)
|
||||
{
|
||||
EmojiSection *section = data;
|
||||
GtkEmojiChooser *chooser;
|
||||
GtkAdjustment *adj;
|
||||
GtkAllocation alloc = { 0, 0, 0, 0 };
|
||||
double page_increment, value;
|
||||
gboolean dummy;
|
||||
|
||||
chooser = GTK_EMOJI_CHOOSER (gtk_widget_get_ancestor (GTK_WIDGET (button), GTK_TYPE_EMOJI_CHOOSER));
|
||||
|
||||
adj = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (chooser->scrolled_window));
|
||||
if (section->heading)
|
||||
gtk_widget_get_allocation (section->heading, &alloc);
|
||||
page_increment = gtk_adjustment_get_page_increment (adj);
|
||||
value = gtk_adjustment_get_value (adj);
|
||||
gtk_adjustment_set_page_increment (adj, alloc.y - value);
|
||||
g_signal_emit_by_name (chooser->scrolled_window, "scroll-child", GTK_SCROLL_PAGE_FORWARD, FALSE, &dummy);
|
||||
gtk_adjustment_set_page_increment (adj, page_increment);
|
||||
}
|
||||
|
||||
static void
|
||||
add_emoji (GtkWidget *box,
|
||||
gboolean prepend,
|
||||
GVariantIter *iter,
|
||||
GVariant *data);
|
||||
|
||||
#define MAX_RECENT (7*3)
|
||||
|
||||
static void
|
||||
add_recent_item (GtkEmojiChooser *chooser,
|
||||
GVariant *item)
|
||||
{
|
||||
GList *children, *l;
|
||||
GVariantIter *codes;
|
||||
const char *name;
|
||||
int i;
|
||||
GVariantBuilder builder;
|
||||
|
||||
g_variant_ref (item);
|
||||
|
||||
g_variant_builder_init (&builder, G_VARIANT_TYPE ("a(ausaau)"));
|
||||
g_variant_builder_add_value (&builder, item);
|
||||
|
||||
g_variant_get_child (item, 1, "&s", &name);
|
||||
|
||||
children = gtk_container_get_children (GTK_CONTAINER (chooser->recent.box));
|
||||
for (l = children, i = 1; l; l = l->next, i++)
|
||||
{
|
||||
GVariant *item2 = g_object_get_data (G_OBJECT (l->data), "emoji-data");
|
||||
const char *name2;
|
||||
|
||||
g_variant_get_child (item2, 1, "&s", &name2);
|
||||
if (strcmp (name, name2) == 0)
|
||||
{
|
||||
gtk_widget_destroy (GTK_WIDGET (l->data));
|
||||
i--;
|
||||
continue;
|
||||
}
|
||||
if (i >= MAX_RECENT)
|
||||
{
|
||||
gtk_widget_destroy (GTK_WIDGET (l->data));
|
||||
continue;
|
||||
}
|
||||
|
||||
g_variant_builder_add_value (&builder, item2);
|
||||
}
|
||||
g_list_free (children);
|
||||
|
||||
g_variant_get_child (item, 0, "au", &codes);
|
||||
add_emoji (chooser->recent.box, TRUE, codes, item);
|
||||
g_variant_iter_free (codes);
|
||||
|
||||
g_settings_set_value (chooser->settings, "recent-emoji", g_variant_builder_end (&builder));
|
||||
|
||||
g_variant_unref (item);
|
||||
}
|
||||
|
||||
static void
|
||||
emoji_activated (GtkFlowBox *box,
|
||||
GtkFlowBoxChild *child,
|
||||
gpointer data)
|
||||
{
|
||||
GtkEmojiChooser *chooser = data;
|
||||
char *text;
|
||||
GtkWidget *label;
|
||||
GVariant *item;
|
||||
|
||||
gtk_popover_popdown (GTK_POPOVER (chooser));
|
||||
|
||||
label = gtk_bin_get_child (GTK_BIN (child));
|
||||
text = g_strdup (gtk_label_get_label (GTK_LABEL (label)));
|
||||
|
||||
item = (GVariant*) g_object_get_data (G_OBJECT (child), "emoji-data");
|
||||
add_recent_item (chooser, item);
|
||||
|
||||
g_signal_emit (data, signals[EMOJI_PICKED], 0, text);
|
||||
g_free (text);
|
||||
}
|
||||
|
||||
static void
|
||||
long_pressed_cb (GtkGesture *gesture,
|
||||
double x,
|
||||
double y,
|
||||
gpointer data)
|
||||
{
|
||||
GtkWidget *child;
|
||||
GtkWidget *popover;
|
||||
GtkWidget *view;
|
||||
GtkWidget *box;
|
||||
GVariant *emoji_data;
|
||||
GtkWidget *parent_popover;
|
||||
GVariantIter *iter;
|
||||
GVariantIter *codes;
|
||||
|
||||
box = gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (gesture));
|
||||
child = GTK_WIDGET (gtk_flow_box_get_child_at_pos (GTK_FLOW_BOX (box), x, y));
|
||||
if (!child)
|
||||
return;
|
||||
|
||||
emoji_data = (GVariant*) g_object_get_data (G_OBJECT (child), "emoji-data");
|
||||
if (!emoji_data)
|
||||
return;
|
||||
|
||||
g_variant_get_child (emoji_data, 2, "aau", &iter);
|
||||
if (g_variant_iter_n_children (iter) == 0)
|
||||
{
|
||||
g_variant_iter_free (iter);
|
||||
return;
|
||||
}
|
||||
|
||||
parent_popover = gtk_widget_get_ancestor (child, GTK_TYPE_POPOVER);
|
||||
popover = gtk_popover_new (child);
|
||||
view = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
|
||||
gtk_style_context_add_class (gtk_widget_get_style_context (view), "view");
|
||||
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), 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 (popover), view);
|
||||
gtk_container_add (GTK_CONTAINER (view), box);
|
||||
|
||||
g_signal_connect (box, "child-activated", G_CALLBACK (emoji_activated), parent_popover);
|
||||
|
||||
g_variant_get_child (emoji_data, 0, "au", &codes);
|
||||
add_emoji (box, FALSE, codes, emoji_data);
|
||||
g_variant_iter_free (codes);
|
||||
while (g_variant_iter_next (iter, "au", &codes))
|
||||
{
|
||||
add_emoji (box, FALSE, codes, emoji_data);
|
||||
g_variant_iter_free (codes);
|
||||
}
|
||||
|
||||
gtk_popover_popup (GTK_POPOVER (popover));
|
||||
}
|
||||
|
||||
static void
|
||||
add_emoji (GtkWidget *box,
|
||||
gboolean prepend,
|
||||
GVariantIter *iter,
|
||||
GVariant *data)
|
||||
{
|
||||
GtkWidget *child;
|
||||
GtkWidget *label;
|
||||
PangoAttrList *attrs;
|
||||
char text[64];
|
||||
char *p = text;
|
||||
gunichar code;
|
||||
|
||||
while (g_variant_iter_next (iter, "u", &code))
|
||||
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_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 (data),
|
||||
(GDestroyNotify)g_variant_unref);
|
||||
|
||||
gtk_container_add (GTK_CONTAINER (child), label);
|
||||
gtk_flow_box_insert (GTK_FLOW_BOX (box), child, prepend ? 0 : -1);
|
||||
}
|
||||
|
||||
static void
|
||||
populate_emoji_chooser (GtkEmojiChooser *chooser)
|
||||
{
|
||||
g_autoptr(GBytes) bytes = NULL;
|
||||
GVariantIter iter;
|
||||
GVariant *item;
|
||||
GtkWidget *box;
|
||||
|
||||
bytes = g_resources_lookup_data ("/org/gtk/libgtk/emoji/emoji.data", 0, NULL);
|
||||
chooser->data = g_variant_ref_sink (g_variant_new_from_bytes (G_VARIANT_TYPE ("a(ausaau)"), bytes, TRUE));
|
||||
|
||||
g_variant_iter_init (&iter, chooser->data);
|
||||
box = chooser->people.box;
|
||||
while ((item = g_variant_iter_next_value (&iter)))
|
||||
{
|
||||
GVariantIter *codes;
|
||||
const char *name;
|
||||
|
||||
g_variant_get_child (item, 0, "au", &codes);
|
||||
g_variant_get_child (item, 1, "&s", &name);
|
||||
|
||||
if (strcmp (name, chooser->body.first) == 0)
|
||||
box = chooser->body.box;
|
||||
else if (strcmp (name, chooser->nature.first) == 0)
|
||||
box = chooser->nature.box;
|
||||
else if (strcmp (name, chooser->food.first) == 0)
|
||||
box = chooser->food.box;
|
||||
else if (strcmp (name, chooser->travel.first) == 0)
|
||||
box = chooser->travel.box;
|
||||
else if (strcmp (name, chooser->activities.first) == 0)
|
||||
box = chooser->activities.box;
|
||||
else if (strcmp (name, chooser->objects.first) == 0)
|
||||
box = chooser->objects.box;
|
||||
else if (strcmp (name, chooser->symbols.first) == 0)
|
||||
box = chooser->symbols.box;
|
||||
else if (strcmp (name, chooser->flags.first) == 0)
|
||||
box = chooser->flags.box;
|
||||
|
||||
add_emoji (box, FALSE, codes, item);
|
||||
g_variant_iter_free (codes);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
update_state (EmojiSection *section,
|
||||
double value)
|
||||
{
|
||||
GtkAllocation alloc = { 0, 0, 0, 20 };
|
||||
|
||||
if (section->heading)
|
||||
gtk_widget_get_allocation (section->heading, &alloc);
|
||||
|
||||
if (alloc.y <= value && value < alloc.y + alloc.height)
|
||||
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 void
|
||||
adj_value_changed (GtkAdjustment *adj,
|
||||
gpointer data)
|
||||
{
|
||||
GtkEmojiChooser *chooser = data;
|
||||
double value = gtk_adjustment_get_value (adj);
|
||||
|
||||
update_state (&chooser->recent, value);
|
||||
update_state (&chooser->people, value);
|
||||
update_state (&chooser->body, value);
|
||||
update_state (&chooser->nature, value);
|
||||
update_state (&chooser->food, value);
|
||||
update_state (&chooser->travel, value);
|
||||
update_state (&chooser->activities, value);
|
||||
update_state (&chooser->objects, value);
|
||||
update_state (&chooser->symbols, value);
|
||||
update_state (&chooser->flags, value);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
filter_func (GtkFlowBoxChild *child,
|
||||
gpointer data)
|
||||
{
|
||||
EmojiSection *section = data;
|
||||
GtkEmojiChooser *chooser;
|
||||
GVariant *emoji_data;
|
||||
const char *text;
|
||||
const char *name;
|
||||
gboolean res;
|
||||
|
||||
res = TRUE;
|
||||
|
||||
chooser = GTK_EMOJI_CHOOSER (gtk_widget_get_ancestor (GTK_WIDGET (child), GTK_TYPE_EMOJI_CHOOSER));
|
||||
text = gtk_entry_get_text (GTK_ENTRY (chooser->search_entry));
|
||||
emoji_data = (GVariant *) g_object_get_data (G_OBJECT (child), "emoji-data");
|
||||
|
||||
if (text[0] == 0 || text[1] == 0)
|
||||
goto out;
|
||||
|
||||
if (!emoji_data)
|
||||
goto out;
|
||||
|
||||
g_variant_get_child (emoji_data, 1, "&s", &name);
|
||||
res = strstr (name, text) != NULL;
|
||||
|
||||
out:
|
||||
if (res)
|
||||
section->empty = FALSE;
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static void
|
||||
invalidate_section (EmojiSection *section)
|
||||
{
|
||||
section->empty = TRUE;
|
||||
gtk_flow_box_invalidate_filter (GTK_FLOW_BOX (section->box));
|
||||
}
|
||||
|
||||
static gboolean
|
||||
update_headings (gpointer data)
|
||||
{
|
||||
GtkEmojiChooser *chooser = data;
|
||||
|
||||
gtk_widget_set_visible (chooser->people.heading, !chooser->people.empty);
|
||||
gtk_widget_set_visible (chooser->body.heading, !chooser->body.empty);
|
||||
gtk_widget_set_visible (chooser->nature.heading, !chooser->nature.empty);
|
||||
gtk_widget_set_visible (chooser->food.heading, !chooser->food.empty);
|
||||
gtk_widget_set_visible (chooser->travel.heading, !chooser->travel.empty);
|
||||
gtk_widget_set_visible (chooser->activities.heading, !chooser->activities.empty);
|
||||
gtk_widget_set_visible (chooser->objects.heading, !chooser->objects.empty);
|
||||
gtk_widget_set_visible (chooser->symbols.heading, !chooser->symbols.empty);
|
||||
gtk_widget_set_visible (chooser->flags.heading, !chooser->flags.empty);
|
||||
|
||||
if (chooser->recent.empty && chooser->people.empty &&
|
||||
chooser->body.empty && chooser->nature.empty &&
|
||||
chooser->food.empty && chooser->travel.empty &&
|
||||
chooser->activities.empty && chooser->objects.empty &&
|
||||
chooser->symbols.empty && chooser->flags.empty)
|
||||
gtk_stack_set_visible_child_name (GTK_STACK (chooser->stack), "empty");
|
||||
else
|
||||
gtk_stack_set_visible_child_name (GTK_STACK (chooser->stack), "list");
|
||||
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
|
||||
static void
|
||||
search_changed (GtkEntry *entry,
|
||||
gpointer data)
|
||||
{
|
||||
GtkEmojiChooser *chooser = data;
|
||||
|
||||
invalidate_section (&chooser->recent);
|
||||
invalidate_section (&chooser->people);
|
||||
invalidate_section (&chooser->body);
|
||||
invalidate_section (&chooser->nature);
|
||||
invalidate_section (&chooser->food);
|
||||
invalidate_section (&chooser->travel);
|
||||
invalidate_section (&chooser->activities);
|
||||
invalidate_section (&chooser->objects);
|
||||
invalidate_section (&chooser->symbols);
|
||||
invalidate_section (&chooser->flags);
|
||||
|
||||
g_idle_add (update_headings, data);
|
||||
}
|
||||
|
||||
static void
|
||||
setup_section (GtkEmojiChooser *chooser,
|
||||
EmojiSection *section,
|
||||
const char *first,
|
||||
gunichar label)
|
||||
{
|
||||
char text[14];
|
||||
char *p;
|
||||
GtkAdjustment *adj;
|
||||
|
||||
section->first = first;
|
||||
|
||||
p = text;
|
||||
p += g_unichar_to_utf8 (label, p);
|
||||
p += g_unichar_to_utf8 (0xfe0e, p);
|
||||
p[0] = 0;
|
||||
gtk_button_set_label (GTK_BUTTON (section->button), text);
|
||||
|
||||
adj = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (chooser->scrolled_window));
|
||||
|
||||
gtk_container_set_focus_vadjustment (GTK_CONTAINER (section->box), adj);
|
||||
gtk_flow_box_set_filter_func (GTK_FLOW_BOX (section->box), filter_func, section, NULL);
|
||||
g_signal_connect (section->button, "clicked", G_CALLBACK (scroll_to_section), section);
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_emoji_chooser_init (GtkEmojiChooser *chooser)
|
||||
{
|
||||
GtkAdjustment *adj;
|
||||
GVariant *variant;
|
||||
GVariantIter iter;
|
||||
GVariant *item;
|
||||
|
||||
gtk_widget_init_template (GTK_WIDGET (chooser));
|
||||
|
||||
chooser->recent_press = gtk_gesture_long_press_new (chooser->recent.box);
|
||||
g_signal_connect (chooser->recent_press, "pressed", G_CALLBACK (long_pressed_cb), chooser);
|
||||
|
||||
chooser->people_press = gtk_gesture_long_press_new (chooser->people.box);
|
||||
g_signal_connect (chooser->people_press, "pressed", G_CALLBACK (long_pressed_cb), chooser);
|
||||
|
||||
chooser->body_press = gtk_gesture_long_press_new (chooser->body.box);
|
||||
g_signal_connect (chooser->body_press, "pressed", G_CALLBACK (long_pressed_cb), chooser);
|
||||
|
||||
adj = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (chooser->scrolled_window));
|
||||
g_signal_connect (adj, "value-changed", G_CALLBACK (adj_value_changed), chooser);
|
||||
|
||||
setup_section (chooser, &chooser->recent, NULL, 0x1f557);
|
||||
setup_section (chooser, &chooser->people, "grinning face", 0x1f642);
|
||||
setup_section (chooser, &chooser->body, "selfie", 0x1f44d);
|
||||
setup_section (chooser, &chooser->nature, "monkey face", 0x1f337);
|
||||
setup_section (chooser, &chooser->food, "grapes", 0x1f374);
|
||||
setup_section (chooser, &chooser->travel, "globe showing Europe-Africa", 0x2708);
|
||||
setup_section (chooser, &chooser->activities, "jack-o-lantern", 0x1f3c3);
|
||||
setup_section (chooser, &chooser->objects, "muted speaker", 0x1f514);
|
||||
setup_section (chooser, &chooser->symbols, "ATM sign", 0x2764);
|
||||
setup_section (chooser, &chooser->flags, "chequered flag", 0x1f3f4);
|
||||
|
||||
populate_emoji_chooser (chooser);
|
||||
|
||||
chooser->settings = g_settings_new ("org.gtk.Settings.EmojiChooser");
|
||||
variant = g_settings_get_value (chooser->settings, "recent-emoji");
|
||||
g_variant_iter_init (&iter, variant);
|
||||
while ((item = g_variant_iter_next_value (&iter)))
|
||||
{
|
||||
GVariantIter *codes;
|
||||
|
||||
g_variant_get_child (item, 0, "au", &codes);
|
||||
add_emoji (chooser->recent.box, FALSE, codes, item);
|
||||
g_variant_iter_free (codes);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_emoji_chooser_show (GtkWidget *widget)
|
||||
{
|
||||
GtkEmojiChooser *chooser = GTK_EMOJI_CHOOSER (widget);
|
||||
GtkAdjustment *adj;
|
||||
|
||||
adj = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (chooser->scrolled_window));
|
||||
gtk_adjustment_set_value (adj, 0);
|
||||
|
||||
gtk_entry_set_text (GTK_ENTRY (chooser->search_entry), "");
|
||||
|
||||
GTK_WIDGET_CLASS (gtk_emoji_chooser_parent_class)->show (widget);
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_emoji_chooser_class_init (GtkEmojiChooserClass *klass)
|
||||
{
|
||||
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
||||
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
|
||||
|
||||
object_class->finalize = gtk_emoji_chooser_finalize;
|
||||
widget_class->show = gtk_emoji_chooser_show;
|
||||
|
||||
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/gtkemojichooser.ui");
|
||||
|
||||
gtk_widget_class_bind_template_child (widget_class, GtkEmojiChooser, search_entry);
|
||||
gtk_widget_class_bind_template_child (widget_class, GtkEmojiChooser, stack);
|
||||
gtk_widget_class_bind_template_child (widget_class, GtkEmojiChooser, scrolled_window);
|
||||
|
||||
gtk_widget_class_bind_template_child (widget_class, GtkEmojiChooser, recent.box);
|
||||
gtk_widget_class_bind_template_child (widget_class, GtkEmojiChooser, recent.button);
|
||||
|
||||
gtk_widget_class_bind_template_child (widget_class, GtkEmojiChooser, people.box);
|
||||
gtk_widget_class_bind_template_child (widget_class, GtkEmojiChooser, people.heading);
|
||||
gtk_widget_class_bind_template_child (widget_class, GtkEmojiChooser, people.button);
|
||||
|
||||
gtk_widget_class_bind_template_child (widget_class, GtkEmojiChooser, body.box);
|
||||
gtk_widget_class_bind_template_child (widget_class, GtkEmojiChooser, body.heading);
|
||||
gtk_widget_class_bind_template_child (widget_class, GtkEmojiChooser, body.button);
|
||||
|
||||
gtk_widget_class_bind_template_child (widget_class, GtkEmojiChooser, nature.box);
|
||||
gtk_widget_class_bind_template_child (widget_class, GtkEmojiChooser, nature.heading);
|
||||
gtk_widget_class_bind_template_child (widget_class, GtkEmojiChooser, nature.button);
|
||||
|
||||
gtk_widget_class_bind_template_child (widget_class, GtkEmojiChooser, food.box);
|
||||
gtk_widget_class_bind_template_child (widget_class, GtkEmojiChooser, food.heading);
|
||||
gtk_widget_class_bind_template_child (widget_class, GtkEmojiChooser, food.button);
|
||||
|
||||
gtk_widget_class_bind_template_child (widget_class, GtkEmojiChooser, travel.box);
|
||||
gtk_widget_class_bind_template_child (widget_class, GtkEmojiChooser, travel.heading);
|
||||
gtk_widget_class_bind_template_child (widget_class, GtkEmojiChooser, travel.button);
|
||||
|
||||
gtk_widget_class_bind_template_child (widget_class, GtkEmojiChooser, activities.box);
|
||||
gtk_widget_class_bind_template_child (widget_class, GtkEmojiChooser, activities.heading);
|
||||
gtk_widget_class_bind_template_child (widget_class, GtkEmojiChooser, activities.button);
|
||||
|
||||
gtk_widget_class_bind_template_child (widget_class, GtkEmojiChooser, objects.box);
|
||||
gtk_widget_class_bind_template_child (widget_class, GtkEmojiChooser, objects.heading);
|
||||
gtk_widget_class_bind_template_child (widget_class, GtkEmojiChooser, objects.button);
|
||||
|
||||
gtk_widget_class_bind_template_child (widget_class, GtkEmojiChooser, symbols.box);
|
||||
gtk_widget_class_bind_template_child (widget_class, GtkEmojiChooser, symbols.heading);
|
||||
gtk_widget_class_bind_template_child (widget_class, GtkEmojiChooser, symbols.button);
|
||||
|
||||
gtk_widget_class_bind_template_child (widget_class, GtkEmojiChooser, flags.box);
|
||||
gtk_widget_class_bind_template_child (widget_class, GtkEmojiChooser, flags.heading);
|
||||
gtk_widget_class_bind_template_child (widget_class, GtkEmojiChooser, flags.button);
|
||||
|
||||
gtk_widget_class_bind_template_callback (widget_class, emoji_activated);
|
||||
gtk_widget_class_bind_template_callback (widget_class, search_changed);
|
||||
}
|
||||
|
||||
GtkWidget *
|
||||
gtk_emoji_chooser_new (void)
|
||||
{
|
||||
return GTK_WIDGET (g_object_new (GTK_TYPE_EMOJI_CHOOSER, NULL));
|
||||
}
|
41
gtk/gtkemojichooser.h
Normal file
41
gtk/gtkemojichooser.h
Normal file
@ -0,0 +1,41 @@
|
||||
/* gtkemojichooser.h: An Emoji chooser 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_CHOOSER (gtk_emoji_chooser_get_type ())
|
||||
#define GTK_EMOJI_CHOOSER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_EMOJI_CHOOSER, GtkEmojiChooser))
|
||||
#define GTK_EMOJI_CHOOSER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_EMOJI_CHOOSER, GtkEmojiChooserClass))
|
||||
#define GTK_IS_EMOJI_CHOOSER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_EMOJI_CHOOSER))
|
||||
#define GTK_IS_EMOJI_CHOOSER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_EMOJI_CHOOSER))
|
||||
#define GTK_EMOJI_CHOOSER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_EMOJI_CHOOSER, GtkEmojiChooserClass))
|
||||
|
||||
typedef struct _GtkEmojiChooser GtkEmojiChooser;
|
||||
typedef struct _GtkEmojiChooserClass GtkEmojiChooserClass;
|
||||
|
||||
GType gtk_emoji_chooser_get_type (void) G_GNUC_CONST;
|
||||
GtkWidget *gtk_emoji_chooser_new (void);
|
||||
|
||||
G_END_DECLS
|
16
gtk/org.gtk.Settings.EmojiChooser.gschema.xml
Normal file
16
gtk/org.gtk.Settings.EmojiChooser.gschema.xml
Normal file
@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<schemalist>
|
||||
|
||||
<schema id='org.gtk.Settings.EmojiChooser' path='/org/gtk/settings/emoji-chooser/'>
|
||||
<key name='recent-emoji' type='a(ausaau)'>
|
||||
<default>[]</default>
|
||||
<summary>Recently used Emoji</summary>
|
||||
<description>
|
||||
An array of Emoji definitions to show in the Emoji chooser. Each Emoji is
|
||||
specified as an array of codepoints, a name, and an optional array of
|
||||
nested Emoji.
|
||||
</description>
|
||||
</key>
|
||||
</schema>
|
||||
|
||||
</schemalist>
|
@ -4370,3 +4370,52 @@ stackswitcher button.text-button.circular { // FIXME aggregate with buttons
|
||||
min-height: 32px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
popover.emoji-picker { padding-left: 0; padding-right: 0; }
|
||||
|
||||
button.emoji-section {
|
||||
border-color: transparent;
|
||||
border-width: 3px;
|
||||
border-style: none none solid;
|
||||
border-radius: 0;
|
||||
|
||||
margin: 2px 4px 2px 4px;
|
||||
padding: 3px 0 0;
|
||||
min-width: 32px;
|
||||
min-height: 28px;
|
||||
|
||||
/* reset props inherited from the button style */
|
||||
background: none;
|
||||
box-shadow: none;
|
||||
text-shadow: none;
|
||||
|
||||
outline-offset: -5px;
|
||||
}
|
||||
|
||||
button.emoji-section label { padding: 0; }
|
||||
|
||||
button.emoji-section:hover { border-color: $borders_color; }
|
||||
|
||||
button.emoji-section:checked { border-color: $selected_bg_color; }
|
||||
|
||||
button.emoji-section:backdrop:not(:checked) { border-color: transparent; }
|
||||
|
||||
button.emoji-section { color: $borders_color; }
|
||||
|
||||
button.emoji-section:hover { color: mix($fg_color, $borders_color, 0.5); }
|
||||
|
||||
button.emoji-section:checked { color: $fg_color; }
|
||||
|
||||
button.emoji-section:backdrop { color: $backdrop_borders_color; }
|
||||
|
||||
button.emoji-section:backdrop:checked { color: $backdrop_fg_color; }
|
||||
|
||||
.emoji {
|
||||
font-size: x-large;
|
||||
padding: 6px;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.emoji:hover {
|
||||
background: $selected_bg_color;
|
||||
}
|
||||
|
@ -1893,6 +1893,32 @@ stackswitcher button.text-button { min-width: 100px; }
|
||||
|
||||
stackswitcher button.circular, stackswitcher button.text-button.circular { min-width: 32px; min-height: 32px; padding: 0; }
|
||||
|
||||
popover.emoji-picker { padding-left: 0; padding-right: 0; }
|
||||
|
||||
button.emoji-section { border-color: transparent; border-width: 3px; border-style: none none solid; border-radius: 0; margin: 2px 4px 2px 4px; padding: 3px 0 0; min-width: 32px; min-height: 28px; /* reset props inherited from the button style */ background: none; box-shadow: none; text-shadow: none; outline-offset: -5px; }
|
||||
|
||||
button.emoji-section label { padding: 0; }
|
||||
|
||||
button.emoji-section:hover { border-color: #1b1f20; }
|
||||
|
||||
button.emoji-section:checked { border-color: #215d9c; }
|
||||
|
||||
button.emoji-section:backdrop:not(:checked) { border-color: transparent; }
|
||||
|
||||
button.emoji-section { color: #1b1f20; }
|
||||
|
||||
button.emoji-section:hover { color: #1c2021; }
|
||||
|
||||
button.emoji-section:checked { color: #eeeeec; }
|
||||
|
||||
button.emoji-section:backdrop { color: #202425; }
|
||||
|
||||
button.emoji-section:backdrop:checked { color: #919494; }
|
||||
|
||||
.emoji { font-size: x-large; padding: 6px; border-radius: 6px; }
|
||||
|
||||
.emoji:hover { background: #215d9c; }
|
||||
|
||||
/* GTK NAMED COLORS ---------------- use responsibly! */
|
||||
/*
|
||||
widget text/foreground color */
|
||||
|
@ -1913,6 +1913,32 @@ stackswitcher button.text-button { min-width: 100px; }
|
||||
|
||||
stackswitcher button.circular, stackswitcher button.text-button.circular { min-width: 32px; min-height: 32px; padding: 0; }
|
||||
|
||||
popover.emoji-picker { padding-left: 0; padding-right: 0; }
|
||||
|
||||
button.emoji-section { border-color: transparent; border-width: 3px; border-style: none none solid; border-radius: 0; margin: 2px 4px 2px 4px; padding: 3px 0 0; min-width: 32px; min-height: 28px; /* reset props inherited from the button style */ background: none; box-shadow: none; text-shadow: none; outline-offset: -5px; }
|
||||
|
||||
button.emoji-section label { padding: 0; }
|
||||
|
||||
button.emoji-section:hover { border-color: #b6b6b3; }
|
||||
|
||||
button.emoji-section:checked { border-color: #4a90d9; }
|
||||
|
||||
button.emoji-section:backdrop:not(:checked) { border-color: transparent; }
|
||||
|
||||
button.emoji-section { color: #b6b6b3; }
|
||||
|
||||
button.emoji-section:hover { color: #b5b5b2; }
|
||||
|
||||
button.emoji-section:checked { color: #2e3436; }
|
||||
|
||||
button.emoji-section:backdrop { color: #c0c0bd; }
|
||||
|
||||
button.emoji-section:backdrop:checked { color: #8b8e8f; }
|
||||
|
||||
.emoji { font-size: x-large; padding: 6px; border-radius: 6px; }
|
||||
|
||||
.emoji:hover { background: #4a90d9; }
|
||||
|
||||
/* GTK NAMED COLORS ---------------- use responsibly! */
|
||||
/*
|
||||
widget text/foreground color */
|
||||
|
307
gtk/ui/gtkemojichooser.ui
Normal file
307
gtk/ui/gtkemojichooser.ui
Normal file
@ -0,0 +1,307 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<interface domain="gtk40">
|
||||
<template class="GtkEmojiChooser" parent="GtkPopover">
|
||||
<property name="modal">1</property>
|
||||
<style>
|
||||
<class name="emoji-picker"/>
|
||||
</style>
|
||||
<child>
|
||||
<object class="GtkBox" id="box">
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="GtkSearchEntry" id="search_entry">
|
||||
<signal name="search-changed" handler="search_changed"/>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkStack" id="stack">
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="GtkScrolledWindow" id="scrolled_window">
|
||||
<property name="vexpand">1</property>
|
||||
<property name="hscrollbar-policy">never</property>
|
||||
<property name="min-content-height">250</property>
|
||||
<style>
|
||||
<class name="view"/>
|
||||
</style>
|
||||
<child>
|
||||
<object class="GtkBox" id="emoji_box">
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="margin">6</property>
|
||||
<property name="spacing">6</property>
|
||||
<child>
|
||||
<object class="GtkFlowBox" id="recent.box">
|
||||
<property name="homogeneous">1</property>
|
||||
<property name="selection-mode">none</property>
|
||||
<signal name="child-activated" handler="emoji_activated"/>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="people.heading">
|
||||
<property name="label" translatable="yes" context="emoji category">Smileys & People</property>
|
||||
<property name="xalign">0</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkFlowBox" id="people.box">
|
||||
<property name="homogeneous">1</property>
|
||||
<property name="selection-mode">none</property>
|
||||
<signal name="child-activated" handler="emoji_activated"/>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="body.heading">
|
||||
<property name="label" translatable="yes" context="emoji category">Body & Clothing</property>
|
||||
<property name="xalign">0</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkFlowBox" id="body.box">
|
||||
<property name="homogeneous">1</property>
|
||||
<property name="selection-mode">none</property>
|
||||
<signal name="child-activated" handler="emoji_activated"/>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="nature.heading">
|
||||
<property name="label" translatable="yes" context="emoji category">Animals & Nature</property>
|
||||
<property name="xalign">0</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkFlowBox" id="nature.box">
|
||||
<property name="homogeneous">1</property>
|
||||
<property name="selection-mode">none</property>
|
||||
<signal name="child-activated" handler="emoji_activated"/>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="food.heading">
|
||||
<property name="label" translatable="yes" context="emoji category">Food & Drink</property>
|
||||
<property name="xalign">0</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkFlowBox" id="food.box">
|
||||
<property name="homogeneous">1</property>
|
||||
<property name="selection-mode">none</property>
|
||||
<signal name="child-activated" handler="emoji_activated"/>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="travel.heading">
|
||||
<property name="label" translatable="yes" context="emoji category">Travel & Places</property>
|
||||
<property name="xalign">0</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkFlowBox" id="travel.box">
|
||||
<property name="homogeneous">1</property>
|
||||
<property name="selection-mode">none</property>
|
||||
<signal name="child-activated" handler="emoji_activated"/>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="activities.heading">
|
||||
<property name="label" translatable="yes" context="emoji category">Activities</property>
|
||||
<property name="xalign">0</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkFlowBox" id="activities.box">
|
||||
<property name="homogeneous">1</property>
|
||||
<property name="selection-mode">none</property>
|
||||
<signal name="child-activated" handler="emoji_activated"/>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="objects.heading">
|
||||
<property name="label" translatable="yes" context="emoji category">Objects</property>
|
||||
<property name="xalign">0</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkFlowBox" id="objects.box">
|
||||
<property name="homogeneous">1</property>
|
||||
<property name="selection-mode">none</property>
|
||||
<signal name="child-activated" handler="emoji_activated"/>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="symbols.heading">
|
||||
<property name="label" translatable="yes" context="emoji category">Symbols</property>
|
||||
<property name="xalign">0</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkFlowBox" id="symbols.box">
|
||||
<property name="homogeneous">1</property>
|
||||
<property name="selection-mode">none</property>
|
||||
<signal name="child-activated" handler="emoji_activated"/>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="flags.heading">
|
||||
<property name="label" translatable="yes" context="emoji category">Flags</property>
|
||||
<property name="xalign">0</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkFlowBox" id="flags.box">
|
||||
<property name="homogeneous">1</property>
|
||||
<property name="selection-mode">none</property>
|
||||
<signal name="child-activated" handler="emoji_activated"/>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<child>
|
||||
<object class="GtkButton" id="recent.button">
|
||||
<property name="relief">none</property>
|
||||
<style>
|
||||
<class name="emoji-section"/>
|
||||
</style>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="people.button">
|
||||
<property name="relief">none</property>
|
||||
<style>
|
||||
<class name="emoji-section"/>
|
||||
</style>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="body.button">
|
||||
<property name="relief">none</property>
|
||||
<style>
|
||||
<class name="emoji-section"/>
|
||||
</style>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="nature.button">
|
||||
<property name="relief">none</property>
|
||||
<style>
|
||||
<class name="emoji-section"/>
|
||||
</style>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="food.button">
|
||||
<property name="relief">none</property>
|
||||
<style>
|
||||
<class name="emoji-section"/>
|
||||
</style>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="travel.button">
|
||||
<property name="relief">none</property>
|
||||
<style>
|
||||
<class name="emoji-section"/>
|
||||
</style>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="activities.button">
|
||||
<property name="relief">none</property>
|
||||
<style>
|
||||
<class name="emoji-section"/>
|
||||
</style>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="objects.button">
|
||||
<property name="relief">none</property>
|
||||
<style>
|
||||
<class name="emoji-section"/>
|
||||
</style>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="symbols.button">
|
||||
<property name="relief">none</property>
|
||||
<style>
|
||||
<class name="emoji-section"/>
|
||||
</style>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="flags.button">
|
||||
<property name="relief">none</property>
|
||||
<style>
|
||||
<class name="emoji-section"/>
|
||||
</style>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="name">list</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkGrid">
|
||||
<property name="row-spacing">12</property>
|
||||
<property name="halign">center</property>
|
||||
<property name="valign">center</property>
|
||||
<style>
|
||||
<class name="dim-label"/>
|
||||
</style>
|
||||
<child>
|
||||
<object class="GtkImage">
|
||||
<property name="icon-name">edit-find-symbolic</property>
|
||||
<property name="pixel-size">72</property>
|
||||
<style>
|
||||
<class name="dim-label"/>
|
||||
</style>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left-attach">0</property>
|
||||
<property name="top-attach">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="label" translatable="yes">No Results Found</property>
|
||||
<attributes>
|
||||
<attribute name="weight" value="bold"/>
|
||||
<attribute name="scale" value="1.44"/>
|
||||
</attributes>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left-attach">0</property>
|
||||
<property name="top-attach">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="label" translatable="yes">Try a different search</property>
|
||||
<style>
|
||||
<class name="dim-label"/>
|
||||
</style>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left-attach">0</property>
|
||||
<property name="top-attach">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="name">empty</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</template>
|
||||
</interface>
|
@ -114,6 +114,7 @@ gtk/gtkdragdest.c
|
||||
gtk/gtkdragsource.c
|
||||
gtk/gtkdrawingarea.c
|
||||
gtk/gtkeditable.c
|
||||
gtk/gtkemojichooser.c
|
||||
gtk/gtkentrybuffer.c
|
||||
gtk/gtkentry.c
|
||||
gtk/gtkentrycompletion.c
|
||||
@ -329,6 +330,7 @@ gtk/ui/gtkassistant.ui
|
||||
gtk/ui/gtkcolorchooserdialog.ui
|
||||
gtk/ui/gtkcoloreditor.ui
|
||||
gtk/ui/gtkdialog.ui
|
||||
gtk/ui/gtkemojichooser.ui
|
||||
gtk/ui/gtkfilechooserdialog.ui
|
||||
gtk/ui/gtkfilechooserwidget.ui
|
||||
gtk/ui/gtkfontchooserdialog.ui
|
||||
|
Loading…
Reference in New Issue
Block a user