SARG wrote:I can't wait ;-)
Here you are: the last lesson, related to user actions.
We learned how to create a widget by classic coding (snippet[12]) or by using GtkBuilder and XML syntax (snippet3). And we learned that we can change an existent widget by classic coding only. This lessen will show how to get noticed on actions the user performs on our GUI.
As in all modern GUI toolkits, also GTK+ uses a signal system for that purpose. The basis object (GObject) implements that signal system and provides a single signal, named "notify". Like the object properties also the signals get referenced by a name (a string). Each object that gets derived from GObject inherits this signal and can add further signals.
The next object level is the GtkWidget. It implements a bunch of signals (more than 30) for different purposes. You rarly need this signals, they'll give you low-level-access for complex activities. Not all signals get emitted by default, some of them are masked out in oder to gain execution speed. Try to avoid using those signals.
All further GUI objects are derived from GtkWidget, inheriting the signals and adding new ones. This is the signal level that you'll use most often. Ie. a GtkButton implements the "clicked" signal, that gets emitted when the user clicks on that button. Or a GtkAction emits the "activate" signal, that gets emitted when any proxied widget gets activated (like a click on a menu item or a click on a [toolbar] button, but also a keystroke that triggers that action).
By default the signals are either doing nothing (no handler in the signal queue) or are connected to a default GTK handler. Ie. a right click in a GtkTextView triggers the default handler that pops up a menu for clipboard actions. You can connect customized signal handlers, that get executed when the signal gets emitted. (Rarly used: more than one handler can get connected to a queue. In that case the callbacks get called in the order they were connected. At run-time, you can add further callbacks or remove existent from the queue.)
High level signals are (mostly) SUBs with simple signature, using only two parameters (like GtkButton, GtkAction)
- the widget (GObject PTR) that sends the signal
- an optional gpionter for user data
In low-level signals you can find more complex signatures. Ie. the "menu_button3_event" callback mentioned in my last post gets connected to the signal "button-press-event" (GtkWidget level). The signal handler returns a gboolean value (and therefor is a FUNCTION) and gets five parameters, in order to make it easy to access additional data on how the triggering event happend.
When you want to get noticed on a user action, you've to connect a signal handler to the related widget signal. Therefor
- create a callback in FB source (a SUB or FUNCTION) and
- connect that callback to the widgets signal.
Let's check this in the code. Ie. to connect the callback "menu_button3_event" to the "button-press-event" signal of the GtkTreeView, the later can be done either by classic coding (snippet2 syntax) by
Code: Select all
g_signal_connect(tviewProcVar, "button-press-event", G_CALLBACK(@menu_button3_event), menu10)
or by using a GtkBuilder parsing XML code like
Code: Select all
<object class="GtkTreeView" id="tviewProcVar">
...
<signal name="button-press-event" handler="menu_button3_event" object="menu10" swapped="no"/>
...
Note: the first syntax is based on the GLib macro g_signal_connect(). This macro isn't in the Gir/Gtk-3.0.bi header, since the header gets auto-translated by GirToBac from an .gir file and those files do not contain macro defines. You'll need an additional header to use this syntax:
#INCLUDE ONCE "Gir/_GObjectMacros-2.0.bi".
Since most signal connections wont change at run-time, it's wise not to pollute the source code by that kind of code. I prefer to connect signal handlers by using the XML syntax, whenever possible. Let's analyse the attributes
- name="button-press-event" -> the name of the signal
- handler="menu_button3_event" -> the name of the callback (a SUB or FUNCTION in FB code here)
- object="menu10" -> an optional object ID, that gets passed as last argument in the parameter list (similar to last parameter in g_signal_connect)
- swapped="no" -> the order of the passed arguments (will get explained later)
When GtkBuilder parses that line, it searches the executables list of exported symbols to find the SUB or FUNCTION "menu_button3_event" and connects that adress to the signal handler (at run-time). Since an object is specified, GtkBuilder stores the adress of that object as parameter for user_data, which is the last argument that gets passed when the signal gets emitted and the callback gets called.
This concept offers a lot of possibilities. Beside FB callbacks, you can also connect GTK+ functions to handle a signal. Ie. in the source there is no code to end the program. Instead in file fbdbg.ui the signal "destroy" from the main window (ID="window1") is connected to the GTK function "gtk_main_quit", like
Code: Select all
<object class="GtkWindow" id="window1">
...
<signal name="destroy" handler="gtk_main_quit" swapped="no"/>
...
When the user closes that window (ie. by pressing <Alt>F4), the function gtk_main_quit() gets called and the program ends (and destroys the main window and all subwindows). This function has the signature
void(viod) (=
SUB()) and therefor doesn't receive any parameter.
Another example is the about dialog. You wont find any FB code to handle that dialog. In the fbdbg.ui XML code the signal "activate" of the related action (ID="action099") gets connected to the function gtk_dialog_run(). This function requires a pointer to the dialog to run as the first parameter. We have to specify that object pointer as user_data when the signal gets emitted
Code: Select all
<object class="GtkAction" id="action099">
...
<signal name="activate" handler="gtk_dialog_run" object="aboutdialog1" swapped="yes"/>
...
By default the user_data gets passed as the second argument in an GtkAction "activate" signal. The attribute
swapped="yes" swappes the first and the last argument for that callback call, so that in this case function gtk_dialog_run gets called with the 'aboutdialog1' pointer as first argument.
To close that dialog, its signal "response" is connected to function gtk_widget_hide(). This signal gets emitted when the user performs a final action on the dialog, ie. by pressing <Alt>F4 or Esc, or by clicking the 'Close' button.
Code: Select all
<object class="GtkAboutDialog" id="aboutdialog1">
...
<signal name="response" handler="gtk_widget_hide" swapped="no"/>
...
Since the widget should hide itself, we do not swap the arguments here.
A last example, let's enlight the issue with the ProvVar popup menu. The corrected code is
Code: Select all
<signal name="button-press-event" handler="menu_button3_event" object="menu10" swapped="no"/>
Here the signal "button-press-event" (GtkWidget level) gets connected to the FB callback menu_button3_event(), located in file
main.bas. This function checks which button is pressed, first. It does nothing (RETURN FALSE), exept when button 3 (=right mouse button) is pressed. In that case it calls the function gtk_menu_popup() and returns TRUE (to stop further signal handling by default handlers). The related menu gets passed as the last argument (user_data). When connecting the signals to the diverse widgets, different popup menus get specified as user_data, so that the same callback can be used to pop up widget specific menus.
Due to my mistake when re-naming the menu widget IDs, the wrong menu was specified for that signal connection. fbdbg.exe showed "menu100", which is the first submenu in the ProcVar popup. Instead, "menu10" should popup, which is the complete menu.
As we see, this signal system is a powerful tool to specify program flow in the GUI designer by XML code. But it also has its limits
- not for all GTK functions
- only object pointers from the same GtkBuilder can be used
What you cannot do (and where we need an FB callback) is
- pass a user_data pointer containing an object from FB source
- evaluate dialogs user action (like YES NO)
A last note on callbacks:
The FB compiler cannot check the parameter list for a callback (no compiler can). It's up to you to make sure that your signal handler receives the correct parameter argument types (and returns the right type, if required). Find the callback signatures in the documentation, section "signals" of the related widget.
[Edit]Typo fixed[/Edit]