MP Change Control: refactor dialog implementation
This gets rid of the overly-complicated MVC implementation for a standard simple one.
This commit is contained in:
parent
bf2dfc8f20
commit
5effd5338a
2 changed files with 171 additions and 347 deletions
|
@ -17,9 +17,15 @@
|
|||
|
||||
#include "gui/dialogs/multiplayer/mp_change_control.hpp"
|
||||
|
||||
#include "font/text_formatting.hpp"
|
||||
#include "formatter.hpp"
|
||||
#include "formula/string_utils.hpp"
|
||||
#include "game_board.hpp"
|
||||
#include "game_display.hpp"
|
||||
#include "game_preferences.hpp"
|
||||
#include "gui/auxiliary/find_widget.hpp"
|
||||
#include "gui/dialogs/helper.hpp"
|
||||
#include "gui/widgets/button.hpp"
|
||||
#include "gui/widgets/label.hpp"
|
||||
#ifdef GUI2_EXPERIMENTAL_LISTBOX
|
||||
#include "gui/widgets/list.hpp"
|
||||
#else
|
||||
|
@ -27,23 +33,17 @@
|
|||
#endif
|
||||
#include "gui/widgets/settings.hpp"
|
||||
#include "gui/widgets/window.hpp"
|
||||
|
||||
#include "font/text_formatting.hpp"
|
||||
#include "formula/string_utils.hpp"
|
||||
#include "game_board.hpp"
|
||||
#include "game_display.hpp"
|
||||
#include "game_preferences.hpp"
|
||||
#include "log.hpp"
|
||||
#include "menu_events.hpp"
|
||||
#include "resources.hpp"
|
||||
#include "team.hpp"
|
||||
|
||||
#include <vector>
|
||||
#include "utils/functional.hpp"
|
||||
|
||||
static lg::log_domain log_gui("gui/dialogs/mp_change_control");
|
||||
#define ERR_GUI LOG_STREAM(err, log_gui)
|
||||
#define WRN_GUI LOG_STREAM(warn, log_gui)
|
||||
#define LOG_GUI LOG_STREAM(info, log_gui)
|
||||
#define ERR_GUI LOG_STREAM(err, log_gui)
|
||||
#define WRN_GUI LOG_STREAM(warn, log_gui)
|
||||
#define LOG_GUI LOG_STREAM(info, log_gui)
|
||||
#define DBG_GUI LOG_STREAM(debug, log_gui)
|
||||
|
||||
namespace gui2
|
||||
|
@ -70,346 +70,151 @@ namespace dialogs
|
|||
*
|
||||
*/
|
||||
|
||||
template <class D, class V, void (V::*fptr)(window&)>
|
||||
void dialog_view_callback(widget& caller)
|
||||
{
|
||||
D* dialog = dynamic_cast<D*>(caller.dialog());
|
||||
assert(dialog);
|
||||
window* win = dynamic_cast<window*>(caller.get_window());
|
||||
assert(win);
|
||||
(*(dialog->get_view()).*fptr)(*win);
|
||||
}
|
||||
|
||||
/**
|
||||
* The model is an interface defining the data to be displayed or otherwise
|
||||
* acted upon in the user interface.
|
||||
*/
|
||||
class mp_change_control::model
|
||||
{
|
||||
public:
|
||||
model() : sides_list(nullptr), nicks_list(nullptr), sides(), nicks()
|
||||
{
|
||||
}
|
||||
|
||||
listbox* sides_list;
|
||||
listbox* nicks_list;
|
||||
|
||||
// contains the mapping from listbox labels to actual sides
|
||||
// (note that due to hidden= attribute nth list item doesn't have to be nth
|
||||
// side)
|
||||
std::vector<int> sides;
|
||||
// contains the mapping from listbox labels to actual nicks
|
||||
std::vector<std::string> nicks;
|
||||
|
||||
void clear_sides()
|
||||
{
|
||||
DBG_GUI << "Sides list: clearing\n";
|
||||
sides_list->clear();
|
||||
sides.clear();
|
||||
}
|
||||
|
||||
void add_side(int side_num, const std::string& label)
|
||||
{
|
||||
sides.push_back(side_num);
|
||||
DBG_GUI << "Sides list: adding item (side_num: \"" << side_num
|
||||
<< "\" label: \"" << label << "\")\n";
|
||||
std::map<std::string, string_map> data;
|
||||
string_map item;
|
||||
item["id"] = std::string("side_") + std::to_string(side_num);
|
||||
item["label"] = label;
|
||||
item["use_markup"] = "true";
|
||||
data.emplace("side", item);
|
||||
sides_list->add_row(data);
|
||||
}
|
||||
|
||||
void clear_nicks()
|
||||
{
|
||||
DBG_GUI << "Nicks list: clearing\n";
|
||||
nicks_list->clear();
|
||||
nicks.clear();
|
||||
}
|
||||
|
||||
void add_nick(const std::string& nick, const std::string& label)
|
||||
{
|
||||
DBG_GUI << "Nicks list: adding item (nick: \"" << nick << "\" label: \""
|
||||
<< label << "\")\n";
|
||||
nicks.push_back(nick);
|
||||
std::map<std::string, string_map> data;
|
||||
string_map item;
|
||||
item["id"] = nick;
|
||||
item["label"] = label;
|
||||
item["use_markup"] = "true";
|
||||
data.emplace("nick", item);
|
||||
nicks_list->add_row(data);
|
||||
}
|
||||
};
|
||||
|
||||
class side_controller
|
||||
{
|
||||
public:
|
||||
side_controller(const std::string& name,
|
||||
mp_change_control::model& m,
|
||||
int side_number)
|
||||
: model_(m), name_(name), side_number_(side_number)
|
||||
{
|
||||
}
|
||||
|
||||
~side_controller()
|
||||
{
|
||||
}
|
||||
|
||||
std::string name() const
|
||||
{
|
||||
return name_;
|
||||
}
|
||||
|
||||
int side_number() const
|
||||
{
|
||||
return side_number_;
|
||||
}
|
||||
|
||||
void show_nicks_list()
|
||||
{
|
||||
DBG_GUI << "Nicks list: showing for side " << side_number_ << '\n';
|
||||
// model_.selected_side = side_number_;
|
||||
model_.clear_nicks();
|
||||
|
||||
std::set<std::string> nicks;
|
||||
for(std::vector<team>::const_iterator it = resources::gameboard->teams().begin();
|
||||
it != resources::gameboard->teams().end();
|
||||
++it) {
|
||||
if(!it->is_local_ai() && !it->is_network_ai() && !it->is_idle()
|
||||
&& !it->is_empty() && !it->current_player().empty())
|
||||
nicks.insert(it->current_player());
|
||||
}
|
||||
|
||||
const std::set<std::string>& observers = resources::screen->observers();
|
||||
|
||||
nicks.insert(observers.begin(), observers.end());
|
||||
nicks.insert(preferences::login()); // in case we are an observer, it
|
||||
// isn't in the observers set then
|
||||
// and has to be added manually
|
||||
|
||||
int i = 0; // because we need to know which row contains the controlling
|
||||
// player
|
||||
|
||||
for(const auto & nick : nicks)
|
||||
{
|
||||
if(side_number_ <= static_cast<int>(resources::gameboard->teams().size())
|
||||
&& resources::gameboard->teams().at(side_number_ - 1).current_player()
|
||||
== nick) {
|
||||
std::string label_str = "<b>" + nick + "</b>";
|
||||
model_.add_nick(nick, label_str);
|
||||
model_.nicks_list->select_row(i);
|
||||
} else
|
||||
model_.add_nick(nick, nick);
|
||||
++i;
|
||||
}
|
||||
}
|
||||
void handle_nicks_list_selection()
|
||||
{
|
||||
int selected = model_.nicks_list->get_selected_row();
|
||||
DBG_GUI << "Nicks list: row " << selected << " selected, it contains "
|
||||
<< model_.nicks[selected] << '\n';
|
||||
}
|
||||
void update_view_from_model()
|
||||
{
|
||||
show_nicks_list();
|
||||
}
|
||||
|
||||
private:
|
||||
mp_change_control::model& model_;
|
||||
const std::string name_;
|
||||
int side_number_;
|
||||
};
|
||||
|
||||
/**
|
||||
* The controller acts upon the model.
|
||||
*
|
||||
* It retrieves data from repositories, persists it, manipulates it, and
|
||||
* determines how it will be displayed in the view.
|
||||
*/
|
||||
class mp_change_control::controller
|
||||
{
|
||||
public:
|
||||
typedef std::vector<std::shared_ptr<side_controller> >
|
||||
side_controller_ptr_vector;
|
||||
controller(model& m) : model_(m), side_controllers_()
|
||||
{
|
||||
}
|
||||
|
||||
void show_sides_list()
|
||||
{
|
||||
DBG_GUI << "Sides list: filling\n";
|
||||
model_.clear_sides();
|
||||
int sides = resources::gameboard
|
||||
? static_cast<int>(resources::gameboard->teams().size())
|
||||
: 0;
|
||||
for(int side = 1; side <= sides; ++side) {
|
||||
if(!resources::gameboard->teams().at(side - 1).hidden()) {
|
||||
string_map symbols;
|
||||
symbols["side"] = std::to_string(side);
|
||||
std::string side_str = vgettext("Side $side", symbols);
|
||||
side_str = font::span_color(team::get_side_color(side))
|
||||
+ side_str + "</span>";
|
||||
model_.add_side(side, side_str);
|
||||
side_controllers_.emplace_back(std::make_shared<side_controller>(
|
||||
side_str, model_, side));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<side_controller> get_side_controller()
|
||||
{
|
||||
int selected = model_.sides_list->get_selected_row();
|
||||
if(selected < 0 || selected
|
||||
>= static_cast<int>(side_controllers_.size()))
|
||||
return std::shared_ptr<side_controller>(); // null pointer
|
||||
else
|
||||
return side_controllers_.at(selected);
|
||||
}
|
||||
|
||||
void handle_sides_list_item_clicked()
|
||||
{
|
||||
int selected = model_.sides_list->get_selected_row();
|
||||
DBG_GUI << "Sides list: selected row: " << selected << " for side "
|
||||
<< model_.sides[selected] << '\n';
|
||||
if(get_side_controller())
|
||||
get_side_controller()->update_view_from_model();
|
||||
}
|
||||
|
||||
void handle_nicks_list_item_clicked()
|
||||
{
|
||||
int selected = model_.sides_list->get_selected_row();
|
||||
DBG_GUI << "Nicks list: selected row: " << selected << " with nick "
|
||||
<< model_.nicks[selected] << '\n';
|
||||
if(get_side_controller())
|
||||
get_side_controller()->handle_nicks_list_selection();
|
||||
}
|
||||
|
||||
void update_view_from_model()
|
||||
{
|
||||
if(get_side_controller())
|
||||
get_side_controller()->update_view_from_model();
|
||||
}
|
||||
|
||||
void change_control(events::menu_handler* mh)
|
||||
{
|
||||
int selected_side = model_.sides_list->get_selected_row();
|
||||
int selected_nick = model_.nicks_list->get_selected_row();
|
||||
DBG_GUI << "Main: changing control of side "
|
||||
<< model_.sides[selected_side] << " to nick "
|
||||
<< model_.nicks[selected_nick] << '\n';
|
||||
if(mh) // since in unit tests we pass a null pointer to it
|
||||
mh->request_control_change(model_.sides[selected_side],
|
||||
model_.nicks[selected_nick]);
|
||||
}
|
||||
|
||||
private:
|
||||
model& model_;
|
||||
side_controller_ptr_vector side_controllers_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* The view is an interface that displays data (the model) and routes user
|
||||
* commands to the controller to act upon that data.
|
||||
*/
|
||||
class mp_change_control::view
|
||||
{
|
||||
public:
|
||||
view() : model_(), controller_(model_)
|
||||
{
|
||||
}
|
||||
|
||||
void pre_show(window& window)
|
||||
{
|
||||
model_.clear_sides();
|
||||
controller_.show_sides_list();
|
||||
model_.clear_nicks();
|
||||
controller_.update_view_from_model();
|
||||
}
|
||||
|
||||
void handle_sides_list_item_clicked(window& window)
|
||||
{
|
||||
controller_.handle_sides_list_item_clicked();
|
||||
}
|
||||
|
||||
void handle_nicks_list_item_clicked(window& window)
|
||||
{
|
||||
controller_.handle_nicks_list_item_clicked();
|
||||
}
|
||||
|
||||
void bind(window& window)
|
||||
{
|
||||
DBG_GUI << "Main: Binding widgets and callbacks\n";
|
||||
model_.sides_list
|
||||
= &find_widget<listbox>(&window, "sides_list", false);
|
||||
model_.nicks_list
|
||||
= &find_widget<listbox>(&window, "nicks_list", false);
|
||||
|
||||
#ifdef GUI2_EXPERIMENTAL_LISTBOX
|
||||
connect_signal_notify_modified(
|
||||
*model_.sides_list,
|
||||
std::bind(&mp_change_control::view::
|
||||
handle_sides_list_item_clicked,
|
||||
this,
|
||||
std::ref(window)));
|
||||
|
||||
connect_signal_notify_modified(
|
||||
*model_.nicks_list,
|
||||
std::bind(&mp_change_control::view::
|
||||
handle_nicks_list_item_clicked,
|
||||
this,
|
||||
std::ref(window)));
|
||||
#else
|
||||
model_.sides_list->set_callback_value_change(
|
||||
dialog_view_callback<mp_change_control,
|
||||
mp_change_control::view,
|
||||
&mp_change_control::view::
|
||||
handle_sides_list_item_clicked>);
|
||||
|
||||
model_.nicks_list->set_callback_value_change(
|
||||
dialog_view_callback<mp_change_control,
|
||||
mp_change_control::view,
|
||||
&mp_change_control::view::
|
||||
handle_nicks_list_item_clicked>);
|
||||
#endif
|
||||
}
|
||||
|
||||
void post_show(int retval, events::menu_handler* mh)
|
||||
{
|
||||
if(retval == window::OK) {
|
||||
controller_.change_control(mh);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
model model_;
|
||||
controller controller_;
|
||||
};
|
||||
|
||||
REGISTER_DIALOG(mp_change_control)
|
||||
|
||||
mp_change_control::mp_change_control(events::menu_handler* mh)
|
||||
: menu_handler_(mh), view_(new view)
|
||||
: menu_handler_(mh)
|
||||
, selected_side_(0)
|
||||
, selected_nick_(0)
|
||||
, sides_()
|
||||
, nicks_()
|
||||
{
|
||||
}
|
||||
|
||||
std::shared_ptr<mp_change_control::view> mp_change_control::get_view()
|
||||
{
|
||||
return view_;
|
||||
}
|
||||
|
||||
void mp_change_control::pre_show(window& window)
|
||||
{
|
||||
view_->bind(window);
|
||||
view_->pre_show(window);
|
||||
listbox& sides_list = find_widget<listbox>(&window, "sides_list", false);
|
||||
listbox& nicks_list = find_widget<listbox>(&window, "nicks_list", false);
|
||||
|
||||
#ifdef GUI2_EXPERIMENTAL_LISTBOX
|
||||
connect_signal_notify_modified(sides_list,
|
||||
std::bind(&mp_change_control::handle_sides_list_item_clicked, this, std::ref(window)));
|
||||
|
||||
connect_signal_notify_modified(nicks_list,
|
||||
std::bind(&mp_change_control::handle_nicks_list_item_clicked, this, std::ref(window)));
|
||||
#else
|
||||
sides_list.set_callback_value_change(
|
||||
dialog_callback<mp_change_control, &mp_change_control::handle_sides_list_item_clicked>);
|
||||
|
||||
nicks_list.set_callback_value_change(
|
||||
dialog_callback<mp_change_control, &mp_change_control::handle_nicks_list_item_clicked>);
|
||||
#endif
|
||||
|
||||
//
|
||||
// Initialize sides list
|
||||
//
|
||||
const unsigned int num_sides = resources::gameboard
|
||||
? static_cast<unsigned int>(resources::gameboard->teams().size())
|
||||
: 0;
|
||||
|
||||
for(unsigned int side = 1; side <= num_sides; ++side) {
|
||||
if(resources::gameboard->teams().at(side - 1).hidden()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
sides_.push_back(side);
|
||||
|
||||
std::map<std::string, string_map> data;
|
||||
string_map item;
|
||||
|
||||
std::string side_str = vgettext("Side $side", {{"side", std::to_string(side)}});
|
||||
side_str = font::span_color(team::get_side_color(side)) + side_str + "</span>";
|
||||
|
||||
item["id"] = (formatter() << "side_" << side).str();
|
||||
item["label"] = side_str;
|
||||
item["use_markup"] = "true";
|
||||
data.emplace("side", item);
|
||||
|
||||
sides_list.add_row(data);
|
||||
}
|
||||
|
||||
//
|
||||
// Prepare set of available nicknames
|
||||
//
|
||||
std::set<std::string> temp_nicks;
|
||||
for(const auto& team : resources::gameboard->teams()) {
|
||||
if(!team.is_local_ai() && !team.is_network_ai() && !team.is_idle()
|
||||
&& !team.is_empty() && !team.current_player().empty())
|
||||
{
|
||||
temp_nicks.insert(team.current_player());
|
||||
}
|
||||
}
|
||||
|
||||
const std::set<std::string>& observers = resources::screen->observers();
|
||||
temp_nicks.insert(observers.begin(), observers.end());
|
||||
|
||||
// In case we are an observer, it isn't in the observers set and has to be added manually.
|
||||
temp_nicks.insert(preferences::login());
|
||||
|
||||
//
|
||||
// Initialize nick list
|
||||
//
|
||||
for(const std::string& nick : temp_nicks) {
|
||||
nicks_.push_back(nick);
|
||||
|
||||
std::map<std::string, string_map> data;
|
||||
string_map item;
|
||||
|
||||
item["id"] = nick;
|
||||
item["label"] = nick;
|
||||
item["use_markup"] = "true";
|
||||
data.emplace("nick", item);
|
||||
|
||||
nicks_list.add_row(data);
|
||||
}
|
||||
|
||||
handle_sides_list_item_clicked(window);
|
||||
handle_nicks_list_item_clicked(window);
|
||||
}
|
||||
|
||||
void mp_change_control::post_show(window& /*window*/)
|
||||
void mp_change_control::handle_sides_list_item_clicked(window& window)
|
||||
{
|
||||
view_->post_show(get_retval(), menu_handler_);
|
||||
selected_side_ = find_widget<listbox>(&window, "sides_list", false).get_selected_row();
|
||||
|
||||
highlight_side_nick(window);
|
||||
}
|
||||
|
||||
void mp_change_control::handle_nicks_list_item_clicked(window& window)
|
||||
{
|
||||
selected_nick_ = find_widget<listbox>(&window, "nicks_list", false).get_selected_row();
|
||||
}
|
||||
|
||||
void mp_change_control::highlight_side_nick(window& window)
|
||||
{
|
||||
listbox& nicks_list = find_widget<listbox>(&window, "nicks_list", false);
|
||||
const auto& teams = resources::gameboard->teams();
|
||||
|
||||
int i = 0;
|
||||
for(const std::string& nick : nicks_) {
|
||||
std::string label_str = "";
|
||||
|
||||
if(selected_side_ <= static_cast<unsigned int>(teams.size()) && teams.at(selected_side_).current_player() == nick) {
|
||||
label_str = formatter() << "<b>" << nick << "</b>";
|
||||
} else {
|
||||
label_str = nick;
|
||||
}
|
||||
|
||||
grid* row_grid = nicks_list.get_row_grid(i);
|
||||
find_widget<label>(row_grid, nick, false).set_label(label_str);
|
||||
|
||||
++i;
|
||||
}
|
||||
}
|
||||
|
||||
void mp_change_control::post_show(window& window)
|
||||
{
|
||||
if(window.get_retval() == window::OK) {
|
||||
DBG_GUI << "Main: changing control of side "
|
||||
<< sides_[selected_side_] << " to nick "
|
||||
<< nicks_[selected_nick_] << std::endl;
|
||||
|
||||
if(menu_handler_) { // since in unit tests we pass a null pointer to it
|
||||
menu_handler_->request_control_change(
|
||||
sides_[selected_side_],
|
||||
nicks_[selected_nick_]
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace dialogs
|
||||
|
|
|
@ -17,7 +17,13 @@
|
|||
#define GUI_DIALOGS_MP_CHANGE_CONTROL_HPP_INCLUDED
|
||||
|
||||
#include "gui/dialogs/modal_dialog.hpp"
|
||||
#include "menu_events.hpp"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace events
|
||||
{
|
||||
class menu_handler;
|
||||
}
|
||||
|
||||
namespace gui2
|
||||
{
|
||||
|
@ -27,12 +33,7 @@ namespace dialogs
|
|||
class mp_change_control : public modal_dialog
|
||||
{
|
||||
public:
|
||||
class model;
|
||||
class view;
|
||||
class controller;
|
||||
|
||||
explicit mp_change_control(events::menu_handler* mh);
|
||||
std::shared_ptr<view> get_view();
|
||||
|
||||
private:
|
||||
/** Inherited from modal_dialog, implemented by REGISTER_DIALOG. */
|
||||
|
@ -40,11 +41,29 @@ private:
|
|||
|
||||
/** Inherited from modal_dialog. */
|
||||
virtual void pre_show(window& window) override;
|
||||
|
||||
/** Inherited from modal_dialog. */
|
||||
virtual void post_show(window& window) override;
|
||||
|
||||
void handle_sides_list_item_clicked(window& window);
|
||||
void handle_nicks_list_item_clicked(window& window);
|
||||
|
||||
void highlight_side_nick(window& window);
|
||||
|
||||
events::menu_handler* menu_handler_;
|
||||
std::shared_ptr<view> view_;
|
||||
|
||||
unsigned int selected_side_;
|
||||
unsigned int selected_nick_;
|
||||
|
||||
// Contains the mapping from listbox labels to actual sides
|
||||
std::vector<int> sides_;
|
||||
|
||||
// Contains the mapping from listbox labels to actual nicks
|
||||
std::vector<std::string> nicks_;
|
||||
|
||||
//std::map<int, std::string> side_nick_map_;
|
||||
};
|
||||
|
||||
} // namespace dialogs
|
||||
} // namespace gui2
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue