Connexion au Web

| David Boddie | traducteur : Thibaut Cuvelier | Qt Quarterly |

L'apparition de WebKit dans Qt 4.4 ouvre le monde du web aux applications Qt, en effaçant les frontières entre les applications locales traditionnelles et les services en ligne. Dans cet article, nous allons voir une manière d'utiliser cette approche hybride dans le développement d'applications.

Cet article est une traduction autorisée de Plugging into the Web, par David Boddie.

Commentez

- Titre : Connexion au Web

- Auteurs : David Boddie, traducteur : Thibaut Cuvelier, Qt Quarterly

- Parution : 18 juin 2009

- Mise à jour : 1 août 2011

- Licence : Copyright ® 2009 David Boddie. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- I. L'article original

- II. Introduction

- III. Premiers pas avec QtWebKit

- IV. Widgets dans une page

- V. Un exemple simple

- VI. Combler le manque

- VII. Lier le tout

- VIII. Divers

I - L'article original

Qt Quarterly est une revue trimestrielle électronique proposée par Nokia à destination des développeurs et utilisateurs de Qt. Vous pouvez trouver les versions originales.

Nokia, Qt, Qt Quarterly et leurs logos sont des marques déposées de Nokia Corporation en Finlande et/ou dans les autres pays. Les autres marques déposées sont détenues par leurs propriétaires respectifs.

Cet article est la traduction de l'article Plugging into the Web de David Boddie paru dans la Qt Quarterly Issue 26.

Cet article est une traduction d'un des tutoriels écrits par Nokia Corporation and/or its subsidiary(-ies) inclus dans la documentation de Qt, en anglais. Les éventuels problèmes résultant d'une mauvaise traduction ne sont pas imputables à Nokia.

II - Introduction

Les navigateurs s'enrichissent sans cesse, les interfaces Web 2.0 se démocratisent : de nombreux développeurs utilisent le Web comme boîte à outils graphiques. Bien que les interfaces en ligne aient été améliorées depuis les débuts de l'Internet, de temps en temps, des utilisateurs ont vraiment besoin d'accéder à des widgets natifs.

À travers quelques exemples simples, nous montrerons comment intégrer des widgets Qt à une page web, et jetterons un oeil à une manière d'intégrer des composants Qt avec du contenu dynamique en ligne.

III - Premiers pas avec QtWebKit

QtWebKit propose une intégration sur deux niveaux de WebKit à Qt. À bas niveau, Qt fournit des widgets sur lequels les pages web seront rendues. À haut niveau, une série de classes représentent toutes les fonctionnalités d'un navigateur habituel.

QWebView est un widget utilisé pour afficher des pages web. QWebPage représente le contenu d'une page ; QWebFrame représente une frame individuelle dans une page web. Le code pour afficher une page web est très simple.

int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QWebView view;
view.load(QUrl("http://www.nokia.com/"));
view.show();
return app.exec();
}

Ce widget propose les fonctionnalités indispensables d'un navigateur : support du CSS et du JavaScript. D'autres technologies peuvent être ajoutées pour une expérience plus complète.

IV - Widgets dans une page

Puisque Qt est utilisé pour rendre des pages web, il est possible d'intégrer des widgets standards et personnalisés. Tout ce que nous devons faire est de placer quelques balises pour indiquer l'emplacement du widget, et d'un mécanisme pour savoir quand le créer.

La balise qui est utilisée fait partie du standard HTML4 : <object>, qui sert à intégrer tous types d'objets dans une page web. Lors de la description du widget à afficher, il y a en général trois paramètres : data, qui indique l'endroit où les données peuvent être récupérées, width et height, pour indiquer les dimensions du widget.

Voici la manière dont nous décririons un tel objet.

<object type="text/csv;header=present;charset=utf8"
data="qrc:/data/accounts.csv" width="100%" height="300"></object>

QtWebKit utilise le mécanisme de l'usine de plug-ins pour intégrer des widgets à des pages web. Les usines sont dérivées de QWebPluginFactory, et peuvent fournir plus qu'un widget.

V - Un exemple simple

