Developpez.com

Qt

Choisissez la catégorie, puis la rubrique :

Viadeo Twitter Facebook Share on Google+   
Logo Documentation Qt ·  Page d'accueil  ·  Toutes les classes  ·  Toutes les fonctions  ·  Vues d'ensemble  · 

Exemple Application

Fichiers :

Images :

L'exemple Application montre comment implémenter une application fenêtrée standard avec des menus, barres d'outils et une barre d'état. L'exemple lui-même est un simple éditeur de texte construit autour de QPlainTextEdit.

image

La quasi-totalité du code de l'exemple Application se trouve dans la classe MainWindow, qui dérive de QMainWindow. QMainWindow fournit l'infrastructure pour des fenêtres possédant menus, barres d'outils, fenêtres dockables et barre d'état. L'application contient des entrées File (fichier), Edit (éditer) et Help (aide) dans la barre de menu, avec les menus popup suivants :

image

La barre d'état en bas de la fenêtre principale affiche une description de l'entrée de menu ou du bouton de la barre d'outils sous le pointeur de souris.

Pour ne pas compliquer l'exemple, les fichiers récemment ouverts ne sont pas affichés dans le menu File, bien que cette fonction soit désirable dans 90 % des applications. L'exemple fichiers récents montre comment implémenter cette fonction. De plus, cet exemple ne peut charger qu'un fichier à la fois. Les exemples SDI et MDI montrent comment lever ces restrictions.

Définition de la classe MainWindow

Voici la définition de la classe :

 class MainWindow : public QMainWindow
 {
     Q_OBJECT
 
 public:
     MainWindow();
 
 protected:
     void closeEvent(QCloseEvent *event);
 
 private slots:
     void newFile();
     void open();
     bool save();
     bool saveAs();
     void about();
     void documentWasModified();
 
 private:
     void createActions();
     void createMenus();
     void createToolBars();
     void createStatusBar();
     void readSettings();
     void writeSettings();
     bool maybeSave();
     void loadFile(const QString &fileName);
     bool saveFile(const QString &fileName);
     void setCurrentFile(const QString &fileName);
     QString strippedName(const QString &fullFileName);
 
     QPlainTextEdit *textEdit;
     QString curFile;
 
     QMenu *fileMenu;
     QMenu *editMenu;
     QMenu *helpMenu;
     QToolBar *fileToolBar;
     QToolBar *editToolBar;
     QAction *newAct;
     QAction *openAct;
     QAction *saveAct;
     QAction *saveAsAct;
     QAction *exitAct;
     QAction *cutAct;
     QAction *copyAct;
     QAction *pasteAct;
     QAction *aboutAct;
     QAction *aboutQtAct;
 };

L'API publique se limite au constructeur. Dans la section protected, nous réimplémentons QWidget::closeEvent() pour détecter les tentatives de fermeture de la fenêtre et avertir l'utilisateur en cas de modifications non sauvegardées. Dans la section private slots, nous déclarons les slots correspondant aux entrées de menu, ainsi qu'un mystérieux slot documentWasModified(). Enfin, dans la section private de la classe, nous avons divers membres qui seront expliqués par la suite.

Implémentation de la classe MainWindow

 #include <QtGui>
 
 #include "mainwindow.h"

Nous commençons par inclure <QtGui>, un fichier d'en-tête qui contient la définition de toutes les classes de QtCore et QtGui ; cela nous évite d'avoir à inclure chaque classe individuellement. Nous incluons aussi mainwindow.h.

Vous vous demandez peut-être pourquoi nous n'incluons pas simplement <QtGui> dans mainwindow.h. Nous évitons de le faire car l'inclusion d'un fichier d'en-tête aussi important depuis un autre fichier d'en-tête peut rapidement dégrader les performances. Dans le cas présent, cela ne poserait pas de problème, mais il vaut mieux, de façon générale, n'inclure que les fichiers d'en-tête strictement nécessaires dans un autre fichier d'en-tête.

 MainWindow::MainWindow()
 {
     textEdit = new QPlainTextEdit;
     setCentralWidget(textEdit);
 
     createActions();
     createMenus();
     createToolBars();
     createStatusBar();
 
     readSettings();
 
     connect(textEdit->document(), SIGNAL(contentsChanged()),
             this, SLOT(documentWasModified()));
 
     setCurrentFile("");
     setUnifiedTitleAndToolBarOnMac(true);
 }

