Tutorial

Inti Logo

« Index
Getting Started »

Introduction

  1. Installation
  2. Header Files
  3. Reference Counting
  4. Memory Management
  5. Signals and Slots
  6. Library Organization
  7. Base Class Hierarchy

Inti is a set of integrated C++ foundation classes for developing GTK+ applications on UNIX-like systems such as Linux. It is released under the GNU Lesser General Public License so it can be used to develop open software, free software, or even commercial non-free software using Inti without having to spend anything for licenses or royalties.

Essentially, Inti is a C++ application development platform built around the GTK+ application programmers interface (API). It provides more than 330 C++ classes that wrap most of the C objects found in the GTK+ library. It comes with its own system of signals and slots which make using native GTK+ signals or writing your own signals easy and includes a standard string compatible UTF-8 string class.

Inti is easier to use than GTK+ because it lets you use all the useful features of the GTK+ C API through a high-level C++ interface, including classes and the Standard Template Library.  Inti doesn't need to support any deprecated or legacy code because it's a new library and it maintains consistency with GTK+ so that all the basic concepts developers already know can continue to be be used. Many of the features found in Inti are based on the original Inti project by Havoc Pennington, such as his system of signals and slots which was inspired Karl Nelson's libsigc++ library.


Installation

The first thing to do, of course, is download the Inti source code and install it. You can always get the latest version from http://inti.sourceforge.net. You can also view other sources of Inti information online. Inti uses GNU autoconf for configuration. Once untar'd, type ./configure --help to see a list of options. You should use the GNU compiler to compile Inti.

The Inti source distribution contains the complete reference documentation and this tutorial. If the reference documentation is missing for some reason, don't worry. It's very likely your Linux distribution comes with a program called doxygen, which should be in the <usr/bin> directory. Inti uses doxygen to compile the reference documentation from special comments in the header files. Just change to the Inti source directory <docs/reference> and run the following command:

doxygen Doxyfile

If you can't find doxygen on your hard disk you will have to install it from your original distribution disks, or download the latest version from http://www.freshmeat.net.

The Inti source distribution also contains the complete source code for all of the examples used in this tutorial, along with Makefiles to aid their compilation. You can compile all the examples at once by running the Makefile in the <examples> directory. The latest addition to the tutorial is the Dial example which shows you how to extend the widget set by creating a new type of widget. This widget is akin to creating a new GTK+ widget  in C.

Inti has the following dependencies:

Most distributions come with GTK+ so you should ensure it's installed before you try to install Inti. If your distribution doesn't come with the right version you will have to download it from http://www.gtk.org. GTK+ has it's own dependencies so you will have to install those packages as well:

To compile the Inti source you will need to enter the following commands at the shell prompt:

./configure --prefix=directory
make
make install


directory is the directory where you want to install Inti. If directory is omitted Inti will install itself into the <usr/local> directory. The command "make install" will install the library with debugging and compiler symbols. In general this is recommended if you want to debug your applications. If you want to install Inti with the symbols stripped out run "make install-strip" instead. The stripped library (libinti) weighs in at just 2.7 megabytes.


Header Files

The Inti header files are well set out and easy to read. Each header file wraps one GTK+ object along with any helper classes and enums it needs. Other header files that are included are wrapped in an include guard to prevent multiple inclusions. A header file for a derived class always includes the header file for its base class. This means you only have to include the most derived header file to include all the header files in an object's inheritance path. For example, including <inti/gtk/dialog.h> also includes <inti/gtk/window.h>,<inti/gtk/bin.h>,<inti/gtk/container.h> and <inti/gtk/widget.h> so you wouldn't need to include these files.

A typical header file and has the following layout:

ifndef INTI_GTK_BUTTON_H
#define INTI_GTK_BUTTON_H

#ifndef INTI_GTK_BIN_H
#include <inti/gtk/bin.h>
#endif

#ifndef __GTK_BUTTON_H__
#include <gtk/gtkbutton.h>
#endif


namespace Inti {

namespace Gtk {

class Button : public Bin
{
    Button(const Button&);
    Button& operator=(const Button&);

protected:
// Constructors
    explicit Button(GtkButton *button, bool reference = false);

// Overridable signal handlers
    virtual void on_clicked();

// Properties
    typedef G::Property<bool> UseUnderlinePropertyType;
    static const UseUnderlinePropertyType use_underline_property;

// Signals
    typedef G::Signal0<void> ClickedSignalType;
    static const ClickedSignalType clicked_signal;

public:
// Constructors
    Button();

    virtual ~Button();

// Accessors
    GtkButton* gtk_button() const { return (GtkButton*)instance; }

