# EventHandler class for SDL::App::FPS - used to register callbacks for events
package SDL::App::FPS::EventHandler;
# (C) by Tels 
use strict;
use Exporter;
use SDL::App::FPS::Thingy;
use vars qw/@ISA $VERSION @EXPORT_OK/;
@ISA = qw/SDL::App::FPS::Thingy Exporter/;
@EXPORT_OK = qw/char2key char2type_kind FPS_EVENT/;
use SDL;
$VERSION = '0.05';
##############################################################################
# constants
sub FPS_EVENT () { -1; }
sub BUTTON_MOUSE_LEFT ()        { 1; }
sub BUTTON_MOUSE_RIGHT ()       { 4; }
sub BUTTON_MOUSE_MIDDLE ()      { 2; }
sub BUTTON_MOUSE_WHEEL_DOWN ()  { 8; }
sub BUTTON_MOUSE_WHEEL_UP ()    { 16; }
##############################################################################
# methods
sub _init
  {
  my $self = shift;
  $self->{type} = shift;
  $self->{kind} = shift;
  $self->{callback} = shift;
  
  $self->{args} = [ @_ ];
  $self->_init_mod();
  }
my $remap = {
  SDLK_LSHIFT() => KMOD_LSHIFT,
  SDLK_RSHIFT() => KMOD_RSHIFT,
  SDLK_LCTRL() => KMOD_LCTRL,
  SDLK_RCTRL() => KMOD_RCTRL,
  SDLK_LALT() => KMOD_LALT,
  SDLK_RALT() => KMOD_RALT,
  };
sub _init_mod
  { 
  my $self = shift;
  $self->{mod} = { };
  if (ref($self->{kind}) eq 'ARRAY')
    {
    # [ SLDK_a, SLDK_LSHIFT, ... ]
    my $mod = $self->{kind};
    $self->{kind} = $mod->[0];
    shift @{$mod};
    # convert @$mod to %$mod
    foreach my $m (@$mod)
      {
      if (exists $remap->{$m})
        {
        # silently remap SLDK_LSHIFT => KMOD_LSHIFT
        $self->{mod}->{$remap->{$m}} = 1;
        }
      else
        { 
        $self->{mod}->{$m} = 1;
        }
      }
    $self->{ignore_mod} = 0;			# don't ignore additionals
    }
  else
    {
    $self->{ignore_mod} = 1;			# do ignore additionals
    }
  $self->{require_all} = 0;			# don't require all of them
  $self;
  }
sub ignore_additional_modifiers
  {
  my $self = shift;
  if (@_ > 0)
    { 
    $self->{ignore_mod} = $_[0] ? 1 : 0;
    }
  $self->{ignore_mod};			
  }
sub require_all_modifiers
  {
  my $self = shift;
  if (@_ > 0)
    { 
    $self->{require_all} = $_[0] ? 1 : 0;
    }
  $self->{require_all};			
  }
sub check
  {
  # check whether the event matched the occured event or not
  my ($self,$event,$type,$key) = @_;
  return if $self->{active} == 0;
  return unless $type == $self->{type};
  if ($type == FPS_EVENT || $type == SDL_KEYDOWN || $type == SDL_KEYUP)
    {
    return unless $key eq $self->{kind};
    }
  elsif ($type == SDL_MOUSEBUTTONUP || $type == SDL_MOUSEBUTTONDOWN)
    {
    # watch for more than one button with one event:
    return if ($self->{kind} & $key) == 0;
    }
  my $required = 0;
  # find out which modifiers (these we watch) are pressed
  my $mods = $event->key_mod();
  foreach my $mod (keys %{$self->{mod}})
    {
    # this watched one is pressed
    if (($mods & $mod) != 0)
      {
      $required++;
      $mods -= $mod;				# eliminate this bit
      }
    }
  
  if ($self->{ignore_mod} != 1)
    {
    # $mods != 0 if there were additional modifiers and we don't ignore this
    return if $mods != 0;
    }
  # if not all of the required ones were pressed, and we require all
  return if $self->{require_all} != 0 &&
    $required != scalar keys %{$self->{mod}};
  
  # when we watch some modifiers, at least one must be pressed
  return if $required == 0 && scalar keys %{$self->{mod}} != 0;
  # event happened, so call callback
  &{$self->{callback}}($self->{app},$self,$event,@{$self->{args}});
  }
sub rebind ($$$)
  {
  my ($self) = shift;
  my $old_type = $self->{type};
  $self->{type} = shift;
  $self->{kind} = shift;
  $self->_init_mod();
  $self->{app}->_rebound_event_handler($self,$old_type);
  $self; 
  }
sub type ()
  {
  # return the type this event handler watches out for
  my $self = shift;
  $self->{type};
  }
