diff options
author | fiaxh <git@mx.ax.lt> | 2017-03-24 00:15:00 +0100 |
---|---|---|
committer | fiaxh <git@mx.ax.lt> | 2017-03-24 00:24:33 +0100 |
commit | c0314212a0d951494fe6397fa53a9c5689a3ff87 (patch) | |
tree | b97793573e7729208bbaa1b7376842de7e6b8cec /main | |
parent | 5862e253377a5d6259dfa6a8cc0731f2f7028830 (diff) | |
download | dino-c0314212a0d951494fe6397fa53a9c5689a3ff87.tar.gz dino-c0314212a0d951494fe6397fa53a9c5689a3ff87.zip |
Tab completion for MUC occupants
Diffstat (limited to 'main')
-rw-r--r-- | main/CMakeLists.txt | 4 | ||||
-rw-r--r-- | main/data/chat_input.ui | 2 | ||||
-rw-r--r-- | main/data/conversation_summary/view.ui | 1 | ||||
-rw-r--r-- | main/src/ui/chat_input/occupants_tab_completer.vala | 116 | ||||
-rw-r--r-- | main/src/ui/chat_input/smiley_converter.vala | 58 | ||||
-rw-r--r-- | main/src/ui/chat_input/view.vala (renamed from main/src/ui/chat_input.vala) | 47 | ||||
-rw-r--r-- | main/src/ui/unified_window.vala | 4 |
7 files changed, 192 insertions, 40 deletions
diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index 60d91c7b..ace08960 100644 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -68,7 +68,9 @@ SOURCES src/ui/add_conversation/list_row.vala src/ui/add_conversation/select_jid_fragment.vala src/ui/avatar_generator.vala - src/ui/chat_input.vala + src/ui/chat_input/occupants_tab_completer.vala + src/ui/chat_input/smiley_converter.vala + src/ui/chat_input/view.vala src/ui/conversation_list_titlebar.vala src/ui/conversation_selector/chat_row.vala src/ui/conversation_selector/conversation_row.vala diff --git a/main/data/chat_input.ui b/main/data/chat_input.ui index f8a1c2f7..2436ff82 100644 --- a/main/data/chat_input.ui +++ b/main/data/chat_input.ui @@ -1,7 +1,7 @@ <?xml version="1.0" encoding="UTF-8"?> <interface> <requires lib="gtk+" version="3.22"/> - <template class="DinoUiChatInput"> + <template class="DinoUiChatInputView"> <property name="hexpand">True</property> <property name="orientation">horizontal</property> <property name="margin">5</property> diff --git a/main/data/conversation_summary/view.ui b/main/data/conversation_summary/view.ui index d00314fc..07fb7b71 100644 --- a/main/data/conversation_summary/view.ui +++ b/main/data/conversation_summary/view.ui @@ -11,6 +11,7 @@ <property name="visible">True</property> <child> <object class="GtkScrolledWindow" id="scrolled"> + <property name="hscrollbar_policy">never</property> <property name="visible">True</property> <child> <object class="GtkBox"> diff --git a/main/src/ui/chat_input/occupants_tab_completer.vala b/main/src/ui/chat_input/occupants_tab_completer.vala new file mode 100644 index 00000000..93b0e7ff --- /dev/null +++ b/main/src/ui/chat_input/occupants_tab_completer.vala @@ -0,0 +1,116 @@ +using Gdk; +using Gee; +using Gtk; + +using Dino.Entities; + +namespace Dino.Ui.ChatInput { + +/** + * - With given prefix: Complete from occupant list (sorted lexicographically) + * - W/o prefix: Complete from received messages (most recent first) + * - At the start (with ",") and in the middle of a text + * - Backwards tabbing + */ +class OccupantsTabCompletor { + + private StreamInteractor stream_interactor; + private Conversation? conversation; + private TextView text_input; + + private Gee.List<string> completions = new ArrayList<string>(); + private bool active = false; + private int index = -1; + + public OccupantsTabCompletor(StreamInteractor stream_interactor, TextView text_input) { + this.stream_interactor = stream_interactor; + this.text_input = text_input; + + text_input.key_press_event.connect(on_text_input_key_press); + } + + public void initialize_for_conversation(Conversation conversation) { + this.conversation = conversation; + } + + public bool on_text_input_key_press(EventKey event) { + if (conversation.type_ == Conversation.Type.GROUPCHAT) { + if (event.keyval == Key.Tab || event.keyval == Key.ISO_Left_Tab) { + string text = text_input.buffer.text; + int start_index = int.max(text.last_index_of(" "), text.last_index_of("\n")) + 1; + string word = text.substring(start_index); + if (!active) { + if (word == "") { + completions = generate_completions_from_messages(); + } else { + completions = generate_completions_from_occupants(word); + } + if (completions.size > 0) { + active = true; + index = -1; + } + } + if (event.keyval != Key.ISO_Group_Shift && active) { + text_input.buffer.text = next_completion(event.keyval == Key.ISO_Left_Tab); + return true; + } + } else if (event.keyval != Key.Shift_L && active) { + active = false; + } + } + return false; + } + + private string next_completion(bool backwards) { + string text = text_input.buffer.text; + int start_index = int.max(text.last_index_of(" "), text.last_index_of("\n")) + 1; + string prev_completion = text.substring(start_index); + if (index > -1) { + start_index = int.max( + text.substring(0, text.length - 1).last_index_of(" "), + text.substring(0, text.length - 1).last_index_of("\n") + ) + 1; + prev_completion = text.substring(start_index); + } + if (backwards) { + index = int.max(index, 0) - 1; + if (index < 0) index = completions.size - 1; + } else { + index = (index + 1) % (completions.size); + } + if (start_index == 0) { + return completions[index] + ", "; + } else { + return text.substring(0, text.length - prev_completion.length) + completions[index] + " "; + } + } + + private Gee.List<string> generate_completions_from_messages() { + Gee.List<string> ret = new ArrayList<string>(); + Gee.List<Message>? messages = stream_interactor.get_module(MessageManager.IDENTITY).get_messages(conversation, 10); + if (messages != null) { + for (int i = messages.size - 1; i > 0; i--) { + string resourcepart = messages[i].from.resourcepart; + string own_nick = stream_interactor.get_module(MucManager.IDENTITY).get_nick(conversation.counterpart, conversation.account); + if (resourcepart != null && resourcepart != "" && resourcepart != own_nick && !ret.contains(resourcepart)) { + ret.add(resourcepart); + } + } + } + return ret; + } + + private Gee.List<string> generate_completions_from_occupants(string prefix) { + Gee.List<string> ret = new ArrayList<string>(); + Gee.List<Jid>? occupants = stream_interactor.get_module(MucManager.IDENTITY).get_other_occupants(conversation.counterpart, conversation.account); + if (occupants != null) { + foreach (Jid jid in occupants) { + if (jid.resourcepart.to_string().has_prefix(prefix)) ret.add(jid.resourcepart.to_string()); + } + } + ret.sort(); + return ret; + } +} + +}
\ No newline at end of file diff --git a/main/src/ui/chat_input/smiley_converter.vala b/main/src/ui/chat_input/smiley_converter.vala new file mode 100644 index 00000000..73413972 --- /dev/null +++ b/main/src/ui/chat_input/smiley_converter.vala @@ -0,0 +1,58 @@ +using Gdk; +using Gee; +using Gtk; + +using Dino.Entities; + +namespace Dino.Ui.ChatInput { + +class SmileyConverter { + + private StreamInteractor stream_interactor; + private TextView text_input; + private static HashMap<string, string> smiley_translations = new HashMap<string, string>(); + + static construct { + smiley_translations[":)"] = "🙂"; + smiley_translations[":D"] = "😀"; + smiley_translations[";)"] = "😉"; + smiley_translations["O:)"] = "😇"; + smiley_translations["O:-)"] = "😇"; + smiley_translations["]:>"] = "😈"; + smiley_translations[":o"] = "😮"; + smiley_translations[":P"] = "😛"; + smiley_translations[";P"] = "😜"; + smiley_translations[":("] = "🙁"; + smiley_translations[":'("] = "😢"; + smiley_translations[":/"] = "😕"; + } + + public SmileyConverter(StreamInteractor stream_interactor, TextView text_input) { + this.stream_interactor = stream_interactor; + this.text_input = text_input; + + text_input.key_press_event.connect(on_text_input_key_press); + } + + public bool on_text_input_key_press(EventKey event) { + if (event.keyval == Key.space || event.keyval == Key.Return) { + check_convert(); + } + return false; + } + + private void check_convert() { + if (Dino.Settings.instance().convert_utf8_smileys) { + foreach (string smiley in smiley_translations.keys) { + if (text_input.buffer.text.has_suffix(smiley)) { + if (text_input.buffer.text.length == smiley.length || + text_input.buffer.text[text_input.buffer.text.length - smiley.length - 1] == ' ') { + text_input.buffer.text = text_input.buffer.text.substring(0, text_input.buffer.text.length - smiley.length) + smiley_translations[smiley]; + } + } + } + } + } +} + +}
\ No newline at end of file diff --git a/main/src/ui/chat_input.vala b/main/src/ui/chat_input/view.vala index 1ca38786..310c0f35 100644 --- a/main/src/ui/chat_input.vala +++ b/main/src/ui/chat_input/view.vala @@ -5,37 +5,26 @@ using Gtk; using Dino.Entities; using Xmpp; -namespace Dino.Ui { +namespace Dino.Ui.ChatInput { [GtkTemplate (ui = "/org/dino-im/chat_input.ui")] -public class ChatInput : Box { +public class View : Box { [GtkChild] private ScrolledWindow scrolled; [GtkChild] private TextView text_input; - private Conversation? conversation; private StreamInteractor stream_interactor; + private Conversation? conversation; private HashMap<Conversation, string> entry_cache = new HashMap<Conversation, string>(Conversation.hash_func, Conversation.equals_func); - private static HashMap<string, string> smiley_translations = new HashMap<string, string>(); private int vscrollbar_min_height; + private OccupantsTabCompletor occupants_tab_completor; + private SmileyConverter smiley_converter; - static construct { - smiley_translations[":)"] = "🙂"; - smiley_translations[":D"] = "😀"; - smiley_translations[";)"] = "😉"; - smiley_translations["O:)"] = "😇"; - smiley_translations["]:>"] = "😈"; - smiley_translations[":o"] = "😮"; - smiley_translations[":P"] = "😛"; - smiley_translations[";P"] = "😜"; - smiley_translations[":("] = "🙁"; - smiley_translations[":'("] = "😢"; - smiley_translations[":/"] = "😕"; - smiley_translations["-.-"] = "😑"; - } - - public ChatInput(StreamInteractor stream_interactor) { + public View(StreamInteractor stream_interactor) { this.stream_interactor = stream_interactor; + occupants_tab_completor = new OccupantsTabCompletor(stream_interactor, text_input); + smiley_converter = new SmileyConverter(stream_interactor, text_input); + scrolled.get_vscrollbar().get_preferred_height(out vscrollbar_min_height, null); scrolled.vadjustment.notify["upper"].connect_after(on_upper_notify); text_input.key_press_event.connect(on_text_input_key_press); @@ -43,6 +32,8 @@ public class ChatInput : Box { } public void initialize_for_conversation(Conversation conversation) { + occupants_tab_completor.initialize_for_conversation(conversation); + if (this.conversation != null) entry_cache[this.conversation] = text_input.buffer.text; this.conversation = conversation; @@ -81,9 +72,6 @@ public class ChatInput : Box { } private bool on_text_input_key_press(EventKey event) { - if (event.keyval == Key.space || event.keyval == Key.Return) { - check_convert_smiley(); - } if (event.keyval == Key.Return) { if ((event.state & ModifierType.SHIFT_MASK) > 0) { text_input.buffer.insert_at_cursor("\n", 1); @@ -102,19 +90,6 @@ public class ChatInput : Box { scrolled.get_vscrollbar().visible = (scrolled.vadjustment.upper > scrolled.max_content_height - 2 * vscrollbar_min_height); } - private void check_convert_smiley() { - if (Dino.Settings.instance().convert_utf8_smileys) { - foreach (string smiley in smiley_translations.keys) { - if (text_input.buffer.text.has_suffix(smiley)) { - if (text_input.buffer.text.length == smiley.length || - text_input.buffer.text[text_input.buffer.text.length - smiley.length - 1] == ' ') { - text_input.buffer.text = text_input.buffer.text.substring(0, text_input.buffer.text.length - smiley.length) + smiley_translations[smiley]; - } - } - } - } - } - private void on_text_input_changed() { if (text_input.buffer.text != "") { stream_interactor.get_module(ChatInteraction.IDENTITY).on_message_entered(conversation); diff --git a/main/src/ui/unified_window.vala b/main/src/ui/unified_window.vala index 4a409128..66a4a087 100644 --- a/main/src/ui/unified_window.vala +++ b/main/src/ui/unified_window.vala @@ -9,7 +9,7 @@ public class UnifiedWindow : Window { private NoAccountsPlaceholder accounts_placeholder = new NoAccountsPlaceholder() { visible=true }; private NoConversationsPlaceholder conversations_placeholder = new NoConversationsPlaceholder() { visible=true }; - private ChatInput chat_input; + private ChatInput.View chat_input; private ConversationListTitlebar conversation_list_titlebar; private ConversationSelector.View filterable_conversation_list; private ConversationSummary.View conversation_frame; @@ -62,7 +62,7 @@ public class UnifiedWindow : Window { } private void setup_unified() { - chat_input = new ChatInput(stream_interactor) { visible=true }; + chat_input = new ChatInput.View(stream_interactor) { visible=true }; conversation_frame = new ConversationSummary.View(stream_interactor) { visible=true }; filterable_conversation_list = new ConversationSelector.View(stream_interactor) { visible=true }; |