Pour montrer la manière d'utiliser l'usine, nous allons implémenter un simple widget qui peut afficher le contenu d'un fichier CSV (Comma-Separated Values, format de fichier de stockage de données). Le widget CSVView est simplement dérivé de QTableView, avec quelques fonctions supplémentaires pour mettre en place le modèle interne des données. Les instances de l'usine CSVFactory sont responsables de la création de widgets et de la demande des données en leur nom.

class CSVFactory : public QWebPluginFactory
{
Q_OBJECT

public:
CSVFactory(QObject *parent = 0);
QObject *create(const QString &mimeType,
const QUrl &url, const QStringList &argumentNames,
const QStringList &argumentValues) const;
QList<QWebPluginFactory::Plugin> plugins() const;

private:
QNetworkAccessManager *manager;
};

Les fonctions publiques donnent un bon aperçu de la manière dont QtWebKit va utiliser l'usine pour créer des widgets. Nous allons commencer par le constructeur.

CSVFactory::CSVFactory(QObject *parent)
: QWebPluginFactory(parent)
{
manager = new QNetworkAccessManager(this);
};

L'usine contient une gestionnaire d'accès réseau qu'elle va utiliser pour récupérer les données dont elle aura besoin pour son widget.

La fonction plugins() est utilisée pour récupérer des informations sur les widgets qui peuvent être construits par l'usine. Notre implémentation renvoie le type MIME qu'elle attend et fournit une description du plug-in.

QList<QWebPluginFactory::Plugin> CSVFactory::plugins()
const
{
QWebPluginFactory::MimeType mimeType;
mimeType.name = "text/csv";
mimeType.description = "Comma-separated values";
mimeType.fileExtensions = QStringList() << "csv";

QWebPluginFactory::Plugin plugin;
plugin.name = "CSV file viewer";
plugin.description = "A CSV file Web plugin.";
plugin.mimeTypes = QList<MimeType>() << mimeType;

return QList<QWebPluginFactory::Plugin>() << plugin;
}

La majeure partie de l'action se déroule dans la fonction create(). Elle est appelée avec un type MIME qui décrit le type de données à afficher, une URL qui pointe vers ces données, et des informations à propos de tous les paramètres passés par la page web. Nous commençons par chercher l'information basique sur le type MIME passée en paramètre, et ne continuons que si nous la reconnaissons.

QObject *CSVFactory::create(const QString &mimeType,
const QUrl &url, const QStringList &argumentNames,
const QStringList &argumentValues) const
{
if (mimeType != "text/csv")
return 0;

CSVView *view = new CSVView(
argumentValues[argumentNames.indexOf("type")]);

Nous construisons un widget en utilisant les informations transmises par le type MIME pleinement spécifié, ce qui est garanti d'être dans la liste des arguments si un type MIME a été spécifié.

QNetworkRequest request(url);
QNetworkReply *reply = manager->get(request);
connect(reply, SIGNAL(finished()),
view, SLOT(updateModel()));
connect(reply, SIGNAL(finished()),
reply, SLOT(deleteLater()));

return view;
}

Finalement, nous utilisons le gestionnaire de connexion réseau pour chercher les données spécifiées dans le paramètre url. Nous connectons son signal finished() au slot updateModel() de la vue, pour qu'elle puisse utiliser ces données. L'objet de la requête est intentionnellement créé sur le tas. Le signal finished() est connecté au slot deleteLater(), pour s'assurer que Qt le détruira dès qu'il n'est plus nécessaire.

La classe CSVView ne propose que peu d'extensions à QTableView, avec un slot public pour s'occuper des données qui arrivent, et une variable privée pour enregistrer l'information exacte du type MIME.

class CSVView : public QTableView
{
Q_OBJECT

public:
CSVView(const QString &mimeType, QWidget *parent = 0);

public slots:
void updateModel();

private:
void addRow(bool firstLine, QStandardItemModel *model,
const QList<QStandardItem *> &items);

QString mimeType;
};

Le nouveau constructeur n'est utilisé que pour stocker le type MIME des données.

CSVView::CSVView(const QString &mimeType, QWidget *parent)
: QTableView(parent)
{
this->mimeType = mimeType;
}

