add display chat manager, notifications support outside game_display

The functionality of tracking observers and displaying chat messages
is moved to a manager class, which the gui owns.

The functionality of displaying notifications is similarly moved out
of the game_display and to a private namespace. (Static singleton
pattern seems okay here since there really won't need to be more
than one of these for a single application, it seems.)
This commit is contained in:
Chris Beck 2014-06-27 14:29:31 -04:00
parent b0e407ac66
commit cb5a9a0d28
25 changed files with 612 additions and 447 deletions

View file

@ -229,6 +229,8 @@
<Unit filename="..\..\src\dialogs.hpp" />
<Unit filename="..\..\src\display.cpp" />
<Unit filename="..\..\src\display.hpp" />
<Unit filename="..\..\src\display_chat_manager.cpp" />
<Unit filename="..\..\src\display_chat_manager.hpp" />
<Unit filename="..\..\src\display_context.cpp" />
<Unit filename="..\..\src\display_context.hpp" />
<Unit filename="..\..\src\editor\action\action.cpp" />
@ -860,6 +862,8 @@
<Unit filename="..\..\src\network_asio.hpp" />
<Unit filename="..\..\src\network_worker.cpp" />
<Unit filename="..\..\src\network_worker.hpp" />
<Unit filename="..\..\src\notifications.cpp" />
<Unit filename="..\..\src\notifications.hpp" />
<Unit filename="..\..\src\overlay.hpp" />
<Unit filename="..\..\src\pathfind\astarsearch.cpp" />
<Unit filename="..\..\src\pathfind\pathfind.cpp" />

View file

@ -20108,6 +20108,14 @@
RelativePath="..\..\src\display.hpp"
>
</File>
<File
RelativePath="..\..\src\display_chat_manager.cpp"
>
</File>
<File
RelativePath="..\..\src\display_chat_manager.hpp"
>
</File>
<File
RelativePath="..\..\src\display_context.cpp"
>
@ -20696,6 +20704,14 @@
RelativePath="..\..\src\network_worker.hpp"
>
</File>
<File
RelativePath="..\..\src\notifications.cpp"
>
</File>
<File
RelativePath="..\..\src\notifications.hpp"
>
</File>
<File
RelativePath="..\..\src\overlay.hpp"
>

View file

