# Vue d'ensemble de l'architecture de KDE

Bernd
Gehrmann

    bernd@kdevelop.org

2001
2002
Bernd Gehrmann
&FDLNotice;

Cette documentation donne une vue d'ensemble de la plate-forme de développement de KDE

KDE
architecture
développement
programmation

## Structure des bibliothèques

### Bibliothèques par nom

kdecore

  La bibliothèque kdecore est l'environnement d'applications de base de tout programme basé sur KDE. Elle fournit l'accès au système de configuration, la gestion de la ligne de commande, le chargement et la manipulation des icônes, certains types spéciaux de communication entre processus, la gestion des fichiers et divers autres utilitaires.

kdeui

  La bibliothèque `kdeui` fournit de nombreux widgets et boîtes de dialogue standard dont Qt ne dispose pas ou qui ont davantage de fonctionnalités que leurs contreparties Qt. Elle contient également plusieurs widgets qui sont sous-classés à partir de ceux de Qt et sont mieux intégrés au bureau KDE en respectant les préférences de l'utilisateur.

kio

  La bibliothèque `kio` contient des ressources pour les entrées / sorties asynchrones, transparentes vis-à-vis du réseau et un accès à la gestion des types mime. Elle fournit aussi les boîtes de dialogue des fichiers KDE et ses classes d'aide.

kjs

  La bibliothèque `kjs` fournit une implémentation de JavaScript.

khtml

  La bibliothèque `khtml` contient la partie KHTML, un widget de navigation HTML, l'API et l'analyseur DOM, y compris les interfaces à Java et JavaScript.

### Classes groupées

Squelette fondamental d'une application — classes requises par pratiquement toutes les applications.

- #### KApplication

  Initialise et contrôle une application KDE.

- #### KUniqueApplication

  Veille à ce qu'une seule instance d'une application puisse s'exécuter.

- #### KAboutData

  Contient des informations sur la zone « À propos ».

- #### KCmdLineArgs

  Traitement des arguments en ligne de commande.

Paramètres de configuration — accès à la base de données de configuration hiérarchique de KDE, aux réglages globaux et aux ressources des applications.

- #### KConfig

  Fournit l'accès à la base de données de configuration de KDE.

- #### KSimpleConfig

  Accès aux fichiers de configuration simples, non hiérarchiques.

- #### KDesktopFile

  Accès aux fichiers `.desktop`.

- #### KGlobalSettings

  Accès pratique aux réglages non spécifiques à une application.

Gestion des fichiers et des URL — décodage des URL, fichiers temporaires, &etc;

- #### KURL

  Représente et analyse les URL.

- #### KTempFile

  Crée des fichiers uniques pour les données temporaires.

- #### KSaveFile

  Permet d'enregistrer des fichiers atomiquement.

Communication entre processus — classes d'assistant DCOP et invocation des sous-processus.

- #### KProcess

  Invoque et contrôle les processus enfants.

- #### KShellProcess

  Invoque les processus enfants via un shell.

- #### PtyProcess

  Communication avec des processus enfants au moyen d'un pseudoterminal.

- #### KIPC

  Mécanisme IPC simple utilisant ClientMessages sous X11.

- #### DCOPClient

  Messagerie DCOP.

- #### KDCOPPropertyProxy

  Une classe de proxy annonçant les propriétés Qt au moyen de DCOP.

- #### KDCOPActionProxy

  Une classe de proxy annonçant une interface DCOP pour des actions.

Classes d'utilitaires — gestion de la mémoire, expressions rationnelles, manipulation des chaînes, nombres aléatoires

- #### KRegExp

  Correspondance des expressions rationnelles POSIX.

- #### KStringHandler

  Une interface luxueuse pour la manipulation des chaînes.

- #### KZoneAllocator

  Allocateur de mémoire efficace pour de grands groupes de petits objets.

- #### KRandomSequence

  Générateur de nombres pseudoaléatoires.

Accélérateurs clavier — classes aidant à établir des associations de touches cohérentes sur tout le bureau.

- #### KAccel

  Collection de raccourcis clavier.

- #### KStdAccel

  Accès aisé aux touches courantes de raccourcis clavier.

- ####

  Collection de raccourcis clavier pour l'ensemble du système.

Traitement des images — chargement et manipulation des icônes.

- #### KIconLoader

  Charge des icônes d'une manière se conformant à un thème.

- #### KIconTheme

  Classes d'aide pour KIconLoader.

- #### KPixmap

  Une classe de pixmaps avec des possibilités de tramage étendues.

- #### KPixmapEffect

  Effets de pixmaps comme les dégradés et les motifs.

- #### KPixmapIO

  Conversion rapide `QImage` en `QPixmap`.

Glisser et déposer — objets guides pour les couleurs et les URL.

- #### KURLDrag

  Un objet guide pour les URL.

- #### KColorDrag

  Un objet guide pour les couleurs.

- #### KMultipleDrag

  Permet de construire des objets guides à partir de plusieurs autres.

Complétement automatique

- #### KCompletion

  Complétement automatique générique des chaînes.

- #### KURLCompletion

  Complétement automatique des URL.

- #### KShellCompletion

  Complétement automatique des exécutables.

Widgets — classes de widgets pour le mode liste, les règles, le choix des couleurs, &etc;

- #### KListView

  Une variante de `QListView` qui fait honneur aux réglages de KDE sur l'ensemble du système.

- #### KListBox

  Une variante de `QListBox` qui fait honneur aux réglages de KDE sur l'ensemble du système.

- #### KIconView

  Une variante de `QIconView` qui fait honneur aux réglages de KDE sur l'ensemble du système.

- #### KLineEdit

  Une variante de `QLineEdit` avec la prise en charge du complétement.

- #### KComboBox

  Une variante de `QComboBox` avec la prise en charge du complétement.

- #### KFontCombo

  Une zone de liste modifiable pour sélectionner les polices.

- #### KColorCombo

  Une zone de liste modifiable pour sélectionner les couleurs.

- #### KColorButton

  Un bouton pour sélectionner les couleurs.

- #### KURLCombo

  Une zone de liste modifiable pour sélectionner les noms de fichiers et les URL.

- #### KURLRequester

  Une édition de lignes pour sélectionner les noms de fichiers et les URL.

- #### KRuler

  Un widget règle.

- #### KAnimWidget

  Des animations.

- #### KNumInput

  Un widget pour saisir les nombres.

- #### KPasswordEdit

  Un widget pour saisir les mots de passe.

Boîtes de dialogue — boîtes de dialogue complètes pour la sélection des fichiers, des couleurs et des polices.

- #### KFileDialog

  Une boîte de dialogue de sélection des fichiers.

- #### KColorDialog

  Une boîte de dialogue de sélection des couleurs.

- #### KFontDialog

  Une boîte de dialogue pour la sélection des polices.

- #### KIconDialog

  Une boîte de dialogue pour la sélection des icônes.

- #### KKeyDialog

  Une boîte de dialogue pour l'édition des associations de touches clavier.

- #### KEditToolBar

  Une boîte de dialogue pour l'édition des barres d'outils.

- #### KTipDialog

  Une boîte de dialogue « Astuce du jour ».

- #### KAboutDialog

  Une boîte de dialogue « À propos ».

- #### KLineEditDlg

  Une boîte de dialogue simple pour saisir du texte.

- #### KURLRequesterDlg

  Une boîte de dialogue simple pour saisir les URL.

- #### KMessageBox

  Une boîte de dialogue pour signaler les erreurs et les avertissements.

- #### KPasswordDialog

  Une boîte de dialogue pour saisir les mots de passe.

Actions et interface graphique XML

- #### KAction

  Abstraction pour une action qui peut être intégrée dans les barres de menus et les barres d'outils.

- #### KActionCollection

  Un ensemble d'actions.

- #### KXMLGUIClient

  Un fragment d'interface graphique se composant d'une collection d'actions et d'une arborescence DOM représentant leur emplacement dans l'interface graphique.

- #### KPartManager

  Gère l'activation des clients XMLGUI.

Modules externes (plugins) et composants

- #### KLibrary

  Représente une bibliothèque chargée dynamiquement.

- #### KLibLoader

  Chargement d'une bibliothèque partagée.

- #### KLibFactory

  Fabrique d'objets dans des modules externes.

- #### KServiceType

  Représente un type de service.

- #### KService

  Représente un service.

- #### KMimeType

  Représente un type MIME.

- #### KServiceTypeProfile

  Préférences utilisateur pour la mise en correspondance des types MIME.

- #### KTrader

  Requêtes de services.

## Graphiques

### Graphiques bas niveau avec QPainter

#### Rendu avec QPainter

Le modèle d'imagerie bas niveau de Qt est basé sur les possibilités fournies par X11 et d'autres systèmes de fenêtrage pour lesquels des ports Qt existent. Mais il les étend également en implémentant des fonctionnalités additionnelles telles que les transformations affines arbitraires pour le texte et les pixmaps.

La classe graphique centrale pour le dessin en 2D avec Qt est QPainter. Elle peut dessiner sur un QPaintDevice. Il y a trois périphériques de dessin possibles implémentés : l'un est QWidget qui représente un widget sur l'écran. Le deuxième est QPrinter qui représente une imprimante et produit une sortie Postscript. Le troisième est la classe QPicture qui enregistre les commandes de dessin, peut les enregistrer sur disque et les lire plus tard. Un format possible de stockage pour le dessin est le standard SVG du W3C.

Il est donc possible de réutiliser le code de rendu pour afficher un widget pour l'impression, avec les mêmes fonctionnalités prises en charge. Bien sûr, en pratique, le code est utilisé dans un contexte légèrement différent. Le dessin sur un widget est presque exclusivement exécuté dans la méthode paintEvent() d'une classe de widget.

    void FooWidget::paintEvent()
    {
        QPainter p(this);
        // Configurer le pinceau
        // Utiliser le pinceau
    }

En dessinant sur une imprimante, vous devez veiller à employer QPrinter::newPage() pour terminer une page et en commencer une nouvelle — chose qui naturellement n'a rien de pertinent lorsqu'il s'agit de dessiner des widgets. De plus, au moment de l'impression vous pouvez être amené à employer la métrique du périphérique afin d'en calculer les coordonnées.

#### Transformations

Lorsqu'on se sert de QPainter, ce dernier trace par défaut le système de coordonnées naturel du périphérique utilisé. Cela signifie que si vous dessinez le long de l'axe horizontal une ligne d'une longueur de 10 unités, elle sera tracée sur l'écran comme une ligne horizontale d'une longueur de 10 pixels. Cependant, QPainter peut appliquer des transformations affines arbitraires avant de véritablement rendre les formes et les courbes. Une transformation affine met en correspondance les coordonnées x et y linéairement en x' et y' en conséquence

La matrice 3x3 dans cette équation peut être définie avec QPainter::setWorldMatrix() et elle est de type QWMatrix. Normalement, il s'agit de la matrice identité, &cad; que m11 et m22 sont égales à un, et les autres paramètres sont nuls. Il y a essentiellement trois groupes différents de transformations :

- ##### Translations

  Celles-ci déplacent tous les points d'un objet d'une quantité fixe dans une certaine direction. Une matrice de translation peut être obtenue en appelant la méthode m.translate(dx, dy) pour une QWMatrix. Ceci correspond à la matrice

