The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
#!/usr/bin/perl

# Copyright 2009 by the gtk2-perl team (see the file AUTHORS)
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Library General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# Library General Public License for more details.
#
# You should have received a copy of the GNU Library General Public
# License along with this library; if not, see <http://www.gnu.org/licenses/>.


package My::Object;
use strict;
use warnings;
use Gtk2;
use Glib::Object::Subclass
  'Gtk2::Object',
  signals => { mysig => { param_types   => [],
                          return_type   => undef,
                          flags         => ['run-last','action'],
                          class_closure => \&do_mysig },
               mysig_with_long => { param_types   => [ 'Glib::Long' ],
                                    return_type   => undef,
                                    flags         => ['run-last','action'],
                                    class_closure => \&do_mysig_with_long },
               mysig_with_float => { param_types   => [ 'Glib::Double' ],
                                     return_type   => undef,
                                     flags         => ['run-last','action'],
                                     class_closure => \&do_mysig_with_float },
             };
my $mysig_seen;
sub do_mysig {
  #Test::More::diag ("mysig runs");
  $mysig_seen = 1;
}
my $mysig_with_long_value;
sub do_mysig_with_long {
  my ($self, $value) = @_;
  #Test::More::diag ("mysig_with_long runs, value=$value");
  $mysig_with_long_value = $value;
}
my $mysig_with_float_value;
sub do_mysig_with_float {
  my ($self, $value) = @_;
  #Test::More::diag ("mysig_with_float runs, value=$value");
  $mysig_with_float_value = $value;
}

package My::Widget;
use strict;
use warnings;
use Gtk2;
use Glib::Object::Subclass
  'Gtk2::EventBox',
  signals => { mywidgetsig => { parameter_types => [],
                          return_type => undef,
                          flags => ['run-last','action'],
                          class_closure => \&do_mywidgetsig },
             };
my $mywidgetsig_seen;
sub do_mywidgetsig {
  #Test::More::diag ("mywidgetsig runs");
  $mywidgetsig_seen = 1;
}


package main;
use strict;
use warnings;
# Note: need '-init' to make Gtk2::Rc do its thing ...
use Gtk2::TestHelper tests => 43;

# A few tests below require a valid keymap, which some display servers lack.
# So try to determine if we're affected and skip the relevant tests if so.
my $keymap = Gtk2::Gdk::Keymap->get_default();
my @entries = $keymap->get_entries_for_keyval(
  Gtk2::Gdk->keyval_from_name('Return'));
my $have_valid_keymap = scalar @entries != 0;

#-----------------------------------------------------------------------------
# new()

my $mybindings = Gtk2::BindingSet->new('mybindings');
ok ($mybindings, 'new()');

#-----------------------------------------------------------------------------
# priority constants

is (Gtk2::GTK_PATH_PRIO_LOWEST, 0);
ok (Gtk2::GTK_PATH_PRIO_GTK);
ok (Gtk2::GTK_PATH_PRIO_APPLICATION);
ok (Gtk2::GTK_PATH_PRIO_THEME);
ok (Gtk2::GTK_PATH_PRIO_RC);
ok (Gtk2::GTK_PATH_PRIO_HIGHEST);

#-----------------------------------------------------------------------------
# set_name() field accessor

is ($mybindings->set_name, 'mybindings',
    'set_name() of mybindings');

#-----------------------------------------------------------------------------
# find()

ok (Gtk2::BindingSet->find('mybindings'),
    'find() mybindings');
is (Gtk2::BindingSet->find('nosuchbindingset'), undef,
    'find() not found');

#-----------------------------------------------------------------------------
# by_class()

ok (Gtk2::BindingSet->by_class('Gtk2::Entry'),
    'by_class() Gtk2::Entry');

#-----------------------------------------------------------------------------
# activate()

