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.

Comme expliqué précédemment, GTK4 utilise la bibliothèque GObject (GLib Object System) pour offrir des fonctionnalités de programmation orientée objet (POO) grâce à des macros. Cet article montre comment créer une classe Contact permettant de générer des instances d'objets Contact contenant des informations personnelles (nom, adresse e-mail, numéro de téléphone). Ces informations sont ensuite stockées dans une liste à l'aide de la classe GListStore et affichées grâce à un widget d'affichage appelé GtkColumnView.

Une capture d'écran de l'application Contacts GTK4, développée sous Ubuntu 24.04, est présentée ci-dessus.

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.

Programmation orientée objet (POO)

La POO est une méthode de conception logicielle qui modélise les caractéristiques des objets du monde réel à l'aide de classes logicielles dotées de propriétés et de méthodes. Une classe sert de modèle ou de plan pour la construction d'un objet. De nombreuses instances d'objets peuvent être créées à partir d'une même classe. Le principal avantage de la POO est de simplifier la résolution de problèmes concrets en modélisant les objets naturels comme des objets logiciels. Identifier l'état et le comportement des objets du monde réel est le point de départ de la réflexion en termes de POO.

En POO, l'héritage permet à une nouvelle classe, appelée classe enfant ou sous-classe, d'hériter des propriétés et des méthodes d'une classe existante, appelée classe parente, souvent de type GObject. Avec GTK4, des macros sont utilisées pour créer une nouvelle classe enfant ou sous-classe à l'aide de fichiers d'en-tête (.h) et de code source (.c). Dans le cas le plus simple, la macro G_DECLARE_FINAL_TYPE() est utilisée dans le fichier d'en-tête, et elle est associée à la macro G_DEFINE_FINAL_TYPE() dans le fichier source.

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.

Classe Contact

Une classe Contact, héritant de GObject, sera créée avec des propriétés pour le nom, l'adresse e-mail et le numéro de téléphone d'une personne. Dans une application réelle de gestion de contacts, de nombreuses autres propriétés seraient présentes, notamment pour l'adresse postale.

Le code source complet de ce projet est disponible au téléchargement via le lien suivant : https://github.com/crispinprojects/fullcircle

Ouvrez et consultez les fichiers person-contact.h, person-contact.c et main.c pour suivre les explications ci-dessous.

Les fichiers person-contact.h et person-contact.c sont respectivement l'en-tête et le code source de la classe Contact. Les déclarations de fonctions sont placées dans l'en-tête, tandis que leur implémentation se trouve dans le code source. Les fonctions implémentées dans person-contact.c peuvent être utilisées par d'autres fichiers du projet, à condition d'être déclarées dans l'en-tête et de ne pas être privées.

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.

Le fichier d'en-tête contient les gardes d'en-tête C standard, puis inclut glib-object.h qui définit GObject. Les macros G_BEGIN_DECLS et G_END_DECLS sont les déclarations de début et de fin. La directive #define est utilisée pour associer PERSON_TYPE_CONTACT à la fonction person_contact_get_type().

La macro G_DECLARE_FINAL_TYPE est utilisée pour déclarer une nouvelle classe (c'est-à-dire l'enregistrer auprès du système de types). Le terme FINAL dans le nom de la macro indique que la classe Contact ne peut pas être héritée. La macro s'utilise comme suit :

G_DECLARE_FINAL_TYPE(PersonContact, person_contact, PERSON, CONTACT, GObject)

Le premier argument de cette macro est le nom de la classe enfant en camelCase, soit PersonContact. Le deuxième argument est person_contact, écrit en minuscules avec un tiret bas. Il sert de préfixe à la fonction. Le troisième argument, PERSON, est l'espace de noms et constitue la première partie du nom de la classe. Choisir le bon espace de noms est important pour éviter les conflits de noms et fournir du contexte. Dans une application plus vaste, un espace de noms plus spécifique, lié au nom de l'application, sera utilisé. Le quatrième argument, CONTACT, est le nom de l'objet. Le dernier argument, GObject, est la classe parente. La convention de nommage utilisée dans le code est due au fait que les macros exigent des noms en CamelCase ou des mots séparés par un tiret bas « _ », ce qui peut ne pas être évident lors de la première utilisation d'un GObject en 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.

Les déclarations suivantes sont des fonctions d'accès appelées getters et setters. Un getter renvoie la valeur d'une propriété, tandis qu'un setter la définit ou la met à jour. Il y a trois propriétés : name, email et phone.

Dans le fichier person_contact.c, une structure est définie comme suit :