- ##### Changement d'échelle

  Celle-ci étire ou rétrécit les coordonnées d'un objet, en le rendant plus gros ou plus petit sans le distordre. Une transformation de changement d'échelle peut être appliquée à une QWMatrix en appelant m.scale(sx, sy). Ceci correspond à la matrice

  En attribuant à l'un de ces paramètres une valeur négative, on peut réaliser une mise en miroir du système de coordonnées.

- ##### Glissement

  Une distorsion du système de coordonnées avec deux paramètres. Une transformation de glissement peut être appliquée en appelant m.shear(sh, sv), correspondant à la matrice

- ##### Rotation

  Celle-ci fait tourner un objet. Une transformation de rotation peut être appliquée en appelant m.rotate(alpha). Notez que l'angle doit être indiqué en degrés, non sous un angle mathématique ! La matrice correspondante est

  Notez qu'une rotation est équivalente à une combinaison de changement d'échelle et de glissement.

Voici quelques images qui montre l'effet de la transformation élémentaire de notre mascotte :

a) Normale | b) Après rotation de 30 degrés | c) Après glissement de 0.4 | d) Mise en miroir

Les transformations peuvent être combinées en multipliant les matrices élémentaires. Notez que les opérations sur les matrices ne sont pas commutatives en général et, par conséquent, l'effet combiné d'une concaténation dépend de l'ordre dans lequel les matrices sont multipliées.

#### Définition des attributs de frappe

Le rendu des lignes, courbes et contours des polygones peut être modifié en définissant un crayon spécial avec QPainter::setPen(). L'argument de cette fonction est un objet QPen. Les propriétés qui y sont enregistrées sont un style, une couleur, un style de jointure et un style de capuchon.

Le style de crayon est un membre de l'énumération Qt::PenStyle, et peut prendre une des valeurs suivantes :

Le style de jointure est un membre de l'énumération Qt::PenJoinStyle. Il spécifie comment est tracée la jonction entre des lignes mutliples qui sont reliées l'une à l'autre. Il peut prendre une des valeurs suivantes :

a) MiterJoin (jointure en onglet) | c) BevelJoin (jointure biseautée) | b) RoundJoin (jointure arrondie)

Le style de capuchon est un membre de l'énumération Qt::PenCapStyle et spécifie comment sont tracés les points finaux des lignes. Il prend l'une des valeurs provenant du tableau suivant :

a) FlatCap (capuchon plat) | b) SquareCap (capuchon carré) | c) RoundCap (capuchon rond)

#### Définition des attributs de remplissage

Le style de remplissage des polygones, cercles ou rectangles peut être modifié en définissant une brosse spéciale avec QPainter::setBrush(). Cette fonction prend un objet QBrush comme argument. Les brosses peuvent être construites de quatre manières différentes :

- QBrush::QBrush() — crée une brosse qui ne remplit pas les formes.

- QBrush::QBrush(BrushStyle) — crée une brosse noire avec un des motifs par défaut illustrés ci-dessous.

- QBrush::QBrush(const QColor &, BrushStyle) — crée une brosse colorée avec un des motifs illustrés ci-dessous.

- QBrush::QBrush(const QColor &, const QPixmap) — crée une brosse colorée avec le motif personnalisé que vous indiquez comme second paramètre.

Un style de brosse par défaut est un membre de l'énumération Qt::BrushStyle. Voici une illustration de tous les motifs prédéfinis :

Une autre manière de personnaliser le comportement de la brosse est d'utiliser la fonction QPainter::setBrushOrigin().

#### Couleur

Les couleurs jouent un rôle à la fois lors de la frappe des courbes et lors du remplissage des formes. Dans Qt, les couleurs sont représentées par la classe QColor. Qt ne prend en charge aucune fonctionnalité graphique avancée comme les profils de couleur ICC (International Color Consortium) et la correction des couleurs. Les couleurs sont habituellement construites en spécifiant leurs composantes rouge, verte et bleue, puisque le modèle RVB est la manière dont sont composés les pixels sur un moniteur.

Il est également possible d'utiliser une teinte, une saturation et une valeur. Cette représentation HSV est ce dont vous vous servez dans la boîte des couleurs Gtk, &pex; dans Le GIMP. Ici, la teinte correspond à l'angle sur la roue de couleurs, alors que la saturation correspond à la distance depuis le centre du cercle. La valeur peut être choisie avec un curseur séparé.

#### Autres paramètres

Normalement, lorsque vous peignez sur un périphérique de peinture, les pixels que vous dessinez remplacent ceux qui s'y trouvaient auparavant. Cela signifie que lorsque vous peignez une certaine région avec une couleur rouge et que vous peignez la même région avec une couleur verte par la suite, seule la couleur bleue sera visible. Le modèle d'imagerie de Qt ne prend pas en charge la transparence, &cad; une manière de fondre l'avant-plan peint avec l'arrière-plan. Cependant, il y a un moyen simple de combiner arrière-plan et avant-plan avec des opérateurs booléens. La méthode QPainter::setRasterOp() définit l'opérateur utilisé, qui provient de l'énumération RasterOp.

La valeur par défaut est CopyROP, qui ignore l'arrière-plan. Un autre choix courant est XorROP. Si vous tracez une ligne noire avec cet opérateur sur une image colorée, la zone couverte est alors inversée. Cet effet est par exemple permet de créer les sélections d'étirement dans les programmes de manipulation d'image connus sous l'expression « fourmis en marche ».

#### Traçage des primitives graphiques

Dans ce qui suit, nous répertorions les éléments graphiques élémentaires que gère QPainter. La plupart d'entre eux existe en plusieurs versions saturées qui prennent un nombre différent d'arguments. Par exemple, les méthodes qui portent sur les rectangles prennent soit un QRect comme argument, soit un ensemble de quatre entiers.

- Traçage d'un seul point — drawPoint().

- Traçage des lignes — drawLine(), drawLineSegments() et drawPolyLine().

- Traçage et remplissage des rectangles — drawRect(), drawRoundRect(), fillRect() et eraseRect().

- Traçage et remplissage des cercles, des ellipses et de parties de ceux-ci — drawEllipse(), drawArc(), drawPie et drawChord().

- Traçage et remplissage des polygones en général — drawPolygon().

- Traçage des courbes de Bézier — drawQuadBezier() [drawCubicBezier dans Qt 3.0].

#### Traçage des pixmaps et des images

Qt fournit deux classes très différentes pour représenter les images.

QPixmap corrrespond directement aux objets pixmaps dans X11. Les pixmaps sont des objets côté serveur et peuvent — sur une carte graphique moderne — même être enregistrés directement dans la mémoire de la carte. Ce comportement la rend très efficace pour transférer les pixmaps à l'écran. Les pixmaps agissent aussi comme l'équivalent d'un hors-écran de widgets — la classe QPixmap étant une sous-classe de QPaintDevice, vous pouvez dessiner dessus avec un QPainter. Les opérations de dessin élémentaires sont habituellement accélérées par les cartes graphiques modernes. Par conséquent, un motif d'usage courant est d'utiliser les pixmaps pour le double tamponnement. Cela signifie que, au lieu de peindre directement sur un widget, vous peignez sur un objet pixmap temporaire et que vous employez la fonction bitBlt pour transférer le pixmap au widget. Pour des retraçages complexes, cette astuce permet d'éviter le papillottement.

En revanche, les objets QImage résident côté client. Ils se distinguent en fournissant un accès direct aux pixels de l'image. Ce comportement explique leur utilisation dans la manipulation des images, ainsi que les tâches comme le chargement et l'enregistrement sur disque (la méthode load() de QPixmap considère QImage comme une étape intermédiaire). Par ailleurs, le traçage d'une image sur un widget est une opération relativement coûteuse car elle implique un transfert vers le serveur X qui peut prendre du temps, en particulier pour les images de grandes dimensions et pour les serveurs distants. En fonction de la profondeur de couleur, la conversion de QImage en QPixmap peut aussi exiger un tramage.

#### Traçage du texte

Le texte peut être tracé avec une des variantes saturées de la méthode QPainter::drawText(). Celles-ci dessinent une QString soit à un point donné, soit dans un rectangle donné, en utilisant la police définie par QPainter::setFont(). Il y a également un paramètre qui prend une combinaison OU exclusif de certaines drapeaux à partir des énumérations Qt::AlignmentFlags et Qt::TextFlags

En commençant par la version 3.0, Qt tient compte de la disposition complète du texte, même pour les langues qui s'écrivent de droite à gauche.

Une manière plus sophistiquée d'afficher du texte marqué est la classe QSimpleRichText. Les objets de cette classe peuvent être construits avec un élément de texte à l'aide d'un sous-ensemble des marqueurs HTML, qui est assez riche et fournit même des tableaux. Le style du texte peut être personnalisé par l'emploi d'une QStyleSheet (la documentation des marqueurs se trouve également ici). Une fois l'objet texte enrichi construit, il peut être rendu sur un widget ou un autre périphérique de traçage avec la méthode QSimpleRichText::draw().

### Graphiques structurés avec QCanvas

QPainter offre un modèle d'imagerie puissant pour peindre sur les widgets et les pixmaps. Toutefois, son utilisation peut être fastidieuse. Chaque fois que votre widget reçoit un événement peinture, il doit analyser la QPaintEvent::region() ou la QPaintEvent::rect() qui doit être redessinée. Puis il lui faut configurer un QPainter et peindre tous les objets qui se chevauchent sur cette région. Imaginez par exemple un programme de dessin vectoriel qui permet de faire glisser des objets comme les polygones, les cercles et les groupes de ceux-ci tout autour. Chaque fois que ces objets se déplacent un peu, l'événement souris du widget déclenche un événement peinture pour l'ensemble de la zone couverte par les objets dans leur ancienne position et dans leur nouvelle position. La découverte des retraçages nécessaires et leur exécution d'une manière efficace peut être difficile et peut entraîner un conflit avec la structure orientée objet du code source du programme.

À titre d'alternative, Qt contient la classe QCanvas dans laquelle vous placerez des objets graphiques comme les polygones, le texte, les pixmaps. Vous pouvez également fournir des éléments additionnels en sous-classant QCanvasItem ou une de ses sous-classes plus spécialisées. Un canevas peut être représenté sur l'écran par un ou plusieurs widgets de la classe QCanvasView que vous devez sous-classer afin de gérer les interactions utilisateur. Qt tient compte de tous les retraçages des objets de la vue, si elles sont occasionnées par le widget exposé, les nouveaux objets créés ou modifiés, voire d'autres choses. En utilisant le double tamponnement, ceci peut être effectué d'une manière efficace et sans papillotement.

Les éléments du canevas peuvent se chevaucher les uns les autres. Dans ce cas, celui qui est visible dépend de l'ordre que QCanvasItem::setZ() peut affecter. Les éléments peuvent aussi être rendus visibles ou invisibles. Vous pouvez également fournir un arrière-plan à dessiner « derrière » tous les éléments et un avant-plan. Pour associer des événements souris à des objets, dans le canevas, il y a la méthode QCanvas::collisions() qui retourne une liste des éléments se chevauchant à un point donné. Voici une capture d'écran d'une vue du canevas en action :

