The Battle for Wesnoth  1.15.0-dev
events.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2003 - 2018 by David White <dave@whitevine.net>
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 #include "events.hpp"
16 #include "cursor.hpp"
17 #include "desktop/clipboard.hpp"
18 #include "log.hpp"
19 #include "ogl/utils.hpp"
20 #include "quit_confirmation.hpp"
21 #include "video.hpp"
22 #include "sdl/userevent.hpp"
23 
24 #if defined _WIN32
26 #endif
27 
28 #include <algorithm>
29 #include <atomic>
30 #include <cassert>
31 #include <deque>
32 #include <iterator>
33 #include <utility>
34 #include <vector>
35 #include <thread>
36 
37 #include <SDL.h>
38 
39 #include <boost/range/adaptor/reversed.hpp>
40 
41 #define ERR_GEN LOG_STREAM(err, lg::general)
42 
43 namespace
44 {
45 struct invoked_function_data
46 {
47  explicit invoked_function_data(const std::function<void(void)>& func)
48  : f(func)
49  , finished(false)
50  , thrown_exception()
51  {
52  }
53 
54  /** The actual function to call. */
55  const std::function<void(void)>& f;
56 
57  /** Whether execution in the main thread is complete. */
58  std::atomic_bool finished;
59 
60  /** Stores any exception thrown during the execution of @ref f. */
61  std::exception_ptr thrown_exception;
62 
63  void call()
64  {
65  try {
66  f();
67  } catch(const CVideo::quit&) {
68  // Handle this exception in the main thread.
69  throw;
70  } catch(...) {
71  thrown_exception = std::current_exception();
72  }
73 
74  finished = true;
75  }
76 };
77 }
78 
79 namespace events
80 {
81 void context::add_handler(sdl_handler* ptr)
82 {
83  /* Add new handlers to the staging list initially.
84  * This ensures that if an event handler adds more handlers, the new handlers
85  * won't be called for the event that caused them to be added.
86  */
87  staging_handlers.push_back(ptr);
88 }
89 
90 bool context::remove_handler(sdl_handler* ptr)
91 {
92  static int depth = 0;
93  ++depth;
94 
95  // The handler is most likely on the back of the events list,
96  // so look there first, otherwise do a complete search.
97  if(!handlers.empty() && handlers.back() == ptr) {
98  if(focused_handler != handlers.end() && *focused_handler == ptr) {
99  focused_handler = handlers.end();
100  }
101 
102  handlers.pop_back();
103  } else {
104  const handler_list::iterator i = std::find(handlers.begin(), handlers.end(), ptr);
105 
106  if(i == handlers.end()) {
107  --depth;
108 
109  // The handler may be in the staging area. Search it from there.
110  auto j = std::find(staging_handlers.begin(), staging_handlers.end(), ptr);
111  if(j != staging_handlers.end()) {
112  staging_handlers.erase(j);
113  return true;
114  } else {
115  return false;
116  }
117  }
118 
119  if(i == focused_handler) {
120  focused_handler != handlers.begin() ? --focused_handler : ++focused_handler;
121  }
122 
123  handlers.erase(i);
124  }
125 
126  --depth;
127 
128  if(depth == 0) {
129  cycle_focus();
130  } else {
131  focused_handler = handlers.end();
132  }
133 
134  return true;
135 }
136 
137 void context::cycle_focus()
138 {
139  if(handlers.begin() == handlers.end()) {
140  return;
141  }
142 
143  handler_list::iterator current = focused_handler;
144  handler_list::iterator last = focused_handler;
145 
146  if(last != handlers.begin()) {
147  --last;
148  }
149 
150  if(current == handlers.end()) {
151  current = handlers.begin();
152  } else {
153  ++current;
154  }
155 
156  while(current != last) {
157  if(current != handlers.end() && (*current)->requires_event_focus()) {
158  focused_handler = current;
159  break;
160  }
161 
162  if(current == handlers.end()) {
163  current = handlers.begin();
164  } else {
165  ++current;
166  }
167  }
168 }
169 
171 {
172  const handler_list::iterator i = std::find(handlers.begin(), handlers.end(), ptr);
173  if(i != handlers.end() && (*i)->requires_event_focus()) {
174  focused_handler = i;
175  }
176 }
177 
178 void context::add_staging_handlers()
179 {
180  if(staging_handlers.empty()) {
181  return;
182  }
183 
184  std::copy(staging_handlers.begin(), staging_handlers.end(), std::back_inserter(handlers));
185  staging_handlers.clear();
186 }
187 
188 context::~context()
189 {
190  for(sdl_handler* h : handlers) {
191  if(h->has_joined()) {
192  h->has_joined_ = false;
193  }
194 
195  if(h->has_joined_global()) {
196  h->has_joined_global_ = false;
197  }
198  }
199 }
200 
201 // This object stores all the event handlers. It is a stack of event 'contexts'.
202 // a new event context is created when e.g. a modal dialog is opened, and then
203 // closed when that dialog is closed. Each context contains a list of the handlers
204 // in that context. The current context is the one on the top of the stack.
205 // The global context must always be in the first position, so we initialize it here.
206 std::deque<context> event_contexts(1);
207 
208 std::vector<pump_monitor*> pump_monitors;
209 
210 pump_monitor::pump_monitor()
211 {
212  pump_monitors.push_back(this);
213 }
214 
215 pump_monitor::~pump_monitor()
216 {
217  pump_monitors.erase(std::remove(pump_monitors.begin(), pump_monitors.end(), this), pump_monitors.end());
218 }
219 
220 event_context::event_context()
221 {
222  event_contexts.emplace_back();
223 }
224 
225 event_context::~event_context()
226 {
227  assert(event_contexts.empty() == false);
228  event_contexts.pop_back();
229 }
230 
231 sdl_handler::sdl_handler(const bool auto_join)
232  : has_joined_(false)
233  , has_joined_global_(false)
234 {
235  if(auto_join) {
236  assert(!event_contexts.empty());
237  event_contexts.back().add_handler(this);
238  has_joined_ = true;
239  }
240 }
241 
243 {
244  if(has_joined_) {
245  leave();
246  }
247 
248  if(has_joined_global_) {
249  leave_global();
250  }
251 }
252 
254 {
255  // this assert will fire if someone will inadvertently try to join
256  // an event context but might end up in the global context instead.
257  assert(&event_contexts.back() != &event_contexts.front());
258 
259  join(event_contexts.back());
260 }
261 
263 {
264  if(has_joined_global_) {
265  leave_global();
266  }
267 
268  if(has_joined_) {
269  leave(); // should not be in multiple event contexts
270  }
271 
272  // join self
273  c.add_handler(this);
274  has_joined_ = true;
275 
276  // instruct members to join
277  for(auto member : handler_members()) {
278  member->join(c);
279  }
280 }
281 
283 {
284  if(has_joined_) {
285  leave(); // should not be in multiple event contexts
286  }
287 
288  for(auto& context : boost::adaptors::reverse(event_contexts)) {
289  handler_list& handlers = context.handlers;
290  if(std::find(handlers.begin(), handlers.end(), parent) != handlers.end()) {
291  join(context);
292  return;
293  }
294  }
295 
296  join(event_contexts.back());
297 }
298 
300 {
302 
303  if(members.empty()) {
304  assert(event_contexts.empty() == false);
305  }
306 
307  for(auto member : members) {
308  member->leave();
309  }
310 
311  for(auto& context : boost::adaptors::reverse(event_contexts)) {
312  if(context.remove_handler(this)) {
313  break;
314  }
315  }
316 
317  has_joined_ = false;
318 }
319 
321 {
322  if(has_joined_) {
323  leave();
324  }
325 
326  if(has_joined_global_) {
327  leave_global(); // Should not be in multiple event contexts
328  }
329 
330  // Join self
331  event_contexts.front().add_handler(this);
332  has_joined_global_ = true;
333 
334  // Instruct members to join
335  for(auto member : handler_members()) {
336  member->join_global();
337  }
338 }
339 
341 {
342  for(auto member : handler_members()) {
343  member->leave_global();
344  }
345 
346  event_contexts.front().remove_handler(this);
347 
348  has_joined_global_ = false;
349 }
350 
351 void focus_handler(const sdl_handler* ptr)
352 {
353  if(event_contexts.empty() == false) {
354  event_contexts.back().set_focus(ptr);
355  }
356 }
357 
358 bool has_focus(const sdl_handler* hand, const SDL_Event* event)
359 {
360  if(event_contexts.empty()) {
361  return true;
362  }
363 
364  if(hand->requires_event_focus(event) == false) {
365  return true;
366  }
367 
368  const handler_list::iterator foc = event_contexts.back().focused_handler;
369  auto& handlers = event_contexts.back().handlers;
370 
371  // If no-one has focus at the moment, this handler obviously wants
372  // focus, so give it to it.
373  if(foc == handlers.end()) {
374  focus_handler(hand);
375  return true;
376  }
377 
378  sdl_handler* const foc_hand = *foc;
379  if(foc_hand == hand) {
380  return true;
381  } else if(!foc_hand->requires_event_focus(event)) {
382  // If the currently focused handler doesn't need focus for this event
383  // allow the most recent interested handler to take care of it
384  for(auto i = handlers.rbegin(); i != handlers.rend(); ++i) {
385  sdl_handler* const thief_hand = *i;
386 
387  if(thief_hand != foc_hand && thief_hand->requires_event_focus(event)) {
388  // Steal focus
389  focus_handler(thief_hand);
390 
391  // Position the previously focused handler to allow stealing back
392  handlers.splice(handlers.end(), handlers, foc);
393 
394  return thief_hand == hand;
395  }
396  }
397  }
398 
399  return false;
400 }
401 
402 const uint32_t resize_timeout = 100;
405 
406 static bool remove_on_resize(const SDL_Event& a)
407 {
408  if(a.type == SHOW_HELPTIP_EVENT) {
409  return true;
410  }
411 
412  if((a.type == SDL_WINDOWEVENT) && (
413  a.window.event == SDL_WINDOWEVENT_RESIZED ||
414  a.window.event == SDL_WINDOWEVENT_SIZE_CHANGED ||
415  a.window.event == SDL_WINDOWEVENT_EXPOSED)
416  ) {
417  return true;
418  }
419 
420  return false;
421 }
422 
424 {
425  // Add things as necessary.
426 }
427 
428 void finalize()
429 {
430  // Add things as necessary.
431 }
432 
433 // TODO: I'm uncertain if this is always safe to call at static init; maybe set in main() instead?
435 
437 {
438  if(std::this_thread::get_id() != main_thread) {
439  // Can only call this on the main thread!
440  return;
441  }
442 
443  peek_for_resize();
444  pump_info info;
445 
446  // Used to keep track of double click events
447  static int last_mouse_down = -1;
448  static int last_click_x = -1, last_click_y = -1;
449 
450  SDL_Event temp_event;
451  int poll_count = 0;
452  int begin_ignoring = 0;
453 
454  std::vector<SDL_Event> events;
455  while(SDL_PollEvent(&temp_event)) {
456  if(temp_event.type == INVOKE_FUNCTION_EVENT) {
457  static_cast<invoked_function_data*>(temp_event.user.data1)->call();
458  continue;
459  }
460 
461  ++poll_count;
462  peek_for_resize();
463 
464  if(!begin_ignoring && temp_event.type == SDL_WINDOWEVENT && (
465  temp_event.window.event == SDL_WINDOWEVENT_ENTER ||
466  temp_event.window.event == SDL_WINDOWEVENT_FOCUS_GAINED)
467  ) {
468  begin_ignoring = poll_count;
469  } else if(begin_ignoring > 0 && is_input(temp_event)) {
470  // ignore user input events that occurred after the window was activated
471  continue;
472  }
473 
474  events.push_back(temp_event);
475  }
476 
477  auto ev_it = events.begin();
478  for(int i = 1; i < begin_ignoring; ++i) {
479  if(is_input(*ev_it)) {
480  // ignore user input events that occurred before the window was activated
481  ev_it = events.erase(ev_it);
482  } else {
483  ++ev_it;
484  }
485  }
486 
487  bool resize_found = false;
488  for(const SDL_Event& event : boost::adaptors::reverse(events)) {
489  if(event.type == SDL_WINDOWEVENT && event.window.event == SDL_WINDOWEVENT_RESIZED) {
490  resize_found = true;
491  last_resize_event = event;
492  last_resize_event_used = false;
493 
494  // Since we're working backwards, the first resize event found is the last in the list.
495  break;
496  }
497  }
498 
499  // remove all inputs, draw events and only keep the last of the resize events
500  // This will turn horrible after ~38 days when the uint32_t wraps.
501  if(resize_found || SDL_GetTicks() <= last_resize_event.window.timestamp + resize_timeout) {
502  events.erase(std::remove_if(events.begin(), events.end(), remove_on_resize), events.end());
503  } else if(SDL_GetTicks() > last_resize_event.window.timestamp + resize_timeout && !last_resize_event_used) {
504  events.insert(events.begin(), last_resize_event);
505  last_resize_event_used = true;
506  }
507 
508  for(const SDL_Event& event : events) {
509  for(context& c : event_contexts) {
510  c.add_staging_handlers();
511  }
512 
513  switch(event.type) {
514  case SDL_WINDOWEVENT:
515  switch(event.window.event) {
516  case SDL_WINDOWEVENT_ENTER:
517  case SDL_WINDOWEVENT_FOCUS_GAINED:
519  break;
520 
521  case SDL_WINDOWEVENT_LEAVE:
522  case SDL_WINDOWEVENT_FOCUS_LOST:
524  break;
525 
526  case SDL_WINDOWEVENT_RESIZED:
527  info.resize_dimensions.first = event.window.data1;
528  info.resize_dimensions.second = event.window.data2;
529  break;
530  }
531 
532  // make sure this runs in it's own scope.
533  {
534  flip_locker flip_lock(CVideo::get_singleton());
535  for(auto& context : event_contexts) {
536  for(auto handler : context.handlers) {
537  handler->handle_window_event(event);
538  }
539  }
540 
541  for(auto global_handler : event_contexts.front().handlers) {
542  global_handler->handle_window_event(event);
543  }
544  }
545 
546  // This event was just distributed, don't re-distribute.
547  continue;
548 
549  case SDL_MOUSEMOTION: {
550  // Always make sure a cursor is displayed if the mouse moves or if the user clicks
551  cursor::set_focus(true);
552  break;
553  }
554 
555  case SDL_MOUSEBUTTONDOWN: {
556  // Always make sure a cursor is displayed if the mouse moves or if the user clicks
557  cursor::set_focus(true);
558  if(event.button.button == SDL_BUTTON_LEFT) {
559  static const int DoubleClickTime = 500;
560  static const int DoubleClickMaxMove = 3;
561 
562  if(last_mouse_down >= 0 && info.ticks() - last_mouse_down < DoubleClickTime
563  && std::abs(event.button.x - last_click_x) < DoubleClickMaxMove
564  && std::abs(event.button.y - last_click_y) < DoubleClickMaxMove
565  ) {
566  sdl::UserEvent user_event(DOUBLE_CLICK_EVENT, event.button.x, event.button.y);
567  ::SDL_PushEvent(reinterpret_cast<SDL_Event*>(&user_event));
568  }
569 
570  last_mouse_down = info.ticks();
571  last_click_x = event.button.x;
572  last_click_y = event.button.y;
573  }
574  break;
575  }
576 
577 #ifndef __APPLE__
578  case SDL_KEYDOWN: {
579  if(event.key.keysym.sym == SDLK_F4 &&
580  (event.key.keysym.mod == KMOD_RALT || event.key.keysym.mod == KMOD_LALT)
581  ) {
583  continue; // this event is already handled
584  }
585  break;
586  }
587 #endif
588 
589 #if defined(_X11) && !defined(__APPLE__)
590  case SDL_SYSWMEVENT: {
591  // clipboard support for X11
593  break;
594  }
595 #endif
596 
597 #if defined _WIN32
598  case SDL_SYSWMEVENT: {
600  break;
601  }
602 #endif
603 
604  case SDL_QUIT: {
606  continue; // this event is already handled.
607  }
608  }
609 
610  for(auto global_handler : event_contexts.front().handlers) {
611  global_handler->handle_event(event);
612  }
613 
614  if(event_contexts.size() > 1) {
615  for(auto handler : event_contexts.back().handlers) {
616  handler->handle_event(event);
617  }
618  }
619  }
620 
621  //
622  // Draw things. This is the code that actually makes anything appear on the screen.
623  //
624  CVideo& video = CVideo::get_singleton();
625 
626 #ifdef USE_GL_RENDERING
627  gl::clear_screen();
628 #else
629  video.clear_screen();
630 #endif
631 
633 
634  video.render_screen();
635 
636  // Inform the pump monitors that an events::run_event_loop() has occurred
637  for(auto monitor : pump_monitors) {
638  monitor->process(info);
639  }
640 }
641 
643 {
644  if(event_contexts.empty() == false) {
645  event_contexts.back().add_staging_handlers();
646 
647  for(auto handler : event_contexts.back().handlers) {
648  handler->process_event();
649  }
650  }
651 }
652 
654 {
655  SDL_Event event;
656  event.window.type = SDL_WINDOWEVENT;
657  event.window.event = SDL_WINDOWEVENT_RESIZED;
658  event.window.windowID = 0; // We don't check this anyway... I think...
659  event.window.data1 = CVideo::get_singleton().get_width();
660  event.window.data2 = CVideo::get_singleton().get_height();
661 
662  SDL_PushEvent(&event);
663 }
664 
666 {
667  if(event_contexts.empty() == false) {
668  event_contexts.back().add_staging_handlers();
669 
670  // Events may cause more event handlers to be added and/or removed,
671  // so we must use indexes instead of iterators here.
672  for(auto handler : event_contexts.back().handlers) {
673  handler->draw();
674  }
675  }
676 }
677 
679 {
680  for(auto& context : event_contexts) {
681  for(auto handler : context.handlers) {
682  handler->draw();
683  }
684  }
685 }
686 
687 int pump_info::ticks(unsigned* refresh_counter, unsigned refresh_rate)
688 {
689  if(!ticks_ && !(refresh_counter && ++*refresh_counter % refresh_rate)) {
690  ticks_ = ::SDL_GetTicks();
691  }
692 
693  return ticks_;
694 }
695 
696 /* The constants for the minimum and maximum are picked from the headers. */
697 #define INPUT_MIN 0x300
698 #define INPUT_MAX 0x8FF
699 
700 bool is_input(const SDL_Event& event)
701 {
702  return event.type >= INPUT_MIN && event.type <= INPUT_MAX;
703 }
704 
706 {
707  SDL_FlushEvents(INPUT_MIN, INPUT_MAX);
708 }
709 
711 {
712 #if 0
713  SDL_Event events[100];
714  int num = SDL_PeepEvents(events, 100, SDL_PEEKEVENT, SDL_WINDOWEVENT, SDL_WINDOWEVENT);
715  for(int i = 0; i < num; ++i) {
716  if(events[i].type == SDL_WINDOWEVENT && events[i].window.event == SDL_WINDOWEVENT_RESIZED) {
717  // Add something here if needed.
718  }
719  }
720 #endif
721 }
722 
723 void call_in_main_thread(const std::function<void(void)>& f)
724 {
725  if(std::this_thread::get_id() == main_thread) {
726  // nothing special to do if called from the main thread.
727  f();
728  return;
729  }
730 
731  invoked_function_data fdata{f};
732 
733  SDL_Event sdl_event;
734  sdl::UserEvent sdl_userevent(INVOKE_FUNCTION_EVENT, &fdata);
735 
736  sdl_event.type = INVOKE_FUNCTION_EVENT;
737  sdl_event.user = sdl_userevent;
738 
739  SDL_PushEvent(&sdl_event);
740 
741  while(!fdata.finished) {
742  SDL_Delay(10);
743  }
744 
745  if(fdata.thrown_exception) {
746  std::rethrow_exception(fdata.thrown_exception);
747  }
748 }
749 
750 } // end events namespace
void raise_resize_event()
Definition: events.cpp:653
void remove()
Removes a tip.
Definition: tooltip.cpp:188
void discard_input()
Discards all input events.
Definition: events.cpp:705
std::vector< events::sdl_handler * > sdl_handler_vector
Definition: events.hpp:167
bool last_resize_event_used
Definition: events.cpp:404
#define INVOKE_FUNCTION_EVENT
Definition: events.hpp:28
static const std::thread::id main_thread
Definition: events.cpp:434
logger & info()
Definition: log.cpp:90
#define a
Definition: video.hpp:36
int ticks(unsigned *refresh_counter=nullptr, unsigned refresh_rate=1)
Definition: events.cpp:687
void add_handler(sdl_handler *ptr)
Definition: events.cpp:81
virtual void leave_global()
Definition: events.cpp:340
Type that can be thrown as an exception to quit to desktop.
Definition: video.hpp:202
static CVideo & get_singleton()
Definition: video.hpp:48
#define h
bool remove_handler(sdl_handler *ptr)
Definition: events.cpp:90
-file util.hpp
virtual void join_same(sdl_handler *parent)
Definition: events.cpp:282
void call_in_main_thread(const std::function< void(void)> &f)
Definition: events.cpp:723
void render_screen()
Renders the screen.
Definition: video.cpp:242
void raise_draw_all_event()
Definition: events.cpp:678
static bool remove_on_resize(const SDL_Event &a)
Definition: events.cpp:406
void focus_handler(const sdl_handler *ptr)
Definition: events.cpp:351
const t_string id
void set_focus(bool focus)
Definition: cursor.cpp:218
virtual bool requires_event_focus(const SDL_Event *=nullptr) const
Definition: events.hpp:78
#define INPUT_MAX
Definition: events.cpp:698
virtual void join()
Definition: events.cpp:253
std::list< sdl_handler * > handler_list
Definition: events.hpp:33
int get_width(bool as_pixels=true) const
Returns the window renderer width in pixels or screen coordinates.
Definition: video.cpp:225
void peek_for_resize()
Definition: events.cpp:710
const uint32_t resize_timeout
Definition: events.cpp:402
void raise_draw_event()
Definition: events.cpp:665
virtual ~sdl_handler()
Definition: events.cpp:242
#define DOUBLE_CLICK_EVENT
Definition: events.hpp:23
void raise_process_event()
Definition: events.cpp:642
static void quit_to_desktop()
std::size_t i
Definition: function.cpp:933
#define SHOW_HELPTIP_EVENT
Definition: events.hpp:27
bool is_input(const SDL_Event &event)
Is the event an input event?
Definition: events.cpp:700
void handle_system_event(const SDL_Event &)
Definition: clipboard.cpp:52
virtual std::vector< sdl_handler * > handler_members()
Definition: events.hpp:93
int get_height(bool as_pixels=true) const
Returns the window renderer height in pixels or in screen coordinates.
Definition: video.cpp:230
Handling of system events.
Definition: manager.hpp:41
std::vector< pump_monitor * > pump_monitors
Definition: events.cpp:208
#define f
void finalize()
Definition: events.cpp:428
bool has_focus(const sdl_handler *hand, const SDL_Event *event)
Definition: events.cpp:358
Standard logging facilities (interface).
void run_event_loop()
Definition: events.cpp:436
std::pair< int, int > resize_dimensions
Definition: events.hpp:132
SDL_Event last_resize_event
Definition: events.cpp:403
static void reverse(lua_State *L, StkId from, StkId to)
Definition: lapi.cpp:193
static void handle_system_event(const SDL_Event &event)
Frees resources when a notification disappears, switches user to the wesnoth window if the notificati...
virtual void leave()
Definition: events.cpp:299
handler_list handlers
Definition: events.hpp:57
std::deque< context > event_contexts(1)
mock_char c
void clear_screen()
Clear the screen contents.
Definition: video.cpp:312
virtual void join_global()
Definition: events.cpp:320
std::string::const_iterator iterator
Definition: tokenizer.hpp:24
HOTKEY_COMMAND get_id(const std::string &command)
returns get_hotkey_command(command).id
void initialise()
Definition: events.cpp:423
#define INPUT_MIN
Definition: events.cpp:697