Dans le constructeur, nous commençons par créer un widget QPlainTextEdit en tant qu'enfant de la fenêtre principale (l'objet this). Nous appelons ensuite QMainWindow::setCentralWidget() pour dire que ce widget va occuper la partie centrale de la fenêtre principale, entre les barres d'outils et la barre d'état.

Nous appelons ensuite createActions(), createMenus(), createToolBars() et createStatusBar(), quatre fonctions privées de construction de l'interface utilisateur. Après cela, nous appelons readSettings() pour restaurer les préférences utilisateur.

Nous établissons une connexion signal-slot entre l'objet document QPlainTextEdit et notre slot documentWasModified(). Lorsque l'utilisateur modifie le texte dans le QPlainTextEdit, nous voulons mettre à jour la barre de titre pour montrer que le fichier a été modifié.

Pour finir, nous définissons le titre de la fenêtre en utilisant la fonction privée setCurrentFile(). Nous y reviendrons plus tard.

 void MainWindow::closeEvent(QCloseEvent *event)
 {
     if (maybeSave()) {
         writeSettings();
         event->accept();
     } else {
         event->ignore();
     }
 }

Lorsque l'utilisateur essaie de fermer la fenêtre, nous appelons la fonction privée maybeSave() pour lui donner la possibilité de sauvegarder les changements en cours. La fonction renvoie true si l'utilisateur veut que l'application se ferme ; sinon elle renvoie false. Dans le premier cas, nous sauvegardons les préférences utilisateur sur le disque et acceptons l'événement de fermeture ; dans le second cas, nous ignorons l'événement de fermeture, ce qui veut dire que l'application va continuer à fonctionner comme si rien ne s'était passé.

 void MainWindow::newFile()
 {
     if (maybeSave()) {
         textEdit->clear();
         setCurrentFile("");
     }
 }

Le slot newFile() est invoqué lorsque l'utilisateur sélectionne File|New depuis le menu. Nous appelons maybeSave() pour sauvegarder les changements en cours et si l'utilisateur accepte de continuer, nous vidons le QPlainTextEdit et appelons la fonction privée setCurrentFile() pour mettre à jour le titre de fenêtre et désactiver le drapeau windowModified.

 void MainWindow::open()
 {
     if (maybeSave()) {
         QString fileName = QFileDialog::getOpenFileName(this);
         if (!fileName.isEmpty())
             loadFile(fileName);
     }
 }

Le slot open() est invoqué lorsque l'utilisateur clique sur File|Open. Nous ouvrons une QFileDialog demandant à l'utilisateur de choisir un fichier. Si l'utilisateur choisit un fichier (c'est-à-dire que fileName n'est pas une chaîne vide), nous appelons la fonction privée loadFile() pour réellement charger le fichier.

 bool MainWindow::save()
 {
     if (curFile.isEmpty()) {
         return saveAs();
     } else {
         return saveFile(curFile);
     }
 }

Le slot save() est invoqué lorsque l'utilisateur clique sur File|Save. Si l'utilisateur n'a pas encore fourni de nom pour le fichier, nous appelons saveAs() ; sinon, nous appelons la fonction privée saveFile() pour réellement sauvegarder le fichier.

 bool MainWindow::saveAs()
 {
     QString fileName = QFileDialog::getSaveFileName(this);
     if (fileName.isEmpty())
         return false;
 
     return saveFile(fileName);
 }

Dans saveAs(), nous commençons par ouvrir une QFileDialog demandant à l'utilisateur de choisir un nom de fichier. Si l'utilisateur clique sur Cancel (annuler), le nom de fichier renvoyé est vide et nous ne faisons rien.

 void MainWindow::about()
 {
    QMessageBox::about(this, tr("À propos de Application"),
             tr("L'exemple <b>Application</b> montre comment écrire "
                "des applications d'interface utilisateur moderne en utilisant Qt "
                "avec une barre de menu, d'outils et d'état."));
 }

La fenêtre « À propos de... » de l'application est générée avec une seule instruction, en utilisant la fonction statique QMessageBox::about(), qui permet l'insertion de code HTML.