    GtkButtonClass* gtk_button_class() const;

    operator GtkButton* () const;
    
    bool get_use_underline()const;

// Methods
    void clicked();

    void set_use_underline(bool use_underline);

// Properties
    typedef G::PropertyProxy<G::Object, UseUnderlinePropertyType> UseUnderlinePropertyProxy;
    const UseUnderlinePropertyProxy prop_use_underline()
    {
        return UseUnderlinePropertyProxy(this, &use_underline_property);
    }

// Signals
    typedef G::SignalProxy<TypeInstance, ClickedSignalType> ClickedSignalProxy;
    const ClickedSignalProxy sig_clicked()
    {
        return ClickedSignalProxy(this, &clicked_signal);
    }
};

} // namespace Gtk

} // namespace Inti

#endif // INTI_GTK_BUTTON_H

This cut down version of <inti/gtk/button.h>should help you better understand the Inti header files. As you can see the class members have a specific organization. First all the private members are declared, then the protected members and last the public members. I find this layout helpful in understanding how an unfamiliar object works.

The copy constructor and assignment operator are declared private to prevent inadvertent copying.  Most objects will have a protected constructor like Gtk::Button's. This constructor wraps a pointer to a new GtkButton object and sets its reference flag to false, telling Inti that initially the button is in an unowned floating state with a reference count  of 1. Next any protected accessors and methods are declared and then any properties and signals. All wrapped GTK+ signals have a overridable virtual signal handler which is named by prefixing on_ to the GTK+ signal name, in this case on_clicked(). If you derive a new object from an Inti class you don't need to explicitly connect slots to any of the signals. Instead you can just override the virtual signal handlers, but if you want a signal's default behaviour don't forget to call the base class signal handler. For example, if you derive MyButton from Gtk::Button and want to respond to its clicked signal you would declare the following line in the protected section of the MyButton class declaration:

virtual void on_clicked();

and then add the following lines of code to the MyButton source file.

void
MyButton::on_clicked()
{
    // do something here
    Gtk::Button::on_clicked();
}

Inti wraps the GTK+ property and signal systems. C++ properties, like C++ signals are protected static objects that  require an object pointer. This is how you would set a property value:

use_underline_property.set(this, true);

and this is how you would get a property value:

bool use_underline = use_underline_property.get(this);

You can only get and set properties this way from within a class. To set a property or connect to a signal from anywhere in your code you have to use its public accessor function. For properties these are named by prefixing prop_ to the GTK+ property name. For signals these are named by prefixing sig_to the GTK+ signal name. Public accessor functions implicitly pass the object this pointer for you. You can set the use_underline_property for a button using the public accessor function like this:

button->prop_use_underline().set(true);

and you can get the use_underline_property like this:

bool use_underline;
button->prop_use_underline().get(&use_underline);


You wont always need to use properties because frequently a property has a get and set function, like use_underline so you would use these instead. You can connect to a protected signal like this:

clicked_signal.connect(this, slot(this, &MyButton::clicked_function));

but you wouldn't. It's easier to override the virtual signal handler, in this case on_clicked(). To connect to a signal from any where in your code using a signal's public accessor function you would do this:

Gtk::Button *button = new Gtk::Button("Push");
button->sig_clicked().connect(slot(this, &MyWindow::clicked_function));

In the public section of the class any constructors and the destructor are declared first, followed by any accessors, then any methods, and last any property and signal accessors. Most classes have four public accessor functions similar to these:

GtkButton* gtk_button() const { return (GtkButton*)instance; }

GtkButtonClass* gtk_button_class() const;

operator GtkButton* () const;   

For Gtk::Button, gtk_button() is an inline function that returns a pointer to the underlying GtkButton object. gtk_button_class() returns a pointer the button's GtkButtonClass object. The last accessor is Gtk::Button's conversion operator. It does an implicit conversion from a Gtk::Button object to a GtkButton pointer safely, by testing for null.

The public property and signal accessors are declared last and are inline functions that return a temporary proxy object. A PropertyProxy object  implicitly passes the object this pointer and gives you access to a protected property's public get and set functions. A SignalProxy object implicitly passes the object this pointer and gives you access to a protected signal's public connect function.


 Reference counting

In Inti an object's memory management depends on its reference count so it's important that you understand the reference counting scheme. The scheme used is the  GTK+ reference counting scheme.

