Tutorial

Inti Logo

« Introduction
Moving On »

Getting Started

  1. Hello World in Inti
  2. Compiling Hello World
  3. Theory of Signals and Slots
  4. Events
  5. 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.

Basic Window


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:

using namespace Inti;

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:

show();

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.

using namespace Main;

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:

init(&argc, &argv);

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:

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:

Window window;

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:

window.show();

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:

run();

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.

Hello World

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 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:

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.

using namespace Inti;

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.

set_border_width(10);

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.

add(*button);

The final step in the constructor is to display the newly created button.

button->show();

The method on_delete_event() is a bit special. It is the predefined virtual signal handler for delete_event. Thedelete_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.

init(&argc, &argv);

Next an instance of HelloWindow is created on the stack.

HelloWorld window;

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.

window.show();

And of course, we call run() to start the main processing loop which then waits for events to come from the X server.

run();

And the final return. Control returns here after Inti::Main::quit() is called.

return 0;

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.



« Introduction
Index
Top

Moving On »