L'appel tr() autour de la chaîne littérale signale que la chaîne doit être traduite. Une bonne habitude est d'appeler tr() sur toutes les chaînes visibles par l'utilisateur, au cas où vous décideriez plus tard de traduire votre application dans d'autres langues. La documentation Internationalisation avec Qt décrit tr() en détail.

 void MainWindow::documentWasModified()
 {
     setWindowModified(textEdit->document()->isModified());
 }

Le slot documentWasModified() est invoqué à chaque fois que le texte dans le QPlainTextEdit change suite à des modifications par l'utilisateur. Nous appelons QWidget::setWindowModified() afin que la barre de titre indique que le fichier a été modifié. La façon dont cette information est montrée varie suivant les plateformes.

 void MainWindow::createActions()
 {
     newAct = new QAction(QIcon(":/images/new.png"), tr("&Nouveau"), this);
     newAct->setShortcuts(QKeySequence::New);
     newAct->setStatusTip(tr("Crée un nouveau fichier"));
     connect(newAct, SIGNAL(triggered()), this, SLOT(newFile()));
 
     openAct = new QAction(QIcon(":/images/open.png"), tr("&Ouvrir..."), this);
     openAct->setShortcuts(QKeySequence::Open);
     openAct->setStatusTip(tr("Ouvre un fichier existant"));
     connect(openAct, SIGNAL(triggered()), this, SLOT(open()));
     ...
     aboutQtAct = new QAction(tr("À propos de &Qt"), this);
     aboutQtAct->setStatusTip(tr("Affiche la boîte de dialogue à propos de Qt"));
     connect(aboutQtAct, SIGNAL(triggered()), qApp, SLOT(aboutQt()));

La fonction privée createActions(), qui est appelée depuis le constructeur de MainWindow, crée des QAction. Le code est très répétitif, donc nous ne montrons que les actions correspondant à File|New, File|Open et Help|About Qt.

Une QAction est un objet représentant une action utilisateur, comme une sauvegarde de fichier ou la demande d'une boîte de dialogue. Une action peut être intégrée dans un QMenu ou une QToolBar (ou les deux) ou dans n'importe quel autre widget réimplémentant QWidget::actionEvent().

Une action possède un texte, qui est affiché dans le menu, une icône, une touche de raccourci, une infobulle, une information d'état (montré dans la barre d'état), un texte « What's This ? » (« qu'est-ce que c'est ? »), etc. Elle émet un signal triggered() à chaque fois que l'utilisateur invoque l'action (par exemple en cliquant sur l'entrée de menu associée ou le bouton de la barre d'outils). Nous connectons ce signal à un slot qui va réaliser l'action.

Le code ci-dessus contient un autre idiome demandant une explication. Pour certaines actions, nous spécifions une icône sous forme de QIcon au constructeur de QAction. Le constructeur de QIcon prend le nom de fichier d'une image qu'il essaie de charger. Ici, le nom de fichier commence par :. Ce type de nom de fichier n'est pas un nom de fichier ordinaire mais un chemin dans les ressources enregistrées dans l'exécutable. Nous y reviendrons lorsque nous étudierons le fichier application.qrc intégré dans le projet.

     cutAct->setEnabled(false);
     copyAct->setEnabled(false);
     connect(textEdit, SIGNAL(copyAvailable(bool)),
             cutAct, SLOT(setEnabled(bool)));
     connect(textEdit, SIGNAL(copyAvailable(bool)),
             copyAct, SLOT(setEnabled(bool)));
 }

Les actions Edit|Cut and Edit|Copy ne doivent être accessibles que si le QPlainTextEdit contient un texte sélectionné. Nous les désactivons par défaut et connectons le signal QPlainTextEdit::copyAvailable() au slot QAction::setEnabled(), ce qui permet de n'activer les actions que si l'éditeur de texte contient une sélection.

 void MainWindow::createMenus()
 {
     fileMenu = menuBar()->addMenu(tr("&Fichier"));
     fileMenu->addAction(newAct);
     fileMenu->addAction(openAct);
     fileMenu->addAction(saveAct);
     fileMenu->addAction(saveAsAct);
     fileMenu->addSeparator();
     fileMenu->addAction(exitAct);
 
     editMenu = menuBar()->addMenu(tr("&Édition"));
     editMenu->addAction(cutAct);
     editMenu->addAction(copyAct);
     editMenu->addAction(pasteAct);
 
     menuBar()->addSeparator();
 
     helpMenu = menuBar()->addMenu(tr("&Aide"));
     helpMenu->addAction(aboutAct);
     helpMenu->addAction(aboutQtAct);
 }

