Serenity Operating System
at portability 243 lines 9.6 kB view raw
1/* 2 * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org> 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions are met: 7 * 8 * 1. Redistributions of source code must retain the above copyright notice, this 9 * list of conditions and the following disclaimer. 10 * 11 * 2. Redistributions in binary form must reproduce the above copyright notice, 12 * this list of conditions and the following disclaimer in the documentation 13 * and/or other materials provided with the distribution. 14 * 15 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 19 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 21 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 22 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 23 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 24 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 */ 26 27#include "IRCAppWindow.h" 28#include "IRCChannel.h" 29#include "IRCWindow.h" 30#include "IRCWindowListModel.h" 31#include <LibGUI/AboutDialog.h> 32#include <LibGUI/Action.h> 33#include <LibGUI/Application.h> 34#include <LibGUI/BoxLayout.h> 35#include <LibGUI/InputBox.h> 36#include <LibGUI/Menu.h> 37#include <LibGUI/MenuBar.h> 38#include <LibGUI/Splitter.h> 39#include <LibGUI/StackWidget.h> 40#include <LibGUI/TableView.h> 41#include <LibGUI/ToolBar.h> 42#include <stdio.h> 43#include <stdlib.h> 44 45static IRCAppWindow* s_the; 46 47IRCAppWindow& IRCAppWindow::the() 48{ 49 return *s_the; 50} 51 52IRCAppWindow::IRCAppWindow() 53 : m_client(IRCClient::construct()) 54{ 55 ASSERT(!s_the); 56 s_the = this; 57 58 set_icon(Gfx::Bitmap::load_from_file("/res/icons/16x16/app-irc-client.png")); 59 60 update_title(); 61 set_rect(200, 200, 600, 400); 62 setup_actions(); 63 setup_menus(); 64 setup_widgets(); 65 66 setup_client(); 67} 68 69IRCAppWindow::~IRCAppWindow() 70{ 71} 72 73void IRCAppWindow::update_title() 74{ 75 set_title(String::format("IRC Client: %s@%s:%d", m_client->nickname().characters(), m_client->hostname().characters(), m_client->port())); 76} 77 78void IRCAppWindow::setup_client() 79{ 80 m_client->aid_create_window = [this](void* owner, IRCWindow::Type type, const String& name) { 81 return create_window(owner, type, name); 82 }; 83 m_client->aid_get_active_window = [this] { 84 return static_cast<IRCWindow*>(m_container->active_widget()); 85 }; 86 m_client->aid_update_window_list = [this] { 87 m_window_list->model()->update(); 88 }; 89 m_client->on_nickname_changed = [this](const String&) { 90 update_title(); 91 }; 92 m_client->on_part_from_channel = [this](auto&) { 93 update_part_action(); 94 }; 95 96 if (m_client->hostname().is_empty()) { 97 auto input_box = add<GUI::InputBox>("Enter server:", "Connect to server"); 98 auto result = input_box->exec(); 99 if (result == GUI::InputBox::ExecCancel) 100 ::exit(0); 101 102 m_client->set_server(input_box->text_value(), 6667); 103 } 104 update_title(); 105 bool success = m_client->connect(); 106 ASSERT(success); 107} 108 109void IRCAppWindow::setup_actions() 110{ 111 m_join_action = GUI::Action::create("Join channel", { Mod_Ctrl, Key_J }, Gfx::Bitmap::load_from_file("/res/icons/16x16/irc-join.png"), [&](auto&) { 112 auto input_box = add<GUI::InputBox>("Enter channel name:", "Join channel"); 113 if (input_box->exec() == GUI::InputBox::ExecOK && !input_box->text_value().is_empty()) 114 m_client->handle_join_action(input_box->text_value()); 115 }); 116 117 m_part_action = GUI::Action::create("Part from channel", { Mod_Ctrl, Key_P }, Gfx::Bitmap::load_from_file("/res/icons/16x16/irc-part.png"), [this](auto&) { 118 auto* window = m_client->current_window(); 119 if (!window || window->type() != IRCWindow::Type::Channel) { 120 // FIXME: Perhaps this action should have been disabled instead of allowing us to activate it. 121 return; 122 } 123 m_client->handle_part_action(window->channel().name()); 124 }); 125 126 m_whois_action = GUI::Action::create("Whois user", Gfx::Bitmap::load_from_file("/res/icons/16x16/irc-whois.png"), [&](auto&) { 127 auto input_box = GUI::InputBox::construct("Enter nickname:", "IRC WHOIS lookup", this); 128 if (input_box->exec() == GUI::InputBox::ExecOK && !input_box->text_value().is_empty()) 129 m_client->handle_whois_action(input_box->text_value()); 130 }); 131 132 m_open_query_action = GUI::Action::create("Open query", { Mod_Ctrl, Key_O }, Gfx::Bitmap::load_from_file("/res/icons/16x16/irc-open-query.png"), [&](auto&) { 133 auto input_box = GUI::InputBox::construct("Enter nickname:", "Open IRC query with...", this); 134 if (input_box->exec() == GUI::InputBox::ExecOK && !input_box->text_value().is_empty()) 135 m_client->handle_open_query_action(input_box->text_value()); 136 }); 137 138 m_close_query_action = GUI::Action::create("Close query", { Mod_Ctrl, Key_D }, Gfx::Bitmap::load_from_file("/res/icons/16x16/irc-close-query.png"), [](auto&) { 139 printf("FIXME: Implement close-query action\n"); 140 }); 141 142 m_change_nick_action = GUI::Action::create("Change nickname", Gfx::Bitmap::load_from_file("/res/icons/16x16/irc-nick.png"), [this](auto&) { 143 auto input_box = GUI::InputBox::construct("Enter nickname:", "Change nickname", this); 144 if (input_box->exec() == GUI::InputBox::ExecOK && !input_box->text_value().is_empty()) 145 m_client->handle_change_nick_action(input_box->text_value()); 146 }); 147} 148 149void IRCAppWindow::setup_menus() 150{ 151 auto menubar = make<GUI::MenuBar>(); 152 auto app_menu = GUI::Menu::construct("IRC Client"); 153 app_menu->add_action(GUI::CommonActions::make_quit_action([](auto&) { 154 dbgprintf("Terminal: Quit menu activated!\n"); 155 GUI::Application::the().quit(0); 156 return; 157 })); 158 menubar->add_menu(move(app_menu)); 159 160 auto server_menu = GUI::Menu::construct("Server"); 161 server_menu->add_action(*m_change_nick_action); 162 server_menu->add_separator(); 163 server_menu->add_action(*m_join_action); 164 server_menu->add_action(*m_part_action); 165 server_menu->add_separator(); 166 server_menu->add_action(*m_whois_action); 167 server_menu->add_action(*m_open_query_action); 168 server_menu->add_action(*m_close_query_action); 169 menubar->add_menu(move(server_menu)); 170 171 auto help_menu = GUI::Menu::construct("Help"); 172 help_menu->add_action(GUI::Action::create("About", [this](const GUI::Action&) { 173 GUI::AboutDialog::show("IRC Client", Gfx::Bitmap::load_from_file("/res/icons/32x32/app-irc-client.png"), this); 174 })); 175 menubar->add_menu(move(help_menu)); 176 177 GUI::Application::the().set_menubar(move(menubar)); 178} 179 180void IRCAppWindow::setup_widgets() 181{ 182 auto widget = GUI::Widget::construct(); 183 set_main_widget(widget); 184 widget->set_fill_with_background_color(true); 185 widget->set_layout(make<GUI::VerticalBoxLayout>()); 186 widget->layout()->set_spacing(0); 187 188 auto toolbar = widget->add<GUI::ToolBar>(); 189 toolbar->set_has_frame(false); 190 toolbar->add_action(*m_change_nick_action); 191 toolbar->add_separator(); 192 toolbar->add_action(*m_join_action); 193 toolbar->add_action(*m_part_action); 194 toolbar->add_separator(); 195 toolbar->add_action(*m_whois_action); 196 toolbar->add_action(*m_open_query_action); 197 toolbar->add_action(*m_close_query_action); 198 199 auto outer_container = widget->add<GUI::Widget>(); 200 outer_container->set_layout(make<GUI::VerticalBoxLayout>()); 201 outer_container->layout()->set_margins({ 2, 0, 2, 2 }); 202 203 auto horizontal_container = outer_container->add<GUI::HorizontalSplitter>(); 204 205 m_window_list = horizontal_container->add<GUI::TableView>(); 206 m_window_list->set_headers_visible(false); 207 m_window_list->set_alternating_row_colors(false); 208 m_window_list->set_size_columns_to_fit_content(true); 209 m_window_list->set_model(m_client->client_window_list_model()); 210 m_window_list->set_activates_on_selection(true); 211 m_window_list->set_size_policy(GUI::SizePolicy::Fixed, GUI::SizePolicy::Fill); 212 m_window_list->set_preferred_size(100, 0); 213 m_window_list->on_activation = [this](auto& index) { 214 set_active_window(m_client->window_at(index.row())); 215 }; 216 217 m_container = horizontal_container->add<GUI::StackWidget>(); 218 m_container->on_active_widget_change = [this](auto*) { 219 update_part_action(); 220 }; 221 222 create_window(&m_client, IRCWindow::Server, "Server"); 223} 224 225void IRCAppWindow::set_active_window(IRCWindow& window) 226{ 227 m_container->set_active_widget(&window); 228 window.clear_unread_count(); 229 auto index = m_window_list->model()->index(m_client->window_index(window)); 230 m_window_list->selection().set(index); 231} 232 233void IRCAppWindow::update_part_action() 234{ 235 auto* window = static_cast<IRCWindow*>(m_container->active_widget()); 236 bool is_open_channel = window && window->type() == IRCWindow::Type::Channel && window->channel().is_open(); 237 m_part_action->set_enabled(is_open_channel); 238} 239 240NonnullRefPtr<IRCWindow> IRCAppWindow::create_window(void* owner, IRCWindow::Type type, const String& name) 241{ 242 return m_container->add<IRCWindow>(m_client, owner, type, name); 243}