From 5fab9a3a2cf048330f687dda48c76c95a3a67d98 Mon Sep 17 00:00:00 2001 From: dec05eba Date: Fri, 9 Nov 2018 09:46:43 +0100 Subject: Add room joining --- css/style.css | 25 ++- depends/dchat_core | 2 +- images/messages_icon.png | Bin 0 -> 817 bytes include/ChatWindow.hpp | 25 ++- include/InputDialog.hpp | 6 +- include/RoomNotificationsWindow.hpp | 36 +++++ include/RoomSettingsWindow.hpp | 30 ++++ include/Topbar.hpp | 22 +++ run.sh | 3 +- src/ChatMessage.cpp | 2 +- src/ChatWindow.cpp | 306 +++++++++++++++++++++++++++--------- src/InputDialog.cpp | 38 +++-- src/RoomNotificationsWindow.cpp | 105 +++++++++++++ src/RoomSettingsWindow.cpp | 88 +++++++++++ src/Topbar.cpp | 44 ++++++ src/Window.cpp | 16 +- 16 files changed, 638 insertions(+), 110 deletions(-) create mode 100644 images/messages_icon.png create mode 100644 include/RoomNotificationsWindow.hpp create mode 100644 include/RoomSettingsWindow.hpp create mode 100644 include/Topbar.hpp create mode 100644 src/RoomNotificationsWindow.cpp create mode 100644 src/RoomSettingsWindow.cpp create mode 100644 src/Topbar.cpp diff --git a/css/style.css b/css/style.css index b48451f..1c34440 100644 --- a/css/style.css +++ b/css/style.css @@ -68,6 +68,11 @@ separator { background-size: 0px; } +treeview { + background-color: #36393e; + color: #f7f7f7; +} + .separator-horizontal-margin { margin-top: 10px; margin-bottom: 10px; @@ -91,7 +96,12 @@ separator { border: 1px solid black; } -#side-panels separator { +.side-panels { + border: none; + outline: none; +} + +.side-panels separator { border: 1px solid #2f3136; background-color: #2f3136; } @@ -123,14 +133,15 @@ separator { font-weight: bold; } -#left-panel { +.left-panel { background-color: #36393e; padding: 10px 10px 10px 10px; border: 1px solid #36393e; } -#left-panel separator { +.left-panel separator { border: 1px solid #36393e; + border-top: 3px solid #444444; background-color: #36393e; } @@ -233,6 +244,14 @@ textview text { font-size: 14px; } +.chat-input-focused text { + color: #f7f7f7; +} + +.chat-input-unfocused text { + color: #a7a7a7; +} + .window-notification { background-color: #aa3030; } diff --git a/depends/dchat_core b/depends/dchat_core index fff3205..b37a6fa 160000 --- a/depends/dchat_core +++ b/depends/dchat_core @@ -1 +1 @@ -Subproject commit fff32050deb68c10118afd98e1f0f45cfe289934 +Subproject commit b37a6fa4c2fb73376b2be701127b6dbf7f7bdb83 diff --git a/images/messages_icon.png b/images/messages_icon.png new file mode 100644 index 0000000..24337f4 Binary files /dev/null and b/images/messages_icon.png differ diff --git a/include/ChatWindow.hpp b/include/ChatWindow.hpp index d457b7d..48bbff5 100644 --- a/include/ChatWindow.hpp +++ b/include/ChatWindow.hpp @@ -1,6 +1,8 @@ #pragma once #include "ImageButton.hpp" +#include "RoomSettingsWindow.hpp" +#include "RoomNotificationsWindow.hpp" #include #include #include @@ -17,6 +19,7 @@ namespace dchat { class ChatMessage; class Window; + class Topbar; class ChatWindow : public Gtk::Grid { @@ -25,24 +28,31 @@ namespace dchat ~ChatWindow(); void addRoom(std::shared_ptr room); void addMessage(const RoomAddMessageRequest &request); - void addUser(std::shared_ptr room, std::shared_ptr user); + void addUser(const RoomAddUserRequest &request); void setUserNickname(const UserChangeNicknameRequest &request); void changeRoomName(const RoomChangeNameRequest &request); + void addInviteRequest(const InviteUserRequest &request); void scrollToBottom(); + + Topbar *topbar; + Gtk::Stack stack; + Gtk::Grid chatPage; + RoomSettingsWindow roomSettingsWindow; + RoomNotificationsWindow roomNotificationsWindow; + Window *window; private: - void setupTopBar(); + void setupTopbar(); void setupLeftPanel(Gtk::Paned *sidePanels); void setupMessageArea(Gtk::Grid *rightPanel); void setupChatInput(Gtk::Grid *rightPanel); void setCurrentRoom(std::shared_ptr room); private: - Gtk::Grid topbar; - Gtk::Entry topbarSearchBar; Gtk::Grid leftPanelChannels; Gtk::Stack leftPanelUsersStack; - ImageButton addRoomButton; - Gtk::Label currentChannelTitle; + ImageButton createRoomButton; + ImageButton joinRoomButton; + ImageButton userSettingsButton; Gtk::ScrolledWindow messageArea; Gtk::Stack messageAreaStack; Gtk::TextView chatInput; @@ -60,6 +70,7 @@ namespace dchat int roomCount; RoomData *currentRoomData; std::shared_ptr currentRoom; - Window *window; + bool chatInputShowPlaceholder; + bool chatInputChangeByPlaceholder; }; } \ No newline at end of file diff --git a/include/InputDialog.hpp b/include/InputDialog.hpp index 4b2b02a..ae06de2 100644 --- a/include/InputDialog.hpp +++ b/include/InputDialog.hpp @@ -2,15 +2,15 @@ #include #include +#include namespace dchat { class InputDialog : public Gtk::Dialog { public: - InputDialog(const char *title, const char *text, const char *acceptText = "Create", const char *cancelText = "Cancel"); - Glib::ustring getInput() const; + InputDialog(const char *title, const std::vector &texts, const char *acceptText = "Create", const char *cancelText = "Cancel"); - Gtk::Entry entry; + std::vector entries; }; } \ No newline at end of file diff --git a/include/RoomNotificationsWindow.hpp b/include/RoomNotificationsWindow.hpp new file mode 100644 index 0000000..e4e29f5 --- /dev/null +++ b/include/RoomNotificationsWindow.hpp @@ -0,0 +1,36 @@ +#pragma once + +#include +#include +#include +#include +#include + +namespace dchat +{ + class ChatWindow; + + class RoomNotificationsWindow : public Gtk::Stack + { + public: + RoomNotificationsWindow(ChatWindow *chatWindow); + + void addInviteRequest(const InviteUserRequest &request); + private: + class RoomNotifications : public Gtk::ScrolledWindow + { + public: + Glib::RefPtr listStore; + Gtk::TreeModelColumn userPublicKeyColumn; + Gtk::TreeModelColumn messageColumn; + Gtk::TreeModel::ColumnRecord columns; + std::unordered_map inviteRequests; + }; + + RoomNotifications* createRoomNotifications(); + + std::unordered_map roomNotificationsMap; + + ChatWindow *chatWindow; + }; +} \ No newline at end of file diff --git a/include/RoomSettingsWindow.hpp b/include/RoomSettingsWindow.hpp new file mode 100644 index 0000000..9ffd808 --- /dev/null +++ b/include/RoomSettingsWindow.hpp @@ -0,0 +1,30 @@ +#pragma once + +#include +#include +#include +#include + +namespace Gtk +{ + class Paned; +} + +namespace dchat +{ + class ChatWindow; + + class RoomSettingsWindow : public Gtk::Grid + { + public: + RoomSettingsWindow(ChatWindow *chatWindow); + void selectRoom(std::shared_ptr room); + + void setupLeftPanel(Gtk::Paned *sidePanels); + void setupRightPanel(Gtk::Paned *sidePanels); + private: + ChatWindow *chatWindow; + Gtk::Entry roomNameEntry; + Gtk::Label inviteKey; + }; +} \ No newline at end of file diff --git a/include/Topbar.hpp b/include/Topbar.hpp new file mode 100644 index 0000000..ebb7729 --- /dev/null +++ b/include/Topbar.hpp @@ -0,0 +1,22 @@ +#pragma once + +#include "ImageButton.hpp" +#include +#include +#include + +namespace dchat +{ + class Topbar : public Gtk::Grid + { + public: + Topbar(); + void setTitle(const Glib::ustring &title); + + ImageButton roomSettingsButton; + ImageButton roomNotificationsButton; + private: + Gtk::SearchEntry topbarSearchBar; + Gtk::Label currentRoomTitle; + }; +} \ No newline at end of file diff --git a/run.sh b/run.sh index 542f03f..b78f4e8 100755 --- a/run.sh +++ b/run.sh @@ -13,4 +13,5 @@ if [ ! -f ~/.local/share/fonts/Lato-Bold.ttf ]; then cp ./fonts/Lato-Bold.ttf ~/.local/share/fonts/Lato-Bold.ttf fc-cache fi -env GTK_THEME="css/style.css" ./sibs-build/debug/dchat +platform=`sibs platform` +env GTK_THEME="css/style.css" ./sibs-build/$platform/debug/dchat diff --git a/src/ChatMessage.cpp b/src/ChatMessage.cpp index fe3afdc..98d7f88 100644 --- a/src/ChatMessage.cpp +++ b/src/ChatMessage.cpp @@ -10,7 +10,7 @@ namespace dchat avatar.set_halign(Gtk::ALIGN_START); avatar.set_valign(Gtk::ALIGN_START); avatar.set_size_request(50, 50); - avatar.url = "https://discordemoji.com/assets/emoji/7752_PepePOOGERSFAST.gif"; + //avatar.url = "https://discordemoji.com/assets/emoji/7752_PepePOOGERSFAST.gif"; username.set_selectable(true); username.set_alignment(Gtk::ALIGN_START, Gtk::ALIGN_START); diff --git a/src/ChatWindow.cpp b/src/ChatWindow.cpp index a991e57..473b5d1 100644 --- a/src/ChatWindow.cpp +++ b/src/ChatWindow.cpp @@ -2,10 +2,12 @@ #include "../include/ChatMessage.hpp" #include "../include/InputDialog.hpp" #include "../include/Window.hpp" +#include "../include/Topbar.hpp" #include #include #include #include +#include #include namespace dchat @@ -14,20 +16,33 @@ namespace dchat const int MERGE_MESSAGE_TIMESTAMP_DIFF_SEC = 60; ChatWindow::ChatWindow(Window *_window) : - addRoomButton("images/add_button_small.png", " Add room"), + roomSettingsWindow(this), + roomNotificationsWindow(this), + createRoomButton("images/add_button_small.png", " Create room"), + joinRoomButton("images/add_button_small.png", " Join room"), + userSettingsButton("images/settings-icon.png", " User settings"), roomCount(0), currentRoomData(nullptr), - window(_window) + window(_window), + chatInputShowPlaceholder(true), + chatInputChangeByPlaceholder(false) { assert(window); + stack.set_homogeneous(false); + stack.set_transition_type(Gtk::StackTransitionType::STACK_TRANSITION_TYPE_SLIDE_LEFT_RIGHT); + stack.set_transition_duration(250); leftPanelUsersStack.set_homogeneous(false); messageAreaStack.set_homogeneous(false); - setupTopBar(); + setupTopbar(); + attach(stack, 0, 1, 2, 2); - Gtk::Paned *sidePanels = Gtk::manage(new Gtk::Paned()); - sidePanels->set_name("side-panels"); - sidePanels->set_border_width(0); - attach(*sidePanels, 0, 1, 1, 2); + stack.add(chatPage, "chat"); + stack.add(roomSettingsWindow, "settings"); + stack.add(roomNotificationsWindow, "notifications"); + + Gtk::Paned *sidePanels = Gtk::manage(new Gtk::Paned(Gtk::ORIENTATION_HORIZONTAL)); + sidePanels->get_style_context()->add_class("side-panels"); + chatPage.attach(*sidePanels, 0, 1, 1, 2); setupLeftPanel(sidePanels); @@ -41,6 +56,9 @@ namespace dchat set_vexpand(true); set_hexpand(true); + chatPage.show_all(); + stack.show(); + stack.set_visible_child("chat"); } ChatWindow::~ChatWindow() @@ -54,37 +72,48 @@ namespace dchat } } - void ChatWindow::setupTopBar() + void ChatWindow::setupTopbar() { - topbar.set_name("top-bar"); - topbar.set_hexpand(true); - attach(topbar, 0, 0, 2, 1); - - Gtk::Grid *topbarLeft = Gtk::manage(new Gtk::Grid()); - topbarLeft->set_name("top-bar-left"); - topbarLeft->set_size_request(180); - topbarLeft->set_valign(Gtk::ALIGN_CENTER); - topbarLeft->set_halign(Gtk::ALIGN_CENTER); - topbar.attach(*topbarLeft, 0, 0, 1, 1); - - topbarSearchBar.set_name("top-bar-search"); - topbarSearchBar.set_placeholder_text("Search..."); - topbarSearchBar.set_size_request(180); - topbarLeft->attach(topbarSearchBar, 0, 0, 1, 1); - - Gtk::Grid *topbarRight = Gtk::manage(new Gtk::Grid()); - topbarRight->set_name("top-bar-right"); - topbarRight->set_hexpand(true); - topbarRight->set_valign(Gtk::ALIGN_CENTER); - topbar.attach_next_to(*topbarRight, *topbarLeft, Gtk::POS_RIGHT, 1, 1); - - currentChannelTitle.set_name("current-room-title"); - currentChannelTitle.set_hexpand(true); - currentChannelTitle.set_alignment(Gtk::ALIGN_START, Gtk::ALIGN_CENTER); - topbarRight->attach(currentChannelTitle, 0, 0, 1, 1); - - ImageButton *channelSettings = Gtk::manage(new ImageButton("images/settings-icon.png", nullptr)); - topbarRight->attach_next_to(*channelSettings, currentChannelTitle, Gtk::POS_RIGHT, 1, 1); + topbar = Gtk::manage(new Topbar()); + topbar->show_all(); + attach(*topbar, 0, 0, 2, 1); + + topbar->roomSettingsButton.signal_clicked().connect([this] + { + if(!currentRoom) + { + window->windowNotification->show("You need to be inside a room to go to settings"); + return; + } + + if(!currentRoom->localUser) + { + window->windowNotification->show("You need to be a member of the room to go to room settings"); + return; + } + + roomSettingsWindow.show_all(); + roomSettingsWindow.selectRoom(currentRoom); + stack.set_visible_child("settings"); + }); + + topbar->roomNotificationsButton.signal_clicked().connect([this] + { + if(!currentRoom) + { + window->windowNotification->show("You need to be inside a room to go to room notifications"); + return; + } + + if(!currentRoom->localUser) + { + window->windowNotification->show("You need to be a member of the room to go to room notifications"); + return; + } + + roomNotificationsWindow.show_all(); + stack.set_visible_child("notifications"); + }); } void ChatWindow::setupLeftPanel(Gtk::Paned *sidePanels) @@ -92,53 +121,110 @@ namespace dchat Gtk::Grid *leftPanelLayout = Gtk::manage(new Gtk::Grid()); leftPanelLayout->set_vexpand(true); leftPanelLayout->set_size_request(200); - leftPanelLayout->set_name("left-panel"); + leftPanelLayout->get_style_context()->add_class("left-panel"); sidePanels->add1(*leftPanelLayout); Gtk::Paned *leftPanel = Gtk::manage(new Gtk::Paned(Gtk::ORIENTATION_VERTICAL)); leftPanel->set_vexpand(true); leftPanelLayout->attach(*leftPanel, 0, 0, 1, 2); - leftPanelChannels.set_vexpand(true); - leftPanel->add1(leftPanelChannels); + Gtk::Grid *channelsLayout = Gtk::manage(new Gtk::Grid()); + channelsLayout->set_orientation(Gtk::ORIENTATION_VERTICAL); + channelsLayout->set_vexpand(true); + channelsLayout->set_hexpand(true); + leftPanel->add1(*channelsLayout); 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); + channelsLayout->attach(*channelsTitle, 0, 0, 1, 1); + + Gtk::ScrolledWindow *channelsScrollWindow = Gtk::manage(new Gtk::ScrolledWindow()); + channelsScrollWindow->set_vexpand(true); + channelsScrollWindow->set_hexpand(true); + channelsScrollWindow->set_overlay_scrolling(false); + channelsLayout->attach_next_to(*channelsScrollWindow, *channelsTitle, Gtk::POS_BOTTOM, 1, 2); + + leftPanelChannels.set_vexpand(true); + leftPanelChannels.set_hexpand(true); + channelsScrollWindow->add(leftPanelChannels); //// - leftPanelUsersStack.set_vexpand(true); - leftPanel->add2(leftPanelUsersStack); + Gtk::Grid *userLayout = Gtk::manage(new Gtk::Grid()); + leftPanel->add2(*userLayout); - addRoomButton.set_halign(Gtk::ALIGN_START); - leftPanelLayout->attach_next_to(addRoomButton, *leftPanel, Gtk::POS_BOTTOM, 1, 1); - addRoomButton.signal_clicked().connect([this]() + Gtk::Label *usersTitle = Gtk::manage(new Gtk::Label()); + usersTitle->get_style_context()->add_class("users-title"); + usersTitle->set_text("Users"); + usersTitle->set_halign(Gtk::ALIGN_START); + userLayout->attach(*usersTitle, 0, 0, 1, 1); + + //leftPanelUsersStack.set_vexpand(true); + userLayout->attach_next_to(leftPanelUsersStack, *usersTitle, Gtk::POS_BOTTOM, 1, 2); + + createRoomButton.set_halign(Gtk::ALIGN_START); + createRoomButton.set_valign(Gtk::ALIGN_END); + leftPanelLayout->attach_next_to(createRoomButton, *leftPanel, Gtk::POS_BOTTOM, 1, 1); + createRoomButton.signal_clicked().connect([this]() { - InputDialog createRoomDialog("Create a new room", "Room name"); + InputDialog createRoomDialog("Create a new room", { "Room name" }); switch(createRoomDialog.run()) { case Gtk::RESPONSE_ACCEPT: { // TODO: Show error inline in the create room dialog - Glib::ustring roomName = createRoomDialog.getInput(); + Glib::ustring roomName = createRoomDialog.entries[0]->get_text(); if(roomName.size() == 0 || roomName.size() > 32) window->windowNotification->show("Room name has to be between 1 and 32 characters"); else - window->rooms->createRoom(roomName); + { + auto room = window->rooms->createRoom(roomName); + setCurrentRoom(room); + } + break; + } + default: + break; + } + }); + + joinRoomButton.set_halign(Gtk::ALIGN_START); + leftPanelLayout->attach_next_to(joinRoomButton, createRoomButton, Gtk::POS_BOTTOM, 1, 1); + joinRoomButton.signal_clicked().connect([this]() + { + InputDialog joinRoomDialog("Join room", { "Invite key", "Message to send to admin" }); + switch(joinRoomDialog.run()) + { + case Gtk::RESPONSE_ACCEPT: + { + // TODO: Show error inline in the join room dialog + Glib::ustring inviteKey = joinRoomDialog.entries[0]->get_text(); + Glib::ustring message = joinRoomDialog.entries[1]->get_text(); + if(inviteKey.size() != 130) + window->windowNotification->show("Invite key has to be 130 characters"); + else + window->rooms->requestJoinRoom(inviteKey, message); break; } default: break; } }); + + userSettingsButton.set_halign(Gtk::ALIGN_START); + leftPanelLayout->attach_next_to(userSettingsButton, joinRoomButton, Gtk::POS_BOTTOM, 1, 1); + userSettingsButton.signal_clicked().connect([this]() + { + printf("user settings!\n"); + }); } void ChatWindow::setupMessageArea(Gtk::Grid *rightPanel) { //messageArea.set_valign(Gtk::ALIGN_START); messageArea.set_vexpand(true); + messageArea.set_overlay_scrolling(false); messageArea.set_policy(Gtk::PolicyType::POLICY_NEVER, Gtk::PolicyType::POLICY_AUTOMATIC); rightPanel->attach(messageArea, 0, 0, 1, 2); @@ -153,6 +239,7 @@ namespace dchat Gtk::ScrolledWindow *chatScrollWindow = Gtk::manage(new Gtk::ScrolledWindow()); chatScrollWindow->set_hexpand(true); + chatScrollWindow->set_overlay_scrolling(false); chatScrollWindow->set_policy(Gtk::PolicyType::POLICY_NEVER, Gtk::PolicyType::POLICY_NEVER); chatScrollWindow->set_name("chat-scroll-view"); chatArea->attach(*chatScrollWindow, 0, 0, 1, 1); @@ -161,8 +248,35 @@ namespace dchat chatInput.set_editable(false); chatInput.set_name("chat-input"); chatInput.set_wrap_mode(Gtk::WrapMode::WRAP_WORD_CHAR); + chatInput.get_buffer()->set_text("Type a message..."); + chatInput.get_style_context()->add_class("chat-input-unfocused"); + chatInputShowPlaceholder = true; chatScrollWindow->add(chatInput); + chatInput.signal_focus_in_event().connect([this](GdkEventFocus *event) + { + if(chatInputShowPlaceholder) + { + chatInputChangeByPlaceholder = true; + chatInput.get_buffer()->set_text(""); + } + chatInput.get_style_context()->remove_class("chat-input-unfocused"); + chatInput.get_style_context()->add_class("chat-input-focused"); + return false; + }); + + chatInput.signal_focus_out_event().connect([this](GdkEventFocus *event) + { + if(chatInputShowPlaceholder) + { + chatInputChangeByPlaceholder = true; + chatInput.get_buffer()->set_text("Type a message..."); + chatInput.get_style_context()->remove_class("chat-input-focused"); + chatInput.get_style_context()->add_class("chat-input-unfocused"); + } + return false; + }); + 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) @@ -178,17 +292,27 @@ namespace dchat } return false; }, false); + chatInput.get_buffer()->signal_changed().connect([this, chatScrollWindow, fontSize] { + if(chatInputChangeByPlaceholder) + { + chatInputChangeByPlaceholder = false; + } + else + { + chatInputShowPlaceholder = chatInput.get_buffer()->get_char_count() == 0; + chatInputChangeByPlaceholder = false; + } + 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); + if(numLines <= 10) + chatScrollWindow->set_min_content_height(fontSize * std::min(numLines, 10)); auto adj = chatScrollWindow->get_vadjustment(); - if(chatInput.get_buffer()->get_line_count() <= 10) + if(chatInput.get_buffer()->get_line_count() <= 11) { chatScrollWindow->set_policy(Gtk::PolicyType::POLICY_NEVER, Gtk::PolicyType::POLICY_NEVER); adj->set_value(0); @@ -198,7 +322,9 @@ namespace dchat chatScrollWindow->set_policy(Gtk::PolicyType::POLICY_NEVER, Gtk::PolicyType::POLICY_ALWAYS); } } + chatPrevNumLines = numLines; }); + chatScrollWindow->get_vadjustment()->signal_value_changed().connect([this, chatScrollWindow]() { auto adj = chatScrollWindow->get_vadjustment(); @@ -222,13 +348,6 @@ namespace dchat leftPanelUsersLayout->show(); leftPanelUsersStack.add(*leftPanelUsersLayout, roomIdStr); - Gtk::Label *usersTitle = Gtk::manage(new Gtk::Label()); - usersTitle->get_style_context()->add_class("users-title"); - usersTitle->set_text("Users"); - usersTitle->set_halign(Gtk::ALIGN_START); - usersTitle->show(); - leftPanelUsersLayout->attach(*usersTitle, 0, 0, 1, 1); - Gtk::Grid *messageAreaLayout = new Gtk::Grid(); messageAreaLayout->show(); messageAreaStack.add(*messageAreaLayout, roomIdStr); @@ -245,15 +364,22 @@ namespace dchat { setCurrentRoom(room); }); - leftPanelChannels.attach(*roomButton, 0, 1 + roomCount, 1, 1); + leftPanelChannels.attach(*roomButton, 0, roomCount, 1, 1); ++roomCount; - currentRoomData = new RoomData { leftPanelUsersLayout, messageAreaLayout, roomButton }; - roomDataById[*room->id] = currentRoomData; - currentRoom = room; - chatInput.set_editable(true); - leftPanelUsersStack.set_visible_child(roomIdStr); - messageAreaStack.set_visible_child(roomIdStr); + RoomData *roomData = new RoomData { leftPanelUsersLayout, messageAreaLayout, roomButton }; + roomDataById[*room->id] = roomData; + if(!currentRoom) + { + currentRoom = room; + currentRoomData = roomData; + if(room->localUser) + chatInput.set_editable(true); + else + chatInput.set_editable(false); + leftPanelUsersStack.set_visible_child(roomIdStr); + messageAreaStack.set_visible_child(roomIdStr); + } } void ChatWindow::setCurrentRoom(std::shared_ptr room) @@ -261,10 +387,13 @@ namespace dchat std::string roomIdStr = room->id->toString(); leftPanelUsersStack.set_visible_child(roomIdStr); messageAreaStack.set_visible_child(roomIdStr); - currentChannelTitle.set_text(room->name); + topbar->setTitle(room->name); currentRoom = room; currentRoomData = roomDataById[*room->id]; - chatInput.set_editable(true); + if(room->localUser) + chatInput.set_editable(true); + else + chatInput.set_editable(false); // TODO: Instead of scrolling to bottom, remember scroll position (even after restarting application). // We want to show oldest unread message first @@ -315,16 +444,31 @@ namespace dchat } } - void ChatWindow::addUser(std::shared_ptr room, std::shared_ptr user) + void ChatWindow::addUser(const RoomAddUserRequest &request) { Gtk::Label *username = Gtk::manage(new Gtk::Label("Anonymous")); username->set_halign(Gtk::ALIGN_START); username->show(); username->get_style_context()->add_class("username-list-username"); - user->userdata = username; - RoomData *roomData = roomDataById[*room->id]; - roomData->leftPanelUsersLayout->attach(*username, 0, room->userByPublicKey.size(), 1, 1); - fprintf(stderr, "Added user %s\n", user->publicKey.toString().c_str()); + request.user->userdata = username; + RoomData *roomData = roomDataById[*request.room->id]; + roomData->leftPanelUsersLayout->attach(*username, 0, request.room->userByPublicKey.size() - 1, 1, 1); + fprintf(stderr, "Added user %s\n", request.user->publicKey.toString().c_str()); + + if(roomData == currentRoomData) + { + if(request.room->localUser) + chatInput.set_editable(true); + else + chatInput.set_editable(false); + } + + if(!request.loadedFromCache && request.isLocalUser) + { + Glib::ustring msg = "You were added to room "; + msg += request.room->name + " by " + request.addedByUser->nickname; + window->windowNotification->show(msg); + } } void ChatWindow::setUserNickname(const UserChangeNicknameRequest &request) @@ -339,10 +483,22 @@ namespace dchat Gtk::Button *button = roomDataById[*request.room->id]->button; static_cast(button->get_child())->set_text(request.newName); if(*request.room->id == *currentRoom->id) - currentChannelTitle.set_text(request.newName); + topbar->setTitle(request.newName); fprintf(stderr, "Changed room %s name to %s\n", request.room->id->toString().c_str(), request.newName.c_str()); } + void ChatWindow::addInviteRequest(const InviteUserRequest &request) + { + auto notification = Gio::Notification::create("Invite"); + Glib::ustring body = "User "; + body += request.userPublicKey.toString(); + body += " wants to join your room " + request.room->name + ". Message from user: "; + body += request.message; + notification->set_body(body); + window->get_application()->send_notification(notification); + roomNotificationsWindow.addInviteRequest(request); + } + void ChatWindow::scrollToBottom() { while(gtk_events_pending()) diff --git a/src/InputDialog.cpp b/src/InputDialog.cpp index 520bfa2..16c4d01 100644 --- a/src/InputDialog.cpp +++ b/src/InputDialog.cpp @@ -1,22 +1,37 @@ #include "../include/InputDialog.hpp" #include +#include namespace dchat { - InputDialog::InputDialog(const char *title, const char *text, const char *acceptText, const char *cancelText) + InputDialog::InputDialog(const char *title, const std::vector &texts, const char *acceptText, const char *cancelText) { set_title(title); Gtk::Box *box = get_content_area(); - Gtk::Label *label = Gtk::manage(new Gtk::Label(text)); - label->set_valign(Gtk::ALIGN_END); - label->set_halign(Gtk::ALIGN_CENTER); - box->pack_start(*label, true, true); + //box->set_halign(Gtk::ALIGN_CENTER); + //box->set_valign(Gtk::ALIGN_CENTER); + Gtk::Grid *grid = Gtk::manage(new Gtk::Grid()); + grid->set_halign(Gtk::ALIGN_CENTER); + grid->set_valign(Gtk::ALIGN_CENTER); + box->pack_start(*grid, true, true); + int i = 0; + for(const char *text : texts) + { + Gtk::Label *label = Gtk::manage(new Gtk::Label(text)); + label->set_valign(Gtk::ALIGN_END); + label->set_halign(Gtk::ALIGN_CENTER); + grid->attach(*label, 0, i, 1, 1); - entry.set_valign(Gtk::ALIGN_CENTER); - entry.set_halign(Gtk::ALIGN_CENTER); - entry.set_hexpand(true); - box->pack_end(entry, true, true); + Gtk::Entry *entry = Gtk::manage(new Gtk::Entry()); + entry->set_valign(Gtk::ALIGN_CENTER); + entry->set_halign(Gtk::ALIGN_CENTER); + entry->set_hexpand(true); + grid->attach(*entry, 0, i + 1, 2, 1); + entries.push_back(entry); + + i += 2; + } add_button(acceptText, Gtk::RESPONSE_ACCEPT); add_button(cancelText, Gtk::RESPONSE_CANCEL); @@ -24,9 +39,4 @@ namespace dchat set_size_request(300, 150); } - - Glib::ustring InputDialog::getInput() const - { - return entry.get_text(); - } } \ No newline at end of file diff --git a/src/RoomNotificationsWindow.cpp b/src/RoomNotificationsWindow.cpp new file mode 100644 index 0000000..a491840 --- /dev/null +++ b/src/RoomNotificationsWindow.cpp @@ -0,0 +1,105 @@ +#include "../include/RoomNotificationsWindow.hpp" +#include "../include/ChatWindow.hpp" +#include "../include/Window.hpp" +#include +#include +#include + +namespace dchat +{ + RoomNotificationsWindow::RoomNotificationsWindow(ChatWindow *_chatWindow) : + chatWindow(_chatWindow) + { + set_vexpand(true); + set_hexpand(true); + set_border_width(25); + } + + RoomNotificationsWindow::RoomNotifications* RoomNotificationsWindow::createRoomNotifications() + { + RoomNotifications *roomNotifications = Gtk::manage(new RoomNotifications()); + roomNotifications->columns.add(roomNotifications->userPublicKeyColumn); + roomNotifications->columns.add(roomNotifications->messageColumn); + roomNotifications->listStore = Gtk::ListStore::create(roomNotifications->columns); + + Gtk::TreeView *treeView = Gtk::manage(new Gtk::TreeView(roomNotifications->listStore)); + treeView->append_column("Public key", roomNotifications->userPublicKeyColumn); + treeView->append_column("Message", roomNotifications->messageColumn); + treeView->set_reorderable(); + //treeView->set_rules_hint(); + treeView->set_headers_visible(); + treeView->set_headers_clickable(); + treeView->set_activate_on_single_click(false); + treeView->get_selection()->set_mode(Gtk::SelectionMode::SELECTION_SINGLE); + treeView->signal_row_activated().connect([roomNotifications, treeView, this](const Gtk::TreeModel::Path &path, Gtk::TreeViewColumn *column) + { + if(path.empty()) return; + auto selectedRow = treeView->get_selection()->get_selected(); + Glib::ustring userPublicKeyStr = selectedRow->get_value(roomNotifications->userPublicKeyColumn); + assert(roomNotifications->inviteRequests.find(userPublicKeyStr) != roomNotifications->inviteRequests.end()); + InviteUserRequest &request = roomNotifications->inviteRequests[userPublicKeyStr]; + + Gtk::Dialog dialog; + dialog.set_title("Add user to room"); + Glib::ustring msg = "Are you sure you want to add the user "; + msg += userPublicKeyStr; + msg += " to the room "; + msg += request.room->name + " (" + request.room->id->toString() + ") ?"; + dialog.get_content_area()->pack_start(*Gtk::manage(new Gtk::Label(msg))); + dialog.add_button("Yes", Gtk::RESPONSE_YES); + dialog.add_button("No", Gtk::RESPONSE_NO); + switch(dialog.run()) + { + case Gtk::RESPONSE_YES: + { + assert(!request.room->groups.empty()); + // TODO: Add user to a guest group instead + try + { + request.room->addUser(request.userPublicKey, request.room->groups[0]); + roomNotifications->listStore->erase(selectedRow); + } + catch(std::exception &e) + { + chatWindow->window->windowNotification->show(Glib::ustring("Failed to add user to room, reason: ") + e.what()); + } + } + default: + break; + } + }); + roomNotifications->add(*treeView); + + roomNotifications->set_overlay_scrolling(false); + //roomNotifications->set_size_request(640, 480); + //roomNotifications->set_valign(Gtk::ALIGN_START); + //roomNotifications->set_halign(Gtk::ALIGN_CENTER); + return roomNotifications; + } + + void RoomNotificationsWindow::addInviteRequest(const InviteUserRequest &request) + { + RoomNotifications *roomNotifications = nullptr; + auto it = roomNotificationsMap.find(request.room.get()); + if(it == roomNotificationsMap.end()) + { + roomNotifications = createRoomNotifications(); + add(*roomNotifications, request.room->id->toString()); + roomNotificationsMap[request.room.get()] = roomNotifications; + } + else + roomNotifications = it->second; + + std::string userPublicKeyStr = request.userPublicKey.toString(); + if(roomNotifications->inviteRequests.find(userPublicKeyStr) != roomNotifications->inviteRequests.end()) + { + fprintf(stderr, "Got duplicate invite request from user %s for room %s\n", userPublicKeyStr.c_str(), request.room->id->toString().c_str()); + return; + } + roomNotifications->inviteRequests[userPublicKeyStr] = request; + + Gtk::TreeModel::Row row = *roomNotifications->listStore->append(); + row[roomNotifications->userPublicKeyColumn] = userPublicKeyStr; + row[roomNotifications->messageColumn] = request.message; + } +} \ No newline at end of file diff --git a/src/RoomSettingsWindow.cpp b/src/RoomSettingsWindow.cpp new file mode 100644 index 0000000..e20e36e --- /dev/null +++ b/src/RoomSettingsWindow.cpp @@ -0,0 +1,88 @@ +#include "../include/RoomSettingsWindow.hpp" +#include "../include/ChatWindow.hpp" +#include +#include +#include +#include +#include + +namespace dchat +{ + RoomSettingsWindow::RoomSettingsWindow(ChatWindow *_chatWindow) : + chatWindow(_chatWindow) + { + assert(chatWindow); + Gtk::Paned *sidePanels = Gtk::manage(new Gtk::Paned(Gtk::ORIENTATION_HORIZONTAL)); + sidePanels->get_style_context()->add_class("side-panels"); + sidePanels->set_vexpand(true); + sidePanels->set_hexpand(true); + attach(*sidePanels, 0, 0, 1, 1); + + setupLeftPanel(sidePanels); + setupRightPanel(sidePanels); + + set_vexpand(true); + set_hexpand(true); + } + + void RoomSettingsWindow::selectRoom(std::shared_ptr room) + { + roomNameEntry.set_text(room->name); + inviteKey.set_text(room->inviteKey); + } + + void RoomSettingsWindow::setupLeftPanel(Gtk::Paned *sidePanels) + { + Gtk::Grid *leftPanel = Gtk::manage(new Gtk::Grid()); + leftPanel->set_vexpand(true); + leftPanel->set_valign(Gtk::ALIGN_START); + leftPanel->set_halign(Gtk::ALIGN_START); + leftPanel->set_size_request(200); + leftPanel->get_style_context()->add_class("left-panel"); + sidePanels->add1(*leftPanel); + + Gtk::Label *settingsLabel = Gtk::manage(new Gtk::Label("Settings")); + leftPanel->attach(*settingsLabel, 0, 0, 1, 1); + + Gtk::ToggleButton *generalButton = Gtk::manage(new Gtk::ToggleButton("General")); + leftPanel->attach_next_to(*generalButton, *settingsLabel, Gtk::POS_BOTTOM, 1, 1); + + Gtk::ToggleButton *returnToChatButton = Gtk::manage(new Gtk::ToggleButton("Return to chat")); + returnToChatButton->signal_clicked().connect([this] + { + chatWindow->chatPage.show_all(); + chatWindow->stack.set_visible_child("chat"); + }); + leftPanel->attach_next_to(*returnToChatButton, *generalButton, Gtk::POS_BOTTOM, 1, 1); + } + + void RoomSettingsWindow::setupRightPanel(Gtk::Paned *sidePanels) + { + Gtk::Grid *rightPanel = Gtk::manage(new Gtk::Grid()); + rightPanel->set_vexpand(true); + rightPanel->set_valign(Gtk::ALIGN_START); + rightPanel->set_halign(Gtk::ALIGN_START); + sidePanels->add2(*rightPanel); + + Gtk::Label *roomNameLabel = Gtk::manage(new Gtk::Label("Room name")); + rightPanel->attach(*roomNameLabel, 0, 0, 1, 1); + + roomNameEntry.set_editable(false); + rightPanel->attach_next_to(roomNameEntry, *roomNameLabel, Gtk::POS_BOTTOM, 1, 1); + + Gtk::Label *inviteKeyLabel = Gtk::manage(new Gtk::Label("Invite key")); + rightPanel->attach_next_to(*inviteKeyLabel, roomNameEntry, Gtk::POS_BOTTOM, 1, 1); + + inviteKey.set_selectable(true); + inviteKey.set_line_wrap(true); + inviteKey.set_line_wrap_mode(Pango::WRAP_WORD_CHAR); + rightPanel->attach_next_to(inviteKey, *inviteKeyLabel, Gtk::POS_BOTTOM, 1, 1); + + Gtk::Button *copyInviteKeyButton = Gtk::manage(new Gtk::Button("_Copy", true)); + copyInviteKeyButton->signal_clicked().connect([this] + { + Gtk::Clipboard::get()->set_text(inviteKey.get_text()); + }); + rightPanel->attach_next_to(*copyInviteKeyButton, inviteKey, Gtk::POS_RIGHT, 1, 1); + } +} \ No newline at end of file diff --git a/src/Topbar.cpp b/src/Topbar.cpp new file mode 100644 index 0000000..6339761 --- /dev/null +++ b/src/Topbar.cpp @@ -0,0 +1,44 @@ +#include "../include/Topbar.hpp" + +namespace dchat +{ + Topbar::Topbar() : + roomSettingsButton("images/settings-icon.png", nullptr), + roomNotificationsButton("images/messages_icon.png", nullptr) + { + set_name("top-bar"); + set_hexpand(true); + + Gtk::Grid *topbarLeft = Gtk::manage(new Gtk::Grid()); + topbarLeft->set_name("top-bar-left"); + topbarLeft->set_size_request(180); + topbarLeft->set_valign(Gtk::ALIGN_CENTER); + topbarLeft->set_halign(Gtk::ALIGN_CENTER); + attach(*topbarLeft, 0, 0, 1, 1); + + topbarSearchBar.set_name("top-bar-search"); + topbarSearchBar.set_placeholder_text("Search..."); + topbarSearchBar.set_size_request(180); + topbarLeft->attach(topbarSearchBar, 0, 0, 1, 1); + + Gtk::Grid *topbarRight = Gtk::manage(new Gtk::Grid()); + topbarRight->set_name("top-bar-right"); + topbarRight->set_hexpand(true); + topbarRight->set_valign(Gtk::ALIGN_CENTER); + attach_next_to(*topbarRight, *topbarLeft, Gtk::POS_RIGHT, 1, 1); + + currentRoomTitle.set_name("current-room-title"); + currentRoomTitle.set_selectable(true); + currentRoomTitle.set_hexpand(true); + currentRoomTitle.set_alignment(Gtk::ALIGN_START, Gtk::ALIGN_CENTER); + topbarRight->attach(currentRoomTitle, 0, 0, 1, 1); + + topbarRight->attach_next_to(roomSettingsButton, currentRoomTitle, Gtk::POS_RIGHT, 1, 1); + topbarRight->attach_next_to(roomNotificationsButton, roomSettingsButton, Gtk::POS_RIGHT, 1, 1); + } + + void Topbar::setTitle(const Glib::ustring &title) + { + currentRoomTitle.set_text(title); + } +} \ No newline at end of file diff --git a/src/Window.cpp b/src/Window.cpp index 22e5501..adad21e 100644 --- a/src/Window.cpp +++ b/src/Window.cpp @@ -20,6 +20,7 @@ namespace dchat overlay.add(stack); add(overlay); + stack.set_homogeneous(false); stack.set_transition_type(Gtk::StackTransitionType::STACK_TRANSITION_TYPE_SLIDE_LEFT_RIGHT); stack.set_transition_duration(250); stack.add(loginWindow, "login"); @@ -27,6 +28,7 @@ namespace dchat overlay.show(); windowNotification->show_all(); + stack.set_visible_child("login"); stack.show(); //chatWindow.show_all(); //loginWindow.show(); @@ -45,7 +47,7 @@ namespace dchat rooms->loginUser(username.raw(), password.raw()); //windowNotification->show(Glib::ustring("Successfully logged in as ") + username); drawBackgroundConnection.disconnect(); - chatWindow.show_all(); + chatWindow.show(); stack.set_visible_child(chatWindow); chatWindow.scrollToBottom(); } @@ -71,7 +73,7 @@ namespace dchat rooms->registerUser(username.raw(), password.raw()); windowNotification->show(Glib::ustring("Successfully registered user ") + username); drawBackgroundConnection.disconnect(); - chatWindow.show_all(); + chatWindow.show(); stack.set_visible_child(chatWindow); } catch(std::exception &e) @@ -109,11 +111,11 @@ namespace dchat }; roomCallbackFuncs.createRoomCallbackFunc = [this](std::shared_ptr room) { - chatWindow.addRoom(room); + chatWindow.addRoom(room); }; - roomCallbackFuncs.addUserCallbackFunc = [this](std::shared_ptr room, std::shared_ptr user) + roomCallbackFuncs.addUserCallbackFunc = [this](const RoomAddUserRequest &request) { - chatWindow.addUser(room, user); + chatWindow.addUser(request); }; roomCallbackFuncs.addMessageCallbackFunc = [this](const RoomAddMessageRequest &request) { @@ -127,6 +129,10 @@ namespace dchat { chatWindow.changeRoomName(request); }; + roomCallbackFuncs.receiveInviteUserCallbackFunc = [this](const InviteUserRequest &request) + { + chatWindow.addInviteRequest(request); + }; windowNotification->show("Connecting to 83.252.53.188:27130"); Rooms::connect("83.252.53.188", 27130, roomCallbackFuncs); -- cgit v1.2.3