Créer des actions ne suffit pas à les rendre accessibles à l'utilisateur ; nous devons aussi les ajouter au système de menu. C'est ce que fait createMenus(). Nous créons un menu File (fichier), Edit (modifier) et Help (aide). QMainWindow::menuBar() nous permet d'accéder à la barre de menu de la fenêtre. Nous n'avons pas besoin de créer la barre de menu nous-mêmes ; elle est créée la première fois que nous appelons cette fonction.

Juste avant de créer le menu Help, nous appelons QMenuBar::addSeparator(). Cela n'a pas d'effet pour la plupart des styles de widgets (par exemple les styles Windows et Mac OS X) mais pour les styles basés sur Motif cela a pour effet de cadrer Help à droite de la barre de menu. Essayez de lancer l'application avec différents styles pour voir le résultat :

 application -style=windows
 application -style=motif
 application -style=cde

Étudions maintenant les barres d'outils :

 void MainWindow::createToolBars()
 {
     fileToolBar = addToolBar(tr("Fichier"));
     fileToolBar->addAction(newAct);
     fileToolBar->addAction(openAct);
     fileToolBar->addAction(saveAct);
 
     editToolBar = addToolBar(tr("Édition"));
     editToolBar->addAction(cutAct);
     editToolBar->addAction(copyAct);
     editToolBar->addAction(pasteAct);
 }

La création de barres d'outils est très similaire à la création de menus. Les actions que nous associons aux menus peuvent être réutilisées dans les barres d'outils.

 void MainWindow::createStatusBar()
 {
     statusBar()->showMessage(tr("Prêt"));
 }

QMainWindow::statusBar() renvoie un pointeur sur le widget QStatusBar de la fenêtre principale. Comme pour QMainWindow::menuBar(), le widget est créé automatiquement la première fois que la fonction est appelée.

 void MainWindow::readSettings()
 {
     QSettings settings("Trolltech", "Exemple Application");
     QPoint pos = settings.value("pos", QPoint(200, 200)).toPoint();
     QSize size = settings.value("size", QSize(400, 400)).toSize();
     resize(size);
     move(pos);
 }

La fonction readSettings() est appelée depuis le constructeur pour charger les préférences utilisateur et les autres paramètres de l'application. La classe QSettings fournit une interface de haut niveau pour l'enregistrement permanent de paramètres sur disque. Sur Windows, elle utilise la (trop) célèbre base de registres Windows ; sur Mac OS X, elle utilise l'API native CFPreferences basée sur XML ; sur Unix/X11, elle utilise des fichiers texte.

Le constructeur de QSettings prend des arguments identifiant votre organisation et le nom du produit. Cela permet aux paramètres de différentes applications d'être conservés séparément.

Nous utilisons QSettings::value() pour extraire la valeur des paramètres pos et size. Le second argument de QSettings::value() est optionnel et spécifie une valeur par défaut pour le paramètre. Cette valeur est utilisée la première fois que l'application est lancée.

Au moment de restaurer la position et la taille d'une fenêtre, il est important d'appeler QWidget::resize() avant QWidget::move(). L'explication est donnée dans la documentation Géométrie de la fenêtre.

 void MainWindow::writeSettings()
 {
     QSettings settings("Trolltech", "Exemple Application");
     settings.setValeur("pos", pos());
     settings.setValeur("size", size());
 }

La fonction writeSettings() est appelée depuis closeEvent(). L'écriture des paramètres est similaire à leur lecture, en plus simple. Les arguments du constructeur de QSettings sont les mêmes que pour readSettings().

 bool MainWindow::maybeSave()
 {
     if (textEdit->document()->isModified()) {
         QMessageBox::StandardButton ret;
         ret = QMessageBox::warning(this, tr("Application"),
                      tr("Le document a été modifié.\n"
                         "Voulez-vous sauvegarder vos changements ?"),
                      QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel);
         if (ret == QMessageBox::Save)
             return save();
         else if (ret == QMessageBox::Cancel)
             return false;
     }
     return true;
 }

