From 620123fbd6c18dc48a25cc735565f6d8d85f8639 Mon Sep 17 00:00:00 2001 From: dec05eba Date: Mon, 26 Oct 2020 09:48:25 +0100 Subject: Matrix: add room tags Fix pinned events that are added after starting QuickMedia (before this change it adds all elements again to the list). Add /me command. Other fixes... --- src/QuickMedia.cpp | 838 ++++++++++++++++++++++------------------------------- 1 file changed, 345 insertions(+), 493 deletions(-) (limited to 'src/QuickMedia.cpp') diff --git a/src/QuickMedia.cpp b/src/QuickMedia.cpp index 054b3ed..c4532cd 100644 --- a/src/QuickMedia.cpp +++ b/src/QuickMedia.cpp @@ -392,6 +392,9 @@ namespace QuickMedia { } Program::~Program() { + images_to_upscale_queue.close(); + if(image_upscale_thead.joinable()) + image_upscale_thead.join(); if(matrix) delete matrix; if(disp) @@ -509,14 +512,13 @@ namespace QuickMedia { } image_upscale_thead = std::thread([this]{ - CopyOp copy_op; + std::optional copy_op_opt; while(true) { - { - std::unique_lock lock(image_upscale_mutex); - while(images_to_upscale.empty()) image_upscale_cv.wait(lock); - copy_op = images_to_upscale.front(); - images_to_upscale.pop_front(); - } + copy_op_opt = images_to_upscale_queue.pop_wait(); + if(!copy_op_opt) + break; + + CopyOp ©_op = copy_op_opt.value(); Path tmp_file = copy_op.source; tmp_file.append(".tmp.png"); @@ -538,7 +540,6 @@ namespace QuickMedia { file_overwrite(copy_op.destination.data.c_str(), "1"); } }); - image_upscale_thead.detach(); } if(strcmp(plugin_name, "file-manager") != 0 && start_dir) { @@ -615,14 +616,16 @@ namespace QuickMedia { } if(!tabs.empty()) { - page_loop(std::move(tabs)); + page_loop(tabs); return exit_code; } if(matrix) { matrix->use_tor = use_tor; { - auto window_size = window.getSize(); + auto window_size_u = window.getSize(); + window_size.x = window_size_u.x; + window_size.y = window_size_u.y; sf::Text loading_text("Loading...", *font.get(), 24); loading_text.setPosition(window_size.x * 0.5f - loading_text.getLocalBounds().width * 0.5f, window_size.y * 0.5f - loading_text.getLocalBounds().height * 0.5f); window.clear(back_color); @@ -634,21 +637,56 @@ namespace QuickMedia { } else { fprintf(stderr, "Failed to load session cache, redirecting to login page\n"); current_page = PageType::CHAT_LOGIN; + chat_login_page(); } - while(window.isOpen()) { - switch(current_page) { - case PageType::CHAT_LOGIN: - chat_login_page(); - break; - case PageType::CHAT: - chat_page(); - break; - default: + if(!window.isOpen()) + return exit_code; + + auto rooms_body = create_body(); + rooms_body->thumbnail_mask_shader = &circle_mask_shader; + auto matrix_rooms_page = std::make_unique(this, rooms_body.get(), "All rooms"); + + auto rooms_tags_body = create_body(); + rooms_tags_body->thumbnail_mask_shader = &circle_mask_shader; + auto matrix_rooms_tag_page = std::make_unique(this, rooms_tags_body.get()); + + MatrixQuickMedia matrix_handler(this, matrix, matrix_rooms_page.get(), matrix_rooms_tag_page.get()); + matrix->start_sync(&matrix_handler); + + tabs.push_back(Tab{std::move(rooms_body), std::move(matrix_rooms_page), create_search_bar("Search...", SEARCH_DELAY_FILTER)}); + tabs.push_back(Tab{std::move(rooms_tags_body), std::move(matrix_rooms_tag_page), create_search_bar("Search...", SEARCH_DELAY_FILTER)}); + + sf::Sprite load_sprite(loading_icon); + sf::Vector2u loading_icon_size = loading_icon.getSize(); + load_sprite.setOrigin(loading_icon_size.x * 0.5f, loading_icon_size.y * 0.5f); + + sf::Clock timer; + sf::Event event; + while(window.isOpen() && !matrix->is_initial_sync_finished()) { + while(window.pollEvent(event)) { + if(event.type == sf::Event::Closed) window.close(); - break; + else if(event.type == sf::Event::Resized) { + window_size.x = event.size.width; + window_size.y = event.size.height; + sf::FloatRect visible_area(0, 0, window_size.x, window_size.y); + window.setView(sf::View(visible_area)); + } } + window.clear(back_color); + load_sprite.setPosition(window_size.x * 0.5f - loading_icon_size.x * 0.5f, window_size.y * 0.5f - loading_icon_size.y * 0.5f); + load_sprite.setRotation(timer.getElapsedTime().asSeconds() * 400.0); + window.draw(load_sprite); + window.display(); + } + + while(window.isOpen()) { + page_loop(tabs); } + + exit(exit_code); // Exit immediately without waiting for anything to finish + //matrix->stop_sync(); } return exit_code; @@ -896,7 +934,9 @@ namespace QuickMedia { } std::unique_ptr Program::create_body() { - return std::make_unique(this, font.get(), bold_font.get(), cjk_font.get(), loading_icon); + auto body = std::make_unique(this, font.get(), bold_font.get(), cjk_font.get(), loading_icon); + body->thumbnail_mask_shader = &circle_mask_shader; + return body; } std::unique_ptr Program::create_search_bar(const std::string &placeholder, int search_delay) { @@ -926,7 +966,15 @@ namespace QuickMedia { selected_files.push_back(filepath); } - void Program::page_loop(std::vector tabs) { + bool Program::is_window_focused() { + return window.hasFocus(); + } + + RoomData* Program::get_current_chat_room() { + return current_chat_room; + } + + void Program::page_loop(std::vector &tabs) { if(tabs.empty()) { show_notification("QuickMedia", "No tabs provided!", Urgency::CRITICAL); return; @@ -1047,24 +1095,27 @@ namespace QuickMedia { } } window.setKeyRepeatEnabled(true); - redraw = true; } else if(new_tabs.size() == 1 && new_tabs[0].page->get_type() == PageTypez::IMAGE_BOARD_THREAD) { current_page = PageType::IMAGE_BOARD_THREAD; image_board_thread_page(static_cast(new_tabs[0].page.get()), new_tabs[0].body.get()); - redraw = true; } else if(new_tabs.size() == 1 && new_tabs[0].page->get_type() == PageTypez::VIDEO) { current_page = PageType::VIDEO_CONTENT; video_content_page(new_tabs[0].page.get(), selected_item->url, selected_item->get_title()); - redraw = true; + } else if(new_tabs.size() == 1 && new_tabs[0].page->get_type() == PageTypez::CHAT) { + current_page = PageType::CHAT; + current_chat_room = matrix->get_room_by_id(selected_item->url); + chat_page(static_cast(new_tabs[0].page.get()), current_chat_room); + current_chat_room = nullptr; } else { - page_loop(std::move(new_tabs)); - tabs[selected_tab].page->on_navigate_to_page(); - if(content_storage_json.isObject()) { - const Json::Value &chapters_json = content_storage_json["chapters"]; - if(chapters_json.isObject()) - json_chapters = &chapters_json; - } + page_loop(new_tabs); + } + tabs[selected_tab].page->on_navigate_to_page(); + if(content_storage_json.isObject()) { + const Json::Value &chapters_json = content_storage_json["chapters"]; + if(chapters_json.isObject()) + json_chapters = &chapters_json; } + redraw = true; } else { // TODO: Show the exact cause of error (get error message from curl). // TODO: Make asynchronous @@ -1118,9 +1169,6 @@ namespace QuickMedia { while (window.isOpen() && loop_running) { sf::Int32 frame_time_ms = frame_timer.restart().asMilliseconds(); - Tab ¤t_tab = tabs[selected_tab]; - TabAssociatedData ¤t_tab_associated_data = tab_associated_data[selected_tab]; - while (window.pollEvent(event)) { if (event.type == sf::Event::Closed) { window.close(); @@ -1131,10 +1179,10 @@ namespace QuickMedia { window.setView(sf::View(visible_area)); } - if(current_tab.search_bar) { + if(tabs[selected_tab].search_bar) { if(event.type == sf::Event::TextEntered) - current_tab.search_bar->onTextEntered(event.text.unicode); - current_tab.search_bar->on_event(event); + tabs[selected_tab].search_bar->onTextEntered(event.text.unicode); + tabs[selected_tab].search_bar->on_event(event); } if(event.type == sf::Event::Resized || event.type == sf::Event::GainedFocus) @@ -1144,26 +1192,26 @@ namespace QuickMedia { bool hit_bottom = false; switch(event.key.code) { case sf::Keyboard::Down: - hit_bottom = !current_tab.body->select_next_item(); + hit_bottom = !tabs[selected_tab].body->select_next_item(); break; case sf::Keyboard::PageDown: - hit_bottom = !current_tab.body->select_next_page(); + hit_bottom = !tabs[selected_tab].body->select_next_page(); break; case sf::Keyboard::End: - current_tab.body->select_last_item(); + tabs[selected_tab].body->select_last_item(); hit_bottom = true; break; default: hit_bottom = false; break; } - if(hit_bottom && current_tab_associated_data.fetch_status == FetchStatus::NONE && !current_tab_associated_data.fetching_next_page_running && current_tab.page) { + if(hit_bottom && tab_associated_data[selected_tab].fetch_status == FetchStatus::NONE && !tab_associated_data[selected_tab].fetching_next_page_running && tabs[selected_tab].page) { gradient_inc = 0.0; - current_tab_associated_data.fetching_next_page_running = true; - int next_page = current_tab_associated_data.fetched_page + 1; - Page *page = current_tab.page.get(); - std::string update_search_text = current_tab_associated_data.update_search_text; - current_tab_associated_data.next_page_future = std::async(std::launch::async, [update_search_text, next_page, page]() { + tab_associated_data[selected_tab].fetching_next_page_running = true; + int next_page = tab_associated_data[selected_tab].fetched_page + 1; + Page *page = tabs[selected_tab].page.get(); + std::string update_search_text = tab_associated_data[selected_tab].update_search_text; + tab_associated_data[selected_tab].next_page_future = std::async(std::launch::async, [update_search_text, next_page, page]() { BodyItems result_items; if(page->get_page(update_search_text, next_page, result_items) != PluginResult::OK) fprintf(stderr, "Failed to get next page (page %d)\n", next_page); @@ -1171,33 +1219,33 @@ namespace QuickMedia { }); } } else if(event.key.code == sf::Keyboard::Up) { - current_tab.body->select_previous_item(); + tabs[selected_tab].body->select_previous_item(); } else if(event.key.code == sf::Keyboard::PageUp) { - current_tab.body->select_previous_page(); + tabs[selected_tab].body->select_previous_page(); } else if(event.key.code == sf::Keyboard::Home) { - current_tab.body->select_first_item(); + tabs[selected_tab].body->select_first_item(); } else if(event.key.code == sf::Keyboard::Escape) { goto page_end; } else if(event.key.code == sf::Keyboard::Left) { if(selected_tab > 0) { - current_tab.body->clear_cache(); + tabs[selected_tab].body->clear_cache(); --selected_tab; redraw = true; } } else if(event.key.code == sf::Keyboard::Right) { if(selected_tab < (int)tabs.size() - 1) { - current_tab.body->clear_cache(); + tabs[selected_tab].body->clear_cache(); ++selected_tab; redraw = true; } } else if(event.key.code == sf::Keyboard::Tab) { - if(current_tab.search_bar) current_tab.search_bar->set_to_autocomplete(); + if(tabs[selected_tab].search_bar) tabs[selected_tab].search_bar->set_to_autocomplete(); } else if(event.key.code == sf::Keyboard::Enter) { - if(!current_tab.search_bar) submit_handler(); + if(!tabs[selected_tab].search_bar) submit_handler(); } else if(event.key.code == sf::Keyboard::T && event.key.control) { - BodyItem *selected_item = current_tab.body->get_selected(); - if(selected_item && current_tab.page && current_tab.page->is_trackable()) { - TrackablePage *trackable_page = static_cast(current_tab.page.get()); + BodyItem *selected_item = tabs[selected_tab].body->get_selected(); + if(selected_item && tabs[selected_tab].page && tabs[selected_tab].page->is_trackable()) { + TrackablePage *trackable_page = static_cast(tabs[selected_tab].page.get()); TrackResult track_result = trackable_page->track(selected_item->get_title()); // TODO: Show proper error message when this fails. For example if we are already tracking the manga if(track_result == TrackResult::OK) { @@ -1212,9 +1260,9 @@ namespace QuickMedia { if(redraw) { redraw = false; - if(current_tab.search_bar) current_tab.search_bar->onWindowResize(window_size); + if(tabs[selected_tab].search_bar) tabs[selected_tab].search_bar->onWindowResize(window_size); // TODO: Dont show tabs if there is only one tab - get_body_dimensions(window_size, current_tab.search_bar.get(), body_pos, body_size, true); + get_body_dimensions(window_size, tabs[selected_tab].search_bar.get(), body_pos, body_size, true); gradient_points[0].position.x = 0.0f; gradient_points[0].position.y = window_size.y - gradient_height; @@ -1229,14 +1277,14 @@ namespace QuickMedia { gradient_points[3].position.y = window_size.y; } - if(current_tab.search_bar) current_tab.search_bar->update(); + if(tabs[selected_tab].search_bar) tabs[selected_tab].search_bar->update(); - if(current_tab.page->is_lazy_fetch_page() && current_tab_associated_data.fetch_status == FetchStatus::NONE && !current_tab_associated_data.lazy_fetch_finished) { - current_tab_associated_data.fetch_status = FetchStatus::LOADING; - current_tab_associated_data.fetch_type = FetchType::LAZY; - current_tab_associated_data.search_result_text.setString("Fetching page..."); - LazyFetchPage *lazy_fetch_page = static_cast(current_tab.page.get()); - current_tab_associated_data.fetch_future = std::async(std::launch::async, [lazy_fetch_page]() { + if(tabs[selected_tab].page->is_lazy_fetch_page() && tab_associated_data[selected_tab].fetch_status == FetchStatus::NONE && !tab_associated_data[selected_tab].lazy_fetch_finished) { + tab_associated_data[selected_tab].fetch_status = FetchStatus::LOADING; + tab_associated_data[selected_tab].fetch_type = FetchType::LAZY; + tab_associated_data[selected_tab].search_result_text.setString("Fetching page..."); + LazyFetchPage *lazy_fetch_page = static_cast(tabs[selected_tab].page.get()); + tab_associated_data[selected_tab].fetch_future = std::async(std::launch::async, [lazy_fetch_page]() { FetchResult fetch_result; fetch_result.result = lazy_fetch_page->lazy_fetch(fetch_result.body_items); return fetch_result; @@ -1246,6 +1294,8 @@ namespace QuickMedia { for(size_t i = 0; i < tabs.size(); ++i) { TabAssociatedData &associated_data = tab_associated_data[i]; + tabs[i].page->update(); + if(associated_data.fetching_next_page_running && is_future_ready(associated_data.next_page_future)) { BodyItems new_body_items = associated_data.next_page_future.get(); fprintf(stderr, "Finished fetching page %d, num new messages: %zu\n", associated_data.fetched_page + 1, new_body_items.size()); @@ -1304,18 +1354,18 @@ namespace QuickMedia { } window.clear(back_color); - if(current_tab.search_bar) current_tab.search_bar->draw(window, false); + if(tabs[selected_tab].search_bar) tabs[selected_tab].search_bar->draw(window, false); { float shade_extra_height = 0.0f; - if(!current_tab.search_bar) + if(!tabs[selected_tab].search_bar) shade_extra_height = 10.0f; const float width_per_tab = window_size.x / tabs.size(); tab_background.setSize(sf::Vector2f(std::floor(width_per_tab - tab_margin_x * 2.0f), tab_height)); - float tab_vertical_offset = current_tab.search_bar ? current_tab.search_bar->getBottomWithoutShadow() : 0.0f; - current_tab.body->draw(window, body_pos, body_size, *json_chapters); + float tab_vertical_offset = tabs[selected_tab].search_bar ? tabs[selected_tab].search_bar->getBottomWithoutShadow() : 0.0f; + tabs[selected_tab].body->draw(window, body_pos, body_size, *json_chapters); const float tab_y = tab_spacer_height + std::floor(tab_vertical_offset + tab_height * 0.5f - (tab_text_size + 5.0f) * 0.5f) + shade_extra_height; tab_shade.setPosition(0.0f, tab_spacer_height + std::floor(tab_vertical_offset)); @@ -1338,7 +1388,7 @@ namespace QuickMedia { } } - if(current_tab_associated_data.fetching_next_page_running) { + if(tab_associated_data[selected_tab].fetching_next_page_running) { 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 bottom_color = interpolate_colors(back_color, sf::Color(175, 180, 188), progress); @@ -1350,12 +1400,12 @@ namespace QuickMedia { window.draw(gradient_points, 4, sf::Quads); // Note: sf::Quads doesn't work with egl } - if(!current_tab_associated_data.search_result_text.getString().isEmpty()) { - auto search_result_text_bounds = current_tab_associated_data.search_result_text.getLocalBounds(); - current_tab_associated_data.search_result_text.setPosition( + if(!tab_associated_data[selected_tab].search_result_text.getString().isEmpty()) { + auto search_result_text_bounds = tab_associated_data[selected_tab].search_result_text.getLocalBounds(); + tab_associated_data[selected_tab].search_result_text.setPosition( std::floor(body_pos.x + body_size.x * 0.5f - search_result_text_bounds.width * 0.5f), std::floor(body_pos.y + body_size.y * 0.5f - search_result_text_bounds.height * 0.5f)); - window.draw(current_tab_associated_data.search_result_text); + window.draw(tab_associated_data[selected_tab].search_result_text); } window.display(); @@ -1785,7 +1835,7 @@ namespace QuickMedia { if(!video_loaded) { window.clear(back_color); load_sprite.setPosition(window_size.x * 0.5f - loading_icon_size.x * 0.5f, window_size.y * 0.5f - loading_icon_size.y * 0.5f); - load_sprite.setRotation(-time_watched_timer.getElapsedTime().asSeconds() * 400.0); + load_sprite.setRotation(time_watched_timer.getElapsedTime().asSeconds() * 400.0); window.draw(load_sprite); window.display(); continue; @@ -1998,9 +2048,7 @@ namespace QuickMedia { CopyOp copy_op; copy_op.source = image_filepath_tmp; copy_op.destination = image_filepath; - std::unique_lock lock(image_upscale_mutex); - images_to_upscale.push_back(std::move(copy_op)); - image_upscale_cv.notify_one(); + images_to_upscale_queue.push(std::move(copy_op)); } else { fprintf(stderr, "Info: not upscaling %s because the file is already large on your monitor (screen height: %d, image height: %d)\n", image_filepath_tmp.data.c_str(), screen_height, image_height); image_upscale_status[image_index] = 1; @@ -2014,9 +2062,7 @@ namespace QuickMedia { CopyOp copy_op; copy_op.source = image_filepath_tmp; copy_op.destination = image_filepath; - std::unique_lock lock(image_upscale_mutex); - images_to_upscale.push_back(std::move(copy_op)); - image_upscale_cv.notify_one(); + images_to_upscale_queue.push(std::move(copy_op)); } if(rename_immediately) { @@ -2238,8 +2284,7 @@ namespace QuickMedia { image_download_future.get(); image_download_cancel = false; } - std::unique_lock lock(image_upscale_mutex); - images_to_upscale.clear(); + images_to_upscale_queue.clear(); image_upscale_status.clear(); } return page_navigation; @@ -2328,8 +2373,7 @@ namespace QuickMedia { image_download_future.get(); image_download_cancel = false; } - std::unique_lock lock(image_upscale_mutex); - images_to_upscale.clear(); + images_to_upscale_queue.clear(); image_upscale_status.clear(); } } @@ -2459,12 +2503,12 @@ namespace QuickMedia { } }; - comment_input.on_submit_callback = [&post_comment_future, &navigation_stage, &request_new_google_captcha_challenge, &comment_to_post, &captcha_post_id, &captcha_solved_time, &post_comment, &thread_page](const std::string &text) -> bool { + comment_input.on_submit_callback = [&post_comment_future, &navigation_stage, &request_new_google_captcha_challenge, &comment_to_post, &captcha_post_id, &captcha_solved_time, &post_comment, &thread_page](std::string text) -> bool { if(text.empty()) return false; assert(navigation_stage == NavigationStage::REPLYING); - comment_to_post = text; + comment_to_post = std::move(text); if(!captcha_post_id.empty() && captcha_solved_time.getElapsedTime().asSeconds() < 120) { post_comment_future = std::async(std::launch::async, [&post_comment]() -> bool { post_comment(); @@ -2909,19 +2953,6 @@ namespace QuickMedia { sf::Text text; }; - static std::string extract_first_line(const std::string &str, size_t max_length) { - size_t index = str.find('\n'); - if(index == std::string::npos) { - if(str.size() > max_length) - return str.substr(0, max_length) + " (...)"; - return str; - } else if(index == 0) { - return ""; - } else { - return str.substr(0, std::min(index, max_length)) + " (...)"; - } - } - static std::string remove_reply_formatting(const std::string &str) { if(strncmp(str.c_str(), "> <@", 4) == 0) { size_t index = str.find("> ", 4); @@ -2982,9 +3013,10 @@ namespace QuickMedia { struct PinnedEventData { std::string event_id; FetchStatus status = FetchStatus::NONE; + Message *message = nullptr; }; - void Program::chat_page() { + void Program::chat_page(MatrixChatPage *chat_page, RoomData *current_room) { assert(strcmp(plugin_name, "matrix") == 0); auto video_page = std::make_unique(this); @@ -2996,7 +3028,7 @@ namespace QuickMedia { 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); + pinned_tab.text = sf::Text("Pinned messages", *font, tab_text_size); tabs.push_back(std::move(pinned_tab)); ChatTab messages_tab; @@ -3007,127 +3039,12 @@ namespace QuickMedia { messages_tab.text = sf::Text("Messages", *font, tab_text_size); tabs.push_back(std::move(messages_tab)); - ChatTab rooms_tab; - rooms_tab.body = std::make_unique(this, font.get(), bold_font.get(), cjk_font.get(), loading_icon); - //rooms_tab.body->line_separator_color = sf::Color::Transparent; - rooms_tab.body->thumbnail_mask_shader = &circle_mask_shader; - rooms_tab.text = sf::Text("Rooms", *font, tab_text_size); - tabs.push_back(std::move(rooms_tab)); - 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 - bool synced = false; - RoomData *current_room = nullptr; bool is_window_focused = window.hasFocus(); - // Returns -1 if no rooms or no unread rooms - auto find_top_body_position_for_unread_room = [&tabs](BodyItem *item_to_swap, int start_index) { - for(int i = start_index; i < (int)tabs[ROOMS_TAB_INDEX].body->items.size(); ++i) { - const auto &body_item = tabs[ROOMS_TAB_INDEX].body->items[i]; - if(static_cast(body_item->userdata)->last_message_read || body_item.get() == item_to_swap) - return i; - } - return -1; - }; - - // Returns -1 if no rooms or all rooms have unread mentions - auto find_top_body_position_for_mentioned_room = [&tabs](BodyItem *item_to_swap, int start_index) { - for(int i = start_index; i < (int)tabs[ROOMS_TAB_INDEX].body->items.size(); ++i) { - const auto &body_item = tabs[ROOMS_TAB_INDEX].body->items[i]; - if(!static_cast(body_item->userdata)->has_unread_mention || body_item.get() == item_to_swap) - return i; - } - return -1; - }; - - 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] - (RoomSyncData &room_sync_data, bool is_first_sync) mutable - { - 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 - if(!is_window_focused || room != current_room || is_first_sync || selected_tab == ROOMS_TAB_INDEX) - show_notification("QuickMedia matrix - " + matrix->message_get_author_displayname(message.get()) + " (" + room->get_name() + ")", message->body); - } - } - } - - for(auto &[room, sync_data] : room_sync_data) { - if(sync_data.messages.empty()) - continue; - - std::shared_ptr me = matrix->get_me(room); - time_t read_marker_message_timestamp = 0; - if(me) { - auto read_marker_message = room->get_message_by_id(room->get_user_read_marker(me)); - if(read_marker_message) - read_marker_message_timestamp = read_marker_message->timestamp; - } - - // 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 : 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(); - } - - if(!last_unread_message && !is_first_sync) - continue; - - BodyItem *room_body_item = static_cast(room->userdata); - assert(room_body_item); - - if(last_unread_message) { - std::string room_desc = "Unread: " + matrix->message_get_author_displayname(last_unread_message) + ": " + extract_first_line(last_unread_message->body, 150); - if(room->has_unread_mention) - room_desc += "\n** You were mentioned **"; // TODO: Better notification? - room_body_item->set_description(std::move(room_desc)); - room_body_item->set_title_color(sf::Color(255, 100, 100)); - room->last_message_read = false; - - // Swap order of rooms in body list to put rooms with mentions at the top and then unread messages and then all the other rooms - // TODO: Optimize with hash map instead of linear search? or cache the index - Body *rooms_body = tabs[ROOMS_TAB_INDEX].body.get(); - int room_body_index = rooms_body->get_index_by_body_item(room_body_item); - if(room_body_index != -1) { - std::shared_ptr body_item = rooms_body->items[room_body_index]; - int body_swap_index = -1; - if(room->has_unread_mention) - body_swap_index = find_top_body_position_for_mentioned_room(body_item.get(), 0); - else if(!room->last_message_read) - body_swap_index = find_top_body_position_for_unread_room(body_item.get(), 0); - if(body_swap_index != -1 && body_swap_index != room_body_index) { - rooms_body->items.erase(rooms_body->items.begin() + room_body_index); - if(body_swap_index < room_body_index) - rooms_body->items.insert(rooms_body->items.begin() + body_swap_index, std::move(body_item)); - else - rooms_body->items.insert(rooms_body->items.begin() + (body_swap_index - 1), std::move(body_item)); - } - } - } else if(is_first_sync) { - Message *last_unread_message = nullptr; - 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; - } - } - if(last_unread_message) - room_body_item->set_description(matrix->message_get_author_displayname(last_unread_message) + ": " + extract_first_line(last_unread_message->body, 150)); - } - } - }; - enum class ChatState { NAVIGATING, TYPING_MESSAGE, @@ -3197,6 +3114,8 @@ namespace QuickMedia { auto body_item = find_body_item_by_event_id(body_items, num_body_items, message->related_event_id); if(body_item) { body_item->set_description(message_get_body_remove_formatting(message.get())); + // TODO: Append the new message to the body item so the body item should have a list of edit events + //body_item->userdata = message.get(); if(message->related_event_type == RelatedEventType::REDACTION) set_body_as_deleted(message.get(), body_item.get()); it = unreferenced_events.erase(it); @@ -3211,6 +3130,9 @@ namespace QuickMedia { // TODO: Optimize with hash map? auto modify_related_messages_in_current_room = [&set_body_as_deleted, &unreferenced_event_by_room, ¤t_room, &find_body_item_by_event_id, &tabs](Messages &messages) { + if(messages.empty()) + return; + auto &unreferenced_events = unreferenced_event_by_room[current_room]; auto &body_items = tabs[MESSAGES_TAB_INDEX].body->items; for(auto &message : messages) { @@ -3219,6 +3141,8 @@ namespace QuickMedia { auto body_item = find_body_item_by_event_id(body_items.data(), body_items.size(), message->related_event_id); if(body_item) { body_item->set_description(message_get_body_remove_formatting(message.get())); + // TODO: Append the new message to the body item so the body item should have a list of edit events + //body_item->userdata = message.get(); if(message->related_event_type == RelatedEventType::REDACTION) set_body_as_deleted(message.get(), body_item.get()); } else { @@ -3228,9 +3152,16 @@ namespace QuickMedia { } }; - auto process_new_pinned_events = [&tabs](const std::vector &pinned_events) { + auto process_pinned_events = [&tabs](const std::optional> &pinned_events) { + if(!pinned_events || pinned_events->empty()) + return; + + bool empty_before = tabs[PINNED_TAB_INDEX].body->items.empty(); + int selected_before = tabs[PINNED_TAB_INDEX].body->get_selected_item(); + tabs[PINNED_TAB_INDEX].body->items.clear(); + // TODO: Add message to rooms messages when there are new pinned events - for(const std::string &event : pinned_events) { + for(const std::string &event : pinned_events.value()) { auto body = BodyItem::create(""); body->set_description("Loading message..."); PinnedEventData *event_data = new PinnedEventData(); @@ -3239,62 +3170,37 @@ namespace QuickMedia { 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) { - tabs[ROOMS_TAB_INDEX].body->filter_search_fuzzy(text); - //tabs[ROOMS_TAB_INDEX].body->select_first_item(); + if(empty_before) + tabs[PINNED_TAB_INDEX].body->select_last_item(); + else + tabs[PINNED_TAB_INDEX].body->set_selected_item(selected_before); }; - room_search_bar.onTextSubmitCallback = - [this, &tabs, &selected_tab, ¤t_room, &room_name_text, - &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&) - { - BodyItem *selected_item = tabs[ROOMS_TAB_INDEX].body->get_selected(); - if(!selected_item) - return; + Body url_selection_body(this, font.get(), bold_font.get(), cjk_font.get(), loading_icon); + + 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(); - tabs[ROOMS_TAB_INDEX].body->clear_cache(); + std::vector pinned_events; + matrix->get_all_pinned_events(current_room, pinned_events); + process_pinned_events(pinned_events); + tabs[PINNED_TAB_INDEX].body->select_last_item(); - current_room = (RoomData*)selected_item->userdata; - assert(current_room); - selected_tab = MESSAGES_TAB_INDEX; - tabs[MESSAGES_TAB_INDEX].body->clear_items(); + room_name_text.setString(static_cast(current_room->userdata)->get_title()); + room_avatar_thumbnail_data = std::make_shared(); - 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); - tabs[PINNED_TAB_INDEX].body->select_last_item(); - - room_name_text.setString(static_cast(current_room->userdata)->get_title()); - room_avatar_thumbnail_data = std::make_shared(); - - read_marker_timeout_ms = 0; - redraw = true; - room_search_bar.clear(); - tabs[ROOMS_TAB_INDEX].body->filter_search_fuzzy(""); - }; + read_marker_timeout_ms = 0; + redraw = true; Entry chat_input("Press m to begin writing a message...", font.get(), cjk_font.get()); chat_input.draw_background = false; chat_input.set_editable(false); - chat_input.on_submit_callback = [this, &tabs, &chat_input, &selected_tab, ¤t_room, &new_page, &chat_state, ¤tly_operating_on_item](const std::string &text) mutable { + chat_input.on_submit_callback = [this, &tabs, &chat_input, &selected_tab, ¤t_room, &new_page, &chat_state, ¤tly_operating_on_item](std::string text) mutable { if(!current_room) return false; @@ -3302,27 +3208,28 @@ namespace QuickMedia { if(text.empty()) return false; + std::string msgtype; if(chat_state == ChatState::TYPING_MESSAGE && text[0] == '/') { - std::string command = strip(text); - if(command == "/upload") { + if(text == "/upload") { new_page = PageType::FILE_MANAGER; chat_input.set_editable(false); chat_state = ChatState::NAVIGATING; return true; - } else if(command == "/logout") { - new_page = PageType::CHAT_LOGIN; - chat_input.set_editable(false); - chat_state = ChatState::NAVIGATING; + } else if(text == "/logout") { + show_notification("QuickMedia", "/logout command is temporary disabled. Delete " + get_storage_dir().join("matrix").join("session.json").data + " and restart QuickMedia to logout", Urgency::CRITICAL); return true; + } else if(strncmp(text.c_str(), "/me ", 4) == 0) { + msgtype = "m.emote"; + text.erase(text.begin(), text.begin() + 4); } else { - fprintf(stderr, "Error: invalid command: %s, expected /upload\n", command.c_str()); + fprintf(stderr, "Error: invalid command: %s, expected /upload, /logout or /me\n", text.c_str()); return false; } } if(chat_state == ChatState::TYPING_MESSAGE) { // TODO: Make asynchronous - if(matrix->post_message(current_room, text, std::nullopt, std::nullopt) == PluginResult::OK) { + if(matrix->post_message(current_room, text, std::nullopt, std::nullopt, msgtype) == PluginResult::OK) { chat_input.set_editable(false); chat_state = ChatState::NAVIGATING; if(tabs[MESSAGES_TAB_INDEX].body->is_last_item_fully_visible()) @@ -3361,16 +3268,6 @@ namespace QuickMedia { return false; }; - struct SyncFutureResult { - Rooms rooms; - RoomSyncData room_sync_data; - }; - - std::future sync_future; - bool sync_running = false; - sf::Clock sync_timer; - sf::Int32 sync_min_time_ms = 0; // Sync immediately the first time - std::future previous_messages_future; bool fetching_previous_messages_running = false; RoomData *previous_messages_future_room = nullptr; @@ -3397,6 +3294,7 @@ namespace QuickMedia { if(related_body_item) { *body_item = *related_body_item; event_data->status = FetchStatus::FINISHED_LOADING; + event_data->message = static_cast(related_body_item->userdata); body_item->userdata = event_data; return; } @@ -3405,7 +3303,7 @@ namespace QuickMedia { std::string message_event_id = event_data->event_id; fetch_future_room = current_room; fetch_body_item = body_item; - body_item->embedded_item_status = FetchStatus::LOADING; + event_data->status = FetchStatus::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]() { @@ -3488,8 +3386,6 @@ namespace QuickMedia { const float chat_input_padding_x = 10.0f; const float chat_input_padding_y = 10.0f; - Body url_selection_body(this, font.get(), bold_font.get(), cjk_font.get(), loading_icon); - auto launch_url = [this, &video_page, &redraw](const std::string &url) mutable { if(url.empty()) return; @@ -3520,6 +3416,9 @@ namespace QuickMedia { }; auto add_new_messages_to_current_room = [this, &tabs, &selected_tab, ¤t_room](Messages &messages) { + if(messages.empty()) + return; + 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) @@ -3536,48 +3435,68 @@ namespace QuickMedia { } }; - auto add_new_rooms = [&tabs, ¤t_room, &room_search_bar, &room_name_text](Rooms &rooms) { - if(rooms.empty()) - return; + auto display_url_or_image = [this, &selected_tab, &redraw, &video_page, &launch_url, &chat_state, &url_selection_body](BodyItem *selected) { + if(!selected) + return false; - std::string search_filter_text = room_search_bar.get_text(); - - for(size_t i = 0; i < rooms.size(); ++i) { - auto &room = rooms[i]; - std::string room_name = room->get_name(); - if(room_name.empty()) - room_name = room->id; - - auto body_item = BodyItem::create(std::move(room_name)); - body_item->thumbnail_url = room->get_avatar_url(); - body_item->userdata = room; // Note: this has to be valid as long as the room list is valid! - body_item->thumbnail_mask_type = ThumbnailMaskType::CIRCLE; - body_item->thumbnail_size = sf::Vector2i(32, 32); - tabs[ROOMS_TAB_INDEX].body->filter_search_fuzzy_item(search_filter_text, body_item.get()); - tabs[ROOMS_TAB_INDEX].body->items.push_back(body_item); - room->userdata = body_item.get(); - } + Message *selected_item_message = nullptr; + if(selected_tab == MESSAGES_TAB_INDEX) { + selected_item_message = static_cast(selected->userdata); + } else if(selected_tab == PINNED_TAB_INDEX && static_cast(selected->userdata)->status == FetchStatus::FINISHED_LOADING) { + selected_item_message = static_cast(selected->userdata)->message; + } + + if(selected_item_message) { + MessageType message_type = selected_item_message->type; + std::string *selected_url = &selected->url; + if(!selected_url->empty()) { + if(message_type == MessageType::VIDEO || message_type == MessageType::IMAGE || message_type == MessageType::AUDIO) { + page_stack.push(PageType::CHAT); + watched_videos.clear(); + current_page = PageType::VIDEO_CONTENT; + bool is_audio = (message_type == MessageType::AUDIO); + bool prev_no_video = no_video; + no_video = is_audio; + // TODO: Add title + video_content_page(video_page.get(), *selected_url, "No title"); + no_video = prev_no_video; + redraw = true; + return true; + } - if(current_room) - return; + launch_url(*selected_url); + return true; + } + } - current_room = rooms[0]; - room_name_text.setString(static_cast(current_room->userdata)->get_title()); + // TODO: If content type is a file, show file-manager prompt where it should be saved and asynchronously save it instead + std::vector urls; + extract_urls(selected->get_description(), urls); + if(urls.size() == 1) { + launch_url(urls[0]); + return true; + } else if(urls.size() > 1) { + chat_state = ChatState::URL_SELECTION; + url_selection_body.clear_items(); + for(const std::string &url : urls) { + auto body_item = BodyItem::create(url); + url_selection_body.items.push_back(std::move(body_item)); + } + return true; + } + return false; }; float tab_shade_height = 0.0f; + bool frame_skip_text_entry = false; + + SyncData sync_data; while (current_page == PageType::CHAT) { sf::Int32 frame_time_ms = frame_timer.restart().asMilliseconds(); while (window.pollEvent(event)) { base_event_handler(event, PageType::EXIT, tabs[selected_tab].body.get(), nullptr, false, false); - 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); - } - if(event.type == sf::Event::GainedFocus) { is_window_focused = true; redraw = true; @@ -3620,7 +3539,7 @@ namespace QuickMedia { tabs[selected_tab].body->select_next_page(); } else if(event.key.code == sf::Keyboard::End) { tabs[selected_tab].body->select_last_item(); - } else if((event.key.code == sf::Keyboard::Left) && synced && selected_tab > 0) { + } else if((event.key.code == sf::Keyboard::Left) && selected_tab > 0) { tabs[selected_tab].body->clear_cache(); --selected_tab; read_marker_timer.restart(); @@ -3630,7 +3549,7 @@ namespace QuickMedia { typing = false; typing_futures.push_back(std::async(typing_async_func, false, current_room)); } - } else if((event.key.code == sf::Keyboard::Right) && synced && selected_tab < (int)tabs.size() - 1) { + } else if((event.key.code == sf::Keyboard::Right) && selected_tab < (int)tabs.size() - 1) { tabs[selected_tab].body->clear_cache(); ++selected_tab; read_marker_timer.restart(); @@ -3640,50 +3559,93 @@ namespace QuickMedia { typing = false; typing_futures.push_back(std::async(typing_async_func, false, current_room)); } + } else if(event.key.code == sf::Keyboard::Escape) { + goto chat_page_end; } - if(selected_tab == MESSAGES_TAB_INDEX && event.key.code == sf::Keyboard::Enter) { + if((selected_tab == MESSAGES_TAB_INDEX || selected_tab == PINNED_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; - std::string selected_url = selected->url; - if(selected_url.empty() && selected->embedded_item) { - selected_url = selected->embedded_item->url; - message_type = static_cast(selected->embedded_item->userdata)->type; + if(!display_url_or_image(selected)) + display_url_or_image(selected->embedded_item.get()); + } + } + + if(selected_tab == MESSAGES_TAB_INDEX && current_room) { + if(event.key.code == sf::Keyboard::U) { + frame_skip_text_entry = true; + new_page = PageType::FILE_MANAGER; + chat_input.set_editable(false); + } + + if(event.key.code == sf::Keyboard::M) { + frame_skip_text_entry = true; + chat_input.set_editable(true); + chat_state = ChatState::TYPING_MESSAGE; + } + + if(event.key.control && event.key.code == sf::Keyboard::V) { + frame_skip_text_entry = true; + // TODO: Make asynchronous. + // TODO: Upload multiple files. + std::string err_msg; + if(matrix->post_file(current_room, sf::Clipboard::getString(), err_msg) != PluginResult::OK) { + std::string desc = "Failed to upload media to room, error: " + err_msg; + show_notification("QuickMedia", desc.c_str(), Urgency::CRITICAL); } - if(!selected_url.empty()) { - if(message_type == MessageType::VIDEO || message_type == MessageType::IMAGE || message_type == MessageType::AUDIO) { - page_stack.push(PageType::CHAT); - watched_videos.clear(); - current_page = PageType::VIDEO_CONTENT; - bool is_audio = (message_type == MessageType::AUDIO); - bool prev_no_video = no_video; - no_video = is_audio; - // TODO: Add title - video_content_page(video_page.get(), selected_url, "No title"); - no_video = prev_no_video; - redraw = true; - continue; - } + } + + if(event.key.code == sf::Keyboard::R) { + frame_skip_text_entry = true; + std::shared_ptr selected = tabs[selected_tab].body->get_selected_shared(); + if(selected) { + chat_state = ChatState::REPLYING; + currently_operating_on_item = selected; + chat_input.set_editable(true); + replying_to_text.setString("Replying to:"); + } else { + // TODO: Show inline notification + show_notification("QuickMedia", "No message selected for replying"); + } + } - launch_url(selected_url); - continue; + if(event.key.code == sf::Keyboard::E) { + frame_skip_text_entry = true; + std::shared_ptr selected = tabs[selected_tab].body->get_selected_shared(); + if(selected) { + if(!selected->url.empty()) { // cant edit messages that are image/video posts + // TODO: Show inline notification + show_notification("QuickMedia", "You can only edit messages with no file attached to it"); + } else if(!matrix->was_message_posted_by_me(selected->userdata)) { + // TODO: Show inline notification + show_notification("QuickMedia", "You can't edit a message that was posted by somebody else"); + } else { + chat_state = ChatState::EDITING; + currently_operating_on_item = selected; + chat_input.set_editable(true); + chat_input.set_text(selected->get_description()); // TODO: Description? it may change in the future, in which case this should be edited + chat_input.move_caret_to_end(); + replying_to_text.setString("Editing message:"); + } + } else { + // TODO: Show inline notification + show_notification("QuickMedia", "No message selected for editing"); } + } - // TODO: If content type is a file, show file-manager prompt where it should be saved and asynchronously save it instead - std::vector urls; - extract_urls(selected->get_description(), urls); - if(selected->embedded_item) - extract_urls(selected->embedded_item->get_description(), urls); - if(urls.size() == 1) { - launch_url(urls[0]); - } else if(urls.size() > 1) { - chat_state = ChatState::URL_SELECTION; - url_selection_body.clear_items(); - for(const std::string &url : urls) { - auto body_item = BodyItem::create(url); - url_selection_body.items.push_back(std::move(body_item)); + if(event.key.code == sf::Keyboard::D) { + frame_skip_text_entry = true; + BodyItem *selected = tabs[selected_tab].body->get_selected(); + if(selected) { + // TODO: Make asynchronous + std::string err_msg; + if(matrix->delete_message(current_room, selected->userdata, err_msg) != PluginResult::OK) { + // TODO: Show inline notification + show_notification("QuickMedia", "Failed to delete message, reason: " + err_msg, Urgency::CRITICAL); } + } else { + // TODO: Show inline notification + show_notification("QuickMedia", "No message selected for deletion"); } } } @@ -3719,80 +3681,10 @@ namespace QuickMedia { continue; launch_url(selected_item->get_title()); } - } 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); - } - - if(event.key.code == sf::Keyboard::M) { - chat_input.set_editable(true); - chat_state = ChatState::TYPING_MESSAGE; - } - - if(event.key.control && event.key.code == sf::Keyboard::V) { - // TODO: Make asynchronous. - // TODO: Upload multiple files. - std::string err_msg; - if(matrix->post_file(current_room, sf::Clipboard::getString(), err_msg) != PluginResult::OK) { - std::string desc = "Failed to upload media to room, error: " + err_msg; - show_notification("QuickMedia", desc.c_str(), Urgency::CRITICAL); - } - } - - if(event.key.code == sf::Keyboard::R) { - std::shared_ptr selected = tabs[selected_tab].body->get_selected_shared(); - if(selected) { - chat_state = ChatState::REPLYING; - currently_operating_on_item = selected; - chat_input.set_editable(true); - replying_to_text.setString("Replying to:"); - } else { - // TODO: Show inline notification - show_notification("QuickMedia", "No message selected for replying"); - } - } - - if(event.key.code == sf::Keyboard::E) { - std::shared_ptr selected = tabs[selected_tab].body->get_selected_shared(); - if(selected) { - if(!selected->url.empty()) { // cant edit messages that are image/video posts - // TODO: Show inline notification - show_notification("QuickMedia", "You can only edit messages with no file attached to it"); - } else if(!matrix->was_message_posted_by_me(selected->userdata)) { - // TODO: Show inline notification - show_notification("QuickMedia", "You can't edit a message that was posted by somebody else"); - } else { - chat_state = ChatState::EDITING; - currently_operating_on_item = selected; - chat_input.set_editable(true); - chat_input.set_text(selected->get_description()); // TODO: Description? it may change in the future, in which case this should be edited - chat_input.move_caret_to_end(); - replying_to_text.setString("Editing message:"); - } - } else { - // TODO: Show inline notification - show_notification("QuickMedia", "No message selected for editing"); - } - } - - if(event.key.code == sf::Keyboard::D) { - BodyItem *selected = tabs[selected_tab].body->get_selected(); - if(selected) { - // TODO: Make asynchronous - std::string err_msg; - if(matrix->delete_message(current_room, selected->userdata, err_msg) != PluginResult::OK) { - // TODO: Show inline notification - show_notification("QuickMedia", "Failed to delete message, reason: " + err_msg, Urgency::CRITICAL); - } - } else { - // TODO: Show inline notification - show_notification("QuickMedia", "No message selected for deletion"); - } - } } - if((chat_state == ChatState::TYPING_MESSAGE || chat_state == ChatState::REPLYING || chat_state == ChatState::EDITING) && selected_tab == MESSAGES_TAB_INDEX) { + if((chat_state == ChatState::TYPING_MESSAGE || chat_state == ChatState::REPLYING || chat_state == ChatState::EDITING) && selected_tab == MESSAGES_TAB_INDEX && !frame_skip_text_entry) { + frame_skip_text_entry = false; if(event.type == sf::Event::TextEntered) { //chat_input.onTextEntered(event.text.unicode); // TODO: Also show typing event when ctrl+v pasting? @@ -3819,6 +3711,9 @@ namespace QuickMedia { chat_input.process_event(event); } } + frame_skip_text_entry = false; + + chat_page->update(); switch(new_page) { case PageType::FILE_MANAGER: { @@ -3833,7 +3728,7 @@ namespace QuickMedia { file_manager_tabs.push_back(Tab{std::move(file_manager_body), std::move(file_manager_page), create_search_bar("Search...", SEARCH_DELAY_FILTER)}); selected_files.clear(); - page_loop(std::move(file_manager_tabs)); + page_loop(file_manager_tabs); if(selected_files.empty()) { fprintf(stderr, "No files selected!\n"); @@ -3854,19 +3749,7 @@ namespace QuickMedia { break; } case PageType::CHAT_LOGIN: { - new_page = PageType::CHAT; - matrix->logout(); - tabs[MESSAGES_TAB_INDEX].body->clear_cache(); - // TODO: Instead of doing this, exit this current function and navigate to chat login page instead. - // This doesn't currently work because at the end of this function there are futures that need to wait - // and one of them is /sync, which has a timeout of 30 seconds. That timeout has to be killed somehow. - //delete current_plugin; - //current_plugin = new Matrix(); - current_page = PageType::CHAT_LOGIN; - chat_login_page(); - if(current_page == PageType::CHAT) - chat_page(); - exit(0); + abort(); break; } default: @@ -3918,8 +3801,6 @@ namespace QuickMedia { float room_name_padding_y = 0.0f; if(selected_tab == MESSAGES_TAB_INDEX || selected_tab == PINNED_TAB_INDEX) room_name_padding_y = room_name_total_height; - else if(selected_tab == ROOMS_TAB_INDEX) - room_name_padding_y = room_search_bar.getBottomWithoutShadow(); chat_input_height_full = chat_input.get_height() + chat_input_padding_y * 2.0f; if(selected_tab != MESSAGES_TAB_INDEX || selected_tab == PINNED_TAB_INDEX) @@ -3933,17 +3814,12 @@ namespace QuickMedia { if(redraw) { redraw = false; - room_search_bar.onWindowResize(window_size); float room_name_padding_y = 0.0f; float padding_bottom = 0.0f; if(selected_tab == MESSAGES_TAB_INDEX || selected_tab == PINNED_TAB_INDEX) { room_name_padding_y = 10.0f + room_name_total_height; tab_vertical_offset = 10.0f; - } else if(selected_tab == ROOMS_TAB_INDEX) { - room_name_padding_y = room_search_bar.getBottomWithoutShadow(); - tab_vertical_offset = 0.0f; - padding_bottom = 10.0f; } tab_shade_height = tab_spacer_height + std::floor(tab_vertical_offset) + tab_height + room_name_padding_y + padding_bottom; @@ -3971,42 +3847,11 @@ namespace QuickMedia { logo_sprite.setPosition(logo_padding_x, std::floor(window_size.y - chat_input_shade.getSize().y * 0.5f - logo_size.y * 0.5f)); } - room_search_bar.update(); - - if(!sync_running && sync_timer.getElapsedTime().asMilliseconds() >= sync_min_time_ms) { - fprintf(stderr, "Time since last sync: %d ms\n", sync_timer.getElapsedTime().asMilliseconds()); - sync_min_time_ms = 1000; - sync_running = true; - sync_timer.restart(); - sync_future = std::async(std::launch::async, [this]() { - SyncFutureResult result; - if(matrix->sync(result.room_sync_data) == PluginResult::OK) { - fprintf(stderr, "Synced matrix\n"); - matrix->get_room_join_updates(result.rooms); - } else { - fprintf(stderr, "Failed to sync matrix\n"); - } - - return result; - }); - } - - if(is_future_ready(sync_future)) { - SyncFutureResult sync_result = sync_future.get(); - - add_new_rooms(sync_result.rooms); - - 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_data, !synced); - sync_running = false; - synced = true; - } + sync_data.messages.clear(); + matrix->get_room_sync_data(current_room, sync_data); + add_new_messages_to_current_room(sync_data.messages); + modify_related_messages_in_current_room(sync_data.messages); + process_pinned_events(sync_data.pinned_events); if(is_future_ready(set_read_marker_future)) { set_read_marker_future.get(); @@ -4045,6 +3890,7 @@ namespace QuickMedia { if(message) { *fetch_body_item = *message_to_body_item(message.get(), matrix->get_me(current_room).get()); event_data->status = FetchStatus::FINISHED_LOADING; + event_data->message = message.get(); fetch_body_item->userdata = event_data; } else { fetch_body_item->set_description("Failed to load message!"); @@ -4091,8 +3937,6 @@ 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(selected_tab == ROOMS_TAB_INDEX) { - room_search_bar.draw(window, false); } gradient_points[0].position.x = 0.0f; @@ -4136,7 +3980,7 @@ namespace QuickMedia { const float margin = 5.0f; const float replying_to_text_height = replying_to_text.getLocalBounds().height + margin; - const float item_height = std::min(body_size.y - replying_to_text_height - margin, tabs[MESSAGES_TAB_INDEX].body->get_item_height(currently_operating_on_item.get()) + margin); + const float item_height = std::min(body_size.y - replying_to_text_height - margin, tabs[MESSAGES_TAB_INDEX].body->get_item_height(currently_operating_on_item.get(), body_size.x) + margin); sf::RectangleShape overlay(sf::Vector2f(window_size.x, window_size.y - tab_shade_height - chat_input_height_full)); overlay.setPosition(0.0f, tab_shade_height); @@ -4176,13 +4020,6 @@ namespace QuickMedia { } } - // TODO: Cache /sync, then we wont only see loading text - if(!synced) { - sf::Text loading_text("Loading...", *font, 24); - loading_text.setPosition(body_pos.x + body_size.x * 0.5f - loading_text.getLocalBounds().width * 0.5f, body_pos.y + body_size.y * 0.5f - loading_text.getLocalBounds().height * 0.5f); - window.draw(loading_text); - } - 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) { @@ -4210,6 +4047,21 @@ namespace QuickMedia { window.display(); } - exit(0); // Ignore futures and quit immediately + chat_page_end: + // TODO: Cancel these instead + if(set_read_marker_future.valid()) + set_read_marker_future.get(); + if(previous_messages_future.valid()) + previous_messages_future.get(); + if(fetch_message_future.valid()) + fetch_message_future.get(); + for(auto &typing_future : typing_futures) { + if(typing_future.valid()) + typing_future.get(); + } + + for(auto &body_item : tabs[PINNED_TAB_INDEX].body->items) { + delete (PinnedEventData*)body_item->userdata; + } } } -- cgit v1.2.3