aboutsummaryrefslogtreecommitdiff
path: root/main/src/ui/chat_input
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/src/ui/chat_input
parent5862e253377a5d6259dfa6a8cc0731f2f7028830 (diff)
downloaddino-c0314212a0d951494fe6397fa53a9c5689a3ff87.tar.gz
dino-c0314212a0d951494fe6397fa53a9c5689a3ff87.zip
Tab completion for MUC occupants
Diffstat (limited to 'main/src/ui/chat_input')
-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.vala102
3 files changed, 276 insertions, 0 deletions
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/view.vala b/main/src/ui/chat_input/view.vala
new file mode 100644
index 00000000..310c0f35
--- /dev/null
+++ b/main/src/ui/chat_input/view.vala
@@ -0,0 +1,102 @@
+using Gdk;
+using Gee;
+using Gtk;
+
+using Dino.Entities;
+using Xmpp;
+
+namespace Dino.Ui.ChatInput {
+
+[GtkTemplate (ui = "/org/dino-im/chat_input.ui")]
+public class View : Box {
+
+ [GtkChild] private ScrolledWindow scrolled;
+ [GtkChild] private TextView text_input;
+
+ private StreamInteractor stream_interactor;
+ private Conversation? conversation;
+ private HashMap<Conversation, string> entry_cache = new HashMap<Conversation, string>(Conversation.hash_func, Conversation.equals_func);
+ private int vscrollbar_min_height;
+ private OccupantsTabCompletor occupants_tab_completor;
+ private SmileyConverter smiley_converter;
+
+ 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);
+ text_input.buffer.changed.connect(on_text_input_changed);
+ }
+
+ 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;
+
+ text_input.buffer.changed.disconnect(on_text_input_changed);
+ text_input.buffer.text = "";
+ if (entry_cache.has_key(conversation)) {
+ text_input.buffer.text = entry_cache[conversation];
+ }
+ text_input.buffer.changed.connect(on_text_input_changed);
+
+ text_input.grab_focus();
+ }
+
+ private void send_text() {
+ string text = text_input.buffer.text;
+ if (text.has_prefix("/")) {
+ string[] token = text.split(" ", 2);
+ switch(token[0]) {
+ case "/kick":
+ stream_interactor.get_module(MucManager.IDENTITY).kick(conversation.account, conversation.counterpart, token[1]);
+ break;
+ case "/me":
+ stream_interactor.get_module(MessageManager.IDENTITY).send_message(text, conversation);
+ break;
+ case "/nick":
+ stream_interactor.get_module(MucManager.IDENTITY).change_nick(conversation.account, conversation.counterpart, token[1]);
+ break;
+ case "/topic":
+ stream_interactor.get_module(MucManager.IDENTITY).change_subject(conversation.account, conversation.counterpart, token[1]);
+ break;
+ }
+ } else {
+ stream_interactor.get_module(MessageManager.IDENTITY).send_message(text, conversation);
+ }
+ text_input.buffer.text = "";
+ }
+
+ private bool on_text_input_key_press(EventKey event) {
+ if (event.keyval == Key.Return) {
+ if ((event.state & ModifierType.SHIFT_MASK) > 0) {
+ text_input.buffer.insert_at_cursor("\n", 1);
+ } else if (text_input.buffer.text != ""){
+ send_text();
+ }
+ return true;
+ }
+ return false;
+ }
+
+ private void on_upper_notify() {
+ scrolled.vadjustment.value = scrolled.vadjustment.upper - scrolled.vadjustment.page_size;
+
+ // hack for vscrollbar not requiring space and making textview higher //TODO doesn't resize immediately
+ scrolled.get_vscrollbar().visible = (scrolled.vadjustment.upper > scrolled.max_content_height - 2 * vscrollbar_min_height);
+ }
+
+ private void on_text_input_changed() {
+ if (text_input.buffer.text != "") {
+ stream_interactor.get_module(ChatInteraction.IDENTITY).on_message_entered(conversation);
+ } else {
+ stream_interactor.get_module(ChatInteraction.IDENTITY).on_message_cleared(conversation);
+ }
+ }
+}
+
+} \ No newline at end of file