Tutorial
|
|
|
|
Getting Started
- Hello World in Inti
- Compiling Hello World
- Theory of Signals and Slots
- Events
- Stepping through Hello World
To begin our introduction to Inti lets start with the simplest program
possible. This program will create a 200x200 pixel window and has
no way of exiting except to be killed by using the shell.
The header file for Basic Window is base.h:
#include
<inti/main.h>
#include <inti/gtk/window.h>
using namespace Inti;
class Window : public
Gtk::Window
{
public:
Window();
virtual ~Window();
};
|
and the source file is base.cc:
#include
"base.h"
Window::Window()
{
set_title("Basic Window");
show();
}
Window::~Window()
{
}
int main (int
argc, char *argv[])
{
using namespace Main;
init(&argc, &argv);
Window window;
window.sig_destroy().connect(slot(&Inti::Main::quit));
run();
return 0;
} |
You can compile the program with g++ using:
g++ -Wall base.cc -o
base `pkg-config inti-1.0 --cflags --libs`
|
The meaning of the unusual compilation options is
explained below in Compiling Hello
World. The program includes <inti/main.h>
and <inti/gtk/window.h> which declares the base
classes and member functions, etc. that are used in the application.
The next line:
opens up the Inti namespace to avoid having to use the namespace
as a prefix
. It lets us write code like Gtk::Window instead of
Inti::Gtk::Window. The class declaration for Window declares only two
member functions: a
constructor and a
destructor.
In
the source file, the first line in the constructor:
set_title("Basic
Window");
|
calls the set_title() method that Window inherits from Gtk::Window. It
sets the window's title to Basic Window. The next line in the
constructor:
calls the show() method that Window inherits from Gtk::Widget. It
displays the window. The destructor here doesn't do anything. The next
line is inside the program's main function.
Again, this declaration opens up the Main namespace to avoid having to
use the namespace as a prefix. The only other thing to note here is
that Inti wraps the GTK+ library initialization, main event loop and
event functions into the Main namespace rather than a static class.
This is a design you wont use very often, if ever, but here it serves
the purpose of Main well. The next line:
needs to be called by all Inti applications. This function initializes
the library for use, sets up default signal handlers, and checks the
arguments passed to your application on the command line, looking for
one of the following:
- --gtk-module
- --g-fatal-warnings
- --gtk-debug
- --gtk-no-debug
- --gdk-debug
- --gdk-no-debug
- --display
- --sync
- --name
- --class
It removes these from the argument list, leaving
anything it does not recognize for your application to parse or ignore.
This creates a set of standard arguments accepted by all Inti
applications. The next line of code:
creates an instance of Window on the stack. The
window is displayed at this point because show() was called in the
Window constructor. Alternatively you could have called show() at
this point with the line:
The Window constructor takes no
parameters. It calls the default Gtk::Window constructor which creates
the toplevel window we need for window manager decoration and
placement. Rather than creating a window of 0x0 size, a window
without children is set to 200x200 by default so you can still
manipulate it. The next line of code:
window.sig_destroy().connect(slot(&Inti::Main::quit));
|
connects the window's inherited
destroy_signal to a slot that calls Inti::Main::quit(). When the
destroy
signal is emitted Inti::Main::quit() exits the main processing loop
and ends the application.
sig_destroy() is a public signal accessor
function declared in Gtk::Object. All signal accessor
functions are named by prefixing sig_ to the GTK signal name, in
this case destroy. The connect() method is the signal's
member function to call when connecting a slot. The
slot() function is an overloaded factory function that
generates slots. A slot looks like a function but it is a function
object or functor. More about slots later. The last line:
enters the GTK main processing loop. It's another
call you will see in every Inti application. When control reaches this
point, Inti will sleep waiting for X events (such as button or key
presses), timeouts, or file IO notifications to occur. In our simple
example, however, events are ignored.
Hello World in
Inti
Now for a program with a widget (a button). It's the classic hello
world a la Inti.
The header file for helloworld is helloworld.h
#include
<inti/main.h>
#include <inti/core.h>
using namespace Inti;
class HelloWorld : public Gtk::Window
{
protected:
virtual bool
on_delete_event(const Gdk::EventAny&
event);
void on_hello();
public:
HelloWorld();
~HelloWorld();
}; |
and the source file is helloworld.cc:
#include
"helloworld.h"
#include <inti/gtk/button.h>
#include <iostream>
HelloWorld::HelloWorld()
{
// Sets the border
width of the window.
set_border_width(10);
// Creates a new button
with the label "Hello World".
Gtk::Button *button = new Gtk::Button("Hello World");
// When the button
receives the "clicked" signal, it calls the on_hello() slot.
button->sig_clicked().connect(slot(this, &HelloWorld::on_hello));
// This will cause the
window to be destroyed by calling HelloWindow's inherited dispose
method.
button->sig_clicked().connect(slot(this, &HelloWorld::dispose));
// This packs the
button into the window (a Gtk::Container).
add(*button);
// The final step is
to display this newly created widget.
button->show();
}
HelloWorld::~HelloWorld()
{
}
bool
HelloWorld::on_delete_event(const
Gdk::EventAny& event)
{
// When the window is
given the "delete_event" signal (this is given by the window manager,
// usually by the "close" option, or on the
titlebar), the on_delete_event() slot is called.
// If you return false a "destroy" signal is
emitted. Returning true means you don't want
// the window to be destroyed. This is useful for
popping up 'are you sure you want to quit?'
// type dialogs.
std::cout << "delete event occurred" <<
'\n';
return true;
}
void
HelloWorld::on_hello()
{
std::cout << "Hello World" << std::endl;
}
int main (int argc, char
*argv[])
{
using namespace Main;
init(&argc, &argv);
HelloWorld window;
window.sig_destroy().connect(slot(&Inti::Main::quit));
window.show();
run();
return 0;
} |
Compiling Hello
World
To compile HelloWorld use:
g++ -Wall -g helloworld.cc -o helloworld `pkg-config inti-1.0
--cflags --libs`
This uses the program pkg-config, which can be obtained from
http://www.freedesktop.org. This
program reads the inti-1.0.pc file which comes with Inti to
determine what compiler switches are needed to compile programs
that use Inti. The --cflags option will output a list of include
directories for the compiler to look in, and the --libs option will
output the list of libraries for the compiler to link with and the
directories to find them in.
Note that the type of single quote used in the compile command
above is significant.
The libraries that are usually linked in are:
- The Inti library (-linti), the core application development
platform based on GTK+.
- The GTK library (-lgtk), the widget library, based on top of
GDK.
- The GDK library (-lgdk), the Xlib wrapper.
- The gdk-pixbuf library (-lgdk_pixbuf), the image manipulation
library.
- The Pango library (-lpango) for internationalized text.
- The gobject library (-lgobject), containing the type system on
which GTK is based.
- The gmodule library (-lgmodule), which is used to load run time
extensions.
- The GLib library (-lglib), containing miscellaneous functions;
GTK is built on top of GLib so you will always require this library.
- The Xlib library (-lX11) which is used by GDK.
- The Xext library (-lXext). This contains code for shared memory
pixmaps and other X extensions.
- The math library (-lm). This is used by GTK for various purposes.
The theory of
Signals and Slots
Before we look in detail at helloworld, we'll
discuss GTK signals and slots.
GTK is an event driven toolkit, which means it will sleep in the main
processing loop until an event occurs and control is passed to the
appropriate function. This passing of control is done using the idea of
signals.
When an event occurs, such as the press of a mouse button, the
appropriate signal will be
emitted by the widget that was
pressed. This is how GTK does most of its useful work. There are
signals that all widgets inherit, such as
destroy, and there
are signals that are widget specific, such as
toggled on a
toggle button.
Inti wraps the GTK signal system into a set of C++ signal classes, one
for every GTK+ signal. A signal is usually declared as a protected
static member of the class that causes its emission, such as the
Gtk::Button::clicked_signal.
You can think of a signal as a list of functions or methods to be
called when a specific event occurs. Adding a function or method to be
invoked is called connecting to the signal and is done using the
signal's
connect() method:
Connection
connect(TypeInstance *instance, SlotType *slot, bool
after = false)const; |
The first argument is a pointer to the widget instance which will
be emitting the signal. The second argument is a pointer to the
function
or method slot to be called on signal emission. The last argument
specifies whether to call the slot after the signal, or to let the
signal's default behavior preside (i.e. depending on GTK_RUN_FIRST
and GTK_RUN_LAST). The default behaviour is
false which is
appropriate most of the time so you can just ignore this argument.
The return value is a connection object which can be used to
control the signal connection by either calling its block(),
unblock() or disconnect() methods. You don't have to explicitly
disconnect a signal because this gets done automatically when a widget
gets destroyed.
A signal is usually accessed through its public accessor function, like
sig_destroy()
in the helloworld example or
sig_clicked() in the
Gtk::Button
class. A signal accessor function returns a
signal proxy object.
A signal proxy exports a signal's public
connect() method and
passes it a pointer to the widget instance. You can connect to a
signal through its accessor function like this:
button->sig_clicked().connect(slot(this,
&MyWindow::on_button_clicked)); |
In Inti you always have a choice. That is, you can either connect to a
signal through its accessor function or you can derive a new widget
class and override the signal's virtual signal handler. Every signal
has a virtual signal handler which is named by prefixing
on_
to the GTK signal name. For
Gtk::Button::clicked_signal it's:
virtual
void on_clicked(); |
If you override a signal's virtual signal handler and want
to keep the signal's default behaviour you must explicitly call the
base class signal handler.
void
MyButton::on_clicked()
{
...
MyButtonBaseClass::on_clicked();
} |
A slot is a callable object that can be connected to a signal.
A
slot can either be created from a class method or a static function.
There are 7 slot classes named Slot0 to Slot6 which take a return
value and a variable number of function arguments, 0 to 6. The
slot()
function is an overloaded factory function that can generate a slot
for you. For class methods
slot() takes two arguments. The
first argument is either a pointer or reference to a class and the
second argument is a function pointer to a class method. For static
functions
slot() takes only one argument a function pointer to
a static function. The return value and argument list of the function
or method pointed to must match the return value and argument list of
the signal. For example,
Gtk::Button::clicked_signal is
declared as:
typedef
G::Signal0<void>
ClickedSignalType;
static const ClickedSignalType
clicked_signal; |
As you can see
clicked_signal returns a void and takes no
arguments so any static function or class method that returns a void
and takes no arguments can be connected to it. The
sig_clicked()
accessor function is an inline function:
typedef
G::SignalProxy<TypeInstance, ClickedSignalType>
ClickedSignalProxy;
const ClickedSignalProxy
sig_clicked()
{
return
ClickedSignalProxy(this,
&clicked_signal);
}
|
If you want to have one slot handle several widgets you can bind an
extra argument to a slot. To do this you include the file
<inti/bind.h>
in your application and call the
bind() function. Like
slot()
the
bind() function is an overloaded factory function that
generates bound slots. A bound slot is a slot that binds a signal
passing
n arguments to a slot that takes
n+1
arguments. The extra argument is stored in the bound slot and passed to
the slot function or method on signal emission. There is an example of
this in the helloworld2 application later.
It is important to note that the slot() and bind()
factory functions both return a newly allocated slot which must be
unreferenced. A signal will call unref() for all the slots
connected to it when the signal itself is destroyed, so you wont need
to. There are some class methods that take a slot as an argument. In
the case of Gtk::Menu and Gtk::Toolbar, these methods call connect()
internally to connect the slot to a menuitem or toolbar button signal
so you
wont need to call unref(). In other cases the slot argument
is used instead of a C callback and unref() will need to be
called. You can't miss these slots because they're all declared as typedefs
at the top of their respective class declarations.
Events
In addition to the signal mechanism described
above, there is a set of signals that reflect the X event mechanism.
Slots may also be connected to these signals. The signal accessor
functions are declared in the
Gtk::Widget class and are:
- sig_event()
- sig_button_press_event()
- sig_button_release_event()
- sig_scroll_event()
- sig_motion_notify_event()
- sig_destroy_event()
- sig_expose_event()
- sig_key_press_event()
- sig_key_release_event()
- sig_enter_notify_event()
- sig_leave_notify_event()
- sig_configure_event()
- sig_focus_in_event()
- sig_focus_out_event()
- sig_map_event()
- sig_unmap_event()
- sig_property_notify_event()
- sig_selection_clear_event()
- sig_selection_request_event()
- sig_selection_notify_event()
- sig_proximity_in_event()
- sig_proximity_out_event()
- sig_visibility_notify_event()
- sig_client_event()
- sig_no_expose_event()
- sig_window_state_event()
The
delete_event signal is the one exception. Since only
toplevel windows can receive this signal its accessor function
sig_delete_event()
is
declared in
Gtk::Window.
Stepping
through
HelloWorld
Now that we have all that theory behind us, let's
clarify by walking through the example
helloworld program.
The header file
helloworld.h includes two files,
<inti/main.h>
and
<inti/core.h>. Every program needs to include
the first. The latter is a convenience header file that includes
the header files used by every window. In this case it includes
<inti/gtk/window.h>
and
<inti/gtk/box.h>.
Again, the
using statement is used so we don't
have to use the Inti namespace prefix.
Next is the HelloWorld class declaration.
class
HelloWorld : public Gtk::Window
{
protected:
virtual bool
on_delete_event(const Gdk::EventAny&
event);
void
on_hello();
public:
HelloWorld();
~HelloWorld();
}; |
HelloWorld is derived from
Gtk::Window. its inheritance path
looks like this:
G::TypeInstance <-
G::Object <- Gtk::Object <- Gtk::Widget <- Gtk::Container
<- Gtk::Bin <- Gtk::Window |
HelloWorld redeclares Gtk::Window's
virtual
on_delete_event() so it can override its default behaviour. Next,
it declares an
on_hello() method to respond to button clicks.
The only other methods declared are the
constructor and
destructor.
The source file
helloworld.cc includes the following files:
#include
"helloworld.h"
#include <inti/gtk/button.h>
#include <iostream> |
The first line in the HelloWorld constructor sets the border width of
the window to 10. The border is a blank area around the inside edge of
the window where no widgets will go.
HelloWorld inherits
set_border_width() from Gtk::Container.
Next, we create a button with the label
Hello World.
Gtk::Button *button = new Gtk::Button("Hello World");
|
We then connect a slot to the button so when it emits the
clicked_signal,
our
on_hello() method gets called. Obviously, the
clicked_signal is emitted when we click the button with our mouse
pointer.
button->sig_clicked().connect(slot(this, &HelloWorld::on_hello));
|
sig_clicked() is the public
accessor function for
Gtk::Button::clicked_signal.
You can connect more than one slot to a signal. Here we connect a
second slot that calls the window's inherited dispose() method so that
when the button is clicked our main window is destroyed.
button->sig_clicked().connect(slot(this, &HelloWorld::dispose));
|
Just a brief word about
dispose(). When you want
to destroy a widget you call
dispose(). There is no destroy
method in Inti. The name dispose is used because it better reflects
what happens when a widget is destroyed. In GTK+ 2.0 when you ask for
a widget to be destroyed it may not be destroyed immediately.
Instead, it gets marked for destruction and is disposed of later when
its reference count drops to zero.
Next we make a packing call which will be explained in depth later on
in
Packing Widgets but it is fairly
easy to understand. It simply tells Inti that the button is to be
placed
in the window where it will be displayed. Note that HelloWorld is also
a Gtk::Container and a container can only contain one widget. There
are other widgets, that are described later, which are designed to
layout multiple widgets in various ways.
The final step in the constructor is to display the newly created
button.
The method
on_delete_event() is a bit special. It is the
predefined
virtual signal handler for
delete_event. The
delete_event
occurs when the window manager sends this event to the application.
We
have a choice here as to what to do about these events. We can
ignore them, make some sort of response, or simply quit the
application. The value you return in this method lets GTK know what
action to take. By returning
true we let GTK know that we
don't
want to have the
destroy_signal emitted, keeping our
application running. By returning
false, we ask that the
destroy_signal
be emitted.
bool
HelloWorld::on_delete_event(const
Gdk::EventAny& event)
{
std::cout << "delete event
occurred" << '\n';
return
true;
} |
on_delete_event() uses a C++ console output statement to
display the message "delete event occurred" on the screen. It does this
by using the output operator <<. The << causes whatever
expression is on its right side to be output to the device on the left.
cout
is the predefined identifier that stands for console output and refers
to the computer screen. To use cout in your program you include the
C++ header file
<iostream>.
Next is the slot method that will be called when the button is
clicked.
It uses a C++ console output statement to display the message "Hello
World" on the screen.
void
HelloWorld::on_hello()
{
std::cout << "Hello World"
<< std::endl;
}
|
I assume you know about the
main() function...
yes, as with other applications, all Inti applications will also have
one of these.
int
main(int argc, char
*argv[]) |
After the initial
using statement
the first line of
code calls init(). As before, this initializes the toolkit, and parses
the arguments found on the command line. Any argument it recognizes
from the command line it removes from the list, and modifies argc and
argv to make it look like they never existed, allowing your
application to parse the remaining arguments.
Next an instance of HelloWindow is created on the stack.
The
Inti::Main::quit() function causes the program to quit.
This function tells GTK that it is to exit from the main event loop
when
control is returned to it. There is no need to write a method that
calls
this function. Instead, we simply generate a slot for it and connect
that slot to our main window's
destroy signal. The next line
accomplishes this:
window.sig_destroy().connect(slot(&Inti::Main::quit));
|
The window widget is shown last so the whole window will pop up at
once rather than seeing the window pop up, and then the button form
inside of it. Although with such a simple example, you'd never notice.
And of course, we call
run() to start the main processing
loop
which then waits for events to come from the X server.
And the final return. Control returns
here after Inti::Main::quit() is called.
When we click the mouse on an Inti button, the widget emits a
clicked
signal. In order for us to use this information, Inti sets up the
Gtk::Button
clicked_signal to catch that signal and dispatch
it
to Gtk::Button's virtual
on_clicked() signal handler
and any connected slots. In our example, when the button we created
is clicked, the
on_hello() method is called first and then the
slot method. This calls the window's inherited
Gtk::Object::dispose()
method which destroys the window widget. This causes the window to
emit the
destroy signal, which is caught by its Gtk::Object
destroy_signal, which calls
Inti::Main::quit(), which
simply exits Inti.
Another course of events is to use the window manager to kill the
window, which will cause the
delete_event to be emitted. This
is caught by the Gtk::Window delete_event_signal which calls our
on_delete_event()
method. If we return
true here, the window will be left as is
and nothing will happen. Returning
false will cause GTK to
emit
the
destroy signal which of course calls
Inti::Main::quit(),
exiting Inti.