La fonction maybeSave() est appelée pour sauvegarder les changements en cours. Si des changements sont en cours, elle ouvre une QMessageBox permettant à l'utilisateur de sauvegarder le document. Les options sont QMessageBox::Yes, QMessageBox::No et QMessageBox::Cancel. Le bouton Yes est spécifié comme le bouton par défaut (le bouton invoqué lorsque l'utilisateur presse Return) en utilisant le drapeau QMessageBox::Default ; le bouton Cancel est spécifié comme le bouton d'échappement (le bouton invoqué lorsque l'utilisateur presse Esc) en utilisant le drapeau QMessageBox::Escape.

La fonction maybeSave() renvoie true dans tous les cas, sauf quand l'utilisateur clique Cancel. L'appelant doit vérifier la valeur de retour et arrêter le traitement en cours si la valeur de retour est false.

 void MainWindow::loadFile(const QString &fileName)
 {
     QFile file(fileName);
     if (!file.open(QFile::ReadOnly | QFile::Text)) {
         QMessageBox::warning(this, tr("Application"),
                              tr("Impossible de lire le fichier %1:\n%2.")
                              .arg(fileName)
                              .arg(file.errorString()));
         return;
     }
 
     QTextStream in(&file);
 #ifndef QT_NO_CURSOR
     QApplication::setOverrideCursor(Qt::WaitCursor);
 #endif
     textEdit->setPlainText(in.readAll());
 #ifndef QT_NO_CURSOR
     QApplication::restoreOverrideCursor();
 #endif
 
     setCurrentFile(fileName);
     statusBar()->showMessage(tr("Fichier chargé"), 2000);
 }

Dans loadFile(), nous utilisons QFile et QTextStream pour lire les données. L'objet QFile donne accès aux octets stockés dans le fichier.

Nous commençons par ouvrir le fichier en mode lecture seule. Le drapeau QFile::Text indique que le fichier est de type texte et pas un fichier binaire. Sur Unix et Mac OS X, cela ne fait pas de différence, mais sur Windows, cela permet la transformation des séquences de fin de ligne « \r\n » en « \n » à la lecture.

Si nous réussissons à ouvrir le fichier, nous utilisons un objet QTextStream pour lire les données. QTextStream convertit automatiquement les données 8 bits en QString Unicode et gère divers encodages. Si aucun encodage n'est spécifié, QTextStream suppose que le fichier est écrit dans l'encodage 8 bits par défaut du système (par exemple, Latin-1 ; voir QTextCodec::codecForLocale() pour les détails).

Étant donné que l'appel à QTextStream::readAll() peut être long, nous définissons le pointeur Qt::WaitCursor comme pointeur de l'application entière pour la durée de l'opération.

Enfin, nous appelons la fonction privée setCurrentFile(), que nous allons expliquer dans la suite, et nous affichons la chaîne « File loaded » (« Fichier chargé ») dans la barre d'état pendant 2 secondes (2000 millisecondes).

 bool MainWindow::saveFile(const QString &fileName)
 {
     QFile file(fileName);
     if (!file.open(QFile::WriteOnly | QFile::Text)) {
         QMessageBox::warning(this, tr("Application"),
                              tr("Impossible d'écrire dans le fichier %1:\n%2.")
                              .arg(fileName)
                              .arg(file.errorString()));
         return false;
     }
 
     QTextStream out(&file);
 #ifndef QT_NO_CURSOR
     QApplication::setOverrideCursor(Qt::WaitCursor);
 #endif
     out << textEdit->toPlainText();
 #ifndef QT_NO_CURSOR
     QApplication::restoreOverrideCursor();
 #endif
 
     setCurrentFile(fileName);
     statusBar()->showMessage(tr("Fichier sauvegardé"), 2000);
     return true;
 }

L'enregistrement d'un fichier est très similaire à son chargement. Ici, le drapeau QFile::Text permet la conversion sur Windows du « \n » en « \r\n » pour se conformer à la convention du système.

 void MainWindow::setCurrentFile(const QString &fileName)
 {
     curFile = fileName;
     textEdit->document()->setModified(false);
     setWindowModified(false);
 
     QString shownName = curFile;
     if (curFile.isEmpty())
         shownName = "untitled.txt";
     setWindowFilePath(shownName);
 }

