Fix menu_button widget trying to be two things at the same time.

The menu_button is now only a drop-down menu that supports a single selection.
A new multimenu_button widget supports a drop-down menu with multiple selections.
The new multimenu_button widget displays its current selection while not open.
This commit is contained in:
Celtic Minstrel 2017-04-16 00:08:50 -04:00
parent b7aea011f9
commit 1793133a59
14 changed files with 820 additions and 78 deletions

View file

@ -717,6 +717,12 @@
max="-1" max="-1"
super="gui/button_definition" super="gui/button_definition"
[/tag] [/tag]
[tag]
name="multimenu_button_definition"
min="0"
max="-1"
super="gui/button_definition"
[/tag]
[tag] [tag]
name="drawing_definition" name="drawing_definition"
min="0" min="0"
@ -1591,6 +1597,37 @@
[/key] [/key]
[/tag] [/tag]
[/tag] [/tag]
[tag]
name="multimenu_button"
min="0"
max="-1"
super="gui/window/resolution/grid/row/column/button"
[key]
name="maximum_shown"
type="int"
default="-1"
[/key]
[tag]
name = "option"
min="0"
max="-1"
[key]
name="label"
type="string"
default=""
[/key]
[key]
name="tooltip"
type="string"
default=""
[/key]
[key]
name="checkbox"
type="bool"
default=""
[/key]
[/tag]
[/tag]
[tag] [tag]
name="drawing" name="drawing"
min="0" min="0"

View file

@ -0,0 +1,159 @@
#textdomain wesnoth-lib
###
### Definition of the default button.
###
#define _GUI_DRAW_BORDER _COLOR
[rectangle]
x = 0
y = 0
w = "(width)"
h = "(height)"
border_thickness = 1
border_color = {_COLOR}
[/rectangle]
#enddef
#define _GUI_TEXT FONT_SIZE FONT_COLOR
[text]
x = 5
y = {GUI__TEXT_VERTICALLY_CENTRED}
w = "(text_width)"
h = "(text_height)"
# 25 offset to accomadate the arrow
maximum_width = "(width - 25)"
font_size = {FONT_SIZE}
color = {FONT_COLOR}
text = "(text)"
text_markup = false
[/text]
#enddef
#define _GUI_RESOLUTION RESOLUTION MIN_WIDTH DEFAULT_WIDTH HEIGHT EXTRA_WIDTH EXTRA_HEIGHT FONT_SIZE BASE_NAME IPF
[resolution]
{RESOLUTION}
min_width = {MIN_WIDTH}
min_height = {HEIGHT}
default_width = {DEFAULT_WIDTH}
default_height = {HEIGHT}
max_width = 0
max_height = {HEIGHT}
text_extra_width = {EXTRA_WIDTH}
text_extra_height = {EXTRA_HEIGHT}
text_font_size = {FONT_SIZE}
[state_enabled]
[draw]
[image]
w = "(width)"
h = "(height)"
name = "buttons/{BASE_NAME}.png{IPF}"
[/image]
{_GUI_DRAW_BORDER ({GUI__BORDER_COLOR_DARK})}
{_GUI_TEXT ({FONT_SIZE}) ({GUI__FONT_COLOR_ENABLED__TITLE})}
[image]
x = "(width - 25)"
y = 2
name = "icons/arrows/short_arrow_left_25.png~ROTATE(-90)"
[/image]
[/draw]
[/state_enabled]
[state_disabled]
[draw]
[image]
w = "(width)"
h = "(height)"
name = "buttons/{BASE_NAME}.png~GS(){IPF}"
[/image]
{_GUI_DRAW_BORDER ({GUI__FONT_COLOR_DISABLED__DEFAULT})}
{_GUI_TEXT ({FONT_SIZE}) ({GUI__FONT_COLOR_DISABLED__TITLE})}
[image]
x = "(width - 25)"
y = 2
name = "icons/arrows/short_arrow_left_25.png~ROTATE(-90)~GS()"
[/image]
[/draw]
[/state_disabled]
[state_pressed]
[draw]
[image]
w = "(width)"
h = "(height)"
name = "buttons/{BASE_NAME}-pressed.png{IPF}"
[/image]
{_GUI_DRAW_BORDER ({GUI__BORDER_COLOR_DARK})}
{_GUI_TEXT ({FONT_SIZE}) ({GUI__FONT_COLOR_ENABLED__TITLE})}
[image]
x = "(width - 25)"
y = 2
name = "icons/arrows/short_arrow_left_25-active.png~ROTATE(-90)"
[/image]
[/draw]
[/state_pressed]
[state_focused]
[draw]
[image]
w = "(width)"
h = "(height)"
# Doesn't have its own 'active' variation image
name = "buttons/{BASE_NAME}-pressed.png{IPF}"
[/image]
{_GUI_DRAW_BORDER ({GUI__BORDER_COLOR_DARK})}
{_GUI_TEXT ({FONT_SIZE}) ({GUI__FONT_COLOR_ENABLED__TITLE})}
[image]
x = "(width - 25)"
y = 2
name = "icons/arrows/short_arrow_left_25-active.png~ROTATE(-90)"
[/image]
[/draw]
[/state_focused]
[/resolution]
#enddef
[multimenu_button_definition]
id = "default"
description = "Default button"
{_GUI_RESOLUTION () 40 180 30 13 4 ({GUI_FONT_SIZE_SMALL}) "button_dropdown/button_dropdown" ()}
[/multimenu_button_definition]
#undef _GUI_RESOLUTION
#undef _GUI_DRAW_BORDER
#undef _GUI_TEXT