# The rc mechanism doesn't actually parse anything or create any
# GtkBindingSet's until one or more GtkSettings objects exist and are
# interested in the rc values.  Create a dummy label widget to force that to
# happen and thus ensure creation of the "some_bindings" set.
#
my $dummy_label = Gtk2::Label->new;

Gtk2::Rc->parse_string (<<'HERE');
binding "some_bindings" {
  bind "Return" { "mysig" () }
}
HERE

{
  my $some_bindings = Gtk2::BindingSet->find('some_bindings');
  ok ($some_bindings, 'find() of RC parsed bindings');

  my $myobj = My::Object->new;
  $mysig_seen = 0;
  ok ($some_bindings->activate (Gtk2::Gdk->keyval_from_name('Return'),
                                [],$myobj),
      'activate() return true on myobj');
  is ($mysig_seen, 1, 'activate() runs mysig on myobj');
}

#-----------------------------------------------------------------------------
# add_path() and $object->bindings_activate() and bindings_activate_event()

Gtk2::Rc->parse_string (<<'HERE');
binding "my_widget_bindings" {
  bind "Return" { "mywidgetsig" () }
}
HERE

# As of Gtk 2.12 $gtkobj->bindings_activate() only actually works on a
# Gtk2::Widget, not a Gtk2::Object, hence using My::Widget to exercise
# add_path() instead of My::Object.
SKIP: {
  skip 'Need a keymap and gtk+ >= 2.4', 5
    unless $have_valid_keymap && Gtk2->CHECK_VERSION(2, 4, 0);

  my $my_widget_bindings = Gtk2::BindingSet->find('my_widget_bindings');
  ok ($my_widget_bindings, 'find() of RC parsed bindings');

  $my_widget_bindings->add_path ('class', 'My__Widget',
                                 Gtk2::GTK_PATH_PRIO_APPLICATION);

  my $mywidget = My::Widget->new;
  my $keyval = Gtk2::Gdk->keyval_from_name ('Return');
  my $modifiers = [];

  $mywidgetsig_seen = 0;
  ok ($mywidget->bindings_activate ($keyval,$modifiers),
      'bindings_activate() return true on mywidget');
  is ($mywidgetsig_seen, 1,
      'bindings_activate() runs mywidgetsig on mywidget');

  # This diabolical bit of code is what it takes to synthesize a
  # Gtk2::Gdk::Event::Key which gtk_bindings_activate_event() will dispatch.
  # That func looks at the hardware_keycode and group, rather than the
  # keyval in the event, so must generate those.  hardware_keycode values
  # are basically arbitrary aren't they?  At any rate the strategy is to
  # lookup what hardware code is Return in the display keymap and use that.
  # gtk_bindings_activate_event() then ends up then going the other way,
  # turning the hardware code into a keyval to lookup in the bindingset!
  #
  # The gtk_widget_get_display() docs say $mywidget won't have a display
  # until it's the child of a toplevel.  Gtk 2.12 will give you back the
  # default display before then, but probably better not to rely on that.
  #
  my $toplevel = Gtk2::Window->new;
  $toplevel->add ($mywidget);
  my $display = $mywidget->get_display;
  my $keymap = Gtk2::Gdk::Keymap->get_for_display ($display);
  my @keys = $keymap->get_entries_for_keyval ($keyval);
  # diag "keys ", explain \@keys;

  my $event = Gtk2::Gdk::Event->new ('key-press');
  $event->window ($mywidget->window);
  $event->keyval ($keyval);
  $event->set_state ($modifiers);
  $event->group($keys[0]->{'group'});
  $event->hardware_keycode($keys[0]->{'keycode'});
  $mywidget->bindings_activate_event ($event);

  $mywidgetsig_seen = 0;
  ok ($mywidget->bindings_activate_event ($event),
      'bindings_activate() return true on mywidget');
  is ($mywidgetsig_seen, 1,
      'bindings_activate() runs mywidgetsig on mywidget');

  $toplevel->destroy;
}

