aboutsummaryrefslogtreecommitdiff
path: root/main
diff options
context:
space:
mode:
authorfiaxh <git@mx.ax.lt>2017-03-24 00:15:00 +0100
committerfiaxh <git@mx.ax.lt>2017-03-24 00:24:33 +0100
commitc0314212a0d951494fe6397fa53a9c5689a3ff87 (patch)
treeb97793573e7729208bbaa1b7376842de7e6b8cec /main
parent5862e253377a5d6259dfa6a8cc0731f2f7028830 (diff)
downloaddino-c0314212a0d951494fe6397fa53a9c5689a3ff87.tar.gz
dino-c0314212a0d951494fe6397fa53a9c5689a3ff87.zip
Tab completion for MUC occupants
Diffstat (limited to 'main')
-rw-r--r--main/CMakeLists.txt4
-rw-r--r--main/data/chat_input.ui2
-rw-r--r--main/data/conversation_summary/view.ui1
-rw-r--r--main/src/ui/chat_input/occupants_tab_completer.vala116
-rw-r--r--main/src/ui/chat_input/smiley_converter.vala58
-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.vala4
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 };