Add a new give_control command.

Applies patch #2625 with some minor modifications.
This commit is contained in:
Mark de Wever 2011-04-10 16:40:40 +00:00
parent 0f67c8db6a
commit 4ac8cababc
10 changed files with 703 additions and 11 deletions

View file

@ -6,6 +6,9 @@ Version 1.9.5+svn:
* Language and i18n:
* Updated translations: British English, French, Galician, German, Greek,
Irish, Japanese, Old English, Spanish
* User interface:
* Patch #2625: added a GUI interface to changing control in multiplayer games. The
command to access it is currently :control_dialog
* WML engine:
* Patch #2610: changed default for turns in [scenario] tag to -1 (unlimited)
* Introduced [recall]check_passability=yes|no key (default yes)

View file

@ -0,0 +1,216 @@
#textdomain wesnoth-lib
###
### Definition of the control panel window.
###
#define _GUI_SIDES_LISTBOX
[listbox]
id = "sides_list"
definition = "default"
[header]
[row]
[column]
grow_factor = 1
horizontal_grow = "true"
border = "all"
border_size = 5
[label]
id = "side_title"
definition = "default"
label = _ "Side"
[/label]
[/column]
[/row]
[/header]
[list_definition]
[row]
[column]
vertical_grow = "true"
horizontal_grow = "true"
[toggle_panel]
definition = "default"
[grid]
[row]
[column]
grow_factor = 1
horizontal_grow = "true"
border = "all"
border_size = 5
[label]
id = "side"
definition = "default"
[/label]
[/column]
[/row]
[/grid]
[/toggle_panel]
[/column]
[/row]
[/list_definition]
[/listbox]
#enddef
#define _GUI_NICKS_LISTBOX
[listbox]
id = "nicks_list"
definition = "default"
[header]
[row]
[column]
grow_factor = 1
horizontal_grow = "true"
border = "all"
border_size = 5
[label]
id = "nick_title"
definition = "default"
label = _ "Nick"
[/label]
[/column]
[/row]
[/header]
[list_definition]
[row]
[column]
vertical_grow = "true"
horizontal_grow = "true"
[toggle_panel]
definition = "default"
[grid]
[row]
[column]
grow_factor = 1
horizontal_grow = "true"
border = "all"
border_size = 5
[label]
id = "nick"
definition = "default"
[/label]
[/column]
[/row]
[/grid]
[/toggle_panel]
[/column]
[/row]
[/list_definition]
[/listbox]
#enddef
#define _GUI_LISTBOXES_GRID
[grid]
[row]
[column]
vertical_alignment=top
grow_factor= 0
border = "all"
border_size = 5
{_GUI_SIDES_LISTBOX}
[/column]
[column]
vertical_alignment=top
grow factor = 1
border = "all"
border_size = 5
{_GUI_NICKS_LISTBOX}
[/column]
[/row]
[/grid]
#enddef
#define _GUI_BUTTONS_GRID
[grid]
[row]
[column]
grow_factor = 7
border = "all"
border_size = 5
horizontal_alignment = "left"
[spacer]
definition = "default"
[/spacer]
[/column]
[column]
grow_factor = 0
border = "all"
border_size = 5
[button]
id = "ok"
definition = "default"
label = _ "OK"
[/button]
[/column]
[column]
grow_factor = 0
border = "all"
border_size = 5
[button]
id = "cancel"
definition = "default"
label = _ "Cancel"
[/button]
[/column]
[/row]
[/grid]
#enddef
[window]
id = "mp_change_control"
description = "Dialog used to change side's controller during MP."
[resolution]
definition = "default"
automatic_placement = "true"
vertical_placement = "center"
horizontal_placement = "center"
[tooltip]
id = "tooltip_large"
[/tooltip]
[helptip]
id = "tooltip_large"
[/helptip]
[grid]
[row] #header
grow_factor = 0
[column]
grow_factor = 7
horizontal_alignment = "left"
[label]
definition = "title"
label = _ "Change control"
[/label]
[/column]
[/row]
[row]
[column]
{_GUI_LISTBOXES_GRID}
[/column]
[/row]
[row] #status
grow_factor = 0
[column]
{_GUI_BUTTONS_GRID}
[/column]
[/row]
[/grid]
[/resolution]
[/window]
#undef _GUI_BUTTONS_GRID
#undef _GUI_LISTBOXES_GRID
#undef _GUI_NICKS_LISTBOX
#undef _GUI_SIDES_LISTBOX