View file

@ -740,10 +740,11 @@
horizontal_alignment = "left" horizontal_alignment = "left"
[menu_button] [multimenu_button]
id = "type_filter" id = "type_filter"
definition = "default" definition = "default"
[/menu_button] maximum_shown = 2
[/multimenu_button]
[/column] [/column]
[column] [column]

View file

@ -236,10 +236,10 @@
border = "all" border = "all"
border_size = 5 border_size = 5
[menu_button] [multimenu_button]
id = "mods_menu" id = "mods_menu"
definition = "default" definition = "default"
[/menu_button] [/multimenu_button]
[/column] [/column]

View file

@ -32,10 +32,11 @@
border_size = 5 border_size = 5
horizontal_alignment = "left" horizontal_alignment = "left"
[menu_button] [multimenu_button]
id = "hotkey_category_menu" id = "hotkey_category_menu"
definition = "default" definition = "default"
[/menu_button] maximum_shown = 3
[/multimenu_button]
[/column] [/column]
[/row] [/row]

View file

@ -2177,6 +2177,12 @@
<ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Test_Debug|Win32'">$(IntDir)Gui\Widgets\</ObjectFileName> <ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Test_Debug|Win32'">$(IntDir)Gui\Widgets\</ObjectFileName>
<ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Test_Release|Win32'">$(IntDir)Gui\Widgets\</ObjectFileName> <ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Test_Release|Win32'">$(IntDir)Gui\Widgets\</ObjectFileName>
</ClCompile> </ClCompile>
<ClCompile Include="..\..\src\gui\widgets\multimenu_button.cpp">
<ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">$(IntDir)Gui\Widgets\</ObjectFileName>
<ObjectFileName Condition="'$(Configuration)|$(Platform)'=='ReleaseDEBUG|Win32'">$(IntDir)Gui\Widgets\</ObjectFileName>
<ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Test_Debug|Win32'">$(IntDir)Gui\Widgets\</ObjectFileName>
<ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Test_Release|Win32'">$(IntDir)Gui\Widgets\</ObjectFileName>
</ClCompile>
<ClCompile Include="..\..\src\gui\widgets\multi_page.cpp"> <ClCompile Include="..\..\src\gui\widgets\multi_page.cpp">
<ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">$(IntDir)Gui\Widgets\</ObjectFileName> <ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">$(IntDir)Gui\Widgets\</ObjectFileName>
<ObjectFileName Condition="'$(Configuration)|$(Platform)'=='ReleaseDEBUG|Win32'">$(IntDir)Gui\Widgets\</ObjectFileName> <ObjectFileName Condition="'$(Configuration)|$(Platform)'=='ReleaseDEBUG|Win32'">$(IntDir)Gui\Widgets\</ObjectFileName>
@ -3760,6 +3766,7 @@
<ClInclude Include="..\..\src\gui\widgets\matrix.hpp" /> <ClInclude Include="..\..\src\gui\widgets\matrix.hpp" />
<ClInclude Include="..\..\src\gui\widgets\menu_button.hpp" /> <ClInclude Include="..\..\src\gui\widgets\menu_button.hpp" />
<ClInclude Include="..\..\src\gui\widgets\minimap.hpp" /> <ClInclude Include="..\..\src\gui\widgets\minimap.hpp" />
<ClInclude Include="..\..\src\gui\widgets\multimenu_button.hpp" />
<ClInclude Include="..\..\src\gui\widgets\multi_page.hpp" /> <ClInclude Include="..\..\src\gui\widgets\multi_page.hpp" />
<ClInclude Include="..\..\src\gui\widgets\pane.hpp" /> <ClInclude Include="..\..\src\gui\widgets\pane.hpp" />
<ClInclude Include="..\..\src\gui\widgets\panel.hpp" /> <ClInclude Include="..\..\src\gui\widgets\panel.hpp" />

View file