sub kind ()
  {
  # return the kind this event handler watches out for
  my $self = shift;
  $self->{kind};
  }
my $char2key = {
  a => SDLK_a,
  b => SDLK_b,
  c => SDLK_c,
  d => SDLK_d,
  e => SDLK_e,
  f => SDLK_f,
  g => SDLK_g,
  h => SDLK_h,
  i => SDLK_i,
  j => SDLK_j,
  k => SDLK_k,
  l => SDLK_l,
  m => SDLK_m,
  n => SDLK_n,
  o => SDLK_o,
  p => SDLK_p,
  q => SDLK_q(),
  r => SDLK_r,
  s => SDLK_s,
  t => SDLK_t,
  u => SDLK_u,
  v => SDLK_v,
  w => SDLK_w,
  x => SDLK_x,
  y => SDLK_y,
  z => SDLK_z,
  
  A => [ SDLK_a, KMOD_SHIFT ],
  B => [ SDLK_b, KMOD_SHIFT ],
  C => [ SDLK_c, KMOD_SHIFT ],
  D => [ SDLK_d, KMOD_SHIFT ],
  E => [ SDLK_e, KMOD_SHIFT ],
  F => [ SDLK_f, KMOD_SHIFT ],
  G => [ SDLK_g, KMOD_SHIFT ],
  H => [ SDLK_h, KMOD_SHIFT ],
  I => [ SDLK_i, KMOD_SHIFT ],
  J => [ SDLK_j, KMOD_SHIFT ],
  K => [ SDLK_k, KMOD_SHIFT ],
  L => [ SDLK_l, KMOD_SHIFT ],
  M => [ SDLK_m, KMOD_SHIFT ],
  N => [ SDLK_n, KMOD_SHIFT ],
  O => [ SDLK_o, KMOD_SHIFT ],
  P => [ SDLK_p, KMOD_SHIFT ],
  Q => [ SDLK_q, KMOD_SHIFT ],
  R => [ SDLK_r, KMOD_SHIFT ],
  S => [ SDLK_s, KMOD_SHIFT ],
  T => [ SDLK_t, KMOD_SHIFT ],
  U => [ SDLK_u, KMOD_SHIFT ],
  V => [ SDLK_v, KMOD_SHIFT ],
  W => [ SDLK_w, KMOD_SHIFT ],
  X => [ SDLK_x, KMOD_SHIFT ],
  Y => [ SDLK_y, KMOD_SHIFT ],
  Z => [ SDLK_z, KMOD_SHIFT ],
  0 => SDLK_0,
  1 => SDLK_1,
  2 => SDLK_2,
  3 => SDLK_3,
  4 => SDLK_4,
  5 => SDLK_5,
  6 => SDLK_6,
  7 => SDLK_7,
  8 => SDLK_8,
  9 => SDLK_9,
  '!' => [ SDLK_1, KMOD_SHIFT ],
  '"' => [ SDLK_2, KMOD_SHIFT ],
  '§' => [ SDLK_3, KMOD_SHIFT ],
  '$' => [ SDLK_4, KMOD_SHIFT ],
  '%' => [ SDLK_5, KMOD_SHIFT ],
  '&' => [ SDLK_6, KMOD_SHIFT ],
  '/' => [ SDLK_7, KMOD_SHIFT ],
  '(' => [ SDLK_7, KMOD_SHIFT ],
  ')' => [ SDLK_8, KMOD_SHIFT ],
  '=' => [ SDLK_0, KMOD_SHIFT ],
  '_' => [ SDLK_MINUS, KMOD_SHIFT ],
  ';' => [ SDLK_COMMA, KMOD_SHIFT ],
  ':' => [ SDLK_PERIOD, KMOD_SHIFT ],
  '>' => [ SDLK_LESS, KMOD_SHIFT ],
  '*' => [ SDLK_PLUS, KMOD_SHIFT ],
  '@' => [ SDLK_q, KMOD_RALT ],
  };
