summaryrefslogtreecommitdiff
path: root/components
diff options
context:
space:
mode:
authorouwou <26526779+ouwou@users.noreply.github.com>2021-01-11 18:27:46 -0500
committerouwou <26526779+ouwou@users.noreply.github.com>2021-01-11 18:27:46 -0500
commite8cbb9d3d1ecca25f1e0a31a75fac70c7a3ea0cb (patch)
treeecb21c74221f320422ff2390c940b921d02331f2 /components
parentdef598941a74d6960985171ef5f446bdf8858182 (diff)
downloadabaddon-portaudio-e8cbb9d3d1ecca25f1e0a31a75fac70c7a3ea0cb.tar.gz
abaddon-portaudio-e8cbb9d3d1ecca25f1e0a31a75fac70c7a3ea0cb.zip
add typing indicator with optional res/typing_indicator.gif
Diffstat (limited to 'components')
-rw-r--r--components/chatwindow.cpp17
-rw-r--r--components/chatwindow.hpp2
-rw-r--r--components/typingindicator.cpp106
-rw-r--r--components/typingindicator.hpp24
4 files changed, 147 insertions, 2 deletions
diff --git a/components/chatwindow.cpp b/components/chatwindow.cpp
index 2e688de..de1aa50 100644
--- a/components/chatwindow.cpp
+++ b/components/chatwindow.cpp
@@ -1,6 +1,7 @@
#include "chatwindow.hpp"
#include "chatmessage.hpp"
#include "../abaddon.hpp"
+#include "typingindicator.hpp"
ChatWindow::ChatWindow() {
m_main = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_VERTICAL));
@@ -8,13 +9,15 @@ ChatWindow::ChatWindow() {
m_scroll = Gtk::manage(new Gtk::ScrolledWindow);
m_input = Gtk::manage(new Gtk::TextView);
m_input_scroll = Gtk::manage(new Gtk::ScrolledWindow);
+ m_typing_indicator = Gtk::manage(new TypingIndicator);
+
+ m_typing_indicator->set_valign(Gtk::ALIGN_END);
+ m_typing_indicator->show();
m_main->get_style_context()->add_class("messages");
m_list->get_style_context()->add_class("messages");
m_input_scroll->get_style_context()->add_class("message-input");
- m_input->signal_key_press_event().connect(sigc::mem_fun(*this, &ChatWindow::on_key_press_event), false);
-
m_main->set_hexpand(true);
m_main->set_vexpand(true);
@@ -27,6 +30,7 @@ ChatWindow::ChatWindow() {
m_scroll->set_can_focus(false);
m_scroll->set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_ALWAYS);
+ m_scroll->show();
m_list->signal_size_allocate().connect([this](Gtk::Allocation &) {
if (m_should_scroll_to_bottom)
@@ -38,16 +42,20 @@ ChatWindow::ChatWindow() {
m_list->set_vexpand(true);
m_list->set_focus_hadjustment(m_scroll->get_hadjustment());
m_list->set_focus_vadjustment(m_scroll->get_vadjustment());
+ m_list->show();
m_input->set_hexpand(false);
m_input->set_halign(Gtk::ALIGN_FILL);
m_input->set_valign(Gtk::ALIGN_CENTER);
m_input->set_wrap_mode(Gtk::WRAP_WORD_CHAR);
+ m_input->signal_key_press_event().connect(sigc::mem_fun(*this, &ChatWindow::on_key_press_event), false);
+ m_input->show();
m_input_scroll->set_propagate_natural_height(true);
m_input_scroll->set_min_content_height(20);
m_input_scroll->set_max_content_height(250);
m_input_scroll->set_policy(Gtk::POLICY_NEVER, Gtk::POLICY_AUTOMATIC);
+ m_input_scroll->show();
m_completer.SetBuffer(m_input->get_buffer());
m_completer.SetGetChannelID([this]() -> auto {
@@ -80,11 +88,15 @@ ChatWindow::ChatWindow() {
return ret;
});
+ m_completer.show();
+
m_input_scroll->add(*m_input);
m_scroll->add(*m_list);
m_main->add(*m_scroll);
m_main->add(m_completer);
m_main->add(*m_input_scroll);
+ m_main->add(*m_typing_indicator);
+ m_main->show();
}
Gtk::Widget *ChatWindow::GetRoot() const {
@@ -114,6 +126,7 @@ void ChatWindow::SetMessages(const std::set<Snowflake> &msgs) {
void ChatWindow::SetActiveChannel(Snowflake id) {
m_active_channel = id;
+ m_typing_indicator->SetActiveChannel(id);
}
void ChatWindow::AddNewMessage(Snowflake id) {
diff --git a/components/chatwindow.hpp b/components/chatwindow.hpp
index bac27c0..c1740c4 100644
--- a/components/chatwindow.hpp
+++ b/components/chatwindow.hpp
@@ -6,6 +6,7 @@
#include "chatmessage.hpp"
#include "completer.hpp"
+class TypingIndicator;
class ChatWindow {
public:
ChatWindow();
@@ -47,6 +48,7 @@ protected:
Gtk::ScrolledWindow *m_input_scroll;
Completer m_completer;
+ TypingIndicator *m_typing_indicator;
public:
typedef sigc::signal<void, Snowflake, Snowflake> type_signal_action_message_delete;
diff --git a/components/typingindicator.cpp b/components/typingindicator.cpp
new file mode 100644
index 0000000..cc0ae7d
--- /dev/null
+++ b/components/typingindicator.cpp
@@ -0,0 +1,106 @@
+#include <filesystem>
+#include "typingindicator.hpp"
+#include "../abaddon.hpp"
+#include "../util.hpp"
+
+constexpr static const int MaxUsersInIndicator = 4;
+
+TypingIndicator::TypingIndicator()
+ : Gtk::Box(Gtk::ORIENTATION_HORIZONTAL) {
+ m_label.set_text("");
+ m_label.set_ellipsize(Pango::ELLIPSIZE_END);
+ m_label.set_valign(Gtk::ALIGN_END);
+ m_img.set_margin_right(5);
+ get_style_context()->add_class("typing-indicator");
+
+ Abaddon::Get().GetDiscordClient().signal_typing_start().connect(sigc::mem_fun(*this, &TypingIndicator::OnUserTypingStart));
+ Abaddon::Get().GetDiscordClient().signal_message_create().connect(sigc::mem_fun(*this, &TypingIndicator::OnMessageCreate));
+
+ add(m_img);
+ add(m_label);
+ m_label.show();
+
+ // try loading gif
+ if (!std::filesystem::exists("./res/typing_indicator.gif")) return;
+ auto gif_data = ReadWholeFile("./res/typing_indicator.gif");
+ auto loader = Gdk::PixbufLoader::create();
+ loader->signal_size_prepared().connect([&](int inw, int inh) {
+ int w, h;
+ GetImageDimensions(inw, inh, w, h, 20, 10);
+ loader->set_size(w, h);
+ });
+ loader->write(gif_data.data(), gif_data.size());
+ try {
+ loader->close();
+ m_img.property_pixbuf_animation() = loader->get_animation();
+ } catch (const std::exception &) {}
+}
+
+void TypingIndicator::AddUser(Snowflake channel_id, const UserData &user, int timeout) {
+ auto current_connection_it = m_typers[channel_id].find(user.ID);
+ if (current_connection_it != m_typers.at(channel_id).end()) {
+ current_connection_it->second.disconnect();
+ m_typers.at(channel_id).erase(current_connection_it);
+ }
+
+ Snowflake user_id = user.ID;
+ auto cb = [this, user_id, channel_id]() -> bool {
+ m_typers.at(channel_id).erase(user_id);
+ ComputeTypingString();
+ return false;
+ };
+ m_typers[channel_id][user.ID] = Glib::signal_timeout().connect_seconds(cb, timeout);
+ ComputeTypingString();
+}
+
+void TypingIndicator::SetActiveChannel(Snowflake id) {
+ m_active_channel = id;
+ ComputeTypingString();
+}
+
+void TypingIndicator::OnUserTypingStart(Snowflake user_id, Snowflake channel_id) {
+ const auto &discord = Abaddon::Get().GetDiscordClient();
+ const auto user = discord.GetUser(user_id);
+ if (!user.has_value()) return;
+
+ AddUser(channel_id, *user, 10);
+}
+
+void TypingIndicator::OnMessageCreate(Snowflake message_id) {
+ const auto msg = Abaddon::Get().GetDiscordClient().GetMessage(message_id);
+ if (!msg.has_value()) return;
+ m_typers[msg->ChannelID].erase(msg->Author.ID);
+ ComputeTypingString();
+}
+
+void TypingIndicator::SetTypingString(const Glib::ustring &str) {
+ m_label.set_text(str);
+ if (str == "")
+ m_img.hide();
+ else if (m_img.property_pixbuf_animation().get_value())
+ m_img.show();
+}
+
+void TypingIndicator::ComputeTypingString() {
+ const auto &discord = Abaddon::Get().GetDiscordClient();
+ std::vector<UserData> typers;
+ for (const auto &[id, conn] : m_typers[m_active_channel]) {
+ const auto user = discord.GetUser(id);
+ if (user.has_value())
+ typers.push_back(*user);
+ }
+ if (typers.size() == 0) {
+ SetTypingString("");
+ } else if (typers.size() == 1) {
+ SetTypingString(typers[0].Username + " is typing...");
+ } else if (typers.size() == 2) {
+ SetTypingString(typers[0].Username + " and " + typers[1].Username + " are typing...");
+ } else if (typers.size() > 2 && typers.size() <= MaxUsersInIndicator) {
+ Glib::ustring str;
+ for (int i = 0; i < typers.size() - 1; i++)
+ str += typers[i].Username + ", ";
+ SetTypingString(str + "and " + typers[typers.size() - 1].Username + " are typing...");
+ } else { // size() > MaxUsersInIndicator
+ SetTypingString("Several people are typing...");
+ }
+}
diff --git a/components/typingindicator.hpp b/components/typingindicator.hpp
new file mode 100644
index 0000000..d9633f4
--- /dev/null
+++ b/components/typingindicator.hpp
@@ -0,0 +1,24 @@
+#pragma once
+#include <gtkmm.h>
+#include <unordered_map>
+#include "../discord/snowflake.hpp"
+#include "../discord/user.hpp"
+
+class TypingIndicator : public Gtk::Box {
+public:
+ TypingIndicator();
+ void SetActiveChannel(Snowflake id);
+
+private:
+ void AddUser(Snowflake channel_id, const UserData &user, int timeout);
+ void OnUserTypingStart(Snowflake user_id, Snowflake channel_id);
+ void OnMessageCreate(Snowflake message_id);
+ void SetTypingString(const Glib::ustring &str);
+ void ComputeTypingString();
+
+ Gtk::Image m_img;
+ Gtk::Label m_label;
+
+ Snowflake m_active_channel;
+ std::unordered_map<Snowflake, std::unordered_map<Snowflake, sigc::connection>> m_typers; // channel id -> [user id -> connection]
+};