@ -1529,6 +1529,9 @@
<ClCompile Include="..\..\src\gui\dialogs\outro.cpp"> <ClCompile Include="..\..\src\gui\dialogs\outro.cpp">
<Filter>Gui\Dialogs</Filter> <Filter>Gui\Dialogs</Filter>
</ClCompile> </ClCompile>
<ClCompile Include="..\..\src\gui\widgets\multimenu_button.cpp">
<Filter>Gui\Widgets</Filter>
</ClCompile>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ClInclude Include="..\..\src\addon\client.hpp"> <ClInclude Include="..\..\src\addon\client.hpp">
@ -2972,6 +2975,9 @@
<ClInclude Include="..\..\src\gui\core\canvas_private.hpp"> <ClInclude Include="..\..\src\gui\core\canvas_private.hpp">
<Filter>Gui\Core</Filter> <Filter>Gui\Core</Filter>
</ClInclude> </ClInclude>
<ClInclude Include="..\..\src\gui\widgets\multimenu_button.hpp">
<Filter>Gui\Widgets</Filter>
</ClInclude>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<CustomBuild Include="..\..\src\tests\test_sdl_utils.hpp"> <CustomBuild Include="..\..\src\tests\test_sdl_utils.hpp">

View file

@ -35,6 +35,7 @@
#include "gui/widgets/button.hpp" #include "gui/widgets/button.hpp"
#include "gui/widgets/label.hpp" #include "gui/widgets/label.hpp"
#include "gui/widgets/menu_button.hpp" #include "gui/widgets/menu_button.hpp"
#include "gui/widgets/multimenu_button.hpp"
#include "gui/widgets/stacked_widget.hpp" #include "gui/widgets/stacked_widget.hpp"
#include "gui/widgets/drawing.hpp" #include "gui/widgets/drawing.hpp"
#include "gui/widgets/image.hpp" #include "gui/widgets/image.hpp"
@ -359,7 +360,7 @@ void addon_manager::pre_show(window& window)
status_filter.set_values(status_filter_entries); status_filter.set_values(status_filter_entries);
status_filter.connect_click_handler(std::bind(&addon_manager::apply_filters, this, std::ref(window))); status_filter.connect_click_handler(std::bind(&addon_manager::apply_filters, this, std::ref(window)));
menu_button& type_filter = find_widget<menu_button>(&window, "type_filter", false); multimenu_button& type_filter = find_widget<multimenu_button>(&window, "type_filter", false);
std::vector<config> type_filter_entries; std::vector<config> type_filter_entries;
for(const auto& f : type_filter_types_) { for(const auto& f : type_filter_types_) {
@ -538,7 +539,7 @@ boost::dynamic_bitset<> addon_manager::get_status_filter_visibility(const window
boost::dynamic_bitset<> addon_manager::get_type_filter_visibility(const window& window) const boost::dynamic_bitset<> addon_manager::get_type_filter_visibility(const window& window) const
{ {
const menu_button& type_filter = find_widget<const menu_button>(&window, "type_filter", false); const multimenu_button& type_filter = find_widget<const multimenu_button>(&window, "type_filter", false);
boost::dynamic_bitset<> toggle_states = type_filter.get_toggle_states(); boost::dynamic_bitset<> toggle_states = type_filter.get_toggle_states();
if(toggle_states.none()) { if(toggle_states.none()) {

View file

@ -28,7 +28,7 @@
#else #else
#include "gui/widgets/listbox.hpp" #include "gui/widgets/listbox.hpp"
#endif #endif
#include "gui/widgets/menu_button.hpp" #include "gui/widgets/multimenu_button.hpp"
#include "gui/widgets/multi_page.hpp" #include "gui/widgets/multi_page.hpp"
#include "gui/widgets/scroll_label.hpp" #include "gui/widgets/scroll_label.hpp"
#include "gui/widgets/settings.hpp" #include "gui/widgets/settings.hpp"
@ -165,7 +165,7 @@ void campaign_selection::pre_show(window& window)
// //
// Set up Mods selection dropdown // Set up Mods selection dropdown
// //
menu_button& mods_menu = find_widget<menu_button>(&window, "mods_menu", false); multimenu_button& mods_menu = find_widget<multimenu_button>(&window, "mods_menu", false);
if(!engine_.get_const_extras_by_type(ng::create_engine::MOD).empty()) { if(!engine_.get_const_extras_by_type(ng::create_engine::MOD).empty()) {
@ -218,7 +218,7 @@ void campaign_selection::post_show(window& window)
void campaign_selection::mod_toggled(window& window) void campaign_selection::mod_toggled(window& window)
{ {
boost::dynamic_bitset<> new_mod_states = find_widget<menu_button>(&window, "mods_menu", false).get_toggle_states(); boost::dynamic_bitset<> new_mod_states = find_widget<multimenu_button>(&window, "mods_menu", false).get_toggle_states();
// Get a mask of any mods that were toggled, regardless of new state // Get a mask of any mods that were toggled, regardless of new state
mod_states_ = mod_states_ ^ new_mod_states; mod_states_ = mod_states_ ^ new_mod_states;

View file

@ -44,6 +44,7 @@
#include "gui/dialogs/transient_message.hpp" #include "gui/dialogs/transient_message.hpp"
#include "gui/widgets/button.hpp" #include "gui/widgets/button.hpp"
#include "gui/widgets/menu_button.hpp" #include "gui/widgets/menu_button.hpp"
#include "gui/widgets/multimenu_button.hpp"
#include "gui/widgets/grid.hpp" #include "gui/widgets/grid.hpp"
#include "gui/widgets/image.hpp" #include "gui/widgets/image.hpp"
#include "gui/widgets/label.hpp" #include "gui/widgets/label.hpp"
@ -704,7 +705,7 @@ void preferences_dialog::post_build(window& window)
hotkey_category_entries.emplace_back(config_of("label", name)("checkbox", false)); hotkey_category_entries.emplace_back(config_of("label", name)("checkbox", false));
} }
menu_button& hotkey_menu = find_widget<menu_button>(&window, "hotkey_category_menu", false); multimenu_button& hotkey_menu = find_widget<multimenu_button>(&window, "hotkey_category_menu", false);
hotkey_menu.set_values(hotkey_category_entries); hotkey_menu.set_values(hotkey_category_entries);
hotkey_menu.set_keep_open(true); hotkey_menu.set_keep_open(true);
@ -859,7 +860,7 @@ void preferences_dialog::default_hotkey_callback(window& window)
listbox& hotkey_list = setup_hotkey_list(window); listbox& hotkey_list = setup_hotkey_list(window);
hotkey_list.set_active_sorting_option({0, listbox::SORT_ASCENDING}, true); hotkey_list.set_active_sorting_option({0, listbox::SORT_ASCENDING}, true);
find_widget<menu_button>(&window, "hotkey_category_menu", false).reset_toggle_states(); find_widget<multimenu_button>(&window, "hotkey_category_menu", false).reset_toggle_states();
} }
void preferences_dialog::remove_hotkey_callback(listbox& hotkeys) void preferences_dialog::remove_hotkey_callback(listbox& hotkeys)
@ -872,7 +873,7 @@ void preferences_dialog::remove_hotkey_callback(listbox& hotkeys)
void preferences_dialog::hotkey_type_filter_callback(window& window) const void preferences_dialog::hotkey_type_filter_callback(window& window) const
{ {
const menu_button& hotkey_menu = find_widget<const menu_button>(&window, "hotkey_category_menu", false); const multimenu_button& hotkey_menu = find_widget<const multimenu_button>(&window, "hotkey_category_menu", false);
boost::dynamic_bitset<> toggle_states = hotkey_menu.get_toggle_states(); boost::dynamic_bitset<> toggle_states = hotkey_menu.get_toggle_states();
boost::dynamic_bitset<> res(visible_hotkeys_.size()); boost::dynamic_bitset<> res(visible_hotkeys_.size());

View file

@ -45,7 +45,6 @@ menu_button::menu_button()
, retval_(0) , retval_(0)
, values_() , values_()
, selected_() , selected_()
, toggle_states_()
, keep_open_(false) , keep_open_(false)
, droplist_(nullptr) , droplist_(nullptr)
{ {
@ -141,7 +140,7 @@ void menu_button::signal_handler_left_button_click(const event::ui_event event,
// If a button has a retval do the default handling. // If a button has a retval do the default handling.
dialogs::drop_down_menu droplist(this->get_rectangle(), this->values_, this->selected_, this->get_use_markup(), this->keep_open_, dialogs::drop_down_menu droplist(this->get_rectangle(), this->values_, this->selected_, this->get_use_markup(), this->keep_open_,
std::bind(&menu_button::toggle_state_changed, this)); nullptr);
droplist_ = &droplist; droplist_ = &droplist;
@ -176,47 +175,9 @@ void menu_button::signal_handler_left_button_click(const event::ui_event event,
droplist_ = nullptr; droplist_ = nullptr;
/* In order to allow toggle button states to be specified by verious dialogs in the values config, we write the state
* bools to the values_ config here, but only if a checkbox= key was already provided. The value of the checkbox= key
* is handled by the drop_down_menu widget.
*
* Passing the dynamic_bitset directly to the drop_down_menu ctor would mean bool values would need to be passed to this
* class independently of the values config by dialogs that use this widget. However, the bool states are also saved
* in a dynamic_bitset class member which can be fetched for other uses if necessary.
*/
update_config_from_toggle_states();
handled = true; handled = true;
} }
void menu_button::update_config_from_toggle_states()
{
for(unsigned i = 0; i < values_.size(); i++) {
::config& c = values_[i];
if(c.has_attribute("checkbox")) {
c["checkbox"] = toggle_states_[i];
}
}
}
void menu_button::reset_toggle_states()
{
toggle_states_.reset();
update_config_from_toggle_states();
}
void menu_button::toggle_state_changed()
{
assert(droplist_ != nullptr);
toggle_states_ = droplist_->get_toggle_states();
if(callback_toggle_state_change_ != nullptr) {
callback_toggle_state_change_(toggle_states_);
}
}
void menu_button::set_values(const std::vector<::config>& values, int selected) void menu_button::set_values(const std::vector<::config>& values, int selected)
{ {
assert(static_cast<size_t>(selected) < values.size()); assert(static_cast<size_t>(selected) < values.size());
@ -228,7 +189,6 @@ void menu_button::set_values(const std::vector<::config>& values, int selected)
values_ = values; values_ = values;
selected_ = selected; selected_ = selected;
toggle_states_.resize(values_.size(), false);
set_label(values_[selected_]["label"]); set_label(values_[selected_]["label"]);
} }

View file

@ -88,27 +88,12 @@ public:
callback_state_change_ = callback; callback_state_change_ = callback;
} }
/**
* Sets a callback that will be called immediately when any toggle button is selected or deselected.
*/
virtual void set_callback_toggle_state_change(std::function<void(boost::dynamic_bitset<>)> callback)
{
callback_toggle_state_change_ = callback;
}
/** Returns the value of the selected row */ /** Returns the value of the selected row */
std::string get_value_string() const std::string get_value_string() const
{ {
return values_[selected_]["label"]; return values_[selected_]["label"];
} }
boost::dynamic_bitset<> get_toggle_states() const
{
return toggle_states_;
}
void reset_toggle_states();
void set_keep_open(const bool keep_open) void set_keep_open(const bool keep_open)
{ {
keep_open_ = keep_open; keep_open_ = keep_open;
@ -150,8 +135,6 @@ private:
int selected_; int selected_;
boost::dynamic_bitset<> toggle_states_;
bool keep_open_; bool keep_open_;
dialogs::drop_down_menu* droplist_; dialogs::drop_down_menu* droplist_;
@ -159,10 +142,6 @@ private:
/** See selectable_item::set_callback_state_change. */ /** See selectable_item::set_callback_state_change. */
std::function<void(widget&)> callback_state_change_; std::function<void(widget&)> callback_state_change_;
std::function<void(boost::dynamic_bitset<>)> callback_toggle_state_change_;
void update_config_from_toggle_states();
/** See @ref styled_widget::get_control_type. */ /** See @ref styled_widget::get_control_type. */
virtual const std::string& get_control_type() const override; virtual const std::string& get_control_type() const override;
@ -177,8 +156,6 @@ private:
void signal_handler_left_button_up(const event::ui_event event, bool& handled); void signal_handler_left_button_up(const event::ui_event event, bool& handled);
void signal_handler_left_button_click(const event::ui_event event, bool& handled); void signal_handler_left_button_click(const event::ui_event event, bool& handled);
void toggle_state_changed();
}; };
// }---------- DEFINITION ---------{ // }---------- DEFINITION ---------{

View file

@ -0,0 +1,379 @@
/*
Copyright (C) 2008 - 2017 by Mark de Wever <koraq@xs4all.nl>
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/widgets/multimenu_button.hpp"
#include "gui/core/log.hpp"
#include "gui/core/widget_definition.hpp"
#include "gui/core/window_builder.hpp"
#include "gui/core/window_builder/helper.hpp"
#include "gui/core/register_widget.hpp"
#include "gui/widgets/settings.hpp"
#include "gui/widgets/window.hpp"
#include "config_assign.hpp"
#include "sound.hpp"
#include "formula/string_utils.hpp"
#include "utils/functional.hpp"
#include "gettext.hpp"
#define LOG_SCOPE_HEADER get_control_type() + " [" + id() + "] " + __func__
#define LOG_HEADER LOG_SCOPE_HEADER + ':'
namespace gui2
{
// ------------ WIDGET -----------{
REGISTER_WIDGET(multimenu_button)
multimenu_button::multimenu_button()
: styled_widget(COUNT)
, state_(ENABLED)
, retval_(0)
, values_()
, toggle_states_()
, keep_open_(false)
, droplist_(nullptr)
{
values_.emplace_back(config_of("label", this->get_label()));
connect_signal<event::MOUSE_ENTER>(
std::bind(&multimenu_button::signal_handler_mouse_enter, this, _2, _3));
connect_signal<event::MOUSE_LEAVE>(
std::bind(&multimenu_button::signal_handler_mouse_leave, this, _2, _3));
connect_signal<event::LEFT_BUTTON_DOWN>(std::bind(
&multimenu_button::signal_handler_left_button_down, this, _2, _3));
connect_signal<event::LEFT_BUTTON_UP>(
std::bind(&multimenu_button::signal_handler_left_button_up, this, _2, _3));
connect_signal<event::LEFT_BUTTON_CLICK>(std::bind(
&multimenu_button::signal_handler_left_button_click, this, _2, _3));
}
void multimenu_button::set_active(const bool active)
{
if(get_active() != active) {
set_state(active ? ENABLED : DISABLED);
}
}
bool multimenu_button::get_active() const
{
return state_ != DISABLED;
}
unsigned multimenu_button::get_state() const
{
return state_;
}
void multimenu_button::set_state(const state_t state)
{
if(state != state_) {
state_ = state;
set_is_dirty(true);
}
}
const std::string& multimenu_button::get_control_type() const
{
static const std::string type = "multimenu_button";
return type;
}
void multimenu_button::signal_handler_mouse_enter(const event::ui_event event, bool& handled)
{
DBG_GUI_E << LOG_HEADER << ' ' << event << ".\n";
set_state(FOCUSED);
handled = true;
}
void multimenu_button::signal_handler_mouse_leave(const event::ui_event event, bool& handled)
{
DBG_GUI_E << LOG_HEADER << ' ' << event << ".\n";
set_state(ENABLED);
handled = true;
}
void multimenu_button::signal_handler_left_button_down(const event::ui_event event, bool& handled)
{
DBG_GUI_E << LOG_HEADER << ' ' << event << ".\n";
window* window = get_window();
if(window) {
window->mouse_capture();
}
set_state(PRESSED);
handled = true;
}
void multimenu_button::signal_handler_left_button_up(const event::ui_event event, bool& handled)
{
DBG_GUI_E << LOG_HEADER << ' ' << event << ".\n";
set_state(FOCUSED);
handled = true;
}
void multimenu_button::signal_handler_left_button_click(const event::ui_event event, bool& handled)
{
assert(get_window());
DBG_GUI_E << LOG_HEADER << ' ' << event << ".\n";
sound::play_UI_sound(settings::sound_button_click);
// If a button has a retval do the default handling.
dialogs::drop_down_menu droplist(this->get_rectangle(), this->values_, -1, this->get_use_markup(), this->keep_open_,
std::bind(&multimenu_button::toggle_state_changed, this));
droplist_ = &droplist;
droplist.show(get_window()->video());
droplist_ = nullptr;
if(retval_ != 0) {
if(window* window = get_window()) {
window->set_retval(retval_);
return;
}
}
/* In order to allow toggle button states to be specified by verious dialogs in the values config, we write the state
* bools to the values_ config here, but only if a checkbox= key was already provided. The value of the checkbox= key
* is handled by the drop_down_menu widget.
*
* Passing the dynamic_bitset directly to the drop_down_menu ctor would mean bool values would need to be passed to this
* class independently of the values config by dialogs that use this widget. However, the bool states are also saved
* in a dynamic_bitset class member which can be fetched for other uses if necessary.
*/
update_config_from_toggle_states();
handled = true;
}
void multimenu_button::update_label() {
std::vector<t_string> selected;
for(size_t i = 0; i < toggle_states_.size() && i < values_.size(); i++) {
if(!toggle_states_[i]) {
continue;
}
selected.push_back(values_[i]["label"]);
}
if(selected.size() == 0) {
set_label(_("multimenu^None Selected"));
} else if(selected.size() == 1) {
set_label(selected[0]);
} else if(selected.size() == 2) {
const string_map variables = {{"first", selected[0]}, {"second", selected[1]}, {"excess", "1"}};
if(max_shown_ == 1) {
set_label(VNGETTEXT("multimenu^$first and 1 other","multimenu^$first and $excess others", 1, variables));
} else {
set_label(VGETTEXT("multimenu^$first and $second", variables));
}
} else if(selected.size() == values_.size()) {
set_label(_("multimenu^All Selected"));
} else {
int excess = selected.size() - max_shown_;
if(max_shown_ > 0 && excess > 0) {
selected.resize(max_shown_);
}
std::string first = selected[0];
for(size_t i = 1; i < selected.size() - 1; i++) {
const string_map variables = {{"first", first}, {"second", selected[i]}};
first = VGETTEXT("multimenu^$first, $second", variables);
}
if(max_shown_ > 0 && excess > 0) {
const string_map variables = {{"first", first}, {"excess", std::to_string(excess + 1)}};
set_label(VNGETTEXT("multimenu^$first and 1 other","$first and $excess others", excess + 1, variables));
} else {
const string_map variables = {{"first", first}, {"second", selected.back()}};
set_label(VGETTEXT("multimenu^$first and $second", variables));
}
}
}
void multimenu_button::update_config_from_toggle_states()
{
for(unsigned i = 0; i < values_.size(); i++) {
::config& c = values_[i];
if(c.has_attribute("checkbox")) {
c["checkbox"] = toggle_states_[i];
}
}
}
void multimenu_button::reset_toggle_states()
{
toggle_states_.reset();
update_config_from_toggle_states();
}
void multimenu_button::toggle_state_changed()
{
assert(droplist_ != nullptr);
toggle_states_ = droplist_->get_toggle_states();
fire(event::NOTIFY_MODIFIED, *this, nullptr);
update_label();
if(callback_toggle_state_change_ != nullptr) {
callback_toggle_state_change_(toggle_states_);
}
}
void multimenu_button::set_values(const std::vector<::config>& values)
{
set_is_dirty(true);
values_ = values;
toggle_states_.resize(values_.size(), false);
toggle_states_.reset();
set_label(_("multimenu^None Selected"));
}
// }---------- DEFINITION ---------{
multimenu_button_definition::multimenu_button_definition(const config& cfg)
: styled_widget_definition(cfg)
{
DBG_GUI_P << "Parsing multimenu_button " << id << '\n';
load_resolutions<resolution>(cfg);
}
/*WIKI
* @page = GUIWidgetDefinitionWML
* @order = 1_multimenu_button
*
* == multimenu_button ==
*
* @macro = multimenu_button_description
*
* The following states exist:
* * state_enabled, the multimenu_button is enabled.
* * state_disabled, the multimenu_button is disabled.
* * state_pressed, the left mouse multimenu_button is down.
* * state_focused, the mouse is over the multimenu_button.
* @begin{parent}{name="gui/"}
* @begin{tag}{name="multimenu_button_definition"}{min=0}{max=-1}{super="generic/widget_definition"}
* @begin{tag}{name="resolution"}{min=0}{max=-1}{super="generic/widget_definition/resolution"}
* @begin{tag}{name="state_enabled"}{min=0}{max=1}{super="generic/state"}
* @end{tag}{name="state_enabled"}
* @begin{tag}{name="state_disabled"}{min=0}{max=1}{super="generic/state"}
* @end{tag}{name="state_disabled"}
* @begin{tag}{name="state_pressed"}{min=0}{max=1}{super="generic/state"}
* @end{tag}{name="state_pressed"}
* @begin{tag}{name="state_focused"}{min=0}{max=1}{super="generic/state"}
* @end{tag}{name="state_focused"}
* @end{tag}{name="resolution"}
* @end{tag}{name="multimenu_button_definition"}
* @end{parent}{name="gui/"}
*/
multimenu_button_definition::resolution::resolution(const config& cfg)
: resolution_definition(cfg)
{
// Note the order should be the same as the enum state_t in multimenu_button.hpp.
state.emplace_back(cfg.child("state_enabled"));
state.emplace_back(cfg.child("state_disabled"));
state.emplace_back(cfg.child("state_pressed"));
state.emplace_back(cfg.child("state_focused"));
}
// }---------- BUILDER -----------{
/*WIKI_MACRO
* @begin{macro}{multimenu_button_description}
*
* A multimenu_button is a styled_widget to choose an element from a list of elements.
* @end{macro}
*/
/*WIKI
* @page = GUIWidgetInstanceWML
* @order = 2_multimenu_button
* @begin{parent}{name="gui/window/resolution/grid/row/column/"}
* @begin{tag}{name="multimenu_button"}{min=0}{max=-1}{super="generic/widget_instance"}
* == multimenu_button ==
*
* @macro = multimenu_button_description
*
* Instance of a multimenu_button. When a multimenu_button has a return value it sets the
* return value for the window. Normally this closes the window and returns
* this value to the caller. The return value can either be defined by the
* user or determined from the id of the multimenu_button. The return value has a
* higher precedence as the one defined by the id. (Of course it's weird to
* give a multimenu_button an id and then override its return value.)
*
* When the multimenu_button doesn't have a standard id, but you still want to use the
* return value of that id, use return_value_id instead. This has a higher
* precedence as return_value.
*
* List with the multimenu_button specific variables:
* @begin{table}{config}
* return_value_id & string & "" & The return value id. $
* return_value & int & 0 & The return value. $
* maximum_shown & int & -1 & The maximum number of currently selected values to list on the button. $
*
* @end{table}
* @end{tag}{name="multimenu_button"}
* @end{parent}{name="gui/window/resolution/grid/row/column/"}
*/
namespace implementation
{
builder_multimenu_button::builder_multimenu_button(const config& cfg)
: builder_styled_widget(cfg)
, retval_id_(cfg["return_value_id"])
, retval_(cfg["return_value"])
, max_shown_(cfg["maximum_shown"])
, options_()
{
for(const auto& option : cfg.child_range("option")) {
options_.push_back(option);
}
}
widget* builder_multimenu_button::build() const
{
multimenu_button* widget = new multimenu_button();
init_control(widget);
widget->set_retval(get_retval(retval_id_, retval_, id));
widget->set_max_shown(max_shown_);
if(!options_.empty()) {
widget->set_values(options_);
}
DBG_GUI_G << "Window builder: placed multimenu_button '" << id
<< "' with definition '" << definition << "'.\n";
return widget;
}
} // namespace implementation
// }------------ END --------------
} // namespace gui2

View file

@ -0,0 +1,213 @@
/*
Copyright (C) 2008 - 2017 by Mark de Wever <koraq@xs4all.nl>
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.
*/
#pragma once
#include "gui/core/widget_definition.hpp"
#include "gui/core/window_builder.hpp"
#include "gui/dialogs/drop_down_menu.hpp"
#include "gui/widgets/styled_widget.hpp"
#include "gui/widgets/selectable_item.hpp"
#include <boost/dynamic_bitset.hpp>
class config;
namespace gui2
{
// ------------ WIDGET -----------{
/**
* Simple push button.
*/
class multimenu_button : public styled_widget
{
public:
multimenu_button();
/***** ***** ***** ***** Inherited ***** ***** ***** *****/
/** See @ref styled_widget::set_active. */
virtual void set_active(const bool active) override;
/** See @ref styled_widget::get_active. */
virtual bool get_active() const override;
/** See @ref styled_widget::get_state. */
virtual unsigned get_state() const override;
/** Inherited from tclickable. */
void connect_click_handler(const event::signal_function& signal)
{
connect_signal_mouse_left_click(*this, signal);
}
/** Inherited from tclickable. */
void disconnect_click_handler(const event::signal_function& signal)
{
disconnect_signal_mouse_left_click(*this, signal);
}
/***** ***** ***** setters / getters for members ***** ****** *****/
void set_retval(const int retval)
{
retval_ = retval;
}
void set_max_shown(const int max)
{
max_shown_ = max;
}
int get_max_shown()
{
return max_shown_;
}
void set_values(const std::vector<::config>& values);
/**
* Sets a callback that will be called immediately when any toggle button is selected or deselected.
*/
virtual void set_callback_toggle_state_change(std::function<void(boost::dynamic_bitset<>)> callback)
{
callback_toggle_state_change_ = callback;
}
/** Returns the value of the selected row */
//std::string get_value_string() const;
boost::dynamic_bitset<> get_toggle_states() const
{
return toggle_states_;
}
void reset_toggle_states();
void set_keep_open(const bool keep_open)
{
keep_open_ = keep_open;
}
private:
/**
* Possible states of the widget.
*
* Note the order of the states must be the same as defined in settings.hpp.
*/
enum state_t {
ENABLED,
DISABLED,
PRESSED,
FOCUSED,
COUNT
};
void set_state(const state_t state);
/**
* Current state of the widget.
*
* The state of the widget determines what to render and how the widget
* reacts to certain 'events'.
*/
state_t state_;
/**
* The return value of the button.
*
* If this value is not 0 and the button is clicked it sets the retval of
* the window and the window closes itself.
*/
int retval_;
/**
* The maximum number of selected states to list in the label
*/
int max_shown_;
std::vector<::config> values_;
boost::dynamic_bitset<> toggle_states_;
bool keep_open_;
dialogs::drop_down_menu* droplist_;
std::function<void(boost::dynamic_bitset<>)> callback_toggle_state_change_;
void update_config_from_toggle_states();
void update_label();
/** See @ref styled_widget::get_control_type. */
virtual const std::string& get_control_type() const override;
/***** ***** ***** signal handlers ***** ****** *****/
void signal_handler_mouse_enter(const event::ui_event event, bool& handled);
void signal_handler_mouse_leave(const event::ui_event event, bool& handled);
void signal_handler_left_button_down(const event::ui_event event, bool& handled);
void signal_handler_left_button_up(const event::ui_event event, bool& handled);
void signal_handler_left_button_click(const event::ui_event event, bool& handled);
void toggle_state_changed();
};
// }---------- DEFINITION ---------{
struct multimenu_button_definition : public styled_widget_definition
{
explicit multimenu_button_definition(const config& cfg);
struct resolution : public resolution_definition
{
explicit resolution(const config& cfg);
};
};
// }---------- BUILDER -----------{
class styled_widget;
namespace implementation
{
struct builder_multimenu_button : public builder_styled_widget
{
public:
explicit builder_multimenu_button(const config& cfg);
using builder_styled_widget::build;
widget* build() const;
private:
std::string retval_id_;
int retval_, max_shown_;
std::vector<::config> options_;
};
} // namespace implementation
// }------------ END --------------
} // namespace gui2