# if we define these in $char2key, we could skip the costly eval. OTOH, the
# eval will be done only once, while $char2key wastes memory...
#        SDLK_END SDLK_EQUALS SDLK_ESCAPE SDLK_EURO SDLK_EXCLAIM SDLK_F1
#        SDLK_F10 SDLK_F11 SDLK_F12 SDLK_F13 SDLK_F14 SDLK_F15 SDLK_F2 SDLK_F3
#        SDLK_F4 SDLK_F5 SDLK_F6 SDLK_F7 SDLK_F8 SDLK_F9 SDLK_GREATER
#        SDLK_HASH SDLK_HELP SDLK_HOME SDLK_INSERT SDLK_KP0 SDLK_KP1 SDLK_KP2
#        SDLK_KP3 SDLK_KP4 SDLK_KP5 SDLK_KP6 SDLK_KP7 SDLK_KP8 SDLK_KP9
#        SDLK_KP_DIVIDE SDLK_KP_ENTER SDLK_KP_EQUALS SDLK_KP_MINUS
##        SDLK_KP_MULTIPLY SDLK_KP_PERIOD SDLK_KP_PLUS SDLK_LALT SDLK_LCTRL
#        SDLK_LEFT SDLK_LEFTBRACKET SDLK_LEFTPAREN SDLK_LESS SDLK_LMETA
#        SDLK_LSHIFT SDLK_LSUPER SDLK_MENU SDLK_MINUS SDLK_MODE SDLK_NUMLOCK
#        SDLK_PAGEDOWN SDLK_PAGEUP SDLK_PAUSE SDLK_PERIOD SDLK_PLUS SDLK_POWER
#        SDLK_PRINT SDLK_QUESTION SDLK_QUOTE SDLK_QUOTEDBL SDLK_RALT
##        SDLK_RCTRL SDLK_RETURN SDLK_RIGHT SDLK_RIGHTBRACKET SDLK_RIGHTPAREN
#        SDLK_RMETA SDLK_RSHIFT SDLK_RSUPER SDLK_SCROLLOCK SDLK_SEMICOLON
#        SDLK_SLASH SDLK_SPACE SDLK_SYSREQ SDLK_TAB SDLK_UNDERSCORE SDLK_UP
sub char2key
  {
  # convert a character like 'a' to a key event like SDLK_a
  my $char = shift;
  return $char2key->{$char} if exists $char2key->{$char};
  if ($char =~ /^[A-Z]A-Z+/)
    {
    $char = 'SDLK_' . $char if $char =~ /^SDLK_/;
    return eval "$char()";
    }
  return;  
  }
sub char2type_kind
  {
  # convert a character like 'a' to a key event like SDLK_a
  # and a string like "PRINT" to SDL_KEYDOWN, SDLK_PRINT
  my $key = shift;
  my $type = SDL_KEYDOWN;
  if ($key =~ /^([LMR]MB|MWD|MWU)$/)
    {
    $type = SDL_MOUSEBUTTONDOWN;
    if ($key eq 'LMB')
      {
      $key = BUTTON_MOUSE_LEFT;
      }
    elsif ($key eq 'RMB')
      {
      $key = BUTTON_MOUSE_RIGHT;
      }
    elsif ($key eq 'MMB')
      {
      $key = BUTTON_MOUSE_MIDDLE;
      }
    elsif ($key eq 'MWU')
      {
      $key = BUTTON_MOUSE_WHEEL_UP;
      }
    elsif ($key eq 'MWD')
      {
      $key = BUTTON_MOUSE_WHEEL_DOWN;
      }
    return ($type, $key);
    }
  # if passed something like "123", user passed SDLK_foo() as key.
  return ($type,$key) if $key =~ /^\d{2,}\z/;
  return ($type,$char2key->{$key}) if exists $char2key->{$key};
  $key = "SDLK_$key" unless $key =~ /^SDLK_/;
  my $char = eval "$key()"; 
  ($type, $char);
  }
1;
__END__
=pod
=head1 NAME
SDL::App::FPS::EventHandler - an event handler class for SDL::App::FPS
=head1 SYNOPSIS
	my $handler = SDL::App::FPS::EventHandler->new( $app,
		SDL_KEYDOWN,
		SDLK_SPACE,
		sub { my $self = shift; $self->pause(); },
	};
	my $handler2 = SDL::App::FPS::EventHandler->new( $app,
		SDL_MOUSEBUTTONDOWN,
		LEFTMOUSEBUTTON,
		sub { my $self = shift; $self->time_warp(2,2000); },
	};
=head1 DESCRIPTION
This package provides an event handler class.
Event handlers are register to watch out for certain external events like
keypresses, mouse movements and so on, and when these happen, call a callback
routine.
=head1 CALLBACK
Once the event has occured, the callback code (CODE ref) is called with the
following parameters:
	&{$callback}($self,$handler,$event);
C<$self> is the app the event handler resides in (e.g. the object of type
SDL::App::FPS), C<$handler> is the event handler itself, and C<$event> the
SDL::Event that caused the handler to be activated.
=head1 METHODS
=over 2
=item new()
	my $handler = SDL::App::FPS::EventHandler->new(
		$app,
		$type,
		$kind,
		$callback,
	);