La fonction setCurrentFile() est appelée pour réinitialiser l'état de quelques variables lorsqu'un fichier est chargé ou enregistré ou lorsque l'utilisateur commence à éditer un nouveau fichier (dans ce dernier cas fileName est vide). Nous mettons à jour la variable curFile, désactivons le drapeau QTextDocument::modified et le drapeau associé QWidget:windowModified et mettons à jour le titre de la fenêtre avec le nouveau nom de fichier (ou untitled.txt).

L'appel à la fonction strippedName() autour de curFile dans l'appel QWidget::setWindowTitle() raccourcit le nom de fichier en excluant le chemin. Voici la fonction :

 QString MainWindow::strippedName(const QString &fullFileName)
 {
     return QFileInfo(fullFileName).fileName();
 }

La fonction

main()

de cette application est typique des applications qui contiennent une fenêtre principale :

 #include <QApplication>
 
 #include "mainwindow.h"
 
 int main(int argc, char *argv[])
 {
     Q_INIT_RESOURCE(application);
 
     QApplication app(argc, argv);
     app.setOrganizationName("Trolltech");
     app.setApplicationName("Exemple Application");
     MainWindow mainWin;
     mainWin.show();
     return app.exec();
 }

Le fichier de ressources

Vous vous souvenez probablement que pour certaines actions, nous avons spécifié des icônes avec des noms de fichiers commençant par : et mentionné que ces noms de fichiers ne sont pas des noms de fichiers ordinaires mais des chemins dans les ressources enregistrées de l'exécutable. Ces ressources sont compilées.

Les ressources associées à une application sont spécifiées dans un fichier .qrc, un format de fichier basé sur XML qui liste des fichiers présents sur le disque. Voici le fichier application.qrc utilisé par notre exemple Application :

 <!DOCTYPE RCC><RCC version="1.0">
 <qresource>
     <file>images/copy.png</file>
     <file>images/cut.png</file>
     <file>images/new.png</file>
     <file>images/open.png</file>
     <file>images/paste.png</file>
     <file>images/save.png</file>
 </qresource>
 </RCC>

Les fichiers .png listés dans le fichier application.qrc sont des fichiers présents dans l'arborescence des sources de l'exemple Application. Les chemins sont relatifs au répertoire où se trouve le fichier application.qrc (le répertoire mainwindows/application).

Le fichier de ressources doit être mentionné dans le fichier application.pro pour que qmake le prenne en compte :

 RESOURCES     = application.qrc

qmake produira des règles de génération d'un fichier qrc_application.cpp qui sera compilé dans l'application. Ce fichier contient les données des images et des autres ressources sous forme de tableaux C++ statiques contenant des données binaires compressées. Voir Le système de ressources de Qt pour plus d'informations sur les ressources.

Remerciements

Merci à <!idiallo!> pour la traduction et à <!johnlamericain!>, <!ClaudeLELOUP!> et <!jacques_jean!> pour leur relecture !

Warning: include(): https:// wrapper is disabled in the server configuration by allow_url_include=0 in /home/developpez/www/developpez-com/upload/qt/doc/bs.php on line 4 Warning: include(https://qt.developpez.com/index/rightColumn): failed to open stream: no suitable wrapper could be found in /home/developpez/www/developpez-com/upload/qt/doc/bs.php on line 4 Warning: include(): Failed opening 'https://qt.developpez.com/index/rightColumn' for inclusion (include_path='.:/usr/php53/lib/php') in /home/developpez/www/developpez-com/upload/qt/doc/bs.php on line 4
Cette page est une traduction d'une page de la documentation de Qt, écrite par Nokia Corporation and/or its subsidiary(-ies). Les éventuels problèmes résultant d'une mauvaise traduction ne sont pas imputables à Nokia. Qt 4.7
Copyright © 2018 Developpez LLC. Tous droits réservés Developpez LLC. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez LLC. Sinon, vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts. Cette page est déposée à la SACD.
Vous avez déniché une erreur, une redirection cassée ou tout autre problème, quel qu'il soit ? Ou bien vous désirez participer à ce projet de traduction ? N'hésitez pas à nous contacter ou par MP !
Responsable bénévole de la rubrique Qt : Thibaut Cuvelier -