diff --git a/data/gresource.xml b/data/gresource.xml
index 2c15f6543..98c960f7d 100644
--- a/data/gresource.xml
+++ b/data/gresource.xml
@@ -61,6 +61,7 @@
icons/scalable/actions/tuba-newspaper-symbolic.svg
icons/scalable/actions/tuba-explore2-large-symbolic.svg
icons/scalable/actions/tuba-text-justify-left-symbolic.svg
+ icons/scalable/actions/tuba-cat-symbolic.svg
gtk/dropdown/icon.ui
diff --git a/data/icons/scalable/actions/tuba-cat-symbolic.svg b/data/icons/scalable/actions/tuba-cat-symbolic.svg
new file mode 100644
index 000000000..fa81255c8
--- /dev/null
+++ b/data/icons/scalable/actions/tuba-cat-symbolic.svg
@@ -0,0 +1,2 @@
+
+
diff --git a/src/API/Emoji.vala b/src/API/Emoji.vala
index b788eeee2..da25bf240 100644
--- a/src/API/Emoji.vala
+++ b/src/API/Emoji.vala
@@ -1,4 +1,11 @@
public class Tuba.API.Emoji : Entity {
public string shortcode { get; set; }
public string url { get; set; }
+ public string category { get; set; default=_("Other"); }
+ public bool visible_in_picker { get; set; default=true; }
+ public bool is_other {
+ get {
+ return category == _("Other");
+ }
+ }
}
diff --git a/src/Application.vala b/src/Application.vala
index 5ac21a95e..ae689326d 100644
--- a/src/Application.vala
+++ b/src/Application.vala
@@ -127,7 +127,9 @@ namespace Tuba {
streams = new Streams ();
network = new Network ();
entity_cache = new EntityCache ();
- image_cache = new ImageCache ();
+ image_cache = new ImageCache () {
+ maintenance_secs = 60 * 5
+ };
accounts = new SecretAccountStore();
accounts.init ();
diff --git a/src/Dialogs/Composer/Completion/CompletionProvider.vala b/src/Dialogs/Composer/Completion/CompletionProvider.vala
index c38d5efef..70ceaff3d 100644
--- a/src/Dialogs/Composer/Completion/CompletionProvider.vala
+++ b/src/Dialogs/Composer/Completion/CompletionProvider.vala
@@ -7,13 +7,6 @@ public abstract class Tuba.CompletionProvider: Object, GtkSource.CompletionProvi
public string? trigger_char { get; construct; }
protected bool is_capturing_input { get; set; default = false; }
protected int empty_triggers = 0;
- protected ImageCache image_cache;
-
- construct {
- image_cache = new ImageCache () {
- maintenance_secs = 60
- };
- }
public virtual bool is_trigger (Gtk.TextIter iter, unichar ch) {
if (this.trigger_char == null) {
diff --git a/src/Dialogs/Composer/EditorPage.vala b/src/Dialogs/Composer/EditorPage.vala
index 6d146ad37..6ccc1d617 100644
--- a/src/Dialogs/Composer/EditorPage.vala
+++ b/src/Dialogs/Composer/EditorPage.vala
@@ -26,6 +26,7 @@ public class Tuba.EditorPage : ComposerPage {
install_languages (status.language);
add_button (new Gtk.Separator (Orientation.VERTICAL));
install_cw (status.spoiler_text);
+ add_button (new Gtk.Separator (Orientation.VERTICAL));
install_emoji_picker();
validate ();
@@ -199,9 +200,18 @@ public class Tuba.EditorPage : ComposerPage {
tooltip_text = _("Emoji Picker")
};
+ var custom_emoji_picker = new Widgets.CustomEmojiChooser ();
+ var custom_emoji_button = new MenuButton () {
+ icon_name = "tuba-cat-symbolic",
+ popover = custom_emoji_picker,
+ tooltip_text = _("Custom Emoji Picker")
+ };
+
add_button(emoji_button);
+ add_button(custom_emoji_button);
emoji_picker.emoji_picked.connect(on_emoji_picked);
+ custom_emoji_picker.emoji_picked.connect(on_emoji_picked);
}
protected void on_emoji_picked(string emoji_unicode) {
diff --git a/src/Views/MediaViewer.vala b/src/Views/MediaViewer.vala
index 111dbb15d..a05326ff4 100644
--- a/src/Views/MediaViewer.vala
+++ b/src/Views/MediaViewer.vala
@@ -201,7 +201,6 @@ public class Tuba.Views.MediaViewer : Gtk.Box {
private Gee.ArrayList- items = new Gee.ArrayList
- ();
protected Gtk.Button fullscreen_btn;
protected Adw.HeaderBar headerbar;
- protected ImageCache image_cache;
private Adw.Carousel carousel;
private Adw.CarouselIndicatorDots carousel_dots;
@@ -224,10 +223,6 @@ public class Tuba.Views.MediaViewer : Gtk.Box {
overlay.add_overlay (generate_media_buttons ());
overlay.child = carousel;
- image_cache = new ImageCache () {
- maintenance_secs = 60 * 5
- };
-
var drag = new Gtk.GestureDrag ();
drag.drag_begin.connect(on_drag_begin);
drag.drag_update.connect(on_drag_update);
diff --git a/src/Widgets/CustomEmojiChooser.vala b/src/Widgets/CustomEmojiChooser.vala
new file mode 100644
index 000000000..687b70854
--- /dev/null
+++ b/src/Widgets/CustomEmojiChooser.vala
@@ -0,0 +1,197 @@
+public class Tuba.Widgets.CustomEmojiChooser : Gtk.Popover {
+ public string query { get; set; default = ""; }
+ public signal void emoji_picked (string shortcode);
+ public bool is_populated { get; protected set; default=false; }
+
+ private Gee.HashMap> gen_emojis_cat_map () {
+ var res = new Gee.HashMap>();
+ var emojis = accounts.active.instance_emojis;
+
+ if (emojis != null && emojis.size > 0) {
+ emojis.@foreach (e => {
+ if (!e.visible_in_picker) return true;
+
+ if (res.has_key(e.category)) {
+ var array = res.get (e.category);
+ array.add (e);
+ } else {
+ var array = new Gee.ArrayList ();
+ array.add (e);
+ res.set(e.category, array);
+ }
+
+ return true;
+ });
+ }
+
+ return res;
+ }
+
+ private Gtk.Box custom_emojis_box;
+ private Gtk.SearchEntry entry;
+ private Gtk.FlowBox results;
+ private Gtk.Label results_label;
+ private Gtk.ScrolledWindow custom_emojis_scrolled;
+ private GLib.ListStore list_store = new GLib.ListStore (typeof (API.Emoji));
+ construct {
+ this.add_css_class ("emoji-picker");
+ custom_emojis_box = new Gtk.Box (Gtk.Orientation.VERTICAL, 6) {
+ margin_end = 6,
+ margin_bottom = 6,
+ margin_start = 6
+ };
+ custom_emojis_scrolled = new Gtk.ScrolledWindow () {
+ hscrollbar_policy = Gtk.PolicyType.NEVER,
+ height_request = 360
+ };
+ var content_box = new Gtk.Box (Gtk.Orientation.VERTICAL, 0);
+ content_box.append (custom_emojis_scrolled);
+
+ this.child = content_box;
+ custom_emojis_scrolled.child = custom_emojis_box;
+
+ results_label = create_category_label (_("Results"));
+ results_label.visible = false;
+ custom_emojis_box.append (results_label);
+
+ results = create_emoji_box ();
+ custom_emojis_box.append (results);
+
+ results.bind_model (list_store, model => {
+ var emoji = model as API.Emoji;
+ if (emoji == null) Process.exit (0);
+ return create_emoji_button (emoji);
+ });
+
+ entry = new Gtk.SearchEntry () {
+ text = query,
+ hexpand = true
+ };
+
+ var entry_bin = new Adw.Bin () {
+ css_classes = { "emoji-searchbar" },
+ child = entry
+ };
+ content_box.prepend (entry_bin);
+
+ entry.activate.connect (search);
+ entry.search_changed.connect (search);
+ entry.stop_search.connect (search);
+ }
+
+ protected void search () {
+ query = entry.text.chug ().chomp ().down ().replace (":", "");
+ list_store.remove_all ();
+
+ if (query == "") {
+ results_label.visible = false;
+ return;
+ }
+
+ var emojis = accounts.active.instance_emojis;
+
+ if (emojis != null && emojis.size > 0) {
+ var at_least_one = false;
+ emojis.@foreach (e => {
+ if (!e.visible_in_picker) return true;
+ if (query in e.shortcode) {
+ at_least_one = true;
+ list_store.append (e);
+ };
+
+ return true;
+ });
+
+ if (at_least_one) {
+ results_label.label = _("Results");
+ custom_emojis_scrolled.scroll_child (Gtk.ScrollType.START, false);
+ } else {
+ results_label.label = _("No Results");
+ }
+
+ results_label.visible = true;
+ }
+ }
+
+ protected void on_custom_emoji_picked (Gtk.Button emoji_btn) {
+ var emoji = emoji_btn.child as Emoji;
+ if (emoji != null) {
+ on_close ();
+ emoji_picked (@":$(emoji.shortcode):");
+ }
+ }
+
+ protected void on_close () {
+ this.popdown ();
+ }
+
+ public override void show () {
+ base.show ();
+
+ GLib.Idle.add (() => {
+ if (!is_populated) populate_chooser ();
+ entry.grab_focus ();
+ return GLib.Source.REMOVE;
+ });
+ }
+
+ protected void populate_chooser () {
+ var categorized_custom_emojis = gen_emojis_cat_map ();
+
+ categorized_custom_emojis.@foreach (e => {
+ if (e.key == _("Other")) return true;
+ create_category (e.key, e.value);
+
+ return true;
+ });
+
+ if (categorized_custom_emojis.has_key (_("Other")))
+ create_category (categorized_custom_emojis.size > 1 ? _("Other") : _("Custom Emojis"), categorized_custom_emojis.get(_("Other")));
+
+ is_populated = true;
+ }
+
+ protected Gtk.Button create_emoji_button (API.Emoji emoji) {
+ var emoji_btn = new Gtk.Button () {
+ css_classes = { "flat" },
+ child = new Widgets.Emoji (emoji.url, emoji.shortcode)
+ };
+ emoji_btn.set_css_name ("emoji");
+
+ emoji_btn.clicked.connect (on_custom_emoji_picked);
+ return emoji_btn;
+ }
+
+ protected void create_category (string key, Gee.ArrayList value) {
+ custom_emojis_box.append (create_category_label (key));
+
+ var emojis_flowbox = create_emoji_box ();
+ value.@foreach (emoji => {
+ emojis_flowbox.append (create_emoji_button (emoji));
+
+ return true;
+ });
+
+ custom_emojis_box.append (emojis_flowbox);
+ }
+
+ protected Gtk.FlowBox create_emoji_box () {
+ return new Gtk.FlowBox () {
+ homogeneous = true,
+ column_spacing = 6,
+ row_spacing = 6,
+ max_children_per_line = 6,
+ min_children_per_line = 6,
+ selection_mode = Gtk.SelectionMode.NONE
+ };
+ }
+
+ protected Gtk.Label create_category_label (string label) {
+ return new Gtk.Label (label) {
+ wrap = true,
+ wrap_mode = Pango.WrapMode.WORD_CHAR,
+ halign = Gtk.Align.START,
+ margin_top = 3
+ };
+ }
+}
diff --git a/src/Widgets/Emoji.vala b/src/Widgets/Emoji.vala
index 81387c47a..4a494041d 100644
--- a/src/Widgets/Emoji.vala
+++ b/src/Widgets/Emoji.vala
@@ -4,21 +4,27 @@ using Gdk;
public class Tuba.Widgets.Emoji : Adw.Bin {
protected Image image;
+ public string? shortcode { get; set; }
construct {
image = new Gtk.Image ();
child = image;
}
- public Emoji (string emoji_url, string? shortcode = null) {
- if (shortcode != null)
- image.tooltip_text = shortcode;
- image_cache.request_paintable (emoji_url, on_cache_response);
+ public Emoji (string emoji_url, string? t_shortcode = null) {
+ if (t_shortcode != null) {
+ image.tooltip_text = t_shortcode;
+ shortcode = t_shortcode;
+ }
+
+ GLib.Idle.add (() => {
+ image_cache.request_paintable (emoji_url, on_cache_response);
+ return GLib.Source.REMOVE;
+ });
}
void on_cache_response (bool is_loaded, owned Paintable? data) {
- var image_widget = (child as Image);
- if (child != null && image_widget != null)
- image_widget.paintable = data;
+ if (image != null)
+ image.paintable = data;
}
}
diff --git a/src/Widgets/meson.build b/src/Widgets/meson.build
index c11ccfc9d..b571a9caa 100644
--- a/src/Widgets/meson.build
+++ b/src/Widgets/meson.build
@@ -3,6 +3,7 @@ sources += files(
'Background.vala',
'BookWyrmPage.vala',
'Conversation.vala',
+ 'CustomEmojiChooser.vala',
'Emoji.vala',
'EmojiLabel.vala',
'LabelWithWidgets.vala',