Ici, le maillage est dessiné en arrière-plan. De plus, il y a un élément QCanvasText et un QCanvasPolygon violet. Le papillon est un QCanvasPixmap. Il a des zones transparentes de façon à ce que vous puissiez voir les éléments sous-jacents à travers lui.

Un didactitiel sur l'utilisation de QCanvas pour écrire des jeux basés sur des objets images se trouve ici.

### Graphiques en 3D avec OpenGL

#### Interface bas niveau

Le standard de facto pour le rendu des graphiques en 3D aujourd'hui est OpenGL. Les implémentations de ces spécifications sont présentes dans Microsoft Windows, Mac OS X, XFree86 et gèrent souvent les fonctionnalités d'accélération matérielle qu'offrent les cartes graphiques modernes. OpenGL lui-même ne se consacre qu'au rendu sur une zone spécifiée du tampon de trame grâce à un contexte GL et n'a aucune interaction avec la boîte à outils de l'environnement

Qt offre le widget QGLWidget qui encapsule une fenêtre avec un contexte GL associé. Vous l'utiliserez essentiellement en le sous-classant et en réimplémentant certaines méthodes.

- Au lieu de réimplémenter paintEvent() et d'utiliser QPainter pour dessiner le contenu du widget, annulez paintGL() et utilisez les commandes GL pour rendre une scène. QLWidget prendra soin de faire de son contexte GL le contexte actuel avant que paintGL() ne soit appelé, et il l'éliminera par la suite.

- La méthode virtuelle initializeGL() est appelée immédiatement avant la première fois où resizeGL() ou paintGL() est appelées. Elle peut servir à construire des listes d'affichage pour des objets et procéder à quelques initialisations.

- Au lieu de réimplémenter resizeEvent(), annulez resizeGL(). Cette dernière peut servir pour définir la fenêtre d'affichage de manière appropriée.

- Au lieu d'appeler update() quand l'état de la scène a changé — &pex; quand vous l'animez avec un minuteur — appelez updateGL(). Cette action déclenchera un retraçage.

En général, QGLWidget se comporte tout comme n'importe quel autre widget, &cad; &pex; que vous pouvez traiter les événements souris comme d'habitude, redimensionner le widget et le combiner avec d'autres dans une topologie.

Qt contient quelques exemples de l'utilisation de QGLWidget dans son exemple `démo`. Vous trouverez ici un ensemble de didactitiels ; d'autres informations et une référence d'OpenGL sont disponibles sur la page d'accueil d'OpenGL.

#### Interfaces haut niveau

OpenGL est une interface assez bas niveau pour dessiner des graphiques en 3D. De la même manière que QCanvas donne au programmeur une interface de plus haut niveau avec des détails, des objets et leurs propriétés, il y a également des interfaces haut niveau pour les graphiques en 3D. Une des interfaces les plus connues est Open Inventor. Technologie à l'origine développée par SGI, il existe aujourd'hui l'implémentation open source Coin, complétée par l'association d'une boîte à outils à Qt appelée SoQt.

Le concept fondamental d'Open Inventor est celui d'une scène. Une scène peut être chargée depuis un disque et enregistrée dans un format spécial étroitement lié à VRML. Une scène consiste en une collection d'objets appelés nœuds. Inventor fournit déjà une riche collection de nœuds réutilisables, tels que des cubes, des cylindres et des mailles, en plus de sources lumineuses, de matériaux, de caméras, &etc; Les nœuds sont représentés par des classes C++ et peuvent être combinés et sous-classés.

Vous trouverez une introduction à Inventor ici (en général, vous pouvez substituer toutes les mentions de SoXt par SoQt dans cet article).

## Interface utilisateur

### Le motif de l'action

### Définition des menus et des barres d'outils dans XML

#### Introduction

Alors que le motif des actions permet d'encapsuler les actions déclenchées par l'utilisateur dans un objet qui peut être « enfiché » quelque part dans les barres de menus ou les barres d'outils, il ne résoud pas par lui-même le problème de la construction des menus proprement dits. En particulier, vous aurez à construire tous les menus contextuels en code C++ et à insérer explicitement les actions dans un certain ordre, à l'étude du guide de style pour les actions standard. Ceci complique la tâche de l'utilisateur pour personnaliser les menus ou changer les raccourcis pour les adapter à ses besoins, sans modifier le code source.

Ce problème est résolu par un ensemble de classes appelé `XMLGUI`. En susbtance, celui-ci sépare les actions (codées en C++) de leur apparance dans les barres de menus et les barres d'outils (codées en XML). Sans modifier aucun code source, les menus peuvent être simplement personnalisés en ajustant un fichier XML. En outre, il permet de s'assurer que les actions standard (telles que Fichier Ouvrir ou Aide À propos de) apparaissent dans les endroits suggérés par le guide de style. XMLGUI est particulièrement important pour les programmes modulaires, dans lesquels les éléments apparaissant dans la barre de menus peuvent provenir de nombreux modules externes (plugins) ou parties différents.

La classe de KDE pour les fenêtres de premier niveau, KMainWindow, hérite de KXMLGUIClient et gère donc XMLGUI en dehors de l'ordinateur. Toutes les actions créées en son sein doivent avoir la `actionCollection()` du client comme parent. Un appel à `createGUI()` construira ensuite l'ensemble complet des barres de menus et d'outils défini dans le fichier XML des applications (par convention, avec le suffixe `ui.rc`).

#### Un exemple : un menu dans KView

Dans ce qui suit, nous prenons l'afficheur d'images KView de KDE à titre d'exemple. Il a un fichier `ui.rc` nommé `kviewui.rc` qui est installé avec le fragment `Makefile.am`

    rcdir = $(kde_datadir)/kview
    rc_DATA = kviewui.rc

Voici un extrait du fichier `kviewui.rc`. Pour la simplicité, nous n'afficherons que la définition du menu Affichage.

    <!DOCTYPE kpartgui>
    <kpartgui name="kview">
      <MenuBar>
        <Menu name="affichage" >
          <Action name="zoom50" />
          <Action name="zoom100" />
          <Action name="zoom200" />
          <Action name="zoomMaxpect" />
          <Separator/>
          <Action name="plein écran" />
        </Menu>
      </MenuBar>
    </kpartgui>

La partie correspondante de la configuration en C++ est :

    KStdAction::zoomIn    ( this, SLOT(slotZoomIn()), actionCollection() );
      KStdAction::zoomOut   ( this, SLOT(slotZoomOut()), actionCollection() );
      KStdAction::zoom      ( this, SLOT(slotZoom()), actionCollection() );
      new KAction           ( i18n("&Half size"), ALT+Key_0,
                              this, SLOT(slotHalfSize()),
                              actionCollection(), "zoom50" );
      new KAction           ( i18n("&Normal size"), ALT+Key_1,
                              this, SLOT(slotDoubleSize()),
                              actionCollection(), "zoom100" );
      new KAction           ( i18n("&Double size"), ALT+Key_2,
                              this, SLOT(slotDoubleSize()),
                              actionCollection(), "zoom200" );
      new KAction           ( i18n("&Fill Screen"), ALT+Key_3,
                              this, SLOT(slotFillScreen()),
                              actionCollection(), "zoomMaxpect" );
      new KAction           ( i18n("Fullscreen &Mode"), CTRL+SHIFT+Key_F,
                              this, SLOT(slotFullScreen()),
                              actionCollection(), "fullscreen" );

Le menu Affichage résultant de la définition de cette interface graphique ressemble à celle de cette capture d'écran :

Le fichier XML commence par une déclaration de type de document. La DTD pour kpartgui se trouve dans les sources kdelibs dans `kdeui/kpartgui.dtd`. L'élément le plus externe du fichier contient le nom d'instance de l'application comme attribut. Il peut également contenir un numéro de version de la forme « version=2 ». Ce détail est utile lorsque vous diffusez de nouvelles versions d'une application avec une structure de menu modifiée, &pex; avec davantage de fonctionnalités. Si vous gonflez le numéro de version du fichier `ui.rc`, KDE s'assure que toute version personnalisée du fichier est éliminée et que le nouveau fichier est utilisé à la place.

La ligne suivante, `<MenuBar>`, contient une déclaration d'une barre de menus. Vous pouvez aussi insérer n'importe quel nombre de déclarations `<ToolBar>` afin de créer quelques barres d'outils. Le menu contient un sous-menu avec le nom « affichage ». Ce nom est déjà prédéfini et ainsi, vous voyez une version traduite du mot « View » dans la capture d'écran. Si vous déclarez vos propres sous-menus, ajoutez le titre explicitement. Par exemple, KView a un sous-menu avec le titre « Image » déclaré comme suit :

    <Menu name="image" >
       <text>&amp;Image</text>
       ...
    </Menu>

Dans l'environnement automake de KDE, de tels titres sont automatiquement extraits et placés dans le fichier `.po` de l'application pour que les traducteurs puissent le traiter. Notez que vous devez écrire le marqueur d'accélérateur « & » sous la forme conforme à XML, « &amp; ».

Revenons à notre exemple. Le menu Affichage de KView contient quelques actions personnalisées : `zoom50`, `zoom100`, `zoom200`, `zoomMaxpect` et `fullscreen`, déclarées avec un élément `<Action>`. Le séparateur dans les captures d'écran correspond à l'élément `<Separator>`.

Vous noterez que certains éléments de menus n'ont pas d'élément correspondant dans le fichier XML. Ce sont des actions standard. Les actions standard sont créées par la classe KStdAction. Quand vous créez de telles actions dans votre application (comme dans l'exemple C++ ci-dessus), elles sont automatiquement insérées dans une position imposée et éventuellement avec une icône et une touche de raccourci. Consultez ces emplacements dans le fichier `kdeui/ui_standards.rc`, dans les sources kdelibs.

#### Un exemple : les barres d'outils dans Konqueror

Pour l'étude des barres d'outils, passons à la définition de l'interface graphique de Konqueror. Cet extrait définit la barre d'URL qui contient le champ de saisie des URL.

    <ToolBar name="locationToolBar" fullWidth="true" newline="true" >
      <text>Location Toolbar</text>
      <Action name="clear_location" />
      <Action name="location_label" />
      <Action name="toolbar_url_combo" />
      <Action name="go_url" />
    </ToolBar>

La première chose que nous remarquons est qu'il y a beaucoup plus d'attributs que pour les barres de menus. Celles-ci comprennent :

- `fullWidth` : indique à XMLGUI que la barre d'outils a la même largeur que la fenêtre de premier niveau. Si celle-ci est « false », la barre d'outils prend seulement l'espace nécessaire et les autres barres d'outils sont placées sur la même ligne.

- `newline` : ceci est en rapport avec l'option ci-dessus. Si « newline » est « true », la barre d'outils commence une nouvelle ligne. Sinon elle peut être placée dans la même ligne, associée à la barre d'outils précédente.

- `noEdit` : normalement, les barres d'outils peuvent être personnalisées par l'utilisateur, &pex; dans Configuration Configurer les barres d'outils dans Konqueror. Le fait de définir cette option à « true » marque cette barre d'outils comme non modifiable. Ceci est important pour les barres d'outils qui sont remplis d'éléments au moment de l'exécution, &pex; la barre d'outils des signets de Konqueror.

- `iconText` : indique à XMLGUI d'afficher le texte de l'action qui suit l'icône. Normalement, le texte n'est affiché que sous la forme d'une bulle d'aide lorsque le curseur de la souris reste sur l'icône un instant. Des valeurs possibles pour cet attribut sont « icononly » (affiche uniquement l'icône), « textonly » (affiche uniquement le texte), « icontextright » (affiche le texte sur le côté droit de l'icône) et « icontextbottom » (affiche le texte au-dessous de l'icône).

