1 /*
2    Copyright (C) 2009 - 2018 by Mark de Wever <koraq@xs4all.nl>
3    Part of the Battle for Wesnoth Project https://www.wesnoth.org/
4 
5    This program is free software; you can redistribute it and/or modify
6    it under the terms of the GNU General Public License as published by
7    the Free Software Foundation; either version 2 of the License, or
8    (at your option) any later version.
9    This program is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY.
11 
12    See the COPYING file for more details.
13 */
14 
15 #define GETTEXT_DOMAIN "wesnoth-lib"
16 
17 #include "gui/widgets/repeating_button.hpp"
18 
19 #include "gui/core/log.hpp"
20 #include "gui/core/timer.hpp"
21 #include "gui/core/register_widget.hpp"
22 #include "gui/widgets/settings.hpp"
23 #include "gui/widgets/window.hpp"
24 #include "sound.hpp"
25 
26 #include "utils/functional.hpp"
27 
28 #define LOG_SCOPE_HEADER get_control_type() + " [" + id() + "] " + __func__
29 #define LOG_HEADER LOG_SCOPE_HEADER + ':'
30 
31 namespace gui2
32 {
33 
34 // ------------ WIDGET -----------{
35 
REGISTER_WIDGET(repeating_button)36 REGISTER_WIDGET(repeating_button)
37 
38 repeating_button::repeating_button(const implementation::builder_repeating_button& builder)
39 	: styled_widget(builder, type())
40 	, clickable_item()
41 	, state_(ENABLED)
42 	, repeat_timer_(0)
43 {
44 	connect_signal<event::MOUSE_ENTER>(std::bind(
45 			&repeating_button::signal_handler_mouse_enter, this, _2, _3));
46 	connect_signal<event::MOUSE_LEAVE>(std::bind(
47 			&repeating_button::signal_handler_mouse_leave, this, _2, _3));
48 
49 	connect_signal<event::LEFT_BUTTON_DOWN>(std::bind(
50 			&repeating_button::signal_handler_left_button_down, this, _2, _3));
51 	connect_signal<event::LEFT_BUTTON_UP>(std::bind(
52 			&repeating_button::signal_handler_left_button_up, this, _2, _3));
53 }
54 
~repeating_button()55 repeating_button::~repeating_button()
56 {
57 	if(repeat_timer_) {
58 		remove_timer(repeat_timer_);
59 	}
60 }
61 
connect_signal_mouse_left_down(const event::signal_function & signal)62 void repeating_button::connect_signal_mouse_left_down(
63 		const event::signal_function& signal)
64 {
65 	connect_signal<event::LEFT_BUTTON_DOWN>(signal);
66 }
67 
disconnect_signal_mouse_left_down(const event::signal_function & signal)68 void repeating_button::disconnect_signal_mouse_left_down(
69 		const event::signal_function& signal)
70 {
71 	disconnect_signal<event::LEFT_BUTTON_DOWN>(signal);
72 }
73 
set_active(const bool active)74 void repeating_button::set_active(const bool active)
75 {
76 	if(get_active() != active) {
77 		set_state(active ? ENABLED : DISABLED);
78 	}
79 }
80 
get_active() const81 bool repeating_button::get_active() const
82 {
83 	return state_ != DISABLED;
84 }
85 
get_state() const86 unsigned repeating_button::get_state() const
87 {
88 	return state_;
89 }
90 
set_state(const state_t state)91 void repeating_button::set_state(const state_t state)
92 {
93 	if(state != state_) {
94 		state_ = state;
95 		set_is_dirty(true);
96 
97 		if(state_ == DISABLED && repeat_timer_) {
98 			remove_timer(repeat_timer_);
99 			repeat_timer_ = 0;
100 		}
101 	}
102 }
103 
signal_handler_mouse_enter(const event::ui_event event,bool & handled)104 void repeating_button::signal_handler_mouse_enter(const event::ui_event event,
105 												   bool& handled)
106 {
107 	DBG_GUI_E << LOG_HEADER << ' ' << event << ".\n";
108 
109 	set_state(FOCUSED);
110 	handled = true;
111 }
112 
signal_handler_mouse_leave(const event::ui_event event,bool & handled)113 void repeating_button::signal_handler_mouse_leave(const event::ui_event event,
114 												   bool& handled)
115 {
116 	DBG_GUI_E << LOG_HEADER << ' ' << event << ".\n";
117 
118 	set_state(ENABLED);
119 	handled = true;
120 }
121 
122 void
signal_handler_left_button_down(const event::ui_event event,bool & handled)123 repeating_button::signal_handler_left_button_down(const event::ui_event event,
124 												   bool& handled)
125 {
126 	DBG_GUI_E << LOG_HEADER << ' ' << event << ".\n";
127 
128 	// If the timer isn't set it's the initial down event.
129 	if(!repeat_timer_) {
130 
131 		// mimic the old gui and only play the sound once.
132 		sound::play_UI_sound(settings::sound_button_click);
133 
134 		window* window = get_window();
135 		if(window) {
136 			repeat_timer_ = add_timer(settings::repeat_button_repeat_time,
137 									  [this, window](unsigned int) {
138 											window->fire(event::LEFT_BUTTON_DOWN, *this);
139 									  },true);
140 
141 			window->mouse_capture();
142 		}
143 
144 		set_state(PRESSED);
145 	}
146 
147 	handled = true;
148 }
149 
signal_handler_left_button_up(const event::ui_event event,bool & handled)150 void repeating_button::signal_handler_left_button_up(const event::ui_event event,
151 													  bool& handled)
152 {
153 	DBG_GUI_E << LOG_HEADER << ' ' << event << ".\n";
154 
155 	if(repeat_timer_) {
156 		remove_timer(repeat_timer_);
157 		repeat_timer_ = 0;
158 	}
159 
160 	if(get_active()) {
161 		set_state(FOCUSED);
162 	}
163 	handled = true;
164 }
165 
166 // }---------- DEFINITION ---------{
167 
repeating_button_definition(const config & cfg)168 repeating_button_definition::repeating_button_definition(const config& cfg)
169 	: styled_widget_definition(cfg)
170 {
171 	DBG_GUI_P << "Parsing repeating button " << id << '\n';
172 
173 	load_resolutions<resolution>(cfg);
174 }
175 
176 /*WIKI
177  * @page = GUIWidgetDefinitionWML
178  * @order = 1_repeating_button
179  *
180  * == Repeating button ==
181  *
182  * @macro = repeating_button_description
183  *
184  * The following states exist:
185  * * state_enabled, the repeating_button is enabled.
186  * * state_disabled, the repeating_button is disabled.
187  * * state_pressed, the left mouse repeating_button is down.
188  * * state_focused, the mouse is over the repeating_button.
189  * @begin{parent}{name="gui/"}
190  * @begin{tag}{name="repeating_button_definition"}{min=0}{max=-1}{super="generic/widget_definition"}
191  * @begin{tag}{name="resolution"}{min=0}{max=-1}{super="generic/widget_definition/resolution"}
192  * @begin{tag}{name="state_enabled"}{min=0}{max=1}{super="generic/state"}
193  * @end{tag}{name="state_enabled"}
194  * @begin{tag}{name="state_disabled"}{min=0}{max=1}{super="generic/state"}
195  * @end{tag}{name="state_disabled"}
196  * @begin{tag}{name="state_pressed"}{min=0}{max=1}{super="generic/state"}
197  * @end{tag}{name="state_pressed"}
198  * @begin{tag}{name="state_focused"}{min=0}{max=1}{super="generic/state"}
199  * @end{tag}{name="state_focused"}
200  * @end{tag}{name="resolution"}
201  * @end{tag}{name="repeating_button_definition"}
202  * @end{parent}{name="gui/"}
203  */
resolution(const config & cfg)204 repeating_button_definition::resolution::resolution(const config& cfg)
205 	: resolution_definition(cfg)
206 {
207 	// Note the order should be the same as the enum state_t in
208 	// repeating_button.hpp.
209 	state.emplace_back(cfg.child("state_enabled"));
210 	state.emplace_back(cfg.child("state_disabled"));
211 	state.emplace_back(cfg.child("state_pressed"));
212 	state.emplace_back(cfg.child("state_focused"));
213 }
214 
215 // }---------- BUILDER -----------{
216 
217 /*WIKI_MACRO
218  * @begin{macro}{repeating_button_description}
219  *
220  *        A repeating_button is a styled_widget that can be pushed down and repeat a
221  *        certain action. Once the button is down every x milliseconds it is
222  *        down a new down event is triggered.
223  * @end{macro}
224  */
225 
226 /*WIKI
227  * @page = GUIWidgetInstanceWML
228  * @order = 2_repeating_button
229  *
230  * == Repeating button ==
231  *
232  * @macro = repeating_button_description
233  * @begin{parent}{name="gui/window/resolution/grid/row/column/"}
234  * @begin{tag}{name="repeating_button"}{min=0}{max=-1}{super="gui/window/resolution/grid/row/column/button"}
235  * @end{tag}{name="repeating_button"}
236  * @end{parent}{name="gui/window/resolution/grid/row/column/"}
237  */
238 
239 namespace implementation
240 {
241 
builder_repeating_button(const config & cfg)242 builder_repeating_button::builder_repeating_button(const config& cfg)
243 	: builder_styled_widget(cfg)
244 {
245 }
246 
build() const247 widget* builder_repeating_button::build() const
248 {
249 	repeating_button* widget = new repeating_button(*this);
250 
251 	DBG_GUI_G << "Window builder: placed repeating button '" << id
252 			  << "' with definition '" << definition << "'.\n";
253 
254 	return widget;
255 }
256 
257 } // namespace implementation
258 
259 // }------------ END --------------
260 
261 } // namespace gui2
262