The Battle for Wesnoth  1.17.12+dev
display_chat_manager.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2014 - 2022
3  by Chris Beck <render787@gmail.com>
4  Part of the Battle for Wesnoth Project https://www.wesnoth.org/
5 
6  This program is free software; you can redistribute it and/or modify
7  it under the terms of the GNU General Public License as published by
8  the Free Software Foundation; either version 2 of the License, or
9  (at your option) any later version.
10  This program is distributed in the hope that it will be useful,
11  but WITHOUT ANY WARRANTY.
12 
13  See the COPYING file for more details.
14 */
15 
16 #include "display_chat_manager.hpp"
17 
19 #include "display.hpp"
20 #include "floating_label.hpp"
21 #include "game_board.hpp" // <-- only needed for is_observer()
22 #include "preferences/game.hpp"
23 #include "log.hpp"
24 #include "font/sdl_ttf_compat.hpp"
25 #include "mp_ui_alerts.hpp"
27 #include "color.hpp"
30 #include "video.hpp" // only for faked
31 
32 #include <SDL2/SDL_timer.h>
33 
34 static lg::log_domain log_engine("engine");
35 #define ERR_NG LOG_STREAM(err, log_engine)
36 
37 namespace {
38  const int chat_message_border = 5;
39  const int chat_message_x = 10;
40  const color_t chat_message_color {255,255,255,SDL_ALPHA_OPAQUE};
41  const color_t chat_message_bg {0,0,0,140};
42 }
43 
45  : speaker_handle(speaker), handle(h), created_at(SDL_GetTicks())
46 {}
47 
48 
49 void display_chat_manager::add_chat_message(const std::time_t& time, const std::string& speaker,
50  int side, const std::string& message, events::chat_handler::MESSAGE_TYPE type,
51  bool bell)
52 {
53  const bool whisper = speaker.find("whisper: ") == 0;
54  std::string sender = speaker;
55  if (whisper) {
56  sender.assign(speaker, 9, speaker.size());
57  add_whisperer( sender );
58  }
59  //remove disconnected user from whisperer
60  std::string::size_type pos = message.find(" has disconnected");
61  if (pos != std::string::npos){
62  for(std::set<std::string>::const_iterator w = whisperers().begin(); w != whisperers().end(); ++w){
63  if (*w == message.substr(0,pos)) remove_whisperer(*w);
64  }
65  }
66 
67  if (!preferences::parse_should_show_lobby_join(sender, message)) return;
68  if (preferences::is_ignored(sender)) return;
69 
70  //preferences::parse_admin_authentication(sender, message); TODO: replace
71 
72  bool is_observer = false;
73  { //TODO: Clean this block up somehow
74 
75  const game_board * board = dynamic_cast<const game_board*>(&my_disp_.get_disp_context());
76 
77  if (board) {
78  is_observer = board->is_observer();
79  }
80  }
81 
82  if (bell) {
83  if ((type == events::chat_handler::MESSAGE_PRIVATE && (!is_observer || whisper))
84  || utils::word_match(message, preferences::login())) {
85  mp::ui_alerts::private_message(false, sender, message);
86  } else if (preferences::is_friend(sender)) {
87  mp::ui_alerts::friend_message(false, sender, message);
88  } else if (sender == "server") {
89  mp::ui_alerts::server_message(false, sender, message);
90  } else {
91  mp::ui_alerts::public_message(false, sender, message);
92  }
93  }
94 
95  bool action = false;
96 
97  std::string msg;
98 
99  if (message.compare(0,4,"/me ") == 0) {
100  msg.assign(message, 4, message.size());
101  action = true;
102  } else {
103  msg = message;
104  }
105 
106  try {
107  // We've had a joker who send an invalid utf-8 message to crash clients
108  // so now catch the exception and ignore the message.
110  } catch (utf8::invalid_utf8_exception&) {
111  ERR_NG << "Invalid utf-8 found, chat message is ignored.";
112  return;
113  }
114 
115  int ypos = chat_message_x;
116  for(std::vector<chat_message>::const_iterator m = chat_messages_.begin(); m != chat_messages_.end(); ++m) {
117  ypos += std::max(font::get_floating_label_rect(m->handle).h,
118  font::get_floating_label_rect(m->speaker_handle).h);
119  }
120  color_t speaker_color {255,255,255,SDL_ALPHA_OPAQUE};
121  if(side >= 1) {
122  speaker_color = team::get_side_color_range(side).mid();
123  }
124 
125  color_t message_color = chat_message_color;
126  std::stringstream str;
127  std::stringstream message_str;
128 
130  if(action) {
131  str << "<" << speaker << " " << msg << ">";
132  message_color = speaker_color;
133  message_str << " ";
134  } else {
135  if (!speaker.empty())
136  str << "<" << speaker << ">";
137  message_str << msg;
138  }
139  } else {
140  if(action) {
141  str << "*" << speaker << " " << msg << "*";
142  message_color = speaker_color;
143  message_str << " ";
144  } else {
145  if (!speaker.empty())
146  str << "*" << speaker << "*";
147  message_str << msg;
148  }
149  }
150 
151  // Prepend message with timestamp.
152  std::stringstream message_complete;
153  message_complete << preferences::get_chat_timestamp(time) << str.str();
154 
155  const SDL_Rect rect = my_disp_.map_outside_area();
156 
157  font::floating_label spk_flabel(message_complete.str());
158  spk_flabel.set_font_size(font::SIZE_15);
159  spk_flabel.set_color(speaker_color);
160  spk_flabel.set_position(rect.x + chat_message_x, rect.y + ypos);
161  spk_flabel.set_clip_rect(rect);
162  spk_flabel.set_alignment(font::LEFT_ALIGN);
163  spk_flabel.set_bg_color(chat_message_bg);
164  spk_flabel.set_border_size(chat_message_border);
165  spk_flabel.use_markup(false);
166 
167  int speaker_handle = font::add_floating_label(spk_flabel);
168 
169  font::floating_label msg_flabel(message_str.str());
170  msg_flabel.set_font_size(font::SIZE_15);
171  msg_flabel.set_color(message_color);
172  msg_flabel.set_position(rect.x + chat_message_x + font::get_floating_label_rect(speaker_handle).w,
173  rect.y + ypos);
174  msg_flabel.set_clip_rect(rect);
175  msg_flabel.set_alignment(font::LEFT_ALIGN);
176  msg_flabel.set_bg_color(chat_message_bg);
177  msg_flabel.set_border_size(chat_message_border);
178  msg_flabel.use_markup(false);
179 
180  int message_handle = font::add_floating_label(msg_flabel);
181 
182  chat_messages_.emplace_back(speaker_handle,message_handle);
183 
185 }
186 
187 static unsigned int safe_subtract(unsigned int a, unsigned int b)
188 {
189  return (a > b) ? a - b : 0;
190 }
191 
193 {
194  //NOTE: prune_chat_messages(false) seems to be only called when a new message is added, which in
195  // particular means the aging feature won't work unless new messages are addded regularly
196  const unsigned message_aging = preferences::chat_message_aging();
197  const unsigned max_chat_messages = preferences::chat_lines();
198  const bool enable_aging = message_aging != 0;
199 
200  const unsigned remove_before = enable_aging ? safe_subtract(SDL_GetTicks(), message_aging * 60 * 1000) : 0;
201  int movement = 0;
202 
203  if(enable_aging || remove_all || chat_messages_.size() > max_chat_messages) {
204  while (!chat_messages_.empty() &&
205  (remove_all ||
206  chat_messages_.front().created_at < remove_before ||
207  chat_messages_.size() > max_chat_messages))
208  {
209  const chat_message &old = chat_messages_.front();
210  movement += font::get_floating_label_rect(old.handle).h;
213  chat_messages_.erase(chat_messages_.begin());
214  }
215  }
216 
217  for(const chat_message &cm : chat_messages_) {
218  font::move_floating_label(cm.speaker_handle, 0, - movement);
219  font::move_floating_label(cm.handle, 0, - movement);
220  }
221 }
Game board class.
Definition: game_board.hpp:52
void public_message(bool is_lobby, const std::string &sender, const std::string &message)
static unsigned int safe_subtract(unsigned int a, unsigned int b)
void remove_floating_label(int handle, int fadeout)
removes the floating label given by &#39;handle&#39; from the screen
const std::set< std::string > & whisperers() const
#define a
static const color_range get_side_color_range(int side)
Definition: team.cpp:945
void private_message(bool is_lobby, const std::string &sender, const std::string &message)
int chat_lines()
Definition: game.cpp:887
#define h
static void msg(const char *act, debug_info &i, const char *to="", const char *result="")
Definition: debugger.cpp:110
void set_font_size(int font_size)
std::vector< chat_message > chat_messages_
void friend_message(bool is_lobby, const std::string &sender, const std::string &message)
void add_chat_message(const std::time_t &time, const std::string &speaker, int side, const std::string &msg, events::chat_handler::MESSAGE_TYPE type, bool bell)
void move_floating_label(int handle, double xmove, double ymove)
moves the floating label given by &#39;handle&#39; by (xmove,ymove)
#define b
bool is_friend(const std::string &nick)
Definition: game.cpp:265
void add_whisperer(const std::string &nick)
const int SIZE_15
Definition: constants.cpp:28
The basic class for representing 8-bit RGB or RGBA colour values.
Definition: color.hpp:58
#define ERR_NG
bool headless()
The game is running headless.
Definition: video.cpp:143
std::string pango_word_wrap(const std::string &unwrapped_text, int font_size, int max_width, int max_height, int max_lines, bool)
Uses Pango to word wrap text.
map_display and display: classes which take care of displaying the map and game-data on the screen...
void prune_chat_messages(bool remove_all=false)
void remove_whisperer(const std::string &nick)
bool is_ignored(const std::string &nick)
Definition: game.cpp:277
std::string login()
static lg::log_domain log_engine("engine")
Thrown by operations encountering invalid UTF-8 data.
bool is_observer() const
Check if we are an observer in this game.
An abstract description of a rectangle with integer coordinates.
Definition: rect.hpp:46
const display_context & get_disp_context() const
Definition: display.hpp:178
int add_floating_label(const floating_label &flabel)
add a label floating on the screen above everything else.
SDL_Rect get_floating_label_rect(int handle)
int w
std::string get_chat_timestamp(const std::time_t &t)
Definition: game.cpp:864
int chat_message_aging()
Definition: game.cpp:902
Standard logging facilities (interface).
void server_message(bool is_lobby, const std::string &sender, const std::string &message)
rect map_outside_area() const
Returns the available area for a map, this may differ from the above.
Definition: display.cpp:564
bool parse_should_show_lobby_join(const std::string &sender, const std::string &message)
Definition: game.cpp:305
std::shared_ptr< halo_record > handle
Definition: halo.hpp:31
Transitional API for porting SDL_ttf-based code to Pango.
color_t mid() const
Average color shade.
Definition: color_range.hpp:86
bool word_match(const std::string &message, const std::string &word)
Check if a message contains a word.