- `hidden` : si ceci est « true », la barre d'outils n'est pas visible initialement et doit être activée par un élément de menu.

- `position` : la valeur par défaut pour cet attribut est « top », ce qui signifie que la barre d'outils est positionnée sous la barre de menus. Pour les programmes dotés de nombreux outils, comme les programmes graphiques, il peut être intéressant de remplacer ceci par « left », « right » ou « bottom ».

#### Menus dynamiques

À l'évidence, un fichier XML ne peut contenir qu'une description statique d'une interface utilisateur. Souvent, il y a des menus qui changent au moment de l'exécution. Par exemple, le menu Document de Konqueror contient un ensemble d'éléments Ouvrir avec quelque chose avec les applications capables de charger un fichier avec un type MIME donné. Chaque fois que le document affiché change, la liste des éléments du menu est mise à jour. XMLGUI est préparée à gérer ce type de cas avec la notion de listes d'actions. Une liste d'actions est déclarée comme un seul élément dans le fichier XML mais se compose de plusieurs actions qui sont « enfichées » dans le menu au moment de l'exécution. L'exemple ci-dessus est mis en œuvre avec la déclaration suivante dans le fichier XML de Konqueror :

    <Menu name="filchier">
      <text>&amp;Location</text>
      ...
      <ActionList name="openwith">
      ...
    </Menu>

La fonction `KXMLGUIClient::plugActionList()` est alors utilisée pour ajouter des actions à afficher, alors que la fonction `KXMLGuiClient::unplugActionList()` supprime toutes les fonctions « enfichées ». Voici à quoi ressemble la routine responsable de la mise à jour :

    void MainWindow::updateOpenWithActions()
    {
        unplugActionList("openwith");
        openWithActions.clear();
        for ( /* itérer sur les services pertinents */ ) {
            KAction *action = new KAction( ...);
            openWithActions.append(action);
        }
        plugActionList("openwith", openWithActions);
    }

Notez que, contrairement aux actions statiques, celles qui sont créées ici ne sont pas construites avec la collection des actions comme parent et que vous êtes responsable de leur suppression en propre. La manière la plus souple pour ce faire est d'utiliser `openWithActions.setAutoDelete(true)` dans l'exemple ci-dessus.

#### Menus contextuels

Les exemples ci-dessus ne contenaient que des cas où étaient créées une barre de menus et des barres d'outils d'une fenêtre principale. Ici, les processus de construction de ces conteneurs vous sont complètement cachés derrière l'appel `createGUI()` (sauf si vous avez des conteneurs personnalisés). Toutefois, il y a des cas où vous serez amené à construire d'autres conteneurs et à les doter de définitions d'interfaces graphiques provenant du fichier XML. Les menus contextuels en sont un exemple. Afin d'obtenir un pointeur vers un menu contextuel, vous devez le demander à la fabrique du client :

    void MainWindow::popupRequested()
    {
        QWidget *w = factory()->container("context_popup", this);
        QPopupMenu *popup = static_cast<QPopupMenu *>(w);
        popup->exec(QCursor::pos());
    }

La méthode `KXMLGUIFactory::container()` utilisée précédemment examine le fichier XML pour savoir si elle y trouve un conteneur du nom donné. Ainsi, voici à quoi pourrait ressembler une définition possible :

    ...
    <Menu name="context_popup">
      <Action name="file_add"/>
      <Action name="file_remove"/>
    </Menu>
    ...

### Mise à disposition d'une aide en ligne

Le fait de rendre un programme facile et intuitif à utiliser implique une large palette de fonctions habituellement appelées « aide en ligne ». L'aide en ligne a plusieurs buts, partiellement en conflit : d'une part, elle doit fournir à l'utilisateur des réponses à la question « Comment puis-je effectuer telle ou telle tâche ? », de l'autre, elle doit l'aider à explorer l'application et à trouver les fonctionnalités qu'il ne connaît pas encore. Il est important de reconnaître que cet objectif peut être atteint en offrant plusieurs niveaux d'aide :

- Les bulles d'aide sont de petites étiquettes qui apparaissent sur les éléments de l'interface utilisateur quand la souris y reste quelques secondes. Elles sont particulièrement importantes pour les barres d'outils où les icônes ne sont pas toujours suffisantes pour expliquer la finalité d'un bouton.

- L'aide « Qu'est-ce que c'est ? » est souvent une explication plus longue et plus fournie d'un widget ou d'un élément de menu. Elle est également plus incertaine à utiliser. Dans les boîtes de dialogue, on peut l'invoquer de deux façons : soit en appuyant sur MajF1, soit en cliquant sur le point d'interrogation dans la barre de titre (où la prise en charge de cette dernière dépend du gestionnaire de fenêtres). Le pointeur de la souris se transforme alors en point d'interrogation et la fenêtre d'aide apparaît quand on clique sur un élément de l'interface utilisateur. L'aide « Qu'est-ce que c'est ? » est d'ordinaire activée dans la barre d'outils par un bouton contenant une flèche et un point d'interrogation.

- Le problème avec cette approche est que l'utilisateur ne peut pas voir si un widget fournit de l'aide ou non. Lorsqu'il active le bouton en forme de point d'interrogation et qu'il n'obtient aucune fenêtre d'aide en cliquant un élément de l'interface utilisateur, il est très rapidement frustré.

  L'avantage des fenêtres d'aide « Qu'est-ce que c'est ? » telles qu'elles sont fournies par Qt et KDE est qu'elles peuvent contenir du texte enrichi, &cad; différentes polices, du texte en gras et en italique, voire des images et des tableaux.

  Un exemple de l'aide « Qu'est-ce que c'est ? » :

- Pour finir, tous les programmes devraient avoir un manuel. Un manuel s'affiche en principe dans KHelpCenter en activant le menu Aide. Cela signifie qu'une application supplémentaire complète apparaît et distrait l'utilisateur de son travail. En conséquence, la consultation du manuel ne devrait être nécessaire que si d'autres fonctions comme les bulles d'aide et l'aide « Qu'est-ce que c'est ? » ne sont pas suffisantes. Naturellement un manuel a l'avantage de ne pas décrire un seul aspect isolé de l'application dans un contexte plus spacieux. Les manuels de KDE sont écrits à l'aide du langage de marquage DocBook.

Du point de vue du programmeur, Qt fournit un moyen aisé d'utiliser l'API pour l'aide en ligne. Pour affecter une bulle d'aide à un widget, faites appel à la classe QToolTip.

    QToolTip::add(w, i18n("Ce widget ne fait rien"))

Si les barres de menus et les barres d'outils sont créées à l'aide du motif d'actions, la chaîne employée comme bulle d'aide est dérivée du premier argument du constructeur KAction :

    action = new KAction(i18n("&Delete"), "editdelete",
                         SHIFT+Key_Delete, actionCollection(), "del")

Ici, il est également possible d'affecter un texte qui est affiché dans la barre d'état lorsque l'élément du menu respectif est mis en évidence :

    action->setStatusText(i18n("Supprime le fichier marqué"))

L'API pour l'aide « Qu'est-ce que c'est ? » est très similaire. Dans les boîtes de dialogue, utilisez le code suivant :

    QWhatsThis::add(w, i18n("<qt>This demonstrates <b>Qt</b>'s"
                            " rich text engine.<ul>"
                            "<li>Foo</li>"
                            "<li>Bar</li>"
                            "</ul></qt>"))

Pour les éléments de menu, utilisez

    action->setWhatsThis(i18n("Supprime le fichier marqué"))

L'invocation de KHelpCenter est encapsulée dans la classe KApplication. Pour afficher le manuel de votre application, utilisez simplement

    kapp->invokeHelp()

Cette commande affiche la première page avec la table des matières. Si vous voulez n'afficher qu'une certaine section du manuel, vous pouvez fournir un argument supplémentaire à `invokeHelp()`, déterminant l'ancrage vers lequel le navigateur saute.

## Composants et services

### Services KDE

#### Que sont les services KDE ?

La notion de service est un concept fondamental dans l'architecture modulaire de KDE. Il n'y aucune implémentation technique stricte associée à ce terme — les services peuvent être des modules externes (plugins) sous la forme de bibliothèques partagées, ou bien il peut s'agir de programmes contrôlés via DCOP. En se proclamant être d'un certain type de service, un service promet d'implémenter certaines API ou fonctionnalités. En termes C++, on peut penser à un type de service comme à une classe d'abstraction, et à un service comme à une implémentation de cette interface.

L'avantage de cette séparation est clair : une application utilisant un type de service n'a pas à en connaître les implémentations possibles. Elle se contente d'employer les API associées au type de service. De cette manière, le service utilisé peut être modifié sans affecter l'application. En outre, l'utilisateur peut configurer les services qu'il préfère pour certaines fonctionnalités.

Quelques exemples :

- Le moteur de rendu HTML utilisé dans Konqueror est un composant intégrable qui implémente les types de services `KParts/ReadOnlyPart` et `Navigateur/Affichage`.

- Dans KDevelop HEAD, la majorité des fonctionnalités est conditionnée en modules externes avec le type de service `KDevelop/Part`. Au démarrage, tous les services de ce type sont chargés, de telle sorte que vous puissiez étendre l'EDi d'une manière très souple.

- Dans l'affichage Icône, Konqueror affiche — si activé — des représentations miniatures des images, des pages HTML, des fichiers PDF et texte. Cette capacité peut être étendue. Si vous souhaitez qu'elle affiche des aperçus des images de vos propres fichiers de données avec tel ou tel type MIME, mettez en œuvre un service avec un type de service `ThumbCreator`.

À l'évidence, un service n'est pas seulement caractérisé par les types de services qu'il met en œuvre, mais aussi par quelques propriétés. Par exemple, un ThumbCreator ne se se contente pas d'implémenter la classe C++ avec le type `ThumbCreator`, il a aussi une liste de types MIME dont il est responsable. De la même manière, les « parties » de KDevelop ont comme propriété le langage de programmation qu'elles prennent en charge. Lorsqu'une application demande un type de service, elle peut également dresser la liste des contraintes sur les propriétés du service. Dans l'exemple ci-dessus, quand KDevelop charge les modules externes pour un projet Java, il demande seulement ceux qui ont Java comme propriété de langage de programmation. À cette fin, KDE contient un courtier à part entière dans le style de CORBA, doté d'un langage de requête complexe.

#### Définition des types de services

Les nouveaux types de services sont ajoutés en installant une description dans le dossier `KDEDIR/share/servicetypes`. Dans un environnement automake, cette tâche s'effectue avec ce fragment de `Makefile.am` :

    kde_servicetypesdir_DATA = kdeveloppart.desktop
    EXTRA_DIST = $(kde_servicetypesdir_DATA)

