The Battle for Wesnoth  1.15.2+dev
controller_base.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2006 - 2018 by Joerg Hinrichs <joerg.hinrichs@alice-dsl.de>
3  wesnoth playlevel Copyright (C) 2003 by David White <dave@whitevine.net>
4  Part of the Battle for Wesnoth Project https://www.wesnoth.org/
5 
6  This program is free software; you can redistribute it and/or modify
7  it under the terms of the GNU General Public License as published by
8  the Free Software Foundation; either version 2 of the License, or
9  (at your option) any later version.
10  This program is distributed in the hope that it will be useful,
11  but WITHOUT ANY WARRANTY.
12 
13  See the COPYING file for more details.
14 */
15 
16 #include "controller_base.hpp"
17 
18 #include "display.hpp"
19 #include "events.hpp"
20 #include "game_config_manager.hpp"
22 #include "log.hpp"
23 #include "map/map.hpp"
24 #include "mouse_handler_base.hpp"
25 #include "preferences/game.hpp"
27 #include "show_dialog.hpp" //gui::in_dialog
28 #include "gui/core/event/handler.hpp" // gui2::is_in_dialog
29 #include "soundsource.hpp"
30 #include "gui/core/timer.hpp"
31 
32 static lg::log_domain log_display("display");
33 #define ERR_DP LOG_STREAM(err, log_display)
34 
35 static const int long_touch_duration_ms = 800;
36 
38  : game_config_(game_config_manager::get()->game_config())
39  , key_()
40  , scrolling_(false)
41  , scroll_up_(false)
42  , scroll_down_(false)
43  , scroll_left_(false)
44  , scroll_right_(false)
45  , key_release_listener_(*this)
46  , last_mouse_is_touch_(false)
47  , long_touch_timer_(0)
48 {
49 }
50 
52 {
53  if(long_touch_timer_ != 0) {
56  }
57 }
58 
60 {
61  if(long_touch_timer_ != 0 && !get_mouse_handler_base().dragging_started()) {
62  int x_now;
63  int y_now;
64  uint32_t mouse_state = SDL_GetMouseState(&x_now, &y_now);
65 
66 #ifdef MOUSE_TOUCH_EMULATION
67  if(mouse_state & SDL_BUTTON(SDL_BUTTON_RIGHT)) {
68  // Monkey-patch touch controls again to make them look like left button.
69  mouse_state = SDL_BUTTON(SDL_BUTTON_LEFT);
70  }
71 #endif
72 
73  // Workaround for double-menu b/c of slow events processing, or I don't know.
74  int dx = x - x_now;
75  int dy = y - y_now;
76  int threshold = get_mouse_handler_base().drag_threshold();
77  bool yes_actually_dragging = dx * dx + dy * dy >= threshold * threshold;
78 
79  if(!yes_actually_dragging
80  && (mouse_state & SDL_BUTTON(SDL_BUTTON_LEFT)) != 0
81  && sdl::point_in_rect(x_now, y_now, get_display().map_area()))
82  {
84  if(m != nullptr) {
85  show_menu(get_display().get_theme().context_menu()->items(), x_now, y_now, true, get_display());
86  }
87  }
88  }
89 
91 }
92 
93 void controller_base::handle_event(const SDL_Event& event)
94 {
95  if(gui::in_dialog()) {
96  return;
97  }
98 
100 
101  SDL_Event new_event = {};
102 
103  switch(event.type) {
104  case SDL_TEXTINPUT:
105  if(have_keyboard_focus()) {
107  }
108  break;
109 
110  case SDL_TEXTEDITING:
111  if(have_keyboard_focus()) {
112  SDL_Event evt = event;
113  evt.type = SDL_TEXTINPUT;
115  SDL_StopTextInput();
116  SDL_StartTextInput();
117  }
118  break;
119 
120  case SDL_KEYDOWN:
121  // Detect key press events, unless there something that has keyboard focus
122  // in which case the key press events should go only to it.
123  if(have_keyboard_focus()) {
124  if(event.key.keysym.sym == SDLK_ESCAPE) {
126  break;
127  }
128 
129  process_keydown_event(event);
131  process_keyup_event(event);
132  } else {
134  }
135  break;
136 
137  case SDL_KEYUP:
138  process_keyup_event(event);
140  break;
141 
142  case SDL_JOYBUTTONDOWN:
144  break;
145 
146  case SDL_JOYHATMOTION:
148  break;
149 
150  case SDL_MOUSEMOTION:
151  // Ignore old mouse motion events in the event queue
152  if(SDL_PeepEvents(&new_event, 1, SDL_GETEVENT, SDL_MOUSEMOTION, SDL_MOUSEMOTION) > 0) {
153  while(SDL_PeepEvents(&new_event, 1, SDL_GETEVENT, SDL_MOUSEMOTION, SDL_MOUSEMOTION) > 0) {
154  };
155  if(new_event.motion.which != SDL_TOUCH_MOUSEID) {
156  mh_base.mouse_motion_event(new_event.motion, is_browsing());
157  }
158  } else {
159  if(new_event.motion.which != SDL_TOUCH_MOUSEID) {
160  mh_base.mouse_motion_event(event.motion, is_browsing());
161  }
162  }
163  break;
164 
165  case SDL_FINGERMOTION:
166  if(SDL_PeepEvents(&new_event, 1, SDL_GETEVENT, SDL_FINGERMOTION, SDL_FINGERMOTION) > 0) {
167  while(SDL_PeepEvents(&new_event, 1, SDL_GETEVENT, SDL_FINGERMOTION, SDL_FINGERMOTION) > 0) {
168  };
169  mh_base.touch_motion_event(new_event.tfinger, is_browsing());
170  } else {
171  mh_base.touch_motion_event(event.tfinger, is_browsing());
172  }
173  break;
174 
175  case SDL_MOUSEBUTTONDOWN:
176  last_mouse_is_touch_ = event.button.which == SDL_TOUCH_MOUSEID;
177 
181  std::bind(&controller_base::long_touch_callback, this, event.button.x, event.button.y));
182  }
183 
184  mh_base.mouse_press(event.button, is_browsing());
186  break;
187 
188  case SDL_FINGERDOWN:
189  // handled by mouse case
190  break;
191 
192  case SDL_MOUSEBUTTONUP:
193  if(long_touch_timer_ != 0) {
195  long_touch_timer_ = 0;
196  }
197 
198  last_mouse_is_touch_ = event.button.which == SDL_TOUCH_MOUSEID;
199 
200  mh_base.mouse_press(event.button, is_browsing());
201  if(mh_base.get_show_menu()) {
202  show_menu(get_display().get_theme().context_menu()->items(), event.button.x, event.button.y, true,
203  get_display());
204  }
205  break;
206  case DOUBLE_CLICK_EVENT:
207  {
208  int x = static_cast<int>(reinterpret_cast<std::intptr_t>(event.user.data1));
209  int y = static_cast<int>(reinterpret_cast<std::intptr_t>(event.user.data2));
210  if(event.user.code == static_cast<int>(SDL_TOUCH_MOUSEID)
211  // TODO: Move to right_click_show_menu?
212  && sdl::point_in_rect(x, y, get_display().map_area())
213  // TODO: This chain repeats in several places, move to a method.
214  && get_display().get_theme().context_menu() != nullptr) {
215  show_menu(get_display().get_theme().context_menu()->items(),
216  x,
217  y,
218  true,
219  get_display());
220  }
221  }
222  break;
223 
224  case SDL_FINGERUP:
225  // handled by mouse case
226  break;
227 
228  case SDL_MOUSEWHEEL:
229 #if defined(_WIN32) || defined(__APPLE__)
230  mh_base.mouse_wheel(-event.wheel.x, event.wheel.y, is_browsing());
231 #else
232  mh_base.mouse_wheel(event.wheel.x, event.wheel.y, is_browsing());
233 #endif
234  break;
235 
236  case TIMER_EVENT:
237  gui2::execute_timer(reinterpret_cast<size_t>(event.user.data1));
238  break;
239 
240  // TODO: Support finger specifically, like pan the map. For now, SDL's "shadow mouse" events will do.
241  case SDL_MULTIGESTURE:
242  default:
243  break;
244  }
245 }
246 
248 {
249  if(gui2::is_in_dialog()) {
250  return;
251  }
252 
254 }
255 
257 {
258  if(event.type == SDL_KEYUP) {
259  hotkey::keyup_event(event, controller_.get_hotkey_command_executor());
260  }
261 }
262 
264 {
265  return true;
266 }
267 
268 bool controller_base::handle_scroll(int mousex, int mousey, int mouse_flags)
269 {
270  const bool mouse_in_window =
271  CVideo::get_singleton().window_has_flags(SDL_WINDOW_MOUSE_FOCUS)
272  || preferences::get("scroll_when_mouse_outside", true);
273 
275  int dx = 0, dy = 0;
276 
277  int scroll_threshold = preferences::mouse_scroll_enabled()
279  : 0;
280 
281  for(const theme::menu& m : get_display().get_theme().menus()) {
282  if(sdl::point_in_rect(mousex, mousey, m.get_location())) {
283  scroll_threshold = 0;
284  }
285  }
286 
287  // Apply keyboard scrolling
288  dy -= scroll_up_ * scroll_speed;
289  dy += scroll_down_ * scroll_speed;
290  dx -= scroll_left_ * scroll_speed;
291  dx += scroll_right_ * scroll_speed;
292 
293  // Scroll if mouse is placed near the edge of the screen
294  if(mouse_in_window) {
295  if(mousey < scroll_threshold) {
296  dy -= scroll_speed;
297  }
298 
299  if(mousey > get_display().video().get_height() - scroll_threshold) {
300  dy += scroll_speed;
301  }
302 
303  if(mousex < scroll_threshold) {
304  dx -= scroll_speed;
305  }
306 
307  if(mousex > get_display().video().get_width() - scroll_threshold) {
308  dx += scroll_speed;
309  }
310  }
311 
313 
314  // Scroll with middle-mouse if enabled
315  if((mouse_flags & SDL_BUTTON_MMASK) != 0 && preferences::middle_click_scrolls()) {
316  const SDL_Point original_loc = mh_base.get_scroll_start();
317 
318  if(mh_base.scroll_started()) {
319  const SDL_Rect& rect = get_display().map_outside_area();
320 
321  if(sdl::point_in_rect(mousex, mousey, rect) && mh_base.scroll_started()) {
322  // Scroll speed is proportional from the distance from the first
323  // middle click and scrolling speed preference.
324  const double speed = 0.04 * std::sqrt(static_cast<double>(scroll_speed));
325  const double snap_dist = 16; // Snap to horizontal/vertical scrolling
326  const double x_diff = (mousex - original_loc.x);
327  const double y_diff = (mousey - original_loc.y);
328 
329  if(std::fabs(x_diff) > snap_dist || std::fabs(y_diff) <= snap_dist) {
330  dx += speed * x_diff;
331  }
332 
333  if(std::fabs(y_diff) > snap_dist || std::fabs(x_diff) <= snap_dist) {
334  dy += speed * y_diff;
335  }
336  }
337  } else { // Event may fire mouse down out of order with respect to initial click
338  mh_base.set_scroll_start(mousex, mousey);
339  }
340  }
341 
342  return get_display().scroll(dx, dy);
343 }
344 
345 void controller_base::play_slice(bool is_delay_enabled)
346 {
347  CKey key;
348 
350  l->play_slice();
351  }
352 
353  events::pump();
356 
357  // Update sound sources before scrolling
359  l->update();
360  }
361 
362  const theme::menu* const m = get_display().menu_pressed();
363  if(m != nullptr) {
364  const SDL_Rect& menu_loc = m->location(get_display().video().screen_area());
365  show_menu(m->items(), menu_loc.x + 1, menu_loc.y + menu_loc.h + 1, false, get_display());
366 
367  return;
368  }
369 
370  const theme::action* const a = get_display().action_pressed();
371  if(a != nullptr) {
372  const SDL_Rect& action_loc = a->location(get_display().video().screen_area());
373  execute_action(a->items(), action_loc.x + 1, action_loc.y + action_loc.h + 1, false);
374 
375  return;
376  }
377 
378  auto str_vec = additional_actions_pressed();
379  if(!str_vec.empty()) {
380  execute_action(str_vec, 0, 0, false);
381  return;
382  }
383 
384  bool was_scrolling = scrolling_;
385 
386  int mousex, mousey;
387  uint8_t mouse_flags = SDL_GetMouseState(&mousex, &mousey);
388 
389  scrolling_ = handle_scroll(mousex, mousey, mouse_flags);
390 
391  map_location highlighted_hex = get_display().mouseover_hex();
392 
393  // be nice when window is not visible // NOTE should be handled by display instead, to only disable drawing
394  if(is_delay_enabled && !CVideo::get_singleton().window_has_flags(SDL_WINDOW_SHOWN)) {
395  CVideo::delay(200);
396  }
397 
398  // Scrolling ended, update the cursor and the brightened hex
399  if(!scrolling_ && was_scrolling) {
400  get_mouse_handler_base().mouse_update(is_browsing(), highlighted_hex);
401  }
402 }
403 
405  const std::vector<config>& items_arg, int xloc, int yloc, bool context_menu, display& disp)
406 {
408  if(!cmd_exec) {
409  return;
410  }
411 
412  std::vector<config> items;
413  for(const config& c : items_arg) {
414  const std::string& id = c["id"];
416 
417  if(cmd_exec->can_execute_command(command) && (!context_menu || in_context_menu(command.id))) {
418  items.emplace_back("id", id);
419  }
420  }
421 
422  if(items.empty()) {
423  return;
424  }
425 
426  cmd_exec->show_menu(items, xloc, yloc, context_menu, disp);
427 }
428 
429 void controller_base::execute_action(const std::vector<std::string>& items_arg, int xloc, int yloc, bool context_menu)
430 {
432  if(!cmd_exec) {
433  return;
434  }
435 
436  std::vector<std::string> items;
437  for(const std::string& item : items_arg) {
439  if(cmd_exec->can_execute_command(command)) {
440  items.push_back(item);
441  }
442  }
443 
444  if(items.empty()) {
445  return;
446  }
447 
448  cmd_exec->execute_action(items, xloc, yloc, context_menu, get_display());
449 }
450 
452 {
453  return true;
454 }
455 
456 const config& controller_base::get_theme(const config& game_config, std::string theme_name)
457 {
458  if(theme_name.empty()) {
459  theme_name = preferences::theme();
460  }
461 
462  if(const config& c = game_config.find_child("theme", "id", theme_name)) {
463  return c;
464  }
465 
466  ERR_DP << "Theme '" << theme_name << "' not found. Trying the default theme." << std::endl;
467 
468  if(const config& c = game_config.find_child("theme", "id", "Default")) {
469  return c;
470  }
471 
472  ERR_DP << "Default theme not found." << std::endl;
473 
474  static config empty;
475  return empty;
476 }
virtual bool in_context_menu(hotkey::HOTKEY_COMMAND command) const
static const config & get_theme(const config &game_config, std::string theme_name)
const map_location & mouseover_hex() const
Definition: display.hpp:285
virtual plugins_context * get_plugins_context()
Get (optionally) a plugins context a derived class uses.
virtual SDL_Rect & location(const SDL_Rect &screen) const
Definition: theme.cpp:317
const SDL_Point get_scroll_start() const
virtual void process_keyup_event(const SDL_Event &)
Process keyup (always).
static const int long_touch_duration_ms
HOTKEY_COMMAND id
the names are strange: the "hotkey::HOTKEY_COMMAND" is named id, and the string to identify the objec...
theme & get_theme()
Definition: display.hpp:379
void set_scroll_start(int x, int y)
Called when the middle click scrolling.
int mouse_scroll_threshold()
Gets the threshold for when to scroll.
Definition: general.cpp:760
static lg::log_domain log_display("display")
virtual int drag_threshold() const
Minimum dragging distance to fire the drag&drop.
virtual void show_menu(const std::vector< config > &items_arg, int xloc, int yloc, bool context_menu, display &disp)
config & find_child(config_key_type key, const std::string &name, const std::string &value)
Returns the first child of tag key with a name attribute containing value.
Definition: config.cpp:836
#define a
bool in_dialog()
Definition: show_dialog.cpp:56
int scroll_speed()
Definition: general.cpp:731
bool is_in_dialog()
Is a dialog open?
Definition: handler.cpp:1118
#define TIMER_EVENT
Definition: events.hpp:24
Stores all information related to functions that can be bound to hotkeys.
void touch_motion_event(const SDL_TouchFingerEvent &event, const bool browse)
virtual void play_slice(bool is_delay_enabled=true)
static CVideo & get_singleton()
Definition: video.hpp:43
void mbutton_event(const SDL_Event &event, command_executor *executor)
virtual display & get_display()=0
Get a reference to a display member a derived class uses.
const std::vector< std::string > items
void mouse_motion_event(const SDL_MouseMotionEvent &event, const bool browse)
controller_base framework: controller_base is roughly analogous to a "dialog" class in a GUI toolkit ...
virtual bool have_keyboard_focus()
Derived classes should override this to return false when arrow keys should not scroll the map...
virtual bool can_execute_command(const hotkey_command &command, int index=-1) const =0
virtual std::vector< std::string > additional_actions_pressed()
const theme::action * action_pressed()
Definition: display.cpp:1721
void run_events(command_executor *executor)
virtual void mouse_press(const SDL_MouseButtonEvent &event, const bool browse)
virtual soundsource::manager * get_soundsource_man()
Get (optionally) a soundsources manager a derived class uses.
virtual bool is_browsing() const
std::string get(const std::string &key)
Definition: general.cpp:228
void jbutton_event(const SDL_Event &event, command_executor *executor)
const theme::menu * menu_pressed()
Definition: display.cpp:1737
std::string theme()
Definition: game.cpp:843
void handle_event(const SDL_Event &event) override
virtual void show_menu(const std::vector< config > &items_arg, int xloc, int yloc, bool context_menu, display &gui)
void keyup_event(const SDL_Event &, command_executor *executor)
void handle_event(const SDL_Event &event) override
Process mouse- and keypress-events from SDL.
map_display and display: classes which take care of displaying the map and game-data on the screen...
int get_width(bool as_pixels=true) const
Returns the window renderer width in pixels or screen coordinates.
Definition: video.cpp:307
const std::vector< std::string > & items() const
Definition: theme.hpp:178
virtual ~controller_base()
void raise_draw_event()
Definition: events.cpp:776
void jhat_event(const SDL_Event &event, command_executor *executor)
virtual events::mouse_handler_base & get_mouse_handler_base()=0
Get a reference to a mouse handler member a derived class uses.
void pump()
Definition: events.cpp:475
bool point_in_rect(int x, int y, const SDL_Rect &rect)
Tests whether a point is inside a rectangle.
Definition: rect.cpp:22
virtual void process(events::pump_info &) override
virtual void execute_action(const std::vector< std::string > &items_arg, int xloc, int yloc, bool context_menu)
Encapsulates the map of the game.
Definition: location.hpp:42
virtual void process_keydown_event(const SDL_Event &)
Process keydown (always).
#define DOUBLE_CLICK_EVENT
Definition: events.hpp:23
void raise_process_event()
Definition: events.cpp:753
virtual void process_focus_keydown_event(const SDL_Event &)
Process keydown (only when the general map display does not have focus).
const std::vector< config > & items() const
Definition: theme.hpp:231
Game configuration data as global variables.
Definition: build_info.cpp:49
bool middle_click_scrolls()
Definition: general.cpp:745
void execute_action(const std::vector< std::string > &items_arg, int xloc, int yloc, bool context_menu, display &gui)
const SDL_Rect & map_outside_area() const
Returns the available area for a map, this may differ from the above.
Definition: display.hpp:237
Contains the gui2 timer routines.
CURSOR_TYPE get()
Definition: cursor.cpp:215
virtual void mouse_wheel(int xscroll, int yscroll, bool browse)
Called when scrolling with the mouse wheel.
std::size_t add_timer(const uint32_t interval, const std::function< void(std::size_t id)> &callback, const bool repeat)
Adds a new timer.
Definition: timer.cpp:126
int get_height(bool as_pixels=true) const
Returns the window renderer height in pixels or in screen coordinates.
Definition: video.cpp:312
size_t long_touch_timer_
Context menu timer.
void key_event(const SDL_Event &event, command_executor *executor)
bool handle_scroll(int mousex, int mousey, int mouse_flags)
Handle scrolling by keyboard, joystick and moving mouse near map edges.
void long_touch_callback(int x, int y)
virtual hotkey::command_executor * get_hotkey_command_executor()
Optionally get a command executor to handle context menu events.
bool mouse_scroll_enabled()
Definition: general.cpp:750
virtual display & gui()=0
Reference to the used display objects.
Standard logging facilities (interface).
CVideo & video()
Gets the underlying screen object.
Definition: display.hpp:196
const menu * context_menu() const
Definition: theme.hpp:255
static void delay(unsigned int milliseconds)
Waits a given number of milliseconds before returning.
Definition: video.cpp:317
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:68
Class that keeps track of all the keys on the keyboard.
Definition: key.hpp:27
mock_char c
const hotkey_command & get_hotkey_command(const std::string &command)
returns the hotkey_command with the given name
void mouse_update(const bool browse, map_location loc)
Update the mouse with a fake mouse motion.
bool scroll(int xmov, int ymov, bool force=false)
Scrolls the display by xmov,ymov pixels.
Definition: display.cpp:1902
#define ERR_DP
bool window_has_flags(uint32_t flags) const
Tests whether the given flags are currently set on the SDL window.
Definition: video.cpp:375
bool execute_timer(const std::size_t id)
Executes a timer.
Definition: timer.cpp:200
std::pair< std::string, unsigned > item
Definition: help_impl.hpp:371
bool remove_timer(const std::size_t id)
Removes a timer.
Definition: timer.cpp:167