Skip to content

Commit

Permalink
add display chat manager, notifications support outside game_display
Browse files Browse the repository at this point in the history
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.)
  • Loading branch information
cbeck88 committed Jun 27, 2014
1 parent b0e407a commit cb5a9a0
Show file tree
Hide file tree
Showing 25 changed files with 612 additions and 447 deletions.
4 changes: 4 additions & 0 deletions projectfiles/CodeBlocks/wesnoth.cbp
Expand Up @@ -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" />
Expand Down Expand Up @@ -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" />
Expand Down
16 changes: 16 additions & 0 deletions projectfiles/VC9/wesnoth.vcproj
Expand Up @@ -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"
>
Expand Down Expand Up @@ -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"
>
Expand Down
2 changes: 2 additions & 0 deletions src/CMakeLists.txt
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions src/SConscript
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
3 changes: 2 additions & 1 deletion src/ai/contexts.cpp
Expand Up @@ -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
Expand Down Expand Up @@ -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);
}
}
Expand Down
3 changes: 2 additions & 1 deletion src/ai/formula/ai.cpp
Expand Up @@ -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
Expand Down Expand Up @@ -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);

}
Expand Down
209 changes: 209 additions & 0 deletions src/display_chat_manager.cpp
@@ -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);
}
}



60 changes: 60 additions & 0 deletions src/display_chat_manager.hpp
@@ -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

0 comments on commit cb5a9a0

Please sign in to comment.