#include "../include/ChatWindow.hpp" #include "../include/ChatMessage.hpp" #include #include #include #include #include namespace dchat { // Merge all messages that are written by the same user without interrupt within a timeframe const int MERGE_MESSAGE_TIMESTAMP_DIFF_SEC = 60; ChatWindow::ChatWindow() : roomCount(0) { setupTopBar(); Gtk::Paned *sidePanels = Gtk::manage(new Gtk::Paned()); sidePanels->set_name("side-panels"); sidePanels->set_border_width(0); attach(*sidePanels, 0, 1, 1, 2); setupLeftPanel(sidePanels); Gtk::Grid *rightPanel = Gtk::manage(new Gtk::Grid()); rightPanel->set_hexpand(true); sidePanels->add2(*rightPanel); setupMessageArea(rightPanel); setupChatInput(rightPanel); set_vexpand(true); set_hexpand(true); } void ChatWindow::setupTopBar() { topbar.set_name("top-bar"); topbar.set_hexpand(true); attach(topbar, 0, 0, 2, 1); topbarSearchBar.set_size_request(175); topbarSearchBar.set_name("top-bar-search"); topbarSearchBar.set_placeholder_text("Search..."); topbar.attach(topbarSearchBar, 0, 0, 1, 1); Gtk::Alignment *topbarSpacer = Gtk::manage(new Gtk::Alignment()); topbarSpacer->set_size_request(50); topbar.attach_next_to(*topbarSpacer, topbarSearchBar, Gtk::POS_RIGHT, 1, 1); currentChannelTitle.set_text("Linux"); currentChannelTitle.set_name("current-channel-title"); topbar.attach_next_to(currentChannelTitle, *topbarSpacer, Gtk::POS_RIGHT, 1, 1); } void ChatWindow::setupLeftPanel(Gtk::Paned *sidePanels) { Gtk::Paned *leftPanel = Gtk::manage(new Gtk::Paned(Gtk::ORIENTATION_VERTICAL)); leftPanel->set_size_request(200); leftPanel->set_name("left-panel"); leftPanel->set_vexpand(true); sidePanels->add1(*leftPanel); leftPanelChannels.set_vexpand(true); leftPanel->add1(leftPanelChannels); Gtk::Label *channelsTitle = Gtk::manage(new Gtk::Label()); channelsTitle->set_name("channels-title"); channelsTitle->set_text("Channels"); channelsTitle->set_halign(Gtk::ALIGN_START); leftPanelChannels.attach(*channelsTitle, 0, 0, 1, 1); //// leftPanelUsers.set_vexpand(true); leftPanel->add2(leftPanelUsers); Gtk::Label *usersTitle = Gtk::manage(new Gtk::Label()); usersTitle->set_name("users-title"); usersTitle->set_text("Users"); usersTitle->set_halign(Gtk::ALIGN_START); leftPanelUsers.attach(*usersTitle, 0, 0, 1, 1); } void ChatWindow::setupMessageArea(Gtk::Grid *rightPanel) { //messageArea.set_valign(Gtk::ALIGN_START); messageArea.set_vexpand(true); messageArea.set_policy(Gtk::PolicyType::POLICY_NEVER, Gtk::PolicyType::POLICY_AUTOMATIC); rightPanel->attach(messageArea, 0, 0, 1, 2); messageAreaLayout.set_name("chat-area-layout"); messageArea.add(messageAreaLayout); } void ChatWindow::setupChatInput(Gtk::Grid *rightPanel) { Gtk::Grid *chatArea = Gtk::manage(new Gtk::Grid()); rightPanel->attach_next_to(*chatArea, messageArea, Gtk::POS_BOTTOM, 1, 1); Gtk::ScrolledWindow *chatScrollWindow = Gtk::manage(new Gtk::ScrolledWindow()); chatScrollWindow->set_hexpand(true); chatScrollWindow->set_policy(Gtk::PolicyType::POLICY_NEVER, Gtk::PolicyType::POLICY_NEVER); chatScrollWindow->set_name("chat-scroll-view"); chatArea->attach(*chatScrollWindow, 0, 0, 1, 1); chatInput.set_hexpand(true); chatInput.set_name("chat-input"); chatInput.set_wrap_mode(Gtk::WrapMode::WRAP_WORD_CHAR); chatScrollWindow->add(chatInput); double fontSize = 18.5;//PANGO_PIXELS(chatInput.get_style_context()->get_font().get_size()); chatPrevNumLines = 1; chatInput.signal_key_press_event().connect([this](GdkEventKey *event) { if((event->keyval == GDK_KEY_Return || event->keyval == GDK_KEY_KP_Enter) && !(event->state & Gdk::SHIFT_MASK)) { currentRoom->publishMessage(chatInput.get_buffer()->get_text()); chatInput.get_buffer()->set_text(""); return true; } return false; }, false); chatInput.get_buffer()->signal_changed().connect([this, chatScrollWindow, fontSize] { int numLines = chatInput.get_buffer()->get_line_count(); numLines = std::min(numLines, 10); if(numLines != chatPrevNumLines) { chatPrevNumLines = numLines; chatScrollWindow->set_min_content_height(fontSize * numLines); auto adj = chatScrollWindow->get_vadjustment(); if(chatInput.get_buffer()->get_line_count() <= 10) { chatScrollWindow->set_policy(Gtk::PolicyType::POLICY_NEVER, Gtk::PolicyType::POLICY_NEVER); adj->set_value(0); } else { chatScrollWindow->set_policy(Gtk::PolicyType::POLICY_NEVER, Gtk::PolicyType::POLICY_ALWAYS); } } }); chatScrollWindow->get_vadjustment()->signal_value_changed().connect([this, chatScrollWindow]() { auto adj = chatScrollWindow->get_vadjustment(); if(chatInput.get_buffer()->get_line_count() <= 11) { chatScrollWindow->set_policy(Gtk::PolicyType::POLICY_NEVER, Gtk::PolicyType::POLICY_NEVER); adj->set_value(0); } else { chatScrollWindow->set_policy(Gtk::PolicyType::POLICY_NEVER, Gtk::PolicyType::POLICY_ALWAYS); } }); } void ChatWindow::addRoom(std::shared_ptr room) { fprintf(stderr, "Added channel %s\n", room->id->toString().c_str()); Gtk::ToggleButton *channelButton = Gtk::manage(new Gtk::ToggleButton("Channel name")); channelButton->set_active(true); channelButton->get_style_context()->add_class("channel-button"); channelButton->set_hexpand(true); channelButton->get_child()->set_halign(Gtk::ALIGN_START); channelButton->show(); leftPanelChannels.attach(*channelButton, 0, 1 + roomCount, 1, 1); ++roomCount; roomDataById[*room->id] = { channelButton }; currentRoom = room; } void ChatWindow::addMessage(const RoomAddMessageRequest &request) { auto roomMessages = request.room->messages; RoomMessage *lastMessage = nullptr; if(!roomMessages.empty()) lastMessage = &roomMessages.back(); if(lastMessage && lastMessage->creator->publicKey == request.message.creator->publicKey) { int64_t msgTimeDiff = (int64_t)request.message.timestampSeconds - (int64_t)lastMessage->timestampSeconds; if(msgTimeDiff <= MERGE_MESSAGE_TIMESTAMP_DIFF_SEC) { auto message = messageById[lastMessage->id]; message->text.set_text(message->text.get_text() + "\n" + request.message.text); // Since messages that are sent withing a timeframe are combined, several message ids can refer to the same message messageById[request.message.id] = message; if(*request.room->id == *currentRoom->id) { auto adj = messageArea.get_vadjustment(); adj->set_value(adj->get_upper()); messageAreaLayout.queue_draw(); while(gtk_events_pending()) gtk_main_iteration_do(FALSE); } return; } } ChatMessage *message = Gtk::manage(new ChatMessage(request.message.creator->nickname, request.message.text, request.message.timestampSeconds)); message->set_valign(Gtk::Align::ALIGN_START); message->set_hexpand(true); message->show_all(); messageById[request.message.id] = message; messageAreaLayout.attach(*message, 0, roomMessages.size(), 1, 1); // TODO: When we get a message in the current room we scroll to the bottom, but this should only be done if we are not manually scrolling to view old messages if(*request.room->id == *currentRoom->id) { auto adj = messageArea.get_vadjustment(); adj->set_value(adj->get_upper()); messageAreaLayout.queue_draw(); while(gtk_events_pending()) gtk_main_iteration_do(FALSE); } } void ChatWindow::addUser(std::shared_ptr room, std::shared_ptr user) { Gtk::Label *username = Gtk::manage(new Gtk::Label("NoName")); username->set_halign(Gtk::ALIGN_START); username->show(); username->get_style_context()->add_class("username-list-username"); user->userdata = username; leftPanelUsers.attach(*username, 0, room->userByPublicKey.size(), 1, 1); fprintf(stderr, "Added user %s\n", user->publicKey.toString().c_str()); } void ChatWindow::setUserNickname(const UserChangeNicknameRequest &request) { Gtk::Label *userNicknameLabel = (Gtk::Label*)request.user->userdata; userNicknameLabel->set_text(request.newNickname); fprintf(stderr, "Set nickname for user %s to %s\n", request.user->publicKey.toString().c_str(), request.newNickname.c_str()); } void ChatWindow::scrollToBottom() { auto adj = messageArea.get_vadjustment(); adj->set_value(adj->get_upper()); } }