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