diff options
Diffstat (limited to 'main/src')
-rw-r--r-- | main/src/ui/conversation_summary/conversation_item_skeleton.vala | 2 | ||||
-rw-r--r-- | main/src/ui/global_search.vala | 174 | ||||
-rw-r--r-- | main/src/ui/unified_window.vala | 6 |
3 files changed, 179 insertions, 3 deletions
diff --git a/main/src/ui/conversation_summary/conversation_item_skeleton.vala b/main/src/ui/conversation_summary/conversation_item_skeleton.vala index a8da93ef..a4e45f7a 100644 --- a/main/src/ui/conversation_summary/conversation_item_skeleton.vala +++ b/main/src/ui/conversation_summary/conversation_item_skeleton.vala @@ -176,7 +176,7 @@ public class DefaultSkeletonHeader : Box { return datetime.format(format); } - public virtual string get_relative_time(DateTime datetime) { + public static string get_relative_time(DateTime datetime) { DateTime now = new DateTime.now_local(); TimeSpan timespan = now.difference(datetime); if (timespan > 365 * TimeSpan.DAY) { diff --git a/main/src/ui/global_search.vala b/main/src/ui/global_search.vala new file mode 100644 index 00000000..cadee9c1 --- /dev/null +++ b/main/src/ui/global_search.vala @@ -0,0 +1,174 @@ +using Gtk; +using Pango; + +using Dino.Entities; + +namespace Dino.Ui { + +[GtkTemplate (ui = "/im/dino/Dino/global_search.ui")] +class GlobalSearch : Box { + private StreamInteractor stream_interactor; + private string search = ""; + private int loaded_results = -1; + private Mutex reloading_mutex = Mutex(); + + [GtkChild] public SearchEntry search_entry; + [GtkChild] public Label entry_number_label; + [GtkChild] public ScrolledWindow results_scrolled; + [GtkChild] public Box results_box; + + public GlobalSearch init(StreamInteractor stream_interactor) { + this.stream_interactor = stream_interactor; + + search_entry.search_changed.connect(() => { + set_search(search_entry.text); + }); + + results_scrolled.vadjustment.notify["value"].connect(() => { + if (results_scrolled.vadjustment.upper - (results_scrolled.vadjustment.value + results_scrolled.vadjustment.page_size) < 100) { + if (!reloading_mutex.trylock()) return; + Gee.List<Message> new_messages = stream_interactor.get_module(SearchProcessor.IDENTITY).match_messages(search, loaded_results); + if (new_messages.size == 0) { + reloading_mutex.unlock(); + return; + } + loaded_results += new_messages.size; + append_messages(new_messages); + } + }); + results_scrolled.vadjustment.notify["upper"].connect_after(() => { + reloading_mutex.trylock(); + reloading_mutex.unlock(); + }); + return this; + } + + private void clear_search() { + results_box.@foreach((widget) => { widget.destroy(); }); + } + + private void set_search(string search) { + clear_search(); + this.search = search; + + int match_count = stream_interactor.get_module(SearchProcessor.IDENTITY).count_match_messages(search); + entry_number_label.label = "<i>" + _("%i search results").printf(match_count) + "</i>"; + Gee.List<Message> messages = stream_interactor.get_module(SearchProcessor.IDENTITY).match_messages(search); + loaded_results += messages.size; + append_messages(messages); + } + + private void append_messages(Gee.List<Message> messages) { + foreach (Message message in messages) { + if (message.from == null) { + print("wtf null\n"); + continue; + } + Conversation? conversation = stream_interactor.get_module(ConversationManager.IDENTITY).get_conversation_for_message(message); + Gee.List<Message> before_message = stream_interactor.get_module(MessageStorage.IDENTITY).get_messages_before_message(conversation, message.local_time, message.id, 1); + Gee.List<Message> after_message = stream_interactor.get_module(MessageStorage.IDENTITY).get_messages_after_message(conversation, message.local_time, message.id, 1); + + Box context_box = new Box(Orientation.VERTICAL, 5) { visible=true }; + if (before_message != null && before_message.size > 0) { + context_box.add(get_context_message_widget(before_message.first())); + } + context_box.add(get_match_message_widget(message)); + if (after_message != null && after_message.size > 0) { + context_box.add(get_context_message_widget(after_message.first())); + } + + Label date_label = new Label(ConversationSummary.DefaultSkeletonHeader.get_relative_time(message.time)) { xalign=0, visible=true }; + date_label.get_style_context().add_class("dim-label"); + + string display_name = Util.get_conversation_display_name(stream_interactor, conversation); + string title = message.type_ == Message.Type.GROUPCHAT ? _("In %s").printf(display_name) : _("With %s").printf(display_name); + Box header_box = new Box(Orientation.HORIZONTAL, 10) { margin_left=7, visible=true }; + header_box.add(new Label(@"<b>$(Markup.escape_text(title))</b>") { ellipsize=EllipsizeMode.END, xalign=0, use_markup=true, visible=true }); + header_box.add(date_label); + + Box result_box = new Box(Orientation.VERTICAL, 7) { visible=true }; + result_box.add(header_box); + result_box.add(context_box); + + results_box.add(result_box); + } + } + + // Workaround GTK TextView issues + private void force_alloc_width(Widget widget, int width) { + Allocation alloc = Allocation(); + widget.get_preferred_width(out alloc.width, null); + widget.get_preferred_height(out alloc.height, null); + alloc.width = width; + widget.size_allocate(alloc); + } + + private Widget get_match_message_widget(Message message) { + Grid grid = get_skeleton(message); + grid.margin_top = 3; + grid.margin_bottom = 3; + + string text = message.body.replace("\n", "").replace("\r", ""); + if (text.length > 200) { + int index = text.index_of(search); + if (index + search.length <= 100) { + text = text.substring(0, 150) + " … " + text.substring(text.length - 50, 50); + } else if (index >= text.length - 100) { + text = text.substring(0, 50) + " … " + text.substring(text.length - 150, 150); + } else { + text = text.substring(0, 25) + " … " + text.substring(index - 50, 50) + text.substring(index, 100) + " … " + text.substring(text.length - 25, 25); + } + } + TextView tv = new TextView() { wrap_mode=Gtk.WrapMode.WORD_CHAR, hexpand=true, visible=true }; + tv.buffer.text = text; + TextTag link_tag = tv.buffer.create_tag("hit", background: "yellow"); + + Regex url_regex = new Regex(search.down()); + MatchInfo match_info; + url_regex.match(text.down(), 0, out match_info); + for (; match_info.matches(); match_info.next()) { + int start; + int end; + match_info.fetch_pos(0, out start, out end); + start = text[0:start].char_count(); + end = text[0:end].char_count(); + TextIter start_iter; + TextIter end_iter; + tv.buffer.get_iter_at_offset(out start_iter, start); + tv.buffer.get_iter_at_offset(out end_iter, end); + tv.buffer.apply_tag(link_tag, start_iter, end_iter); + } + grid.attach(tv, 1, 1, 1, 1); + + // force_alloc_width(tv, this.width_request); + + Button button = new Button() { relief=ReliefStyle.NONE, visible=true }; + button.add(grid); + return button; + } + + private Grid get_context_message_widget(Message message) { + Grid grid = get_skeleton(message); + grid.margin_left = 7; + Label label = new Label(message.body.replace("\n", "").replace("\r", "")) { ellipsize=EllipsizeMode.MIDDLE, xalign=0, visible=true }; + grid.attach(label, 1, 1, 1, 1); + grid.opacity = 0.55; + return grid; + } + + private Grid get_skeleton(Message message) { + AvatarImage image = new AvatarImage() { height=32, width=32, margin_right=7, valign=Align.START, visible=true, allow_gray = false }; + image.set_jid(stream_interactor, message.from, message.account); + Grid grid = new Grid() { row_homogeneous=false, visible=true }; + grid.attach(image, 0, 0, 1, 2); + + string display_name = Util.get_display_name(stream_interactor, message.from, message.account); + string color = Util.get_name_hex_color(stream_interactor, message.account, message.from, false); // TODO Util.is_dark_theme(name_label) + Label name_label = new Label("") { use_markup=true, xalign=0, visible=true }; + name_label.label = @"<span size='small' foreground=\"#$color\">$display_name</span>"; + grid.attach(name_label, 1, 0, 1, 1); + return grid; + } +} + +} diff --git a/main/src/ui/unified_window.vala b/main/src/ui/unified_window.vala index 3292aa3d..e5444f9d 100644 --- a/main/src/ui/unified_window.vala +++ b/main/src/ui/unified_window.vala @@ -19,6 +19,7 @@ public class UnifiedWindow : Window { private Paned paned; private Revealer search_revealer; private SearchEntry search_entry; + private GlobalSearch search_box; private Stack stack = new Stack() { visible=true }; private StreamInteractor stream_interactor; @@ -41,9 +42,9 @@ public class UnifiedWindow : Window { conversation_titlebar.search_button.bind_property("active", search_revealer, "reveal-child", BindingFlags.SYNC_CREATE | BindingFlags.BIDIRECTIONAL); search_revealer.notify["child-revealed"].connect(() => { if (search_revealer.child_revealed) { - search_entry.grab_focus(); + search_box.search_entry.grab_focus(); } else { - search_entry.text = ""; + search_box.search_entry.text = ""; } }); @@ -96,6 +97,7 @@ public class UnifiedWindow : Window { chat_input = ((ChatInput.View) builder.get_object("chat_input")).init(stream_interactor); conversation_frame = ((ConversationSummary.ConversationView) builder.get_object("conversation_frame")).init(stream_interactor); filterable_conversation_list = ((ConversationSelector.View) builder.get_object("conversation_list")).init(stream_interactor); + search_box = ((GlobalSearch) builder.get_object("search_box")).init(stream_interactor); search_revealer = (Revealer) builder.get_object("search_revealer"); search_entry = (SearchEntry) builder.get_object("search_entry"); } |