Nous allons seulement regarder de plus près quelques parties de updateModel(), qui commence par rapatrier l'objet QNetworkReply qui a causé son exécution avant de vérifier s'il y a eu des erreurs.

void CSVView::updateModel()
{
QNetworkReply *reply =
static_cast<QNetworkReply *>(sender());

if (reply->error() != QNetworkReply::NoError)
return;

bool hasHeader = false;
QString charset = "latin1";

En supposant que les données soient correctes, nous devons déterminer si le CSV contient un entête, et deviner quel encodage a été utilisé. Ces deux informations peuvent être stockées dans l'information complète du type MIME, nous le vérifions donc avant de continuer, ce qui est montré dans cet exemple.

QTextStream stream(reply);
stream.setCodec(
QTextCodec::codecForName(charset.toLatin1()));

QStandardItemModel *model =
new QStandardItemModel(this);

Vu que QNetworkReply dérive de QIODevice, la réponse peut être lue en utilisant un flux de texte correctement configuré, les données pouvent être stockées dans un conteneur standard. Les mécanismes utilisés dépassent le cadre de cet article. À la fin de cette fonction, nous fermons l'objet de la réponse et appliquons le modèle à la vue.

reply->close();

setModel(model);
resizeColumnsToContents();
horizontalHeader()->setStretchLastSection(true);
}

Dès que la réponse a été lue, rempli de données, le plug-in n'a presque plus besoin de faire quelque chose. La possession du widget de la vue est gérée autre part, et nous nous sommes assurés que le modèle sera détruit quand il ne sera plus nécessaire, en le faisant objet enfant de la vue.

Regardons rapidement le code de MainWindow.

MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
QWebSettings::globalSettings()->setAttribute(
QWebSettings::PluginsEnabled, true);

QWebView *webView = new QWebView;
CSVFactory *factory = new CSVFactory(webView, this);
webView->page()->setPluginFactory(factory);
QFile file(":/pages/index.html");
file.open(QFile::ReadOnly);
webView->setHtml(file.readAll());

setCentralWidget(webView);
}

À part la création de l'usine et son application sur la QWebPage, la tâche la plus importante est l'activation des plug-ins web. Si ce paramètre global n'est pas défini, les plug-ins ne seront pas utilisés et les balises <object> seront simplement ignorées.

VI - Combler le manque

Pouvoir insérer des widgets dans une page web est assez utile, mais nous pouvons aussi les laisser communiquer avec le reste de la page, voire même la modifier. Actuellement, cela se fait grâce au moteur JavaScript inclus dans WebKit. Pour illustrer ceci, nous modifions l'exemple précédent pour que la sélection d'une colonne entraîne la mise à jour de trois champs de saisie dans un formulaire HTML.

Pour effectuer cette communication, nous devons utiliser une version mise à jour de notre widget CSVView pour qu'il puisse envoyer un signal dès qu'une colonne est sélectionnée. nous avons besoin d'une fonction JavaScript pour modifier les éléments de la page. Finalement, d'un peu de code pour effectuer la communication.

Sur la page, le formulaire et le plug-in sont ainsi déclarés.

<object type="text/csv;header=present;charset=utf8"
data="qrc:/data/accounts.csv" width="100%" height="300">
<param name="related" value="customers"></param>
</object>

<form>
<input id="customers_name" name="name" ... > ...
<input id="customers_address" name="address" ... > ...
<input id="customers_quantity" name="quantity" ... > ...
</form>

Dans la balise <object>, nous incluons un élément <param>, qui renvoie aux identifiants des éléments à modifier dans le <form> qui convient. L'attribut related vaut "customers". Le formulaire qui devra être modifié contient des éléments <input> avec des id qui sont dérivés de cet identifiant.

Dans cet exemple, quand l'usine crée le widget de la vue, nous saisissons l'opportunité de passer l'identifiant au constructeur de notre widget.

QObject *CSVFactory::create(...) const
{
CSVView *view = new CSVView(arguments["type"],
arguments["related"]);

Nous exposons aussi le widget de la vue à la frame dans la page qui contient les éléments, paramétrons une connexion entre la vue et la fonction JavaScript définie dans l'entête de la page.

QWebFrame *frame = webView->page()->mainFrame();
frame->addToJavaScriptWindowObject(
arguments["related"] + "_input", view);
frame->evaluateJavaScript(
arguments["related"] +
"_input.rowSelected.connect(fillInForm);\n");
}

