aboutsummaryrefslogtreecommitdiff
path: root/src/QuickMedia.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/QuickMedia.cpp')
-rw-r--r--src/QuickMedia.cpp321
1 files changed, 260 insertions, 61 deletions
diff --git a/src/QuickMedia.cpp b/src/QuickMedia.cpp
index 755f6df..28bd72f 100644
--- a/src/QuickMedia.cpp
+++ b/src/QuickMedia.cpp
@@ -1672,6 +1672,7 @@ namespace QuickMedia {
while(window.isOpen()) {
auto matrix_chat_page = std::make_unique<MatrixChatPage>(this, current_chat_room->id, rooms_page);
bool move_room = chat_page(matrix_chat_page.get(), current_chat_room, tabs, selected_tab);
+ matrix_chat_page->messages_tab_visible = false;
if(!move_room)
break;
@@ -3890,6 +3891,16 @@ namespace QuickMedia {
return nullptr;
}
+ // Returns |default_value| if the input items is empty
+ static size_t get_body_item_sorted_insert_position_by_author(BodyItems &body_items, const std::string &display_name, size_t default_value) {
+ for(size_t i = 0; i < body_items.size(); ++i) {
+ auto &body_item = body_items[i];
+ if(strcasecmp(display_name.c_str(), body_item->get_author().c_str()) <= 0)
+ return i;
+ }
+ return default_value;
+ }
+
bool Program::chat_page(MatrixChatPage *matrix_chat_page, RoomData *current_room, std::vector<Tab> &room_tabs, int room_selected_tab) {
assert(current_room);
assert(strcmp(plugin_name, "matrix") == 0);
@@ -3918,17 +3929,17 @@ namespace QuickMedia {
messages_tab.body->line_separator_color = sf::Color::Transparent;
tabs.push_back(std::move(messages_tab));
- // ChatTab users_tab;
- // users_tab.body = create_body();
- // users_tab.body->thumbnail_max_size = CHAT_MESSAGE_THUMBNAIL_MAX_SIZE;
- // users_tab.body->attach_side = AttachSide::TOP;
- // //users_tab.body->line_separator_color = sf::Color::Transparent;
- // users_tab.text = sf::Text("Users", *FontLoader::get_font(FontLoader::FontType::LATIN), tab_text_size);
- // tabs.push_back(std::move(users_tab));
+ ChatTab users_tab;
+ users_tab.body = create_body();
+ users_tab.body->thumbnail_max_size = CHAT_MESSAGE_THUMBNAIL_MAX_SIZE;
+ users_tab.body->attach_side = AttachSide::TOP;
+ users_tab.body->line_separator_color = sf::Color::Transparent;
+ tabs.push_back(std::move(users_tab));
- Tabs ui_tabs(&rounded_rectangle_shader, back_color);
+ Tabs ui_tabs(&rounded_rectangle_shader, sf::Color::Transparent);
const int PINNED_TAB_INDEX = ui_tabs.add_tab("Pinned messages (0)");
const int MESSAGES_TAB_INDEX = ui_tabs.add_tab("Messages");
+ const int USERS_TAB_INDEX = ui_tabs.add_tab("Users");
ui_tabs.set_selected(MESSAGES_TAB_INDEX);
matrix_chat_page->chat_body = tabs[MESSAGES_TAB_INDEX].body.get();
@@ -3967,6 +3978,8 @@ namespace QuickMedia {
tabs[selected_tab].body->clear_cache();
if(selected_tab == MESSAGES_TAB_INDEX)
matrix_chat_page->messages_tab_visible = true;
+ else
+ matrix_chat_page->messages_tab_visible = false;
read_marker_timer.restart();
redraw = true;
if(typing) {
@@ -4318,12 +4331,85 @@ namespace QuickMedia {
}
};
+ struct Mention {
+ sf::Clock filter_timer;
+ bool visible = false;
+ bool filter_updated = false;
+ sf::String filter;
+ Body *users_tab_body = nullptr;
+
+ void show() {
+ visible = true;
+ }
+
+ void hide() {
+ visible = false;
+ filter_updated = false;
+ filter.clear();
+ users_tab_body->filter_search_fuzzy("");
+ }
+
+ void handle_event(const sf::Event &event) {
+ if(visible) {
+ if(event.type == sf::Event::TextEntered) {
+ filter_timer.restart();
+ if(event.text.unicode > 32) {
+ filter += event.text.unicode;
+ filter_updated = true;
+ } else if(event.text.unicode == 8) { // 8 = backspace
+ if(filter.getSize() == 0) {
+ hide();
+ } else {
+ filter.erase(filter.getSize() - 1, 1);
+ filter_updated = true;
+ }
+ } else if(event.text.unicode == ' ' || event.text.unicode == '\t') {
+ hide();
+ }
+ } else if(event.type == sf::Event::KeyPressed) {
+ if(event.key.code == sf::Keyboard::Up) {
+ users_tab_body->select_previous_item();
+ } else if(event.key.code == sf::Keyboard::Down) {
+ users_tab_body->select_next_item();
+ } else if(event.key.code == sf::Keyboard::Enter && event.key.shift) {
+ hide();
+ }
+ }
+ }
+
+ if(event.type == sf::Event::TextEntered && event.text.unicode == '@' && !visible)
+ show();
+ }
+
+ void update() {
+ if(visible && filter_updated && filter_timer.getElapsedTime().asMilliseconds() > 50) {
+ filter_updated = false;
+ // TODO: Use std::string instead of sf::String
+ auto u8 = filter.toUtf8();
+ users_tab_body->filter_search_fuzzy(*(std::string*)&u8);
+ }
+ }
+ };
+
+ Mention mention;
+ mention.users_tab_body = tabs[USERS_TAB_INDEX].body.get();
+
bool frame_skip_text_entry = false;
- chat_input.on_submit_callback = [this, &frame_skip_text_entry, &tabs, &me, &chat_input, &ui_tabs, MESSAGES_TAB_INDEX, &current_room, &new_page, &chat_state, &pending_sent_replies, &currently_operating_on_item, &post_task_queue, &process_reactions](std::string text) mutable {
- if(!current_room)
+ chat_input.on_submit_callback = [this, &frame_skip_text_entry, &mention, &tabs, &me, &chat_input, &ui_tabs, MESSAGES_TAB_INDEX, USERS_TAB_INDEX, &current_room, &new_page, &chat_state, &pending_sent_replies, &currently_operating_on_item, &post_task_queue, &process_reactions](std::string text) mutable {
+ if(mention.visible) {
+ BodyItem *selected_mention_item = tabs[USERS_TAB_INDEX].body->get_selected();
+ if(selected_mention_item) {
+ std::string str_to_append = selected_mention_item->get_description();
+ if(!str_to_append.empty())
+ str_to_append.erase(0, 1);
+ str_to_append += ": ";
+ chat_input.replace(chat_input.get_caret_index() - mention.filter.getSize(), mention.filter.getSize(), sf::String::fromUtf8(str_to_append.begin(), str_to_append.end()));
+ mention.hide();
+ }
return false;
-
+ }
+
frame_skip_text_entry = true;
const int selected_tab = ui_tabs.get_selected();
@@ -4522,21 +4608,6 @@ namespace QuickMedia {
if(!event_data)
return;
-#if 0
- if(event_data->message->user->resolve_state == UserResolveState::NOT_RESOLVED) {
- fetch_message = event_data->message;
- event_data->message->user->resolve_state = UserResolveState::RESOLVING;
- std::string user_id = event_data->message->user->user_id;
- fetch_message_future = [this, &current_room, user_id]() {
- matrix->update_user_with_latest_state(current_room, user_id);
- return FetchMessageResult{FetchMessageType::USER_UPDATE, nullptr};
- };
- return;
- } else if(event_data->message->user->resolve_state == UserResolveState::RESOLVING) {
- return;
- }
-#endif
-
// Fetch replied to message
if(event_data->status == FetchStatus::FINISHED_LOADING && event_data->message) {
if(event_data->message->related_event_id.empty() || (body_item->embedded_item_status != FetchStatus::NONE && body_item->embedded_item_status != FetchStatus::QUEUED_LOADING))
@@ -4594,21 +4665,6 @@ namespace QuickMedia {
if(!message)
return;
-#if 0
- if(message->user->resolve_state == UserResolveState::NOT_RESOLVED) {
- fetch_message = message;
- message->user->resolve_state = UserResolveState::RESOLVING;
- std::string user_id = message->user->user_id;
- fetch_message_future = AsyncTask<FetchMessageResult>([this, &current_room, user_id]() {
- matrix->update_user_with_latest_state(current_room, user_id);
- return FetchMessageResult{FetchMessageType::USER_UPDATE, nullptr};
- });
- return;
- } else if(message->user->resolve_state == UserResolveState::RESOLVING) {
- return;
- }
-#endif
-
if(message_is_timeline(message) && (!last_visible_timeline_message || message->timestamp > last_visible_timeline_message->timestamp))
last_visible_timeline_message = message;
@@ -4890,7 +4946,7 @@ namespace QuickMedia {
}
};
- auto cleanup_tasks = [&set_read_marker_future, &fetch_message_future, &fetch_users_future, &typing_state_queue, &typing_state_thread, &post_task_queue, &provisional_message_queue, &fetched_messages_set, &sent_messages, &pending_sent_replies, &post_thread, &tabs, PINNED_TAB_INDEX]() {
+ auto cleanup_tasks = [&set_read_marker_future, &fetch_message_future, &fetch_users_future, &typing_state_queue, &typing_state_thread, &post_task_queue, &provisional_message_queue, &fetched_messages_set, &sent_messages, &pending_sent_replies, &post_thread, &tabs, MESSAGES_TAB_INDEX, PINNED_TAB_INDEX, USERS_TAB_INDEX]() {
set_read_marker_future.cancel();
fetch_message_future.cancel();
fetch_users_future.cancel();
@@ -4912,15 +4968,102 @@ namespace QuickMedia {
//unreferenced_event_by_room.clear();
if(!tabs.empty()) {
+ tabs[MESSAGES_TAB_INDEX].body->clear_items();
for(auto &body_item : tabs[PINNED_TAB_INDEX].body->items) {
delete (PinnedEventData*)body_item->userdata;
}
tabs[PINNED_TAB_INDEX].body->clear_items();
+ tabs[USERS_TAB_INDEX].body->clear_items();
}
//tabs.clear();
};
+ auto on_add_user_event = [&ui_tabs, &tabs, USERS_TAB_INDEX](MatrixAddUserEvent *event) {
+ // Ignore if the user already exists in the room
+ // TODO: Remove the need for this
+ for(auto &body_item : tabs[USERS_TAB_INDEX].body->items) {
+ if(body_item->url == event->user_info.user_id)
+ return;
+ }
+
+ std::string display_name = event->user_info.display_name.value_or(event->user_info.user_id);
+ size_t insert_position = get_body_item_sorted_insert_position_by_author(tabs[USERS_TAB_INDEX].body->items, display_name, 0);
+
+ auto body_item = BodyItem::create("");
+ body_item->url = event->user_info.user_id;
+ body_item->set_author(std::move(display_name));
+ body_item->set_author_color(user_id_to_color(event->user_info.user_id));
+ body_item->set_description(event->user_info.user_id);
+ body_item->set_description_color(sf::Color(179, 179, 179));
+ if(event->user_info.avatar_url)
+ body_item->thumbnail_url = event->user_info.avatar_url.value();
+ body_item->thumbnail_mask_type = ThumbnailMaskType::CIRCLE;
+ body_item->thumbnail_size = AVATAR_THUMBNAIL_SIZE;
+ tabs[USERS_TAB_INDEX].body->items.insert(tabs[USERS_TAB_INDEX].body->items.begin() + insert_position, std::move(body_item));
+ tabs[USERS_TAB_INDEX].body->items_set_dirty();
+
+ ui_tabs.set_text(USERS_TAB_INDEX, "Users (" + std::to_string(tabs[USERS_TAB_INDEX].body->items.size()) + ")");
+ };
+
+ // TODO: Actually trigger this when a user leaves the room. Also remove the user from the room in the matrix plugin
+ auto on_remove_user_event = [&ui_tabs, &tabs, USERS_TAB_INDEX](MatrixRemoveUserEvent *event) {
+ for(auto it = tabs[USERS_TAB_INDEX].body->items.begin(), end = tabs[USERS_TAB_INDEX].body->items.end(); it != end; ++it) {
+ if((*it)->url == event->user_info.user_id) {
+ tabs[USERS_TAB_INDEX].body->items.erase(it);
+ ui_tabs.set_text(USERS_TAB_INDEX, "Users (" + std::to_string(tabs[USERS_TAB_INDEX].body->items.size()) + ")");
+ return;
+ }
+ }
+ };
+
+ auto on_user_info_event = [&tabs, USERS_TAB_INDEX](MatrixUserInfoEvent *event) {
+ for(auto it = tabs[USERS_TAB_INDEX].body->items.begin(), end = tabs[USERS_TAB_INDEX].body->items.end(); it != end; ++it) {
+ if((*it)->url == event->user_info.user_id) {
+ if(event->user_info.avatar_url)
+ (*it)->thumbnail_url = event->user_info.avatar_url.value();
+
+ if(event->user_info.display_name) {
+ std::string display_name;
+ if(event->user_info.display_name.value().empty())
+ display_name = event->user_info.user_id;
+ else
+ display_name = event->user_info.display_name.value();
+
+ (*it)->set_author(std::move(display_name));
+
+ auto user_body_item = *it;
+ tabs[USERS_TAB_INDEX].body->items.erase(it);
+
+ // TODO: extract_first_line_remove_newline_elipses(room->get_user_display_name(message->user), AUTHOR_MAX_LENGTH),
+ // But that should be done in Text because we need author to be 100% the same as in the input to reorder users
+ size_t insert_position = get_body_item_sorted_insert_position_by_author(tabs[USERS_TAB_INDEX].body->items, user_body_item->get_author(), 0);
+ tabs[USERS_TAB_INDEX].body->items.insert(tabs[USERS_TAB_INDEX].body->items.begin() + insert_position, std::move(user_body_item));
+ tabs[USERS_TAB_INDEX].body->items_set_dirty();
+ }
+
+ return;
+ }
+ }
+ };
+
+ matrix->enable_event_queue(current_room);
+ {
+ auto users_in_room = current_room->get_users();
+ for(auto &user : users_in_room) {
+ std::string display_name = current_room->get_user_display_name(user);
+ std::string avatar_url = current_room->get_user_avatar_url(user);
+
+ MatrixEventUserInfo user_info;
+ user_info.user_id = user->user_id;
+ user_info.display_name = std::move(display_name);
+ user_info.avatar_url = std::move(avatar_url);
+
+ MatrixAddUserEvent add_user_event(std::move(user_info));
+ on_add_user_event(&add_user_event);
+ }
+ }
+
// TODO: Remove this once synapse bug has been resolved where /sync does not include user info for new messages when using message filter that limits number of messages for initial sync,
// and then only call this when viewing the users tab for the first time.
// Note that this is not needed when new users join the room, as those will be included in the sync timeline (with membership events)
@@ -4959,6 +5102,9 @@ namespace QuickMedia {
tabs[i].body->on_top_reached = on_top_reached;
}
+ const float body_padding_horizontal = 10.0f;
+ const float body_padding_vertical = 10.0f;
+
while (current_page == PageType::CHAT && window.isOpen() && !move_room) {
sf::Int32 frame_time_ms = frame_timer.restart().asMilliseconds();
while (window.pollEvent(event)) {
@@ -4977,8 +5123,12 @@ namespace QuickMedia {
base_event_handler(event, PageType::EXIT, tabs[selected_tab].body.get(), nullptr, false, false);
event_idle_handler(event);
- if(!frame_skip_text_entry)
- chat_input.process_event(event);
+ if(!frame_skip_text_entry) {
+ if(!mention.visible || event.type != sf::Event::KeyPressed || (event.key.code != sf::Keyboard::Up && event.key.code != sf::Keyboard::Down && event.key.code != sf::Keyboard::Left && event.key.code != sf::Keyboard::Right))
+ chat_input.process_event(event);
+ if(chat_input.is_editable())
+ mention.handle_event(event);
+ }
if(draw_room_list) {
if(room_tabs[room_selected_tab].body->on_event(window, event, false))
@@ -5150,7 +5300,7 @@ namespace QuickMedia {
}
}
- if(event.key.control && event.key.code == sf::Keyboard::D) {
+ if(event.key.control && event.key.code == sf::Keyboard::D && !chat_input.is_editable()) {
frame_skip_text_entry = true;
BodyItem *selected = tabs[selected_tab].body->get_selected();
if(selected) {
@@ -5203,14 +5353,18 @@ namespace QuickMedia {
typing = true;
}
} else if(event.type == sf::Event::KeyPressed && event.key.code == sf::Keyboard::Escape) {
- chat_input.set_editable(false);
- chat_input.set_text("");
- chat_state = ChatState::NAVIGATING;
- currently_operating_on_item = nullptr;
- if(typing && current_room) {
- fprintf(stderr, "Stopped typing\n");
- typing = false;
- typing_state_queue.push(false);
+ if(mention.visible) {
+ mention.hide();
+ } else {
+ chat_input.set_editable(false);
+ chat_input.set_text("");
+ chat_state = ChatState::NAVIGATING;
+ currently_operating_on_item = nullptr;
+ if(typing && current_room) {
+ fprintf(stderr, "Stopped typing\n");
+ typing = false;
+ typing_state_queue.push(false);
+ }
}
}
}
@@ -5219,6 +5373,8 @@ namespace QuickMedia {
update_idle_state();
handle_window_close();
+ mention.update();
+
matrix_chat_page->update();
switch(new_page) {
@@ -5251,6 +5407,7 @@ namespace QuickMedia {
break;
}
case PageType::CHAT_LOGIN: {
+ matrix->disable_event_queue();
previous_messages_future.cancel();
cleanup_tasks();
tabs.clear();
@@ -5308,11 +5465,11 @@ namespace QuickMedia {
const int selected_tab = ui_tabs.get_selected();
float room_name_padding_y = 0.0f;
- if(selected_tab == MESSAGES_TAB_INDEX || selected_tab == PINNED_TAB_INDEX)
+ if(selected_tab == MESSAGES_TAB_INDEX || selected_tab == PINNED_TAB_INDEX || selected_tab == USERS_TAB_INDEX)
room_name_padding_y = room_name_total_height;
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)
+ if(selected_tab != MESSAGES_TAB_INDEX)
chat_input_height_full = 0.0f;
const float chat_height = chat_input.get_height();
@@ -5324,14 +5481,12 @@ namespace QuickMedia {
if(redraw) {
redraw = false;
- if(selected_tab == MESSAGES_TAB_INDEX || selected_tab == PINNED_TAB_INDEX) {
+ if(selected_tab == MESSAGES_TAB_INDEX || selected_tab == PINNED_TAB_INDEX || selected_tab == USERS_TAB_INDEX) {
tab_vertical_offset = std::floor(10.0f * get_ui_scale());
}
tab_shade_height = std::floor(tab_vertical_offset) + Tabs::get_height() + room_name_padding_y;
- float body_padding_horizontal = 10.0f;
- float body_padding_vertical = std::floor(10.0f);
float body_width = window_size.x - body_padding_horizontal * 2.0f;
/*if(body_width <= 480.0f) {
body_width = window_size.x;
@@ -5377,6 +5532,23 @@ namespace QuickMedia {
}
}
+ std::unique_ptr<MatrixEvent> matrix_event;
+ while((matrix_event = matrix->pop_event()) != nullptr) {
+ if(matrix_event) {
+ switch(matrix_event->type) {
+ case MatrixEvent::Type::ADD_USER:
+ on_add_user_event(static_cast<MatrixAddUserEvent*>(matrix_event.get()));
+ break;
+ case MatrixEvent::Type::REMOVE_USER:
+ on_remove_user_event(static_cast<MatrixRemoveUserEvent*>(matrix_event.get()));
+ break;
+ case MatrixEvent::Type::USER_INFO:
+ on_user_info_event(static_cast<MatrixUserInfoEvent*>(matrix_event.get()));
+ break;
+ }
+ }
+ }
+
sync_data.messages.clear();
sync_data.pinned_events = std::nullopt;
matrix->get_room_sync_data(current_room, sync_data);
@@ -5462,15 +5634,27 @@ namespace QuickMedia {
window.clear(back_color);
- if(chat_state == ChatState::URL_SELECTION)
+ if(chat_state == ChatState::URL_SELECTION) {
url_selection_body.draw(window, body_pos, body_size);
- else
+ } else {
tabs[selected_tab].body->draw(window, body_pos, body_size);
+ if(selected_tab == MESSAGES_TAB_INDEX && mention.visible) {
+ const float user_mention_body_height = std::floor(300.0f * get_ui_scale());
+ sf::RectangleShape user_mention_background(sf::Vector2f(body_size.x + body_padding_vertical*2.0f, user_mention_body_height));
+ user_mention_background.setPosition(sf::Vector2f(body_pos.x - body_padding_vertical, body_pos.y + body_size.y - user_mention_body_height));
+ user_mention_background.setFillColor(sf::Color(33, 37, 44));
+
+ window.draw(user_mention_background);
+ tabs[USERS_TAB_INDEX].body->draw(window,
+ user_mention_background.getPosition() + sf::Vector2f(body_padding_vertical, body_padding_vertical),
+ user_mention_background.getSize() - sf::Vector2f(body_padding_vertical*2.0f, body_padding_vertical));
+ }
+ }
//tab_shade.setSize(sf::Vector2f(window_size.x, tab_shade_height));
//window.draw(tab_shade);
- if(selected_tab == MESSAGES_TAB_INDEX || selected_tab == PINNED_TAB_INDEX) {
+ if(selected_tab == MESSAGES_TAB_INDEX || selected_tab == PINNED_TAB_INDEX || selected_tab == USERS_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();
@@ -5608,6 +5792,13 @@ namespace QuickMedia {
if(matrix && !matrix->is_initial_sync_finished()) {
std::string err_msg;
if(matrix->did_initial_sync_fail(err_msg)) {
+ matrix->disable_event_queue();
+ previous_messages_future.cancel();
+ cleanup_tasks();
+ tabs.clear();
+ unreferenced_events.clear();
+ unresolved_reactions.clear();
+ all_messages.clear();
show_notification("QuickMedia", "Initial matrix sync failed, error: " + err_msg, Urgency::CRITICAL);
matrix->logout();
current_page = PageType::CHAT_LOGIN;
@@ -5631,6 +5822,13 @@ namespace QuickMedia {
while(!matrix->is_initial_sync_finished()) {
std::this_thread::sleep_for(std::chrono::milliseconds(10));
if(matrix->did_initial_sync_fail(err_msg)) {
+ matrix->disable_event_queue();
+ previous_messages_future.cancel();
+ cleanup_tasks();
+ tabs.clear();
+ unreferenced_events.clear();
+ unresolved_reactions.clear();
+ all_messages.clear();
show_notification("QuickMedia", "Initial matrix sync failed, error: " + err_msg, Urgency::CRITICAL);
matrix->logout();
current_page = PageType::CHAT_LOGIN;
@@ -5675,6 +5873,7 @@ namespace QuickMedia {
}
chat_page_end:
+ matrix->disable_event_queue();
previous_messages_future.cancel();
cleanup_tasks();
window.setTitle("QuickMedia - matrix");