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