From 97564d40636aafb251644f61a0b990e392afd7a4 Mon Sep 17 00:00:00 2001 From: dec05eba Date: Thu, 22 Oct 2020 21:56:33 +0200 Subject: Matrix: add pinned messages tab --- include/Body.hpp | 1 + plugins/Matrix.hpp | 22 +++-- src/Body.cpp | 46 +++++++++- src/QuickMedia.cpp | 221 ++++++++++++++++++++++++++++++++++--------------- src/Text.cpp | 7 +- src/plugins/Matrix.cpp | 66 +++++++++++++-- 6 files changed, 278 insertions(+), 85 deletions(-) diff --git a/include/Body.hpp b/include/Body.hpp index baed595..5fde04e 100644 --- a/include/Body.hpp +++ b/include/Body.hpp @@ -34,6 +34,7 @@ namespace QuickMedia { class BodyItem { public: BodyItem(std::string _title); + BodyItem& operator=(BodyItem &other); static std::shared_ptr create(std::string title) { return std::make_shared(std::move(title)); } diff --git a/plugins/Matrix.hpp b/plugins/Matrix.hpp index f24a08f..c3c5539 100644 --- a/plugins/Matrix.hpp +++ b/plugins/Matrix.hpp @@ -78,6 +78,8 @@ namespace QuickMedia { // Ignores duplicates void append_messages(const std::vector> &new_messages); + void append_pinned_events(std::vector new_pinned_events); + std::shared_ptr get_message_by_id(const std::string &id); std::vector> get_users_excluding_me(const std::string &my_user_id); @@ -86,6 +88,7 @@ namespace QuickMedia { void release_room_lock(); const std::vector>& get_messages_thread_unsafe() const; + const std::vector& get_pinned_events_unsafe() const; std::string id; std::string name; @@ -111,6 +114,7 @@ namespace QuickMedia { std::unordered_map> user_info_by_user_id; std::vector> messages; std::unordered_map> message_by_event_id; + std::vector pinned_events; }; enum class MessageDirection { @@ -127,16 +131,23 @@ namespace QuickMedia { }; using Messages = std::vector>; - using RoomSyncMessages = std::unordered_map; + + struct SyncData { + Messages messages; + std::vector pinned_events; + }; + + using RoomSyncData = std::unordered_map; using Rooms = std::vector; bool message_contains_user_mention(const std::string &msg, const std::string &username); class Matrix { public: - PluginResult sync(RoomSyncMessages &room_messages); + PluginResult sync(RoomSyncData &room_sync_data); void get_room_join_updates(Rooms &new_rooms); - PluginResult get_all_synced_room_messages(RoomData *room, Messages &messages); + void get_all_synced_room_messages(RoomData *room, Messages &messages); + void get_all_pinned_events(RoomData *room, std::vector &events); PluginResult get_previous_room_messages(RoomData *room, Messages &messages); // |url| should only be set when uploading media. @@ -176,12 +187,13 @@ namespace QuickMedia { bool use_tor = false; private: - PluginResult sync_response_to_body_items(const rapidjson::Document &root, RoomSyncMessages &room_messages); + PluginResult sync_response_to_body_items(const rapidjson::Document &root, RoomSyncData &room_sync_data); PluginResult get_previous_room_messages(RoomData *room_data); void events_add_user_info(const rapidjson::Value &events_json, RoomData *room_data); void events_add_user_read_markers(const rapidjson::Value &events_json, RoomData *room_data); - void events_add_messages(const rapidjson::Value &events_json, RoomData *room_data, MessageDirection message_dir, RoomSyncMessages *room_messages, bool has_unread_notifications); + void events_add_messages(const rapidjson::Value &events_json, RoomData *room_data, MessageDirection message_dir, RoomSyncData *room_sync_data, bool has_unread_notifications); void events_set_room_name(const rapidjson::Value &events_json, RoomData *room_data); + void events_add_pinned_events(const rapidjson::Value &events_json, RoomData *room_data, RoomSyncData &room_sync_data); std::shared_ptr parse_message_event(const rapidjson::Value &event_item_json, RoomData *room_data); PluginResult upload_file(RoomData *room, const std::string &filepath, UploadInfo &file_info, UploadInfo &thumbnail_info, std::string &err_msg); diff --git a/src/Body.cpp b/src/Body.cpp index f1c101f..f9b3edc 100644 --- a/src/Body.cpp +++ b/src/Body.cpp @@ -35,6 +35,50 @@ namespace QuickMedia { set_title(std::move(_title)); } + BodyItem& BodyItem::operator=(BodyItem &other) { + url = other.url; + thumbnail_url = other.thumbnail_url; + attached_content_url = other.attached_content_url; + visible = other.visible; + dirty = other.dirty; + dirty_description = other.dirty_description; + dirty_author = other.dirty_author; + dirty_timestamp = other.dirty_timestamp; + thumbnail_is_local = other.thumbnail_is_local; + if(other.title_text) + title_text = std::make_unique(*other.title_text); + else + title_text = nullptr; + if(other.description_text) + description_text = std::make_unique(*other.description_text); + else + description_text = nullptr; + if(other.author_text) + author_text = std::make_unique(*other.author_text); + else + author_text = nullptr; + if(other.timestamp_text) + timestamp_text = std::make_unique(*other.timestamp_text); + else + timestamp_text = nullptr; + replies = other.replies; + post_number = other.post_number; + userdata = other.userdata; + last_drawn_time = other.last_drawn_time; + embedded_item_status = other.embedded_item_status; + embedded_item = other.embedded_item; + thumbnail_mask_type = other.thumbnail_mask_type; + thumbnail_size = other.thumbnail_size; + title = other.title; + description = other.description; + author = other.author; + timestamp = other.timestamp; + title_color = other.title_color; + author_color = other.author_color; + description_color = other.description_color; + return *this; + } + Body::Body(Program *program, sf::Font *font, sf::Font *bold_font, sf::Font *cjk_font, sf::Texture &loading_icon_texture) : font(font), bold_font(bold_font), @@ -362,7 +406,7 @@ namespace QuickMedia { float item_height = get_item_height(item.get()); prev_pos.y -= (item_height + spacing_y); - if(prev_pos.y + item_height + spacing_y < start_y) + if(prev_pos.y + item_height + spacing_y <= start_y) break; // This is needed here rather than above the loop, since update_dirty_text cant be called inside scissor because it corrupts the text for some reason diff --git a/src/QuickMedia.cpp b/src/QuickMedia.cpp index d0a93fe..5c1bdfa 100644 --- a/src/QuickMedia.cpp +++ b/src/QuickMedia.cpp @@ -2856,13 +2856,7 @@ namespace QuickMedia { } } - enum class ChatTabType { - MESSAGES, - ROOMS - }; - struct ChatTab { - ChatTabType type; std::unique_ptr body; std::future future; sf::Text text; @@ -2934,16 +2928,35 @@ namespace QuickMedia { return result_items; } + enum class PinnedEventStatus { + NONE, + LOADING, + FINISHED_LOADING, + FAILED_TO_LOAD + }; + + struct PinnedEventData { + std::string event_id; + PinnedEventStatus status = PinnedEventStatus::NONE; + }; + void Program::chat_page() { assert(strcmp(plugin_name, "matrix") == 0); auto video_page = std::make_unique(this); std::vector tabs; - int selected_tab = 0; + + ChatTab pinned_tab; + pinned_tab.body = std::make_unique(this, font.get(), bold_font.get(), cjk_font.get(), loading_icon); + pinned_tab.body->draw_thumbnails = true; + pinned_tab.body->thumbnail_max_size = CHAT_MESSAGE_THUMBNAIL_MAX_SIZE; + pinned_tab.body->thumbnail_mask_shader = &circle_mask_shader; + //pinned_tab.body->line_separator_color = sf::Color::Transparent; + pinned_tab.text = sf::Text("Pinned", *font, tab_text_size); + tabs.push_back(std::move(pinned_tab)); ChatTab messages_tab; - messages_tab.type = ChatTabType::MESSAGES; messages_tab.body = std::make_unique(this, font.get(), bold_font.get(), cjk_font.get(), loading_icon); messages_tab.body->draw_thumbnails = true; messages_tab.body->thumbnail_max_size = CHAT_MESSAGE_THUMBNAIL_MAX_SIZE; @@ -2953,7 +2966,6 @@ namespace QuickMedia { tabs.push_back(std::move(messages_tab)); ChatTab rooms_tab; - rooms_tab.type = ChatTabType::ROOMS; rooms_tab.body = std::make_unique(this, font.get(), bold_font.get(), cjk_font.get(), loading_icon); rooms_tab.body->draw_thumbnails = true; //rooms_tab.body->line_separator_color = sf::Color::Transparent; @@ -2961,8 +2973,11 @@ namespace QuickMedia { rooms_tab.text = sf::Text("Rooms", *font, tab_text_size); tabs.push_back(std::move(rooms_tab)); - const int MESSAGES_TAB_INDEX = 0; - const int ROOMS_TAB_INDEX = 1; + const int PINNED_TAB_INDEX = 0; + const int MESSAGES_TAB_INDEX = 1; + const int ROOMS_TAB_INDEX = 2; + + int selected_tab = MESSAGES_TAB_INDEX; // This is needed to get initial data, with joined rooms etc. TODO: Remove this once its cached // and allow asynchronous update of rooms @@ -2992,10 +3007,10 @@ namespace QuickMedia { auto process_new_room_messages = [this, &selected_tab, ¤t_room, &is_window_focused, &tabs, &find_top_body_position_for_unread_room, &find_top_body_position_for_mentioned_room] - (RoomSyncMessages &room_sync_messages, bool is_first_sync) mutable + (RoomSyncData &room_sync_data, bool is_first_sync) mutable { - for(auto &[room, messages] : room_sync_messages) { - for(auto &message : messages) { + for(auto &[room, sync_data] : room_sync_data) { + for(auto &message : sync_data.messages) { if(message->mentions_me) { room->has_unread_mention = true; // TODO: What if the message or username begins with "-"? also make the notification image be the avatar of the user @@ -3005,8 +3020,8 @@ namespace QuickMedia { } } - for(auto &[room, messages] : room_sync_messages) { - if(messages.empty()) + for(auto &[room, sync_data] : room_sync_data) { + if(sync_data.messages.empty()) continue; std::shared_ptr me = matrix->get_me(room); @@ -3020,7 +3035,7 @@ namespace QuickMedia { // TODO: this wont always work because we dont display all types of messages from server, such as "joined", "left", "kicked", "banned", "changed avatar", "changed display name", etc. // TODO: Binary search? Message *last_unread_message = nullptr; - for(auto &message : messages) { + for(auto &message : sync_data.messages) { if(message->related_event_type != RelatedEventType::EDIT && message->related_event_type != RelatedEventType::REDACTION && message->timestamp > read_marker_message_timestamp) last_unread_message = message.get(); } @@ -3060,7 +3075,7 @@ namespace QuickMedia { } } else if(is_first_sync) { Message *last_unread_message = nullptr; - for(auto it = messages.rbegin(), end = messages.rend(); it != end; ++it) { + for(auto it = sync_data.messages.rbegin(), end = sync_data.messages.rend(); it != end; ++it) { if((*it)->related_event_type != RelatedEventType::EDIT && (*it)->related_event_type != RelatedEventType::REDACTION) { last_unread_message = (*it).get(); break; @@ -3170,6 +3185,19 @@ namespace QuickMedia { } }; + auto process_new_pinned_events = [&tabs](const std::vector &pinned_events) { + // TODO: Add message to rooms messages when there are new pinned events + for(const std::string &event : pinned_events) { + auto body = BodyItem::create(""); + body->set_description("Loading message..."); + PinnedEventData *event_data = new PinnedEventData(); + event_data->event_id = event; + event_data->status = PinnedEventStatus::NONE; + body->userdata = event_data; + tabs[PINNED_TAB_INDEX].body->items.push_back(std::move(body)); + } + }; + SearchBar room_search_bar(*font, &plugin_logo, "Search..."); room_search_bar.autocomplete_search_delay = SEARCH_DELAY_FILTER; room_search_bar.onTextUpdateCallback = [&tabs](const std::string &text) { @@ -3179,7 +3207,7 @@ namespace QuickMedia { room_search_bar.onTextSubmitCallback = [this, &tabs, &selected_tab, ¤t_room, &room_name_text, - &modify_related_messages_in_current_room, &room_avatar_thumbnail_data, + &modify_related_messages_in_current_room, &process_new_pinned_events, &room_avatar_thumbnail_data, &read_marker_timeout_ms, &redraw, &room_search_bar] (const std::string&) { @@ -3194,15 +3222,20 @@ namespace QuickMedia { selected_tab = MESSAGES_TAB_INDEX; tabs[MESSAGES_TAB_INDEX].body->clear_items(); - Messages new_messages; - if(matrix->get_all_synced_room_messages(current_room, new_messages) == PluginResult::OK) { - tabs[MESSAGES_TAB_INDEX].body->insert_items_by_timestamps(messages_to_body_items(new_messages, matrix->get_me(current_room).get())); - modify_related_messages_in_current_room(new_messages); - tabs[MESSAGES_TAB_INDEX].body->select_last_item(); - } else { - std::string err_msg = "Failed to get messages in room: " + current_room->id; - show_notification("QuickMedia", err_msg, Urgency::CRITICAL); + for(auto &body_item : tabs[PINNED_TAB_INDEX].body->items) { + delete((PinnedEventData*)body_item->userdata); } + tabs[PINNED_TAB_INDEX].body->clear_items(); + + Messages all_messages; + matrix->get_all_synced_room_messages(current_room, all_messages); + tabs[MESSAGES_TAB_INDEX].body->insert_items_by_timestamps(messages_to_body_items(all_messages, matrix->get_me(current_room).get())); + modify_related_messages_in_current_room(all_messages); + tabs[MESSAGES_TAB_INDEX].body->select_last_item(); + + std::vector pinned_events; + matrix->get_all_pinned_events(current_room, pinned_events); + process_new_pinned_events(pinned_events); room_name_text.setString(static_cast(current_room->userdata)->get_title()); room_avatar_thumbnail_data = std::make_shared(); @@ -3217,11 +3250,11 @@ namespace QuickMedia { chat_input.draw_background = false; chat_input.set_editable(false); - chat_input.on_submit_callback = [this, &chat_input, &tabs, &selected_tab, ¤t_room, &new_page, &chat_state, ¤tly_operating_on_item](const std::string &text) mutable { + chat_input.on_submit_callback = [this, &chat_input, &selected_tab, ¤t_room, &new_page, &chat_state, ¤tly_operating_on_item](const std::string &text) mutable { if(!current_room) return false; - if(tabs[selected_tab].type == ChatTabType::MESSAGES) { + if(selected_tab == MESSAGES_TAB_INDEX) { if(text.empty()) return false; @@ -3282,7 +3315,7 @@ namespace QuickMedia { struct SyncFutureResult { Rooms rooms; - RoomSyncMessages room_sync_messages; + RoomSyncData room_sync_data; }; std::future sync_future; @@ -3294,18 +3327,52 @@ namespace QuickMedia { bool fetching_previous_messages_running = false; RoomData *previous_messages_future_room = nullptr; - std::future> fetch_reply_message_future; - bool fetching_reply_message_running = false; - RoomData *fetch_reply_future_room = nullptr; - BodyItem *fetch_reply_body_item = nullptr; + //const int num_fetch_message_threads = 4; + std::future> fetch_message_future; + bool fetching_message_running = false; + RoomData *fetch_future_room = nullptr; + BodyItem *fetch_body_item = nullptr; + int fetch_message_tab = -1; // TODO: How about instead fetching all messages we have, not only the visible ones? also fetch with multiple threads. // TODO: Cancel when going to another room? - tabs[MESSAGES_TAB_INDEX].body->body_item_render_callback = [this, ¤t_room, &fetch_reply_message_future, &tabs, &find_body_item_by_event_id, &fetching_reply_message_running, &fetch_reply_future_room, &fetch_reply_body_item](BodyItem *body_item) { - if(fetching_reply_message_running || !current_room) + tabs[PINNED_TAB_INDEX].body->body_item_render_callback = [this, ¤t_room, &fetch_message_future, &tabs, &find_body_item_by_event_id, &fetching_message_running, &fetch_future_room, &fetch_body_item, &fetch_message_tab](BodyItem *body_item) { + if(fetching_message_running || !current_room) + return; + + PinnedEventData *event_data = static_cast(body_item->userdata); + if(!event_data || event_data->status != PinnedEventStatus::NONE) + return; + + // Check if we already have the referenced message as a body item in the messages list, so we dont create a new one + auto related_body_item = find_body_item_by_event_id(tabs[MESSAGES_TAB_INDEX].body->items.data(), tabs[MESSAGES_TAB_INDEX].body->items.size(), event_data->event_id); + if(related_body_item) { + *body_item = *related_body_item; + event_data->status = PinnedEventStatus::FINISHED_LOADING; + body_item->userdata = event_data; + return; + } + + fetching_message_running = true; + std::string message_event_id = event_data->event_id; + fetch_future_room = current_room; + fetch_body_item = body_item; + body_item->embedded_item_status = EmbeddedItemStatus::LOADING; + fetch_message_tab = PINNED_TAB_INDEX; + // TODO: Check if the message is already cached before calling async? is this needed? is async creation expensive? + fetch_message_future = std::async(std::launch::async, [this, &fetch_future_room, message_event_id]() { + return matrix->get_message_by_id(fetch_future_room, message_event_id); + }); + }; + + // TODO: How about instead fetching all messages we have, not only the visible ones? also fetch with multiple threads. + // TODO: Cancel when going to another room? + tabs[MESSAGES_TAB_INDEX].body->body_item_render_callback = [this, ¤t_room, &fetch_message_future, &tabs, &find_body_item_by_event_id, &fetching_message_running, &fetch_future_room, &fetch_body_item, &fetch_message_tab](BodyItem *body_item) { + if(fetching_message_running || !current_room) return; Message *message = static_cast(body_item->userdata); + assert(message); if(message->related_event_id.empty() || body_item->embedded_item_status != EmbeddedItemStatus::NONE) return; @@ -3317,14 +3384,15 @@ namespace QuickMedia { return; } - fetching_reply_message_running = true; + fetching_message_running = true; std::string message_event_id = message->related_event_id; - fetch_reply_future_room = current_room; - fetch_reply_body_item = body_item; + fetch_future_room = current_room; + fetch_body_item = body_item; body_item->embedded_item_status = EmbeddedItemStatus::LOADING; + fetch_message_tab = MESSAGES_TAB_INDEX; // TODO: Check if the message is already cached before calling async? is this needed? is async creation expensive? - fetch_reply_message_future = std::async(std::launch::async, [this, &fetch_reply_future_room, message_event_id]() { - return matrix->get_message_by_id(fetch_reply_future_room, message_event_id); + fetch_message_future = std::async(std::launch::async, [this, &fetch_future_room, message_event_id]() { + return matrix->get_message_by_id(fetch_future_room, message_event_id); }); }; @@ -3403,10 +3471,10 @@ namespace QuickMedia { } }; - auto add_new_messages_to_current_room = [this, &tabs, &selected_tab, ¤t_room, &is_window_focused](Messages &messages) { + auto add_new_messages_to_current_room = [this, &tabs, &selected_tab, ¤t_room](Messages &messages) { int num_items = tabs[MESSAGES_TAB_INDEX].body->items.size(); bool scroll_to_end = num_items == 0; - if(tabs[MESSAGES_TAB_INDEX].body->is_selected_item_last_visible_item() && selected_tab == MESSAGES_TAB_INDEX && is_window_focused) + if(tabs[MESSAGES_TAB_INDEX].body->is_selected_item_last_visible_item() && selected_tab == MESSAGES_TAB_INDEX) scroll_to_end = true; BodyItem *selected_item = tabs[MESSAGES_TAB_INDEX].body->get_selected(); @@ -3456,7 +3524,7 @@ namespace QuickMedia { while (window.pollEvent(event)) { base_event_handler(event, PageType::EXIT, tabs[selected_tab].body.get(), nullptr, false, false); - if(tabs[selected_tab].type == ChatTabType::ROOMS) { + if(selected_tab == ROOMS_TAB_INDEX) { if(event.type == sf::Event::TextEntered) room_search_bar.onTextEntered(event.text.unicode); room_search_bar.on_event(event); @@ -3487,7 +3555,7 @@ namespace QuickMedia { hit_top = false; break; } - if(hit_top && !fetching_previous_messages_running && tabs[selected_tab].type == ChatTabType::MESSAGES && current_room) { + if(hit_top && !fetching_previous_messages_running && selected_tab == MESSAGES_TAB_INDEX && current_room) { gradient_inc = 0; fetching_previous_messages_running = true; previous_messages_future_room = current_room; @@ -3526,7 +3594,7 @@ namespace QuickMedia { } } - if(tabs[selected_tab].type == ChatTabType::MESSAGES && event.key.code == sf::Keyboard::Enter) { + if(selected_tab == MESSAGES_TAB_INDEX && event.key.code == sf::Keyboard::Enter) { BodyItem *selected = tabs[selected_tab].body->get_selected(); if(selected) { MessageType message_type = static_cast(selected->userdata)->type; @@ -3603,7 +3671,7 @@ namespace QuickMedia { continue; launch_url(selected_item->get_title()); } - } else if(event.type == sf::Event::KeyReleased && chat_state == ChatState::NAVIGATING && tabs[selected_tab].type == ChatTabType::MESSAGES && current_room) { + } else if(event.type == sf::Event::KeyReleased && chat_state == ChatState::NAVIGATING && selected_tab == MESSAGES_TAB_INDEX && current_room) { if(event.key.code == sf::Keyboard::U) { new_page = PageType::FILE_MANAGER; chat_input.set_editable(false); @@ -3676,7 +3744,7 @@ namespace QuickMedia { } } - if((chat_state == ChatState::TYPING_MESSAGE || chat_state == ChatState::REPLYING || chat_state == ChatState::EDITING) && tabs[selected_tab].type == ChatTabType::MESSAGES) { + if((chat_state == ChatState::TYPING_MESSAGE || chat_state == ChatState::REPLYING || chat_state == ChatState::EDITING) && selected_tab == MESSAGES_TAB_INDEX) { if(event.type == sf::Event::TextEntered) { //chat_input.onTextEntered(event.text.unicode); // TODO: Also show typing event when ctrl+v pasting? @@ -3825,6 +3893,8 @@ namespace QuickMedia { room_name_padding_y = room_search_bar.getBottomWithoutShadow(); tab_vertical_offset = 0.0f; padding_bottom = 10.0f; + } else if(selected_tab == PINNED_TAB_INDEX) { + padding_bottom = 10.0f; } tab_shade_height = tab_spacer_height + std::floor(tab_vertical_offset) + tab_height + room_name_padding_y + padding_bottom; @@ -3861,7 +3931,7 @@ namespace QuickMedia { sync_timer.restart(); sync_future = std::async(std::launch::async, [this]() { SyncFutureResult result; - if(matrix->sync(result.room_sync_messages) == PluginResult::OK) { + if(matrix->sync(result.room_sync_data) == PluginResult::OK) { fprintf(stderr, "Synced matrix\n"); matrix->get_room_join_updates(result.rooms); } else { @@ -3877,13 +3947,14 @@ namespace QuickMedia { add_new_rooms(sync_result.rooms); - auto room_messages_it = sync_result.room_sync_messages.find(current_room); - if(room_messages_it != sync_result.room_sync_messages.end()) { - add_new_messages_to_current_room(room_messages_it->second); - modify_related_messages_in_current_room(room_messages_it->second); + auto room_messages_it = sync_result.room_sync_data.find(current_room); + if(room_messages_it != sync_result.room_sync_data.end()) { + add_new_messages_to_current_room(room_messages_it->second.messages); + modify_related_messages_in_current_room(room_messages_it->second.messages); + process_new_pinned_events(room_messages_it->second.pinned_events); } - process_new_room_messages(sync_result.room_sync_messages, !synced); + process_new_room_messages(sync_result.room_sync_data, !synced); sync_running = false; synced = true; } @@ -3915,19 +3986,31 @@ namespace QuickMedia { fetching_previous_messages_running = false; } - if(fetching_reply_message_running && fetch_reply_message_future.valid() && fetch_reply_message_future.wait_for(std::chrono::seconds(0)) == std::future_status::ready) { - std::shared_ptr replied_to_message = fetch_reply_message_future.get(); - fprintf(stderr, "Finished fetching reply to message: %s\n", replied_to_message ? replied_to_message->event_id.c_str() : "(null)"); + if(fetching_message_running && fetch_message_future.valid() && fetch_message_future.wait_for(std::chrono::seconds(0)) == std::future_status::ready) { + std::shared_ptr message = fetch_message_future.get(); + fprintf(stderr, "Finished fetching message: %s\n", message ? message->event_id.c_str() : "(null)"); // Ignore finished fetch of messages if it happened in another room. When we navigate back to the room we will get the messages again - if(fetch_reply_future_room == current_room) { - if(replied_to_message) { - fetch_reply_body_item->embedded_item = message_to_body_item(replied_to_message.get(), matrix->get_me(current_room).get()); - fetch_reply_body_item->embedded_item_status = EmbeddedItemStatus::FINISHED_LOADING; - } else { - fetch_reply_body_item->embedded_item_status = EmbeddedItemStatus::FAILED_TO_LOAD; + if(fetch_future_room == current_room) { + if(fetch_message_tab == PINNED_TAB_INDEX) { + PinnedEventData *event_data = static_cast(fetch_body_item->userdata); + if(message) { + *fetch_body_item = *message_to_body_item(message.get(), matrix->get_me(current_room).get()); + event_data->status = PinnedEventStatus::FINISHED_LOADING; + fetch_body_item->userdata = event_data; + } else { + event_data->status = PinnedEventStatus::FAILED_TO_LOAD; + } + } else if(fetch_message_tab == MESSAGES_TAB_INDEX) { + if(message) { + fetch_body_item->embedded_item = message_to_body_item(message.get(), matrix->get_me(current_room).get()); + fetch_body_item->embedded_item_status = EmbeddedItemStatus::FINISHED_LOADING; + } else { + fetch_body_item->embedded_item_status = EmbeddedItemStatus::FAILED_TO_LOAD; + } } } - fetching_reply_message_running = false; + fetching_message_running = false; + fetch_message_tab = -1; } //chat_input.update(); @@ -3946,7 +4029,7 @@ namespace QuickMedia { tab_shade.setSize(sf::Vector2f(window_size.x, tab_shade_height)); window.draw(tab_shade); - if(tabs[selected_tab].type == ChatTabType::MESSAGES) { + if(selected_tab == MESSAGES_TAB_INDEX) { float room_name_text_offset_x = 0.0f; if(room_avatar_sprite.getTexture() && room_avatar_sprite.getTexture()->getNativeHandle() != 0) { auto room_avatar_texture_size = room_avatar_sprite.getTexture()->getSize(); @@ -3958,7 +4041,7 @@ namespace QuickMedia { } room_name_text.setPosition(body_pos.x + room_name_text_offset_x, room_name_text_padding_y + 4.0f); window.draw(room_name_text); - } else if(tabs[selected_tab].type == ChatTabType::ROOMS) { + } else if(selected_tab == ROOMS_TAB_INDEX) { room_search_bar.draw(window, false); } @@ -3987,7 +4070,7 @@ namespace QuickMedia { } // TODO: Have one for each room. Also add bottom one? for fetching new messages (currently not implemented, is it needed?) - if(fetching_previous_messages_running && tabs[selected_tab].type == ChatTabType::MESSAGES) { + if(fetching_previous_messages_running && selected_tab == MESSAGES_TAB_INDEX) { double progress = 0.5 + std::sin(std::fmod(gradient_inc, 360.0) * 0.017453292519943295 - 1.5707963267948966*0.5) * 0.5; gradient_inc += (frame_time_ms * 0.5); sf::Color top_color = interpolate_colors(back_color, sf::Color(175, 180, 188), progress); @@ -4024,7 +4107,7 @@ namespace QuickMedia { tabs[MESSAGES_TAB_INDEX].body->draw_item(window, currently_operating_on_item.get(), body_item_pos, body_item_size); } - if(tabs[selected_tab].type == ChatTabType::MESSAGES && current_room && current_room->userdata && !current_room->last_message_read) { + if(selected_tab == MESSAGES_TAB_INDEX && current_room && current_room->userdata && !current_room->last_message_read) { if(tabs[selected_tab].body->is_last_item_fully_visible()) { BodyItem *current_room_body_item = static_cast(current_room->userdata); std::string room_desc = current_room_body_item->get_description(); @@ -4050,7 +4133,7 @@ namespace QuickMedia { window.draw(loading_text); } - if(tabs[selected_tab].type == ChatTabType::MESSAGES && current_room) { + if(selected_tab == MESSAGES_TAB_INDEX && current_room) { BodyItem *last_visible_item = tabs[selected_tab].body->get_last_fully_visible_item(); if(is_window_focused && chat_state != ChatState::URL_SELECTION && current_room && last_visible_item && !setting_read_marker && read_marker_timer.getElapsedTime().asMilliseconds() >= read_marker_timeout_ms) { Message *message = (Message*)last_visible_item->userdata; diff --git a/src/Text.cpp b/src/Text.cpp index 44730c3..51ff054 100644 --- a/src/Text.cpp +++ b/src/Text.cpp @@ -161,6 +161,10 @@ namespace QuickMedia { this->editable = editable; dirtyCaret = true; + if(editable) + dirty = true; + else + vertices_linear.clear(); } } @@ -466,7 +470,8 @@ namespace QuickMedia } boundingBox.height = num_lines * line_height; - // TODO: Clear vertices_linear when not in edit mode, but take into consideration switching between edit/not-edit mode + if(!editable) + vertices_linear.clear(); } void Text::updateCaret() diff --git a/src/plugins/Matrix.cpp b/src/plugins/Matrix.cpp index b6f6f76..9a2f70b 100644 --- a/src/plugins/Matrix.cpp +++ b/src/plugins/Matrix.cpp @@ -75,6 +75,11 @@ namespace QuickMedia { } } + void RoomData::append_pinned_events(std::vector new_pinned_events) { + std::lock_guard lock(room_mutex); + pinned_events.insert(pinned_events.end(), new_pinned_events.begin(), new_pinned_events.end()); + } + std::shared_ptr RoomData::get_message_by_id(const std::string &id) { std::lock_guard lock(room_mutex); auto message_it = message_by_event_id.find(id); @@ -106,7 +111,11 @@ namespace QuickMedia { return messages; } - PluginResult Matrix::sync(RoomSyncMessages &room_messages) { + const std::vector& RoomData::get_pinned_events_unsafe() const { + return pinned_events; + } + + PluginResult Matrix::sync(RoomSyncData &room_sync_data) { std::vector additional_args = { { "-H", "Authorization: Bearer " + access_token }, { "-m", "35" } @@ -122,7 +131,7 @@ namespace QuickMedia { DownloadResult download_result = download_json(json_root, url, std::move(additional_args), true); if(download_result != DownloadResult::OK) return download_result_to_plugin_result(download_result); - PluginResult result = sync_response_to_body_items(json_root, room_messages); + PluginResult result = sync_response_to_body_items(json_root, room_sync_data); if(result != PluginResult::OK) return result; @@ -148,11 +157,16 @@ namespace QuickMedia { room_list_read_index += num_new_rooms; } - PluginResult Matrix::get_all_synced_room_messages(RoomData *room, Messages &messages) { + void Matrix::get_all_synced_room_messages(RoomData *room, Messages &messages) { room->acquire_room_lock(); messages = room->get_messages_thread_unsafe(); room->release_room_lock(); - return PluginResult::OK; + } + + void Matrix::get_all_pinned_events(RoomData *room, std::vector &events) { + room->acquire_room_lock(); + events = room->get_pinned_events_unsafe(); + room->release_room_lock(); } PluginResult Matrix::get_previous_room_messages(RoomData *room, Messages &messages) { @@ -171,7 +185,7 @@ namespace QuickMedia { return PluginResult::OK; } - PluginResult Matrix::sync_response_to_body_items(const rapidjson::Document &root, RoomSyncMessages &room_messages) { + PluginResult Matrix::sync_response_to_body_items(const rapidjson::Document &root, RoomSyncData &room_sync_data) { if(!root.IsObject()) return PluginResult::ERR; @@ -206,6 +220,7 @@ namespace QuickMedia { const rapidjson::Value &events_json = GetMember(state_json, "events"); events_add_user_info(events_json, room); events_set_room_name(events_json, room); + events_add_pinned_events(events_json, room, room_sync_data); } const rapidjson::Value &ephemeral_json = GetMember(it.value, "ephemeral"); @@ -236,7 +251,7 @@ namespace QuickMedia { const rapidjson::Value &events_json = GetMember(ephemeral_json, "events"); events_add_user_read_markers(events_json, room); } - events_add_messages(events_json, room, MessageDirection::AFTER, &room_messages, has_unread_notifications); + events_add_messages(events_json, room, MessageDirection::AFTER, &room_sync_data, has_unread_notifications); } else { if(ephemeral_json.IsObject()) { const rapidjson::Value &events_json = GetMember(ephemeral_json, "events"); @@ -454,7 +469,7 @@ namespace QuickMedia { return false; } - void Matrix::events_add_messages(const rapidjson::Value &events_json, RoomData *room_data, MessageDirection message_dir, RoomSyncMessages *room_messages, bool has_unread_notifications) { + void Matrix::events_add_messages(const rapidjson::Value &events_json, RoomData *room_data, MessageDirection message_dir, RoomSyncData *room_sync_data, bool has_unread_notifications) { if(!events_json.IsArray()) return; @@ -471,8 +486,8 @@ namespace QuickMedia { return; // TODO: Add directly to this instead when set? otherwise add to new_messages - if(room_messages) - (*room_messages)[room_data] = new_messages; + if(room_sync_data) + (*room_sync_data)[room_data].messages = new_messages; // TODO: Loop and std::move instead? doesn't insert create copies? if(message_dir == MessageDirection::BEFORE) { @@ -753,6 +768,39 @@ namespace QuickMedia { } } + void Matrix::events_add_pinned_events(const rapidjson::Value &events_json, RoomData *room_data, RoomSyncData &room_sync_data) { + if(!events_json.IsArray()) + return; + + std::vector pinned_events; + for(const rapidjson::Value &event_item_json : events_json.GetArray()) { + if(!event_item_json.IsObject()) + continue; + + const rapidjson::Value &type_json = GetMember(event_item_json, "type"); + if(!type_json.IsString() || strcmp(type_json.GetString(), "m.room.pinned_events") != 0) + continue; + + const rapidjson::Value &content_json = GetMember(event_item_json, "content"); + if(!content_json.IsObject()) + continue; + + const rapidjson::Value &pinned_json = GetMember(content_json, "pinned"); + if(!pinned_json.IsArray()) + continue; + + for(const rapidjson::Value &pinned_item_json : pinned_json.GetArray()) { + if(!pinned_item_json.IsString()) + continue; + + pinned_events.push_back(std::string(pinned_item_json.GetString(), pinned_item_json.GetStringLength())); + } + } + + room_sync_data[room_data].pinned_events = pinned_events; + room_data->append_pinned_events(std::move(pinned_events)); + } + PluginResult Matrix::get_previous_room_messages(RoomData *room_data) { std::string from = room_data->prev_batch; if(from.empty()) { -- cgit v1.2.3