// Copyright (c) 2021 hiimgoodpack // Permission is hereby granted, free of charge, to any person obtaining a copy of // this software and associated documentation files (the "Software"), to deal in // the Software without restriction, including without limitation the rights to // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies // of the Software, and to permit persons to whom the Software is furnished to do // so, subject to the following conditions: // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. #include "http.h" #include "websocket.h" #include "brainlet.h" #include // std::strcpy #include // std::unique_ptr #include // std::for_each #include #include #include #include static std::unique_ptr client = nullptr; static std::unordered_map channelRowToId; static std::unordered_map> messages; static std::string currentChannelId; static Gtk::Window* window; static Gtk::Widget* loginInterface; static Gtk::Entry* domainEntry; static Gtk::Entry* usernameEntry; static Gtk::Entry* passwordEntry; static Gtk::Button* loginButton; static Gtk::Widget* chatInterface; static Gtk::ListBox* channelList; static Gtk::ListBox* messageList; static Gtk::Entry* messageEntry; static Gtk::Label* usernameLabel; gboolean update(gpointer) { client->processNextEvent(); return G_SOURCE_CONTINUE; } void addChannel(const Brainlet::Channel channel) { auto row = Gtk::make_managed(); auto name = Gtk::make_managed(); name->set_halign(Gtk::ALIGN_START); name->set_line_wrap(true); name->set_text(channel.name); row->add(*name); channelList->add(*row); row->show_all_children(); row->show(); channelRowToId.emplace(row, channel.id); messages.emplace( std::piecewise_construct, std::forward_as_tuple(channel.id), std::forward_as_tuple() ); } void addMessage(const Brainlet::Message message) { auto row = Gtk::make_managed(); auto text = Gtk::make_managed(); text->set_halign(Gtk::ALIGN_START); text->set_line_wrap(true); std::string messageFormatted = message.authorId + ": " + message.message; text->set_markup(messageFormatted.c_str()); row->add(*text); messageList->add(*row); std::string mention = "@" + client->id; if (message.message.find(mention) != std::string::npos) messageList->select_row(*row); row->show_all_children(); row->show(); } int main(int argc, char* argv[]) { curl_global_init(CURL_GLOBAL_ALL); Glib::RefPtr app = Gtk::Application::create("org.hiimgoodpack.brainlet_client"); Glib::RefPtr builder = Gtk::Builder::create_from_file("./design.glade"); builder->get_widget("Window", window); builder->get_widget("Login", loginInterface); builder->get_widget("Domain", domainEntry); builder->get_widget("Username", usernameEntry); builder->get_widget("Password", passwordEntry); builder->get_widget("LoginButton", loginButton); builder->get_widget("Chat", chatInterface); builder->get_widget("Channels", channelList); builder->get_widget("Messages", messageList); builder->get_widget("Message", messageEntry); builder->get_widget("UsernameLabel", usernameLabel); auto login = [&]() { if (client.get() != nullptr) return; const std::string username = usernameEntry->get_text(); const std::string password = passwordEntry->get_text(); usernameLabel->set_text(username.c_str()); window->remove(); window->add(*chatInterface); client.reset(new Brainlet::Client(domainEntry->get_text())); client->login(username, password); client->onNewChannel = addChannel; client->onNewMessage = [&](const Brainlet::Message message, const Brainlet::Channel channel) { messages.at(channel.id).push_back(message); if (channel.id == currentChannelId) addMessage(message); }; g_timeout_add((1.0f/5.0f)*1000.0f, update, nullptr); }; passwordEntry->signal_activate().connect(login); loginButton->signal_clicked().connect(login); channelList->signal_row_activated().connect([&](Gtk::ListBoxRow* row) { // Clear messages in messageList messageList->foreach(std::bind(std::mem_fn(&Gtk::ListBox::remove), messageList, std::placeholders::_1)); // Add messages in the channel selected currentChannelId = channelRowToId.at(row); const std::vector& channelMessages = messages.at(currentChannelId); std::for_each(channelMessages.begin(), channelMessages.end(), addMessage); }); messageEntry->signal_activate().connect([&]() { // TODO: Show message locally first to avoid latency // Also show some sort of unread indicator client->sendMessage(messageEntry->get_text(), currentChannelId); messageEntry->set_text(""); }); window->add(*loginInterface); app->run(*window); }