@ -700,6 +700,7 @@ set(wesnoth-main_SRC
controller_base.cpp
desktop_util.cpp
dialogs.cpp
display_chat_manager.cpp
editor/action/action.cpp
editor/action/action_item.cpp
editor/action/action_label.cpp
@ -843,6 +844,7 @@ set(wesnoth-main_SRC
multiplayer_ui.cpp
multiplayer_wait.cpp
network_asio.cpp
notifications.cpp
pathfind/pathfind.cpp
pathfind/teleport.cpp
persist_context.cpp

View file

@ -233,6 +233,7 @@ wesnoth_sources = Split("""
controller_base.cpp
desktop_util.cpp
dialogs.cpp
display_chat_manager.cpp
editor/action/action.cpp
editor/action/action_unit.cpp
editor/action/action_label.cpp
@ -477,6 +478,7 @@ wesnoth_sources = Split("""
multiplayer_ui.cpp
multiplayer_wait.cpp
network_asio.cpp
notifications.cpp
pathfind/pathfind.cpp
pathfind/teleport.cpp
persist_context.cpp

View file

@ -35,6 +35,7 @@
#include "chat_events.hpp" // for chat_handler, etc
#include "config.hpp" // for config, etc
#include "display_chat_manager.hpp"
#include "game_board.hpp" // for game_board
#include "game_config.hpp" // for debug
#include "game_display.hpp" // for game_display
@ -353,7 +354,7 @@ const team& readonly_context_impl::current_team() const
void readonly_context_impl::log_message(const std::string& msg)
{
if(game_config::debug) {
resources::screen->add_chat_message(time(NULL), "ai", get_side(), msg,
resources::screen->get_chat_manager().add_chat_message(time(NULL), "ai", get_side(), msg,
events::chat_handler::MESSAGE_PUBLIC, false);
}
}

View file

@ -22,6 +22,7 @@
#include "../../callable_objects.hpp" // for unit_callable, etc
#include "../../chat_events.hpp" // for chat_handler, etc
#include "../../display_chat_manager.hpp"
#include "../../formula_function.hpp" // for formula_expression
#include "../../game_board.hpp" // for game_board
#include "../../game_display.hpp" // for game_display
@ -127,7 +128,7 @@ void formula_ai::handle_exception(game_logic::formula_error& e, const std::strin
void formula_ai::display_message(const std::string& msg) const
{
resources::screen->add_chat_message(time(NULL), "fai", get_side(), msg,
resources::screen->get_chat_manager().add_chat_message(time(NULL), "fai", get_side(), msg,
events::chat_handler::MESSAGE_PUBLIC, false);
}

View file

@ -0,0 +1,209 @@
/*
Copyright (C) 2014 by Chris Beck <render787@gmail.com>
Part of the Battle for Wesnoth Project http://www.wesnoth.org/
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY.
See the COPYING file for more details.
*/
#include "display_chat_manager.hpp"
#include "global.hpp"
#include "display.hpp"
#include "font.hpp"
#include "game_board.hpp" // <-- only needed for is_observer()
#include "game_preferences.hpp"
#include "log.hpp"
#include "marked-up_text.hpp"
#include "notifications.hpp"
#include "sound.hpp"
#include "serialization/string_utils.hpp"
#include <boost/cstdint.hpp>
#include <boost/foreach.hpp>
#include "SDL_timer.h"
#include "SDL_video.h"
static lg::log_domain log_engine("engine");
#define ERR_NG LOG_STREAM(err, log_engine)
using boost::uint32_t;
namespace {
const int chat_message_border = 5;
const int chat_message_x = 10;
const SDL_Color chat_message_color = {255,255,255,255};
const SDL_Color chat_message_bg = {0,0,0,140};
}
display_chat_manager::chat_message::chat_message(int speaker, int h)
: speaker_handle(speaker), handle(h), created_at(SDL_GetTicks())
{}
void display_chat_manager::add_chat_message(const time_t& time, const std::string& speaker,
int side, const std::string& message, events::chat_handler::MESSAGE_TYPE type,
bool bell)
{
const bool whisper = speaker.find("whisper: ") == 0;
std::string sender = speaker;
if (whisper) {
sender.assign(speaker, 9, speaker.size());
}
if (!preferences::parse_should_show_lobby_join(sender, message)) return;
if (preferences::is_ignored(sender)) return;
preferences::parse_admin_authentication(sender, message);
bool is_observer = false;
{ //TODO: Clean this block up somehow
const game_board * board = dynamic_cast<const game_board*>(&my_disp_.get_disp_context());
if (board) {
is_observer = board->is_observer();
}
}
if (bell) {
if ((type == events::chat_handler::MESSAGE_PRIVATE && (!is_observer || whisper))
|| utils::word_match(message, preferences::login())) {
sound::play_UI_sound(game_config::sounds::receive_message_highlight);
} else if (preferences::is_friend(sender)) {
sound::play_UI_sound(game_config::sounds::receive_message_friend);
} else if (sender == "server") {
sound::play_UI_sound(game_config::sounds::receive_message_server);
} else {
sound::play_UI_sound(game_config::sounds::receive_message);
}
}
bool action = false;
std::string msg;
if (message.find("/me ") == 0) {
msg.assign(message, 4, message.size());
action = true;
} else {
msg += message;
}
try {
// We've had a joker who send an invalid utf-8 message to crash clients
// so now catch the exception and ignore the message.
msg = font::word_wrap_text(msg,font::SIZE_SMALL,my_disp_.map_outside_area().w*3/4);
} catch (utf8::invalid_utf8_exception&) {
ERR_NG << "Invalid utf-8 found, chat message is ignored." << std::endl;
return;
}
int ypos = chat_message_x;
for(std::vector<chat_message>::const_iterator m = chat_messages_.begin(); m != chat_messages_.end(); ++m) {
ypos += std::max(font::get_floating_label_rect(m->handle).h,
font::get_floating_label_rect(m->speaker_handle).h);
}
SDL_Color speaker_color = {255,255,255,255};
if(side >= 1) {
speaker_color = int_to_color(team::get_side_color_range(side).mid());
}
SDL_Color message_color = chat_message_color;
std::stringstream str;
std::stringstream message_str;
if(type == events::chat_handler::MESSAGE_PUBLIC) {
if(action) {
str << "<" << speaker << " " << msg << ">";
message_color = speaker_color;
message_str << " ";
} else {
if (!speaker.empty())
str << "<" << speaker << ">";
message_str << msg;
}
} else {
if(action) {
str << "*" << speaker << " " << msg << "*";
message_color = speaker_color;
message_str << " ";
} else {
if (!speaker.empty())
str << "*" << speaker << "*";
message_str << msg;
}
}
// Prepend message with timestamp.
std::stringstream message_complete;
message_complete << preferences::get_chat_timestamp(time) << str.str();
const SDL_Rect rect = my_disp_.map_outside_area();
font::floating_label spk_flabel(message_complete.str());
spk_flabel.set_font_size(font::SIZE_SMALL);
spk_flabel.set_color(speaker_color);
spk_flabel.set_position(rect.x + chat_message_x, rect.y + ypos);
spk_flabel.set_clip_rect(rect);
spk_flabel.set_alignment(font::LEFT_ALIGN);
spk_flabel.set_bg_color(chat_message_bg);
spk_flabel.set_border_size(chat_message_border);
spk_flabel.use_markup(false);
int speaker_handle = font::add_floating_label(spk_flabel);
font::floating_label msg_flabel(message_str.str());
msg_flabel.set_font_size(font::SIZE_SMALL);
msg_flabel.set_color(message_color);
msg_flabel.set_position(rect.x + chat_message_x + font::get_floating_label_rect(speaker_handle).w,
rect.y + ypos);
msg_flabel.set_clip_rect(rect);
msg_flabel.set_alignment(font::LEFT_ALIGN);
msg_flabel.set_bg_color(chat_message_bg);
msg_flabel.set_border_size(chat_message_border);
msg_flabel.use_markup(false);
int message_handle = font::add_floating_label(msg_flabel);
// Send system notification if appropriate.
notifications::send_notification(speaker, message);
chat_messages_.push_back(chat_message(speaker_handle,message_handle));
prune_chat_messages();
}
void display_chat_manager::prune_chat_messages(bool remove_all)
{
const unsigned message_aging = preferences::chat_message_aging();
const unsigned message_ttl = remove_all ? 0 : message_aging * 60 * 1000;
const unsigned max_chat_messages = preferences::chat_lines();
int movement = 0;
if(message_aging != 0 || remove_all || chat_messages_.size() > max_chat_messages) {
while (!chat_messages_.empty() &&
(chat_messages_.front().created_at + message_ttl < SDL_GetTicks() ||
chat_messages_.size() > max_chat_messages))
{
const chat_message &old = chat_messages_.front();
movement += font::get_floating_label_rect(old.handle).h;
font::remove_floating_label(old.speaker_handle);
font::remove_floating_label(old.handle);
chat_messages_.erase(chat_messages_.begin());
}
}
BOOST_FOREACH(const chat_message &cm, chat_messages_) {
font::move_floating_label(cm.speaker_handle, 0, - movement);
font::move_floating_label(cm.handle, 0, - movement);
}
}

View file

@ -0,0 +1,60 @@
/*
Copyright (C) 2014 by Chris Beck <render787@gmail.com>
Part of the Battle for Wesnoth Project http://www.wesnoth.org/
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY.
See the COPYING file for more details.
*/
#ifndef INCL_DISPLAY_CHAT_MGR_HPP_
#define INCL_DISPLAY_CHAT_MGR_HPP_
#include "chat_events.hpp"
#include <boost/cstdint.hpp>
#include <ctime>
#include <set>
#include <string>
#include <vector>
class display;
class display_chat_manager {
public:
display_chat_manager(display & disp) : my_disp_(disp) {}
void add_observer(const std::string& name) { observers_.insert(name); }
void remove_observer(const std::string& name) { observers_.erase(name); }
const std::set<std::string>& observers() const { return observers_; }
void add_chat_message(const time_t& time, const std::string& speaker,
int side, const std::string& msg, events::chat_handler::MESSAGE_TYPE type, bool bell);
void clear_chat_messages() { prune_chat_messages(true); }
friend class game_display; //needed because it calls prune_chat_message
private:
std::set<std::string> observers_;
struct chat_message
{
chat_message(int speaker, int h);
int speaker_handle;
int handle;
boost::uint32_t created_at;
};
void prune_chat_messages(bool remove_all=false);
std::vector<chat_message> chat_messages_;
display & my_disp_;
};
#endif

View file

@ -14,8 +14,9 @@
*/
#include "global.hpp"
#include "floating_textbox.hpp"
#include "display_chat_manager.hpp"
#include "game_display.hpp"
#include "game_preferences.hpp"
#include "log.hpp"
@ -136,7 +137,7 @@ namespace gui{
text.append(line_start ? ": " : " ");
} else if (matches.size() > 1) {
std::string completion_list = utils::join(matches, " ");
resources::screen->add_chat_message(time(NULL), "", 0, completion_list,
resources::screen->get_chat_manager().add_chat_message(time(NULL), "", 0, completion_list,
events::chat_handler::MESSAGE_PRIVATE, false);
}
box_->set_text(text);

View file

@ -23,17 +23,8 @@
#include "gettext.hpp"
#include "wesconfig.h"
#ifdef HAVE_LIBDBUS
#include <dbus/dbus.h>
#endif
#ifdef HAVE_GROWL
#include <Growl/GrowlApplicationBridge-Carbon.h>
#include <Carbon/Carbon.h>
Growl_Delegate growl_obj;
#endif
#include "cursor.hpp"
#include "display_chat_manager.hpp"
#include "fake_unit_manager.hpp"
#include "fake_unit_ptr.hpp"
#include "game_board.hpp"
@ -43,6 +34,7 @@ Growl_Delegate growl_obj;
#include "map.hpp"
#include "map_label.hpp"
#include "marked-up_text.hpp"
#include "notifications.hpp"
#include "reports.hpp"
#include "resources.hpp"
#include "tod_manager.hpp"
@ -50,9 +42,6 @@ Growl_Delegate growl_obj;
#include "unit.hpp"
#include "unit_drawer.hpp"
#include "whiteboard/manager.hpp"
#ifdef _WIN32
#include "windows_tray_notification.hpp"
#endif
#include <boost/foreach.hpp>
@ -86,8 +75,7 @@ game_display::game_display(game_board& board, CVideo& video, boost::weak_ptr<wb:
sidebarScaling_(1.0),
first_turn_(true),
in_game_(false),
observers_(),
chat_messages_(),
chat_man_(new display_chat_manager(*this)),
game_mode_(RUNNING)
{
replace_overlay_map(&overlay_map_);
@ -108,7 +96,7 @@ game_display::~game_display()
{
try {
// SDL_FreeSurface(minimap_);
prune_chat_messages(true);
chat_man_->prune_chat_messages(true);
} catch (...) {}
}
@ -240,7 +228,7 @@ void game_display::pre_draw() {
* @todo FIXME: must modify changed, but best to do it at the
* floating_label level
*/
prune_chat_messages();
chat_man_->prune_chat_messages();
}
@ -669,230 +657,6 @@ std::string game_display::current_team_name() const
return std::string();
}
#ifdef HAVE_LIBDBUS
/** Use KDE 4 notifications. */
static bool kde_style = false;
struct wnotify
{
wnotify()
: id()
, owner()
, message()
{
}
uint32_t id;
std::string owner;
std::string message;
};
static std::list<wnotify> notifications;
static DBusHandlerResult filter_dbus_signal(DBusConnection *, DBusMessage *buf, void *)
{
if (!dbus_message_is_signal(buf, "org.freedesktop.Notifications", "NotificationClosed")) {
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
}
uint32_t id;
dbus_message_get_args(buf, NULL,
DBUS_TYPE_UINT32, &id,
DBUS_TYPE_INVALID);
std::list<wnotify>::iterator i = notifications.begin(),
i_end = notifications.end();
while (i != i_end && i->id != id) ++i;
if (i != i_end)
notifications.erase(i);
return DBUS_HANDLER_RESULT_HANDLED;
}
static DBusConnection *get_dbus_connection()
{
if (preferences::get("disable_notifications", false)) {
return NULL;
}
static bool initted = false;
static DBusConnection *connection = NULL;
if (!initted)
{
initted = true;
if (getenv("KDE_SESSION_VERSION")) {
// This variable is defined for KDE 4 only.
kde_style = true;
}
DBusError err;
dbus_error_init(&err);
connection = dbus_bus_get(DBUS_BUS_SESSION, &err);
if (!connection) {
ERR_DP << "Failed to open DBus session: " << err.message << '\n';
dbus_error_free(&err);
return NULL;
}
dbus_connection_add_filter(connection, filter_dbus_signal, NULL, NULL);
}
if (connection) {
dbus_connection_read_write(connection, 0);
while (dbus_connection_dispatch(connection) == DBUS_DISPATCH_DATA_REMAINS) {}
}
return connection;
}
static uint32_t send_dbus_notification(DBusConnection *connection, uint32_t replaces_id,
const std::string &owner, const std::string &message)
{
DBusMessage *buf = dbus_message_new_method_call(
kde_style ? "org.kde.VisualNotifications" : "org.freedesktop.Notifications",
kde_style ? "/VisualNotifications" : "/org/freedesktop/Notifications",
kde_style ? "org.kde.VisualNotifications" : "org.freedesktop.Notifications",
"Notify");
const char *app_name = "Battle for Wesnoth";
dbus_message_append_args(buf,
DBUS_TYPE_STRING, &app_name,
DBUS_TYPE_UINT32, &replaces_id,
DBUS_TYPE_INVALID);
if (kde_style) {
const char *event_id = "";
dbus_message_append_args(buf,
DBUS_TYPE_STRING, &event_id,
DBUS_TYPE_INVALID);
}
std::string app_icon_ = game_config::path + "/images/wesnoth-icon-small.png";
const char *app_icon = app_icon_.c_str();
const char *summary = owner.c_str();
const char *body = message.c_str();
const char **actions = NULL;
dbus_message_append_args(buf,
DBUS_TYPE_STRING, &app_icon,
DBUS_TYPE_STRING, &summary,
DBUS_TYPE_STRING, &body,
DBUS_TYPE_ARRAY, DBUS_TYPE_STRING, &actions, 0,
DBUS_TYPE_INVALID);
DBusMessageIter iter, hints;
dbus_message_iter_init_append(buf, &iter);
dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &hints);
dbus_message_iter_close_container(&iter, &hints);
int expire_timeout = kde_style ? 5000 : -1;
dbus_message_append_args(buf,
DBUS_TYPE_INT32, &expire_timeout,
DBUS_TYPE_INVALID);
DBusError err;
dbus_error_init(&err);
DBusMessage *ret = dbus_connection_send_with_reply_and_block(connection, buf, 1000, &err);
dbus_message_unref(buf);
if (!ret) {
ERR_DP << "Failed to send visual notification: " << err.message << '\n';
dbus_error_free(&err);
if (kde_style) {
ERR_DP << " Retrying with the freedesktop protocol." << std::endl;
kde_style = false;
return send_dbus_notification(connection, replaces_id, owner, message);
}
return 0;
}
uint32_t id;
dbus_message_get_args(ret, NULL,
DBUS_TYPE_UINT32, &id,
DBUS_TYPE_INVALID);
dbus_message_unref(ret);
// TODO: remove once closing signals for KDE are handled in filter_dbus_signal.
if (kde_style) return 0;
return id;
}
#endif
#if defined(HAVE_LIBDBUS) || defined(HAVE_GROWL) || defined(_WIN32)
void game_display::send_notification(const std::string& owner, const std::string& message)
{
if (preferences::get("disable_notifications", false)) { return; }
#else
void game_display::send_notification(const std::string& /*owner*/, const std::string& /*message*/)
{
#endif
#if defined(HAVE_LIBDBUS) || defined(HAVE_GROWL) || defined(_WIN32)
Uint8 app_state = SDL_GetAppState();
// Do not show notifications when the window is visible...
if ((app_state & SDL_APPACTIVE) != 0)
{
// ... and it has a focus.
if ((app_state & (SDL_APPMOUSEFOCUS | SDL_APPINPUTFOCUS)) != 0) {
return;
}
}
#endif
#ifdef HAVE_LIBDBUS
DBusConnection *connection = get_dbus_connection();
if (!connection) return;
std::list<wnotify>::iterator i = notifications.begin(),
i_end = notifications.end();
while (i != i_end && i->owner != owner) ++i;
if (i != i_end) {
i->message = message + "\n" + i->message;
int endl_pos = -1;
for (int ctr = 0; ctr < 5; ctr++)
endl_pos = i->message.find('\n', endl_pos+1);
i->message = i->message.substr(0,endl_pos);
send_dbus_notification(connection, i->id, owner, i->message);
return;
} else {
uint32_t id = send_dbus_notification(connection, 0, owner, message);
if (!id) return;
wnotify visual;
visual.id = id;
visual.owner = owner;
visual.message = message;
notifications.push_back(visual);
}
#endif
#ifdef HAVE_GROWL
CFStringRef app_name = CFStringCreateWithCString(NULL, "Wesnoth", kCFStringEncodingUTF8);
CFStringRef cf_owner = CFStringCreateWithCString(NULL, owner.c_str(), kCFStringEncodingUTF8);
CFStringRef cf_message = CFStringCreateWithCString(NULL, message.c_str(), kCFStringEncodingUTF8);
//Should be changed as soon as there are more than 2 types of notifications
CFStringRef cf_note_name = CFStringCreateWithCString(NULL, owner == _("Turn changed") ? _("Turn changed") : _("Chat message"), kCFStringEncodingUTF8);
growl_obj.applicationName = app_name;
growl_obj.registrationDictionary = NULL;
growl_obj.applicationIconData = NULL;
growl_obj.growlIsReady = NULL;
growl_obj.growlNotificationWasClicked = NULL;
growl_obj.growlNotificationTimedOut = NULL;
Growl_SetDelegate(&growl_obj);
Growl_NotifyWithTitleDescriptionNameIconPriorityStickyClickContext(cf_owner, cf_message, cf_note_name, NULL, NULL, NULL, NULL);
CFRelease(app_name);
CFRelease(cf_owner);
CFRelease(cf_message);
CFRelease(cf_note_name);
#endif
#ifdef _WIN32
std::string notification_title;
std::string notification_message;
if (owner == _("Turn changed")) {
notification_title = owner;
notification_message = message;
} else {
notification_title = _("Chat message");
notification_message = owner + ": " + message;
}
windows_tray_notification::show(notification_title, notification_message);
#endif
}
void game_display::set_playing_team(size_t teamindex)
{
@ -908,160 +672,4 @@ void game_display::begin_game()
invalidate_all();
}
namespace {
const int chat_message_border = 5;
const int chat_message_x = 10;
const SDL_Color chat_message_color = {255,255,255,255};
const SDL_Color chat_message_bg = {0,0,0,140};
}
void game_display::add_chat_message(const time_t& time, const std::string& speaker,
int side, const std::string& message, events::chat_handler::MESSAGE_TYPE type,
bool bell)
{
const bool whisper = speaker.find("whisper: ") == 0;
std::string sender = speaker;
if (whisper) {
sender.assign(speaker, 9, speaker.size());
}
if (!preferences::parse_should_show_lobby_join(sender, message)) return;
if (preferences::is_ignored(sender)) return;
preferences::parse_admin_authentication(sender, message);
if (bell) {
if ((type == events::chat_handler::MESSAGE_PRIVATE && (!resources::gameboard->is_observer() || whisper))
|| utils::word_match(message, preferences::login())) {
sound::play_UI_sound(game_config::sounds::receive_message_highlight);
} else if (preferences::is_friend(sender)) {
sound::play_UI_sound(game_config::sounds::receive_message_friend);
} else if (sender == "server") {
sound::play_UI_sound(game_config::sounds::receive_message_server);
} else {
sound::play_UI_sound(game_config::sounds::receive_message);
}
}
bool action = false;
std::string msg;
if (message.find("/me ") == 0) {
msg.assign(message, 4, message.size());
action = true;
} else {
msg += message;
}
try {
// We've had a joker who send an invalid utf-8 message to crash clients
// so now catch the exception and ignore the message.
msg = font::word_wrap_text(msg,font::SIZE_SMALL,map_outside_area().w*3/4);
} catch (utf8::invalid_utf8_exception&) {
ERR_NG << "Invalid utf-8 found, chat message is ignored." << std::endl;
return;
}
int ypos = chat_message_x;
for(std::vector<chat_message>::const_iterator m = chat_messages_.begin(); m != chat_messages_.end(); ++m) {
ypos += std::max(font::get_floating_label_rect(m->handle).h,
font::get_floating_label_rect(m->speaker_handle).h);
}
SDL_Color speaker_color = {255,255,255,255};
if(side >= 1) {
speaker_color = int_to_color(team::get_side_color_range(side).mid());
}
SDL_Color message_color = chat_message_color;
std::stringstream str;
std::stringstream message_str;
if(type == events::chat_handler::MESSAGE_PUBLIC) {
if(action) {
str << "<" << speaker << " " << msg << ">";
message_color = speaker_color;
message_str << " ";
} else {
if (!speaker.empty())
str << "<" << speaker << ">";
message_str << msg;
}
} else {
if(action) {
str << "*" << speaker << " " << msg << "*";
message_color = speaker_color;
message_str << " ";
} else {
if (!speaker.empty())
str << "*" << speaker << "*";
message_str << msg;
}
}
// Prepend message with timestamp.
std::stringstream message_complete;
message_complete << preferences::get_chat_timestamp(time) << str.str();
const SDL_Rect rect = map_outside_area();
font::floating_label spk_flabel(message_complete.str());
spk_flabel.set_font_size(font::SIZE_SMALL);
spk_flabel.set_color(speaker_color);
spk_flabel.set_position(rect.x + chat_message_x, rect.y + ypos);
spk_flabel.set_clip_rect(rect);
spk_flabel.set_alignment(font::LEFT_ALIGN);
spk_flabel.set_bg_color(chat_message_bg);
spk_flabel.set_border_size(chat_message_border);
spk_flabel.use_markup(false);
int speaker_handle = font::add_floating_label(spk_flabel);
font::floating_label msg_flabel(message_str.str());
msg_flabel.set_font_size(font::SIZE_SMALL);
msg_flabel.set_color(message_color);
msg_flabel.set_position(rect.x + chat_message_x + font::get_floating_label_rect(speaker_handle).w,
rect.y + ypos);
msg_flabel.set_clip_rect(rect);
msg_flabel.set_alignment(font::LEFT_ALIGN);
msg_flabel.set_bg_color(chat_message_bg);
msg_flabel.set_border_size(chat_message_border);
msg_flabel.use_markup(false);
int message_handle = font::add_floating_label(msg_flabel);
// Send system notification if appropriate.
send_notification(speaker, message);
chat_messages_.push_back(chat_message(speaker_handle,message_handle));
prune_chat_messages();
}
void game_display::prune_chat_messages(bool remove_all)
{
const unsigned message_aging = preferences::chat_message_aging();
const unsigned message_ttl = remove_all ? 0 : message_aging * 60 * 1000;
const unsigned max_chat_messages = preferences::chat_lines();
int movement = 0;
if(message_aging != 0 || remove_all || chat_messages_.size() > max_chat_messages) {
while (!chat_messages_.empty() &&
(chat_messages_.front().created_at + message_ttl < SDL_GetTicks() ||
chat_messages_.size() > max_chat_messages))
{
const chat_message &old = chat_messages_.front();
movement += font::get_floating_label_rect(old.handle).h;
font::remove_floating_label(old.speaker_handle);
font::remove_floating_label(old.handle);
chat_messages_.erase(chat_messages_.begin());
}
}
BOOST_FOREACH(const chat_message &cm, chat_messages_) {
font::move_floating_label(cm.speaker_handle, 0, - movement);
font::move_floating_label(cm.handle, 0, - movement);
}
}

View file

@ -18,6 +18,7 @@
#define GAME_DISPLAY_H_INCLUDED
class config;
class display_chat_manager;
class tod_manager;
class team;
class unit_map;
@ -166,8 +167,6 @@ public:
//void draw_terrain_palette(int x, int y, terrain_type::TERRAIN selected);
t_translation::t_terrain get_terrain_on(int palx, int paly, int x, int y);
void send_notification(const std::string& owner, const std::string& message);
/**
* Sets the team controlled by the player using the computer.
*
@ -193,13 +192,7 @@ public:
std::string current_team_name() const;
void add_observer(const std::string& name) { observers_.insert(name); }
void remove_observer(const std::string& name) { observers_.erase(name); }
const std::set<std::string>& observers() const { return observers_; }
void add_chat_message(const time_t& time, const std::string& speaker,
int side, const std::string& msg, events::chat_handler::MESSAGE_TYPE type, bool bell);
void clear_chat_messages() { prune_chat_messages(true); }
display_chat_manager & get_chat_manager() { return *chat_man_; }
void begin_game();
@ -248,23 +241,7 @@ private:
bool first_turn_, in_game_;
std::set<std::string> observers_;
struct chat_message
{
chat_message(int speaker, int h) : speaker_handle(speaker), handle(h), created_at(SDL_GetTicks())
{}
int speaker_handle;
int handle;
Uint32 created_at;
};
void prune_chat_messages(bool remove_all=false);
std::vector<chat_message> chat_messages_;
boost::scoped_ptr<display_chat_manager> chat_man_;
tgame_mode game_mode_;

View file

@ -23,6 +23,7 @@
#include "conditional_wml.hpp"
#include "handlers.hpp"
#include "../display_chat_manager.hpp"
#include "../game_config.hpp"
#include "../game_display.hpp"
#include "../game_data.hpp"
@ -336,7 +337,7 @@ namespace { // Support functions
msg << " (" << itor->second << ")";
}
resources::screen->add_chat_message(time(NULL), caption, 0, msg.str(),
resources::screen->get_chat_manager().add_chat_message(time(NULL), caption, 0, msg.str(),
events::chat_handler::MESSAGE_PUBLIC, false);
if ( to_cerr )
std::cerr << caption << ": " << msg.str() << '\n';

View file

@ -28,6 +28,7 @@
#include "actions/vision.hpp"
#include "ai/manager.hpp"
#include "dialogs.hpp"
#include "display_chat_manager.hpp"
#include "filechooser.hpp"
#include "formatter.hpp"
#include "formula_string_utils.hpp"
@ -1398,7 +1399,7 @@ void menu_handler::add_chat_message(const time_t& time,
const std::string& speaker, int side, const std::string& message,
events::chat_handler::MESSAGE_TYPE type)
{
gui_->add_chat_message(time, speaker, side, message, type, false);
gui_->get_chat_manager().add_chat_message(time, speaker, side, message, type, false);
}
//simple command args parser, separated from command_handler for clarity.
@ -2801,7 +2802,7 @@ void console_handler::do_controller()
}
void console_handler::do_clear() {
menu_handler_.gui_->clear_chat_messages();
menu_handler_.gui_->get_chat_manager().clear_chat_messages();
}
void console_handler::do_sunset() {
int delay = lexical_cast_default<int>(get_data());
@ -3341,7 +3342,7 @@ void menu_handler::ai_formula()
void menu_handler::clear_messages()
{
gui_->clear_chat_messages(); // also clear debug-messages and WML-error-messages
gui_->get_chat_manager().clear_chat_messages(); // also clear debug-messages and WML-error-messages
}
void menu_handler::change_controller(const std::string& side, const std::string& controller)

View file

@ -20,11 +20,13 @@
#include "ai/configuration.hpp"
#include "dialogs.hpp"
#include "display_chat_manager.hpp"
#include "game_display.hpp"
#include "game_preferences.hpp"
#include "gettext.hpp"
#include "log.hpp"
#include "map.hpp"
#include "notifications.hpp"
#include "wml_separators.hpp"
#include "sound.hpp"
@ -559,7 +561,7 @@ void connect::process_network_data(const config& data,
if (!was_able_to_start && engine_.can_start_game()) {
DBG_MP << "play party full sound" << std::endl;
sound::play_UI_sound(game_config::sounds::party_full_bell);
game_display::get_singleton()->send_notification(_("Wesnoth"), _ ("Ready to start!"));
notifications::send_notification(_("Wesnoth"), _ ("Ready to start!"));
}
}

View file

@ -24,6 +24,7 @@
#include "marked-up_text.hpp"
#include "mp_game_utils.hpp"
#include "multiplayer_wait.hpp"
#include "notifications.hpp"
#include "resources.hpp"
#include "statistics.hpp"
#include "saved_game.hpp"
@ -432,7 +433,7 @@ void wait::start_game()
LOG_NW << "starting game\n";
sound::play_UI_sound(game_config::sounds::mp_game_begins);
game_display::get_singleton()->send_notification(_("Wesnoth"), _ ("Game has begun!"));
notifications::send_notification(_("Wesnoth"), _ ("Game has begun!"));
}
void wait::layout_children(const SDL_Rect& rect)

264
src/notifications.cpp Normal file
View file

@ -0,0 +1,264 @@
#include "notifications.hpp"
#include "global.hpp"
#include "log.hpp"
#include "game_preferences.hpp"
#include "game_config.hpp"
#include <boost/cstdint.hpp>
#include <cstdlib>
#include <list>
#include "SDL_active.h"
#ifdef HAVE_LIBDBUS
#include <dbus/dbus.h>
#endif
#ifdef HAVE_GROWL
#include <Growl/GrowlApplicationBridge-Carbon.h>
#include <Carbon/Carbon.h>
Growl_Delegate growl_obj;
#endif
#ifdef _WIN32
#include "windows_tray_notification.hpp"
#endif
static lg::log_domain log_display("display");
#define ERR_DP LOG_STREAM(err, log_display)
#define LOG_DP LOG_STREAM(info, log_display)
using boost::uint32_t;
namespace notifications
{
#ifdef HAVE_LIBDBUS
/** Use KDE 4 notifications. */
static bool kde_style = false;
struct wnotify
{
wnotify()
: id()
, owner()
, message()
{
}
uint32_t id;
std::string owner;
std::string message;
};
static std::list<wnotify> notifications;
static DBusHandlerResult filter_dbus_signal(DBusConnection *, DBusMessage *buf, void *)
{
if (!dbus_message_is_signal(buf, "org.freedesktop.Notifications", "NotificationClosed")) {
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
}
uint32_t id;
dbus_message_get_args(buf, NULL,
DBUS_TYPE_UINT32, &id,
DBUS_TYPE_INVALID);
std::list<wnotify>::iterator i = notifications.begin(),
i_end = notifications.end();
while (i != i_end && i->id != id) ++i;
if (i != i_end)
notifications.erase(i);
return DBUS_HANDLER_RESULT_HANDLED;
}
static DBusConnection *get_dbus_connection()
{
if (preferences::get("disable_notifications", false)) {
return NULL;
}
static bool initted = false;
static DBusConnection *connection = NULL;
if (!initted)
{
initted = true;
if (getenv("KDE_SESSION_VERSION")) {
// This variable is defined for KDE 4 only.
kde_style = true;
}
DBusError err;
dbus_error_init(&err);
connection = dbus_bus_get(DBUS_BUS_SESSION, &err);
if (!connection) {
ERR_DP << "Failed to open DBus session: " << err.message << '\n';
dbus_error_free(&err);
return NULL;
}
dbus_connection_add_filter(connection, filter_dbus_signal, NULL, NULL);
}
if (connection) {
dbus_connection_read_write(connection, 0);
while (dbus_connection_dispatch(connection) == DBUS_DISPATCH_DATA_REMAINS) {}
}
return connection;
}
static uint32_t send_dbus_notification(DBusConnection *connection, uint32_t replaces_id,
const std::string &owner, const std::string &message)
{
DBusMessage *buf = dbus_message_new_method_call(
kde_style ? "org.kde.VisualNotifications" : "org.freedesktop.Notifications",
kde_style ? "/VisualNotifications" : "/org/freedesktop/Notifications",
kde_style ? "org.kde.VisualNotifications" : "org.freedesktop.Notifications",
"Notify");
const char *app_name = "Battle for Wesnoth";
dbus_message_append_args(buf,
DBUS_TYPE_STRING, &app_name,
DBUS_TYPE_UINT32, &replaces_id,
DBUS_TYPE_INVALID);
if (kde_style) {
const char *event_id = "";
dbus_message_append_args(buf,
DBUS_TYPE_STRING, &event_id,
DBUS_TYPE_INVALID);
}
std::string app_icon_ = game_config::path + "/images/wesnoth-icon-small.png";
const char *app_icon = app_icon_.c_str();
const char *summary = owner.c_str();
const char *body = message.c_str();
const char **actions = NULL;
dbus_message_append_args(buf,
DBUS_TYPE_STRING, &app_icon,
DBUS_TYPE_STRING, &summary,
DBUS_TYPE_STRING, &body,
DBUS_TYPE_ARRAY, DBUS_TYPE_STRING, &actions, 0,
DBUS_TYPE_INVALID);
DBusMessageIter iter, hints;
dbus_message_iter_init_append(buf, &iter);
dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &hints);
dbus_message_iter_close_container(&iter, &hints);
int expire_timeout = kde_style ? 5000 : -1;
dbus_message_append_args(buf,
DBUS_TYPE_INT32, &expire_timeout,
DBUS_TYPE_INVALID);
DBusError err;
dbus_error_init(&err);
DBusMessage *ret = dbus_connection_send_with_reply_and_block(connection, buf, 1000, &err);
dbus_message_unref(buf);
if (!ret) {
ERR_DP << "Failed to send visual notification: " << err.message << '\n';
dbus_error_free(&err);
if (kde_style) {
ERR_DP << " Retrying with the freedesktop protocol." << std::endl;
kde_style = false;
return send_dbus_notification(connection, replaces_id, owner, message);
}
return 0;
}
uint32_t id;
dbus_message_get_args(ret, NULL,
DBUS_TYPE_UINT32, &id,
DBUS_TYPE_INVALID);
dbus_message_unref(ret);
// TODO: remove once closing signals for KDE are handled in filter_dbus_signal.
if (kde_style) return 0;
return id;
}
#endif
#if defined(HAVE_LIBDBUS) || defined(HAVE_GROWL) || defined(_WIN32)
void send_notification(const std::string& owner, const std::string& message)
{
if (preferences::get("disable_notifications", false)) { return; }
#else
void send_notification(const std::string& /*owner*/, const std::string& /*message*/)
{
#endif
#if defined(HAVE_LIBDBUS) || defined(HAVE_GROWL) || defined(_WIN32)
Uint8 app_state = SDL_GetAppState();
// Do not show notifications when the window is visible...
if ((app_state & SDL_APPACTIVE) != 0)
{
// ... and it has a focus.
if ((app_state & (SDL_APPMOUSEFOCUS | SDL_APPINPUTFOCUS)) != 0) {
return;
}
}
#endif
#ifdef HAVE_LIBDBUS
DBusConnection *connection = get_dbus_connection();
if (!connection) return;
std::list<wnotify>::iterator i = notifications.begin(),
i_end = notifications.end();
while (i != i_end && i->owner != owner) ++i;
if (i != i_end) {
i->message = message + "\n" + i->message;
int endl_pos = -1;
for (int ctr = 0; ctr < 5; ctr++)
endl_pos = i->message.find('\n', endl_pos+1);
i->message = i->message.substr(0,endl_pos);
send_dbus_notification(connection, i->id, owner, i->message);
return;
} else {
uint32_t id = send_dbus_notification(connection, 0, owner, message);
if (!id) return;
wnotify visual;
visual.id = id;
visual.owner = owner;
visual.message = message;
notifications.push_back(visual);
}
#endif
#ifdef HAVE_GROWL
CFStringRef app_name = CFStringCreateWithCString(NULL, "Wesnoth", kCFStringEncodingUTF8);
CFStringRef cf_owner = CFStringCreateWithCString(NULL, owner.c_str(), kCFStringEncodingUTF8);
CFStringRef cf_message = CFStringCreateWithCString(NULL, message.c_str(), kCFStringEncodingUTF8);
//Should be changed as soon as there are more than 2 types of notifications
CFStringRef cf_note_name = CFStringCreateWithCString(NULL, owner == _("Turn changed") ? _("Turn changed") : _("Chat message"), kCFStringEncodingUTF8);
growl_obj.applicationName = app_name;
growl_obj.registrationDictionary = NULL;
growl_obj.applicationIconData = NULL;
growl_obj.growlIsReady = NULL;
growl_obj.growlNotificationWasClicked = NULL;
growl_obj.growlNotificationTimedOut = NULL;
Growl_SetDelegate(&growl_obj);
Growl_NotifyWithTitleDescriptionNameIconPriorityStickyClickContext(cf_owner, cf_message, cf_note_name, NULL, NULL, NULL, NULL);
CFRelease(app_name);
CFRelease(cf_owner);
CFRelease(cf_message);
CFRelease(cf_note_name);
#endif
#ifdef _WIN32
std::string notification_title;
std::string notification_message;
if (owner == _("Turn changed")) {
notification_title = owner;
notification_message = message;
} else {
notification_title = _("Chat message");
notification_message = owner + ": " + message;
}
windows_tray_notification::show(notification_title, notification_message);
#endif
}
} //end namespace notifications

6
src/notifications.hpp Normal file
View file

@ -0,0 +1,6 @@
#include <string>
namespace notifications
{
void send_notification(const std::string& owner, const std::string& message);
}

View file

@ -27,6 +27,7 @@
#include "ai/manager.hpp"
#include "ai/testing.hpp"
#include "dialogs.hpp"
#include "display_chat_manager.hpp"
#include "formula_string_utils.hpp"
#include "game_events/handlers.hpp"
#include "game_events/menu_item.hpp"
@ -462,7 +463,7 @@ void play_controller::fire_start(bool execute){
if( saved_game_.classification().random_mode != "" && (network::nconnections() != 0))
{
std::string mes = _("MP game uses an alternative random mode, if you don't know what this message means, then most likeley someone is cheating or someone reloaded a corrupt game.");
gui_->add_chat_message(
gui_->get_chat_manager().add_chat_message(
time(NULL),
"game_engine",
0,

View file

@ -18,9 +18,11 @@
#include "dialogs.hpp"
#include "actions/undo.hpp"
#include "display_chat_manager.hpp"
#include "game_end_exceptions.hpp"
#include "gettext.hpp"
#include "log.hpp"
#include "notifications.hpp"
#include "playturn.hpp"
#include "preferences.hpp"
#include "resources.hpp"
@ -102,7 +104,7 @@ possible_end_play_signal playmp_controller::play_side()
player["name"] = current_team().current_player();
std::string turn_notification_msg = _("$name has taken control");
turn_notification_msg = utils::interpolate_variables_into_string(turn_notification_msg, &player);
gui_->send_notification(_("Turn changed"), turn_notification_msg);
notifications::send_notification(_("Turn changed"), turn_notification_msg);
// Proceed with the parent function.
return playsingle_controller::play_side();
@ -586,7 +588,7 @@ bool playmp_controller::can_execute_command(const hotkey::hotkey_command& cmd, i
void playmp_controller::do_idle_notification()
{
resources::screen->add_chat_message(time(NULL), "", 0,
resources::screen->get_chat_manager().add_chat_message(time(NULL), "", 0,
_("This side is in an idle state. To proceed with the game, it must be assigned to another controller. You may use :droid, :control or :give_control for example."),
events::chat_handler::MESSAGE_PUBLIC, false);
}

View file

@ -25,6 +25,7 @@
#include "ai/game_info.hpp"
#include "ai/testing.hpp"
#include "dialogs.hpp"
#include "display_chat_manager.hpp"
#include "game_end_exceptions.hpp"
#include "game_events/pump.hpp"
#include "game_preferences.hpp"
@ -990,7 +991,7 @@ void playsingle_controller::play_ai_turn(){
*/
void playsingle_controller::do_idle_notification()
{
resources::screen->add_chat_message(time(NULL), "Wesnoth", 0,
resources::screen->get_chat_manager().add_chat_message(time(NULL), "Wesnoth", 0,
"This side is in an idle state. To proceed with the game, the host must assign it to another controller.",
events::chat_handler::MESSAGE_PUBLIC, false);
}

View file

@ -18,6 +18,7 @@
#include "actions/undo.hpp" // for undo_list
#include "chat_events.hpp" // for chat_handler, etc
#include "config.hpp" // for config, etc
#include "display_chat_manager.hpp" // for add_chat_message, add_observer, etc
#include "formula_string_utils.hpp" // for vgettext
#include "game_board.hpp" // for game_board
#include "game_display.hpp" // for game_display
@ -143,23 +144,23 @@ turn_info::PROCESS_DATA_RESULT turn_info::process_network_data(const config& cfg
if (const config &msg = cfg.child("message"))
{
resources::screen->add_chat_message(time(NULL), msg["sender"], msg["side"],
resources::screen->get_chat_manager().add_chat_message(time(NULL), msg["sender"], msg["side"],
msg["message"], events::chat_handler::MESSAGE_PUBLIC,
preferences::message_bell());
}
else if (const config &msg = cfg.child("whisper") /*&& is_observer()*/)
{
resources::screen->add_chat_message(time(NULL), "whisper: " + msg["sender"].str(), 0,
resources::screen->get_chat_manager().add_chat_message(time(NULL), "whisper: " + msg["sender"].str(), 0,
msg["message"], events::chat_handler::MESSAGE_PRIVATE,
preferences::message_bell());
}
else if (const config &ob = cfg.child("observer") )
{
resources::screen->add_observer(ob["name"]);
resources::screen->get_chat_manager().add_observer(ob["name"]);
}
else if (const config &ob = cfg.child("observer_quit"))
{
resources::screen->remove_observer(ob["name"]);
resources::screen->get_chat_manager().remove_observer(ob["name"]);
}
else if (cfg.child("leave_game")) {
throw network::error("");

View file

@ -25,6 +25,7 @@
#include "actions/undo.hpp"
#include "config_assign.hpp"
#include "dialogs.hpp"
#include "display_chat_manager.hpp"
#include "game_display.hpp"
#include "game_preferences.hpp"
#include "game_data.hpp"
@ -681,14 +682,14 @@ static void check_checksums(const config &cfg)
if (!u.valid()) {
std::stringstream message;
message << "non existent unit to checksum at " << loc.x+1 << "," << loc.y+1 << "!";
resources::screen->add_chat_message(time(NULL), "verification", 1, message.str(),
resources::screen->get_chat_manager().add_chat_message(time(NULL), "verification", 1, message.str(),
events::chat_handler::MESSAGE_PRIVATE, false);
continue;
}
if (get_checksum(*u) != ch["value"]) {
std::stringstream message;
message << "checksum mismatch at " << loc.x+1 << "," << loc.y+1 << "!";
resources::screen->add_chat_message(time(NULL), "verification", 1, message.str(),
resources::screen->get_chat_manager().add_chat_message(time(NULL), "verification", 1, message.str(),
events::chat_handler::MESSAGE_PRIVATE, false);
}
}
@ -768,7 +769,7 @@ REPLAY_RETURN do_replay_handle()
get_replay_source().add_chat_message_location();
if (!get_replay_source().is_skipping() || is_whisper) {
int side = child["side"];
resources::screen->add_chat_message(get_time(child), speaker_name, side, message,
resources::screen->get_chat_manager().add_chat_message(get_time(child), speaker_name, side, message,
(team_name.empty() ? events::chat_handler::MESSAGE_PUBLIC
: events::chat_handler::MESSAGE_PRIVATE),
preferences::message_bell());

View file

@ -19,6 +19,7 @@
#include "carryover.hpp"
#include "actions/vision.hpp"
#include "display_chat_manager.hpp"
#include "game_end_exceptions.hpp"
#include "game_errors.hpp" //needed to be thrown
#include "game_events/handlers.hpp"
@ -294,7 +295,7 @@ void replay_controller::reset_replay()
{
DBG_REPLAY << "replay_controller::reset_replay\n";
gui_->clear_chat_messages();
gui_->get_chat_manager().clear_chat_messages();
is_playing_ = false;
player_number_ = 1;
current_turn_ = 1;

View file

@ -39,6 +39,7 @@
#include "ai/manager.hpp" // for manager, holder
#include "attack_prediction.hpp" // for combatant
#include "config.hpp" // for config, etc
#include "display_chat_manager.hpp" // for clear_chat_messages
#include "filesystem.hpp" // for get_wml_location
#include "font.hpp" // for LABEL_COLOR
#include "game_board.hpp" // for game_board
@ -1722,7 +1723,7 @@ static int intf_debug(lua_State* L)
*/
static int intf_clear_messages(lua_State*)
{
resources::screen->clear_chat_messages();
resources::screen->get_chat_manager().clear_chat_messages();
return 0;
}

View file

@ -19,6 +19,7 @@
#include "lua/lauxlib.h"
#include "config.hpp"
#include "display_chat_manager.hpp"
#include "game_display.hpp"
#include "log.hpp"
#include "recall_list_manager.hpp"
@ -40,7 +41,7 @@ static lg::log_domain log_scripting_lua("scripting/lua");
void chat_message(std::string const &caption, std::string const &msg)
{
resources::screen->add_chat_message(time(NULL), caption, 0, msg,
resources::screen->get_chat_manager().add_chat_message(time(NULL), caption, 0, msg,
events::chat_handler::MESSAGE_PUBLIC, false);
}