Voici à quoi ressemble la définition `kdeveloppart.desktop` d'une « partie » KDevelop :

    [Desktop Entry]
    Type=ServiceType
    X-KDE-ServiceType=KDevelop/Part
    Name=KDevelop Part

    [PropertyDef::X-KDevelop-Scope]
    Type=QString

    [PropertyDef::X-KDevelop-ProgrammingLanguages]
    Type=QStringList

    [PropertyDef::X-KDevelop-Args]
    Type=QString

En plus des lignes habituelles, cet exemple explique comment déclarer qu'un service a certaines propriétés. Chaque définition de propriété correspond à un groupe `[PropertyDef::name]` dans le fichier de configuration. Dans ce groupe, la ligne `Type` déclare le type de la propriété. Les types possibles sont tout ce qui peut être enregistré dans une QVariant.

#### Définition des services de bibliothèques partagées

Les définitions des services sont enregistrées dans le dossier `KDEDIR/share/services`:

    kde_servicesdir_DATA = kdevdoxygen.desktop
    EXTRA_DIST = $(kde_servicesdir_DATA)

Le contenu du fichier d'exemple suivant `kdevdoxygen.desktop` définit le module externe `KDevDoxygen` avec le type de service `KDevelop/Part` :

    [Desktop Entry]
    Type=Service
    Comment=Doxygen
    Name=KDevDoxygen
    ServiceTypes=KDevelop/Part
    X-KDE-Library=libkdevdoxygen
    X-KDevelop-ProgrammingLanguages=C,C++,Java
    X-KDevelop-Scope=Project