Pour la page montrée ci-dessus, la vue est ajoutée à la page en tant que customers_input, et le code de connexion revient à écrire ceci.

customers_input.rowSelected.connect(fillInForm);

Où fillInForm est le nom de la fonction JavaScript qui modifiera les éléments du formulaire. Cette fonction attend 4 arguments : l'identifiant du formulaire à modifier, et les nom, adresse et quantité pour une ligne de données.

Le signal rowSelected() est un nouveau signal qui doit donner un peu de travail à fillInForm(). Nous fournissons aussi un slot interne, qui sort les données du modèle et émet le signal.

class CSVView : public QTableView
{
Q_OBJECT

public:
CSVView(const QString &mimeType,
const QString &related, QWidget *parent = 0);

signals:
void rowSelected(const QString &form,
const QString &name, const QString &address,
const QString &quantity);

public slots:
void updateModel();

private slots:
void exportRow(const QModelIndex &current);
};

Dans le slot updateModel() de la vue, en plus de stocker les données dans un QStandardItemModel, le signal currentChanged() du modèle de sélection de la vue est connecté au slot exportRow().

Ainsi, dès qu'un utilisateur sélectionne une ligne dans le tableau, le slot exportRow() est appelé, les données de la ligne sélectionnée sont extraites du modèle et émises dans le signal rowSelected() sous la forme trois QString, et la fonction JavaScript fillInForm() est appelée avec ces trois paramètres. Les conversions de type sont automatiquement effectuées, pour s'assurer que tous les QString soient convertis en objets string du JavaScript.

Voici la fonction JavaScript, pour montrer ce qu'elle fait avec les chaînes qui lui sont données. La fonction est définie dans l'entête de la page.

function fillInForm(form, name, address, quantity)
{
var nameElement = document.getElementById(form+"_name");
var addressElement = document.getElementById(form+"_address");
var quantityElement = document.getElementById(form+"_quantity");

nameElement.value = name;
addressElement.value = address;
quantityElement.value = quantity;
}

Nous utilisons l'identifiant passé dans l'argument form pour dériver les noms pour les attributs id utilisés dans les éléments du formulaire, et obtenir ces éléments en utilisant l'API DOM. Les valeurs de ces éléments sont mises à jour avec les chaînes spécifiées.

VII - Lier le tout

Qt fournit une panoplie de widgets qui peuvent être intégrés avec QWebPluginFactory pour créer des navigateurs spécifiques pour accompagner des applications. Des widgets OpenGL peuvent être utilisés pour créer des navigateurs permettant de visualiser des modèles 3D. Le framework Graphics View fournit de bonnes bases pour d'autres types de visionneurs interactifs.

Bien que nous ayons utilisé les widgets pour démontrer l'utilisation des slots et des signaux pour la communication entre des composants de Qt et des fonctions JavaScript, il n'est pas nécessaire d'inclure des widgets dans une page pour pouvoir le faire. En insérant des objets dans la page, et en évaluant du JavaScript, des applications Qt peuvent examiner et utiliser des informations disponibles en ligne.

Puisque WebKit est un moteur web complet, cette approche peut être utilisée pour intégrer des applications et des services en ligne, en particulier ceux qui utilisent des API JavaScript bien définies. Les techniques montrées peuvent aussi servir à récolter des informations de différentes sources, de les rassembler et de les combiner.

Le support de Qt pour le contenu web s'améliore en même temps que celui de WebKit, le développement de ce dernier avançant rapidement. La majorité des nouvelles fonctionnalités sont supportées dans les snapshots journaliers de Qt, et les développeurs annoncent les plus importantes sur le blog des Qt Labs.

VIII - Divers

Le code source de cet article.

Au nom de toute l'équipe Qt, j'aimerais adresser le plus grand remerciement à Nokia pour nous avoir autorisé la traduction de cet article !

J'aimerais aussi adresser un immense merci à IrmatDen pour sa courageuse relecture !