An Inti reference counted object is created with an initial reference count of 1. When you create a G::Object you own the initial reference and must call unref() to free the object when it's no longer required. An example of G::Objects are Gtk::AccelGroup and Gtk::Style. When you create a Gtk::Object you don't own the initial reference, instead it's a floating reference that keeps the object alive. The floating reference can be removed by anyone at any time by calling sink(). Most widget code assumes that only Gtk::Container will sink an object and call ref() to hold onto a reference to it before sinking. That means the parent container typically holds onto a reference to a child widget. Gtk::Window and Gtk::ItemFactory are exceptions because they both get a ref/sink call on creation. Gtk::Window's initial reference is owned by GTK+ so you don't need to call unref(), unless you called ref(). Gtk::ItemFactory's initial reference is owned by you so you will need call unref(). If you create a GtkObject and never pass it to an owner you have to sink the object yourself and call unref(). If you never call unref() it causes a memory leak.

The destroy signal requests that reference owners drop there references and so causes Gtk::Container to drop its references to child widgets and GTK+ to drop its reference to a toplevel Gtk::Window. This normally leads to object finalization, unless some other object is holding onto a reference.


 Memory Management

Inti installs a C callback function that gets called when a GTK+ object is destroyed. If an object's C++ wrapper was created on the heap this callback function deletes the C++ wrapper. This leads to the most important  rule of memory management in Inti:

Never call delete on a Inti object, Inti calls delete for you if the object was created by operator new. When you want to destroy an object you either call unref() or dispose().You would call unref() if you are holding onto a reference to an object and you would call dispose() if you are not.

To create an Inti object on the heap you have to call new. The new object is created with an initial reference count of 1. If the object is a G::Object you own this initial reference and must call unref().

Gtk::AccelGroup *group = new Gtk::AccelGroup;
... // use group, e.g. pass to a Gtk::MenuItem constructor
group->unref();

Easier still, if you want to free yourself from having to call unref() you can use a smart pointer which calls unref() for you.

Pointer<Gtk::AccelGroup> group = new Gtk::AccelGroup;
... // use group. unref() is called when group goes out of scope

When you pass a Gtk::AccelGroup (or any other G::Object) to an owner, the owner will hold onto a reference to the object so you can safely call unref(). If the object is a Gtk::Object its initial reference will be a floating reference. If you pass a Gtk::Object to an owner, like Gtk::Container you don't have to call unref() because the owner will call ref() to hold onto a reference to the object and call sink() to clear the floating reference. For example:

Gtk::VBox *vbox = new Gtk::VBox;
add(*vbox);

If for some reason you want to hold onto a reference to a Gtk::Object you passed to an owner you have to call ref(), sink() and later unref(). In Inti you don't have to call sink(). Calling ref() on a Gtk::Object will automatically sink the object if it has a floating reference. If you want to hold onto a pointer to a widget you added to a container, for later use, you don't need to call ref(). It is safe to use the pointer as long as the container is valid because the container holds onto a reference to the widget and will only call unref() when the container itself is finalized.

You must call unref() on Inti objects created on the stack before they go out of scope to ensure that the wrapped C object is destroyed properly. Dialog boxes are an exception. When you create a modal dialog box by calling run() you must call dispose() to destroy the dialog box.

Gtk::MessageDialog *dialog = new Gtk::MessageDialog(Gtk::MESSAGE_INFO, Gtk::BUTTONS_CLOSE, this);
dialog->set_message("This is a message");
if (dialog->run())
    dialog->dispose();

Having said all that there are always exceptions and in Inti the exception is ReferencedObject, a reference counting base class. The purpose of ReferencedObject is to provided  the reference counting facilities required to implement automatic memory mangement for those objects that don't derive from G::Object. Examples of such objects inlcude G::Boxed, G::Date, G::Scanner, G::Timer, Gdk::Region, Gtk::TargetList and Pango::Attribute.

Like G::Objects, ReferencedObjects can either be created dynamically with operator new or declared as automatic variables on the stack. Dynamically allocated RerferencedObjects must be expilcitly unreferenced with unref(), so that delete gets called. If you don't want to be bothered calling unref() just use an Inti smart pointer and it will call unref() for you.

Unlike G::Objects, ReferencedObjects declared on the stack don't need to be unreferenced, so you don't need call unref().


 Signals and Slots

Signals and slots are a generic C++ communication framework that allows an object to broadcast an event to a any number of other objects registered to receive that event. Signals and slots are template function objects and are type-safe, flexible and easy to use. Signals specify an interface that when called emits a signal with a specific return type and argument signature. Slots are callable objects that are invoked in response to a signal. They can be created from static functions, class methods and function objects. Adding a slot to a signal's list of slots is called connectingto a signal. Calling each slot in the list is called emitting the signal. A Slot can only be connected to a signal if it has the same return type and argument signature as the signal.

