1 /*
2 Copyright (C) 2016 - 2018 The Battle for Wesnoth Project https://www.wesnoth.org/
3
4 This program is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation; either version 2 of the License, or
7 (at your option) any later version.
8 This program is distributed in the hope that it will be useful,
9 but WITHOUT ANY WARRANTY.
10
11 See the COPYING file for more details.
12 */
13
14 #define GETTEXT_DOMAIN "wesnoth-lib"
15
16 #include "gui/widgets/chatbox.hpp"
17
18 #include "gui/auxiliary/find_widget.hpp"
19
20 #include "gui/core/register_widget.hpp"
21 #include "gui/widgets/button.hpp"
22 #include "gui/widgets/image.hpp"
23 #include "gui/widgets/label.hpp"
24 #include "gui/widgets/listbox.hpp"
25 #include "gui/widgets/multi_page.hpp"
26 #include "gui/widgets/scroll_label.hpp"
27 #include "gui/widgets/settings.hpp"
28 #include "gui/widgets/text_box.hpp"
29 #include "gui/widgets/window.hpp"
30
31 #include "font/pango/escape.hpp"
32 #include "formatter.hpp"
33 #include "formula/string_utils.hpp"
34 #include "gettext.hpp"
35 #include "log.hpp"
36 #include "preferences/credentials.hpp"
37 #include "preferences/game.hpp"
38 #include "preferences/lobby.hpp"
39 #include "scripting/plugins/manager.hpp"
40 #include "wesnothd_connection.hpp"
41
42 static lg::log_domain log_lobby("lobby");
43 #define DBG_LB LOG_STREAM(debug, log_lobby)
44 #define LOG_LB LOG_STREAM(info, log_lobby)
45 #define ERR_LB LOG_STREAM(err, log_lobby)
46
47 #define LOG_SCOPE_HEADER get_control_type() + " [" + id() + "] " + __func__
48 #define LOG_HEADER LOG_SCOPE_HEADER + ':'
49
50 namespace gui2
51 {
52
53 // ------------ WIDGET -----------{
54
REGISTER_WIDGET(chatbox)55 REGISTER_WIDGET(chatbox)
56
57 chatbox::chatbox(const implementation::builder_chatbox& builder)
58 : container_base(builder, type())
59 , roomlistbox_(nullptr)
60 , chat_log_container_(nullptr)
61 , chat_input_(nullptr)
62 , active_window_(0)
63 , active_window_changed_callback_()
64 , lobby_info_(nullptr)
65 , wesnothd_connection_(nullptr)
66 , log_(nullptr)
67 {
68 // We only implement a RECEIVE_KEYBOARD_FOCUS handler; LOSE_KEYBOARD_FOCUS
69 // isn't needed. This handler forwards focus to the input textbox, meaning
70 // if keyboard_focus is called on a chatbox, distributor::keyboard_focus_
71 // will immediately be set to the input textbox, which will then handle focus
72 // loss itself when applicable. Nothing else happens in the interim while
73 // keyboard_focus_ equals `this` to warrent cleanup.
74 connect_signal<event::RECEIVE_KEYBOARD_FOCUS>(
75 std::bind(&chatbox::signal_handler_receive_keyboard_focus, this, _2));
76 }
77
finalize_setup()78 void chatbox::finalize_setup()
79 {
80 roomlistbox_ = find_widget<listbox>(this, "room_list", false, true);
81
82 // We need to bind a lambda here since switch_to_window is overloaded.
83 // A lambda alone would be more verbose because it'd need to specify all the parameters.
84 connect_signal_notify_modified(*roomlistbox_,
85 std::bind([this]() { switch_to_window(roomlistbox_->get_selected_row()); }));
86
87 chat_log_container_ = find_widget<multi_page>(this, "chat_log_container", false, true);
88
89 chat_input_ = find_widget<text_box>(this, "chat_input", false, true);
90
91 connect_signal_pre_key_press(*chat_input_,
92 std::bind(&chatbox::chat_input_keypress_callback, this, _5));
93 }
94
load_log(std::map<std::string,chatroom_log> & log,bool show_lobby)95 void chatbox::load_log(std::map<std::string, chatroom_log>& log, bool show_lobby)
96 {
97 for(const auto& l : log) {
98 const bool is_lobby = l.first == "lobby";
99
100 if(!show_lobby && is_lobby && !l.second.whisper) {
101 continue;
102 }
103
104 find_or_create_window(l.first, l.second.whisper, true, !is_lobby, l.second.log);
105 }
106
107 log_ = &log;
108 }
109
active_window_changed()110 void chatbox::active_window_changed()
111 {
112 lobby_chat_window& t = open_windows_[active_window_];
113
114 // Clear pending messages notification in room listbox
115 grid* grid = roomlistbox_->get_row_grid(active_window_);
116 find_widget<image>(grid, "pending_messages", false).set_visible(widget::visibility::hidden);
117
118 t.pending_messages = 0;
119
120 if(active_window_changed_callback_) {
121 active_window_changed_callback_();
122 }
123 }
124
switch_to_window(lobby_chat_window * t)125 void chatbox::switch_to_window(lobby_chat_window* t)
126 {
127 switch_to_window(t - &open_windows_[0]);
128 }
129
switch_to_window(size_t id)130 void chatbox::switch_to_window(size_t id)
131 {
132 active_window_ = id;
133 assert(active_window_ < open_windows_.size());
134
135 chat_log_container_->select_page(active_window_);
136 roomlistbox_->select_row(active_window_);
137
138 // Grab input focus
139 get_window()->keyboard_capture(chat_input_);
140
141 active_window_changed();
142 }
143
chat_input_keypress_callback(const SDL_Keycode key)144 void chatbox::chat_input_keypress_callback(const SDL_Keycode key)
145 {
146 std::string input = chat_input_->get_value();
147 if(input.empty() || chat_input_->is_composing()) {
148 return;
149 }
150
151 switch(key) {
152 case SDLK_RETURN:
153 case SDLK_KP_ENTER: {
154 if(input[0] == '/') {
155 // TODO: refactor do_speak so it uses context information about
156 // opened window, so e.g. /ignore in a whisper session ignores
157 // the other party without having to specify it's nick.
158 chat_handler::do_speak(input);
159 } else {
160 lobby_chat_window& t = open_windows_[active_window_];
161
162 if(t.whisper) {
163 send_whisper(t.name, input);
164 add_whisper_sent(t.name, input);
165 } else {
166 send_chat_room_message(t.name, input);
167 add_chat_room_message_sent(t.name, input);
168 }
169 }
170
171 chat_input_->save_to_history();
172 chat_input_->set_value("");
173
174 break;
175 }
176
177 case SDLK_TAB: {
178 // TODO: very inefficient! Very! D:
179 std::vector<std::string> matches;
180 for(const auto& ui : lobby_info_->users()) {
181 if(ui.name != preferences::login()) {
182 matches.push_back(ui.name);
183 }
184 }
185
186 const bool line_start = utils::word_completion(input, matches);
187
188 if(matches.empty()) {
189 return;
190 }
191
192 if(matches.size() == 1) {
193 input.append(line_start ? ": " : " ");
194 } else {
195 std::string completion_list = utils::join(matches, " ");
196 append_to_chatbox(completion_list);
197 }
198
199 chat_input_->set_value(input);
200
201 break;
202 }
203
204 default:
205 break;
206 }
207 }
208
append_to_chatbox(const std::string & text,const bool force_scroll)209 void chatbox::append_to_chatbox(const std::string& text, const bool force_scroll)
210 {
211 append_to_chatbox(text, active_window_, force_scroll);
212 }
213
append_to_chatbox(const std::string & text,size_t id,const bool force_scroll)214 void chatbox::append_to_chatbox(const std::string& text, size_t id, const bool force_scroll)
215 {
216 grid& grid = chat_log_container_->page_grid(id);
217
218 scroll_label& log = find_widget<scroll_label>(&grid, "log_text", false);
219 const bool chatbox_at_end = log.vertical_scrollbar_at_end();
220 const unsigned chatbox_position = log.get_vertical_scrollbar_item_position();
221
222 const std::string new_text = formatter()
223 << log.get_label() << "\n" << "<span color='#bcb088'>" << preferences::get_chat_timestamp(time(0)) << text << "</span>";
224
225 log.set_use_markup(true);
226 log.set_label(new_text);
227
228 if(log_ != nullptr) {
229 try {
230 const std::string& room_name = open_windows_[id].name;
231 log_->at(room_name).log = new_text;
232 } catch(const std::out_of_range&) {
233 }
234 }
235
236 if(chatbox_at_end || force_scroll) {
237 log.scroll_vertical_scrollbar(scrollbar_base::END);
238 } else {
239 log.set_vertical_scrollbar_item_position(chatbox_position);
240 }
241 }
242
send_chat_message(const std::string & message,bool)243 void chatbox::send_chat_message(const std::string& message, bool /*allies_only*/)
244 {
245 add_chat_message(time(nullptr), preferences::login(), 0, message);
246
247 ::config c {"message", ::config {"message", message, "sender", preferences::login()}};
248 send_to_server(c);
249 }
250
user_relation_changed(const std::string &)251 void chatbox::user_relation_changed(const std::string& /*name*/)
252 {
253 if(active_window_changed_callback_) {
254 active_window_changed_callback_();
255 }
256 }
257
add_chat_message(const time_t &,const std::string & speaker,int,const std::string & message,events::chat_handler::MESSAGE_TYPE)258 void chatbox::add_chat_message(const time_t& /*time*/,
259 const std::string& speaker,
260 int /*side*/,
261 const std::string& message,
262 events::chat_handler::MESSAGE_TYPE /*type*/)
263 {
264 std::string text;
265
266 // FIXME: the chat_command_handler class (which handles chat commands) dispatches a
267 // message consisting of '/me insert text here' in the case the '/me' or '/emote'
268 // commands are used, so we need to do some manual preprocessing here.
269 if(message.compare(0, 4, "/me ") == 0) {
270 text = formatter() << "<i>" << speaker << " " << font::escape_text(message.substr(4)) << "</i>";
271 } else {
272 text = formatter() << "<b>" << speaker << ":</b> " << font::escape_text(message);
273 }
274
275 append_to_chatbox(text);
276 }
277
add_whisper_sent(const std::string & receiver,const std::string & message)278 void chatbox::add_whisper_sent(const std::string& receiver, const std::string& message)
279 {
280 if(whisper_window_active(receiver)) {
281 add_active_window_message(preferences::login(), message, true);
282 } else if(lobby_chat_window* t = whisper_window_open(receiver, preferences::auto_open_whisper_windows())) {
283 switch_to_window(t);
284 add_active_window_message(preferences::login(), message, true);
285 } else {
286 add_active_window_whisper(VGETTEXT("whisper to $receiver", {{"receiver", receiver}}), message, true);
287 }
288
289 lobby_info_->get_whisper_log(receiver).add_message(preferences::login(), message);
290 }
291
add_whisper_received(const std::string & sender,const std::string & message)292 void chatbox::add_whisper_received(const std::string& sender, const std::string& message)
293 {
294 bool can_go_to_active = !preferences::whisper_friends_only() || preferences::is_friend(sender);
295 bool can_open_new = preferences::auto_open_whisper_windows() && can_go_to_active;
296
297 lobby_info_->get_whisper_log(sender).add_message(sender, message);
298
299 if(whisper_window_open(sender, can_open_new)) {
300 if(whisper_window_active(sender)) {
301 add_active_window_message(sender, message);
302
303 do_notify(mp::NOTIFY_WHISPER, sender, message);
304 } else {
305 add_whisper_window_whisper(sender, message);
306 increment_waiting_whispers(sender);
307
308 do_notify(mp::NOTIFY_WHISPER_OTHER_WINDOW, sender, message);
309 }
310 } else if(can_go_to_active) {
311 add_active_window_whisper(sender, message);
312 do_notify(mp::NOTIFY_WHISPER, sender, message);
313 } else {
314 LOG_LB << "Ignoring whisper from " << sender << "\n";
315 }
316 }
317
add_chat_room_message_sent(const std::string & room,const std::string & message)318 void chatbox::add_chat_room_message_sent(const std::string& room, const std::string& message)
319 {
320 lobby_chat_window* t = room_window_open(room, false);
321 if(!t) {
322 LOG_LB << "Cannot add sent message to ui for room " << room << ", player not in the room\n";
323 return;
324 }
325
326 // Do not open room window here. The player should be in the room before sending messages
327 mp::room_info* ri = lobby_info_->get_room(room);
328 assert(ri);
329
330 if(!room_window_active(room)) {
331 switch_to_window(t);
332 }
333
334 ri->log().add_message(preferences::login(), message);
335 add_active_window_message(preferences::login(), message, true);
336 }
337
add_chat_room_message_received(const std::string & room,const std::string & speaker,const std::string & message)338 void chatbox::add_chat_room_message_received(const std::string& room,
339 const std::string& speaker,
340 const std::string& message)
341 {
342 mp::room_info* ri = lobby_info_->get_room(room);
343 if(!ri) {
344 LOG_LB << "Discarding message to room " << room << " from " << speaker << " (room not open)\n";
345 return;
346 }
347
348 mp::notify_mode notify_mode = mp::NOTIFY_NONE;
349 ri->log().add_message(speaker, message);
350
351 if(room_window_active(room)) {
352 add_active_window_message(speaker, message);
353 notify_mode = mp::NOTIFY_MESSAGE;
354 } else {
355 add_room_window_message(room, speaker, message);
356 increment_waiting_messages(room);
357 notify_mode = mp::NOTIFY_MESSAGE_OTHER_WINDOW;
358 }
359
360 if(speaker == "server") {
361 notify_mode = mp::NOTIFY_SERVER_MESSAGE;
362 } else if (utils::word_match(message, preferences::login())) {
363 notify_mode = mp::NOTIFY_OWN_NICK;
364 } else if (preferences::is_friend(speaker)) {
365 notify_mode = mp::NOTIFY_FRIEND_MESSAGE;
366 }
367
368 do_notify(notify_mode, speaker, message);
369 }
370
whisper_window_active(const std::string & name)371 bool chatbox::whisper_window_active(const std::string& name)
372 {
373 const lobby_chat_window& t = open_windows_[active_window_];
374 return t.name == name && t.whisper == true;
375 }
376
room_window_active(const std::string & room)377 bool chatbox::room_window_active(const std::string& room)
378 {
379 const lobby_chat_window& t = open_windows_[active_window_];
380 return t.name == room && t.whisper == false;
381 }
382
room_window_open(const std::string & room,const bool open_new,const bool allow_close)383 lobby_chat_window* chatbox::room_window_open(const std::string& room, const bool open_new, const bool allow_close)
384 {
385 return find_or_create_window(room, false, open_new, allow_close,
386 VGETTEXT("Room <i>“$name”</i> joined", { { "name", translation::dsgettext("wesnoth-lib", room.c_str()) } }));
387 }
388
whisper_window_open(const std::string & name,bool open_new)389 lobby_chat_window* chatbox::whisper_window_open(const std::string& name, bool open_new)
390 {
391 return find_or_create_window(name, true, open_new, true,
392 VGETTEXT("Whisper session with <i>“$name”</i> started. "
393 "If you do not want to receive messages from this user, type <i>/ignore $name</i>", { { "name", name } }));
394 }
395
find_or_create_window(const std::string & name,const bool whisper,const bool open_new,const bool allow_close,const std::string & initial_text)396 lobby_chat_window* chatbox::find_or_create_window(const std::string& name,
397 const bool whisper,
398 const bool open_new,
399 const bool allow_close,
400 const std::string& initial_text)
401 {
402 for(auto& t : open_windows_) {
403 if(t.name == name && t.whisper == whisper) {
404 return &t;
405 }
406 }
407
408 if(!open_new) {
409 return nullptr;
410 }
411
412 open_windows_.emplace_back(name, whisper);
413
414 //
415 // Add a new chat log page.
416 //
417 string_map item;
418 item["use_markup"] = "true";
419 item["label"] = initial_text;
420 std::map<std::string, string_map> data{{"log_text", item}};
421
422 if(!whisper) {
423 lobby_info_->open_room(name);
424 }
425
426 if(log_ != nullptr) {
427 log_->emplace(name, chatroom_log{item["label"], whisper});
428 }
429
430 chat_log_container_->add_page(data);
431
432 //
433 // Add a new room window tab.
434 //
435 data.clear();
436 item.clear();
437
438 if(!whisper) {
439 item["label"] = translation::dsgettext("wesnoth-lib", name.c_str());
440 } else {
441 item["label"] = "<" + name + ">";
442 }
443
444 data.emplace("room", item);
445
446 grid& row_grid = roomlistbox_->add_row(data);
447
448 //
449 // Set up the Close Window button.
450 //
451 button& close_button = find_widget<button>(&row_grid, "close_window", false);
452
453 if(!allow_close) {
454 close_button.set_visible(widget::visibility::hidden);
455 } else {
456 connect_signal_mouse_left_click(close_button,
457 std::bind(&chatbox::close_window_button_callback, this, open_windows_.back().name, _3, _4));
458 }
459
460 return &open_windows_.back();
461 }
462
close_window_button_callback(std::string room_name,bool & handled,bool & halt)463 void chatbox::close_window_button_callback(std::string room_name, bool& handled, bool& halt)
464 {
465 const int index = std::distance(open_windows_.begin(), std::find_if(open_windows_.begin(), open_windows_.end(),
466 [&room_name](const lobby_chat_window& room) { return room.name == room_name; }
467 ));
468
469 close_window(index);
470
471 handled = halt = true;
472 }
473
send_to_server(const::config & cfg)474 void chatbox::send_to_server(const ::config& cfg)
475 {
476 if(wesnothd_connection_) {
477 wesnothd_connection_->send_data(cfg);
478 }
479 }
480
increment_waiting_whispers(const std::string & name)481 void chatbox::increment_waiting_whispers(const std::string& name)
482 {
483 if(lobby_chat_window* t = whisper_window_open(name, false)) {
484 ++t->pending_messages;
485
486 if(t->pending_messages == 1) {
487 DBG_LB << "do whisper pending mark row " << (t - &open_windows_[0]) << " with " << t->name << "\n";
488
489 grid* grid = roomlistbox_->get_row_grid(t - &open_windows_[0]);
490 find_widget<image>(grid, "pending_messages", false).set_visible(widget::visibility::visible);
491 }
492 }
493 }
494
increment_waiting_messages(const std::string & room)495 void chatbox::increment_waiting_messages(const std::string& room)
496 {
497 if(lobby_chat_window* t = room_window_open(room, false)) {
498 ++t->pending_messages;
499
500 if(t->pending_messages == 1) {
501 int idx = t - &open_windows_[0];
502
503 DBG_LB << "do room pending mark row " << idx << " with " << t->name << "\n";
504
505 grid* grid = roomlistbox_->get_row_grid(idx);
506 find_widget<image>(grid, "pending_messages", false).set_visible(widget::visibility::visible);
507 }
508 }
509 }
510
add_whisper_window_whisper(const std::string & sender,const std::string & message)511 void chatbox::add_whisper_window_whisper(const std::string& sender, const std::string& message)
512 {
513 lobby_chat_window* t = whisper_window_open(sender, false);
514 if(!t) {
515 ERR_LB << "Whisper window not open in add_whisper_window_whisper for " << sender << "\n";
516 return;
517 }
518
519 const std::string text = formatter() << "<b>" << sender << ":</b> " << font::escape_text(message);
520 append_to_chatbox(text, t - &open_windows_[0], false);
521 }
522
add_active_window_whisper(const std::string & sender,const std::string & message,const bool force_scroll)523 void chatbox::add_active_window_whisper(const std::string& sender,
524 const std::string& message,
525 const bool force_scroll)
526 {
527 const std::string text = formatter() << "<b>" << "whisper: " << sender << ":</b> " << font::escape_text(message);
528 append_to_chatbox(text, force_scroll);
529 }
530
close_window(size_t idx)531 void chatbox::close_window(size_t idx)
532 {
533 const lobby_chat_window& t = open_windows_[idx];
534
535 DBG_LB << "Close window " << idx << " - " << t.name << "\n";
536
537 // Can't close the lobby!
538 if((t.name == "lobby" && t.whisper == false) || open_windows_.size() == 1) {
539 return;
540 }
541
542 if(t.whisper == false) {
543 // closing a room window -- send a part to the server
544 ::config data, msg;
545 msg["room"] = t.name;
546 msg["player"] = preferences::login();
547 data.add_child("room_part", std::move(msg));
548
549 send_to_server(data);
550 }
551
552 // Check if we're closing the currently-active window.
553 const bool active_changed = idx == active_window_;
554
555 if(active_window_ == open_windows_.size() - 1) {
556 --active_window_;
557 }
558
559 if(t.whisper) {
560 lobby_info_->get_whisper_log(t.name).clear();
561 } else {
562 lobby_info_->close_room(t.name);
563 }
564
565 if(log_ != nullptr) {
566 log_->erase(t.name);
567 }
568
569 open_windows_.erase(open_windows_.begin() + idx);
570
571 roomlistbox_->remove_row(idx);
572 roomlistbox_->select_row(active_window_);
573
574 chat_log_container_->remove_page(idx);
575 chat_log_container_->select_page(active_window_);
576
577 if(active_changed) {
578 active_window_changed();
579 }
580 }
581
add_room_window_message(const std::string & room,const std::string & sender,const std::string & message)582 void chatbox::add_room_window_message(const std::string& room,
583 const std::string& sender,
584 const std::string& message)
585 {
586 lobby_chat_window* t = room_window_open(room, false);
587 if(!t) {
588 ERR_LB << "Room window not open in add_room_window_message for " << room << "\n";
589 return;
590 }
591
592 const std::string text = formatter() << "<b>" << sender << ":</b> " << font::escape_text(message);
593 append_to_chatbox(text, t - &open_windows_[0], false);
594 }
595
add_active_window_message(const std::string & sender,const std::string & message,const bool force_scroll)596 void chatbox::add_active_window_message(const std::string& sender,
597 const std::string& message,
598 const bool force_scroll)
599 {
600 const std::string text = formatter() << "<b>" << sender << ":</b> " << font::escape_text(message);
601 append_to_chatbox(text, force_scroll);
602 }
603
active_window_room()604 mp::room_info* chatbox::active_window_room()
605 {
606 const lobby_chat_window& t = open_windows_[active_window_];
607 if(t.whisper) {
608 return nullptr;
609 }
610
611 return lobby_info_->get_room(t.name);
612 }
613
process_room_join(const::config & data)614 void chatbox::process_room_join(const ::config& data)
615 {
616 const std::string& room = data["room"];
617 const std::string& player = data["player"];
618
619 DBG_LB << "room join: " << room << " " << player << "\n";
620
621 mp::room_info* r = lobby_info_->get_room(room);
622 if(r) {
623 if(player == preferences::login()) {
624 if(const auto& members = data.child("members")) {
625 r->process_room_members(members);
626 }
627 } else {
628 r->add_member(player);
629
630 /* TODO: add/use preference */
631 add_room_window_message(room, "server", VGETTEXT("$player has entered the room", {{"player", player}}));
632 }
633
634 if(r == active_window_room()) {
635 active_window_changed_callback_();
636 }
637 } else {
638 if(player == preferences::login()) {
639 lobby_chat_window* t = room_window_open(room, true);
640
641 lobby_info_->open_room(room);
642 r = lobby_info_->get_room(room);
643 assert(r);
644
645 if(const auto& members = data.child("members")) {
646 r->process_room_members(members);
647 }
648
649 switch_to_window(t);
650
651 const std::string& topic = data["topic"];
652 if(!topic.empty()) {
653 add_chat_room_message_received("room", "server", room + ": " + topic);
654 }
655 } else {
656 LOG_LB << "Discarding join info for a room the player is not in\n";
657 }
658 }
659 }
660
process_room_part(const::config & data)661 void chatbox::process_room_part(const ::config& data)
662 {
663 // TODO: close room window when the part message is sent
664 const std::string& room = data["room"];
665 const std::string& player = data["player"];
666
667 DBG_LB << "Room part: " << room << " " << player << "\n";
668
669 if(mp::room_info* r = lobby_info_->get_room(room)) {
670 r->remove_member(player);
671
672 /* TODO: add/use preference */
673 add_room_window_message(room, "server", VGETTEXT("$player has left the room", {{"player", player}}));
674 if(active_window_room() == r) {
675 active_window_changed_callback_();
676 }
677 } else {
678 LOG_LB << "Discarding part info for a room the player is not in\n";
679 }
680 }
681
process_room_query_response(const::config & data)682 void chatbox::process_room_query_response(const ::config& data)
683 {
684 const std::string& room = data["room"];
685 const std::string& message = data["message"];
686
687 DBG_LB << "room query response: " << room << " " << message << "\n";
688
689 if(room.empty()) {
690 if(!message.empty()) {
691 add_active_window_message("server", message);
692 }
693
694 if(const ::config& rooms = data.child("rooms")) {
695 // TODO: this should really open a nice join room dialog instead
696 std::stringstream ss;
697 ss << "Rooms:";
698
699 for(const auto & r : rooms.child_range("room")) {
700 ss << " " << r["name"];
701 }
702
703 add_active_window_message("server", ss.str());
704 }
705 } else {
706 if(room_window_open(room, false)) {
707 if(!message.empty()) {
708 add_chat_room_message_received(room, "server", message);
709 }
710
711 if(const ::config& members = data.child("members")) {
712 mp::room_info* r = lobby_info_->get_room(room);
713 assert(r);
714 r->process_room_members(members);
715 if(r == active_window_room()) {
716 active_window_changed_callback_();
717 }
718 }
719 } else {
720 if(!message.empty()) {
721 add_active_window_message("server", room + ": " + message);
722 }
723 }
724 }
725 }
726
process_message(const::config & data,bool whisper)727 void chatbox::process_message(const ::config& data, bool whisper /*= false*/)
728 {
729 std::string sender = data["sender"];
730 DBG_LB << "process message from " << sender << " " << (whisper ? "(w)" : "")
731 << ", len " << data["message"].str().size() << '\n';
732
733 if(preferences::is_ignored(sender)) {
734 return;
735 }
736
737 const std::string& message = data["message"];
738 preferences::parse_admin_authentication(sender, message);
739
740 if(whisper) {
741 add_whisper_received(sender, message);
742 } else {
743 std::string room = data["room"];
744
745 // Attempt to send to the currently active room first.
746 if(room.empty()) {
747 LOG_LB << "Message without a room from " << sender << ", falling back to active window\n";
748 room = open_windows_[active_window_].name;
749 }
750
751 // If we still don't have a name, fall back to lobby.
752 if(room.empty()) {
753 LOG_LB << "Message without a room from " << sender << ", assuming lobby\n";
754 room = "lobby";
755 }
756
757 add_chat_room_message_received(room, sender, message);
758 }
759
760 // Notify plugins about the message
761 ::config plugin_data = data;
762 plugin_data["whisper"] = whisper;
763 plugins_manager::get()->notify_event("chat", plugin_data);
764 }
765
process_network_data(const::config & data)766 void chatbox::process_network_data(const ::config& data)
767 {
768 if(const ::config& message = data.child("message")) {
769 process_message(message);
770 } else if(const ::config& whisper = data.child("whisper")) {
771 process_message(whisper, true);
772 } else if(const ::config& room_join = data.child("room_join")) {
773 process_room_join(room_join);
774 } else if(const ::config& room_part = data.child("room_part")) {
775 process_room_part(room_part);
776 } else if(const ::config& room_query_response = data.child("room_query_response")) {
777 process_room_query_response(room_query_response);
778 }
779 }
780
signal_handler_receive_keyboard_focus(const event::ui_event event)781 void chatbox::signal_handler_receive_keyboard_focus(const event::ui_event event)
782 {
783 DBG_GUI_E << LOG_HEADER << ' ' << event << ".\n";
784
785 // Forward focus to the input textbox.
786 get_window()->keyboard_capture(chat_input_);
787 }
788
789 // }---------- DEFINITION ---------{
790
chatbox_definition(const config & cfg)791 chatbox_definition::chatbox_definition(const config& cfg)
792 : styled_widget_definition(cfg)
793 {
794 load_resolutions<resolution>(cfg);
795 }
796
resolution(const config & cfg)797 chatbox_definition::resolution::resolution(const config& cfg)
798 : resolution_definition(cfg), grid()
799 {
800 state.emplace_back(cfg.child("background"));
801 state.emplace_back(cfg.child("foreground"));
802
803 const config& child = cfg.child("grid");
804 VALIDATE(child, _("No grid defined."));
805
806 grid = std::make_shared<builder_grid>(child);
807 }
808 // }---------- BUILDER -----------{
809
810 namespace implementation
811 {
812
builder_chatbox(const config & cfg)813 builder_chatbox::builder_chatbox(const config& cfg)
814 : builder_styled_widget(cfg)
815 {
816 }
817
build() const818 widget* builder_chatbox::build() const
819 {
820 chatbox* widget = new chatbox(*this);
821
822 DBG_GUI_G << "Window builder: placed unit preview pane '" << id
823 << "' with definition '" << definition << "'.\n";
824
825 const auto conf = widget->cast_config_to<chatbox_definition>();
826 assert(conf);
827
828 widget->init_grid(conf->grid);
829 widget->finalize_setup();
830
831 return widget;
832 }
833
834 } // namespace implementation
835
836 // }------------ END --------------
837
838 } // namespace gui2
839