The Battle for Wesnoth  1.15.0-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  , joystick_manager_()
46  , key_release_listener_(*this)
47  , last_mouse_is_touch_(false)
48  , long_touch_timer_(0)
49 {
50 }
51 
53 {
54  if(long_touch_timer_ != 0) {
57  }
58 }
59 
61 {
62  if(long_touch_timer_ != 0 && !get_mouse_handler_base().dragging_started()) {
63  int x_now;
64  int y_now;
65  uint32_t mouse_state = SDL_GetMouseState(&x_now, &y_now);
66 
67 #ifdef MOUSE_TOUCH_EMULATION
68  if(mouse_state & SDL_BUTTON(SDL_BUTTON_RIGHT)) {
69  // Monkey-patch touch controls again to make them look like left button.
70  mouse_state = SDL_BUTTON(SDL_BUTTON_LEFT);
71  }
72 #endif
73 
74  // Workaround for double-menu b/c of slow events processing, or I don't know.
75  int dx = x - x_now;
76  int dy = y - y_now;
77  int threshold = get_mouse_handler_base().drag_threshold();
78  bool yes_actually_dragging = dx * dx + dy * dy >= threshold * threshold;
79 
80  if(!yes_actually_dragging
81  && (mouse_state & SDL_BUTTON(SDL_BUTTON_LEFT)) != 0
82  && sdl::point_in_rect(x_now, y_now, get_display().map_area()))
83  {
85  if(m != nullptr) {
86  show_menu(get_display().get_theme().context_menu()->items(), x_now, y_now, true, get_display());
87  }
88  }
89  }
90 
92 }
93 
94 void controller_base::handle_event(const SDL_Event& event)
95 {
96  if(gui::in_dialog()) {
97  return;
98  }
99 
101 
102  SDL_Event new_event = {};
103 
104  switch(event.type) {
105  case SDL_TEXTINPUT:
106  if(have_keyboard_focus()) {
108  }
109  break;
110 
111  case SDL_TEXTEDITING:
112  if(have_keyboard_focus()) {
113  SDL_Event evt = event;
114  evt.type = SDL_TEXTINPUT;
116  SDL_StopTextInput();
117  SDL_StartTextInput();
118  }
119  break;
120 
121  case SDL_KEYDOWN:
122  // Detect key press events, unless there something that has keyboard focus
123  // in which case the key press events should go only to it.
124  if(have_keyboard_focus()) {
125  if(event.key.keysym.sym == SDLK_ESCAPE) {
127  break;
128  }
129 
130  process_keydown_event(event);
132  process_keyup_event(event);
133  } else {
135  }
136  break;
137 
138  case SDL_KEYUP:
139  process_keyup_event(event);
141  break;
142 
143  case SDL_JOYBUTTONDOWN:
145  break;
146 
147  case SDL_JOYHATMOTION:
149  break;
150 
151  case SDL_MOUSEMOTION:
152  // Ignore old mouse motion events in the event queue
153  if(SDL_PeepEvents(&new_event, 1, SDL_GETEVENT, SDL_MOUSEMOTION, SDL_MOUSEMOTION) > 0) {
154  while(SDL_PeepEvents(&new_event, 1, SDL_GETEVENT, SDL_MOUSEMOTION, SDL_MOUSEMOTION) > 0) {
155  };
156  if(new_event.motion.which != SDL_TOUCH_MOUSEID) {
157  mh_base.mouse_motion_event(new_event.motion, is_browsing());
158  }
159  } else {
160  if(new_event.motion.which != SDL_TOUCH_MOUSEID) {
161  mh_base.mouse_motion_event(event.motion, is_browsing());
162  }
163  }
164  break;
165 
166  case SDL_FINGERMOTION:
167  if(SDL_PeepEvents(&new_event, 1, SDL_GETEVENT, SDL_FINGERMOTION, SDL_FINGERMOTION) > 0) {
168  while(SDL_PeepEvents(&new_event, 1, SDL_GETEVENT, SDL_FINGERMOTION, SDL_FINGERMOTION) > 0) {
169  };
170  mh_base.touch_motion_event(new_event.tfinger, is_browsing());
171  } else {
172  mh_base.touch_motion_event(event.tfinger, is_browsing());
173  }
174  break;
175 
176  case SDL_MOUSEBUTTONDOWN:
177  last_mouse_is_touch_ = event.button.which == SDL_TOUCH_MOUSEID;
178 
182  std::bind(&controller_base::long_touch_callback, this, event.button.x, event.button.y));
183  }
184 
185  mh_base.mouse_press(event.button, is_browsing());
187  break;
188 
189  case SDL_FINGERDOWN:
190  // handled by mouse case
191  break;
192 
193  case SDL_MOUSEBUTTONUP:
194  if(long_touch_timer_ != 0) {
196  long_touch_timer_ = 0;
197  }
198 
199  last_mouse_is_touch_ = event.button.which == SDL_TOUCH_MOUSEID;
200 
201  mh_base.mouse_press(event.button, is_browsing());
202  if(mh_base.get_show_menu()) {
203  show_menu(get_display().get_theme().context_menu()->items(), event.button.x, event.button.y, true,
204  get_display());
205  }
206  break;
207  case DOUBLE_CLICK_EVENT:
208  {
209  int x = static_cast<int>(reinterpret_cast<std::intptr_t>(event.user.data1));
210  int y = static_cast<int>(reinterpret_cast<std::intptr_t>(event.user.data2));
211  if(event.user.code == static_cast<int>(SDL_TOUCH_MOUSEID)
212  // TODO: Move to right_click_show_menu?
213  && sdl::point_in_rect(x, y, get_display().map_area())
214  // TODO: This chain repeats in several places, move to a method.
215  && get_display().get_theme().context_menu() != nullptr) {
216  show_menu(get_display().get_theme().context_menu()->items(),
217  x,
218  y,
219  true,
220  get_display());
221  }
222  }
223  break;
224 
225  case SDL_FINGERUP:
226  // handled by mouse case
227  break;
228 
229  case SDL_MOUSEWHEEL:
230 #if defined(_WIN32) || defined(__APPLE__)
231  mh_base.mouse_wheel(-event.wheel.x, event.wheel.y, is_browsing());
232 #else
233  mh_base.mouse_wheel(event.wheel.x, event.wheel.y, is_browsing());
234 #endif
235  break;
236 
237  case TIMER_EVENT:
238  gui2::execute_timer(reinterpret_cast<size_t>(event.user.data1));
239  break;
240 
241  // TODO: Support finger specifically, like pan the map. For now, SDL's "shadow mouse" events will do.
242  case SDL_MULTIGESTURE:
243  default:
244  break;
245  }
246 }
247 
249 {
250  if(gui2::is_in_dialog()) {
251  return;
252  }
253 
255 }
256 
258 {
259  if(event.type == SDL_KEYUP) {
260  hotkey::keyup_event(event, controller_.get_hotkey_command_executor());
261  }
262 }
263 
265 {
266  return true;
267 }
268 
269 bool controller_base::handle_scroll(int mousex, int mousey, int mouse_flags, double x_axis, double y_axis)
270 {
271  const bool mouse_in_window =
272  CVideo::get_singleton().window_has_flags(SDL_WINDOW_MOUSE_FOCUS)
273  || preferences::get("scroll_when_mouse_outside", true);
274 
276  int dx = 0, dy = 0;
277 
278  int scroll_threshold = preferences::mouse_scroll_enabled()
280  : 0;
281 
282  for(const theme::menu& m : get_display().get_theme().menus()) {
283  if(sdl::point_in_rect(mousex, mousey, m.get_location())) {
284  scroll_threshold = 0;
285  }
286  }
287 
288  // Apply keyboard scrolling
289  dy -= scroll_up_ * scroll_speed;
290  dy += scroll_down_ * scroll_speed;
291  dx -= scroll_left_ * scroll_speed;
292  dx += scroll_right_ * scroll_speed;
293 
294  // Scroll if mouse is placed near the edge of the screen
295  if(mouse_in_window) {
296  if(mousey < scroll_threshold) {
297  dy -= scroll_speed;
298  }
299 
300  if(mousey > get_display().video().get_height() - scroll_threshold) {
301  dy += scroll_speed;
302  }
303 
304  if(mousex < scroll_threshold) {
305  dx -= scroll_speed;
306  }
307 
308  if(mousex > get_display().video().get_width() - scroll_threshold) {
309  dx += scroll_speed;
310  }
311  }
312 
314 
315  // Scroll with middle-mouse if enabled
316  if((mouse_flags & SDL_BUTTON_MMASK) != 0 && preferences::middle_click_scrolls()) {
317  const SDL_Point original_loc = mh_base.get_scroll_start();
318 
319  if(mh_base.scroll_started()) {
320  const SDL_Rect& rect = get_display().map_outside_area();
321 
322  if(sdl::point_in_rect(mousex, mousey, rect) && mh_base.scroll_started()) {
323  // Scroll speed is proportional from the distance from the first
324  // middle click and scrolling speed preference.
325  const double speed = 0.04 * sqrt(static_cast<double>(scroll_speed));
326  const double snap_dist = 16; // Snap to horizontal/vertical scrolling
327  const double x_diff = (mousex - original_loc.x);
328  const double y_diff = (mousey - original_loc.y);
329 
330  if(std::fabs(x_diff) > snap_dist || std::fabs(y_diff) <= snap_dist) {
331  dx += speed * x_diff;
332  }
333 
334  if(std::fabs(y_diff) > snap_dist || std::fabs(x_diff) <= snap_dist) {
335  dy += speed * y_diff;
336  }
337  }
338  } else { // Event may fire mouse down out of order with respect to initial click
339  mh_base.set_scroll_start(mousex, mousey);
340  }
341  }
342 
343  // scroll with joystick
344  dx += std::round(x_axis * scroll_speed);
345  dy += std::round(y_axis * scroll_speed);
346 
347  return get_display().scroll(dx, dy);
348 }
349 
350 void controller_base::play_slice(bool is_delay_enabled)
351 {
352  CKey key;
353 
355  l->play_slice();
356  }
357 
358  events::pump();
361 
362  // Update sound sources before scrolling
364  l->update();
365  }
366 
367  const theme::menu* const m = get_display().menu_pressed();
368  if(m != nullptr) {
369  const SDL_Rect& menu_loc = m->location(get_display().video().screen_area());
370  show_menu(m->items(), menu_loc.x + 1, menu_loc.y + menu_loc.h + 1, false, get_display());
371 
372  return;
373  }
374 
375  const theme::action* const a = get_display().action_pressed();
376  if(a != nullptr) {
377  const SDL_Rect& action_loc = a->location(get_display().video().screen_area());
378  execute_action(a->items(), action_loc.x + 1, action_loc.y + action_loc.h + 1, false);
379 
380  return;
381  }
382 
383  auto str_vec = additional_actions_pressed();
384  if(!str_vec.empty()) {
385  execute_action(str_vec, 0, 0, false);
386  return;
387  }
388 
389  bool was_scrolling = scrolling_;
390 
391  std::pair<double, double> values = joystick_manager_.get_scroll_axis_pair();
392  const double joystickx = values.first;
393  const double joysticky = values.second;
394 
395  int mousex, mousey;
396  uint8_t mouse_flags = SDL_GetMouseState(&mousex, &mousey);
397 
398  // TODO enable after an axis choosing mechanism is implemented
399 #if 0
400  std::pair<double, double> values = joystick_manager_.get_mouse_axis_pair();
401  mousex += values.first * 10;
402  mousey += values.second * 10;
403  SDL_WarpMouse(mousex, mousey);
404 #endif
405 
406  scrolling_ = handle_scroll(mousex, mousey, mouse_flags, joystickx, joysticky);
407 
408  map_location highlighted_hex = get_display().mouseover_hex();
409 
410  // TODO: enable when the relative cursor movement is implemented well enough
411 #if 0
412  const map_location& selected_hex = get_display().selected_hex();
413 
414  if (selected_hex != map_location::null_location()) {
415  if (joystick_manager_.next_highlighted_hex(highlighted_hex, selected_hex)) {
416  get_mouse_handler_base().mouse_motion(0,0, true, true, highlighted_hex);
417  get_display().scroll_to_tile(highlighted_hex, display::ONSCREEN_WARP, false, true);
418  scrolling_ = true;
419  }
420  } else
421 #endif
422 
423  if(joystick_manager_.update_highlighted_hex(highlighted_hex) && get_display().get_map().on_board(highlighted_hex)) {
424  get_mouse_handler_base().mouse_motion(0, 0, true, true, highlighted_hex);
425  get_display().scroll_to_tile(highlighted_hex, display::ONSCREEN_WARP, false, true);
426  scrolling_ = true;
427  }
428 
429  // be nice when window is not visible // NOTE should be handled by display instead, to only disable drawing
430  if(is_delay_enabled && !CVideo::get_singleton().window_has_flags(SDL_WINDOW_SHOWN)) {
431  CVideo::delay(200);
432  }
433 
434  // Scrolling ended, update the cursor and the brightened hex
435  if(!scrolling_ && was_scrolling) {
436  get_mouse_handler_base().mouse_update(is_browsing(), highlighted_hex);
437  }
438 }
439 
441  const std::vector<config>& items_arg, int xloc, int yloc, bool context_menu, display& disp)
442 {
444  if(!cmd_exec) {
445  return;
446  }
447 
448  std::vector<config> items;
449  for(const config& c : items_arg) {
450  const std::string& id = c["id"];
452 
453  if(cmd_exec->can_execute_command(command) && (!context_menu || in_context_menu(command.id))) {
454  items.emplace_back("id", id);
455  }
456  }
457 
458  if(items.empty()) {
459  return;
460  }
461 
462  cmd_exec->show_menu(items, xloc, yloc, context_menu, disp);
463 }
464 
465 void controller_base::execute_action(const std::vector<std::string>& items_arg, int xloc, int yloc, bool context_menu)
466 {
468  if(!cmd_exec) {
469  return;
470  }
471 
472  std::vector<std::string> items;
473  for(const std::string& item : items_arg) {
474  const hotkey::hotkey_command& command = hotkey::get_hotkey_command(item);
475  if(cmd_exec->can_execute_command(command)) {
476  items.push_back(item);
477  }
478  }
479 
480  if(items.empty()) {
481  return;
482  }
483 
484  cmd_exec->execute_action(items, xloc, yloc, context_menu, get_display());
485 }
486 
488 {
489  return true;
490 }
491 
492 const config& controller_base::get_theme(const config& game_config, std::string theme_name)
493 {
494  if(theme_name.empty()) {
495  theme_name = preferences::theme();
496  }
497 
498  if(const config& c = game_config.find_child("theme", "id", theme_name)) {
499  return c;
500  }
501 
502  ERR_DP << "Theme '" << theme_name << "' not found. Trying the default theme." << std::endl;
503 
504  if(const config& c = game_config.find_child("theme", "id", "Default")) {
505  return c;
506  }
507 
508  ERR_DP << "Default theme not found." << std::endl;
509 
510  static config empty;
511  return empty;
512 }
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
joystick_manager joystick_manager_
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:316
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:888
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)
const map_location & selected_hex() const
Definition: display.hpp:284
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:838
#define a
bool in_dialog()
Definition: show_dialog.cpp:56
int scroll_speed()
Definition: general.cpp:859
bool is_in_dialog()
Is a dialog open?
Definition: handler.cpp:1118
bool update_highlighted_hex(map_location &highlighted_hex)
Used for absolute movement of the cursor.
Definition: joystick.cpp:300
#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.
bool on_board(const map_location &loc) const
Tell if a location is on the map.
Definition: map.cpp:377
const std::vector< std::string > items
virtual void mouse_motion(int x, int y, const bool browse, bool update=false, map_location new_loc=map_location::null_location())=0
Called when a mouse motion event takes place.
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...
std::pair< double, double > get_scroll_axis_pair()
Definition: joystick.cpp:155
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:1700
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:1716
std::string theme()
Definition: game.cpp:818
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)
bool handle_scroll(int mousex, int mousey, int mouse_flags, double joystickx, double joysticky)
Handle scrolling by keyboard, joystick and moving mouse near map edges.
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:320
const std::vector< std::string > & items() const
Definition: theme.hpp:187
virtual ~controller_base()
void raise_draw_event()
Definition: events.cpp:726
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:425
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:703
virtual void process_focus_keydown_event(const SDL_Event &)
Process keydown (only when the general map display does not have focus).
void scroll_to_tile(const map_location &loc, SCROLL_TYPE scroll_type=ONSCREEN, bool check_fogged=true, bool force=true)
Scroll such that location loc is on-screen.
Definition: display.cpp:2160
const std::vector< config > & items() const
Definition: theme.hpp:240
Game configuration data as global variables.
Definition: build_info.cpp:49
bool middle_click_scrolls()
Definition: general.cpp:873
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:213
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:325
size_t long_touch_timer_
Context menu timer.
void key_event(const SDL_Event &event, command_executor *executor)
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:878
const gamemap & get_map() const
Definition: display.hpp:92
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
static const map_location & null_location()
Definition: location.hpp:85
const menu * context_menu() const
Definition: theme.hpp:264
static void delay(unsigned int milliseconds)
Waits a given number of milliseconds before returning.
Definition: video.cpp:330
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:92
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.
std::pair< double, double > get_mouse_axis_pair()
TODO fendrin.
Definition: joystick.cpp:121
bool scroll(int xmov, int ymov, bool force=false)
Scrolls the display by xmov,ymov pixels.
Definition: display.cpp:1880
#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:388
bool execute_timer(const std::size_t id)
Executes a timer.
Definition: timer.cpp:200
bool remove_timer(const std::size_t id)
Removes a timer.
Definition: timer.cpp:167