From 61915ca56617e8f45ae8bd85cb87f0b8a9a895b0 Mon Sep 17 00:00:00 2001 From: bobufa Date: Tue, 10 Jul 2018 00:31:39 +0200 Subject: initial search logic / display --- main/CMakeLists.txt | 3 + main/data/global_search.ui | 35 +++++ main/data/theme.css | 5 + main/data/unified_main_content.ui | 12 +- .../conversation_item_skeleton.vala | 2 +- main/src/ui/global_search.vala | 174 +++++++++++++++++++++ main/src/ui/unified_window.vala | 6 +- 7 files changed, 224 insertions(+), 13 deletions(-) create mode 100644 main/data/global_search.ui create mode 100644 main/src/ui/global_search.vala (limited to 'main') diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index d71dd0ef..1af08217 100644 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -29,6 +29,7 @@ set(RESOURCE_LIST chat_input.ui contact_details_dialog.ui conversation_list_titlebar.ui + global_search.ui conversation_selector/view.ui conversation_selector/chat_row_tooltip.ui conversation_selector/conversation_row.ui @@ -94,6 +95,7 @@ SOURCES src/ui/contact_details/dialog.vala src/ui/contact_details/muc_config_form_provider.vala src/ui/conversation_list_titlebar.vala + src/ui/global_search.vala src/ui/conversation_selector/chat_row.vala src/ui/conversation_selector/conversation_row.vala src/ui/conversation_selector/groupchat_pm_row.vala @@ -110,6 +112,7 @@ SOURCES src/ui/conversation_summary/subscription_notification.vala src/ui/conversation_titlebar/menu_entry.vala src/ui/conversation_titlebar/occupants_entry.vala + src/ui/conversation_titlebar/search_entry.vala src/ui/conversation_titlebar/view.vala src/ui/manage_accounts/account_row.vala src/ui/manage_accounts/add_account_dialog.vala diff --git a/main/data/global_search.ui b/main/data/global_search.ui new file mode 100644 index 00000000..cc5f043b --- /dev/null +++ b/main/data/global_search.ui @@ -0,0 +1,35 @@ + + + + diff --git a/main/data/theme.css b/main/data/theme.css index 52ca1af7..61f15af4 100644 --- a/main/data/theme.css +++ b/main/data/theme.css @@ -26,6 +26,11 @@ window.dino-main .dino-sidebar frame.collapsed { border-bottom: 1px solid @borders; } +window.dino-main .dino-sidebar textview, +window.dino-main .dino-sidebar textview text { + background-color: transparent; +} + window.dino-main .dino-chatinput frame box { background: @theme_base_color; diff --git a/main/data/unified_main_content.ui b/main/data/unified_main_content.ui index 9b396b34..61781ac4 100644 --- a/main/data/unified_main_content.ui +++ b/main/data/unified_main_content.ui @@ -49,16 +49,8 @@ 400 none - - vertical + True - - - True - 12 - - - @@ -72,4 +64,4 @@ - \ No newline at end of file + 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 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 search results").printf(match_count) + ""; + Gee.List messages = stream_interactor.get_module(SearchProcessor.IDENTITY).match_messages(search); + loaded_results += messages.size; + append_messages(messages); + } + + private void append_messages(Gee.List 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 before_message = stream_interactor.get_module(MessageStorage.IDENTITY).get_messages_before_message(conversation, message.local_time, message.id, 1); + Gee.List 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(@"$(Markup.escape_text(title))") { 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 = @"$display_name"; + 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"); } -- cgit v1.2.3-54-g00ecf