There are two signal frameworks in Inti, Inti::Signal and Inti::G::Signal. The Inti::Signal framework lets you implement your own signals in any object. To use Inti::Signal you must include header file <inti/signal.h>.  

A signal can be almost anything. You would define a signal in a class declaration like this:

Inti::Signal1<void, int> button_clicked;

Signal templates specify the return type first followed by a variable number of arguments. The templates are named according to the number of arguments they take and are Signal0 to Signal6. In the above example, when button_clicked is called it emits a signal that passes one argument, an integer button identifier and expects no return type. To connect a slot to the button_clicked signal in MyWindow you would do this:

MyWindow window;
window.button_clicked.connect(slot(&window, &MyWindow::method));

You can emit the button_clicked signal by calling either the signal's emit method:

button_clicked.emit(button_id);

or the signal's function operator:

button_clicked(button_id);

Inti::G::Signal is a specialized signal framework that encapsulates the GTK+ signal system.  These signals are discussed in detail in the Getting Started subsection The theory of Signals and Slots.


 Library Organization

The Inti library is organized into namespaces. The primary namespace is the Inti namespace. Within this namespace there are several secondary namespaces named according to the GTK+ library they wrap. They are: The Main namespace wraps the GTK+ main processing loop and its associated functions and signals. There are five signals in the Main namespace: These signals don't have accessor functions because they're declared static and don't require an object pointer. You connect to these signals in the same way you would connect to any other signal except that for some signals the connect() function takes an extra argument. For example, the timeout_signal connect() method takes a slot and an unsigned integer which is the time in milliseconds that is to elapse before the slot gets called.

Inti::Gtk is a GUI toolkit that provides a complete object orientated widget collection, including all the widgets you might expect (windows, dialog boxes, buttons, notebooks, text fields and so on). It has an advanced tree and list widget with a model-view architecture. It has a feature rich text widget that supports the editing and display of Unicode text using the Pango internationalization subsystem. You can also write your own custom widgets either by subclassing the abstract Inti::Gtk::Widget or by subclassing an existing concrete widget and modifying its functionality.


 Base Class Hierarchy

The base class hierarchy in Inti looks like this:
 
Base Class Heirarchy    

The Inti namespace is assumed. For the most part you wont use MemoryHandler, ReferencedBase, G::TypeInstance or G::TypeInterface. These are abstract classes that form the very core of the Inti class hierarchy.

MemoryHandler is a dynamic memory tracking class based on Item 27 in Scott Meyers book More Effective C++. It keeps track of all heap memory allocations  and ensures that Inti only calls delete on dynamic objects.

ReferencedBase is a simple class that implements the reference counting requirements needed for the Inti smart pointer to work. Any object derived from ReferencedBase can be managed by a smart pointer.

G::TypeInstance is the base class for all objects, widgets and interfaces. Its main purpose is to connect an abstract interface to a concrete object. Many of the classes in the accessibility toolkit (ATK) are interface classes.

G::TypeInterface is the base class for all interface classes. Interfaces are a new design feature implemented in GTK+ 2.0. In Inti interface classes are abstract classes that cannot be instantiated. They must be implemented using multiple inheritance with a concrete object. An example of such a class is Gtk::Entry which inherits from Gtk::Widget, Gtk::Editable and Gtk::CellEditable. This inheritance means that Gtk::Entry isa Gtk::Widget that implements the Gtk::Editable and Gtk::CellEditable interfaces.

The other base classes are the ones that you will use.

ReferencedObject is an abstract base class that adds one feature to ReferencedBase, a reference counter. It implements the reference counting features of ReferencedBase for objects that don't have a reference counter. ReferencedObjects include G::Boxed, G::Scanner, Gdk::Region, Gtk::TargetList and Pango::Attribute.

G::Boxed is the base class for all boxed objects. Examples of boxed objects include Gdk::Color, Gdk::Cursor, G::Value, Gtk::IconSet, Gtk::TextAttributes and Pango::FontDescription. 

G::Object is the base class for all wrapped GTK+ objects and widgets and is the C++ wrapper class for GObject.

Atk::Object is the base class for objects that implement accessibility support via the Accessibility ToolKit (ATK) . An accessible object can be obtained by calling Gtk::Widget::get_accessible() for any widget class.

Gdk::Drawable is the base class for drawable objects such as Gdk::Bitmap, Gdk::Pixmap and Gdk::Window. These classes are frequently used when drawing or manipulating images.

Gtk::Object is the base class in the Inti::Gtk GUI toolkit. Examples of Gtk::Objects are Gtk::AccelGroup, Gtk::ItemFactory and Gtk::Widget, the base class for all widgets.


« Index
Top
Getting Started »