TP : zNavigo, le navigateur web des Zéros !
Depuis le temps que vous pratiquez Qt, vous avez acquis sans vraiment le savoir les capacités de base pour réaliser des programmes complexes. Le but d'un TP comme celui-ci, c'est de vous montrer justement que vous êtes capables de mener à bien des projets qui auraient pu vous sembler complètement fous il y a quelques temps.
Vous ne rêvez pas : le but de ce TP sera de...
réaliser un navigateur web !
Quoi ? Je suis capable de faire ça moi ?

Oui, et nous allons voir comment dans ce chapitre !
Nous allons commencer dans un premier temps par découvrir la notion de
moteur web, pour bien comprendre comment fonctionnent les autres navigateurs. Puis, nous mettrons en place le plan du développement de notre programme afin de nous assurer que nous partons dans la bonne direction et que nous n'oublions rien.
Firefox n'a qu'à bien se tenir.
Comme toujours, il faut d'abord prendre le temps de
réfléchir à son programme avant de foncer le coder tête baissée. C'est ce qu'on appelle la phase de
conception.
Je sais, je me répète à chaque fois, mais c'est vraiment parce que c'est très important. Si je vous dis "
faites-moi un navigateur web" et que vous créez de suite un nouveau projet en vous demandant ce que vous allez bien pouvoir mettre dans le
main, ben... c'est le ramassage assuré.
Pour moi, la conception est l'étape la plus difficile du projet. Plus difficile même que le code. En effet, si vous concevez bien votre programme, si vous réfléchissez bien à la façon dont il doit fonctionner, vous aurez simplifié à l'avance votre projet et vous n'aurez pas à écrire des lignes de code difficiles inutilement.
Dans un premier temps, je vais vous expliquer comment fonctionne un navigateur web. Un peu de culture générale à ce sujet vous permettra de mieux comprendre ce que vous avez à faire (et ce que vous n'avez pas à faire).
Je vous donnerai ensuite quelques conseils pour organiser votre code : quelles classes créer, par quoi commencer, etc.
Les principaux navigateurs
Commençons par le commencement : vous savez ce qu'est un navigateur web ?
Bon, je ne me moque pas de vous, mais il vaut mieux être sûr de ne perdre personne.
Un navigateur web est un programme qui permet de consulter des sites web.
Parmi les plus connus d'entre eux, citons Internet Explorer, Mozilla Firefox ou encore Safari. Mais il y en a aussi beaucoup d'autres, certes moins utilisés, comme Opera, Konqueror, Epiphany, Maxthon, Lynx...
Je vous rassure, il n'est pas nécessaire de tous les connaître pour pouvoir prétendre en créer un.
Par contre, ce qu'il faut que vous sachiez, c'est que chacun de ces navigateurs est constitué de ce qu'on appelle un
moteur web. Qu'est-ce que c'est que cette bête-là ?
Le moteur web
Tous les sites web sont écrits en langage HTML (ou XHTML). Voici un exemple de code HTML permettant de créer une page très simple :
Code : HTML1
2
3
4
5
6
7
8
9 | <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="fr" >
<head>
<title>Bienvenue sur mon site !</title>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
</head>
<body>
</body>
</html>
|
C'est bien joli tout ce code, mais ça ne ressemble pas au résultat visuel qu'on a l'habitude de voir lorsqu'on navigue sur le web.
L'objectif est justement de transformer ce code en un résultat visuel : le site web.
C'est le rôle du moteur web.
Voici son fonctionnement, résumé dans un schéma très simple :
Ca n'a l'air de rien, mais c'est un travail difficile : réaliser un moteur web est très délicat. C'est généralement le fruit des efforts de nombreux programmeurs experts (et encore, ils avouent avoir du mal

). Certains moteurs sont meilleurs que d'autres, mais aucun n'est parfait ni complet. Comme le web est en perpétuelle évolution, il est peu probable qu'un moteur parfait sorte un jour.
Quand on programme un navigateur, on utilise généralement le moteur web sous forme de bibliothèque.
Le moteur web n'est donc pas un programme, mais il est utilisé par des programmes.
...
...
Ce sera plus clair avec un schéma.

Regardons comment est constitué Firefox par exemple :
On voit que le navigateur (en vert) "contient" le moteur web (en jaune au centre).
La partie en vert est habituellement appelée le "chrome", pour désigner l'interface.
Mais c'est nul ! Alors le navigateur web c'est juste les 2-3 boutons en haut et c'est tout ?
Oh non ! Loin de là.
Le navigateur ne se contente pas de gérer les boutons "Précédente", "Suivante", "Actualiser", etc. C'est aussi lui qui gère les marque-pages (favoris), le système d'onglets, les options d'affichage, la barre de recherche, etc.
Tout cela représente déjà un énorme travail ! En fait, les développeurs de Firefox ne sont pas les mêmes que ceux qui développent son moteur web. Il y a des équipes séparées, tellement chacun de ces éléments représente du travail.
Les principaux navigateurs et leurs moteurs
Un grand nombre de navigateurs ne s'occupent pas du moteur web. Ils en utilisent un "tout prêt".
De nombreux navigateurs sont basés sur le même moteur web. Voici un petit schéma de mon crû qui vous permet de vous donner une idée un peu de "qui utilise quoi" :
Les noms des moteurs web ne sont pas connus du grand public. D'ailleurs, il est probable que vous n'ayez entendu parler d'aucun d'eux jusqu'à aujourd'hui. Ce qui est connu, c'est le navigateur, alors que c'est le moteur web qui se tape tout le sale boulot.
Je n'ai pas mis tous les navigateurs et moteurs web existants, mais cela permet déjà d'avoir une bonne idée de ce qui se passe.
Comme vous le voyez, rares sont les navigateurs à avoir leur propre moteur web. On peut noter l'exception d'Opera (et encore, le moteur a été revendu à Adobe qui ne voulait pas en coder un pour son logiciel Dreamweaver).
Tout ça pour dire quoi ? Eh bien déjà que
créer un moteur web n'est ni de votre niveau, ni du mien. Comme de nombreux navigateurs, nous en utiliserons un déjà existant.
Lequel ? Eh bien il se trouve que Qt (oui, parce qu'on parle de Qt ici, j'espère que vous n'avez pas oublié

), Qt donc vous propose depuis peu d'utiliser le moteur WebKit dans vos programmes. C'est donc ce moteur-là que nous allons utiliser pour créer notre navigateur.
Configurer son projet pour utiliser WebKit
WebKit est un des nombreux modules de Qt. Il ne fait pas partie du module "GUI", dédié à la création de fenêtres, il s'agit d'un module à part.
Pour pouvoir l'utiliser, il faudra modifier le fichier .pro du projet pour que Qt sache qu'il a besoin de charger WebKit.
Voici un exemple de fichier .pro qui indique que le projet utilise WebKit :
Code : Autre1
2
3
4
5
6
7
8
9
10
11
12
13
| ######################################################################
# Automatically generated by qmake (2.01a) mer. 18. juin 11:49:49 2008
######################################################################
TEMPLATE = app
QT += webkit
TARGET =
DEPENDPATH += .
INCLUDEPATH += .
# Input
HEADERS += FenPrincipale.h
SOURCES += FenPrincipale.cpp main.cpp |
Le plus simple est de faire un
qmake -project d'abord pour générer le fichier .pro, puis de rajouter la ligne que j'ai surlignée :
QT += webkit
D'autre part, vous devrez rajouter l'include suivant dans les fichiers de votre code source faisant appel à WebKit :
Code : C++
Enfin, il faudra joindre 2 nouvelles DLL à votre programme pour qu'il fonctionne : QtWebKit4.dll et QtNetwork4.dll.
Ouf, tout est prêt.
Objectif
Avant d'aller plus loin, il me semble indispensable de vous montrer à quoi doit ressembler le navigateur une fois terminé.
Votre objectif est de réaliser un navigateur web semblable à celui-ci :
Parmi les fonctionnalités de ce super navigateur, affectueusement nommé "zNavigo", on compte :
- Accès aux pages précédentes et suivantes
- Arrêter le chargement de la page
- Actualiser la page
- Retour à la page d'accueil
- Saisie d'une adresse
- Navigation par onglets
- Affichage du pourcentage de chargement dans la barre d'état
Le menu "Fichier" propose d'ouvrir et de fermer un onglet, ainsi que de quitter le programme.
Le menu "Navigation" reprend le contenu de la barre d'outils (ce qui est très facile à faire grâce aux QAction je vous le rappelle).
Le menu "?" (aide) propose d'afficher les fenêtres "A propos..." et "A propos de Qt..." qui donnent des informations respectivement sur notre programme et sur Qt.
Ca n'a l'air de rien comme ça, mais ça représente déjà un sacré boulot !
Si vous avez du mal dans un premier temps, vous pouvez vous épargner la gestion des onglets... mais moi j'ai trouvé que c'était un peu trop simple sans les onglets, alors j'ai choisi de vous faire jouer avec, histoire de corser le tout.
Les fichiers du projet
J'ai l'habitude de faire une classe par fenêtre. Comme notre projet ne sera constitué (au moins dans un premier temps) que d'une seule fenêtre, nous aurons donc les fichiers suivants :
- main.cpp
- FenPrincipale.h
- FenPrincipale.cpp
Si vous voulez utiliser les mêmes icônes que moi, les voici :
Notez que la dernière est l'icône du programme (affichée en haut à gauche).
Utiliser QWebView pour afficher une page web
Le
QWebView est le principal nouveau widget que vous aurez besoin d'utiliser dans ce chapitre. Il permet d'afficher une page web. C'est lui le
moteur web.
Vous ne savez pas vous en servir, mais vous savez maintenant
lire la doc. Vous allez voir, ce n'est pas bien difficile !
Regardez en particulier les signaux et slots proposés par le QWebView. Il y a tout ce qu'il faut savoir pour, par exemple, connaître le pourcentage de chargement de la page pour le répercuter sur la barre de progression de la barre d'état (signal
loadProgress(int)
).
Comme l'indique la doc, pour créer le widget et charger une page, c'est très simple :
Code : C++1
2 | QWebView *pageWeb = new QWebView;
pageWeb->load(QUrl("http://www.siteduzero.com/"));
|
Voilà c'est tout ce que je vous expliquerai sur QWebView, pour le reste lisez la doc.
La navigation par onglets
Le problème de QWebView, c'est qu'il ne permet d'afficher qu'une seule page web à la fois. Il ne gère pas la navigation par onglets. Il va falloir implémenter le système d'onglets nous-mêmes.
Vous n'avez jamais entendu parler de QTabWidget ? Si si, souvenez-vous, nous l'avons découvert dans un des chapitres précédents. Ce widget-conteneur est capable d'accueillir n'importe quels widgets... comme un QWebView !
En combinant un QTabWidget et des QWebView (un par onglet), vous pourrez reconstituer un véritable navigateur par onglets !
Une petite astuce toutefois, qui pourra vous être bien utile : savoir retrouver un widget contenu dans un widget parent.
Comme vous le savez, le QTabWidget utilise des sous-widgets pour gérer chacune des pages. Ces sous-widgets sont généralement des QWidget génériques (invisibles), qui servent à contenir d'autres widgets.
Dans notre cas :
QTabWidget contient des
QWidget (pages d'onglet) qui eux-mêmes
contiennent chacun un
QWebView (la page web).
La méthode findChild (définie dans QObject) permet de retrouver le widget enfant contenu dans le widget parent.
Par exemple, si je connais le QWidget "pageOnglet", je peux retrouver le QWebView qu'il contient comme ceci :
Code : C++1 | QWebView *pageWeb = pageOnglet->findChild<QWebView *>();
|
Mieux encore, je vous donne la méthode toute faite qui permet de retrouver le QWebView actuellement visualisé par l'utilisateur :
Code : C++1
2
3
4 | QWebView *FenPrincipale::pageActuelle()
{
return onglets->currentWidget()->findChild<QWebView *>();
}
|
"onglets" correspond au QTabWidget.
Sa méthode currentWidget() permet d'obtenir un pointeur vers le QWidget qui sert de page pour la page actuellement affichée.
On demande ensuite à retrouver le QWebView que le QWidget contient à l'aide de la méthode findChild(). Cette méthode utilise les templates C++ (avec <QWebView *>). Je ne vais pas rentrer dans les détails ici car ce serait un peu trop long, mais en gros cela permet de faire en sorte que la méthode retourne bien un QWebView * (sinon elle n'aurait pas su quoi renvoyer).
J'admets, c'est un petit peu compliqué, mais au moins ça pourra vous aider.
Let's go !
Voilà, vous savez déjà tout ce qu'il faut pour vous en sortir.
Notez que ce TP fait la part belle à la QMainWindow, n'hésitez donc pas à relire ce chapitre dans un premier temps pour bien vous remémorer son fonctionnement.
Pour ma part, j'ai choisi de coder la fenêtre "à la main" (pas de Qt Designer donc) car celle-ci est un peu complexe.
Comme il y a beaucoup d'initialisations à faire dans le constructeur, je vous conseille de les placer dans des méthodes que vous appellerez depuis le constructeur pour améliorer la lisibilité :
Code : C++1
2
3
4
5
6
7
8
9 | FenPrincipale::FenPrincipale()
{
creerActions();
creerMenus();
creerBarresOutils();
/* Autres initialisations */
}
|
Bon courage !
Commençons par les choses simples (et un peu répétitives).
main.cpp
Tout d'abord le main.cpp, qui ne devrait pas vous perturber :
Code : C++ 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 | #include <QApplication>
#include <QTranslator>
#include <QLocale>
#include <QLibraryInfo>
#include "FenPrincipale.h"
int main(int argc, char* argv[])
{
QApplication app(argc, argv);
// Traduction des chaînes prédéfinies par Qt dans notre langue
QString locale = QLocale::system().name();
QTranslator translator;
translator.load(QString("qt_") + locale, QLibraryInfo::location(QLibraryInfo::TranslationsPath));
app.installTranslator(&translator);
// Ouverture de la fenêtre principale du navigateur
FenPrincipale principale;
principale.show();
return app.exec();
}
|
Je me contente d'ouvrir la fenêtre principale. Les quelques lignes de code au début sont celles que je vous avais données il y a quelques chapitres, pour faire en sorte que les chaînes de caractères de base (Yes / No) soient traduites en français dans l'application.
Maintenant, créons la classe FenPrincipale, notre plus gros morceau.
FenPrincipale.h (première version)
Dans un premier temps, je ne crée que le squelette de la classe et ses premières méthodes, j'en rajouterai d'autres au fur et à mesure si besoin est.
Code : C++ 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42 | #ifndef HEADER_FENPRINCIPALE
#define HEADER_FENPRINCIPALE
#include <QtGui>
#include <QtWebKit>
class FenPrincipale : public QMainWindow
{
Q_OBJECT
public:
FenPrincipale();
private:
void creerActions();
void creerMenus();
void creerBarresOutils();
void creerBarreEtat();
private slots:
private:
QTabWidget *onglets;
QAction *actionNouvelOnglet;
QAction *actionFermerOnglet;
QAction *actionQuitter;
QAction *actionAPropos;
QAction *actionAProposQt;
QAction *actionPrecedente;
QAction *actionSuivante;
QAction *actionStop;
QAction *actionActualiser;
QAction *actionAccueil;
QAction *actionGo;
QLineEdit *champAdresse;
QProgressBar *progression;
};
#endif
|
La classe hérite de QMainWindow comme prévu. J'ai inclus QtGui et QtWebKit pour pouvoir utiliser le module GUI et le module WebKit (moteur web).
Mon idée c'est, comme je vous l'avais dit, de couper le constructeur en plusieurs sous-méthodes qui s'occupent chacune de créer une section différente de la QMainWindow : actions, menus, barre d'outils, barre d'état...
J'ai prévu une section pour les slots personnalisés mais je n'ai encore rien mis, je verrai au fur et à mesure.
Enfin, j'ai préparé les principaux attributs de la classe. En fin de compte, à part de nombreuses QAction, il n'y en a pas beaucoup. Je n'ai même pas eu besoin de mettre des objets de type QWebView : ceux-ci seront créés à la volée au cours du programme et on pourra les retrouver grâce à la méthode pageActuelle() que je vous ai donnée un peu plus tôt.
Voyons voir l'implémentation du constructeur et de ses sous-méthodes qui génèrent le contenu de la fenêtre.
Construction de la fenêtre
Direction FenPrincipale.cpp, on commence par le constructeur :
Code : C++ 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 | #include "FenPrincipale.h"
FenPrincipale::FenPrincipale()
{
// Génération des widgets de la fenêtre principale
creerActions();
creerMenus();
creerBarresOutils();
creerBarreEtat();
// Génération des onglets et chargement de la page d'accueil
onglets = new QTabWidget;
onglets->addTab(creerOngletPageWeb(tr("http://www.siteduzero.com")), tr("(Nouvelle page)"));
connect(onglets, SIGNAL(currentChanged(int)), this, SLOT(changementOnglet(int)));
setCentralWidget(onglets);
// Définition de quelques propriétés de la fenêtre
setMinimumSize(500, 350);
setWindowIcon(QIcon("images/znavigo.png"));
setWindowTitle(tr("zNavigo"));
}
|
Nous allons voir juste après le code des méthodes creerActions(), creerMenus(), etc. Ce code est un peu long et répétitif, pas très intéressant mais il fallait le faire.
Par contre, ce qui est intéressant ensuite dans le constructeur, c'est que l'on crée le QTabWidget et on lui ajoute un premier onglet. Pour la création d'un onglet, on va faire appel à une méthode "maison" creerOngletPageWeb() qui va se charger de créer le QWidget-page de l'onglet, ainsi que de créer un QWebView et de lui faire charger la page web envoyée en paramètre ("http://www.siteduzero.com" sera donc la page d'accueil par défaut

).
Vous noterez que l'on utilise la fonction de tr() partout, au cas où on voudrait traduire le programme par la suite. C'est une bonne habitude à prendre, même si on n'a pas forcément l'intention de traduire le programme au début (on peut toujours changer d'avis après).
On connecte enfin et surtout le signal currentChanged() du QTabWidget à un slot personnalisé changementOnglet() que l'on va devoir écrire. Ce slot sera appelé à chaque fois que l'utilisateur change d'onglet, pour, par exemple, mettre à jour l'URL dans la barre d'adresse ainsi que le titre de la page affiché en haut de la fenêtre.
Bon, il faut maintenant écrire les méthodes de génération des actions, des menus, etc. C'était un peu long et fastidieux mais je suis arrivé jusqu'au bout.

Ne vous laissez pas impressionner par la taille du code, je n'ai pas tout écrit d'un coup, j'y suis allé petit à petit.
Code : C++ 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82 | void FenPrincipale::creerActions()
{
actionNouvelOnglet = new QAction(tr("&Nouvel onglet"), this);
actionNouvelOnglet->setShortcut(tr("Ctrl+T"));
connect(actionNouvelOnglet, SIGNAL(triggered()), this, SLOT(nouvelOnglet()));
actionFermerOnglet = new QAction(tr("&Fermer l'onglet"), this);
actionFermerOnglet->setShortcut(tr("Ctrl+W"));
connect(actionFermerOnglet, SIGNAL(triggered()), this, SLOT(fermerOnglet()));
actionQuitter = new QAction(tr("&Quitter"), this);
actionQuitter->setShortcut(tr("Ctrl+Q"));
connect(actionQuitter, SIGNAL(triggered()), qApp, SLOT(quit()));
actionPrecedente = new QAction(QIcon("images/precedente.png"), tr("&Precedente"), this);
actionPrecedente->setShortcut(QKeySequence::Back);
connect(actionPrecedente, SIGNAL(triggered()), this, SLOT(precedente()));
actionSuivante = new QAction(QIcon("images/suivante.png"), tr("&Suivante"), this);
actionSuivante->setShortcut(QKeySequence::Forward);
connect(actionSuivante, SIGNAL(triggered()), this, SLOT(suivante()));
actionStop = new QAction(QIcon("images/stop.png"), tr("S&top"), this);
connect(actionStop, SIGNAL(triggered()), this, SLOT(stop()));
actionActualiser = new QAction(QIcon("images/actualiser.png"), tr("&Actualiser"), this);
actionActualiser->setShortcut(QKeySequence::Refresh);
connect(actionActualiser, SIGNAL(triggered()), this, SLOT(actualiser()));
actionAccueil = new QAction(QIcon("images/accueil.png"), tr("A&ccueil"), this);
connect(actionAccueil, SIGNAL(triggered()), this, SLOT(accueil()));
actionGo = new QAction(QIcon("images/go.png"), tr("A&ller à"), this);
connect(actionGo, SIGNAL(triggered()), this, SLOT(chargerPage()));
actionAPropos = new QAction(tr("&A propos..."), this);
connect(actionAPropos, SIGNAL(triggered()), this, SLOT(aPropos()));
actionAPropos->setShortcut(QKeySequence::HelpContents);
actionAProposQt = new QAction(tr("A propos de &Qt..."), this);
connect(actionAProposQt, SIGNAL(triggered()), qApp, SLOT(aboutQt()));
}
void FenPrincipale::creerMenus()
{
QMenu *menuFichier = menuBar()->addMenu(tr("&Fichier"));
menuFichier->addAction(actionNouvelOnglet);
menuFichier->addAction(actionFermerOnglet);
menuFichier->addSeparator();
menuFichier->addAction(actionQuitter);
QMenu *menuNavigation = menuBar()->addMenu(tr("&Navigation"));
menuNavigation->addAction(actionPrecedente);
menuNavigation->addAction(actionSuivante);
menuNavigation->addAction(actionStop);
menuNavigation->addAction(actionActualiser);
menuNavigation->addAction(actionAccueil);
QMenu *menuAide = menuBar()->addMenu(tr("&?"));
menuAide->addAction(actionAPropos);
menuAide->addAction(actionAProposQt);
}
void FenPrincipale::creerBarresOutils()
{
champAdresse = new QLineEdit;
connect(champAdresse, SIGNAL(returnPressed()), this, SLOT(chargerPage()));
QToolBar *toolBarNavigation = addToolBar(tr("Navigation"));
toolBarNavigation->addAction(actionPrecedente);
toolBarNavigation->addAction(actionSuivante);
toolBarNavigation->addAction(actionStop);
toolBarNavigation->addAction(actionActualiser);
toolBarNavigation->addAction(actionAccueil);
toolBarNavigation->addWidget(champAdresse);
toolBarNavigation->addAction(actionGo);
}
void FenPrincipale::creerBarreEtat()
{
progression = new QProgressBar(this);
progression->setVisible(false);
progression->setMaximumHeight(14);
statusBar()->addWidget(progression, 1);
}
|
Ce code ne fait rien d'extraordinairement nouveau, je ne vois pas trop ce que je pourrais commenter. Il y a beaucoup de connexions à des slots personnalisés que l'on devra écrire.
C'était la partie "longue" du code, mais certainement pas la plus complexe.
Voyons maintenant quelques méthodes qui s'occupent de gérer les onglets...
Méthodes de gestion des onglets
En fait, il n'y a que 2 méthodes dans cette catégorie :
- creerOngletPageWeb() : je vous en ai parlé dans le constructeur, elle se charge de créer un QWidget-page ainsi qu'un QWebView à l'intérieur, et de retourner ce QWidget-page à l'appelant pour qu'il puisse créer le nouvel onglet.
- pageActuelle() : une méthode bien pratique que je vous ai donnée un peu plus tôt, qui permet à tout moment d'obtenir un pointeur vers le QWebView de l'onglet actuellement sélectionné.
Voici ces méthodes :
Code : C++ 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37 | QWidget *FenPrincipale::creerOngletPageWeb(QString url)
{
QWidget *pageOnglet = new QWidget;
QWebView *pageWeb = new QWebView;
QVBoxLayout *layout = new QVBoxLayout;
layout->setContentsMargins(0,0,0,0);
layout->addWidget(pageWeb);
pageOnglet->setLayout(layout);
if (url.isEmpty())
{
pageWeb->load(QUrl(tr("html/page_blanche.html")));
}
else
{
if (url.left(7) != "http://")
{
url = "http://" + url;
}
pageWeb->load(QUrl(url));
}
// Gestion des signaux envoyés par la page web
connect(pageWeb, SIGNAL(titleChanged(QString)), this, SLOT(changementTitre(QString)));
connect(pageWeb, SIGNAL(urlChanged(QUrl)), this, SLOT(changementUrl(QUrl)));
connect(pageWeb, SIGNAL(loadStarted()), this, SLOT(chargementDebut()));
connect(pageWeb, SIGNAL(loadProgress(int)), this, SLOT(chargementEnCours(int)));
connect(pageWeb, SIGNAL(loadFinished(bool)), this, SLOT(chargementTermine(bool)));
return pageOnglet;
}
QWebView *FenPrincipale::pageActuelle()
{
return onglets->currentWidget()->findChild<QWebView *>();
}
|
Je ne commente pas pageActuelle(), je l'ai déjà fait auparavant.
Pour ce qui est de creerOngletPageWeb(), elle crée comme prévu un QWidget et elle place un nouveau QWebView à l'intérieur. La page web charge l'URL indiquée en paramètre, et rajoute le "
http://" en préfixe si celui-ci a été oublié.
Si aucune URL n'a été spécifiée, on charge une page blanche. J'ai pour l'occasion créé un fichier HTML vide, placé dans un sous-dossier "html" du programme.
On connecte plusieurs signaux intéressants envoyés par le QWebView, qui, à mon avis, parlent d'eux-mêmes : "Le titre a changé", "L'URL a changé", "Début du chargement", "Chargement en cours", "Chargement terminé".
Bref, rien de sorcier, mais ça fait encore tout plein de slots personnalisés à écrire tout ça !
Bon, il y a de quoi faire. Reprenons notre FenPrincipale.h, que voici maintenant en version complète avec toutes les méthodes et tous les slots.
FenPrincipale.h (version complète)
Code : C++ 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61 | #ifndef HEADER_FENPRINCIPALE
#define HEADER_FENPRINCIPALE
#include <QtGui>
#include <QtWebKit>
class FenPrincipale : public QMainWindow
{
Q_OBJECT
public:
FenPrincipale();
private:
void creerActions();
void creerMenus();
void creerBarresOutils();
void creerBarreEtat();
QWidget *creerOngletPageWeb(QString url = "");
QWebView *pageActuelle();
private slots:
void precedente();
void suivante();
void accueil();
void stop();
void actualiser();
void aPropos();
void nouvelOnglet();
void fermerOnglet();
void chargerPage();
void changementOnglet(int index);
void changementTitre(const QString & titreComplet);
void changementUrl(const QUrl & url);
void chargementDebut();
void chargementEnCours(int pourcentage);
void chargementTermine(bool ok);
private:
QTabWidget *onglets;
QAction *actionNouvelOnglet;
QAction *actionFermerOnglet;
QAction *actionQuitter;
QAction *actionAPropos;
QAction *actionAProposQt;
QAction *actionPrecedente;
QAction *actionSuivante;
QAction *actionStop;
QAction *actionActualiser;
QAction *actionAccueil;
QAction *actionGo;
QLineEdit *champAdresse;
QProgressBar *progression;
};
#endif
|
Les lignes ajoutées par rapport à la fois précédente ont été surlignées.
Maintenant implémentons tout ça.
Implémentation des slots
Slots appelés par les actions de la barre d'outils
Commençons par les actions de la barre d'outils :
Code : C++ 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24 | void FenPrincipale::precedente()
{
pageActuelle()->back();
}
void FenPrincipale::suivante()
{
pageActuelle()->forward();
}
void FenPrincipale::accueil()
{
pageActuelle()->load(QUrl(tr("http://www.siteduzero.com")));
}
void FenPrincipale::stop()
{
pageActuelle()->stop();
}
void FenPrincipale::actualiser()
{
pageActuelle()->reload();
}
|
On utilise la (très) pratique fonction pageActuelle() pour obtenir un pointeur vers le QWebView que l'utilisateur est en train de regarder (histoire d'affecter la page web de l'onglet en cours, et pas les autres).
Toutes ces méthodes, comme back() et forward(), sont des slots. On les appelle ici comme si c'étaient de simples méthodes.
Pourquoi ne pas avoir connecté directement les signaux envoyés par les QAction aux slots du QWebView ?
On aurait pu s'il n'y avait pas eu d'onglets. Le problème justement ici, c'est qu'on gère plusieurs onglets différents.
Par exemple, on ne pouvait pas connecter lors de sa création la QAction "actualiser" au QWebView... parce que le QWebView à actualiser dépend de l'onglet actuellement sélectionné !
Voilà donc pourquoi on passe par un petit slot maison qui va d'abord chercher à savoir quel est le QWebView que l'on est en train de visualiser pour être sûr qu'on recharge la bonne page.
Slots appelés par d'autres actions des menus
Voici les slots appelés par les actions des menus suivants :
- Nouvel onglet
- Fermer l'onglet
- A propos...
Code : C++ 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26 | void FenPrincipale::aPropos()
{
QMessageBox::information(this, tr("A propos..."), tr("zNavigo est un projet réalisé pour illustrer les tutoriels C++ du <a href=\"http://www.siteduzero.com\">Site du Zéro</a>.<br />Les images de ce programme ont été créées par <a href=\"http://www.everaldo.com\">Everaldo Coelho</a>"));
}
void FenPrincipale::nouvelOnglet()
{
int indexNouvelOnglet = onglets->addTab(creerOngletPageWeb(), tr("(Nouvelle page)"));
onglets->setCurrentIndex(indexNouvelOnglet);
champAdresse->setText("");
champAdresse->setFocus(Qt::OtherFocusReason);
}
void FenPrincipale::fermerOnglet()
{
// On ne doit pas fermer le dernier onglet, sinon le QTabWidget ne marche plus
if (onglets->count() > 1)
{
onglets->removeTab(onglets->currentIndex());
}
else
{
QMessageBox::critical(this, tr("Erreur"), tr("Il faut au moins un onglet !"));
}
}
|
Le slot aPropos() se contente d'afficher une boîte de dialogue.
nouvelOnglet() rajoute un nouvel onglet à l'aide de la méthode addTab() du QTabWidget, comme on l'avait fait dans le constructeur. Pour que le nouvel onglet s'affiche immédiatement, on force son affichage avec setCurrentIndex() qui se sert de l'index (numéro) de l'onglet que l'on vient de créer.
On vide la barre d'adresse et on lui donne le focus, c'est-à-dire que le curseur est directement placé dedans pour que l'utilisateur puisse écrire une URL.
fermerOnglet() supprime l'onglet actuellement sélectionné. Il vérifie au préalable que l'on n'est pas en train d'essayer de supprimer le dernier onglet, auquel cas le QTabWidget n'aurait plus lieu d'exister. Un système à onglets sans onglets, ça fait désordre.
Slots de chargement d'une page et de changement d'onglet
Ces slots sont appelés respectivement lorsqu'on demande à charger une page (appui sur la touche Entrée après avoir écrit une URL, ou clic sur le bouton tout à droite de la barre d'outils) et lorsqu'on change d'onglet.
Code : C++ 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 | void FenPrincipale::chargerPage()
{
QString url = champAdresse->text();
// On rajoute le "http://" s'il n'est pas déjà dans l'adresse
if (url.left(7) != "http://")
{
url = "http://" + url;
champAdresse->setText(url);
}
pageActuelle()->load(QUrl(url));
}
void FenPrincipale::changementOnglet(int index)
{
changementTitre(pageActuelle()->title());
changementUrl(pageActuelle()->url());
}
|
On vérifie au préalable que l'utilisateur a mis le préfixe
http://, et si ce n'est pas le cas on le rajoute (sinon l'adresse n'est pas valide).
Lorsque l'utilisateur change d'onglet, on met à jour 2 choses sur la fenêtre : le titre de la page, affiché tout en haut de la fenêtre et sur un onglet, et l'URL inscrite dans la barre d'adresse.
changementTitre() et changementUrl() sont des slots personnalisés, que l'on se permet d'appeler comme n'importe quelle méthode. Ces slots sont aussi automatiquement appelés lorsque le QWebView envoie les signaux correspondants.
Voyons voir comment implémenter ces slots...
Slots appelés lorsqu'un signal est envoyé par le QWebView
Lorsque le QWebView s'active, il va envoyer des signaux. Ceux-ci sont connectés à des slots personnalisés de notre fenêtre. Les voici :
Code : C++ 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37 | void FenPrincipale::changementTitre(const QString & titreComplet)
{
QString titreCourt = titreComplet;
// On tronque le titre pour éviter des onglets trop larges
if (titreComplet.size() > 40)
{
titreCourt = titreComplet.left(40) + "...";
}
setWindowTitle(titreCourt + " - " + tr("zNavigo"));
onglets->setTabText(onglets->currentIndex(), titreCourt);
}
void FenPrincipale::changementUrl(const QUrl & url)
{
if (url.toString() != tr("html/page_blanche.html"))
{
champAdresse->setText(url.toString());
}
}
void FenPrincipale::chargementDebut()
{
progression->setVisible(true);
}
void FenPrincipale::chargementEnCours(int pourcentage)
{
progression->setValue(pourcentage);
}
void FenPrincipale::chargementTermine(bool ok)
{
progression->setVisible(false);
statusBar()->showMessage(tr("Prêt"), 2000);
}
|
Ces slots ne sont pas très complexes. Ils mettent à jour la fenêtre (par exemple la barre de progression en bas) lorsqu'il y a lieu.
Certains sont très utiles, comme changementUrl(). En effet, lorsque l'utilisateur clique sur un lien sur la page, l'URL change et il faut par conséquent mettre à jour le champ d'adresse.
Vous noterez que je tronque le titre de la page à 40 caractères si celui-ci est trop long. Cela permet d'éviter d'avoir des onglets qui font 4km de large.
Ouf !

Pas fâché d'en avoir terminé, mais le plus beau dans tout ça, c'est qu'on a un navigateur parfaitement fonctionnel !
Je remets ici le screenshot par plaisir.
A l'heure actuelle, un bug dans QtWebKit dans la gestion des cookies ne permet pas de se connecter au Site du Zéro. C'est un peu dommage, mais comme le module est assez récent il devrait s'améliorer et être corrigé à l'avenir. Ne soyez donc pas surpris si vous ne pouvez pas vous connecter au site avec.
Télécharger le code source et l'exécutable
Je vous propose de télécharger le code source ainsi que l'exécutable Windows du projet :
Pensez à ajouter les DLL nécessaires dans le même dossier que l'exécutable si vous voulez que celui-ci fonctionne. Cette fois, comme je vous l'avais dit, il faut 2 nouvelles DLL : QtWebKit4.dll et QtNetwork4.dll.
Améliorations possibles
Améliorer le navigateur, c'est possible ?
Certainement ! Il fonctionne, mais il est encore loin d'être parfait, et j'ai des tonnes d'idées pour l'améliorer. Bon ces idées sont repompées des navigateurs qui existent déjà, mais rien ne vous empêche d'en inventer de nouvelles super-révolutionnaires bien sûr.
- Afficher l'historique dans un menu : il existe une classe QWebHistory qui permet de récupérer l'historique de toutes les pages visitées via un QWebView. Pour obtenir un objet de type QWebHistory du QWebView de l'onglet en cours, utilisez pageActuelle()->page()->history()
.
Renseignez-vous ensuite sur la doc de QWebHistory pour essayer de trouver comment récupérer la liste des pages visitées. Vous pourriez, par exemple, rajouter un menu "Historique" qui afficherait l'historique des pages vues sur l'onglet en cours.
- Gestion des adresses HTTPS : certains sites sont sécurisés (comme Paypal et Adsense par exemple). Ils utilisent des adresses https:// au lieu de http://. A cause de la vérification que l'on a faite, notre navigateur n'accepte que les adresses http://. Modifiez-le pour qu'il puisse gérer aussi bien les http:// que les https://.
- Zone de recherche Google : vous pourriez rajouter une zone de recherche google en haut à droite, qui appelle automatiquement Google avec les mots-clés sélectionnés.
Je vous laisse vous renseigner sur le fonctionnement de Google. Vous pouvez imiter un autre navigateur comme Firefox par exemple, pour voir l'URL qu'il charge lorsqu'on fait une recherche Google.
- Recherche dans la page : rajoutez la possibilité de faire une recherche dans le texte de la page web affichée. Indice : QWebView dispose d'une méthode findText() !
- Disparition des onglets s'il ne reste plus qu'une seule page : voilà une amélioration délicate. Un QTabWidget sans onglets ne peut exister. Mais s'il ne reste qu'une seule page affichée, ça ne sert à rien d'utiliser un système à onglets.
Essayer de gérer le cas où il ne reste plus qu'une seule page affichée, afin que le QTabWidget disparaisse (au moins jusqu'à ce qu'on demande à ouvrir un nouvel onglet).
- Fenêtre d'options : vous pourriez créer une nouvelle fenêtre d'options qui permet de définir la taille de police par défaut, l'URL de la page d'accueil, etc.
Pour modifier la taille de la police par défaut, regardez du côté de QWebSettings.
Pour enregistrer les options, vous pouvez passer par la classe QFile pour écrire dans un fichier. Mais j'ai mieux : utilisez la classe QSettings qui est spécialement faite pour enregistrer des options. En général, les options sont enregistrées dans un fichier (.ini, .conf...), mais on peut aussi enregistrer les options dans la base de registres sous Windows.
Prenez bien le temps de lire la description de cette classe, c'est un peu long mais c'est vraiment très intéressant.
- Gestion des marque-pages (favoris) : voilà une fonctionnalité très répandue sur la plupart des navigateurs. L'utilisateur aime bien pouvoir enregistrer les adresses de ses sites web préférés.
Là encore, pour l'enregistrement, je vous recommande chaudement de passer par un QSettings.
Vous pourrez ensuite afficher la liste des sites favoris dans un menu, ou encore dans une nouvelle barre d'outils comme le fait Firefox.
- Impression d'une page web : pourquoi ne pas faire s'amuser avec l'imprimante ?
Les QWebFrame contiennent une méthode print() qui prend en paramètre une imprimante (QPrinter).
Le QWebView est constitué d'une QWebPage qui est elle-même constituée d'un ou plusieurs QWebFrame (généralement un seul). Je vous laisse découvrir comment obtenir un pointeur vers le QWebFrame.
Je vous laisse aussi découvrir comment manipuler les QPrinter, qui permettent de faire appel à l'imprimante. Et je vous invite aussi à jeter un oeil à la classe QPrintDialog qui permet d'afficher une boîte de dialogue générique d'impression.
Ah, que de choses à découvrir !
- Sauvegarde de l'état de la fenêtre à la clôture : c'est peut-être une des fonctionnalités les plus appréciées des navigateurs actuels. Lorsque l'utilisateur veut quitter le programme, enregistrez (toujours avec QSettings) la liste des onglets ouverts avec leurs URL. Vous pourrez ainsi les réouvrir automatiquement lors du prochain chargement du programme.
Voilà, avec tout ce que je vous ai donné à faire, je crois que j'ai le temps d'aller à la nage à Hawaï siroter une Piña Colada dans un bar en bord de mer.
Et peut-être même que j'ai le temps de revenir d'ailleurs.
Informations sur le tutoriel
Retour en haut
Créé : Le 18/09/2007 à 17:13:58
Modifié : Le 17/06/2009 à 11:20:26
Avancement : 100%
Licence : Copie non autorisée
54 commentaires