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