forked from OpenGamers/abaddon
use textviews in channel list + parse emojis
This commit is contained in:
parent
823e1786e0
commit
eb0feef511
@ -9,6 +9,24 @@ void ChannelListRow::Collapse() {}
|
||||
|
||||
void ChannelListRow::Expand() {}
|
||||
|
||||
void ChannelListRow::MakeReadOnly(Gtk::TextView *tv) {
|
||||
tv->set_can_focus(false);
|
||||
tv->set_editable(false);
|
||||
tv->signal_realize().connect([tv]() {
|
||||
auto window = tv->get_window(Gtk::TEXT_WINDOW_TEXT);
|
||||
auto display = window->get_display();
|
||||
auto cursor = Gdk::Cursor::create(display, "default"); // textview uses "text" which looks out of place
|
||||
window->set_cursor(cursor);
|
||||
});
|
||||
// stupid hack to prevent selection
|
||||
auto buf = tv->get_buffer();
|
||||
buf->property_has_selection().signal_changed().connect([tv, buf]() {
|
||||
Gtk::TextBuffer::iterator a, b;
|
||||
buf->get_bounds(a, b);
|
||||
buf->select_range(a, a);
|
||||
});
|
||||
}
|
||||
|
||||
ChannelListRowDMHeader::ChannelListRowDMHeader() {
|
||||
m_ev = Gtk::manage(new Gtk::EventBox);
|
||||
m_box = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL));
|
||||
@ -31,7 +49,8 @@ ChannelListRowDMChannel::ChannelListRowDMChannel(const Channel *data) {
|
||||
ID = data->ID;
|
||||
m_ev = Gtk::manage(new Gtk::EventBox);
|
||||
m_box = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL));
|
||||
m_lbl = Gtk::manage(new Gtk::Label);
|
||||
m_lbl = Gtk::manage(new Gtk::TextView);
|
||||
MakeReadOnly(m_lbl);
|
||||
|
||||
get_style_context()->add_class("channel-row");
|
||||
m_lbl->get_style_context()->add_class("channel-row-label");
|
||||
@ -50,10 +69,12 @@ ChannelListRowDMChannel::ChannelListRowDMChannel(const Channel *data) {
|
||||
}
|
||||
}
|
||||
|
||||
auto buf = m_lbl->get_buffer();
|
||||
if (data->Type == ChannelType::DM)
|
||||
m_lbl->set_text(data->Recipients[0].Username);
|
||||
buf->set_text(data->Recipients[0].Username);
|
||||
else if (data->Type == ChannelType::GROUP_DM)
|
||||
m_lbl->set_text(std::to_string(data->Recipients.size()) + " users");
|
||||
buf->set_text(std::to_string(data->Recipients.size()) + " users");
|
||||
Abaddon::Get().GetEmojis().ReplaceEmojis(buf, ChannelEmojiSize);
|
||||
|
||||
m_box->set_halign(Gtk::ALIGN_START);
|
||||
if (m_icon != nullptr)
|
||||
@ -73,7 +94,8 @@ ChannelListRowGuild::ChannelListRowGuild(const Guild *data) {
|
||||
ID = data->ID;
|
||||
m_ev = Gtk::manage(new Gtk::EventBox);
|
||||
m_box = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL));
|
||||
m_lbl = Gtk::manage(new Gtk::Label);
|
||||
m_lbl = Gtk::manage(new Gtk::TextView);
|
||||
MakeReadOnly(m_lbl);
|
||||
|
||||
if (data->HasIcon()) {
|
||||
auto buf = Abaddon::Get().GetImageManager().GetFromURLIfCached(data->GetIconURL("png", "32"));
|
||||
@ -91,7 +113,11 @@ ChannelListRowGuild::ChannelListRowGuild(const Guild *data) {
|
||||
get_style_context()->add_class("channel-row-guild");
|
||||
m_lbl->get_style_context()->add_class("channel-row-label");
|
||||
|
||||
m_lbl->set_markup("<b>" + Glib::Markup::escape_text(data->Name) + "</b>");
|
||||
auto buf = m_lbl->get_buffer();
|
||||
Gtk::TextBuffer::iterator start, end;
|
||||
buf->get_bounds(start, end);
|
||||
buf->insert_markup(start, "<b>" + Glib::Markup::escape_text(data->Name) + "</b>");
|
||||
Abaddon::Get().GetEmojis().ReplaceEmojis(buf, ChannelEmojiSize);
|
||||
m_box->set_halign(Gtk::ALIGN_START);
|
||||
m_box->pack_start(*m_icon);
|
||||
m_box->pack_start(*m_lbl);
|
||||
@ -108,14 +134,17 @@ ChannelListRowCategory::ChannelListRowCategory(const Channel *data) {
|
||||
ID = data->ID;
|
||||
m_ev = Gtk::manage(new Gtk::EventBox);
|
||||
m_box = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL));
|
||||
m_lbl = Gtk::manage(new Gtk::Label);
|
||||
m_lbl = Gtk::manage(new Gtk::TextView);
|
||||
MakeReadOnly(m_lbl);
|
||||
m_arrow = Gtk::manage(new Gtk::Arrow(Gtk::ARROW_DOWN, Gtk::SHADOW_NONE));
|
||||
|
||||
get_style_context()->add_class("channel-row");
|
||||
get_style_context()->add_class("channel-row-category");
|
||||
m_lbl->get_style_context()->add_class("channel-row-label");
|
||||
|
||||
m_lbl->set_text(data->Name);
|
||||
auto buf = m_lbl->get_buffer();
|
||||
buf->set_text(data->Name);
|
||||
Abaddon::Get().GetEmojis().ReplaceEmojis(buf, ChannelEmojiSize);
|
||||
m_box->set_halign(Gtk::ALIGN_START);
|
||||
m_box->pack_start(*m_arrow);
|
||||
m_box->pack_start(*m_lbl);
|
||||
@ -136,13 +165,16 @@ ChannelListRowChannel::ChannelListRowChannel(const Channel *data) {
|
||||
ID = data->ID;
|
||||
m_ev = Gtk::manage(new Gtk::EventBox);
|
||||
m_box = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL));
|
||||
m_lbl = Gtk::manage(new Gtk::Label);
|
||||
m_lbl = Gtk::manage(new Gtk::TextView);
|
||||
MakeReadOnly(m_lbl);
|
||||
|
||||
get_style_context()->add_class("channel-row");
|
||||
get_style_context()->add_class("channel-row-channel");
|
||||
m_lbl->get_style_context()->add_class("channel-row-label");
|
||||
|
||||
m_lbl->set_text("#" + data->Name);
|
||||
auto buf = m_lbl->get_buffer();
|
||||
buf->set_text("#" + data->Name);
|
||||
Abaddon::Get().GetEmojis().ReplaceEmojis(buf, ChannelEmojiSize);
|
||||
m_box->set_halign(Gtk::ALIGN_START);
|
||||
m_box->pack_start(*m_lbl);
|
||||
m_ev->add(*m_box);
|
||||
@ -189,6 +221,18 @@ ChannelList::ChannelList() {
|
||||
m_main->show_all();
|
||||
|
||||
m_update_dispatcher.connect(sigc::mem_fun(*this, &ChannelList::UpdateListingInternal));
|
||||
|
||||
// maybe will regret doing it this way
|
||||
auto &discord = Abaddon::Get().GetDiscordClient();
|
||||
discord.signal_message_create().connect(sigc::track_obj([this, &discord](Snowflake message_id) {
|
||||
const auto *message = discord.GetMessage(message_id);
|
||||
const auto *channel = discord.GetChannel(message->ChannelID);
|
||||
if (channel == nullptr) return;
|
||||
if (channel->Type == ChannelType::DM || channel->Type == ChannelType::GROUP_DM)
|
||||
CheckBumpDM(message->ChannelID);
|
||||
// clang-format off
|
||||
}, this));
|
||||
// clang-format on
|
||||
}
|
||||
|
||||
Gtk::Widget *ChannelList::GetRoot() const {
|
||||
@ -462,6 +506,7 @@ void ChannelList::DeleteRow(ChannelListRow *row) {
|
||||
m_id_to_row.erase(row->ID);
|
||||
delete row;
|
||||
}
|
||||
|
||||
void ChannelList::on_row_activated(Gtk::ListBoxRow *tmprow) {
|
||||
auto row = dynamic_cast<ChannelListRow *>(tmprow);
|
||||
if (row == nullptr) return;
|
||||
@ -556,7 +601,14 @@ void ChannelList::InsertGuildAt(Snowflake id, int pos) {
|
||||
}
|
||||
|
||||
void ChannelList::AddPrivateChannels() {
|
||||
auto dms = Abaddon::Get().GetDiscordClient().GetPrivateChannels();
|
||||
auto dms_ = Abaddon::Get().GetDiscordClient().GetPrivateChannels();
|
||||
std::vector<const Channel *> dms;
|
||||
const auto &discord = Abaddon::Get().GetDiscordClient();
|
||||
for (const auto &x : dms_)
|
||||
dms.push_back(discord.GetChannel(x));
|
||||
std::sort(dms.begin(), dms.end(), [&](const Channel *a, const Channel *b) -> bool {
|
||||
return a->LastMessageID > b->LastMessageID;
|
||||
});
|
||||
|
||||
m_dm_header_row = Gtk::manage(new ChannelListRowDMHeader);
|
||||
m_dm_header_row->show_all();
|
||||
@ -564,7 +616,9 @@ void ChannelList::AddPrivateChannels() {
|
||||
m_list->add(*m_dm_header_row);
|
||||
|
||||
for (const auto &dm : dms) {
|
||||
auto *dm_row = Gtk::manage(new ChannelListRowDMChannel(Abaddon::Get().GetDiscordClient().GetChannel(dm)));
|
||||
auto *dm_row = Gtk::manage(new ChannelListRowDMChannel(dm));
|
||||
dm_row->Parent = m_dm_header_row;
|
||||
m_dm_id_to_row[dm->ID] = dm_row;
|
||||
dm_row->IsUserCollapsed = false;
|
||||
m_list->add(*dm_row);
|
||||
m_dm_header_row->Children.insert(dm_row);
|
||||
@ -660,6 +714,28 @@ void ChannelList::AttachChannelMenuHandler(Gtk::ListBoxRow *row) {
|
||||
});
|
||||
}
|
||||
|
||||
void ChannelList::CheckBumpDM(Snowflake channel_id) {
|
||||
auto it = m_dm_id_to_row.find(channel_id);
|
||||
if (it == m_dm_id_to_row.end()) return;
|
||||
auto *row = it->second;
|
||||
const auto index = row->get_index();
|
||||
if (index == 1) return; // 1 is top of dm list
|
||||
const bool selected = row->is_selected();
|
||||
row->Parent->Children.erase(row);
|
||||
delete row;
|
||||
auto *dm_row = Gtk::manage(new ChannelListRowDMChannel(Abaddon::Get().GetDiscordClient().GetChannel(channel_id)));
|
||||
dm_row->Parent = m_dm_header_row;
|
||||
m_dm_header_row->Children.insert(dm_row);
|
||||
m_dm_id_to_row[channel_id] = dm_row;
|
||||
dm_row->IsUserCollapsed = false;
|
||||
m_list->insert(*dm_row, 1);
|
||||
m_dm_header_row->Children.insert(dm_row);
|
||||
if (selected)
|
||||
m_list->select_row(*dm_row);
|
||||
if (m_dm_header_row->is_visible() && !m_dm_header_row->IsUserCollapsed)
|
||||
dm_row->show();
|
||||
}
|
||||
|
||||
ChannelList::type_signal_action_channel_item_select ChannelList::signal_action_channel_item_select() {
|
||||
return m_signal_action_channel_item_select;
|
||||
}
|
||||
|
@ -8,6 +8,8 @@
|
||||
#include <sigc++/sigc++.h>
|
||||
#include "../discord/discord.hpp"
|
||||
|
||||
static const constexpr int ChannelEmojiSize = 16;
|
||||
|
||||
class ChannelListRow : public Gtk::ListBoxRow {
|
||||
public:
|
||||
bool IsUserCollapsed;
|
||||
@ -17,6 +19,8 @@ public:
|
||||
|
||||
virtual void Collapse();
|
||||
virtual void Expand();
|
||||
|
||||
static void MakeReadOnly(Gtk::TextView *tv);
|
||||
};
|
||||
|
||||
class ChannelListRowDMHeader : public ChannelListRow {
|
||||
@ -38,7 +42,7 @@ protected:
|
||||
|
||||
Gtk::EventBox *m_ev;
|
||||
Gtk::Box *m_box;
|
||||
Gtk::Label *m_lbl;
|
||||
Gtk::TextView *m_lbl;
|
||||
Gtk::Image *m_icon = nullptr;
|
||||
};
|
||||
|
||||
@ -53,7 +57,7 @@ protected:
|
||||
|
||||
Gtk::EventBox *m_ev;
|
||||
Gtk::Box *m_box;
|
||||
Gtk::Label *m_lbl;
|
||||
Gtk::TextView *m_lbl;
|
||||
Gtk::Image *m_icon;
|
||||
};
|
||||
|
||||
@ -67,7 +71,7 @@ public:
|
||||
protected:
|
||||
Gtk::EventBox *m_ev;
|
||||
Gtk::Box *m_box;
|
||||
Gtk::Label *m_lbl;
|
||||
Gtk::TextView *m_lbl;
|
||||
Gtk::Arrow *m_arrow;
|
||||
};
|
||||
|
||||
@ -78,7 +82,7 @@ public:
|
||||
protected:
|
||||
Gtk::EventBox *m_ev;
|
||||
Gtk::Box *m_box;
|
||||
Gtk::Label *m_lbl;
|
||||
Gtk::TextView *m_lbl;
|
||||
};
|
||||
|
||||
class ChannelList {
|
||||
@ -133,6 +137,7 @@ protected:
|
||||
// i would use one map but in really old guilds there can be a channel w/ same id as the guild so this hacky shit has to do
|
||||
std::unordered_map<Snowflake, ChannelListRow *> m_guild_id_to_row;
|
||||
std::unordered_map<Snowflake, ChannelListRow *> m_id_to_row;
|
||||
std::unordered_map<Snowflake, ChannelListRow *> m_dm_id_to_row;
|
||||
|
||||
void InsertGuildAt(Snowflake id, int pos);
|
||||
|
||||
@ -141,6 +146,8 @@ protected:
|
||||
void AttachGuildMenuHandler(Gtk::ListBoxRow *row);
|
||||
void AttachChannelMenuHandler(Gtk::ListBoxRow *row);
|
||||
|
||||
void CheckBumpDM(Snowflake channel_id);
|
||||
|
||||
public:
|
||||
typedef sigc::signal<void, Snowflake> type_signal_action_channel_item_select;
|
||||
typedef sigc::signal<void, Snowflake> type_signal_action_guild_move_up;
|
||||
|
@ -451,38 +451,7 @@ void ChatMessageItemContainer::HandleUserMentions(Gtk::TextView *tv) {
|
||||
}
|
||||
|
||||
void ChatMessageItemContainer::HandleStockEmojis(Gtk::TextView *tv) {
|
||||
auto buf = tv->get_buffer();
|
||||
auto text = GetText(buf);
|
||||
|
||||
auto &emojis = Abaddon::Get().GetEmojis();
|
||||
int searchpos;
|
||||
for (const auto &pattern : emojis.GetPatterns()) {
|
||||
searchpos = 0;
|
||||
Glib::RefPtr<Gdk::Pixbuf> pixbuf;
|
||||
while (true) {
|
||||
size_t r = text.find(pattern, searchpos);
|
||||
if (r == Glib::ustring::npos) break;
|
||||
if (!pixbuf) {
|
||||
pixbuf = emojis.GetPixBuf(pattern);
|
||||
if (pixbuf)
|
||||
pixbuf = pixbuf->scale_simple(24, 24, Gdk::INTERP_BILINEAR);
|
||||
else
|
||||
break;
|
||||
}
|
||||
searchpos = r + pattern.size();
|
||||
|
||||
const auto start_it = buf->get_iter_at_offset(r);
|
||||
const auto end_it = buf->get_iter_at_offset(r + pattern.size());
|
||||
|
||||
auto it = buf->erase(start_it, end_it);
|
||||
buf->insert_pixbuf(it, pixbuf);
|
||||
|
||||
int alen = text.size();
|
||||
text = GetText(buf);
|
||||
int blen = text.size();
|
||||
searchpos -= (alen - blen);
|
||||
}
|
||||
}
|
||||
Abaddon::Get().GetEmojis().ReplaceEmojis(tv->get_buffer());
|
||||
}
|
||||
|
||||
void ChatMessageItemContainer::HandleCustomEmojis(Gtk::TextView *tv) {
|
||||
|
@ -49,7 +49,7 @@ ChatWindow::ChatWindow() {
|
||||
m_input->set_halign(Gtk::ALIGN_FILL);
|
||||
m_input->set_wrap_mode(Gtk::WRAP_WORD_CHAR);
|
||||
|
||||
m_input_scroll->set_max_content_height(170);
|
||||
m_input_scroll->set_max_content_height(200);
|
||||
m_input_scroll->set_policy(Gtk::POLICY_NEVER, Gtk::POLICY_AUTOMATIC);
|
||||
|
||||
m_input_scroll->add(*m_input);
|
||||
|
@ -21,7 +21,11 @@
|
||||
|
||||
.channel-row-label {
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.channel-row-label, .channel-row-label text {
|
||||
color: #cfd8dc;
|
||||
background: rgba(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
.channel-row:focus {
|
||||
|
@ -14,10 +14,10 @@ void from_json(const nlohmann::json &j, Guild &m) {
|
||||
JS_ON("discovery_splash", m.DiscoverySplash);
|
||||
JS_O("owner", m.IsOwner);
|
||||
JS_D("owner_id", m.OwnerID);
|
||||
std::string tmp;
|
||||
std::optional<std::string> tmp;
|
||||
JS_O("permissions", tmp);
|
||||
if (tmp != "")
|
||||
m.Permissions = std::stoull(tmp);
|
||||
if (tmp.has_value())
|
||||
m.Permissions = std::stoull(*tmp);
|
||||
JS_D("region", m.VoiceRegion);
|
||||
JS_N("afk_channel_id", m.AFKChannelID);
|
||||
JS_D("afk_timeout", m.AFKTimeout);
|
||||
@ -54,8 +54,12 @@ void from_json(const nlohmann::json &j, Guild &m) {
|
||||
JS_D("preferred_locale", m.PreferredLocale);
|
||||
JS_N("public_updates_channel_id", m.PublicUpdatesChannelID);
|
||||
JS_O("max_video_channel_users", m.MaxVideoChannelUsers);
|
||||
JS_O("approximate_member_count", m.ApproximateMemberCount);
|
||||
JS_O("approximate_presence_count", m.ApproximatePresenceCount);
|
||||
JS_O("approximate_member_count", tmp);
|
||||
if (tmp.has_value())
|
||||
m.ApproximateMemberCount = std::stoull(*tmp);
|
||||
JS_O("approximate_presence_count", tmp);
|
||||
if (tmp.has_value())
|
||||
m.ApproximatePresenceCount = std::stoull(*tmp);
|
||||
}
|
||||
|
||||
void Guild::update_from_json(const nlohmann::json &j) {
|
||||
|
37
emojis.cpp
37
emojis.cpp
@ -72,6 +72,43 @@ Glib::ustring EmojiResource::HexToPattern(Glib::ustring hex) {
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
void EmojiResource::ReplaceEmojis(Glib::RefPtr<Gtk::TextBuffer> buf, int size) {
|
||||
auto get_text = [&]() -> auto {
|
||||
Gtk::TextBuffer::iterator a, b;
|
||||
buf->get_bounds(a, b);
|
||||
return buf->get_slice(a, b, true);
|
||||
};
|
||||
auto text = get_text();
|
||||
|
||||
int searchpos;
|
||||
for (const auto &pattern : m_patterns) {
|
||||
searchpos = 0;
|
||||
Glib::RefPtr<Gdk::Pixbuf> pixbuf;
|
||||
while (true) {
|
||||
size_t r = text.find(pattern, searchpos);
|
||||
if (r == Glib::ustring::npos) break;
|
||||
if (!pixbuf) {
|
||||
pixbuf = GetPixBuf(pattern);
|
||||
if (pixbuf)
|
||||
pixbuf = pixbuf->scale_simple(size, size, Gdk::INTERP_BILINEAR);
|
||||
else
|
||||
break;
|
||||
}
|
||||
searchpos = r + pattern.size();
|
||||
|
||||
const auto start_it = buf->get_iter_at_offset(r);
|
||||
const auto end_it = buf->get_iter_at_offset(r + pattern.size());
|
||||
|
||||
auto it = buf->erase(start_it, end_it);
|
||||
buf->insert_pixbuf(it, pixbuf);
|
||||
|
||||
int alen = text.size();
|
||||
text = get_text();
|
||||
int blen = text.size();
|
||||
searchpos -= (alen - blen);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const std::vector<Glib::ustring> &EmojiResource::GetPatterns() const {
|
||||
return m_patterns;
|
||||
|
@ -15,6 +15,7 @@ public:
|
||||
static Glib::ustring PatternToHex(const Glib::ustring &pattern);
|
||||
static Glib::ustring HexToPattern(Glib::ustring hex);
|
||||
const std::vector<Glib::ustring> &GetPatterns() const;
|
||||
void ReplaceEmojis(Glib::RefPtr<Gtk::TextBuffer> buf, int size = 24);
|
||||
|
||||
private:
|
||||
std::unordered_map<std::string, std::pair<int, int>> m_index; // pattern -> [pos, len]
|
||||
|
Loading…
Reference in New Issue
Block a user