En plus des déclarations habituelles, `X-KDE-Library` est une ligne importante. Elle contient le nom de la bibliothèque libtool (sans l'extension `.la`). Elle fixe également (avec le préfixe `init_` ajouté au début) le nom du symbole exporté dans la bibliothèque qui retourne une fabrique d'objets. Pour l'exemple ci-dessus, la bibliothèque doit contenir la fonction suivante :

    extern "C" {
        void *init_libkdevdoxygen()
        {
            return new DoxygenFactory;
        }
    };

Le type de la classe de fabrique `DoxygenFactory` dépend du type de service spécifique que le service met en œuvre. Dans notre exemple d'un module externe KDevelop, la fabrique doit être une `KDevFactory` (qui hérite de `KLibFactory`). KParts::Factory est un autre exemple courant supposé produire des objets KParts::ReadOnlyPart ou dans la plupart des cas, la KLibFactory générique.

#### Utilisation des services de bibliothèques partagées

Pour employer un service de bibliothèque partagée dans une application, vous devez obtenir un objet KService qui le représente. Ce point est étudié dans la section sur les types MIME (et dans une section consacrée au courtier — à écrire)

Une fois l'objet `KService` en main, vous pouvez très simplement charger la bibliothèque et obtenir un pointeur vers son objet fabrique :

    KService *service = ...
    QString libName = QFile::encodeName(service->library());
    KLibFactory *factory = KLibLoader::self()->factory(libName);
    if (!factory) {
        QString name = service->name();
        QString errorMessage = KLibLoader::self()->lastErrorMessage();
        KMessageBox::error(0, i18n("Une erreur de chargement de service % s'est produite1.\n"
                                   "Le diagnostic de libtool est :\n%2")
                              .arg(name).arg(errorMessage);
    }

À partir de ce moment, la suite des opérations dépend à nouveau du type de service. Pour les modules externes génériques, vous créerez des objets avec la méthode KLibFactory::create(). Pour KParts, vous devrez transtyper le pointeur de la fabrique vers les KParts::Factory plus spécifiques et employer sa méthode create() :

    if (factory->inherits("KParts::Factory")) {
        KParts::Factory *partFactory = static_cast<KParts::Factory*>(factory);
        QObject *obj = partFactory->createPart(parentWidget, widgetName,
                                               parent, name, "KParts::ReadOnlyPart");
        ...
    } else {
        cout << "Le service n'implémente pas la fabrique correcte" << endl;
    }

#### Définition des services DCOP

Un service DCOP est habituellement mis en œuvre comme un programme qui est démarré lorsqu'il est nécessaire. Il entre ensuite dans une boucle et écoute les connexions DCOP. Il peut s'agir d'un programme interactif, mais il se peut aussi qu'il s'exécute complètement ou pour une partie de sa durée de vie comme un démon en arrière-plan, sans que l'utilisateur le remarque. Un exemple de ce type de démon est `kio_uiserver`, qui met en œuvre une interaction utilisateur comme boîte de dialogue de progression pour la bibliothèque KIO. L'avantage d'un tel démon centralisé dans ce contexte est que &pex; la progression du téléchargement de différents fichiers peut s'afficher dans une seule fenêtre, même si ces téléchargements ont été lancés à partir de différentes applications.

Un service DCOP est défini différemment depuis un service de bibliothèque partagée. Bien sûr, il ne spécifie pas une bibliothèque, mais plutôt un exécutable. En outre, les services DCOP ne spécifient pas de ligne ServiceType parce qu'ils sont habituellement démarrés par leur nom. À titre de propriétés additionnelles, il contient deux lignes :

`X-DCOP-ServiceType` spécifie la manière dont le service est démarré. La valeur `Unique` indique que le service ne doit pas être démarré plus d'une fois. Cela signifie que si vous tentez de démarrer ce service (&pex; via KApplication::startServiceByName(), KDE vérifie s'il est déjà enregistré avec DCOP et utilise le service en cours d'exécution. S'il n'est pas encore enregistré, KDE le démarre et attend jusqu'à ce qu'il soit enregistré. Ainsi, vous pouvez immédiatement envoyer des appels DCOP au service. Dans un tel cas, le service devra être mis en œuvre en tant que KUniqueApplication.

La valeur `Multi` pour `X-DCOP-ServiceType` indique que, comme des instances multiples du service peuvent coexister, toute tentative pour démarrer le service créera un autre processus. En dernière possibilité, on peut employer la valeur `None`. Dans ce cas, un démarrage du service n'attendra pas jusqu'à ce qu'il soit enregistré avec DCOP.

`X-KDE-StartupNotify` devrait normalement être défini à « false ». Sinon, quand le programme est démarré, la barre de tâches affiche une notification de démarrage ou, en fonction des réglages de l'utilisateur, le curseur est changé.

Voici la définition de `kio_uiserver` :

    [Desktop Entry]
    Type=Service
    Name=kio_uiserver
    Exec=kio_uiserver
    X-DCOP-ServiceType=Unique
    X-KDE-StartupNotify=false

#### Utilisation des services DCOP

Un service DCOP est démarré avec une des quelques méthodes que contient la classe KApplication :

    DCOPClient *client = kapp->dcopClient();
    client->attach();
    if (!client->isApplicationRegistered("kio_uiserver")) {
        QString error;
        if (KApplication::startServiceByName("kio_uiserver", QStringList(), &error))
            cout << "Le démarrage de kioserver a échoué avec le message " << error << endl;
    }
    ...
    QByteArray data, replyData;
    QCString replyType;
    QDataStream arg(data, IO_WriteOnly);
    arg << true;
    if (!client->call("kio_uiserver", "UIServer", "setListMode(bool)",
                      data, replyType, replyData))
        cout << "L'appel à kio_uiserver a échoué" << endl;
    ...

Notez que l'exemple d'un appel DCOP donné utilise ici la mise en ordre explicite des arguments. Vous serez souvent amené à employer de préférence un élément de remplacement généré par dcopidl2cpp parce qu'il est plus simple et moins sujet aux erreurs.

Dans l'exemple donné ici, le service a été démarré « par nom », &cad; que le premier argument vers `KApplication::startServiceByName()` est le nom qui apparaît dans la ligne `Name` du fichier desktop. Une alternative consiste à utiliser `KApplication::startServiceByDesktopName()`, qui prend le nom de fichier de son fichier desktop comme argument, &cad; dans ce cas, `"kio_uiserver.desktop"`.

Tous ces appels prennent une liste d'URL comme deuxième argument, fourni au service sur la ligne de commande. Le troisième argument est un pointeur vers une `QString`. Si le démarrage du service échoue, cet argument devient un message d'erreur traduit.

### Types MIME

#### Que sont les types MIME ?

Les types MIME servent à décrire le type de contenu de blocs de fichiers ou de données. Au départ, ils ont été introduits afin d'autoriser l'envoi des images ou des fichiers son &etc; par courrier électronique (MIME signifie « Multipurpose Internet Mail Extensions », extensions multimédia de messagerie Internet). Plus tard, les navigateurs ont également fait appel à ce système pour déterminer comment présenter à l'utilisateur les données envoyées par un serveur web. Par exemple, une page HTML a un type MIME « text/html », un fichier PostScript « application/postscript ». Dans KDE, ce concept s'emploie à divers endroits :

- Dans l'affichage Icône de Konqueror, les fichiers sont représentés par des icônes. Chaque type MIME a une certaine icône associée illustrée ici.

- Quand vous cliquez sur l'icône d'un fichier ou le nom d'un fichier dans Konqueror, soit le fichier s'affiche dans une vue intégrée, soit une application associée au type du fichier est ouverte.

- Quand vous faites un glisser-déposer de certaines données d'une application à l'autre (ou au sein de la même application), la cible de dépôt peut choisir de n'accepter que certains types de données. De surcroît, elle gérera les données images différemment des données textuelles.

- Les données du presse-papiers ont un type MIME. Traditionnellement, les programmes X ne gèrent que les pixmaps ou les textes, mais avec Qt, il n'y a aucune restriction sur le type des données.

À la lumière des exemples précédents, il est clair que la gestion MIME est un problème complexe. Tout d'abord, il est nécessaire d'établir une correspondance depuis les noms des fichiers vers les types MIME. KDE franchit une étape supplémentaire en permettant même au contenu des fichiers d'être mis en correspondance vers les types MIME, pour les cas où le nom du fichier n'est pas disponible. En second lieu, il est nécessaire de mettre en correspondance les types MIME vers les applications ou les bibliothèques qui peuvent afficher ou modifier un fichier avec un certain type ou bien en créer une image miniature.

Il y a diverses API pour découvrir le type MIME des données ou des fichiers. En général, vous devez arriver à un certain compromis vitesse/fiabilité. Cherchez le type d'un fichier en n'examinant que son nom de fichier (&cad; dans la plupart des cas, l'extension du nom du fichier). Par exemple, un fichier `foo.jpg` est normalement « image/jpeg ». Dans les cas où l'extension est retirée, ce n'est pas sans risque et vous devez vraiment vérifier le contenu du fichier. Cette opération prend assurément plus de temps, en particulier pour les fichiers qu'il faut télécharger via HTTP au préalable. La méthode basée sur le contenu dépend du fichier `KDEDIR/share/mimelnk/magic` et est par conséquent difficile à étendre. Mais en général, les informations sur le type MIME peuvent être aisément mises à la disposition du système en installant un fichier `.desktop` : il est efficacement et commodément accessible grâce aux bibliothèques KDE.

#### Définition des types MIME

Définissons un type `« application/x-foo »` pour notre nouveau programme foobar. À cette fin, vous devez écrire un fichier `foo.desktop` et l'installer dans `KDEDIR/share/mimelnk/application`. (Il s'agit de l'emplacement habituel, qui peut différer entre les distributions). Cette opération peut s'effectuer en ajoutant les lignes suivantes au `Makefile.am` :

    mimedir = $(kde_mimedir)/application
    mime_DATA = foo.desktop
    EXTRA_DIST = $(mime_DATA)

Voici à quoi devrait ressembler le fichier `foo.desktop` :

    [Desktop Entry]
    Type=MimeType
    MimeType=application/x-foo
    Icon=fooicon
    Patterns=*.foo;
    DefaultApp=foobar
    Comment[fr]=Fichier de données Foo
    Comment[en]=Foo Data File

La ligne `« Comment »` est supposée être traduite. Du fait que le fichier `.desktop` spécifie une icône, installez aussi une icône `fooicon.png`, qui représente le fichier, &pex; dans Konqueror.

Dans les bibliothèques KDE, un tel type de définition est mis en correspondance vers une instance de la classe KMimeType. Utilisez-la, comme dans l'exemple suivant :

    KMimeType::Ptr type = KMimeType::mimeType("application/x-foo");
    cout << "Type:    " << type->name() < endl;
    cout << "Icon:    " << type->icon() < endl;
    cout << "Comment: " << type->icon() < endl;
    QStringList patterns = type->patterns();
    QStringList::ConstIterator it;
    for (it = patterns.begin(); it != patterns.end(); ++it)
      cout << "Motif : " << (*it) << endl;

#### Détermination du type MIME des données

La méthode la plus rapide pour déterminer le type d'un fichier est `KMimeType::findByURL()`. Celle-ci cherche la chaîne d'URL et, le plus souvent, détermine le type à partir de l'extension. Pour certains protocoles (&pex;, http, man, info), ce mécanisme n'est pas employé. Par exemple, les scripts CGI sur les serveurs web écrits en Perl ont souvent l'extension `.pl` indiquant un type `"text/x-perl"`. Toutefois, le fichier que livre le serveur est la sortie de ce script, qui est normalement du HTML. Pour un tel cas, `KMimeType::findByURL()` retourne le type MIME `"application/octet-stream"` (disponible grâce à `KMimeType::defaultMimeType()`), indiquant qu'il est impossible de découvrir le type.

    KMimeType::Ptr type = KMimeType::findByURL("/home/bernd/foobar.jpg");
    if (type->name() == KMimeType::defaultMimeType())
        cout << "Impossible de découvrir le type" << endl;
    else
        cout << "Type : " << type->name() << endl;

(cette méthode a quelques arguments supplémentaires, mais comme ils ne sont pas documentés, ignorons-les.)

Vous pouvez être amené à trouver un type MIME à partir du contenu du fichier au lieu du nom du fichier. Ce procédé est plus fiable mais aussi plus lent, puisqu'il exige la lecture d'une partie du fichier. Pour ce faire, faites appel à la classe KMimeMagic qui gère différemment les erreurs :

    KMimeMagicResult *result = KMimeMagic::self()->findFileType("/home/bernd/foobar.jpg");
    if (!result || !result->isValid())
        cout << "Impossible de découvrir le type" << endl;
    else
        cout << "Type : " << result->mimeType() << endl;

Comme variante de cette fonction, vous pouvez aussi déterminer le type d'un bloc de mémoire. Elle est &pex; utilisée dans Kate pour trouver le mode de coloration syntaxique :

    QByteArray array;
    ...
    KMimeMagicResult *result = KMimeMagic::self()->findBufferType(array);
    if (!result || !result->isValid())
        cout << "Impossible de découvrir le type" << endl;
    else
        cout << "Type : " << result->mimeType() << endl;

Bien sûr, même KMimeMagic n'est capable de déterminer un type de fichier qu'à partir du contenu d'un fichier local. Pour les fichiers distants, il y a une autre possibilité :

    KURL url("http://developer.kde.org/favicon.ico");
    QString type = KIO::NetAccess::mimetype(url);
    if (type == KMimeType::defaultMimeType())
        cout << "Impossible de découvrir le type" << endl;
    else
        cout << "Type : " << type << endl;

Cette fonction démarre une tâche KIO pour télécharger une partie du fichier et le vérifie. Notez qu'elle peut être très lente et bloque le programme. Normalement, vous ne l'utiliserez que si `KMimeType::findByURL()` a retourné `"application/octet-stream"`.

Par ailleurs, si vous ne voulez pas bloquer votre application, vous pouvez également démarrer explicitement la tâche KIO et vous connecter à quelques-uns de ses signaux :

    void FooClass::findType()
    {
        KURL url("http://developer.kde.org/favicon.ico");
        KIO::MimetypeJob *job = KIO::mimetype(url);
        connect( job, SIGNAL(result(KIO::Job*)),
                 this, SLOT(mimeResult(KIO::Job*)) );
    }

    void FooClass::mimeResult(KIO::Job *job)
    {
        if (job->error())
            job->showErrorDialog();
        else
            cout << "MIME type: " << ((KIO::MimetypeJob *)job)->mimetype() << endl;
    }

#### Mise en correspondance d'un type MIME vers une application ou un service

Lorsqu'une application est installée, elle prépare un fichier `.desktop` contenant une liste de types MIME que cette application peut charger. De la même manière, les composants comme KParts rendent cette information disponible au moyen de leurs fichiers `.desktop` de services. Il y a donc en général plusieurs programmes et composants pour traiter un type MIME donné. Vous pouvez obtenir ce genre de liste depuis la classe `KServiceTypeProfile` :

    KService::OfferList offers = KServiceTypeProfile::offers("text/html", "Application");
    KService::OfferList::ConstIterator it;
    for (it = offers.begin(); it != offers.end(); ++it) {
        KService::Ptr service = (*it);
        cout << "Nom : " << service->name() << endl;
    }

La valeur de retour de cette fonction est une liste d'offres de services. Un objet `KServiceOffer` conditionne un KService::Ptr avec un numéro de préférence. La liste que `KServiceTypeProfile::offers()` retourne est numérotée dans l'ordre des préférences de l'utilisateur. Ce dernier peut modifier ce comportement en appelant `"keditfiletype text/html"` ou en choisissant Modifier le type du fichier dans le menu contextuel de Konqueror sur un fichier HTML.

Dans l'exemple ci-dessus, une liste d'offres des applications prenant en charge `text/html` a été demandée. Celle-ci contient — entre autres — des éditeurs HTML comme Quanta Plus. Vous pouvez également remplacer le second argument `"Application"` par `"KParts::ReadOnlyPart"`. Dans ce cas, vous obtenez une liste des composants intégrables pour présenter du contenu HTML, &pex;, KHTML.

La plupart du temps, la liste de toutes les offres de services n'a aucun intérêt pour une combinaison type MIME et type de service. Il y a une fonction de confort qui vous donne uniquement l'offre de service ayant la plus grande préférence :

    KService::Ptr offer = KServiceTypeProfile::preferredService("text/html", "Application");
    if (offer)
        cout << "Nom : " << service->name() << endl;
    else
        cout << "Aucun service approprié n'a été trouvé" << endl;

Pour des requêtes encore plus complexes, il y a un courtier à part entière dans le style de CORBA.

Pour exécuter un service d'application avec quelques URL, utilisez KRun :

    KURL::List urlList;
    urlList << "http://www.ietf.org/rfc/rfc1341.txt?number=1341";
    urlList << "http://www.ietf.org/rfc/rfc2046.txt?number=2046";
    KRun::run(offer.service(), urlList);

#### Divers

Dans cette section, nous souhaitons répertorier quelques API qui sont plus ou moins liées à l'étude précédente.

Obtention d'une icône pour une URL. Celle-ci cherche le type de l'URL et retourne l'icône associée.

    KURL url("ftp://ftp.kde.org/pub/incoming/wibble.c");
    QString icon = KMimeType::iconForURL(url);

Exécution d'une URL. Celle-ci cherche le type de l'URL et démarre le programme préféré de l'utilisateur et associé à ce type.

    KURL url("http://dot.kde.org");
    new KRun(url);

### Transparence réseau

#### Introduction

À l'ère du World Wide Web, il est d'une importance capitale que les applications bureautiques puissent accéder aux ressources sur l'internet : elles devront être capables de télécharger des fichiers sur un serveur web, écrire des fichiers sur un serveur ftp ou lire des messages sur un serveur de messagerie. Souvent, la capacité à accéder à des fichiers quel que soit leur emplacement est appelée transparence réseau.

Dans le passé, différentes approches de ces objectifs ont été mises en œuvre. L'ancien système de fichiers NFS est une tentative pour implémenter la transparence réseau au niveau de l'API POSIX. Bien que cette approche fonctionne parfaitement dans des réseaux locaux,étroitement couplés, elle ne convient pas pour les ressources dont l'accès manque de fiabilité et souffre d'une éventuelle lenteur. Ici, l'asynchronicité est importante. Pendant que vous attendez que votre navigateur web télécharge une page, l'interface utilisateur ne devra pas se bloquer. De plus, le rendu de la page ne devrait pas commencer quand la page est complètement disponible, mais devrait être régulièrement actualisé au fur et à mesure que les données arrivent.

Dans les bibliothèques KDE, la transparence réseau est mise en œuvre dans l'API KIO. Le concept central de cette architecture est une tâche d'E/S. Une tâche peut copier ou supprimer des fichiers, ou toute autre chose similaire. Dès lors qu'une tâche est démarrée, elle s'exécute en arrière-plan et ne bloque pas l'application. Toute communication depuis la tâche à nouveau vers l'application — comme la remise des données ou les informations de progression — s'effectue alors qu'elle est intégrée à la boucle d'événement Qt.

L'opération d'arrière-plan est réalisée en démarrant les ioslaves pour exécuter certaines tâches. Les « ioslaves » sont démarrés en tant que processus séparés et communiquent avec eux grâce à des sockets du domaine UNIX. De cette manière, aucun traitement multiprocessus (multi-threading) n'est nécessaire et les esclaves instables ne peuvent pas « planter » l'application qui les utilise.

Les emplacements des fichiers sont exprimés par les URL bien connues. Mais dans KDE, les URL ne se contentent pas d'étendre la plage des fichiers adressables au-delà du système de fichiers local. Ce dernier va aussi dans la direction opposée — &pex;, vous pouvez explorer des archives « tar ». Ceci est réalisable grâce à l'imbrication des URL. Par exemple, un fichier dans une archive tar sur un serveur http pourrait avoir l'URL

    http://www-com.physik.hu-berlin.de/~bernd/article.tgz#tar:/paper.tex

#### Utilisation de KIO

Dans la majorité des cas, les tâches sont créées en appelant des fonctions dans l'espace de noms KIO. Celles-ci prennent une ou deux URL comme arguments, voire d'autres paramètres nécessaires. Lorsque la tâche est terminée, elle émet le signal `result(KIO::Job*)`. Une fois ce signal émis, la tâche se supprime. Ainsi, voici à quoi ressemble un cas classique d'utilisation :

    void FooClass::makeDirectory()
    {
        SimpleJob *job = KIO::mkdir(KURL("file:/home/bernd/kiodir"));
        connect( job, SIGNAL(result(KIO::Job*)),
                 this, SLOT(mkdirResult(KIO::Job*)) );
    }

    void FooClass::mkdirResult(KIO::Job *job)
    {
        if (job->error())
            job->showErrorDialog();
        else
            cout << "mkdir a bien fonctionné" << endl;
    }

Selon le type de tâche, vous pouvez également vous connecter à d'autres signaux.

Voici une vue d'ensemble des fonctions possibles :

KIO::mkdir(const KURL &url, int permission)

  Crée un dossier, optionnellement avec certaines permissions.

KIO::rmdir(const KURL &url)

  Supprime un dossier.

KIO::chmod(const KURL &url, int permissions)

  Change les permissions d'un fichier.

KIO::rename(const KURL &src, const KURL &dest, bool overwrite)

  Renomme un fichier.

KIO::symlink(const QString &target, const KURL &dest, bool overwrite, bool showProgressInfo)

  Crée un lien symbolique.

KIO::stat(const KURL &url, bool showProgressInfo)

  Découvre certaines informations sur le fichier, comme sa taille, l'heure de sa modification et ses permissions. Les informations peuvent être obtenues à partir de KIO::StatJob::statResult() lorsque la tâche est terminée.

KIO::get(const KURL &url, bool reload, bool showProgressInfo)

  Transfère des données depuis une URL.

KIO::put(const KURL &url, int permissions, bool overwrite, bool resume, bool showProgressInfo)

  Transfère des données vers une URL.

KIO::http_post(const KURL &url, const QByteArray &data, bool showProgressInfo)

  Poste des données. Spécial pour HTTP.

KIO::mimetype(const KURL &url, bool showProgressInfo)

  Essaie de trouver le type MIME de l'URL. Le type peut être obtenu depuis KIO::MimetypeJob::mimetype() lorsque la tâche est terminée.

KIO::file_copy(const KURL &src, const KURL &dest, int permissions, bool overwrite, bool resume, bool showProgressInfo)

  Copie un seul fichier.

KIO::file_move(const KURL &src, const KURL &dest, int permissions, bool overwrite, bool resume, bool showProgressInfo)

  Renomme ou déplace un seul fichier.

KIO::file_delete(const KURL &url, bool showProgressInfo)

  Supprime un seul fichier.

KIO::listDir(const KURL &url, bool showProgressInfo)

  Répertorie le contenu d'un dossier. Chaque fois que de nouveaux éléments sont connus, le signal KIO::ListJob::entries() est émis.

KIO::listRecursive(const KURL &url, bool showProgressInfo)

  Similaire à la fonction listDir(), mais celle-ci est récursive.

KIO::copy(const KURL &src, const KURL &dest, bool showProgressInfo)

  Copie un fichier ou un dossier. Les dossiers sont copiés récursivement.

KIO::move(const KURL &src, const KURL &dest, bool showProgressInfo)

  Déplace ou renomme un fichier ou un dossier.

KIO::del(const KURL &src, bool shred, bool showProgressInfo)

  Supprime un fichier ou un dossier.

#### Éléments des dossiers

Les tâches KIO::stat() et KIO::listDir() retournent toutes deux leur résultat sous la forme d'un type UDSEntry, UDSEntryList, respectivement. Cette dernière est définie en tant que QValueList<UDSEntry>. L'acronyme UDS signifie « Universal Directory Service » (service universel de dossiers). Le principe sous-jacent est que l'élément du dossier ne transporte que les informations qu'un ioslave peut fournir, sans plus. Par exemple, l'esclave http ne fournit aucune information sur les droits d'accès ou sur les propriétaires des fichiers. En revanche, un UDSEntry est une liste de UDSAtoms. Chaque atome fournit une partie spécifique d'information. Il se compose d'un type enregistré dans m_uds et soit d'une valeur entière dans m_long, soit d'une valeur chaîne dans m_str, en fonction du type.

Les types suivants sont actuellement définis :

- UDS_SIZE (integer) — taille du fichier.

- UDS_USER (string) — utilisateur propriétaire du fichier.

- UDS_GROUP (string) — groupe propriétaire du fichier.

- UDS_NAME (string) — nom du fichier.

- UDS_ACCESS (integer) — droits d'accès du fichier, comme &pex; enregistrés par la fonction libc stat() dans le champ st_mode.

- UDS_FILE_TYPE (integer) — le type du fichier, comme &pex; enregistré par stat() dans le champ st_mode. Par conséquent, vous pouvez faire appel aux macros libc habituelles comme S_ISDIR pour tester cette valeur. Notez que les données fournies par les ioslaves correspondent à stat(), non lstat(), &cad; que dans le cas de liens symboliques, le type du fichier est ici le type du fichier sur lequel pointe le lien, non le lien lui-même.

- UDS_LINK_DEST (string) — dans le cas d'un lien symbolique, le nom du fichier sur lequel pointe le fichier.

- UDS_MODIFICATION_TIME (integer) — l'heure (comme dans le type time_t) de la dernière modification du fichier, comme &pex; enregistré par stat() dans le champ st_mtime.

- UDS_ACCESS_TIME (integer) — l'heure du dernier accès au fichier, comme &pex; enregistré par stat() dans le champ st_atime.

- UDS_CREATION_TIME (integer) — l'heure à laquelle le fichier a été créé, comme &pex; enregistré par stat() dans le champ st_ctime.

- UDS_URL (string) — fournit l'URL d'un fichier, s'il ne s'agit pas simplement de la concaténation de l'URL d'un dossier et d'un nom de fichier.

- UDS_MIME_TYPE (string) — type MIME du fichier

- UDS_GUESSED_MIME_TYPE (string) — type MIME du fichier comme le devine l'esclave. La différence avec le type précédent est que celui fourni ici ne devra pas être considéré comme fiable (parce que le fait de le déterminer d'une manière sûre serait trop coûteux). Par exemple, la classe KRun vérifie explicitement le type MIME s'il n'a pas d'informations dignes de foi.

Bien que la manière d'enregister des informations sur les fichiers dans un `UDSEntry` soit souple et pratique du point de vue de l'ioslave, son utilisation pour le programmeur d'applications est fastidieuse. Par exemple, pour découvrir le type MIME du fichier, vous devez itérer sur tous les atomes et tester si `m_uds` est `UDS_MIME_TYPE`. Heureusement il y a une API beaucoup plus facile à utiliser : la classe `KFileItem`.

#### Utilisation synchrone

Souvent, l'API asynchrone de KIO est trop complexe à employer et donc, la mise en œuvre de l'asynchronicité complète n'est pas une priorité. Par exemple, dans un programme qui ne peut gérer qu'un fichier document à la fois, il y a de toute façon très peu de choses à faire pendant que le programme télécharge un fichier. Pour ces cas peu complexes, il y a une API beaucoup plus simple, sous la forme d'un ensemble de fonctions statiques dans KIO::NetAccess. Par exemple, pour copier un fichier, utilisez

    KURL source, target;
    source = ...;
    target = ...
    KIO::NetAccess::copy(source, target);

La fonction revient à l'état antérieur une fois que le processus complet de copie est terminé. De plus, cette méthode fournit une boîte de dialogue de progression et s'assure que les processus d'application retracent les événements.

La fonction `download()`, en combinaison avec `removeTempFile()` est particulièrement intéressante. La première télécharge un fichier depuis l'URL donnée et l'enregistre dans un fichier temporaire avec un nom unique. Ce nom est enregistré dans le second argument. Si l'URL est locale, le fichier n'est pas téléchargé et, en revanche, le second argument est défini au nom du fichier local. La fonction `removeTempFile()` supprime le fichier donné par son argument si le fichier est le résultat d'un téléchargement précédent. Si tel n'est pas le cas, elle ne fait rien. Ainsi, le fragment de code suivant est un moyen très aisé d'utiliser le chargement des fichiers, quel que soit leur emplacement :

    KURL url;
    url = ...;
    QString tempFile;
    if (KIO::NetAccess::download(url, tempFile) {
        // load the file with the name tempFile
        KIO::NetAccess::removeTempFile(tempFile);
    }

#### Métadonnées

Comme on peut le voir plus haut, l'interface aux tâches IO est assez abstraite et ne prend en considération aucun échange d'informations entre l'application et l'ioslave spécifique au protocole : ce n'est pas toujours approprié. Par exemple, vous pouvez fournir certains paramètres à l'esclave HTTP pour contrôler le comportement de sa mise en cache ou envoyer un groupe de cookies avec la requête. Le concept de métadonnées a été introduit pour satisfaire cette exigence. Lorsqu'une tâche est créée, vous pouvez la configurer en lui ajoutant des métadonnées. Chaque élément de métadonnées se compose d'une paire clé/valeur. Par exemple, pour empêcher l'esclave HTTP de charger une page web depuis son cache, utilisez :

    void FooClass::reloadPage()
    {
        KURL url("http://www.kdevelop.org/index.html");
        KIO::TransferJob *job = KIO::get(url, true, false);
        job->addMetaData("cache", "reload");
        ...
    }

La même technique s'emploie dans l'autre sens, &cad; pour la communication depuis l'esclave vers l'application. La méhode `Job::queryMetaData()` demande la valeur de la clé remise par l'esclave. Pour l'esclave HTTP, un tel exemple est la clé `« modified »`, qui contient une représentation sous forme de chaîne de la date de la dernière modification de la page web. Voici un exemple de la manière dont vous pouvez l'utiliser :

    void FooClass::printModifiedDate()
    {
        KURL url("http://developer.kde.org/documentation/kde2arch/index.html");
        KIO::TransferJob *job = KIO::get(url, true, false);
        connect( job, SIGNAL(result(KIO::Job*)),
                 this, SLOT(transferResult(KIO::Job*)) );
    }

    void FooClass::transferResult(KIO::Job *job)
    {
        QString mimetype;
        if (job->error())
            job->showErrorDialog();
        else {
            KIO::TransferJob *transferJob = (KIO::TransferJob*) job;
            QString modified = transferJob->queryMetaData("modified");
            cout << "Dernière modification : " << modified << endl;
    }

#### Planification

Lorsque vous employez l'API KIO, vous n'avez d'ordinaire pas à vous préoccuper des détails du démarrage des IOslaves et de la communication avec ces derniers. Le cas normal d'utilisation est de démarrer une tâche et avec certains paramètres, ainsi que de gérer les signaux que la tâche émet.

En coulisses, le scénario est beaucoup plus compliqué. Dès que vous créez une tâche, elle est mise en file d'attente. Quand l'application revient vers la boucle d'événement, KIO alloue les processus esclaves pour les tâches contenues dans la file d'attente. Pour la première tâche démarrée, c'est trivial : un IOslave est démarré pour le protocole approprié. Toutefois, une fois que la tâche (comme un téléchargement depuis un serveur http) est terminée, elle n'est pas tuée immédiatement. Elle est au contraire placée dans un groupe d'esclaves inactifs et tuée après un certain temps d'inactivité (le plus souvent, 3 minutes). Si une nouvelle requête pour le même protocole et le même hôte arrive, l'esclave est réutilisé. L'avantage évident est que, pour une série de tâches concernant le même hôte, le coût nécessaire à la création de nouveaux processus et éventuellement à l'obtention d'une procédure d'authentification est économisé.

Naturellement, la réutilisation n'est possible que si l'esclave existant a déjà terminé sa tâche précédente. Quand une nouvelle requête arrive alors qu'un processus esclave existant s'exécute encore, un nouveau processus doit être démarré et utilisé. Lors de l'usage de l'API dans les exemples précédents, il n'y a aucune limite à la création de nouveaux processus esclaves : si vous démarrez une série consécutive de téléchargements pour 20 fichiers différents, alors KIO démarre 20 processus esclaves. Ce dispositif visant à affecter des esclaves aux tâches est appelé direct. Ce n'est pas toujours celui qui est le plus approprié, car il peut exiger beaucoup de mémoire et placer une charge élevée à la fois sur les machines clientes et serveurs.

Il y a donc un autre moyen : planifier les tâches. Si vous le faites, seul un nombre limité (actuellement, 3) de processus esclaves pour un protocole sera créé. Si vous créez davantage de tâches, elles sont placées dans une file d'attente et traitées lorsqu'un processus esclave devient inactif. Voici comment s'effectue l'opération :

    KURL url("http://developer.kde.org/documentation/kde2arch/index.html");
    KIO::TransferJob *job = KIO::get(url, true, false);
    KIO::Scheduler::scheduleJob(job);

Une troisième possibilité est orientée connexion. Par exemple, pour l'esclave IMAP, il n'est pas judicieux de démarrer de multiples processus pour le même serveur. Une seule connexion IMAP à la fois devra être imposée. Dans ce cas, l'application doit accepter explicitement la notion d'esclave. Il faut qu'elle désalloue un esclave pour une certaine connexion puis qu'elle affecte toutes les tâches qui obtiendront la même connexion au même esclave. Cette opération peut à nouveau être réalisée à l'aide du KIO::Scheduler :

    KURL baseUrl("imap://bernd@albert.physik.hu-berlin.de");
    KIO::Slave *slave = KIO::Scheduler::getConnectedSlave(baseUrl);

    KIO::TransferJob *job1 = KIO::get(KURL(baseUrl, "/INBOX;UID=79374"));
    KIO::Scheduler::assignJobToSlave(slave, job1);

    KIO::TransferJob *job2 = KIO::get(KURL(baseUrl, "/INBOX;UID=86793"));
    KIO::Scheduler::assignJobToSlave(slave, job2);

    ...

    KIO::Scheduler::disconnectSlave(slave);

Vous pouvez déconnecter l'esclave uniquement après que toutes les tâches qui lui sont affectées sont garanties être terminées.

#### Définition d'un ioslave

Dans ce qui suit, nous verrons comment vous pouvez ajouter un nouveau ioslave au système. Par analogie avec les services, les nouveaux ioslaves sont annoncés au système en installant un petit fichier de configuration. Le fragment de Makefile.am suivant installa le protocole ftp :

    protocoldir = $(kde_servicesdir)
    protocol_DATA = ftp.protocol
    EXTRA_DIST = $(mime_DATA)

Voici le contenu du fichier ftp.protocol :

    [Protocol]
    exec=kio_ftp
    protocol=ftp
    input=none
    output=filesystem
    listing=Name,Type,Size,Date,Access,Owner,Group,Link,
    reading=true
    writing=true
    makedir=true
    deleting=true
    Icon=ftp

L'élément `« protocol »` définit de quel protocole cet esclave est responsable. `« exec »` est (contrairement à ce que vous pourriez attendre naïvement), le nom de la bibliothèque qui implémente l'esclave. Quand ce dernier est supposé démarrer, l'exécutable `« kdeinit »` est démarré, lequel à son tour charge cette bibliothèque dans son espace d'adressage. Donc, en pratique, vous pouvez imaginer l'esclave en cours d'exécution comme un processus séparé, bien qu'il soit implémenté en tant que bibliothèque. L'avantage de ce mécanisme est qu'il économise une grande quantité de mémoire et réduit le temps qu'exige l'éditeur de liens au moment de l'exécution.

Les lignes « input » et « output » ne sont pas utilisées actuellement.

Dans le fichier `.protocol`, les lignes restantes définissent les capacités qu'a l'esclave. En général, les fonctionnalités qu'un esclave doit implémenter sont beaucoup plus simples que celles que l'API KIO fournit pour l'application. La raison à cela est que les tâches complexes sont planifiées pour quelques sous-tâches. Par exemple, pour répertorier un dossier récursivement, une tâche sera démarrée pour le dossier de premier niveau. Puis, pour chaque sous-dossier faisant l'objet d'un rapport, de nouvelles sous-tâches sont démarrées. Dans KIO, un planificateur veille à ce qu'il n'y ait pas trop de tâches actives au même moment. De la même manière, pour copier un fichier au sein d'un protocole qui ne prend pas en charge la copie directement (comme le protocole `ftp:`), KIO peut lire le fichier source puis écrire les données sur le fichier de destination. Pour que cela fonctionne, le `.protocol` doit annoncer les actions que son esclave prend en charge.

Du fait que les esclaves sont chargés à titre de bibliothèques partagées mais constituent des programmes autonomes, l'environnement de leur code paraît un peu différent des modules externes (plugins) des bibliothèques partagées normales. La fonction appelée pour démarrer l'esclave est dénommée `kdemain()`. Elle effectue quelques initialisations puis entre dans une boucle d'événement et attend les requêtes de l'application qui l'utilise. Voici à quoi elle ressemble :

    extern "C" { int kdemain(int argc, char **argv); }

    int kdemain(int argc, char **argv)
    {
        KLocale::setMainCatalogue("kdelibs");
        KInstance instance("kio_ftp");
        (void) KGlobal::locale();

        if (argc != 4) {
            fprintf(stderr, "Usage : kio_ftp protocol "
                            "domain-socket1 domain-socket2\n");
            exit(-1);
        }

        FtpSlave slave(argv[2], argv[3]);
        slave.dispatchLoop();
        return 0;
    }

#### Implémentation d'un ioslave

Les esclaves sont implémentés en tant que sous-classes de `KIO::SlaveBase` (FtpSlave dans l'exemple ci-dessus). Ainsi, les actions répertoriées dans le `.protocol` correspondent à certaines fonctions virtuelles dans `KIO::SlaveBase` que l'implémentation de l'esclave doit réimplémenter. Voici une liste des actions possibles et des fonctions virtuelles correspondantes :

lecture — lit des données depuis une URL

  void get(const KURL &url)

écriture — écrit des données dans une URL et crée le fichier s'il n'existe pas encore.

  void put(const KURL &url, int permissions, bool overwrite, bool resume)

déplacement — renomme un fichier.

  void rename(const KURL &src, const KURL &dest, bool overwrite)

suppression — supprime un fichier ou un dossier.

  void del(const KURL &url, bool isFile)

listage — répertorie le contenu d'un dossier.

  void listDir(const KURL &url)

makedir — crée un dossier.

  void mkdir(const KURL &url, int permissions)

En outre, il y a des fonctions réimplémentables non répertoriées dans le fichier `.protocol`. Pour ces opérations, KIO détermine automatiquement si elles sont prises en charge ou non (&cad; que l'implémentation par défaut retourne une erreur).

Fournit des informations sur un fichier, similaire à la fonction C stat().

  void stat(const KURL &url)

Modifie les droits d'accès d'un fichier.

  void chmod(const KURL &url, int permissions)

Détermine le type MIME d'un fichier.

  void mimetype(const KURL &url)

Copie un fichier.

  copy(const KURL &url, const KURL &dest, int permissions, bool overwrite)

Crée un lien symbolique.

  void symlink(const QString &target, const KURL &dest, bool overwrite)

Toutes ces implémentations devront se terminer par un ou deux appels : si l'opération a réussi, elles devront appeler `finished()`. Si une erreur s'est produite, `error()` devra être appelée avec un code d'erreur comme premier argument et une chaîne dans le second. Les codes d'erreur possibles sont répertoriés sous forme d'énumération KIO::Error. Le second argument est habituellement l'URL en question. Elle est utilisée &pex; dans `KIO::Job::showErrorDialog()` afin de paramétrer le message d'erreur humainement lisible.

Pour les esclaves qui correspondent aux protocoles de réseau, il pourrait être intéressant de réimplémenter la méthode `SlaveBase::setHost()`. Cette dernière est appelée pour indiquer au processus esclave l'hôte et le port, ainsi que le nom d'utilisateur et le mot de passe pour se connecter. En général, les métadonnées définies par l'application peuvent être interrogées par `SlaveBase::metaData()`. Vous pouvez vérifier l'existence des métadonnées d'une certaine clé avec `SlaveBase::hasMetaData()`.

#### Communication à nouveau avec l'application

Diverses actions mises en œuvre dans un esclave doivent d'une certaine manière communiquer les données en retour à l'application à l'aide du processus esclave :

- `get()` envoie des blocs de données. Pour ce faire, elle utilise `data()`, qui prend un `QByteArray` comme argument. Bien sûr, vous n'avez pas besoin d'envoyer toutes les données à la fois. Si vous envoyez un gros fichier, appelez `data()` avec des blocs de données plus petits, de façon à ce que l'application puisse les traiter. Appelez `finished()` quand le transfert est terminé.

- `listDir()` signale des informations sur les éléments d'un dossier. À cette fin, appelez `listEntries()` avec une `KIO::UDSEntryList` comme argument. De manière analogue à `data()`, vous pouvez l'appeler plusieurs fois. Quand vous avez fini, appelez `listEntry()` avec le second argument défini à « true ». Vous pouvez aussi appeler `totalSize()` pour signaler le nombre total des éléments du dossier, s'il est connu.

- `stat()` signale des informations sur un fichier, comme sa taille, son type MIME, &etc; Ce genre d'informations est conditionné dans une `KIO::UDSEntry`, que nous aborderons ci-après. Utilisez `statEntry()` pour envoyer un tel élément à l'application.

- `mimetype()` appelle `mimeType()` avec un argument chaîne.

- `get()` et `copy()` peuvent être amenées à fournir des informations sur la progression. Les méthodes `totalSize()`, `processedSize()`, `speed()` s'en chargent. La taille totale et la taille traitée sont signalées sous forme d'octets, la vitesse en octets par seconde.

- Vous pouvez envoyer des paires clé/valeur arbitraires de métadonnées avec `setMetaData()`.

#### Interaction avec l'utilisateur

Parfois, un esclave doit interagir avec l'utilisateur. Des exemples incluent les messages d'information, les boîtes de dialogue d'authentification et de confirmation quand un fichier est sur le point d'être écrasé.

- `infoMessage()` — il s'agit d'un message de retour d'informations, comme un message « Retrieving data from <host> » depuis l'esclave http, qui est souvent affiché dans la barre d'état du programme. Côté application, cette méthode correspond au signal `KIO::Job::infoMessage()`.

- `warning()` — affiche un avertissement dans une boîte de message avec `KMessageBox::information()`. Si une boîte de message est encore ouverte depuis un précédent appel de warning() provenant du même processus esclave, il ne se produit rien.

- `messageBox()` — cette méthode est plus riche que la précédente. Elle permet d'ouvrir une boîte de message avec du texte et une légende, voire quelques boutons. Reportez-vous à l'énumération SlaveBase::MessageBoxType à titre de référence.

- `openPassDlg()` — ouvre une boîte de dialogue pour la saisie du nom d'utilisateur et du mot de passe.

## Licence

&underFDL; &underGPL;