#include "../include/ChatWindow.hpp" #include "../include/ChatMessage.hpp" #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() : channelCount(0), lastMessage(nullptr) { 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); double fontSize = 18.5;//PANGO_PIXELS(chatInput.get_style_context()->get_font().get_size()); chatPrevNumLines = 1; 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); } }); chatScrollWindow->add(chatInput); } void ChatWindow::addChannel(const odhtdb::Hash &nodeHash) { assert(channelDataById.find(nodeHash) == channelDataById.end()); fprintf(stderr, "Added channel %s\n", nodeHash.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 + channelCount, 1, 1); ++channelCount; channelDataById[nodeHash] = { channelButton, 0 }; } void ChatWindow::addLocalMessage(const odhtdb::Hash &channelId, const odhtdb::Signature::PublicKey &userPublicKey, uint32_t timestampSeconds, Glib::ustring msg) { auto it = channelDataById.find(channelId); assert(it != channelDataById.end()); User *user = getUserByPublicKey(userPublicKey); if(!user) { fprintf(stderr, "Unable to add message to channel because the user %s doesn't exist in it\n", userPublicKey.toString().c_str()); return; } if(lastMessage && lastMessage->user->publicKey == userPublicKey) { int64_t msgTimeDiff = (int64_t)timestampSeconds - (int64_t)lastMessage->timestampSeconds; if(msgTimeDiff <= MERGE_MESSAGE_TIMESTAMP_DIFF_SEC) { lastMessage->text.set_text(lastMessage->text.get_text() + "\n" + msg); return; } } ChatMessage *message = Gtk::manage(new ChatMessage(user->name, msg, timestampSeconds, user)); lastMessage = message; message->set_valign(Gtk::Align::ALIGN_START); message->set_hexpand(true); message->show_all(); messageAreaLayout.attach(*message, 0, it->second.messageCount, 1, 1); ++it->second.messageCount; } void ChatWindow::addUser(const odhtdb::Signature::PublicKey &userPublicKey) { User *existingUser = getUserByPublicKey(userPublicKey); if(existingUser) { fprintf(stderr, "ChatWindow::addUser: user %s was not added because the user has already been added\n", userPublicKey.toString().c_str()); return; } 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"); leftPanelUsers.attach(*username, 0, 1 + users.size(), 1, 1); users.push_back(new User { userPublicKey, "NoName" }); fprintf(stderr, "Added user %s\n", userPublicKey.toString().c_str()); } void ChatWindow::setUserNickname(const odhtdb::Signature::PublicKey &userPublicKey, const Glib::ustring &name) { for(size_t i = 0; i < users.size(); ++i) { User *user = users[i]; if(user->publicKey == userPublicKey) { user->name = name; Gtk::Widget *usernameLabel = leftPanelUsers.get_child_at(0, 1 + i); assert(usernameLabel); //assert(usernameLabel->get_type() == Gtk::Label::get_type()); static_cast(usernameLabel)->set_text(name); fprintf(stderr, "Set nickname for user %s to %s\n", userPublicKey.toString().c_str(), name.c_str()); return; } } fprintf(stderr, "ChatWindow::setUserNickname: user %s doesn't exist\n", userPublicKey.toString().c_str()); } User* ChatWindow::getUserByPublicKey(const odhtdb::Signature::PublicKey &publicKey) const { for(User *user : users) { if(user->publicKey == publicKey) { return user; } } return nullptr; } }