IRCAppWindow.cpp 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352
  1. /*
  2. * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
  3. *
  4. * SPDX-License-Identifier: BSD-2-Clause
  5. */
  6. #include "IRCAppWindow.h"
  7. #include "IRCChannel.h"
  8. #include "IRCWindow.h"
  9. #include "IRCWindowListModel.h"
  10. #include <LibGUI/Action.h>
  11. #include <LibGUI/Application.h>
  12. #include <LibGUI/BoxLayout.h>
  13. #include <LibGUI/InputBox.h>
  14. #include <LibGUI/Menu.h>
  15. #include <LibGUI/Menubar.h>
  16. #include <LibGUI/Splitter.h>
  17. #include <LibGUI/StackWidget.h>
  18. #include <LibGUI/TableView.h>
  19. #include <LibGUI/Toolbar.h>
  20. #include <LibGUI/ToolbarContainer.h>
  21. static IRCAppWindow* s_the;
  22. IRCAppWindow& IRCAppWindow::the()
  23. {
  24. return *s_the;
  25. }
  26. IRCAppWindow::IRCAppWindow(String server, int port)
  27. : m_client(IRCClient::construct(server, port))
  28. {
  29. VERIFY(!s_the);
  30. s_the = this;
  31. set_icon(Gfx::Bitmap::try_load_from_file("/res/icons/16x16/app-irc-client.png"));
  32. update_title();
  33. resize(600, 400);
  34. setup_actions();
  35. setup_menus();
  36. setup_widgets();
  37. setup_client();
  38. }
  39. IRCAppWindow::~IRCAppWindow()
  40. {
  41. }
  42. void IRCAppWindow::update_title()
  43. {
  44. set_title(String::formatted("{}@{}:{} - IRC Client", m_client->nickname(), m_client->hostname(), m_client->port()));
  45. }
  46. void IRCAppWindow::setup_client()
  47. {
  48. m_client->aid_create_window = [this](void* owner, IRCWindow::Type type, const String& name) {
  49. return create_window(owner, type, name);
  50. };
  51. m_client->aid_get_active_window = [this] {
  52. return static_cast<IRCWindow*>(m_container->active_widget());
  53. };
  54. m_client->aid_update_window_list = [this] {
  55. m_window_list->model()->invalidate();
  56. };
  57. m_client->on_nickname_changed = [this](const String&) {
  58. update_title();
  59. };
  60. m_client->on_part_from_channel = [this](auto&) {
  61. update_gui_actions();
  62. };
  63. if (m_client->hostname().is_empty()) {
  64. String value;
  65. if (GUI::InputBox::show(this, value, "Enter server:", "Connect to server") == GUI::InputBox::ExecCancel) {
  66. GUI::Application::the()->quit();
  67. return;
  68. }
  69. m_client->set_server(value, 6667);
  70. }
  71. update_title();
  72. bool success = m_client->connect();
  73. VERIFY(success);
  74. }
  75. void IRCAppWindow::setup_actions()
  76. {
  77. m_join_action = GUI::Action::create("&Join Channel...", { Mod_Ctrl, Key_J }, Gfx::Bitmap::try_load_from_file("/res/icons/16x16/irc-join.png"), [&](auto&) {
  78. String value;
  79. if (GUI::InputBox::show(this, value, "Enter channel name:", "Join Channel") == GUI::InputBox::ExecOK && !value.is_empty())
  80. m_client->handle_join_action(value);
  81. });
  82. m_list_channels_action = GUI::Action::create("&List Channels", Gfx::Bitmap::try_load_from_file("/res/icons/16x16/irc-list.png"), [&](auto&) {
  83. m_client->handle_list_channels_action();
  84. });
  85. m_part_action = GUI::Action::create("&Part from Channel", { Mod_Ctrl, Key_P }, Gfx::Bitmap::try_load_from_file("/res/icons/16x16/irc-part.png"), [this](auto&) {
  86. auto* window = m_client->current_window();
  87. if (!window || window->type() != IRCWindow::Type::Channel) {
  88. return;
  89. }
  90. m_client->handle_part_action(window->channel().name());
  91. });
  92. m_whois_action = GUI::Action::create("&Whois User...", Gfx::Bitmap::try_load_from_file("/res/icons/16x16/irc-whois.png"), [&](auto&) {
  93. String value;
  94. if (GUI::InputBox::show(this, value, "Enter nickname:", "Whois User") == GUI::InputBox::ExecOK && !value.is_empty())
  95. m_client->handle_whois_action(value);
  96. });
  97. m_open_query_action = GUI::Action::create("Open &Query...", { Mod_Ctrl, Key_O }, Gfx::Bitmap::try_load_from_file("/res/icons/16x16/irc-open-query.png"), [&](auto&) {
  98. String value;
  99. if (GUI::InputBox::show(this, value, "Enter nickname:", "Open Query") == GUI::InputBox::ExecOK && !value.is_empty())
  100. m_client->handle_open_query_action(value);
  101. });
  102. m_close_query_action = GUI::Action::create("&Close Query", { Mod_Ctrl, Key_D }, Gfx::Bitmap::try_load_from_file("/res/icons/16x16/irc-close-query.png"), [](auto&) {
  103. outln("FIXME: Implement close-query action");
  104. });
  105. m_change_nick_action = GUI::Action::create("Change &Nickname...", Gfx::Bitmap::try_load_from_file("/res/icons/16x16/irc-nick.png"), [this](auto&) {
  106. String value;
  107. if (GUI::InputBox::show(this, value, "Enter nickname:", "Change Nickname") == GUI::InputBox::ExecOK && !value.is_empty())
  108. m_client->handle_change_nick_action(value);
  109. });
  110. m_change_topic_action = GUI::Action::create("Change &Topic...", Gfx::Bitmap::try_load_from_file("/res/icons/16x16/irc-topic.png"), [this](auto&) {
  111. auto* window = m_client->current_window();
  112. if (!window || window->type() != IRCWindow::Type::Channel) {
  113. return;
  114. }
  115. String value;
  116. if (GUI::InputBox::show(this, value, "Enter topic:", "Change Topic") == GUI::InputBox::ExecOK && !value.is_empty())
  117. m_client->handle_change_topic_action(window->channel().name(), value);
  118. });
  119. m_invite_user_action = GUI::Action::create("&Invite User...", Gfx::Bitmap::try_load_from_file("/res/icons/16x16/irc-invite.png"), [this](auto&) {
  120. auto* window = m_client->current_window();
  121. if (!window || window->type() != IRCWindow::Type::Channel) {
  122. return;
  123. }
  124. String value;
  125. if (GUI::InputBox::show(this, value, "Enter nick:", "Invite User") == GUI::InputBox::ExecOK && !value.is_empty())
  126. m_client->handle_invite_user_action(window->channel().name(), value);
  127. });
  128. m_banlist_action = GUI::Action::create("&Ban List", [this](auto&) {
  129. auto* window = m_client->current_window();
  130. if (!window || window->type() != IRCWindow::Type::Channel) {
  131. return;
  132. }
  133. m_client->handle_banlist_action(window->channel().name());
  134. });
  135. m_voice_user_action = GUI::Action::create("&Voice User...", [this](auto&) {
  136. auto* window = m_client->current_window();
  137. if (!window || window->type() != IRCWindow::Type::Channel) {
  138. return;
  139. }
  140. String value;
  141. if (GUI::InputBox::show(this, value, "Enter nick:", "Voice User") == GUI::InputBox::ExecOK && !value.is_empty())
  142. m_client->handle_voice_user_action(window->channel().name(), value);
  143. });
  144. m_devoice_user_action = GUI::Action::create("DeVoice User...", [this](auto&) {
  145. auto* window = m_client->current_window();
  146. if (!window || window->type() != IRCWindow::Type::Channel) {
  147. return;
  148. }
  149. String value;
  150. if (GUI::InputBox::show(this, value, "Enter nick:", "DeVoice user") == GUI::InputBox::ExecOK && !value.is_empty())
  151. m_client->handle_devoice_user_action(window->channel().name(), value);
  152. });
  153. m_hop_user_action = GUI::Action::create("Hop User", [this](auto&) {
  154. auto* window = m_client->current_window();
  155. if (!window || window->type() != IRCWindow::Type::Channel) {
  156. return;
  157. }
  158. String value;
  159. if (GUI::InputBox::show(this, value, "Enter nick:", "Hop User") == GUI::InputBox::ExecOK && !value.is_empty())
  160. m_client->handle_hop_user_action(window->channel().name(), value);
  161. });
  162. m_dehop_user_action = GUI::Action::create("DeHop User", [this](auto&) {
  163. auto* window = m_client->current_window();
  164. if (!window || window->type() != IRCWindow::Type::Channel) {
  165. return;
  166. }
  167. String value;
  168. if (GUI::InputBox::show(this, value, "Enter nick:", "DeHop User") == GUI::InputBox::ExecOK && !value.is_empty())
  169. m_client->handle_dehop_user_action(window->channel().name(), value);
  170. });
  171. m_op_user_action = GUI::Action::create("&Op User", [this](auto&) {
  172. auto* window = m_client->current_window();
  173. if (!window || window->type() != IRCWindow::Type::Channel) {
  174. return;
  175. }
  176. String value;
  177. if (GUI::InputBox::show(this, value, "Enter nick:", "Op User") == GUI::InputBox::ExecOK && !value.is_empty())
  178. m_client->handle_op_user_action(window->channel().name(), value);
  179. });
  180. m_deop_user_action = GUI::Action::create("DeOp user", [this](auto&) {
  181. auto* window = m_client->current_window();
  182. if (!window || window->type() != IRCWindow::Type::Channel) {
  183. return;
  184. }
  185. String value;
  186. if (GUI::InputBox::show(this, value, "Enter nick:", "DeOp User") == GUI::InputBox::ExecOK && !value.is_empty())
  187. m_client->handle_deop_user_action(window->channel().name(), value);
  188. });
  189. m_kick_user_action = GUI::Action::create("&Kick User", [this](auto&) {
  190. auto* window = m_client->current_window();
  191. if (!window || window->type() != IRCWindow::Type::Channel) {
  192. return;
  193. }
  194. String nick_value;
  195. if (GUI::InputBox::show(this, nick_value, "Enter nick:", "Kick User") != GUI::InputBox::ExecOK || nick_value.is_empty())
  196. return;
  197. String reason_value;
  198. if (GUI::InputBox::show(this, reason_value, "Enter reason:", "Reason") == GUI::InputBox::ExecOK)
  199. m_client->handle_kick_user_action(window->channel().name(), nick_value, reason_value.characters());
  200. });
  201. m_cycle_channel_action = GUI::Action::create("C&ycle Channel", [this](auto&) {
  202. auto* window = m_client->current_window();
  203. if (!window || window->type() != IRCWindow::Type::Channel) {
  204. return;
  205. }
  206. m_client->handle_cycle_channel_action(window->channel().name());
  207. });
  208. }
  209. void IRCAppWindow::setup_menus()
  210. {
  211. auto& file_menu = add_menu("&File");
  212. file_menu.add_action(GUI::CommonActions::make_quit_action([](auto&) {
  213. GUI::Application::the()->quit();
  214. }));
  215. auto& server_menu = add_menu("&Server");
  216. server_menu.add_action(*m_change_nick_action);
  217. server_menu.add_separator();
  218. server_menu.add_action(*m_join_action);
  219. server_menu.add_action(*m_list_channels_action);
  220. server_menu.add_separator();
  221. server_menu.add_action(*m_whois_action);
  222. server_menu.add_action(*m_open_query_action);
  223. server_menu.add_action(*m_close_query_action);
  224. auto& channel_menu = add_menu("&Channel");
  225. channel_menu.add_action(*m_change_topic_action);
  226. channel_menu.add_action(*m_invite_user_action);
  227. channel_menu.add_action(*m_banlist_action);
  228. auto& channel_control_menu = channel_menu.add_submenu("Con&trol");
  229. channel_control_menu.add_action(*m_voice_user_action);
  230. channel_control_menu.add_action(*m_devoice_user_action);
  231. channel_control_menu.add_action(*m_hop_user_action);
  232. channel_control_menu.add_action(*m_dehop_user_action);
  233. channel_control_menu.add_action(*m_op_user_action);
  234. channel_control_menu.add_action(*m_deop_user_action);
  235. channel_control_menu.add_separator();
  236. channel_control_menu.add_action(*m_kick_user_action);
  237. channel_menu.add_separator();
  238. channel_menu.add_action(*m_cycle_channel_action);
  239. channel_menu.add_action(*m_part_action);
  240. auto& help_menu = add_menu("&Help");
  241. help_menu.add_action(GUI::CommonActions::make_about_action("IRC Client", GUI::Icon::default_icon("app-irc-client"), this));
  242. }
  243. void IRCAppWindow::setup_widgets()
  244. {
  245. auto& widget = set_main_widget<GUI::Widget>();
  246. widget.set_fill_with_background_color(true);
  247. widget.set_layout<GUI::VerticalBoxLayout>();
  248. widget.layout()->set_spacing(0);
  249. auto& toolbar_container = widget.add<GUI::ToolbarContainer>();
  250. auto& toolbar = toolbar_container.add<GUI::Toolbar>();
  251. toolbar.set_has_frame(false);
  252. toolbar.add_action(*m_change_nick_action);
  253. toolbar.add_separator();
  254. toolbar.add_action(*m_join_action);
  255. toolbar.add_action(*m_part_action);
  256. toolbar.add_separator();
  257. toolbar.add_action(*m_whois_action);
  258. toolbar.add_action(*m_open_query_action);
  259. toolbar.add_action(*m_close_query_action);
  260. auto& outer_container = widget.add<GUI::Widget>();
  261. outer_container.set_layout<GUI::VerticalBoxLayout>();
  262. outer_container.layout()->set_margins({ 2, 0, 2, 2 });
  263. auto& horizontal_container = outer_container.add<GUI::HorizontalSplitter>();
  264. m_window_list = horizontal_container.add<GUI::TableView>();
  265. m_window_list->set_column_headers_visible(false);
  266. m_window_list->set_alternating_row_colors(false);
  267. m_window_list->set_model(m_client->client_window_list_model());
  268. m_window_list->set_activates_on_selection(true);
  269. m_window_list->set_fixed_width(100);
  270. m_window_list->on_activation = [this](auto& index) {
  271. set_active_window(m_client->window_at(index.row()));
  272. };
  273. m_container = horizontal_container.add<GUI::StackWidget>();
  274. m_container->on_active_widget_change = [this](auto*) {
  275. update_gui_actions();
  276. };
  277. create_window(&m_client, IRCWindow::Server, "Server");
  278. }
  279. void IRCAppWindow::set_active_window(IRCWindow& window)
  280. {
  281. m_container->set_active_widget(&window);
  282. window.clear_unread_count();
  283. auto index = m_window_list->model()->index(m_client->window_index(window));
  284. m_window_list->selection().set(index);
  285. }
  286. void IRCAppWindow::update_gui_actions()
  287. {
  288. auto* window = static_cast<IRCWindow*>(m_container->active_widget());
  289. bool is_open_channel = window && window->type() == IRCWindow::Type::Channel && window->channel().is_open();
  290. m_change_topic_action->set_enabled(is_open_channel);
  291. m_invite_user_action->set_enabled(is_open_channel);
  292. m_banlist_action->set_enabled(is_open_channel);
  293. m_voice_user_action->set_enabled(is_open_channel);
  294. m_devoice_user_action->set_enabled(is_open_channel);
  295. m_hop_user_action->set_enabled(is_open_channel);
  296. m_dehop_user_action->set_enabled(is_open_channel);
  297. m_op_user_action->set_enabled(is_open_channel);
  298. m_deop_user_action->set_enabled(is_open_channel);
  299. m_kick_user_action->set_enabled(is_open_channel);
  300. m_cycle_channel_action->set_enabled(is_open_channel);
  301. m_part_action->set_enabled(is_open_channel);
  302. }
  303. NonnullRefPtr<IRCWindow> IRCAppWindow::create_window(void* owner, IRCWindow::Type type, const String& name)
  304. {
  305. return m_container->add<IRCWindow>(m_client, owner, type, name);
  306. }