#-----------------------------------------------------------------------------
# entry_add_signal()

{
  my $bindings = Gtk2::BindingSet->new ('entry_add_signal_test');
  my $obj = My::Object->new;

  {
    my $keyval = Gtk2::Gdk->keyval_from_name('Return');
    my $modifiers = [];
    $bindings->entry_add_signal ($keyval, $modifiers, 'mysig');
    $mysig_seen = 0;
    ok ($bindings->activate ($keyval, $modifiers, $obj),
        'entry_add_signal() activate on MyObject -- dispatch mysig');
    is ($mysig_seen, 1,
        'entry_add_signal() activate on MyObject -- ran mysig');
  }

  # object taking Glib::Long, pass as Glib::Long
  #
  {
    my $keyval = Gtk2::Gdk->keyval_from_name('Escape');
    my $modifiers = [];
    my $arg = 12456;
    $bindings->entry_add_signal ($keyval, $modifiers, 'mysig-with-long',
                                 'Glib::Long', $arg);
    $mysig_with_long_value = 0;
    ok ($bindings->activate ($keyval, $modifiers, $obj),
        'entry_add_signal() activate on MyObject -- dispatch mysig_with_long');
    is ($mysig_with_long_value, $arg,
        'entry_add_signal() activate on MyObject -- mysig_with_long value');
  }

  # object taking Glib::Float, pass as Glib::Double
  #
  {
    my $keyval = Gtk2::Gdk->keyval_from_name('space');
    my $modifiers = [ 'control-mask' ];
    my $arg = 1.25;
    $bindings->entry_add_signal ($keyval, $modifiers, 'mysig-with-float',
                                 'Glib::Double', $arg);
    $mysig_with_float_value = 0;
    ok ($bindings->activate ($keyval, $modifiers, $obj),
        'entry_add_signal() activate on MyObject -- dispatch mysig_with_float');
    delta_ok ($mysig_with_float_value, $arg,
              'entry_add_signal() activate on MyObject -- mysig_with_float value');
  }

  Glib::Type->register_flags ('My::Flags',
                              ['value-one'   =>  8 ],
                              ['value-two'   => 16 ],
                              ['value-three' => 32 ]);

  # object taking Glib::Long, give flags as arrayref
  #
  {
    my $keyval = Gtk2::Gdk->keyval_from_name('Escape');
    my $modifiers = [ 'control-mask' ];
    my $flags = ['value-one', 'value-three'];
    my $flags_num = 40;
    $bindings->entry_add_signal ($keyval, $modifiers, 'mysig-with-long',
                                 'My::Flags', $flags);
    $mysig_with_long_value = -1;
    ok ($bindings->activate ($keyval, $modifiers, $obj),
        'entry_add_signal() activate on MyObject -- dispatch mysig_with_long');
    is ($mysig_with_long_value, $flags_num,
        'entry_add_signal() activate on MyObject -- mysig_with_long value');
  }

  # object taking Glib::Long, give flags as flags object
  #
  {
    my $keyval = Gtk2::Gdk->keyval_from_name('Escape');
    my $modifiers = [ 'control-mask' ];
    my $flags = My::Flags->new (['value-one', 'value-two']);
    my $flags_num = 24;
    $bindings->entry_add_signal ($keyval, $modifiers, 'mysig-with-long',
                                 'Glib::Flags', $flags);
    $mysig_with_long_value = -1;
    ok ($bindings->activate ($keyval, $modifiers, $obj),
        'entry_add_signal() activate on MyObject -- dispatch mysig_with_long');
    is ($mysig_with_long_value, $flags_num,
        'entry_add_signal() activate on MyObject -- mysig_with_long value');
  }

  Glib::Type->register_flags ('My::Enum',
                              [eeeek => 123 ]);

  # object taking Glib::Long, give enum as string
  #
  {
    my $keyval = Gtk2::Gdk->keyval_from_name('space');
    my $modifiers = [];
    $bindings->entry_add_signal ($keyval, $modifiers, 'mysig-with-long',
                                 'My::Enum', 'eeeek');
    $mysig_with_long_value = -1;
    ok ($bindings->activate ($keyval, $modifiers, $obj),
        'entry_add_signal() activate on MyObject -- dispatch mysig_with_long');
    is ($mysig_with_long_value, 123,
        'entry_add_signal() activate on MyObject -- mysig_with_long value');
  }
}

