Chapter 5. Reacting to user input

Objective

We will discuss how to connect to signals, we will also explore event signals.

Introduction

Any Gtk+ program emits signals. To make your program useful, you have to connect these signals to signal handlers. Signal handlers are pieces of code that gets executed as soon as the specified signal is emitted.

There are special signals, called events. These event signals originate from the graphical system and are generated by Gtk2::Gdk

5.1. Signals

Signals give an indication what is happening to the program, it is the programmer's decision to respond or not. We will receive signals when the user clicks on a button, or when the window is minimized, or when text is entered into an entry, the list goes on. Signals gets emitted all the time. Not every Gtk2::Object emits the same signals.

In the "Hello Gtk2-Perl" program, we were already exposed to signal handling. We used Glib::Object's signal_connect method and connected the clicked and delete_event signals to pieces of code which executes when those signals are emitted.

5.1.1. Event Signals

Events are part of the Gtk2::Gdk subsystem. In a Gtk+ program, you will never receive Gtk2::Gdk::Events directly. Instead, all Gtk2::Gdk::Events are passed to a Gtk2::Widget, which emits a corresponding signal. You handle Gtk2::Gdk::Events by connecting signal handlers to Gtk2::Widget's event signals.

Here we assume a typical Linux environment. The X server sends a stream of events to each X client. Events are sent and received in the order of their occurrence. Gtk2::Gdk converts each event from X it receives into a Gtk2::Gdk::Event, then places events in a queue. Gtk+ monitors Gtk2::Gdk's event queue and for each event received, it decides which Gtk2::Widget (if any) should receive the Gtk2::Gdk::Event. The Gtk2::Widget base class defines signals for most event types (such as button_press_event); it also defines a generic event signal. The Gtk+ main loop calls Gtk2::Widget's event method to deliver an event to a widget.

Note

It is important to remember than not all signals are event signals. Some may be the result of a few combined Gtk2::Gdk::Events together. Some signals can be generated with the total absence of any Gtk2::Gdk::Events.

If you want to know the "event" type of signals that may be available to you, you can look at the signals on Gtk2::Widget's man page. All the signals which ends in "event" are Gtk2::Gdk::Event signals.

The factor that decides which Gtk2::Gdk::Event signals will be emitted by the Gtk2::Widget is the event mask. Widgets all have pre-defined event masks, but you are also allowed to add some to them. You use Gtk2::Widget's add_event method.

Tip

You can also find available event masks on the Gtk2::Widgets man page. Remember, the events you are allowed to add to the Gtk2::Widget depends on its type.

Lets say you want to follow the mouse movement on a Gtk2::EventBox, then you first have to ask it to allow these events through

$ebox->add_events (['pointer-motion-mask', 'pointer-motion-hint-mask']);
as by default, the Gtk2::EventBox does not allow mouse movement to end up as event signals. Now you can connect to the motion_notify_event.
$ebox->signal_connect (motion_notify_event => sub { },$userdata);
to catch any mouse movement.

Note

The default signals that gets emitted by widgets, will in 95% + cases, be enough for you to work with, and adding extra event signals to them are only for more specialized requirements.

5.1.2. Signal Callbacks

The callback that you connect to a signal can be a sub, or an anonymous sub (closure). Connecting it to a sub limits you to only one argument for user data. The data that you pass to the sub, is a static "snapshot" of a the value when the program executed the code to connect the event to the sub. To reflect changes in a variable, we have to use a reference.

unsigned = $instance->signal_connect ($detailed_signal, $callback, $data=undef) 
	* $detailed_signal (string)
	* $callback (subroutine)
 	* $data (scalar) arbitrary data to be passed to each invocation of callback

Closures overcome the "one argument" userdata limitation. This will allow you to do the following:

$button->signal_connect('clicked' => sub {
			my ($button,$userdata) = @_;
			&change_labels($lbl_name,$lbl_surname,$lbl_birthdate....);
			},$userdata);
You can now pass variables within the same scope as the closure as arguments to a sub within the closure.

Data passed to to the callback

  • When the signal that was emitted, is an "event" type of signal, the callback receives the following, and in this order:

    • An object instance of the widget who called this sub.

    • A blessed reference to the Gtk2::Gdk::Event which caused the signal.

    • Userdata, if it is defined.

  • If it is NOT an "event" type of signal, the callback receives the following, and in this order:

    • An object instance of the widget who called this sub.

    • Userdata, if it is defined.

A reference to the Gtk2::Gdk::Event is included because we may want to get extra info from it. We may want to check which button was pressed, or which key on the keyboard. In the two sample programs we will show how to get extra info from the Gtk2::Gdk::Event.

Stopping event signals.

The return value of the signal handler for event signals is very important, and determines if the signal will propagate through, or not.

Lets say you want to offer the user the option, when he/she closes a window, to save their work before the window closes, and the program exits. You can then incorporate a return value in the delete-event signal handler. If the return value is boolean TRUE, the event signal will be stopped from propagating, but if it is boolean FALSE, it will continue with the propagation of the signal, and the window will close.

Tip

It is good practice to exclusively return boolean FALSE from the signal handler. This will ensure that other widgets may also receive the event, if they need to.

#good practice to let the event propagate, should we need it somewhere else
	return FALSE;

5.1.3. Events from the mouse

We will look at a program that connects the button_release_event signal generated from a Gtk2::Button to a sub. This sub will then use Gtk2::Gdk::Event::Button to determine which mouse button was released. This will typically be used to pop up a menu when a user "right-click" on a widget.

Screenshot

Figure 5-1. Event Demo (Mouse)

The Code

