Tutorial
|
|
|
|
Introduction
- Installation
- Header Files
- Reference Counting
- Memory Management
- Signals and Slots
- Library Organization
- 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:
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:
- GTK+ 2.2.1 or greater
- pkg-config 0.14
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:
- GLib
- ATK
- Pango
- pkg-config
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:
- Atk - wraps the ATK accessibility library
- Gdk - wraps the GDK drawing kit and
GDK-PIXBUF image library
- G - wraps the GLIB utility library
- Gtk - wraps the GTK GUI toolkit
- Pango - wraps the Pango internationalized text handling
library
The
Main namespace wraps the GTK+ main processing loop and
its associated functions and signals. There are five signals in the
Main namespace:
- quit_signal
- timeout_signal
- idle_signal
- key_snooper_signal
- input_signal
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:
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.
|