From 61c3ec1e6e8f26f6d1efc271794e791ecde8f1e2 Mon Sep 17 00:00:00 2001 From: dec05eba Date: Sun, 27 Jan 2019 16:23:26 +0100 Subject: Use rich text for messages --- css/style.css | 4 ++++ include/ChatMessage.hpp | 8 ++++++- src/ChatMessage.cpp | 63 ++++++++++++++++++++++++++++++++++++++++++++----- src/ChatWindow.cpp | 16 +++++++++---- tests/main.cpp | 36 ++-------------------------- 5 files changed, 82 insertions(+), 45 deletions(-) diff --git a/css/style.css b/css/style.css index a75dce4..e94571b 100644 --- a/css/style.css +++ b/css/style.css @@ -237,6 +237,10 @@ treeview { font-size: 13px; } +.message-message-text text { + background-color: #36393e; +} + textview { caret-color: #f7f7f7; } diff --git a/include/ChatMessage.hpp b/include/ChatMessage.hpp index a50a799..9f628c0 100644 --- a/include/ChatMessage.hpp +++ b/include/ChatMessage.hpp @@ -4,17 +4,23 @@ #include #include #include +#include namespace dchat { + void applyRichText(Gtk::TextView *textView, const Glib::ustring &text); + class ChatMessage : public Gtk::Grid { public: ChatMessage(const Glib::ustring &username, const Glib::ustring &text, uint32_t timestampSeconds); + void appendText(const Glib::ustring &text); + DynamicImage avatar; Gtk::Label username; - Gtk::Label text; uint32_t timestampSeconds; + private: + Gtk::TextView text; }; } diff --git a/src/ChatMessage.cpp b/src/ChatMessage.cpp index e1fe927..e440409 100644 --- a/src/ChatMessage.cpp +++ b/src/ChatMessage.cpp @@ -1,10 +1,46 @@ #include "../include/ChatMessage.hpp" +#include +#include namespace dchat { + static void appendRichText(Gtk::TextView *textView, Glib::RefPtr buffer, Gtk::TextIter iter, const Glib::ustring &text) + { + parseIncomingMessage(text.data(), text.bytes(), [textView, &text, &iter, &buffer](IncomingMessagePart messagePart) + { + switch(messagePart.type) + { + case IncomingMessagePart::Type::TEXT: + { + iter = buffer->insert(iter, text.data() + messagePart.textRange.start, text.data() + messagePart.textRange.end); + break; + } + case IncomingMessagePart::Type::EMOJI: + { + auto anchor = Gtk::TextChildAnchor::create(); + iter = buffer->insert_child_anchor(iter, anchor); + auto image = Gtk::manage(new DynamicImage()); + image->url = text.substr(messagePart.textRange.start, messagePart.textRange.length()); + image->set_size_request(35, 35); + textView->add_child_at_anchor(*image, anchor); + break; + } + default: + assert(false); + break; + } + }); + } + + void applyRichText(Gtk::TextView *textView, const Glib::ustring &text) + { + auto buffer = textView->get_buffer(); + buffer->set_text(""); + appendRichText(textView, buffer, buffer->begin(), text); + } + ChatMessage::ChatMessage(const Glib::ustring &_username, const Glib::ustring &_text, uint32_t _timestampSeconds) : username(_username), - text(_text), timestampSeconds(_timestampSeconds) { avatar.set_halign(Gtk::ALIGN_START); @@ -15,11 +51,12 @@ namespace dchat username.set_alignment(Gtk::ALIGN_START, Gtk::ALIGN_START); username.get_style_context()->add_class("message-message-username"); - text.set_selectable(true); - text.set_alignment(Gtk::ALIGN_START, Gtk::ALIGN_START); - text.set_line_wrap(true); - text.set_line_wrap_mode(Pango::WRAP_WORD_CHAR); - text.set_vexpand(true); + text.set_wrap_mode(Gtk::WRAP_WORD_CHAR); + //text.set_halign(Gtk::ALIGN_START); + //text.set_valign(Gtk::ALIGN_START); + text.set_hexpand(true); + text.set_editable(false); + applyRichText(&text, _text); text.get_style_context()->add_class("message-message-text"); attach(avatar, 0, 0, 1, 2); @@ -31,4 +68,18 @@ namespace dchat set_row_spacing(0); set_vexpand(false); } + + void ChatMessage::appendText(const Glib::ustring &_text) + { + auto buffer = text.get_buffer(); + // Optimized for single ascii characters, such as "\n" + if(_text.bytes() == 1) + { + buffer->insert(buffer->end(), _text); + } + else + { + appendRichText(&text, buffer, buffer->end(), _text); + } + } } \ No newline at end of file diff --git a/src/ChatWindow.cpp b/src/ChatWindow.cpp index 9fc810b..d33c824 100644 --- a/src/ChatWindow.cpp +++ b/src/ChatWindow.cpp @@ -303,8 +303,11 @@ namespace dchat { if(chatInput.get_editable()) { - currentRoom->publishMessage(chatInput.get_buffer()->get_text()); + Glib::ustring str = chatInput.get_buffer()->get_text(); chatInput.get_buffer()->set_text(""); + while(gtk_events_pending()) + gtk_main_iteration_do(FALSE); + currentRoom->publishMessage(str); } return true; } @@ -441,14 +444,19 @@ namespace dchat if(msgTimeDiff <= MERGE_MESSAGE_TIMESTAMP_DIFF_SEC) { auto message = messageById[request.prevMessage->id]; - message->text.set_text(message->text.get_text() + "\n" + request.message->text); + message->appendText("\n"); + message->appendText(request.message->text); // Since messages that are sent withing a timeframe are combined, several message ids can refer to the same message messageById[request.message->id] = message; if(!request.loadedFromCache && *request.room->id == *currentRoom->id) { - currentRoomData->messageAreaLayout->queue_draw(); + currentRoomData->messageAreaLayout->signal_draw(); scrollToBottom(); } + else + { + currentRoomData->messageAreaLayout->signal_draw(); + } return; } } @@ -471,7 +479,7 @@ namespace dchat // TODO: When we get a message in the current room we scroll to the bottom, but this should only be done if we are not manually scrolling to view old messages if(!request.loadedFromCache && *request.room->id == *currentRoom->id) { - roomData->messageAreaLayout->queue_draw(); + roomData->messageAreaLayout->signal_draw(); scrollToBottom(); } } diff --git a/tests/main.cpp b/tests/main.cpp index 0fd291c..24cc74f 100644 --- a/tests/main.cpp +++ b/tests/main.cpp @@ -1,5 +1,5 @@ #include "../include/DynamicImage.hpp" -#include +#include "../include/ChatMessage.hpp" #include #include #include @@ -20,38 +20,6 @@ static void requireEqualValues(int a, int b, const char *file, int line) } #define REQUIRE_EQUAL(a, b) do { requireEqualValues((a), (b), __FILE__, __LINE__); } while(0) -static void applyRichText(Gtk::TextView *textView, const Glib::ustring &text) -{ - auto buffer = textView->get_buffer(); - buffer->set_text(""); - Gtk::TextIter iter = buffer->get_iter_at_offset(0); - - dchat::parseIncomingMessage(text.data(), text.bytes(), [textView, &text, &iter, &buffer](dchat::IncomingMessagePart messagePart) - { - switch(messagePart.type) - { - case dchat::IncomingMessagePart::Type::TEXT: - { - iter = buffer->insert(iter, text.data() + messagePart.textRange.start, text.data() + messagePart.textRange.end); - break; - } - case dchat::IncomingMessagePart::Type::EMOJI: - { - auto anchor = Gtk::TextChildAnchor::create(); - iter = buffer->insert_child_anchor(iter, anchor); - auto image = Gtk::manage(new dchat::DynamicImage()); - image->url = text.substr(messagePart.textRange.start, messagePart.textRange.length()); - image->set_size_request(35, 35); - textView->add_child_at_anchor(*image, anchor); - break; - } - default: - assert(false); - break; - } - }); -} - static int testVisual(int argc, char **argv) { auto app = Gtk::Application::create(argc, argv, "dec05eba.dchat", Gio::APPLICATION_NON_UNIQUE); @@ -67,7 +35,7 @@ static int testVisual(int argc, char **argv) auto textView = Gtk::manage(new Gtk::TextView()); textView->set_size_request(640, 480); textView->set_wrap_mode(Gtk::WRAP_WORD_CHAR); - applyRichText(textView, "Hello world [emoji](https://discordemoji.com/assets/emoji/PeepoHide.png)[emoji](https://discordemoji.com/assets/emoji/PeepoHide.png)"); + dchat::applyRichText(textView, "Hello world [emoji](https://discordemoji.com/assets/emoji/PeepoHide.png)[emoji](https://discordemoji.com/assets/emoji/PeepoHide.png)"); grid->attach(*textView, 0, 0, 1, 1); window.show_all(); -- cgit v1.2.3