The program can be found here: 'Event Demo (Mouse)'

 1 #! /usr/bin/perl -w
 2 
 3 use strict;
 4 use Glib qw/TRUE FALSE/;
 5 use Gtk2 -init;
 6 
 7 my $count =0;
 8 
 9 my $window = Gtk2::Window->new ('toplevel');
10 $window->signal_connect (delete_event => sub { Gtk2->main_quit });
11 	my $button = Gtk2::Button->new ('Action');
12  
13 $window->add ($button);
14 $window->set_position ('center-always');
15 $window->show_all;
16 
17 #beware that the argument is a static snapshot of the varialble!
18 #To get a dynamic one you have to specify a refrence!
19 $button->signal_connect('button-release-event' => sub {
20 	
21 		my ($widget,$event) = @_;
22 		#count is in the same namespace as the closure
23 		&test_button_release($widget,$event,\$count);
24 	});
25 
26 Gtk2->main;
27 
28 sub test_button_release {
29 
30 my ($widget,$event,$parameter)= @_;
31 
32 	my $button_nr = $event->button();
33 	
34 	#to get the value of a reference, we ad a '$' to the reference's variable
35 	#from the camel book: "If what's inside is a reference,
36 	# you can look inside that (dereferencing $foo) by
37 	# prepending another funny character" -> $$parameter
38 	$widget->set_label("You Clicked Mouse button NR: $button_nr"."\nCounter data:". $$parameter);
39 	#we increment the value by 1
40 	${ $parameter }++;
41   	print $count. "\n";
42 	#good practice to let the event propagate, should we need it somewhere else
43 	return FALSE;	
44 }

Important points

  • Remember that event signals pass the Gtk2::Gdk::Event as a second argument to the sub or closure. See the following line:

    my ($widget,$event) = @_;		

  • Certain events can be further inspected. In the program above, we inspect which mouse button was released. For this we use the Gtk2::Gdk::Event::Button (check its man page for more detail) 's button() method. This will return an integer, presenting which button was released.

    my $button_nr = $event->button();

  • The data that you pass to the sub, is a static "snapshot" of a the value when the program executed the code to connect the signal to the sub. To reflect changes in a variable, we have to use a reference.

    #count is in the same namespace as the closure
    &test_button_release($widget,$event,\$count);
    Then we can "dereference" the reference to get is value.
    #to get the value of a reference, we ad a '$' to the reference's variable
    #from the camel book: "If what's inside is a reference,
    # you can look inside that (dereferencing $foo) by
    # prepending another funny character" -> $$parameter
    $widget->set_label("You Clicked Mouse button NR: $button_nr"."\nCounter data:". $$parameter);

5.1.4. Events from the keyboard

You may want to bind certain keys that will cause the program to execute some code as soon as that key or combination of keys is pressed.

In the program, we will do the following:

  • Connect the window's key-press-event signal to a sub that will show us which key was pressed.

  • The sub will use methods from Gtk2::Gdk::Keysyms and Gtk2::Gdk::Event::Key to discover the name of the key that was pressed.

  • To discover the keyvalue (integer) that was pressed, we use

    my $key_nr = Gtk2::Gdk::Event::Key->keyval()

  • We then go throug the Gtk2::Gdk::Keysyms hash's values, and get the key of the integer value.(A bit of a reverse way - we assume each value is also unique. )

    foreach my $key  (keys %Gtk2::Gdk::Keysyms){
    	
    my $key_compare = $Gtk2::Gdk::Keysyms{$key};
    		
    	if($key_compare == $key_nr){
    	#print ("You typed $key \n");
    		
    	last;
    	}
    }

Screenshot

Figure 5-2. Event Demo (Keyboard)

The Code

The program can be found here: 'Event Demo (Keyboard)'

 1 #! /usr/bin/perl -w
 2 use strict;
 3 use Gtk2::Gdk::Keysyms;
 4 use Glib qw/TRUE FALSE/;
 5 use Gtk2 -init;
 6 
 7 my $window = Gtk2::Window->new ('toplevel');
 8 $window->signal_connect (delete_event => sub { Gtk2->main_quit });
 9 $window->signal_connect('key-press-event' => \&show_key);
10 	
11  	my $label = Gtk2::Label->new();
12  	$label->set_markup("<span foreground=\"blue\" size=\"x-large\">Type something on the keyboard!</span>");
13 	
14 $window->add ($label);
15 $window->show_all;
16 $window->set_position ('center-always');
17 
18 Gtk2->main;
19 
20 sub show_key {
21 
22 my ($widget,$event,$parameter)= @_;
23 
24 my $key_nr = $event->keyval();
25 
26 	#run trough the available key names, and get the values of each,
27 	#compare this with $event->keyval(), when you get a match exit the loop
28 	foreach my $key  (keys %Gtk2::Gdk::Keysyms){
29 	
30 		my $key_compare = $Gtk2::Gdk::Keysyms{$key};
31 		
32 		if($key_compare == $key_nr){
33 		#print ("You typed $key \n");
34 		$label->set_markup("<span foreground=\"blue\" size=\"x-large\">You typed a</span><span foreground=\"red\" size=\"30000\"><i><tt> $key</tt></i></span><span foreground=\"blue\" size=\"x-large\"> which has a numeric value of </span><span foreground=\"red\" size=\"30000\"><i><tt> $key_nr!</tt></i></span>");
35 		$widget->set_title("key pressed: $key -> numeric value: $key_nr");
36 		last;
37 		}
38 	}
39 	#good practice to let the event propagate, should we need it somewhere else
40 	return FALSE;
41 }

Tip

From the documentation: "As the list of keycodes is quite large and rather rarely used in application code, we've put it in a separately-loaded module to save space."

To do this, you have to specify:

use Gtk2::Gdk::Keysyms;
at the start of your program.