Creates a new event handler to watch out for $type events (SDL_KEYDOWN,
SDL_MOUSEMOVED, SDL_MOUSEBUTTONDOWN etc) and then for $kind kind of it,
like SDLK_SPACE. Mouse movement events ignore the $kind parameter.
C<$app> is the ref to the application the handler resides in and is passed
as first argument to the callback function when called.
Please note that this event handler B triggers when this key or
button is pressed, regardless of any additional key modifier like SHIFT
beeing pressed. See below for how to change this.
C<$kind> can also be an array ref. This is used to pass a key plus one or
more modifiers that need to be pressed to trigger the event. The default is
that all listed modifiers must be pressed and additional modifiers are not
ignored, e.g. they cause the event not to trigger:
	my $handler = SDL::App::FPS::EventHandler->new(
		$app,
		SLD_KEYDOWN,
		[ SDLK_a, KMOD_LSHIFT ],
		$callback
	);
The list of valid modifiers is:
	KMOD_NUM
        KMOD_CAPS
	KMOD_LCTRL
	KMOD_RCTRL
        KMOD_RSHIFT
	KMOD_LSHIFT
	KMOD_RALT
        KMOD_LALT
These shortcuts exists:
	KMOD_CTRL
	KMOD_SHIFT
        KMOD_ALT
This would only trigger when 'a' and left shift are pressed together.
	my $handler = SDL::App::FPS::EventHandler->new(
		$app,
		SLD_KEYDOWN,
		[ SDLK_a, KMOD_LSHIFT, KMOD_RSHIFT ],
		$callback
	);
This would only trigger when 'a' B left shift B right shift are
pressed together.
	
	my $handler = SDL::App::FPS::EventHandler->new(
		$app,
		SLD_KEYDOWN,
		[ SDLK_a, KMOD_LSHIFT, KMOD_RSHIFT ],
		$callback
	);
	$handler->require_all_modifiers(0);
This would only trigger when 'a' and one of left shift B right shift are
pressed together (but no additional modifiers), but not when 'a' without left
and right shift is pressed (e.g. neither C nor C nor C would count).
	
	my $handler = SDL::App::FPS::EventHandler->new(
		$app,
		SLD_KEYDOWN,
		[ SDLK_a, KMOD_LSHIFT, KMOD_RSHIFT ],
		$callback
	);
	$handler->ignore_additional_modifiers(0);
This would only trigger when 'a' B left shift B right shift are
pressed together, and additional modifiers will be ignored.
E.g. neither C nor C would count, however,
C would count.
When passing only one key as C<$kind>, ignore_additional_key_modifiers() will
be set to true as default.
See L and L for
changing the default behaviour.
=item is_active()
	$handler->is_active();
Returns true if the event handler is active, or false for inactive. Inactive
event handlers ignore any events that might happen.
=item activate()
Set the event handler to active. Newly created ones are always active.
=item deactivate()
Set the event handler to inactive. Newly created ones are always active.
=item rebind()
	$handler->rebind(SDL_KEYUP, SDLK_P);
Set a new type and kind for the handler to watch out for.
C will reset L and
L to the defaults like new() does.
=item require_all_modifiers()
	$eventhandler->require_all_modifiers(1);
	if ($eventhandler->require_all_modifiers())
	  {
	  ...
	  }
Returns true or false. When passed an argument, sets a flag on whether this
handlers requires all set key modifiers or not. When set to false, only one
or some of the set key modifiers (SDLK_LSHIFT, SDLK_RCTRL etc) must be pressed
to trigger the callback. When set to true, all of them must be pressed.
=item ignore_additional_modifiers()
	$eventhandler->ignore_additional_modifiers(1);
	if ($eventhandler->ignore_additional_modifiers())
	  {
	  ...
	  }
Returns true or false. When passed an argument, sets a flag on whether this
handlers ignores additional key modifiers. When set to false, only one
or some of the set key modifiers (depending on require_all_modifiers())
(like SDLK_LSHIFT, SDLK_RCTRL etc) must be pressed to trigger the callback and
no additional modifiers can be pressed.  When set to true, additional
modifiers can be pressed and the event still triggers.
=item id()
Return the handler's unique id.
=item char2key()
	$sdl_key = char2key($char);
	$sdl_key_a = char2key('a');
Converts a character like C<'a'> to a SDL key like C.
=item char2type_kind()
	($type,$sdl_key) = char2type_kind($char);
	($type,$sdl_key) = char2type_kind('a');
	($type,$sdl_key) = char2type_kind('PRINT');
	($type,$sdl_key) = char2type_kind('ENTER');
	($type,$sdl_key) = char2type_kind('LMB');	# left mouse button
Converts a character like C<'a'> to a SDL key like C, and also returns
the type (SDL_KEYDOWN or SDL_MOUSEBUTTONDOWN).
=back
=head1 AUTHORS
(c) 2002, 2003, 2006, Tels 
=head1 SEE ALSO
L, L and L.
=cut