struct _PersonContact { GObject parent_instance; // parent gchar* name; gchar* email; gchar* phone; };

L'héritage, lorsqu'on utilise la bibliothèque GObject, consiste à placer le type parent comme premier champ de la structure. Ici, GObject est l'instance parente. Cela rend toutes les propriétés, méthodes et signaux de GObject accessibles à la sous-classe PersonContact. Par conséquent, puisque PersonContact est une sous-classe de GObject, les fonctions g_object_set() et g_object_get() peuvent être utilisées pour définir et obtenir respectivement les propriétés.

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.

Les champs restants sont utilisés pour les propriétés de PersonContact, à savoir nom, email et téléphone. Les variables nom, email et telephone sont de type gchar*. Il est important de noter que gchar est un synonyme du type de données C char (c'est-à-dire un typedef) et est utilisé par souci de cohérence avec la documentation de l'API GTK4.

La macro G_DEFINE_TYPE permet d'implémenter un nouvel objet GObject définissant la structure de base d'un nouveau type GObject, y compris ses fonctions d'initialisation de classe et d'instance. Elle sert à créer des types d'objets personnalisés, comme illustré ci-dessous.

G_DEFINE_TYPE(PersonContact, person_contact, G_TYPE_OBJECT);

Le premier argument est le nom du nouveau type, en notation CamelCase, ici PersonContact. Le deuxième argument est le nom du nouveau type, en minuscules, les mots étant séparés par un tiret bas « _ », ici person_contact. Le troisième argument est le GType du type parent, qui est G_TYPE_OBJECT car GObject est la classe parente.

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.

Une énumération est utilisée pour définir les propriétés PROP_NAME, PROP_EMAIL et PROP_PHONE. Chaque propriété nécessite un identifiant unique et la valeur zéro ne peut être utilisée ; une propriété factice nommée PROP_0 est donc créée. LAST_PROP représente le nombre de propriétés.

enum { PROP_0, PROP_NAME, PROP_EMAIL, PROP_PHONE, LAST_PROP };

Tous les GObjects requièrent deux constructeurs : les fonctions d'initialisation de classe et d'objet. Dans ce cas, il s'agit de person_contact_class_init() et person_contact_init(). Le constructeur person_contact_class_init() est le constructeur de classe ; il est appelé une seule fois, lors de la création de la première instance de l'objet. À l'intérieur de ce constructeur, les propriétés sont définies à l'aide de g_param_spec_string. Des arguments sont fournis pour nommer et définir la plage de valeurs de chaque propriété. Ces propriétés sont accessibles via les fonctions d'accès (getter et setter). Ces fonctions permettent d'obtenir et de modifier les propriétés « name », « email » et « phone ». La fonction `person_contact_dispose(GObject *object)` est le destructeur.

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.

Le squelette de code présenté ici peut servir de modèle pour écrire d'autres classes GObject, bien que cet exemple n'utilise que des propriétés, sans signaux. L'API de GObject fournit des informations complémentaires.

GListStore

À ce stade, une classe Contact a été écrite, son instance parente étant GObject. En incluant l'en-tête person-contact.h dans le fichier main.c, il est possible d'utiliser des objets Contact.

Un GListStore se comporte comme une simple liste, semblable à un tableau, qui stocke des objets dérivés de GObject, tels que des objets Contact. Il s'agit d'un conteneur qui gère une collection d'objets et propose des méthodes pour les ajouter, les insérer, les supprimer, les rechercher et les trier. GListStore est une implémentation spécifique de l'interface plus générale GListModel, qui fournit une méthode standardisée pour représenter une liste d'objets destinés à être utilisés avec des widgets d'affichage. Ces widgets, tels que GtkColumnView, servent à afficher les données d'un modèle. On parle alors de conception modèle-vue.

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.

Le code ci-dessous montre comment créer un GListModel avec un GListStore utilisé pour stocker deux contacts fictifs, « Mr Jellyfish » et « Mr Puffin », nommés d'après des versions d'Ubuntu. Par exemple, le pointeur PersonContact nommé *contact1 est créé à l'aide du constructeur g_object_new(). Ce constructeur crée une nouvelle instance d'un sous-type GObject en utilisant son type, ici PERSON_TYPE_CONTACT. La fonction g_object_set() définit les propriétés d'un objet et sert à définir le nom, l'adresse e-mail et le numéro de téléphone.

La fonction g_list_store_new() est utilisée pour créer un nouveau GListStore, puis g_list_store_append() est utilisée pour ajouter des objets PersonContact au magasin. Le magasin peut être considéré comme contenant des lignes de détails de contact qui peuvent ensuite être affichées à l'aide d'un widget d'affichage.

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.

Widgets d'affichage

Les widgets d'affichage, tels que GtkColumnView, permettent d'afficher les données d'un modèle. Le widget GtkColumnView sert à afficher une liste d'éléments sur plusieurs colonnes. Dans cet exemple, une colonne est nécessaire pour le nom, une pour l'adresse e-mail et une pour le numéro de téléphone. Pour ce faire, ils utilisent le modèle de conception « factory », qui simplifie la création des objets. La factory est responsable de la création de la représentation visuelle et une factory doit être créée pour chaque colonne. L'extrait de code de la page suivante montre comment une factory est créée pour la colonne « name ».

Deux fonctions de rappel sont utilisées. La première, « callbk_setup », utilise la fonction gtk_list_item_set_child() pour associer un widget Label à la vue de liste. La seconde, « callbk_bind_name », détermine le contenu affiché dans le Label, ici la propriété name.

Utilisez le Makefile fourni pour compiler l'application, qui générera un exécutable nommé « addressbook ». Dans le prochain article, l'application sera étendue pour permettre la manipulation des informations de contact, ainsi que l'enregistrement et l'ouverture de données.

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

Liens externes

API de GObject https://docs.gtk.org/gobject/

Tutoriel sur GObject https://docs.gtk.org/gobject/tutorial.html

La macro G_DECLARE_FINAL_TYPE https://docs.gtk.org/gobject/func.DECLARE_FINAL_TYPE.html

la macro G_DEFINE_FINAL_TYPE https://docs.gtk.org/gobject/func.DEFINE_FINAL_TYPE.html