summaryrefslogtreecommitdiff
path: root/components
diff options
context:
space:
mode:
authorouwou <26526779+ouwou@users.noreply.github.com>2020-09-25 23:02:13 -0400
committerouwou <26526779+ouwou@users.noreply.github.com>2020-09-25 23:02:13 -0400
commit3689f5ae9f439e9d3883817873928c25048a19cd (patch)
tree66cf7cf3f73c034783d96940ed1d0e7daa3e57a3 /components
parent9b78e829b59b57d003f82eb85177453ef88e21ec (diff)
downloadabaddon-portaudio-3689f5ae9f439e9d3883817873928c25048a19cd.tar.gz
abaddon-portaudio-3689f5ae9f439e9d3883817873928c25048a19cd.zip
redo chat messages just a tad
Diffstat (limited to 'components')
-rw-r--r--components/chatmessage.cpp520
-rw-r--r--components/chatmessage.hpp132
-rw-r--r--components/chatwindow.cpp458
-rw-r--r--components/chatwindow.hpp70
4 files changed, 494 insertions, 686 deletions
diff --git a/components/chatmessage.cpp b/components/chatmessage.cpp
index a16a244..74e9fb4 100644
--- a/components/chatmessage.cpp
+++ b/components/chatmessage.cpp
@@ -1,298 +1,150 @@
-#include <sstream>
-#include <iomanip>
#include "chatmessage.hpp"
#include "../abaddon.hpp"
#include "../util.hpp"
-ChatMessageContainer::ChatMessageContainer(const Message *data) {
- UserID = data->Author.ID;
- ChannelID = data->ChannelID;
-
- m_main_box = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL));
- m_content_box = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_VERTICAL));
- m_meta_box = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL));
- m_author = Gtk::manage(new Gtk::Label);
- m_timestamp = Gtk::manage(new Gtk::Label);
-
- auto buf = Abaddon::Get().GetImageManager().GetFromURLIfCached(data->Author.GetAvatarURL());
- if (buf)
- m_avatar = Gtk::manage(new Gtk::Image(buf));
- else
- m_avatar = Gtk::manage(new Gtk::Image(Abaddon::Get().GetImageManager().GetPlaceholder(32)));
-
- m_avatar->set_valign(Gtk::ALIGN_START);
- m_avatar->set_margin_right(10);
-
- get_style_context()->add_class("message-container");
- m_author->get_style_context()->add_class("message-container-author");
- m_timestamp->get_style_context()->add_class("message-container-timestamp");
- m_avatar->get_style_context()->add_class("message-container-avatar");
-
- m_author->set_markup("<span weight=\"bold\">" + Glib::Markup::escape_text(data->Author.Username) + "</span>");
- m_author->set_single_line_mode(true);
- m_author->set_line_wrap(false);
- m_author->set_ellipsize(Pango::ELLIPSIZE_END);
- m_author->set_xalign(0.f);
- m_author->set_can_focus(false);
- m_author->show();
-
- m_timestamp->set_text(data->Timestamp);
- m_timestamp->set_opacity(0.5);
- m_timestamp->set_single_line_mode(true);
- m_timestamp->set_margin_start(12);
- m_timestamp->set_can_focus(false);
- m_timestamp->show();
-
- m_main_box->set_hexpand(true);
- m_main_box->set_vexpand(true);
- m_main_box->set_can_focus(true);
- m_main_box->show();
-
- m_meta_box->set_can_focus(false);
- m_meta_box->show();
-
- m_content_box->set_can_focus(false);
- m_content_box->show();
-
- m_meta_box->add(*m_author);
- m_meta_box->add(*m_timestamp);
- m_content_box->add(*m_meta_box);
- m_main_box->add(*m_avatar);
- m_main_box->add(*m_content_box);
- add(*m_main_box);
- set_margin_bottom(8);
-
- show();
-}
-
-void ChatMessageContainer::SetAvatarFromPixbuf(Glib::RefPtr<Gdk::Pixbuf> pixbuf) {
- m_avatar->property_pixbuf() = pixbuf;
-}
-
-void ChatMessageContainer::Update() {
- auto &discord = Abaddon::Get().GetDiscordClient();
- auto guild_id = discord.GetChannel(ChannelID)->GuildID;
- auto role_id = discord.GetMemberHoistedRole(guild_id, UserID, true);
- auto *user = discord.GetUser(UserID);
- std::string md;
- if (role_id.IsValid()) {
- auto *role = discord.GetRole(role_id);
- if (role != nullptr)
- md = "<span weight='bold' color='#" + IntToCSSColor(role->Color) + "'>" + Glib::Markup::escape_text(user->Username) + "</span>";
- } else {
- md = "<span weight='bold' color='#eeeeee'>" + Glib::Markup::escape_text(user->Username) + "</span>";
- }
- m_author->set_markup(md);
-}
-
-// returns index (1-based) of removed item
-int ChatMessageContainer::RemoveItem(Gtk::Widget *widget) {
- auto children = m_content_box->get_children();
- for (auto i = 0; i < children.size(); i++) {
- if (widget == children[i]) {
- m_content_box->remove(*widget);
- return i;
- }
- }
-
- return -1;
-}
-
-void ChatMessageContainer::AddNewContent(Gtk::Widget *widget, bool prepend) {
- if (prepend) {
- m_content_box->add(*widget);
- m_content_box->reorder_child(*widget, 1);
- } else {
- m_content_box->add(*widget);
- }
-}
-
-void ChatMessageContainer::AddNewContentAtIndex(Gtk::Widget *widget, int index) {
- m_content_box->add(*widget);
- m_content_box->reorder_child(*widget, index); // this doesn't seem very reliable
-}
+ChatMessageItemContainer::ChatMessageItemContainer() {
+ m_main = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_VERTICAL));
+ add(*m_main);
-ChatMessageItem::ChatMessageItem() {
- m_menu_copy_id = Gtk::manage(new Gtk::MenuItem("_Copy ID", true));
- m_menu_copy_id->signal_activate().connect(sigc::mem_fun(*this, &ChatMessageItem::on_menu_copy_id));
+ m_menu_copy_id = Gtk::manage(new Gtk::MenuItem("Copy ID"));
+ m_menu_copy_id->signal_activate().connect(sigc::mem_fun(*this, &ChatMessageItemContainer::on_menu_copy_id));
m_menu.append(*m_menu_copy_id);
- m_menu_delete_message = Gtk::manage(new Gtk::MenuItem("_Delete Message", true));
- m_menu_delete_message->signal_activate().connect(sigc::mem_fun(*this, &ChatMessageItem::on_menu_message_delete));
+ m_menu_delete_message = Gtk::manage(new Gtk::MenuItem("Delete Message"));
+ m_menu_delete_message->signal_activate().connect(sigc::mem_fun(*this, &ChatMessageItemContainer::on_menu_delete_message));
m_menu.append(*m_menu_delete_message);
- m_menu_edit_message = Gtk::manage(new Gtk::MenuItem("_Edit Message", true));
- m_menu_edit_message->signal_activate().connect(sigc::mem_fun(*this, &ChatMessageItem::on_menu_message_edit));
+ m_menu_edit_message = Gtk::manage(new Gtk::MenuItem("Edit Message"));
+ m_menu_edit_message->signal_activate().connect(sigc::mem_fun(*this, &ChatMessageItemContainer::on_menu_edit_message));
m_menu.append(*m_menu_edit_message);
m_menu.show_all();
}
-void ChatMessageItem::on_menu_message_delete() {
- m_signal_action_message_delete.emit(ChannelID, ID);
-}
-
-void ChatMessageItem::on_menu_message_edit() {
- m_signal_action_message_edit.emit(ChannelID, ID);
-}
-
-ChatMessageItem::type_signal_action_message_delete ChatMessageItem::signal_action_message_delete() {
- return m_signal_action_message_delete;
-}
-
-ChatMessageItem::type_signal_action_message_edit ChatMessageItem::signal_action_message_edit() {
- return m_signal_action_message_edit;
-}
+ChatMessageItemContainer *ChatMessageItemContainer::FromMessage(Snowflake id) {
+ const auto *data = Abaddon::Get().GetDiscordClient().GetMessage(id);
+ if (data == nullptr) return nullptr;
-void ChatMessageItem::on_menu_copy_id() {
- Gtk::Clipboard::get()->set_text(std::to_string(ID));
-}
-
-// broken format v
-// clang-format off
-void ChatMessageItem::AttachMenuHandler(Gtk::Widget *widget) {
- widget->signal_button_press_event().connect([this](GdkEventButton *event) -> bool {
- if (event->type == GDK_BUTTON_PRESS && event->button == GDK_BUTTON_SECONDARY) {
- ShowMenu(reinterpret_cast<GdkEvent *>(event));
- return true;
- }
+ auto *container = Gtk::manage(new ChatMessageItemContainer);
+ container->ID = data->ID;
+ container->ChannelID = data->ChannelID;
- return false;
- }, false);
-}
-// clang-format on
-
-void ChatMessageItem::ShowMenu(const GdkEvent *event) {
- const auto &client = Abaddon::Get().GetDiscordClient();
- const auto *data = client.GetMessage(ID);
- if (data->IsDeleted()) {
- m_menu_delete_message->set_sensitive(false);
- m_menu_edit_message->set_sensitive(false);
- } else {
- const bool can_edit = client.GetUserData().ID == data->Author.ID;
- const bool can_delete = can_edit || client.HasChannelPermission(client.GetUserData().ID, ChannelID, Permission::MANAGE_MESSAGES);
- m_menu_delete_message->set_sensitive(can_delete);
- m_menu_edit_message->set_sensitive(can_edit);
+ if (data->Content.size() > 0) {
+ container->m_text_component = CreateTextComponent(data);
+ container->AttachMenuHandler(container->m_text_component);
+ container->m_main->add(*container->m_text_component);
}
- m_menu.popup_at_pointer(event);
-}
-void ChatMessageItem::AddMenuItem(Gtk::MenuItem *item) {
- item->show();
- m_menu.append(*item);
-}
+ // there should only ever be 1 embed (i think?)
+ if (data->Embeds.size() == 1) {
+ container->m_embed_component = CreateEmbedComponent(data);
+ container->AttachMenuHandler(container->m_embed_component);
+ container->m_main->add(*container->m_embed_component);
+ }
-void ChatMessageItem::SetContainer(ChatMessageContainer *container) {
- m_container = container;
-}
+ container->UpdateAttributes();
-ChatMessageContainer *ChatMessageItem::GetContainer() const {
- return m_container;
+ return container;
}
-ChatMessageTextItem::ChatMessageTextItem(const Message *data) {
- m_content = data->Content;
- ID = data->ID;
-
- get_style_context()->add_class("message-text");
-
- set_can_focus(false);
- set_editable(false);
- set_wrap_mode(Gtk::WRAP_WORD_CHAR);
- set_halign(Gtk::ALIGN_FILL);
- set_hexpand(true);
- get_buffer()->set_text(data->Content);
- show();
-
- AttachMenuHandler(this);
- m_menu_copy_content = Gtk::manage(new Gtk::MenuItem("Copy _Message", true));
- AddMenuItem(m_menu_copy_content);
- m_menu_copy_content->signal_activate().connect(sigc::mem_fun(*this, &ChatMessageTextItem::on_menu_copy_content));
-
- Update();
-}
+void ChatMessageItemContainer::UpdateContent() {
+ const auto *data = Abaddon::Get().GetDiscordClient().GetMessage(ID);
+ if (m_text_component != nullptr)
+ m_text_component->get_buffer()->set_text(data->Content);
-void ChatMessageTextItem::on_menu_copy_content() {
- Gtk::Clipboard::get()->set_text(m_content);
-}
+ if (m_embed_component != nullptr) {
+ // easier to delete and remake than really update it
+ delete m_embed_component;
-void ChatMessageTextItem::Update() {
- UpdateAttributes();
+ if (data->Embeds.size() == 1) {
+ m_embed_component = CreateEmbedComponent(data);
+ AttachMenuHandler(m_embed_component);
+ m_main->add(*m_embed_component);
+ }
+ }
}
-void ChatMessageTextItem::UpdateAttributes() {
+void ChatMessageItemContainer::UpdateAttributes() {
const auto *data = Abaddon::Get().GetDiscordClient().GetMessage(ID);
if (data == nullptr) return;
- bool deleted = data->IsDeleted();
- bool edited = data->IsEdited();
-
- auto buf = get_buffer();
- buf->set_text(m_content);
- Gtk::TextBuffer::iterator start, end;
- buf->get_bounds(start, end);
-
- if (deleted) {
- buf->insert_markup(end, "<span color='#ff0000'> [deleted]</span>");
- } else if (edited) {
- buf->insert_markup(end, "<span color='#999999'> [edited]</span>");
- }
-}
-ChatMessageEmbedItem::ChatMessageEmbedItem(const Message *data) {
- m_embed = data->Embeds[0];
- ID = data->ID;
+ const bool deleted = data->IsDeleted();
+ const bool edited = data->IsEdited();
+
+ if (!deleted && !edited) return;
- DoLayout();
- AttachMenuHandler(this);
+ if (m_attrib_label == nullptr) {
+ m_attrib_label = Gtk::manage(new Gtk::Label);
+ m_attrib_label->set_halign(Gtk::ALIGN_START);
+ m_attrib_label->show();
+ m_main->add(*m_attrib_label); // todo: maybe insert markup into existing text widget's buffer if the circumstances are right (or pack horizontally)
+ }
- Update();
+ if (deleted)
+ m_attrib_label->set_markup("<span color='#ff0000'>[deleted]</span>");
+ else if (edited)
+ m_attrib_label->set_markup("<span color='#999999'>[edited]</span>");
}
-void ChatMessageEmbedItem::DoLayout() {
- m_main = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_VERTICAL));
+Gtk::TextView *ChatMessageItemContainer::CreateTextComponent(const Message *data) {
+ auto *tv = Gtk::manage(new Gtk::TextView);
- auto children = get_children();
- auto it = children.begin();
+ tv->get_style_context()->add_class("message-text");
+ tv->set_can_focus(false);
+ tv->set_editable(false);
+ tv->set_wrap_mode(Gtk::WRAP_WORD_CHAR);
+ tv->set_halign(Gtk::ALIGN_FILL);
+ tv->set_hexpand(true);
+ tv->get_buffer()->set_text(data->Content);
- while (it != children.end()) {
- delete *it;
- it++;
- }
+ return tv;
+}
+
+Gtk::EventBox *ChatMessageItemContainer::CreateEmbedComponent(const Message *data) {
+ Gtk::EventBox *ev = Gtk::manage(new Gtk::EventBox);
+ ev->set_can_focus(true);
+ Gtk::Box *main = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_VERTICAL));
+ const auto &embed = data->Embeds[0];
- if (m_embed.Author.Name.length() > 0) {
+ if (embed.Author.Name.length() > 0) {
auto *author_lbl = Gtk::manage(new Gtk::Label);
author_lbl->set_halign(Gtk::ALIGN_START);
author_lbl->set_line_wrap(true);
author_lbl->set_line_wrap_mode(Pango::WRAP_WORD_CHAR);
author_lbl->set_hexpand(false);
- author_lbl->set_text(m_embed.Author.Name);
+ author_lbl->set_text(embed.Author.Name);
author_lbl->get_style_context()->add_class("embed-author");
- m_main->pack_start(*author_lbl);
+ main->pack_start(*author_lbl);
}
- if (m_embed.Title.length() > 0) {
+ if (embed.Title.length() > 0) {
auto *title_label = Gtk::manage(new Gtk::Label);
title_label->set_use_markup(true);
- title_label->set_markup("<b>" + Glib::Markup::escape_text(m_embed.Title) + "</b>");
+ title_label->set_markup("<b>" + Glib::Markup::escape_text(embed.Title) + "</b>");
title_label->set_halign(Gtk::ALIGN_CENTER);
title_label->set_hexpand(false);
title_label->get_style_context()->add_class("embed-title");
- m_main->pack_start(*title_label);
+ title_label->set_single_line_mode(false);
+ title_label->set_line_wrap(true);
+ title_label->set_line_wrap_mode(Pango::WRAP_WORD_CHAR);
+ title_label->set_max_width_chars(50);
+ main->pack_start(*title_label);
}
- if (m_embed.Description.length() > 0) {
+ if (embed.Description.length() > 0) {
auto *desc_label = Gtk::manage(new Gtk::Label);
- desc_label->set_text(m_embed.Description);
+ desc_label->set_text(embed.Description);
desc_label->set_line_wrap(true);
desc_label->set_line_wrap_mode(Pango::WRAP_WORD_CHAR);
desc_label->set_max_width_chars(50);
desc_label->set_halign(Gtk::ALIGN_START);
desc_label->set_hexpand(false);
desc_label->get_style_context()->add_class("embed-description");
- m_main->pack_start(*desc_label);
+ main->pack_start(*desc_label);
}
- if (m_embed.Fields.size() > 0) {
+ // todo: handle inline fields
+ if (embed.Fields.size() > 0) {
auto *flow = Gtk::manage(new Gtk::FlowBox);
flow->set_orientation(Gtk::ORIENTATION_HORIZONTAL);
flow->set_min_children_per_line(3);
@@ -301,9 +153,9 @@ void ChatMessageEmbedItem::DoLayout() {
flow->set_hexpand(false);
flow->set_column_spacing(10);
flow->set_selection_mode(Gtk::SELECTION_NONE);
- m_main->pack_start(*flow);
+ main->pack_start(*flow);
- for (const auto &field : m_embed.Fields) {
+ for (const auto &field : embed.Fields) {
auto *field_box = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_VERTICAL));
auto *field_lbl = Gtk::manage(new Gtk::Label);
auto *field_val = Gtk::manage(new Gtk::Label);
@@ -333,87 +185,171 @@ void ChatMessageEmbedItem::DoLayout() {
}
}
- if (m_embed.Footer.Text.length() > 0) {
+ if (embed.Footer.Text.length() > 0) {
auto *footer_lbl = Gtk::manage(new Gtk::Label);
footer_lbl->set_halign(Gtk::ALIGN_START);
footer_lbl->set_line_wrap(true);
footer_lbl->set_line_wrap_mode(Pango::WRAP_WORD_CHAR);
footer_lbl->set_hexpand(false);
- footer_lbl->set_text(m_embed.Footer.Text);
+ footer_lbl->set_text(embed.Footer.Text);
footer_lbl->get_style_context()->add_class("embed-footer");
- m_main->pack_start(*footer_lbl);
+ main->pack_start(*footer_lbl);
}
- auto style = m_main->get_style_context();
+ auto style = main->get_style_context();
- if (m_embed.Color != -1) {
+ if (embed.Color != -1) {
auto provider = Gtk::CssProvider::create(); // this seems wrong
- std::string css = ".embed { border-left: 2px solid #" + IntToCSSColor(m_embed.Color) + "; }";
+ std::string css = ".embed { border-left: 2px solid #" + IntToCSSColor(embed.Color) + "; }";
provider->load_from_data(css);
style->add_provider(provider, GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
}
style->add_class("embed");
- set_margin_bottom(8);
- set_hexpand(false);
- m_main->set_hexpand(false);
- m_main->set_halign(Gtk::ALIGN_START);
- set_halign(Gtk::ALIGN_START);
+ main->set_margin_bottom(8);
+ main->set_hexpand(false);
+ main->set_hexpand(false);
+ main->set_halign(Gtk::ALIGN_START);
+ main->set_halign(Gtk::ALIGN_START);
- add(*m_main);
- show_all();
-}
+ ev->add(*main);
+ ev->show_all();
-void ChatMessageEmbedItem::UpdateAttributes() {
- const auto *data = Abaddon::Get().GetDiscordClient().GetMessage(ID);
- if (data == nullptr) return;
- bool deleted = data->IsDeleted();
- bool edited = data->IsEdited();
+ return ev;
+}
- if (m_attrib_label == nullptr) {
- m_attrib_label = Gtk::manage(new Gtk::Label);
- m_attrib_label->set_use_markup(true);
- m_attrib_label->show();
- m_main->pack_start(*m_attrib_label);
+void ChatMessageItemContainer::ShowMenu(GdkEvent *event) {
+ const auto &client = Abaddon::Get().GetDiscordClient();
+ const auto *data = client.GetMessage(ID);
+ if (data->IsDeleted()) {
+ m_menu_delete_message->set_sensitive(false);
+ m_menu_edit_message->set_sensitive(false);
+ } else {
+ const bool can_edit = client.GetUserData().ID == data->Author.ID;
+ const bool can_delete = can_edit || client.HasChannelPermission(client.GetUserData().ID, ChannelID, Permission::MANAGE_MESSAGES);
+ m_menu_delete_message->set_sensitive(can_delete);
+ m_menu_edit_message->set_sensitive(can_edit);
}
- if (deleted)
- m_attrib_label->set_markup(" <span color='#ff0000'> [deleted]</span>");
- else if (edited)
- m_attrib_label->set_markup(" <span color='#999999'> [edited]</span>");
+ m_menu.popup_at_pointer(event);
}
-void ChatMessageEmbedItem::Update() {
- UpdateAttributes();
+void ChatMessageItemContainer::on_menu_copy_id() {
+ Gtk::Clipboard::get()->set_text(std::to_string(ID));
}
-ChatMessageUserEventItem::ChatMessageUserEventItem(const Message *data) {
- ID = data->ID;
-
- m_label = Gtk::manage(new Gtk::Label);
-
- get_style_context()->add_class("message-text");
- get_style_context()->add_class("message-text-user-event");
-
- set_can_focus(false);
- set_halign(Gtk::ALIGN_FILL);
- set_hexpand(true);
- m_label->set_halign(Gtk::ALIGN_START);
- m_label->set_use_markup();
- switch (data->Type) {
- case MessageType::GUILD_MEMBER_JOIN:
- m_label->set_markup("<span color='#999999'><i>[user joined]</i></span>");
- break;
- case MessageType::CHANNEL_PINNED_MESSAGE:
- m_label->set_markup("<span color='#999999'><i>[message pinned]</i></span>");
- break;
- default: break;
- }
- add(*m_label);
+void ChatMessageItemContainer::on_menu_delete_message() {
+ m_signal_action_delete.emit();
+}
+
+void ChatMessageItemContainer::on_menu_edit_message() {
+ m_signal_action_edit.emit();
+}
+
+ChatMessageItemContainer::type_signal_action_delete ChatMessageItemContainer::signal_action_delete() {
+ return m_signal_action_delete;
+}
+
+ChatMessageItemContainer::type_signal_action_edit ChatMessageItemContainer::signal_action_edit() {
+ return m_signal_action_edit;
+}
+
+// clang-format off
+void ChatMessageItemContainer::AttachMenuHandler(Gtk::Widget *widget) {
+ widget->signal_button_press_event().connect([this](GdkEventButton *event) -> bool {
+ if (event->type == GDK_BUTTON_PRESS && event->button == GDK_BUTTON_SECONDARY) {
+ ShowMenu(reinterpret_cast<GdkEvent*>(event));
+ return true;
+ }
+
+ return false;
+ }, false);
+}
+// clang-format on
+
+ChatMessageHeader::ChatMessageHeader(const Message *data) {
+ UserID = data->Author.ID;
+ ChannelID = data->ChannelID;
+
+ m_main_box = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL));
+ m_content_box = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_VERTICAL));
+ m_meta_box = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL));
+ m_author = Gtk::manage(new Gtk::Label);
+ m_timestamp = Gtk::manage(new Gtk::Label);
+
+ auto buf = Abaddon::Get().GetImageManager().GetFromURLIfCached(data->Author.GetAvatarURL());
+ if (buf)
+ m_avatar = Gtk::manage(new Gtk::Image(buf));
+ else
+ m_avatar = Gtk::manage(new Gtk::Image(Abaddon::Get().GetImageManager().GetPlaceholder(32)));
+
+ get_style_context()->add_class("message-container");
+ m_author->get_style_context()->add_class("message-container-author");
+ m_timestamp->get_style_context()->add_class("message-container-timestamp");
+ m_avatar->get_style_context()->add_class("message-container-avatar");
+
+ m_avatar->set_valign(Gtk::ALIGN_START);
+ m_avatar->set_margin_right(10);
+
+ m_author->set_markup("<span weight='bold'>" + Glib::Markup::escape_text(data->Author.Username) + "</span>");
+ m_author->set_single_line_mode(true);
+ m_author->set_line_wrap(false);
+ m_author->set_ellipsize(Pango::ELLIPSIZE_END);
+ m_author->set_xalign(0.f);
+ m_author->set_can_focus(false);
+
+ m_timestamp->set_text(data->Timestamp);
+ m_timestamp->set_opacity(0.5);
+ m_timestamp->set_single_line_mode(true);
+ m_timestamp->set_margin_start(12);
+ m_timestamp->set_can_focus(false);
+
+ m_main_box->set_hexpand(true);
+ m_main_box->set_vexpand(true);
+ m_main_box->set_can_focus(true);
+
+ m_meta_box->set_can_focus(false);
+
+ m_content_box->set_can_focus(false);
+
+ m_meta_box->add(*m_author);
+ m_meta_box->add(*m_timestamp);
+ m_content_box->add(*m_meta_box);
+ m_main_box->add(*m_avatar);
+ m_main_box->add(*m_content_box);
+ add(*m_main_box);
+
+ set_margin_bottom(8);
+
show_all();
- AttachMenuHandler(this);
+ UpdateNameColor();
}
-void ChatMessageUserEventItem::Update() {}
+void ChatMessageHeader::UpdateNameColor() {
+ const auto &discord = Abaddon::Get().GetDiscordClient();
+ const auto guild_id = discord.GetChannel(ChannelID)->GuildID;
+ const auto role_id = discord.GetMemberHoistedRole(guild_id, UserID, true);
+ const auto *user = discord.GetUser(UserID);
+ if (user == nullptr) return;
+ const auto *role = discord.GetRole(role_id);
+
+ std::string md;
+ if (role != nullptr)
+ md = "<span weight='bold' color='#" + IntToCSSColor(role->Color) + "'>" + Glib::Markup::escape_text(user->Username) + "</span>";
+ else
+ md = "<span weight='bold' color='#eeeeee'>" + Glib::Markup::escape_text(user->Username) + "</span>";
+
+ m_author->set_markup(md);
+}
+
+void ChatMessageHeader::AddContent(Gtk::Widget *widget, bool prepend) {
+ m_content_box->add(*widget);
+ if (prepend)
+ m_content_box->reorder_child(*widget, 1);
+}
+
+void ChatMessageHeader::SetAvatarFromPixbuf(Glib::RefPtr<Gdk::Pixbuf> pixbuf) {
+ m_avatar->property_pixbuf() = pixbuf;
+}
diff --git a/components/chatmessage.hpp b/components/chatmessage.hpp
index 91c74f1..bf4f0da 100644
--- a/components/chatmessage.hpp
+++ b/components/chatmessage.hpp
@@ -1,125 +1,67 @@
#pragma once
#include <gtkmm.h>
-#include <string>
-#include <sigc++/sigc++.h>
#include "../discord/discord.hpp"
-enum class ChatDisplayType {
- Unknown,
- Text,
- Embed,
- Image,
- GuildMemberJoin,
- ChannelPinnedMessage,
-};
-
-// contains the username and timestamp, chat items get stuck into its box
-class ChatMessageContainer : public Gtk::ListBoxRow {
-public:
- Snowflake UserID;
- Snowflake ChannelID;
-
- ChatMessageContainer(const Message *data);
- void AddNewContent(Gtk::Widget *widget, bool prepend = false);
- void AddNewContentAtIndex(Gtk::Widget *widget, int index);
- void SetAvatarFromPixbuf(Glib::RefPtr<Gdk::Pixbuf> pixbuf);
- void Update();
- int RemoveItem(Gtk::Widget *widget);
-
-protected:
- Gtk::Box *m_main_box;
- Gtk::Box *m_content_box;
- Gtk::Box *m_meta_box;
- Gtk::Label *m_author;
- Gtk::Label *m_timestamp;
- Gtk::Image *m_avatar;
-};
-
-class ChatMessageItem {
+class ChatMessageItemContainer : public Gtk::Box {
public:
- ChatMessageItem();
-
- Snowflake ChannelID;
Snowflake ID;
- ChatDisplayType MessageType = ChatDisplayType::Unknown;
+ Snowflake ChannelID;
- virtual void ShowMenu(const GdkEvent *event);
- void AddMenuItem(Gtk::MenuItem *item);
- virtual void Update() = 0;
+ ChatMessageItemContainer();
+ static ChatMessageItemContainer *FromMessage(Snowflake id);
- void SetContainer(ChatMessageContainer *container);
- ChatMessageContainer *GetContainer() const;
+ // attributes = edited, deleted
+ void UpdateAttributes();
+ void UpdateContent();
protected:
- ChatMessageContainer *m_container = nullptr;
-
+ static Gtk::TextView *CreateTextComponent(const Message *data); // Message.Content
+ static Gtk::EventBox *CreateEmbedComponent(const Message *data); // Message.Embeds[0]
void AttachMenuHandler(Gtk::Widget *widget);
- void on_menu_copy_id();
- void on_menu_message_delete();
- void on_menu_message_edit();
+ void ShowMenu(GdkEvent *event);
Gtk::Menu m_menu;
Gtk::MenuItem *m_menu_copy_id;
Gtk::MenuItem *m_menu_delete_message;
Gtk::MenuItem *m_menu_edit_message;
-public:
- typedef sigc::signal<void, Snowflake, Snowflake> type_signal_action_message_delete;
- typedef sigc::signal<void, Snowflake, Snowflake> type_signal_action_message_edit;
-
- type_signal_action_message_delete signal_action_message_delete();
- type_signal_action_message_edit signal_action_message_edit();
-
-private:
- type_signal_action_message_delete m_signal_action_message_delete;
- type_signal_action_message_edit m_signal_action_message_edit;
-};
-
-class ChatMessageTextItem
- : public Gtk::TextView // oh well
- , public ChatMessageItem {
-public:
- ChatMessageTextItem(const Message *data);
-
- void EditContent(std::string content);
-
- virtual void Update();
-
-protected:
- void UpdateAttributes();
+ void on_menu_copy_id();
+ void on_menu_delete_message();
+ void on_menu_edit_message();
- std::string m_content;
+ Gtk::Box *m_main;
+ Gtk::Label *m_attrib_label = nullptr;
- void on_menu_copy_content();
- Gtk::MenuItem *m_menu_copy_content;
- Gtk::MenuItem *m_menu_delete_message;
-};
+ Gtk::TextView *m_text_component = nullptr;
+ Gtk::EventBox *m_embed_component = nullptr;
-class ChatMessageEmbedItem
- : public Gtk::EventBox
- , public ChatMessageItem {
public:
- ChatMessageEmbedItem(const Message *data);
+ typedef sigc::signal<void> type_signal_action_delete;
+ typedef sigc::signal<void> type_signal_action_edit;
- virtual void Update();
+ type_signal_action_delete signal_action_delete();
+ type_signal_action_edit signal_action_edit();
-protected:
- void DoLayout();
- void UpdateAttributes();
-
- EmbedData m_embed;
- Gtk::Box *m_main;
- Gtk::Label *m_attrib_label = nullptr;
+private:
+ type_signal_action_delete m_signal_action_delete;
+ type_signal_action_edit m_signal_action_edit;
};
-class ChatMessageUserEventItem
- : public Gtk::EventBox
- , public ChatMessageItem {
+class ChatMessageHeader : public Gtk::ListBoxRow {
public:
- ChatMessageUserEventItem(const Message *data);
+ Snowflake UserID;
+ Snowflake ChannelID;
- virtual void Update();
+ ChatMessageHeader(const Message *data);
+ void SetAvatarFromPixbuf(Glib::RefPtr<Gdk::Pixbuf> pixbuf);
+ void AddContent(Gtk::Widget *widget, bool prepend);
+ void UpdateNameColor();
protected:
- Gtk::Label *m_label;
+ Gtk::Box *m_main_box;
+ Gtk::Box *m_content_box;
+ Gtk::Box *m_meta_box;
+ Gtk::Label *m_author;
+ Gtk::Label *m_timestamp;
+ Gtk::Image *m_avatar;
};
diff --git a/components/chatwindow.cpp b/components/chatwindow.cpp
index fe56bf1..ee4aea9 100644
--- a/components/chatwindow.cpp
+++ b/components/chatwindow.cpp
@@ -1,378 +1,306 @@
#include "chatwindow.hpp"
+#include "chatmessage.hpp"
#include "../abaddon.hpp"
-#include <map>
ChatWindow::ChatWindow() {
- m_message_set_dispatch.connect(sigc::mem_fun(*this, &ChatWindow::SetMessagesInternal));
+ m_set_messsages_dispatch.connect(sigc::mem_fun(*this, &ChatWindow::SetMessagesInternal));
m_new_message_dispatch.connect(sigc::mem_fun(*this, &ChatWindow::AddNewMessageInternal));
- m_new_history_dispatch.connect(sigc::mem_fun(*this, &ChatWindow::AddNewHistoryInternal));
- m_message_delete_dispatch.connect(sigc::mem_fun(*this, &ChatWindow::DeleteMessageInternal));
- m_message_edit_dispatch.connect(sigc::mem_fun(*this, &ChatWindow::UpdateMessageInternal));
+ m_delete_message_dispatch.connect(sigc::mem_fun(*this, &ChatWindow::DeleteMessageInternal));
+ m_update_message_dispatch.connect(sigc::mem_fun(*this, &ChatWindow::UpdateMessageInternal));
+ m_history_dispatch.connect(sigc::mem_fun(*this, &ChatWindow::AddNewHistoryInternal));
m_main = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_VERTICAL));
- m_listbox = Gtk::manage(new Gtk::ListBox);
- m_viewport = Gtk::manage(new Gtk::Viewport(Gtk::Adjustment::create(0, 0, 0, 0, 0, 0), Gtk::Adjustment::create(0, 0, 0, 0, 0, 0)));
+ m_list = Gtk::manage(new Gtk::ListBox);
m_scroll = Gtk::manage(new Gtk::ScrolledWindow);
m_input = Gtk::manage(new Gtk::TextView);
- m_entry_scroll = Gtk::manage(new Gtk::ScrolledWindow);
+ m_input_scroll = Gtk::manage(new Gtk::ScrolledWindow);
m_main->get_style_context()->add_class("messages");
- m_listbox->get_style_context()->add_class("messages"); // maybe hacky
+ m_list->get_style_context()->add_class("messages");
m_input->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);
- m_main->show();
m_scroll->signal_edge_reached().connect(sigc::mem_fun(*this, &ChatWindow::on_scroll_edge_overshot));
- auto vadj = m_scroll->get_vadjustment();
- vadj->signal_value_changed().connect([&, vadj]() {
- m_scroll_to_bottom = vadj->get_upper() - vadj->get_page_size() <= vadj->get_value();
+ auto v = m_scroll->get_vadjustment();
+ v->signal_value_changed().connect([this, v] {
+ m_should_scroll_to_bottom = v->get_upper() - v->get_page_size() <= v->get_value();
});
m_scroll->set_can_focus(false);
m_scroll->set_policy(Gtk::POLICY_NEVER, Gtk::POLICY_ALWAYS);
- m_scroll->show();
- m_listbox->signal_size_allocate().connect([this](Gtk::Allocation &) {
- if (m_scroll_to_bottom)
+ m_list->signal_size_allocate().connect([this](Gtk::Allocation &) {
+ if (m_should_scroll_to_bottom)
ScrollToBottom();
});
- m_listbox->set_selection_mode(Gtk::SELECTION_NONE);
- m_listbox->set_hexpand(true);
- m_listbox->set_vexpand(true);
- m_listbox->set_focus_hadjustment(m_scroll->get_hadjustment());
- m_listbox->set_focus_vadjustment(m_scroll->get_vadjustment());
- m_listbox->show();
-
- m_viewport->set_can_focus(false);
- m_viewport->set_valign(Gtk::ALIGN_END);
- m_viewport->set_vscroll_policy(Gtk::SCROLL_NATURAL);
- m_viewport->set_shadow_type(Gtk::SHADOW_NONE);
- m_viewport->show();
+ m_list->set_selection_mode(Gtk::SELECTION_NONE);
+ m_list->set_hexpand(true);
+ m_list->set_vexpand(true);
+ m_list->set_focus_hadjustment(m_scroll->get_hadjustment());
+ m_list->set_focus_vadjustment(m_scroll->get_vadjustment());
m_input->set_hexpand(true);
m_input->set_wrap_mode(Gtk::WRAP_WORD_CHAR);
- m_input->show();
- m_entry_scroll->set_max_content_height(150);
- m_entry_scroll->set_policy(Gtk::POLICY_NEVER, Gtk::POLICY_AUTOMATIC);
- m_entry_scroll->add(*m_input);
- m_entry_scroll->show();
+ m_input_scroll->set_max_content_height(170);
+ m_input_scroll->set_policy(Gtk::POLICY_NEVER, Gtk::POLICY_AUTOMATIC);
- m_viewport->add(*m_listbox);
- m_scroll->add(*m_viewport);
+ m_input_scroll->add(*m_input);
+ m_scroll->add(*m_list);
m_main->add(*m_scroll);
- m_main->add(*m_entry_scroll);
+ m_main->add(*m_input_scroll);
}
Gtk::Widget *ChatWindow::GetRoot() const {
return m_main;
}
+void ChatWindow::Clear() {
+ m_update_mutex.lock();
+ m_set_messages_queue.push(std::set<Snowflake>());
+ m_set_messsages_dispatch.emit();
+ m_update_mutex.unlock();
+}
+
+void ChatWindow::SetMessages(const std::set<Snowflake> &msgs) {
+ m_update_mutex.lock();
+ m_set_messages_queue.push(msgs);
+ m_set_messsages_dispatch.emit();
+ m_update_mutex.unlock();
+}
+
void ChatWindow::SetActiveChannel(Snowflake id) {
m_active_channel = id;
}
+void ChatWindow::AddNewMessage(Snowflake id) {
+ m_update_mutex.lock();
+ m_new_message_queue.push(id);
+ m_new_message_dispatch.emit();
+ m_update_mutex.unlock();
+}
+
+void ChatWindow::DeleteMessage(Snowflake id) {
+ m_update_mutex.lock();
+ m_delete_message_queue.push(id);
+ m_delete_message_dispatch.emit();
+ m_update_mutex.unlock();
+}
+
+void ChatWindow::UpdateMessage(Snowflake id) {
+ m_update_mutex.lock();
+ m_update_message_queue.push(id);
+ m_update_message_dispatch.emit();
+ m_update_mutex.unlock();
+}
+
+void ChatWindow::AddNewHistory(const std::vector<Snowflake> &id) {
+ std::set<Snowflake> x;
+ for (const auto &s : id)
+ x.insert(s);
+ m_update_mutex.lock();
+ m_history_queue.push(x);
+ m_history_dispatch.emit();
+ m_update_mutex.unlock();
+}
+
+void ChatWindow::InsertChatInput(std::string text) {
+ // shouldn't need a mutex cuz called from gui action
+ m_input->get_buffer()->insert_at_cursor(text);
+ m_input->grab_focus();
+}
+
+Snowflake ChatWindow::GetOldestListedMessage() {
+ Snowflake m;
+
+ for (const auto &[id, widget] : m_id_to_widget) {
+ if (id < m)
+ m = id;
+ }
+
+ return m;
+}
+
Snowflake ChatWindow::GetActiveChannel() const {
return m_active_channel;
}
-ChatDisplayType ChatWindow::GetMessageDisplayType(const Message *data) {
- if (data->Type == MessageType::DEFAULT && data->Content.size() > 0)
- return ChatDisplayType::Text;
- else if (data->Type == MessageType::DEFAULT && data->Embeds.size() > 0)
- return ChatDisplayType::Embed;
- else if (data->Type == MessageType::GUILD_MEMBER_JOIN)
- return ChatDisplayType::GuildMemberJoin;
- else if (data->Type == MessageType::CHANNEL_PINNED_MESSAGE)
- return ChatDisplayType::ChannelPinnedMessage;
-
- return ChatDisplayType::Unknown;
-}
+bool ChatWindow::on_key_press_event(GdkEventKey *e) {
+ if (e->keyval == GDK_KEY_Return) {
+ if (e->state & GDK_SHIFT_MASK)
+ return false;
-ChatMessageItem *ChatWindow::CreateMessageComponent(const Message *data) {
- auto type = GetMessageDisplayType(data);
- ChatMessageItem *widget = nullptr;
+ auto buf = m_input->get_buffer();
+ auto text = buf->get_text();
+ buf->set_text("");
- switch (type) {
- case ChatDisplayType::Text: {
- widget = Gtk::manage(new ChatMessageTextItem(data));
+ m_signal_action_chat_submit.emit(text, m_active_channel);
- widget->signal_action_message_delete().connect([this](Snowflake channel_id, Snowflake id) {
- m_signal_action_message_delete.emit(channel_id, id);
- });
- widget->signal_action_message_edit().connect([this](Snowflake channel_id, Snowflake id) {
- m_signal_action_message_edit.emit(channel_id, id);
- });
- } break;
- case ChatDisplayType::Embed: {
- widget = Gtk::manage(new ChatMessageEmbedItem(data));
- } break;
- case ChatDisplayType::GuildMemberJoin:
- case ChatDisplayType::ChannelPinnedMessage: {
- widget = Gtk::manage(new ChatMessageUserEventItem(data));
- } break;
- default: break;
+ return true;
}
- if (widget == nullptr) return nullptr;
- widget->ChannelID = m_active_channel;
- m_id_to_widget[data->ID] = widget;
- return widget;
+ return false;
}
-void ChatWindow::ProcessMessage(const Message *data, bool prepend) {
- if (!Abaddon::Get().GetDiscordClient().IsStarted()) return;
+ChatMessageItemContainer *ChatWindow::CreateMessageComponent(Snowflake id) {
+ auto *container = ChatMessageItemContainer::FromMessage(id);
+ return container;
+}
- ChatMessageContainer *last_row = nullptr;
+void ChatWindow::ProcessNewMessage(Snowflake id, bool prepend) {
+ const auto &client = Abaddon::Get().GetDiscordClient();
+ if (!client.IsStarted()) return; // e.g. load channel and then dc
+ const auto *data = client.GetMessage(id);
+ if (data == nullptr) return;
+
+ ChatMessageHeader *last_row = nullptr;
bool should_attach = false;
if (m_num_rows > 0) {
if (prepend)
- last_row = dynamic_cast<ChatMessageContainer *>(m_listbox->get_row_at_index(0));
+ last_row = dynamic_cast<ChatMessageHeader *>(m_list->get_row_at_index(0));
else
- last_row = dynamic_cast<ChatMessageContainer *>(m_listbox->get_row_at_index(m_num_rows - 1));
- if (last_row != nullptr) { // this should always be true tbh
+ last_row = dynamic_cast<ChatMessageHeader *>(m_list->get_row_at_index(m_num_rows - 1));
+
+ if (last_row != nullptr)
if (last_row->UserID == data->Author.ID)
should_attach = true;
- }
}
- ChatMessageContainer *container;
+ ChatMessageHeader *header;
if (should_attach) {
- container = last_row;
+ header = last_row;
} else {
- container = Gtk::manage(new ChatMessageContainer(data)); // only accesses timestamp and user
- container->Update();
-
- auto user_id = data->Author.ID;
- const auto *user = Abaddon::Get().GetDiscordClient().GetUser(user_id);
+ const auto user_id = data->Author.ID;
+ const auto *user = client.GetUser(user_id);
if (user == nullptr) return;
- Abaddon::Get().GetImageManager().LoadFromURL(user->GetAvatarURL(), [this, user_id](Glib::RefPtr<Gdk::Pixbuf> buf) {
- // am i retarded?
+
+ header = Gtk::manage(new ChatMessageHeader(data));
+ m_num_rows++;
+ Abaddon::Get().GetImageManager().LoadFromURL(user->GetAvatarURL("png", "32"), [this, user_id](Glib::RefPtr<Gdk::Pixbuf> buf) {
Glib::signal_idle().connect([this, buf, user_id]() -> bool {
- auto children = m_listbox->get_children();
+ auto children = m_list->get_children();
for (auto child : children) {
- auto *row = dynamic_cast<ChatMessageContainer *>(child);
+ auto *row = dynamic_cast<ChatMessageHeader *>(child);
if (row == nullptr) continue;
- if (row->UserID == user_id) {
+ if (row->UserID == user_id)
row->SetAvatarFromPixbuf(buf);
- }
}
return false;
});
});
- m_num_rows++;
}
- ChatMessageItem *widget = CreateMessageComponent(data);
-
- if (widget != nullptr) {
- widget->SetContainer(container);
- container->AddNewContent(dynamic_cast<Gtk::Widget *>(widget), prepend);
+ auto *content = CreateMessageComponent(id);
+ if (content != nullptr) {
+ content->signal_action_delete().connect([this, id] {
+ m_signal_action_message_delete.emit(m_active_channel, id);
+ });
+ content->signal_action_edit().connect([this, id] {
+ m_signal_action_message_edit.emit(m_active_channel, id);
+ });
+ header->AddContent(content, prepend);
+ m_id_to_widget[id] = content;
}
- container->set_margin_left(5);
- container->show_all();
+ header->set_margin_left(5);
+ header->show_all();
if (!should_attach) {
if (prepend)
- m_listbox->prepend(*container);
+ m_list->prepend(*header);
else
- m_listbox->add(*container);
+ m_list->add(*header);
}
}
-bool ChatWindow::on_key_press_event(GdkEventKey *e) {
- if (e->keyval == GDK_KEY_Return) {
- auto buffer = m_input->get_buffer();
-
- if (e->state & GDK_SHIFT_MASK)
- return false;
-
- auto text = buffer->get_text();
- buffer->set_text("");
-
- m_signal_action_chat_submit.emit(text, m_active_channel);
+void ChatWindow::SetMessagesInternal() {
+ m_update_mutex.lock();
+ const auto *msgs = &m_set_messages_queue.front();
+ m_update_mutex.unlock();
- return true;
+ // empty the listbox
+ auto children = m_list->get_children();
+ auto it = children.begin();
+ while (it != children.end()) {
+ delete *it;
+ it++;
}
- return false;
-}
-
-void ChatWindow::on_scroll_edge_overshot(Gtk::PositionType pos) {
- if (pos == Gtk::POS_TOP)
- m_signal_action_chat_load_history.emit(m_active_channel);
-}
-
-void ChatWindow::SetMessages(std::set<Snowflake> msgs) {
- std::scoped_lock<std::mutex> guard(m_update_mutex);
- m_message_set_queue.push(msgs);
- m_message_set_dispatch.emit();
-}
-
-void ChatWindow::AddNewMessage(Snowflake id) {
- std::scoped_lock<std::mutex> guard(m_update_mutex);
- m_new_message_queue.push(id);
- m_new_message_dispatch.emit();
-}
-
-void ChatWindow::AddNewHistory(const std::vector<Snowflake> &msgs) {
- std::scoped_lock<std::mutex> guard(m_update_mutex);
- m_new_history_queue.push(msgs);
- m_new_history_dispatch.emit();
-}
-
-void ChatWindow::DeleteMessage(Snowflake id) {
- std::scoped_lock<std::mutex> guard(m_update_mutex);
- m_message_delete_queue.push(id);
- m_message_delete_dispatch.emit();
-}
-
-void ChatWindow::UpdateMessage(Snowflake id) {
- std::scoped_lock<std::mutex> guard(m_update_mutex);
- m_message_edit_queue.push(id);
- m_message_edit_dispatch.emit();
-}
-
-void ChatWindow::Clear() {
- std::scoped_lock<std::mutex> guard(m_update_mutex);
- m_active_channel = Snowflake::Invalid;
- m_message_set_queue.push(std::set<Snowflake>());
- m_message_set_dispatch.emit();
-}
-
-void ChatWindow::InsertChatInput(std::string text) {
- auto buf = m_input->get_buffer();
- buf->insert_at_cursor(text);
- m_input->grab_focus();
-}
-
-Snowflake ChatWindow::GetOldestListedMessage() {
- Snowflake m;
+ m_num_rows = 0;
+ m_id_to_widget.clear();
- for (const auto &[id, widget] : m_id_to_widget) {
- if (id < m)
- m = id;
+ for (const auto &id : *msgs) {
+ ProcessNewMessage(id, false);
}
- return m;
-}
-
-void ChatWindow::ScrollToBottom() {
- auto x = m_scroll->get_vadjustment();
- x->set_value(x->get_upper());
+ m_update_mutex.lock();
+ m_set_messages_queue.pop();
+ m_update_mutex.unlock();
}
void ChatWindow::AddNewMessageInternal() {
- Snowflake id;
- {
- std::scoped_lock<std::mutex> guard(m_update_mutex);
- id = m_new_message_queue.front();
- m_new_message_queue.pop();
- }
-
- auto data = Abaddon::Get().GetDiscordClient().GetMessage(id);
- ProcessMessage(data);
-}
-
-// todo this keeps the scrollbar at the top
-void ChatWindow::AddNewHistoryInternal() {
- std::set<Snowflake> msgs;
- {
- std::scoped_lock<std::mutex> guard(m_update_mutex);
- auto vec = m_new_history_queue.front();
- msgs = std::set<Snowflake>(vec.begin(), vec.end());
- }
-
- for (auto it = msgs.rbegin(); it != msgs.rend(); it++) {
- ProcessMessage(Abaddon::Get().GetDiscordClient().GetMessage(*it), true);
- }
-
- {
- std::scoped_lock<std::mutex> guard(m_update_mutex);
- m_new_history_queue.pop();
- }
+ m_update_mutex.lock();
+ const auto id = m_new_message_queue.front();
+ m_new_message_queue.pop();
+ m_update_mutex.unlock();
+ ProcessNewMessage(id, false);
}
void ChatWindow::DeleteMessageInternal() {
- Snowflake id;
- {
- std::scoped_lock<std::mutex> guard(m_update_mutex);
- id = m_message_delete_queue.front();
- m_message_delete_queue.pop();
- }
-
- if (m_id_to_widget.find(id) == m_id_to_widget.end())
- return;
+ m_update_mutex.lock();
+ const auto id = m_delete_message_queue.front();
+ m_delete_message_queue.pop();
+ m_update_mutex.unlock();
- // todo actually delete it when it becomes setting
+ auto widget = m_id_to_widget.find(id);
+ if (widget == m_id_to_widget.end()) return;
- auto *item = m_id_to_widget.at(id);
- item->Update();
+ auto *x = dynamic_cast<ChatMessageItemContainer *>(widget->second);
+ if (x != nullptr)
+ x->UpdateAttributes();
}
void ChatWindow::UpdateMessageInternal() {
- Snowflake id;
- {
- std::scoped_lock<std::mutex> guard(m_update_mutex);
- id = m_message_edit_queue.front();
- m_message_edit_queue.pop();
- }
-
- if (m_id_to_widget.find(id) == m_id_to_widget.end())
- return;
-
- // GetMessage should give us the new object at this point
- auto *msg = Abaddon::Get().GetDiscordClient().GetMessage(id);
- auto *item = dynamic_cast<Gtk::Widget *>(m_id_to_widget.at(id));
- if (item != nullptr) {
- ChatMessageContainer *container = dynamic_cast<ChatMessageItem *>(item)->GetContainer();
- int idx = container->RemoveItem(item);
- if (idx == -1) return;
- auto *new_widget = CreateMessageComponent(msg);
- new_widget->SetContainer(container);
- container->AddNewContentAtIndex(dynamic_cast<Gtk::Widget *>(new_widget), idx);
+ m_update_mutex.lock();
+ const auto id = m_update_message_queue.front();
+ m_update_message_queue.pop();
+ m_update_mutex.unlock();
+
+ auto widget = m_id_to_widget.find(id);
+ if (widget == m_id_to_widget.end()) return;
+
+ auto *x = dynamic_cast<ChatMessageItemContainer *>(widget->second);
+ if (x != nullptr) {
+ x->UpdateContent();
+ x->UpdateAttributes();
}
}
-void ChatWindow::SetMessagesInternal() {
- auto children = m_listbox->get_children();
- auto it = children.begin();
-
- while (it != children.end()) {
- delete *it;
- it++;
- }
-
- m_num_rows = 0;
- m_id_to_widget.clear();
-
- std::set<Snowflake> *msgs;
- {
- std::scoped_lock<std::mutex> guard(m_update_mutex);
- msgs = &m_message_set_queue.front();
- }
+void ChatWindow::AddNewHistoryInternal() {
+ m_update_mutex.lock();
+ auto msgs = m_history_queue.front();
+ m_history_queue.pop();
+ m_update_mutex.unlock();
- // sort
- std::map<Snowflake, const Message *> sorted_messages;
- for (const auto id : *msgs)
- sorted_messages[id] = Abaddon::Get().GetDiscordClient().GetMessage(id);
+ for (auto it = msgs.rbegin(); it != msgs.rend(); it++)
+ ProcessNewMessage(*it, true);
+}
- for (const auto &[id, msg] : sorted_messages) {
- ProcessMessage(msg);
- }
+void ChatWindow::on_scroll_edge_overshot(Gtk::PositionType pos) {
+ if (pos == Gtk::POS_TOP)
+ m_signal_action_chat_load_history.emit(m_active_channel);
+}
- {
- std::scoped_lock<std::mutex> guard(m_update_mutex);
- m_message_set_queue.pop();
- }
+void ChatWindow::ScrollToBottom() {
+ auto x = m_scroll->get_vadjustment();
+ x->set_value(x->get_upper());
}
ChatWindow::type_signal_action_message_delete ChatWindow::signal_action_message_delete() {
diff --git a/components/chatwindow.hpp b/components/chatwindow.hpp
index 570299d..a399c6b 100644
--- a/components/chatwindow.hpp
+++ b/components/chatwindow.hpp
@@ -1,66 +1,68 @@
#pragma once
#include <gtkmm.h>
-#include <queue>
+#include <string>
#include <mutex>
-#include <unordered_map>
-#include <sigc++/sigc++.h>
-#include "chatmessage.hpp"
+#include <queue>
+#include <set>
#include "../discord/discord.hpp"
+#include "chatmessage.hpp"
class ChatWindow {
public:
ChatWindow();
Gtk::Widget *GetRoot() const;
- void SetActiveChannel(Snowflake id);
Snowflake GetActiveChannel() const;
- void SetMessages(std::set<Snowflake> msgs);
- void AddNewMessage(Snowflake id);
- void AddNewHistory(const std::vector<Snowflake> &msgs);
- void DeleteMessage(Snowflake id);
- void UpdateMessage(Snowflake id);
+
void Clear();
+ void SetMessages(const std::set<Snowflake> &msgs); // clear contents and replace with given set
+ void SetActiveChannel(Snowflake id);
+ void AddNewMessage(Snowflake id); // append new message to bottom
+ void DeleteMessage(Snowflake id); // add [deleted] indicator
+ void UpdateMessage(Snowflake id); // add [edited] indicator
+ void AddNewHistory(const std::vector<Snowflake> &id); // prepend messages
void InsertChatInput(std::string text);
- Snowflake GetOldestListedMessage();
+ Snowflake GetOldestListedMessage(); // oldest message that is currently in the ListBox
protected:
- void ScrollToBottom();
+ ChatMessageItemContainer *CreateMessageComponent(Snowflake id); // to be inserted into header's content box
+ void ProcessNewMessage(Snowflake id, bool prepend); // creates and adds components
+
void SetMessagesInternal();
void AddNewMessageInternal();
- void AddNewHistoryInternal();
void DeleteMessageInternal();
void UpdateMessageInternal();
- ChatDisplayType GetMessageDisplayType(const Message *data);
- ChatMessageItem *CreateMessageComponent(const Message *data);
- void ProcessMessage(const Message *data, bool prepend = false);
- int m_num_rows = 0; // youd think thered be a Gtk::ListBox::get_row_count or something but nope
- std::unordered_map<Snowflake, ChatMessageItem *> m_id_to_widget;
+ void AddNewHistoryInternal();
- bool m_scroll_to_bottom = true;
+ int m_num_rows = 0;
+ std::unordered_map<Snowflake, Gtk::Widget *> m_id_to_widget;
- bool on_key_press_event(GdkEventKey *e);
- void on_scroll_edge_overshot(Gtk::PositionType pos);
-
- Glib::Dispatcher m_message_set_dispatch;
- std::queue<std::set<Snowflake>> m_message_set_queue;
+ Glib::Dispatcher m_set_messsages_dispatch;
+ std::queue<std::set<Snowflake>> m_set_messages_queue;
Glib::Dispatcher m_new_message_dispatch;
std::queue<Snowflake> m_new_message_queue;
- Glib::Dispatcher m_new_history_dispatch;
- std::queue<std::vector<Snowflake>> m_new_history_queue;
- Glib::Dispatcher m_message_delete_dispatch;
- std::queue<Snowflake> m_message_delete_queue;
- Glib::Dispatcher m_message_edit_dispatch;
- std::queue<Snowflake> m_message_edit_queue;
- std::mutex m_update_mutex;
+ Glib::Dispatcher m_delete_message_dispatch;
+ std::queue<Snowflake> m_delete_message_queue;
+ Glib::Dispatcher m_update_message_dispatch;
+ std::queue<Snowflake> m_update_message_queue;
+ Glib::Dispatcher m_history_dispatch;
+ std::queue<std::set<Snowflake>> m_history_queue;
+ mutable std::mutex m_update_mutex;
Snowflake m_active_channel;
+ bool on_key_press_event(GdkEventKey *e);
+ void on_scroll_edge_overshot(Gtk::PositionType pos);
+
+ void ScrollToBottom();
+ bool m_should_scroll_to_bottom = true;
+
Gtk::Box *m_main;
- Gtk::ListBox *m_listbox;
- Gtk::Viewport *m_viewport;
+ Gtk::ListBox *m_list;
Gtk::ScrolledWindow *m_scroll;
- Gtk::ScrolledWindow *m_entry_scroll;
+
Gtk::TextView *m_input;
+ Gtk::ScrolledWindow *m_input_scroll;
public:
typedef sigc::signal<void, Snowflake, Snowflake> type_signal_action_message_delete;