View file

@ -5,7 +5,10 @@ changelog: http://svn.gna.org/viewcvs/*checkout*/wesnoth/trunk/changelog
Version 1.9.5+svn:
* Language and i18n:
* Updated translations: British English, French, Galician, German, Greek,
Irish, Japanese, Old English, Spanish.
Irish, Japanese Old English.
* User interface:
* Patch #2625: added a GUI interface to changing control in multiplayer games. The
command to access it is currently :control_dialog
Version 1.9.5:

View file

@ -440,6 +440,7 @@ set(wesnoth-main_SRC
gui/dialogs/lobby_main.cpp
gui/dialogs/lobby_player_info.cpp
gui/dialogs/message.cpp
gui/dialogs/mp_change_control.cpp
gui/dialogs/mp_cmd_wrapper.cpp
gui/dialogs/mp_connect.cpp
gui/dialogs/mp_create_game.cpp

View file

@ -307,6 +307,7 @@ wesnoth_sources = Split("""
gui/dialogs/lobby_player_info.cpp
gui/dialogs/message.cpp
gui/dialogs/mp_cmd_wrapper.cpp
gui/dialogs/mp_change_control.cpp
gui/dialogs/mp_connect.cpp
gui/dialogs/mp_create_game.cpp
gui/dialogs/mp_create_game_set_password.cpp

View file

@ -0,0 +1,389 @@
/*
Copyright (C) 2011 by Lukasz Dobrogowski <lukasz.dobrogowski@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.
*/
#define GETTEXT_DOMAIN "wesnoth-lib"
#include "gui/dialogs/mp_change_control.hpp"
#include "gui/dialogs/helper.hpp"
#ifdef GUI2_EXPERIMENTAL_LISTBOX
#include "gui/widgets/list.hpp"
#else
#include "gui/widgets/listbox.hpp"
#endif
#include "gui/widgets/settings.hpp"
#include "gui/widgets/window.hpp"
#include "formula_string_utils.hpp"
#include "game_display.hpp"
#include "game_preferences.hpp"
#include "log.hpp"
#include "marked-up_text.hpp"
#include "resources.hpp"
#include "team.hpp"
#include <vector>
#include <boost/bind.hpp>
#include <boost/shared_ptr.hpp>
#include <gui/widgets/button.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 DBG_GUI LOG_STREAM(debug, log_gui)
namespace gui2 {
/*WIKI
* @page = GUIWindowDefinitionWML
* @order = 2_mp_change_control
*
* == Change control dialog ==
*
* This shows the multiplayer change control dialog.
*
* @begin{table}{dialog_widgets}
* sides_list & & listbox & m &
* List of sides participating in the MP game. $
*
* nicks_list & & listbox & m &
* List of nicks of all clients playing or observing the MP game. $
*
* @end{table}
*
*/
template <class D, class V, void (V::*fptr)(twindow&)>
void dialog_view_callback(twidget* caller)
{
D* dialog = dynamic_cast<D*>(caller->dialog());
assert(dialog);
twindow* window = dynamic_cast<twindow*>(caller->get_window());
assert(window);
(*(dialog->get_view()).*fptr)(*window);
}
/**
* The model is an interface defining the data to be displayed or otherwise
* acted upon in the user interface.
*/
class tmp_change_control::model {
public:
model() {}
tlistbox *sides_list;
tlistbox *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_")+str_cast(side_num);
item["label"] = label;
item["use_markup"] = "true";
data.insert(std::make_pair("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.insert(std::make_pair("nick", item));
nicks_list->add_row(data);
}
};
class side_controller {
public:
side_controller(const std::string& name, tmp_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::teams->begin();
it != resources::teams->end(); it++)
{
if (!it->is_ai() && it->controller()!=team::team_info::EMPTY && it->current_player().size()>0)
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
foreach (const std::string &nick, nicks)
{
if (side_number_ <= static_cast<int>(resources::teams->size()) &&
resources::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:
tmp_change_control::model &model_;
std::string const 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 tmp_change_control::controller {
public:
typedef std::vector< boost::shared_ptr<side_controller> > side_controller_ptr_vector;
controller(model &m)
: model_(m)
{
}
void show_sides_list()
{
DBG_GUI << "Sides list: filling\n";
model_.clear_sides();
int sides = resources::teams ? static_cast<int>((*resources::teams).size()) : 0;
for( int side = 1; side<=sides; ++side)
{
if (!resources::teams->at(side-1).hidden())
{
string_map symbols;
symbols["side"] = str_cast(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_.push_back(boost::shared_ptr<side_controller>(
new side_controller(side_str,model_,side)));
}
}
}
boost::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 boost::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 tmp_change_control::view {
public:
view()
: model_(),controller_(model_)
{
}
void pre_show(CVideo &/*video*/, twindow &window)
{
model_.clear_sides();
controller_.show_sides_list();
model_.clear_nicks();
controller_.update_view_from_model();
window.invalidate_layout();//workaround for assertion failure
}
void handle_sides_list_item_clicked(twindow &window)
{
controller_.handle_sides_list_item_clicked();
window.invalidate_layout();//workaround for assertion failure
}
void handle_nicks_list_item_clicked(twindow &window)
{
controller_.handle_nicks_list_item_clicked();
window.invalidate_layout();//workaround for assertion failure
}
void bind(twindow &window)
{
DBG_GUI << "Main: Binding widgets and callbacks\n";
model_.sides_list = &find_widget<tlistbox>(&window, "sides_list", false);
model_.nicks_list = &find_widget<tlistbox>(&window, "nicks_list", false);
#ifdef GUI2_EXPERIMENTAL_LISTBOX
connect_signal_notify_modified(*model_.sides_list, boost::bind(
&tmp_change_control::view::handle_sides_list_item_clicked
, this
, boost::ref(window)));
connect_signal_notify_modified(*model_.nicks_list, boost::bind(
&tmp_change_control::view::handle_nicks_list_item_clicked
, this
, boost::ref(window)));
#else
model_.sides_list->set_callback_value_change(
dialog_view_callback<tmp_change_control, tmp_change_control::view, &tmp_change_control::view::handle_sides_list_item_clicked>);
model_.nicks_list->set_callback_value_change(
dialog_view_callback<tmp_change_control, tmp_change_control::view, &tmp_change_control::view::handle_nicks_list_item_clicked>);
#endif
}
void post_show(int retval, events::menu_handler *mh)
{
if (retval == twindow::OK)
{
controller_.change_control(mh);
}
}
private:
model model_;
controller controller_;
};
REGISTER_DIALOG(mp_change_control)
tmp_change_control::tmp_change_control(events::menu_handler *mh)
: menu_handler_(mh), view_()
{
view_ = boost::shared_ptr<view>(new view());
}
boost::shared_ptr<tmp_change_control::view> tmp_change_control::get_view()
{
return view_;
}
void tmp_change_control::pre_show(CVideo& video, twindow& window)
{
view_->bind(window);
view_->pre_show(video,window);
}
void tmp_change_control::post_show(twindow& /*window*/)
{
view_->post_show(get_retval(), menu_handler_);
}
} //end of namespace gui2

View file

@ -0,0 +1,48 @@
/*
Copyright (C) 2011 by Lukasz Dobrogowski <lukasz.dobrogowski@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 GUI_DIALOGS_MP_CHANGE_CONTROL_HPP_INCLUDED
#define GUI_DIALOGS_MP_CHANGE_CONTROL_HPP_INCLUDED
#include "gui/dialogs/dialog.hpp"
#include "menu_events.hpp"
#include <boost/shared_ptr.hpp>
namespace gui2 {
class tmp_change_control : public tdialog {
public:
class model;
class view;
class controller;
tmp_change_control(events::menu_handler *mh);
boost::shared_ptr<view> get_view();
private:
/** Inherited from tdialog, implemented by REGISTER_DIALOG. */
virtual const std::string& window_id() const;
/** Inherited from tdialog. */
void pre_show(CVideo& video, twindow& window);
void post_show(twindow& window);
events::menu_handler *menu_handler_;
boost::shared_ptr<view> view_;
};
}
#endif /* ! GUI_DIALOGS_MP_CHANGE_CONTROL_HPP_INCLUDED */

View file

@ -37,6 +37,7 @@
#include "gui/dialogs/transient_message.hpp"
#include "gui/dialogs/wml_message.hpp"
#include "gui/dialogs/gamestate_inspector.hpp"
#include "gui/dialogs/mp_change_control.hpp"
#include "gui/dialogs/data_manage.hpp"
#include "gui/dialogs/simple_item_selector.hpp"
#include "gui/dialogs/unit_create.hpp"
@ -2456,6 +2457,7 @@ class console_handler : public map_command_handler<console_handler>, private cha
void do_set_var();
void do_show_var();
void do_inspect();
void do_control_dialog();
void do_manage();
void do_unit();
// void do_buff();
@ -2552,6 +2554,11 @@ class console_handler : public map_command_handler<console_handler>, private cha
_("Grant higher privileges to Lua scripts."), "", "D");
register_command("custom", &console_handler::do_custom,
_("Set the command used by the custom command hotkey"), _("<command>[;<command>...]"));
register_command("give_control"
, &console_handler::do_control_dialog
, _("Invoke a dialog allowing changing control of MP sides.")
, ""
, "N");
register_command("inspect", &console_handler::do_inspect,
_("Launch the gamestate inspector"), "", "D");
register_command("manage", &console_handler::do_manage,
@ -3156,16 +3163,7 @@ void console_handler::do_control() {
command_failed(vgettext("Can't change control of out-of-bounds side: '$side'.", symbols));
return;
}
//if this is our side we are always allowed to change the controller
if (menu_handler_.teams_[side_num - 1].is_human()) {
if (player == preferences::login())
return;
menu_handler_.change_side_controller(side,player);
} else {
//it is not our side, the server will decide if we can change the
//controller (that is if we are host of the game)
menu_handler_.change_side_controller(side,player);
}
menu_handler_.request_control_change(side_num,player);
menu_handler_.textbox_info_.close(*(menu_handler_.gui_));
}
void console_handler::do_clear() {
@ -3444,6 +3442,12 @@ void console_handler::do_inspect() {
inspect_dialog.show(resources::screen->video());
}
void console_handler::do_control_dialog()
{
gui2::tmp_change_control mp_change_control(&menu_handler_);
mp_change_control.show(resources::screen->video());
}
void console_handler::do_manage() {
config cfg;
gui2::tdata_manage manager(cfg);
@ -3607,6 +3611,21 @@ void menu_handler::user_command()
textbox_info_.show(gui::TEXTBOX_COMMAND,sgettext("prompt^Command:"), "", false, *gui_);
}
void menu_handler::request_control_change ( int side_num, const std::string& player )
{
std::string side = str_cast(side_num);
//if this is our side we are always allowed to change the controller
if (teams_[side_num - 1].is_human()) {
if (player == preferences::login())
return;
change_side_controller(side,player);
} else {
//it is not our side, the server will decide if we can change the
//controller (that is if we are host of the game)
change_side_controller(side,player);
}
}
void menu_handler::custom_command()
{
std::vector<std::string> commands = utils::split(preferences::custom_command(), ';');

View file

@ -81,6 +81,7 @@ public:
void unit_hold_position(mouse_handler &mousehandler, int side_num);
void end_unit_turn(mouse_handler &mousehandler, int side_num);
void search();
void request_control_change(int side_num, const std::string &player);
void user_command();
void custom_command();
void ai_formula();

View file

@ -43,6 +43,7 @@
#include "gui/dialogs/gamestate_inspector.hpp"
#include "gui/dialogs/language_selection.hpp"
#include "gui/dialogs/message.hpp"
#include "gui/dialogs/mp_change_control.hpp"
#include "gui/dialogs/mp_cmd_wrapper.hpp"
#include "gui/dialogs/mp_connect.hpp"
#include "gui/dialogs/mp_create_game.hpp"
@ -371,6 +372,7 @@ BOOST_AUTO_TEST_CASE(test_gui2)
test<gui2::tlanguage_selection>();
test<gui2::tmessage>();
test<gui2::tsimple_item_selector>();
test<gui2::tmp_change_control>();
test<gui2::tmp_cmd_wrapper>();
test<gui2::tmp_connect>();
test<gui2::tmp_create_game>();
@ -566,6 +568,15 @@ struct twrapper<gui2::tmessage>
}
};
template<>
struct twrapper<gui2::tmp_change_control>
{
static gui2::tmp_change_control* create()
{
return new gui2::tmp_change_control(NULL);
}
};
template<>
struct twrapper<gui2::tmp_cmd_wrapper>
{