MP Create: split options handling into a helper class

This is so the same code can be used for an option-only configure dialog in SP.
This commit is contained in:
Charles Dang 2016-09-02 03:23:04 +11:00
parent c3a89396fc
commit 725df26dd2
7 changed files with 351 additions and 247 deletions

View file

@ -607,6 +607,8 @@
<Unit filename="../../src/gui/dialogs/multiplayer/mp_login.hpp" />
<Unit filename="../../src/gui/dialogs/multiplayer/mp_method_selection.cpp" />
<Unit filename="../../src/gui/dialogs/multiplayer/mp_method_selection.hpp" />
<Unit filename="../../src/gui/dialogs/multiplayer/mp_options_helper.cpp" />
<Unit filename="../../src/gui/dialogs/multiplayer/mp_options_helper.hpp" />
<Unit filename="../../src/gui/dialogs/multiplayer/synced_choice_wait.cpp" />
<Unit filename="../../src/gui/dialogs/multiplayer/synced_choice_wait.hpp" />
<Unit filename="../../src/gui/dialogs/network_transmission.cpp" />

View file

@ -833,6 +833,7 @@ set(wesnoth-main_SRC
gui/dialogs/multiplayer/mp_join_game_password_prompt.cpp
gui/dialogs/multiplayer/mp_login.cpp
gui/dialogs/multiplayer/mp_method_selection.cpp
gui/dialogs/multiplayer/mp_options_helper.hpp
gui/dialogs/network_transmission.cpp
gui/dialogs/popup.cpp
gui/dialogs/preferences_dialog.cpp

View file

@ -410,6 +410,7 @@ wesnoth_sources = Split("""
gui/dialogs/multiplayer/mp_join_game_password_prompt.cpp
gui/dialogs/multiplayer/mp_login.cpp
gui/dialogs/multiplayer/mp_method_selection.cpp
gui/dialogs/multiplayer/mp_options_helper.hpp
gui/dialogs/multiplayer/synced_choice_wait.cpp
gui/dialogs/network_transmission.cpp
gui/dialogs/popup.cpp

View file

@ -38,8 +38,6 @@
#include "gui/widgets/toggle_button.hpp"
#include "gui/widgets/toggle_panel.hpp"
#include "gui/widgets/text_box.hpp"
#include "gui/widgets/tree_view.hpp"
#include "gui/widgets/tree_view_node.hpp"
#include "game_config.hpp"
#include "savegame.hpp"
#include "settings.hpp"
@ -63,6 +61,7 @@ tmp_create_game::tmp_create_game(const config& cfg, ng::create_engine& create_en
: cfg_(cfg)
, create_engine_(create_eng)
, config_engine_()
, options_manager_()
, selected_game_index_(-1)
, selected_rfm_index_(-1)
, use_map_settings_(register_bool( "use_map_settings", true, prefs::use_map_settings, prefs::set_use_map_settings,
@ -137,6 +136,11 @@ void tmp_create_game::pre_show(twindow& window)
find_widget<tbutton>(&window, "create_game", false),
std::bind(&tmp_create_game::dialog_exit_hook, this, std::ref(window)));
//
// Set up the options manager. Needs to be done before selecting an initial tab
//
options_manager_.reset(new tmp_options_helper(window, create_engine_));
//
// Set up filtering
//
@ -522,229 +526,9 @@ void tmp_create_game::display_games_of_type(twindow& window, ng::level::TYPE typ
update_details(window);
}
template<typename T>
void tmp_create_game::update_options_data_map(T* widget, const option_source& source)
{
options_data_[source][widget->id()] = widget->get_value();
}
template<>
void tmp_create_game::update_options_data_map(ttoggle_button* widget, const option_source& source)
{
options_data_[source][widget->id()] = widget->get_value_bool();
}
void tmp_create_game::reset_options_data(twindow& window, const option_source& source, bool& handled, bool& halt)
{
options_data_[source].clear();
update_options_list(window);
handled = true;
halt = true;
}
void tmp_create_game::display_custom_options(twindow& window, ttree_view& tree, std::string&& type, const config& cfg)
{
// Needed since some compilers don't like passing just {}
static const std::map<std::string, string_map> empty_map;
visible_options_.push_back({type, cfg["id"]});
auto& data_map = options_data_[visible_options_.back()];
auto set_default_data_value = [&](const std::string& widget_id, const config& cfg) {
if(data_map.find(widget_id) == data_map.end() || data_map[widget_id].empty()) {
data_map[widget_id] = cfg["default"];
}
};
for(const auto& options : cfg.child_range("options")) {
std::map<std::string, string_map> data;
string_map item;
item["label"] = cfg["name"];
data.emplace("tree_view_node_label", item);
ttree_view_node& option_node = tree.add_node("option_node", data);
for(const auto& checkbox_option : options.child_range("checkbox")) {
data.clear();
item["label"] = checkbox_option["name"];
item["tooltip"] = checkbox_option["description"];
data.emplace("option_checkbox", item);
ttree_view_node& node = option_node.add_child("option_checkbox_node", data);
ttoggle_button* checkbox = dynamic_cast<ttoggle_button*>(node.find("option_checkbox", true));
VALIDATE(checkbox, missing_widget("option_checkbox"));
const std::string widget_id = checkbox_option["id"];
set_default_data_value(widget_id, checkbox_option);
checkbox->set_id(widget_id);
checkbox->set_value(data_map[widget_id].to_bool());
checkbox->set_callback_state_change(
std::bind(&tmp_create_game::update_options_data_map<ttoggle_button>, this, checkbox, visible_options_.back()));
}
// Only add a spacer if there were an option of this type
if(options.has_child("checkbox")) {
option_node.add_child("options_spacer_node", empty_map);
}
for(const auto& menu_button_option : options.child_range("combo")) {
data.clear();
item.clear();
item["label"] = menu_button_option["name"];
data.emplace("menu_button_label", item);
item["tooltip"] = menu_button_option["description"];
data.emplace("option_menu_button", item);
std::vector<config> combo_items;
std::vector<std::string> combo_values;
config::const_child_itors items = menu_button_option.child_range("item");
for(auto item : items) {
// Comboboxes expect this key to be 'label' not 'name'
item["label"] = item["name"];
combo_items.push_back(item);
combo_values.push_back(item["value"]);
}
if(combo_items.empty()) {
continue;
}
ttree_view_node& node = option_node.add_child("option_menu_button_node", data);
tmenu_button* menu_button = dynamic_cast<tmenu_button*>(node.find("option_menu_button", true));
VALIDATE(menu_button, missing_widget("option_menu_button"));
const std::string widget_id = menu_button_option["id"];
set_default_data_value(widget_id, menu_button_option);
menu_button->set_id(widget_id);
menu_button->set_values(combo_items);
config::attribute_value val = data_map[widget_id];
auto iter = std::find_if(items.begin(), items.end(), [&val](const config& cfg) {
return cfg["value"] == val;
});
if(iter != items.end()) {
menu_button->set_selected(iter - items.begin());
}
menu_button->connect_click_handler(
std::bind(&tmp_create_game::update_options_data_map<tmenu_button>, this, menu_button, visible_options_.back()));
}
// Only add a spacer if there were an option of this type
if(options.has_child("combo")) {
option_node.add_child("options_spacer_node", empty_map);
}
for(const auto& slider_option : options.child_range("slider")) {
data.clear();
item.clear();
item["label"] = slider_option["name"];
data.emplace("slider_label", item);
item["tooltip"] = slider_option["description"];
data.emplace("option_slider", item);
ttree_view_node& node = option_node.add_child("option_slider_node", data);
tslider* slider = dynamic_cast<tslider*>(node.find("option_slider", true));
VALIDATE(slider, missing_widget("option_slider"));
const std::string widget_id = slider_option["id"];
set_default_data_value(widget_id, slider_option);
slider->set_id(widget_id);
slider->set_maximum_value(slider_option["max"].to_int());
slider->set_minimum_value(slider_option["min"].to_int());
slider->set_step_size(slider_option["step"].to_int(1));
slider->set_value(data_map[widget_id].to_int());
connect_signal_notify_modified(*slider,
std::bind(&tmp_create_game::update_options_data_map<tslider>, this, slider, visible_options_.back()));
}
// Only add a spacer if there were an option of this type
if(options.has_child("slider")) {
option_node.add_child("options_spacer_node", empty_map);
}
for(const auto& text_entry_option : options.child_range("entry")) {
data.clear();
item.clear();
item["label"] = text_entry_option["name"];
data.emplace("text_entry_label", item);
item["tooltip"] = text_entry_option["description"];
data.emplace("option_text_entry", item);
ttree_view_node& node = option_node.add_child("option_text_entry_node", data);
ttext_box* textbox = dynamic_cast<ttext_box*>(node.find("option_text_entry", true));
VALIDATE(textbox, missing_widget("option_text_entry"));
const std::string widget_id = text_entry_option["id"];
set_default_data_value(widget_id, text_entry_option);
textbox->set_id(widget_id);
textbox->set_value(data_map[widget_id].str());
textbox->set_text_changed_callback(
std::bind(&tmp_create_game::update_options_data_map<ttext_box>, this, textbox, visible_options_.back()));
}
// Add the Defaults button at the end
ttree_view_node& node = option_node.add_child("options_default_button", empty_map);
connect_signal_mouse_left_click(find_widget<tbutton>(&node, "reset_option_values", false),
std::bind(&tmp_create_game::reset_options_data, this, std::ref(window), visible_options_.back(),
std::placeholders::_3, std::placeholders::_4));
}
}
void tmp_create_game::update_options_list(twindow& window)
{
ttree_view& options_tree = find_widget<ttree_view>(&window, "custom_options", false);
// TODO: might be inefficient to regenerate this every single time this tab is selected
// Maybe look into caching the result if no change has been made to the selection.
visible_options_.clear();
options_tree.clear();
display_custom_options(window, options_tree,
create_engine_.current_level_type() == ng::level::TYPE::CAMPAIGN ? "campaign" : "multiplayer",
create_engine_.current_level().data());
display_custom_options(window, options_tree, "era", create_engine_.curent_era_cfg());
std::set<std::string> activemods(create_engine_.active_mods().begin(), create_engine_.active_mods().end());
for(const auto& mod : create_engine_.get_const_extras_by_type(ng::create_engine::MP_EXTRA::MOD)) {
if(activemods.find(mod->id) != activemods.end()) {
display_custom_options(window, options_tree, "modification", *mod->cfg);
}
}
// No custom options, display a message
find_widget<tcontrol>(&window, "no_options_notice", false).set_visible(options_tree.empty() ? twindow::tvisible::visible : twindow::tvisible::invisible);
options_manager_->update_options_list();
}
void tmp_create_game::show_generator_settings(twindow& window)
@ -981,10 +765,10 @@ void tmp_create_game::post_show(twindow& window)
prefs::set_random_faction_mode(mp_game_settings::RANDOM_FACTION_MODE::enum_to_string(rfm_types_[selected_rfm_index_]));
config options;
for(const auto& source : visible_options_) {
for(const auto& source : options_manager_->get_visible_options()) {
config& mod = options.add_child(source.level_type);
mod["id"] = source.id;
for(const auto& option : options_data_[source]) {
for(const auto& option : options_manager_->get_options_data()[source]) {
// TODO: change this to some key=value format as soon as we drop the old mp configure screen.
mod.add_child("option", config_of("id", option.first)("value", option.second));
}

View file

@ -17,6 +17,7 @@
#include "gui/dialogs/dialog.hpp"
#include "gui/dialogs/multiplayer/plugin_executor.hpp"
#include "gui/dialogs/multiplayer/mp_options_helper.hpp"
#include "game_initialization/create_engine.hpp"
#include "game_initialization/configure_engine.hpp"
@ -30,7 +31,6 @@ namespace gui2
class ttoggle_button;
class ttoggle_panel;
class ttree_view;
class twidget;
class tmp_create_game : public tdialog, private plugin_executor
@ -52,29 +52,9 @@ private:
const config& cfg_;
struct option_source {
std::string level_type;
std::string id;
friend bool operator<(const option_source& a, const option_source& b) {
return a.level_type < b.level_type && a.id < b.id;
}
};
using option_map = std::map<std::string, config::attribute_value>;
std::vector<option_source> visible_options_;
std::map<option_source, option_map> options_data_;
void display_custom_options(twindow& window, ttree_view& options_tree, std::string&& type, const config& data);
template<typename T>
void update_options_data_map(T* widget, const option_source& source);
void update_options_data_map(ttoggle_button* widget, const option_source& source);
void reset_options_data(twindow& window, const option_source& source, bool& handled, bool& halt);
ng::create_engine& create_engine_;
std::unique_ptr<ng::configure_engine> config_engine_;
std::unique_ptr<tmp_options_helper> options_manager_;
int selected_game_index_;
int selected_rfm_index_;

View file

@ -0,0 +1,261 @@
/*
Copyright (C) 2008 - 2016 by 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/multiplayer/mp_options_helper.hpp"
#include "gui/auxiliary/find_widget.hpp"
#include "gui/widgets/button.hpp"
#include "gui/widgets/menu_button.hpp"
#include "gui/widgets/slider.hpp"
#include "gui/widgets/toggle_button.hpp"
#include "gui/widgets/text_box.hpp"
#include "gui/widgets/tree_view.hpp"
#include "gui/widgets/tree_view_node.hpp"
#include "gui/widgets/window.hpp"
namespace gui2 {
tmp_options_helper::tmp_options_helper(twindow& window, ng::create_engine& create_engine)
: options_tree_(find_widget<ttree_view>(&window, "custom_options", false))
, no_options_notice_(find_widget<tcontrol>(&window, "no_options_notice", false))
, visible_options_()
, options_data_()
, create_engine_(create_engine)
{
update_options_list();
}
void tmp_options_helper::update_options_list()
{
visible_options_.clear();
options_tree_.clear();
display_custom_options(options_tree_,
create_engine_.current_level_type() == ng::level::TYPE::CAMPAIGN ? "campaign" : "multiplayer",
create_engine_.current_level().data());
display_custom_options(options_tree_, "era", create_engine_.curent_era_cfg());
std::set<std::string> activemods(create_engine_.active_mods().begin(), create_engine_.active_mods().end());
for(const auto& mod : create_engine_.get_const_extras_by_type(ng::create_engine::MP_EXTRA::MOD)) {
if(activemods.find(mod->id) != activemods.end()) {
display_custom_options(options_tree_, "modification", *mod->cfg);
}
}
// No custom options, display a message
no_options_notice_.set_visible(options_tree_.empty() ? twindow::tvisible::visible : twindow::tvisible::invisible);
}
template<typename T>
void tmp_options_helper::update_options_data_map(T* widget, const option_source& source)
{
options_data_[source][widget->id()] = widget->get_value();
}
template<>
void tmp_options_helper::update_options_data_map(ttoggle_button* widget, const option_source& source)
{
options_data_[source][widget->id()] = widget->get_value_bool();
}
void tmp_options_helper::reset_options_data(const option_source& source, bool& handled, bool& halt)
{
options_data_[source].clear();
update_options_list();
handled = true;
halt = true;
}
void tmp_options_helper::display_custom_options(ttree_view& tree, std::string&& type, const config& cfg)
{
// Needed since some compilers don't like passing just {}
static const std::map<std::string, string_map> empty_map;
visible_options_.push_back({type, cfg["id"]});
auto& data_map = options_data_[visible_options_.back()];
auto set_default_data_value = [&](const std::string& widget_id, const config& cfg) {
if(data_map.find(widget_id) == data_map.end() || data_map[widget_id].empty()) {
data_map[widget_id] = cfg["default"];
}
};
for(const auto& options : cfg.child_range("options")) {
std::map<std::string, string_map> data;
string_map item;
item["label"] = cfg["name"];
data.emplace("tree_view_node_label", item);
ttree_view_node& option_node = tree.add_node("option_node", data);
for(const auto& checkbox_option : options.child_range("checkbox")) {
data.clear();
item["label"] = checkbox_option["name"];
item["tooltip"] = checkbox_option["description"];
data.emplace("option_checkbox", item);
ttree_view_node& node = option_node.add_child("option_checkbox_node", data);
ttoggle_button* checkbox = dynamic_cast<ttoggle_button*>(node.find("option_checkbox", true));
VALIDATE(checkbox, missing_widget("option_checkbox"));
const std::string widget_id = checkbox_option["id"];
set_default_data_value(widget_id, checkbox_option);
checkbox->set_id(widget_id);
checkbox->set_value(data_map[widget_id].to_bool());
checkbox->set_callback_state_change(
std::bind(&tmp_options_helper::update_options_data_map<ttoggle_button>, this, checkbox, visible_options_.back()));
}
// Only add a spacer if there were an option of this type
if(options.has_child("checkbox")) {
option_node.add_child("options_spacer_node", empty_map);
}
for(const auto& menu_button_option : options.child_range("combo")) {
data.clear();
item.clear();
item["label"] = menu_button_option["name"];
data.emplace("menu_button_label", item);
item["tooltip"] = menu_button_option["description"];
data.emplace("option_menu_button", item);
std::vector<config> combo_items;
std::vector<std::string> combo_values;
config::const_child_itors items = menu_button_option.child_range("item");
for(auto item : items) {
// Comboboxes expect this key to be 'label' not 'name'
item["label"] = item["name"];
combo_items.push_back(item);
combo_values.push_back(item["value"]);
}
if(combo_items.empty()) {
continue;
}
ttree_view_node& node = option_node.add_child("option_menu_button_node", data);
tmenu_button* menu_button = dynamic_cast<tmenu_button*>(node.find("option_menu_button", true));
VALIDATE(menu_button, missing_widget("option_menu_button"));
const std::string widget_id = menu_button_option["id"];
set_default_data_value(widget_id, menu_button_option);
menu_button->set_id(widget_id);
menu_button->set_values(combo_items);
config::attribute_value val = data_map[widget_id];
auto iter = std::find_if(items.begin(), items.end(), [&val](const config& cfg) {
return cfg["value"] == val;
});
if(iter != items.end()) {
menu_button->set_selected(iter - items.begin());
}
menu_button->connect_click_handler(
std::bind(&tmp_options_helper::update_options_data_map<tmenu_button>, this, menu_button, visible_options_.back()));
}
// Only add a spacer if there were an option of this type
if(options.has_child("combo")) {
option_node.add_child("options_spacer_node", empty_map);
}
for(const auto& slider_option : options.child_range("slider")) {
data.clear();
item.clear();
item["label"] = slider_option["name"];
data.emplace("slider_label", item);
item["tooltip"] = slider_option["description"];
data.emplace("option_slider", item);
ttree_view_node& node = option_node.add_child("option_slider_node", data);
tslider* slider = dynamic_cast<tslider*>(node.find("option_slider", true));
VALIDATE(slider, missing_widget("option_slider"));
const std::string widget_id = slider_option["id"];
set_default_data_value(widget_id, slider_option);
slider->set_id(widget_id);
slider->set_maximum_value(slider_option["max"].to_int());
slider->set_minimum_value(slider_option["min"].to_int());
slider->set_step_size(slider_option["step"].to_int(1));
slider->set_value(data_map[widget_id].to_int());
connect_signal_notify_modified(*slider,
std::bind(&tmp_options_helper::update_options_data_map<tslider>, this, slider, visible_options_.back()));
}
// Only add a spacer if there were an option of this type
if(options.has_child("slider")) {
option_node.add_child("options_spacer_node", empty_map);
}
for(const auto& text_entry_option : options.child_range("entry")) {
data.clear();
item.clear();
item["label"] = text_entry_option["name"];
data.emplace("text_entry_label", item);
item["tooltip"] = text_entry_option["description"];
data.emplace("option_text_entry", item);
ttree_view_node& node = option_node.add_child("option_text_entry_node", data);
ttext_box* textbox = dynamic_cast<ttext_box*>(node.find("option_text_entry", true));
VALIDATE(textbox, missing_widget("option_text_entry"));
const std::string widget_id = text_entry_option["id"];
set_default_data_value(widget_id, text_entry_option);
textbox->set_id(widget_id);
textbox->set_value(data_map[widget_id].str());
textbox->set_text_changed_callback(
std::bind(&tmp_options_helper::update_options_data_map<ttext_box>, this, textbox, visible_options_.back()));
}
// Add the Defaults button at the end
ttree_view_node& node = option_node.add_child("options_default_button", empty_map);
connect_signal_mouse_left_click(find_widget<tbutton>(&node, "reset_option_values", false),
std::bind(&tmp_options_helper::reset_options_data, this, visible_options_.back(),
std::placeholders::_3, std::placeholders::_4));
}
}
} // namespace gui2

View file

@ -0,0 +1,75 @@
/*
Copyright (C) 2008 - 2016 by 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_OPTIONS_HELPER_HPP_INCLUDED
#define GUI_DIALOGS_MP_OPTIONS_HELPER_HPP_INCLUDED
#include "game_initialization/create_engine.hpp"
namespace gui2
{
class tcontrol;
class ttoggle_button;
class ttree_view;
class twindow;
class tmp_options_helper
{
public:
tmp_options_helper(twindow& window, ng::create_engine& create_engine);
void update_options_list();
private:
struct option_source {
std::string level_type;
std::string id;
friend bool operator<(const option_source& a, const option_source& b) {
return a.level_type < b.level_type && a.id < b.id;
}
};
void display_custom_options(ttree_view& options_tree, std::string&& type, const config& data);
template<typename T>
void update_options_data_map(T* widget, const option_source& source);
void update_options_data_map(ttoggle_button* widget, const option_source& source);
void reset_options_data(const option_source& source, bool& handled, bool& halt);
ttree_view& options_tree_;
tcontrol& no_options_notice_;
using option_map = std::map<std::string, config::attribute_value>;
std::vector<option_source> visible_options_;
std::map<option_source, option_map> options_data_;
ng::create_engine& create_engine_;
public:
const std::vector<option_source>& get_visible_options() const
{
return visible_options_;
}
std::map<option_source, option_map>& get_options_data()
{
return options_data_;
}
};
} // namespace gui2
#endif