Serenity Operating System
at hosted 378 lines 16 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("%s@%s:%d - IRC Client", 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_gui_actions(); 94 }; 95 96 if (m_client->hostname().is_empty()) { 97 auto input_box = GUI::InputBox::construct("Enter server:", "Connect to server", this); 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 = GUI::InputBox::construct("Enter channel name:", "Join channel", this); 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_list_channels_action = GUI::Action::create("List channels", Gfx::Bitmap::load_from_file("/res/icons/16x16/irc-list.png"), [&](auto&) { 118 m_client->handle_list_channels_action(); 119 }); 120 121 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&) { 122 auto* window = m_client->current_window(); 123 if (!window || window->type() != IRCWindow::Type::Channel) { 124 return; 125 } 126 m_client->handle_part_action(window->channel().name()); 127 }); 128 129 m_whois_action = GUI::Action::create("Whois user", Gfx::Bitmap::load_from_file("/res/icons/16x16/irc-whois.png"), [&](auto&) { 130 auto input_box = GUI::InputBox::construct("Enter nickname:", "IRC WHOIS lookup", this); 131 if (input_box->exec() == GUI::InputBox::ExecOK && !input_box->text_value().is_empty()) 132 m_client->handle_whois_action(input_box->text_value()); 133 }); 134 135 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&) { 136 auto input_box = GUI::InputBox::construct("Enter nickname:", "Open IRC query with...", this); 137 if (input_box->exec() == GUI::InputBox::ExecOK && !input_box->text_value().is_empty()) 138 m_client->handle_open_query_action(input_box->text_value()); 139 }); 140 141 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&) { 142 printf("FIXME: Implement close-query action\n"); 143 }); 144 145 m_change_nick_action = GUI::Action::create("Change nickname", Gfx::Bitmap::load_from_file("/res/icons/16x16/irc-nick.png"), [this](auto&) { 146 auto input_box = GUI::InputBox::construct("Enter nickname:", "Change nickname", this); 147 if (input_box->exec() == GUI::InputBox::ExecOK && !input_box->text_value().is_empty()) 148 m_client->handle_change_nick_action(input_box->text_value()); 149 }); 150 151 m_change_topic_action = GUI::Action::create("Change topic", Gfx::Bitmap::load_from_file("/res/icons/16x16/irc-topic.png"), [this](auto&) { 152 auto* window = m_client->current_window(); 153 if (!window || window->type() != IRCWindow::Type::Channel) { 154 return; 155 } 156 auto input_box = GUI::InputBox::construct("Enter topic:", "Change topic", this); 157 if (input_box->exec() == GUI::InputBox::ExecOK && !input_box->text_value().is_empty()) 158 m_client->handle_change_topic_action(window->channel().name(), input_box->text_value()); 159 }); 160 161 m_invite_user_action = GUI::Action::create("Invite user", Gfx::Bitmap::load_from_file("/res/icons/16x16/irc-invite.png"), [this](auto&) { 162 auto* window = m_client->current_window(); 163 if (!window || window->type() != IRCWindow::Type::Channel) { 164 return; 165 } 166 auto input_box = GUI::InputBox::construct("Enter nick:", "Invite user", this); 167 if (input_box->exec() == GUI::InputBox::ExecOK && !input_box->text_value().is_empty()) 168 m_client->handle_invite_user_action(window->channel().name(), input_box->text_value()); 169 }); 170 171 m_banlist_action = GUI::Action::create("Ban list", [this](auto&) { 172 auto* window = m_client->current_window(); 173 if (!window || window->type() != IRCWindow::Type::Channel) { 174 return; 175 } 176 m_client->handle_banlist_action(window->channel().name()); 177 }); 178 179 m_voice_user_action = GUI::Action::create("Voice user", [this](auto&) { 180 auto* window = m_client->current_window(); 181 if (!window || window->type() != IRCWindow::Type::Channel) { 182 return; 183 } 184 auto input_box = GUI::InputBox::construct("Enter nick:", "Voice user", this); 185 if (input_box->exec() == GUI::InputBox::ExecOK && !input_box->text_value().is_empty()) 186 m_client->handle_voice_user_action(window->channel().name(), input_box->text_value()); 187 }); 188 189 m_devoice_user_action = GUI::Action::create("DeVoice user", [this](auto&) { 190 auto* window = m_client->current_window(); 191 if (!window || window->type() != IRCWindow::Type::Channel) { 192 return; 193 } 194 auto input_box = GUI::InputBox::construct("Enter nick:", "DeVoice user", this); 195 if (input_box->exec() == GUI::InputBox::ExecOK && !input_box->text_value().is_empty()) 196 m_client->handle_devoice_user_action(window->channel().name(), input_box->text_value()); 197 }); 198 199 m_hop_user_action = GUI::Action::create("Hop user", [this](auto&) { 200 auto* window = m_client->current_window(); 201 if (!window || window->type() != IRCWindow::Type::Channel) { 202 return; 203 } 204 auto input_box = GUI::InputBox::construct("Enter nick:", "Hop user", this); 205 if (input_box->exec() == GUI::InputBox::ExecOK && !input_box->text_value().is_empty()) 206 m_client->handle_hop_user_action(window->channel().name(), input_box->text_value()); 207 }); 208 209 m_dehop_user_action = GUI::Action::create("DeHop user", [this](auto&) { 210 auto* window = m_client->current_window(); 211 if (!window || window->type() != IRCWindow::Type::Channel) { 212 return; 213 } 214 auto input_box = GUI::InputBox::construct("Enter nick:", "DeHop user", this); 215 if (input_box->exec() == GUI::InputBox::ExecOK && !input_box->text_value().is_empty()) 216 m_client->handle_dehop_user_action(window->channel().name(), input_box->text_value()); 217 }); 218 219 m_op_user_action = GUI::Action::create("Op user", [this](auto&) { 220 auto* window = m_client->current_window(); 221 if (!window || window->type() != IRCWindow::Type::Channel) { 222 return; 223 } 224 auto input_box = GUI::InputBox::construct("Enter nick:", "Op user", this); 225 if (input_box->exec() == GUI::InputBox::ExecOK && !input_box->text_value().is_empty()) 226 m_client->handle_op_user_action(window->channel().name(), input_box->text_value()); 227 }); 228 229 m_deop_user_action = GUI::Action::create("DeOp user", [this](auto&) { 230 auto* window = m_client->current_window(); 231 if (!window || window->type() != IRCWindow::Type::Channel) { 232 return; 233 } 234 auto input_box = GUI::InputBox::construct("Enter nick:", "DeOp user", this); 235 if (input_box->exec() == GUI::InputBox::ExecOK && !input_box->text_value().is_empty()) 236 m_client->handle_deop_user_action(window->channel().name(), input_box->text_value()); 237 }); 238 239 m_kick_user_action = GUI::Action::create("Kick user", [this](auto&) { 240 auto* window = m_client->current_window(); 241 if (!window || window->type() != IRCWindow::Type::Channel) { 242 return; 243 } 244 auto input_box = GUI::InputBox::construct("Enter nick:", "Kick user", this); 245 auto reason_box = GUI::InputBox::construct("Enter reason:", "Reason", this); 246 if (input_box->exec() == GUI::InputBox::ExecOK && !input_box->text_value().is_empty()) 247 if (reason_box->exec() == GUI::InputBox::ExecOK) 248 m_client->handle_kick_user_action(window->channel().name(), input_box->text_value(), reason_box->text_value().characters()); 249 }); 250 251 m_cycle_channel_action = GUI::Action::create("Cycle channel", [this](auto&) { 252 auto* window = m_client->current_window(); 253 if (!window || window->type() != IRCWindow::Type::Channel) { 254 return; 255 } 256 m_client->handle_cycle_channel_action(window->channel().name()); 257 }); 258} 259 260void IRCAppWindow::setup_menus() 261{ 262 auto menubar = make<GUI::MenuBar>(); 263 auto& app_menu = menubar->add_menu("IRC Client"); 264 app_menu.add_action(GUI::CommonActions::make_quit_action([](auto&) { 265 dbgprintf("Terminal: Quit menu activated!\n"); 266 GUI::Application::the().quit(0); 267 return; 268 })); 269 270 auto& server_menu = menubar->add_menu("Server"); 271 server_menu.add_action(*m_change_nick_action); 272 server_menu.add_separator(); 273 server_menu.add_action(*m_join_action); 274 server_menu.add_action(*m_list_channels_action); 275 server_menu.add_separator(); 276 server_menu.add_action(*m_whois_action); 277 server_menu.add_action(*m_open_query_action); 278 server_menu.add_action(*m_close_query_action); 279 280 auto& channel_menu = menubar->add_menu("Channel"); 281 channel_menu.add_action(*m_change_topic_action); 282 channel_menu.add_action(*m_invite_user_action); 283 channel_menu.add_action(*m_banlist_action); 284 channel_menu.add_separator(); 285 channel_menu.add_action(*m_voice_user_action); 286 channel_menu.add_action(*m_devoice_user_action); 287 channel_menu.add_action(*m_hop_user_action); 288 channel_menu.add_action(*m_dehop_user_action); 289 channel_menu.add_action(*m_op_user_action); 290 channel_menu.add_action(*m_deop_user_action); 291 channel_menu.add_separator(); 292 channel_menu.add_action(*m_kick_user_action); 293 channel_menu.add_separator(); 294 channel_menu.add_action(*m_cycle_channel_action); 295 channel_menu.add_action(*m_part_action); 296 297 auto& help_menu = menubar->add_menu("Help"); 298 help_menu.add_action(GUI::Action::create("About", [this](auto&) { 299 GUI::AboutDialog::show("IRC Client", Gfx::Bitmap::load_from_file("/res/icons/32x32/app-irc-client.png"), this); 300 })); 301 302 GUI::Application::the().set_menubar(move(menubar)); 303} 304 305void IRCAppWindow::setup_widgets() 306{ 307 auto& widget = set_main_widget<GUI::Widget>(); 308 widget.set_fill_with_background_color(true); 309 widget.set_layout<GUI::VerticalBoxLayout>(); 310 widget.layout()->set_spacing(0); 311 312 auto& toolbar = widget.add<GUI::ToolBar>(); 313 toolbar.set_has_frame(false); 314 toolbar.add_action(*m_change_nick_action); 315 toolbar.add_separator(); 316 toolbar.add_action(*m_join_action); 317 toolbar.add_action(*m_part_action); 318 toolbar.add_separator(); 319 toolbar.add_action(*m_whois_action); 320 toolbar.add_action(*m_open_query_action); 321 toolbar.add_action(*m_close_query_action); 322 323 auto& outer_container = widget.add<GUI::Widget>(); 324 outer_container.set_layout<GUI::VerticalBoxLayout>(); 325 outer_container.layout()->set_margins({ 2, 0, 2, 2 }); 326 327 auto& horizontal_container = outer_container.add<GUI::HorizontalSplitter>(); 328 329 m_window_list = horizontal_container.add<GUI::TableView>(); 330 m_window_list->set_headers_visible(false); 331 m_window_list->set_alternating_row_colors(false); 332 m_window_list->set_size_columns_to_fit_content(true); 333 m_window_list->set_model(m_client->client_window_list_model()); 334 m_window_list->set_activates_on_selection(true); 335 m_window_list->set_size_policy(GUI::SizePolicy::Fixed, GUI::SizePolicy::Fill); 336 m_window_list->set_preferred_size(100, 0); 337 m_window_list->on_activation = [this](auto& index) { 338 set_active_window(m_client->window_at(index.row())); 339 }; 340 341 m_container = horizontal_container.add<GUI::StackWidget>(); 342 m_container->on_active_widget_change = [this](auto*) { 343 update_gui_actions(); 344 }; 345 346 create_window(&m_client, IRCWindow::Server, "Server"); 347} 348 349void IRCAppWindow::set_active_window(IRCWindow& window) 350{ 351 m_container->set_active_widget(&window); 352 window.clear_unread_count(); 353 auto index = m_window_list->model()->index(m_client->window_index(window)); 354 m_window_list->selection().set(index); 355} 356 357void IRCAppWindow::update_gui_actions() 358{ 359 auto* window = static_cast<IRCWindow*>(m_container->active_widget()); 360 bool is_open_channel = window && window->type() == IRCWindow::Type::Channel && window->channel().is_open(); 361 m_change_topic_action->set_enabled(is_open_channel); 362 m_invite_user_action->set_enabled(is_open_channel); 363 m_banlist_action->set_enabled(is_open_channel); 364 m_voice_user_action->set_enabled(is_open_channel); 365 m_devoice_user_action->set_enabled(is_open_channel); 366 m_hop_user_action->set_enabled(is_open_channel); 367 m_dehop_user_action->set_enabled(is_open_channel); 368 m_op_user_action->set_enabled(is_open_channel); 369 m_deop_user_action->set_enabled(is_open_channel); 370 m_kick_user_action->set_enabled(is_open_channel); 371 m_cycle_channel_action->set_enabled(is_open_channel); 372 m_part_action->set_enabled(is_open_channel); 373} 374 375NonnullRefPtr<IRCWindow> IRCAppWindow::create_window(void* owner, IRCWindow::Type type, const String& name) 376{ 377 return m_container->add<IRCWindow>(m_client, owner, type, name); 378}