As previously explained, GTK4 uses the GObject (GLib Object System) library to provide object-oriented programming (OOP) features using function-like macros. This article shows how to write a contact class so that contact object instances can be created with personal details (name, email, phone). These are then stored in a list using the GListStore class and a view widget called GtkColumnView exploited to display them. A screenshot of the GTK4 contacts application that will be developed using Ubuntu 24.04 is shown above.
Object Oriented Programming (OOP) OOP is a software design method that models the characteristics of real-world objects using software classes which have properties and methods. A class is a template or blueprint for how to build an object. Many object instances can be created from one given class. The main benefit of using the OOP approach is to make it easier to solve real-world problems by modelling natural objects as software objects. Identifying the state and behaviour for real-world objects is the starting point to begin thinking in terms of OOP. In OOP, inheritance allows a new class known as the child class or subclass to inherit properties and methods from an existing class known as the parent class which is often GObject. With GTK4, macros are used to create a new child or subclass using header (.h) and source code (.c) files. In the simplest case, the macro G_DECLARE_FINAL_TYPE() is used in the header file, and this is coupled with the macro G_DEFINE_FINAL_TYPE() in the source file.
Contact Class A contact class which inherits from GObject will be created with properties for a person name, their email address, and telephone details. In a real contact management application, there would be many more properties for items such as the person address. The full source code for this project can be downloaded using the link: https://github.com/crispinprojects/fullcircle Open and view the files person-contact.h, person-contact.c, and main.c file, to follow the explanation below. The files person-contact.h and person-contact.c are the header and source files for the contact class. Function declarations go into the header file and source code implementations go into the source code file. Functions implemented in person-contact.c can be used by other files in the project provided they are declared in the header file and are not private.
The header file has standard C header guards and then includes glib-object.h which defines GObject. The G_BEGIN_DECLS and G_END_DECLS are the begin and end macro declarations. The #define is used to associate PERSON_TYPE_CONTACT with person_contact_get_type(). The macro G_DECLARE_FINAL_TYPE is used to declare a new class (i.e. register the new class to the type system). The FINAL in the macro name means that the contact class cannot be sub-classed. The macro is used as shown below. G_DECLARE_FINAL_TYPE(PersonContact, person_contact, PERSON, CONTACT, GObject) The first argument of this macro is the child class name in camel case i.e. PersonContact. The second argument is person_contact written in lower case and with an underscore. It is used as a function prefix. The third argument, PERSON, is the namespace, and is used as the first part of the name of the class. Choosing the right namespace is important to prevent naming conflicts and provide context, and in a larger application a more specific namespace related to the application name would be used. The fourth argument, CONTACT, is the object name. The last argument, GObject, is the parent class. The naming convention that has been used in the code is because macros require names in Camel case or words separated by an underscore “_”, which may not be obvious if this is the first time of writing a GObject in C.
The next few declarations are accessor functions known as getters and setters. A getter function returns a property value while a setter function sets or updates a property value. There are three properties which are name, email and phone. Inside person_contact.c a structure is defined as shown below. struct _PersonContact { GObject parent_instance; //parent gchar* name; gchar* email; gchar* phone; }; The way in which inheritance works when using the GObject library is to put the parent type as the first field of the structure. In this case GObject is the parent instance. This makes available all the properties, methods, and signals of GObject to the subclass called PersonContact. Consequently, because PersonContact is a subclass of GObject the functions g_object_set() and g_object_get() can be used to set and get properties respectively.
The rest of the fields are used for the PersonContact properties namely name, email and phone. The name, email and phone variables are of type gchar*. Just to note that gchar is an alternate name for the existing C data type char (i.e. a typedef) and is used to be consistent with the GTK4 API documentation. The macro G_DEFINE_TYPE is used to implement a new GObject defining the basic structure for a new GObject type, including its class and instance initialisation functions. It is used to create custom object types as shown below. G_DEFINE_TYPE (PersonContact, person_contact, G_TYPE_OBJECT); The first argument is the name of the new type, in Camel case, which in this case is PersonContact. The second argument is the name of the new type, in lowercase, with words separated by an underscore “_”, which in this case is person_contact. The third argument is the GType of the parent type, which is G_TYPE_OBJECT because GObject is the parent class.
An enum is used to define the properties PROP_NAME, PROP_EMAIL and PROP_PHONE. All properties need a unique identifier and the zero property cannot be used and so a dummy property called PROP_0 is created. LAST_PROP is the number of properties. enum { PROP_0, PROP_NAME, PROP_EMAIL, PROP_PHONE, LAST_PROP }; Two constructors are required by all GObjects which are the class_init and the object init functions. In this case, these are the person_contact_class_init() and person_contact_init(). The person_contact_class_init() is the class constructor which is called only once and gets run on the first time an instance of the object is created. Inside the person_contact_class_init() constructor properties are defined using g_param_spec_string. Arguments are provided to give a property a name and range. These properties have to be accessed using the getter and setter functions. The getter and setter functions are written to get and set the name, email and phone properties. The function person_contact_dispose(GObject *object) is the destructor.
The skeletal code presented here can be used as boilerplate code for writing other GObject classes although only properties are used in this example, and no signals. The GObject API provides further information. GListStore At this stage a contact class has been written with the parent instance being GObject. By including the person-contact.h header in the main.c file, contact objects can be used. A GListStore acts as a simple array-like list that stores objects derived from GObject such as contact objects. It is a container that holds a collection of objects providing methods to append, insert, remove, find, and sort them. GListStore is a specific implementation of the more general GListModel interface which provides a standardised way to represent a list of objects for use with view widgets. View widgets such as GtkColumnView are used to display data from a model. This is known as model view design.
The code below shows how to create a GListModel with a GListStore used to store two fictitious contacts “Mr Jellyfish” and “Mr Puffin”, named after Ubuntu releases. For example, the PersonContact pointer called *contact1 is created using the g_object_new() constructor. This constructor creates a new instance of a GObject subtype using its type which in this case is PERSON_TYPE_CONTACT. The g_object_set() function sets the properties of an object and is used to set the name, email and phone properties. The g_list_store_new() function is used to create a new GListStore and then g_list_store_append() is used to add PersonContact objects to the store. The store can be thought of as holding rows of contact details which can then be displayed using a view widget.
View Widgets View widgets such as GtkColumnView display data from a model. The GtkColumnView view widget is used to display a list of items using multiple columns. In this example, a column for the name, a column for the email, and a column for the phone details, are needed. To do this, they use a “factory” pattern which abstracts the process of object creation. The factory is responsible for creating the visual representation and a factory has to be created for each column. The code snip on the next page shows how a factory is created for the “name” column. Two callbacks are used. The callback called “callbk_setup” uses the gtk_list_item_set_child() function to associate a label widget with the list view. The second callback called “callbk_bind_name” is used to determine what is displayed on the label which in this case is the name property. Use the Makefile in the download to build the application which produces an executable called “addressbook”. In the next article, the application will be expanded to manipulate contact information and save and open data.
External Links GObject API https://docs.gtk.org/gobject/ GObject Tutorial https://docs.gtk.org/gobject/tutorial.html G_DECLARE_FINAL_TYPE Macro https://docs.gtk.org/gobject/func.DECLARE_FINAL_TYPE.html G_DEFINE_FINAL_TYPE Macro https://docs.gtk.org/gobject/func.DEFINE_FINAL_TYPE.html
