add clickable channel mentions, rework links a little
This commit is contained in:
@@ -67,6 +67,7 @@ int Abaddon::StartGTK() {
|
|||||||
m_main_window->GetChatWindow()->signal_action_message_edit().connect(sigc::mem_fun(*this, &Abaddon::ActionChatEditMessage));
|
m_main_window->GetChatWindow()->signal_action_message_edit().connect(sigc::mem_fun(*this, &Abaddon::ActionChatEditMessage));
|
||||||
m_main_window->GetChatWindow()->signal_action_chat_submit().connect(sigc::mem_fun(*this, &Abaddon::ActionChatInputSubmit));
|
m_main_window->GetChatWindow()->signal_action_chat_submit().connect(sigc::mem_fun(*this, &Abaddon::ActionChatInputSubmit));
|
||||||
m_main_window->GetChatWindow()->signal_action_chat_load_history().connect(sigc::mem_fun(*this, &Abaddon::ActionChatLoadHistory));
|
m_main_window->GetChatWindow()->signal_action_chat_load_history().connect(sigc::mem_fun(*this, &Abaddon::ActionChatLoadHistory));
|
||||||
|
m_main_window->GetChatWindow()->signal_action_channel_click().connect(sigc::mem_fun(*this, &Abaddon::ActionListChannelItemClick)); // rename me
|
||||||
|
|
||||||
m_main_window->GetMemberList()->signal_action_insert_mention().connect(sigc::mem_fun(*this, &Abaddon::ActionInsertMention));
|
m_main_window->GetMemberList()->signal_action_insert_mention().connect(sigc::mem_fun(*this, &Abaddon::ActionInsertMention));
|
||||||
|
|
||||||
|
@@ -172,6 +172,7 @@ void ChatMessageItemContainer::UpdateTextComponent(Gtk::TextView *tv) {
|
|||||||
case MessageType::DEFAULT:
|
case MessageType::DEFAULT:
|
||||||
b->insert_markup(s, ParseMessageContent(Glib::Markup::escape_text(data->Content)));
|
b->insert_markup(s, ParseMessageContent(Glib::Markup::escape_text(data->Content)));
|
||||||
HandleLinks(tv);
|
HandleLinks(tv);
|
||||||
|
HandleChannelMentions(tv);
|
||||||
break;
|
break;
|
||||||
case MessageType::GUILD_MEMBER_JOIN:
|
case MessageType::GUILD_MEMBER_JOIN:
|
||||||
b->insert_markup(s, "<span color='#999999'><i>[user joined]</i></span>");
|
b->insert_markup(s, "<span color='#999999'><i>[user joined]</i></span>");
|
||||||
@@ -389,6 +390,75 @@ std::string ChatMessageItemContainer::ParseMentions(std::string content) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ChatMessageItemContainer::HandleChannelMentions(Gtk::TextView *tv) {
|
||||||
|
constexpr static const auto chan_regex = R"(<#(\d+)>)";
|
||||||
|
|
||||||
|
std::regex rgx(chan_regex, std::regex_constants::ECMAScript);
|
||||||
|
|
||||||
|
tv->signal_button_press_event().connect(sigc::mem_fun(*this, &ChatMessageItemContainer::OnClickChannel), false);
|
||||||
|
|
||||||
|
auto buf = tv->get_buffer();
|
||||||
|
std::string text = buf->get_text();
|
||||||
|
|
||||||
|
const auto &discord = Abaddon::Get().GetDiscordClient();
|
||||||
|
|
||||||
|
std::string::const_iterator sstart(text.begin());
|
||||||
|
std::smatch match;
|
||||||
|
while (std::regex_search(sstart, text.cend(), match, rgx)) {
|
||||||
|
std::string channel_id = match.str(1);
|
||||||
|
const auto *chan = discord.GetChannel(channel_id);
|
||||||
|
if (chan == nullptr) {
|
||||||
|
sstart = match.suffix().first;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto tag = buf->create_tag();
|
||||||
|
m_channel_tagmap[tag] = channel_id;
|
||||||
|
tag->property_weight() = Pango::WEIGHT_BOLD;
|
||||||
|
|
||||||
|
const auto start = std::distance(text.cbegin(), sstart) + match.position();
|
||||||
|
auto erase_from = buf->get_iter_at_offset(start);
|
||||||
|
auto erase_to = buf->get_iter_at_offset(start + match.length());
|
||||||
|
auto it = buf->erase(erase_from, erase_to);
|
||||||
|
const std::string replacement = "#" + chan->Name;
|
||||||
|
it = buf->insert_with_tag(it, "#" + chan->Name, tag);
|
||||||
|
|
||||||
|
// rescan the whole thing so i dont have to deal with fixing match positions
|
||||||
|
text = buf->get_text();
|
||||||
|
sstart = text.begin();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// a lot of repetition here so there should probably just be one slot for textview's button-press
|
||||||
|
bool ChatMessageItemContainer::OnClickChannel(GdkEventButton *ev) {
|
||||||
|
if (m_text_component == nullptr) return false;
|
||||||
|
if (ev->type != Gdk::BUTTON_PRESS) return false;
|
||||||
|
if (ev->button != GDK_BUTTON_PRIMARY) return false;
|
||||||
|
|
||||||
|
auto buf = m_text_component->get_buffer();
|
||||||
|
Gtk::TextBuffer::iterator start, end;
|
||||||
|
buf->get_selection_bounds(start, end); // no open if selection
|
||||||
|
if (start.get_offset() != end.get_offset())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
int x, y;
|
||||||
|
m_text_component->window_to_buffer_coords(Gtk::TEXT_WINDOW_WIDGET, ev->x, ev->y, x, y);
|
||||||
|
Gtk::TextBuffer::iterator iter;
|
||||||
|
m_text_component->get_iter_at_location(iter, x, y);
|
||||||
|
|
||||||
|
const auto tags = iter.get_tags();
|
||||||
|
for (auto tag : tags) {
|
||||||
|
const auto it = m_channel_tagmap.find(tag);
|
||||||
|
if (it != m_channel_tagmap.end()) {
|
||||||
|
m_signal_action_channel_click.emit(it->second);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
void ChatMessageItemContainer::HandleLinks(Gtk::TextView *tv) {
|
void ChatMessageItemContainer::HandleLinks(Gtk::TextView *tv) {
|
||||||
constexpr static const auto links_regex = R"(\bhttps?:\/\/[^\s]+\.[^\s]+\b)";
|
constexpr static const auto links_regex = R"(\bhttps?:\/\/[^\s]+\.[^\s]+\b)";
|
||||||
|
|
||||||
@@ -397,43 +467,30 @@ void ChatMessageItemContainer::HandleLinks(Gtk::TextView *tv) {
|
|||||||
tv->signal_button_press_event().connect(sigc::mem_fun(*this, &ChatMessageItemContainer::OnLinkClick), false);
|
tv->signal_button_press_event().connect(sigc::mem_fun(*this, &ChatMessageItemContainer::OnLinkClick), false);
|
||||||
|
|
||||||
auto buf = tv->get_buffer();
|
auto buf = tv->get_buffer();
|
||||||
std::string text = buf->get_text();
|
Gtk::TextBuffer::iterator start, end;
|
||||||
buf->set_text("");
|
buf->get_bounds(start, end);
|
||||||
|
std::string text = buf->get_slice(start, end);
|
||||||
std::string::const_iterator sstart(text.begin());
|
|
||||||
std::smatch match;
|
|
||||||
bool any = false;
|
|
||||||
|
|
||||||
// i'd like to let this be done thru css like .message-link { color: #bitch; } but idk how
|
// i'd like to let this be done thru css like .message-link { color: #bitch; } but idk how
|
||||||
auto &settings = Abaddon::Get().GetSettings();
|
auto &settings = Abaddon::Get().GetSettings();
|
||||||
auto link_color = settings.GetSettingString("misc", "linkcolor", "rgba(40, 200, 180, 255)");
|
auto link_color = settings.GetSettingString("misc", "linkcolor", "rgba(40, 200, 180, 255)");
|
||||||
|
|
||||||
|
std::string::const_iterator sstart(text.begin());
|
||||||
|
std::smatch match;
|
||||||
while (std::regex_search(sstart, text.cend(), match, rgx)) {
|
while (std::regex_search(sstart, text.cend(), match, rgx)) {
|
||||||
any = true;
|
|
||||||
|
|
||||||
Gtk::TextBuffer::iterator start, end;
|
|
||||||
buf->get_bounds(start, end);
|
|
||||||
|
|
||||||
std::string pre = match.prefix().str();
|
|
||||||
|
|
||||||
std::string link = match.str();
|
std::string link = match.str();
|
||||||
auto tag = buf->create_tag();
|
auto tag = buf->create_tag();
|
||||||
m_linkmap[tag] = link;
|
m_link_tagmap[tag] = link;
|
||||||
tag->property_foreground_rgba() = Gdk::RGBA(link_color);
|
tag->property_foreground_rgba() = Gdk::RGBA(link_color);
|
||||||
buf->get_bounds(start, end);
|
|
||||||
end = buf->insert(end, pre);
|
const auto start = std::distance(text.cbegin(), sstart) + match.position();
|
||||||
end = buf->insert_with_tag(end, link, tag);
|
auto erase_from = buf->get_iter_at_offset(start);
|
||||||
|
auto erase_to = buf->get_iter_at_offset(start + match.length());
|
||||||
|
auto it = buf->erase(erase_from, erase_to);
|
||||||
|
it = buf->insert_with_tag(it, link, tag);
|
||||||
|
|
||||||
sstart = match.suffix().first;
|
sstart = match.suffix().first;
|
||||||
}
|
}
|
||||||
|
|
||||||
Gtk::TextBuffer::iterator start, end;
|
|
||||||
buf->get_bounds(start, end);
|
|
||||||
if (any) {
|
|
||||||
buf->insert(end, match.suffix().str());
|
|
||||||
} else {
|
|
||||||
buf->insert(end, text);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ChatMessageItemContainer::OnLinkClick(GdkEventButton *ev) {
|
bool ChatMessageItemContainer::OnLinkClick(GdkEventButton *ev) {
|
||||||
@@ -454,8 +511,8 @@ bool ChatMessageItemContainer::OnLinkClick(GdkEventButton *ev) {
|
|||||||
|
|
||||||
const auto tags = iter.get_tags();
|
const auto tags = iter.get_tags();
|
||||||
for (auto tag : tags) {
|
for (auto tag : tags) {
|
||||||
const auto it = m_linkmap.find(tag);
|
const auto it = m_link_tagmap.find(tag);
|
||||||
if (it != m_linkmap.end()) {
|
if (it != m_link_tagmap.end()) {
|
||||||
LaunchBrowser(it->second);
|
LaunchBrowser(it->second);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -501,6 +558,10 @@ ChatMessageItemContainer::type_signal_action_edit ChatMessageItemContainer::sign
|
|||||||
return m_signal_action_edit;
|
return m_signal_action_edit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ChatMessageItemContainer::type_signal_channel_click ChatMessageItemContainer::signal_action_channel_click() {
|
||||||
|
return m_signal_action_channel_click;
|
||||||
|
}
|
||||||
|
|
||||||
ChatMessageItemContainer::type_signal_image_load ChatMessageItemContainer::signal_image_load() {
|
ChatMessageItemContainer::type_signal_image_load ChatMessageItemContainer::signal_image_load() {
|
||||||
return m_signal_image_load;
|
return m_signal_image_load;
|
||||||
}
|
}
|
||||||
|
@@ -30,9 +30,14 @@ protected:
|
|||||||
std::string ParseMessageContent(std::string content);
|
std::string ParseMessageContent(std::string content);
|
||||||
std::string ParseMentions(std::string content);
|
std::string ParseMentions(std::string content);
|
||||||
|
|
||||||
void HandleLinks(Gtk::TextView *tv);
|
void HandleChannelMentions(Gtk::TextView *tv);
|
||||||
|
bool OnClickChannel(GdkEventButton *ev);
|
||||||
|
|
||||||
|
void
|
||||||
|
HandleLinks(Gtk::TextView *tv);
|
||||||
bool OnLinkClick(GdkEventButton *ev);
|
bool OnLinkClick(GdkEventButton *ev);
|
||||||
std::map<Glib::RefPtr<Gtk::TextTag>, std::string> m_linkmap; // sue me
|
std::map<Glib::RefPtr<Gtk::TextTag>, std::string> m_link_tagmap;
|
||||||
|
std::map<Glib::RefPtr<Gtk::TextTag>, Snowflake> m_channel_tagmap;
|
||||||
|
|
||||||
std::unordered_map<std::string, std::pair<Gtk::Image *, AttachmentData>> m_img_loadmap;
|
std::unordered_map<std::string, std::pair<Gtk::Image *, AttachmentData>> m_img_loadmap;
|
||||||
|
|
||||||
@@ -61,15 +66,18 @@ public:
|
|||||||
|
|
||||||
typedef sigc::signal<void> type_signal_action_delete;
|
typedef sigc::signal<void> type_signal_action_delete;
|
||||||
typedef sigc::signal<void> type_signal_action_edit;
|
typedef sigc::signal<void> type_signal_action_edit;
|
||||||
|
typedef sigc::signal<void, Snowflake> type_signal_channel_click;
|
||||||
|
|
||||||
type_signal_action_delete signal_action_delete();
|
type_signal_action_delete signal_action_delete();
|
||||||
type_signal_action_edit signal_action_edit();
|
type_signal_action_edit signal_action_edit();
|
||||||
|
type_signal_channel_click signal_action_channel_click();
|
||||||
|
|
||||||
type_signal_image_load signal_image_load();
|
type_signal_image_load signal_image_load();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
type_signal_action_delete m_signal_action_delete;
|
type_signal_action_delete m_signal_action_delete;
|
||||||
type_signal_action_edit m_signal_action_edit;
|
type_signal_action_edit m_signal_action_edit;
|
||||||
|
type_signal_channel_click m_signal_action_channel_click;
|
||||||
|
|
||||||
type_signal_image_load m_signal_image_load;
|
type_signal_image_load m_signal_image_load;
|
||||||
};
|
};
|
||||||
|
@@ -45,7 +45,8 @@ ChatWindow::ChatWindow() {
|
|||||||
m_list->set_focus_hadjustment(m_scroll->get_hadjustment());
|
m_list->set_focus_hadjustment(m_scroll->get_hadjustment());
|
||||||
m_list->set_focus_vadjustment(m_scroll->get_vadjustment());
|
m_list->set_focus_vadjustment(m_scroll->get_vadjustment());
|
||||||
|
|
||||||
m_input->set_hexpand(true);
|
m_input->set_hexpand(false);
|
||||||
|
m_input->set_halign(Gtk::ALIGN_FILL);
|
||||||
m_input->set_wrap_mode(Gtk::WRAP_WORD_CHAR);
|
m_input->set_wrap_mode(Gtk::WRAP_WORD_CHAR);
|
||||||
|
|
||||||
m_input_scroll->set_max_content_height(170);
|
m_input_scroll->set_max_content_height(170);
|
||||||
@@ -218,6 +219,9 @@ void ChatWindow::ProcessNewMessage(Snowflake id, bool prepend) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
content->signal_action_channel_click().connect([this](const Snowflake &id) {
|
||||||
|
m_signal_action_channel_click.emit(id);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
header->set_margin_left(5);
|
header->set_margin_left(5);
|
||||||
@@ -326,3 +330,7 @@ ChatWindow::type_signal_action_chat_submit ChatWindow::signal_action_chat_submit
|
|||||||
ChatWindow::type_signal_action_chat_load_history ChatWindow::signal_action_chat_load_history() {
|
ChatWindow::type_signal_action_chat_load_history ChatWindow::signal_action_chat_load_history() {
|
||||||
return m_signal_action_chat_load_history;
|
return m_signal_action_chat_load_history;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ChatWindow::type_signal_action_channel_click ChatWindow::signal_action_channel_click() {
|
||||||
|
return m_signal_action_channel_click;
|
||||||
|
}
|
||||||
|
@@ -69,15 +69,18 @@ public:
|
|||||||
typedef sigc::signal<void, Snowflake, Snowflake> type_signal_action_message_edit;
|
typedef sigc::signal<void, Snowflake, Snowflake> type_signal_action_message_edit;
|
||||||
typedef sigc::signal<void, std::string, Snowflake> type_signal_action_chat_submit;
|
typedef sigc::signal<void, std::string, Snowflake> type_signal_action_chat_submit;
|
||||||
typedef sigc::signal<void, Snowflake> type_signal_action_chat_load_history;
|
typedef sigc::signal<void, Snowflake> type_signal_action_chat_load_history;
|
||||||
|
typedef sigc::signal<void, Snowflake> type_signal_action_channel_click;
|
||||||
|
|
||||||
type_signal_action_message_delete signal_action_message_delete();
|
type_signal_action_message_delete signal_action_message_delete();
|
||||||
type_signal_action_message_edit signal_action_message_edit();
|
type_signal_action_message_edit signal_action_message_edit();
|
||||||
type_signal_action_chat_submit signal_action_chat_submit();
|
type_signal_action_chat_submit signal_action_chat_submit();
|
||||||
type_signal_action_chat_load_history signal_action_chat_load_history();
|
type_signal_action_chat_load_history signal_action_chat_load_history();
|
||||||
|
type_signal_action_channel_click signal_action_channel_click();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
type_signal_action_message_delete m_signal_action_message_delete;
|
type_signal_action_message_delete m_signal_action_message_delete;
|
||||||
type_signal_action_message_edit m_signal_action_message_edit;
|
type_signal_action_message_edit m_signal_action_message_edit;
|
||||||
type_signal_action_chat_submit m_signal_action_chat_submit;
|
type_signal_action_chat_submit m_signal_action_chat_submit;
|
||||||
type_signal_action_chat_load_history m_signal_action_chat_load_history;
|
type_signal_action_chat_load_history m_signal_action_chat_load_history;
|
||||||
|
type_signal_action_channel_click m_signal_action_channel_click;
|
||||||
};
|
};
|
||||||
|
Reference in New Issue
Block a user