Tab completion for MUC occupants
This commit is contained in:
parent
5862e25337
commit
c0314212a0
|
@ -41,13 +41,13 @@ public class MessageManager : StreamInteractionModule, Object {
|
|||
message_sent(message, conversation);
|
||||
}
|
||||
|
||||
public Gee.List<Entities.Message>? get_messages(Conversation conversation) {
|
||||
public Gee.List<Entities.Message>? get_messages(Conversation conversation, int count = 50) {
|
||||
if (messages.has_key(conversation) && messages[conversation].size > 0) {
|
||||
Gee.List<Entities.Message> db_messages = db.get_messages(conversation.counterpart, conversation.account, 50, messages[conversation][0]);
|
||||
Gee.List<Entities.Message> db_messages = db.get_messages(conversation.counterpart, conversation.account, count, messages[conversation][0]);
|
||||
db_messages.add_all(messages[conversation]);
|
||||
return db_messages;
|
||||
} else {
|
||||
Gee.List<Entities.Message> db_messages = db.get_messages(conversation.counterpart, conversation.account, 50, null);
|
||||
Gee.List<Entities.Message> db_messages = db.get_messages(conversation.counterpart, conversation.account, count, null);
|
||||
return db_messages;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -53,7 +53,10 @@ public class MucManager : StreamInteractionModule, Object {
|
|||
}
|
||||
|
||||
public ArrayList<Jid>? get_occupants(Jid jid, Account account) {
|
||||
return stream_interactor.get_module(PresenceManager.IDENTITY).get_full_jids(jid, account);
|
||||
if (is_groupchat(jid, account)) {
|
||||
return stream_interactor.get_module(PresenceManager.IDENTITY).get_full_jids(jid, account);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public ArrayList<Jid>? get_other_occupants(Jid jid, Account account) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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">
|
||||
|
|
116
main/src/ui/chat_input/occupants_tab_completer.vala
Normal file
116
main/src/ui/chat_input/occupants_tab_completer.vala
Normal file
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
58
main/src/ui/chat_input/smiley_converter.vala
Normal file
58
main/src/ui/chat_input/smiley_converter.vala
Normal file
|
@ -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];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
|
@ -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 };
|
||||
|
||||
|
|
Loading…
Reference in a new issue