#-----------------------------------------------------------------------------
# entry_remove()

{
  my $bindings = Gtk2::BindingSet->new ('entry_remove_test');
  my $obj = My::Object->new;

  my $keyval = Gtk2::Gdk->keyval_from_name('Return');
  my $modifiers = [];
  $bindings->entry_add_signal ($keyval, $modifiers, 'mysig');

  $mysig_seen = 0;
  ok ($bindings->activate ($keyval, $modifiers, $obj),
      'before entry_remove() activate on MyObject -- dispatch mysig');
  is ($mysig_seen, 1,
      'before entry_remove() activate on MyObject -- ran mysig');

  $bindings->entry_remove ($keyval, $modifiers);

  $mysig_seen = 0;
  ok (! $bindings->activate ($keyval, $modifiers, $obj),
      'after entry_remove() activate on MyObject -- no dispatch mysig');
  is ($mysig_seen, 0,
      'after entry_remove() activate on MyObject -- no run mysig');
}


#-----------------------------------------------------------------------------
# entry_skip()

SKIP: {
  skip 'Need a keymap', 8
    unless $have_valid_keymap;

  skip 'entry_skip() new in 2.12', 8
    unless Gtk2->CHECK_VERSION(2, 12, 0);

  # see that basic invocation on object doesn't dispatch
  #
  my $skip_bindings = Gtk2::BindingSet->new ('entry_skip_test');
  my $keyval = Gtk2::Gdk->keyval_from_name('Return');
  my $modifiers = [];
  $skip_bindings->entry_add_signal ($keyval, $modifiers, 'mysig');

  my $obj = My::Object->new;

  $mysig_seen = 0;
  ok ($skip_bindings->activate ($keyval, $modifiers, $obj),
      'before entry_skip() activate on MyObject -- dispatch mysig');
  is ($mysig_seen, 1,
      'before entry_skip() activate on MyObject -- ran mysig');

  $skip_bindings->entry_skip ($keyval, $modifiers);

  $mysig_seen = 0;
  ok (! $skip_bindings->activate ($keyval, $modifiers, $obj),
      'after entry_skip() activate on MyObject -- no dispatch mysig');
  is ($mysig_seen, 0,
      'after entry_skip() activate on MyObject -- no run mysig');


  # When an entry_skip() binding shadows another binding the latter doesn't
  # run.
  #
  # This more exercises gtk than it does the bindings, but it does make sure
  # the shared code of ->entry_skip() and ->entry_remove() have the right
  # func under the right name.
  #
  my $mywidget = My::Widget->new;

  $mywidgetsig_seen = 0;
  ok ($mywidget->bindings_activate (Gtk2::Gdk->keyval_from_name('Return'),[]),
      'before entry_skip(), bindings_activate return true on mywidget');
  is ($mywidgetsig_seen, 1,
      'before entry_skip(), bindings_activate runs mywidgetsig on mywidget');

  $skip_bindings->add_path ('widget-class', 'My__Widget',
                            Gtk2::GTK_PATH_PRIO_HIGHEST);

  $mywidgetsig_seen = 0;
  ok (! $mywidget->bindings_activate(Gtk2::Gdk->keyval_from_name('Return'),[]),
      'before entry_skip(), bindings_activate return true on mywidget');
  is ($mywidgetsig_seen, 0,
      'before entry_skip(), bindings_activate runs mywidgetsig on mywidget');
}

exit 0;