Sur cette page

Visionneuse de documents

Une application Widgets permettant d'afficher et d'imprimer des fichiers JSON, du texte et des fichiers PDF.

Interface utilisateur de la visionneuse de documents affichant une fenêtre contextuelle « Sélectionner le mode d'ouverture »

La visionneuse de documents montre comment utiliser une interface utilisateur de type « QMainWindow » avec des barres d’outils, des menus et des actions statiques et dynamiques. De plus, elle illustre les fonctionnalités suivantes dans les applications basées sur des widgets :

  • Utilisation d'QSettings pour interroger et enregistrer les préférences de l'utilisateur, et gestion de l'historique des fichiers précédemment ouverts.
  • Contrôle du comportement du curseur lors du survol des widgets.
  • Création de plugins chargés dynamiquement.
  • Localisation de l’interface utilisateur dans différentes langues.

Exécution de l’exemple

Vous pouvez exécuter l'exemple à partir de :

Création d’une application et de la fenêtre principale

L'application et sa fenêtre principale sont créées dans main.cpp. La fonction main() utilise QCommandLineParser pour traiter les arguments de ligne de commande :help, version et un argument positionnel facultatif, file. Si l'utilisateur a fourni un chemin d'accès à un fichier lors du lancement de l'application, la fenêtre principale l'ouvre :

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);
    QCoreApplication::setOrganizationName("QtProject"_L1);
    QCoreApplication::setApplicationName("DocumentViewer"_L1);
    QCoreApplication::setApplicationVersion("1.0"_L1);

    Translator mainTranslator;
    mainTranslator.setBaseName("docviewer"_L1);
    mainTranslator.install();

    QCommandLineParser parser;
    parser.setApplicationDescription(Tr::tr("A viewer for JSON, PDF and text files"));
    parser.addHelpOption();
    parser.addVersionOption();
    parser.addPositionalArgument("File"_L1, Tr::tr("JSON, PDF or text file to open"));
    parser.process(app);

    const QStringList &positionalArguments = parser.positionalArguments();
    const QString &fileName = (positionalArguments.count() > 0) ? positionalArguments.at(0)
                                                                : QString();

    MainWindow w(mainTranslator);

    // Start application only if plugins are available
    if (!w.hasPlugins()) {
        QMessageBox::critical(nullptr,
                              Tr::tr("No viewer plugins found"),
                              Tr::tr("Unable to load viewer plugins. Exiting application."));
        return 1;
    }

    w.show();
    if (!fileName.isEmpty())
        w.openFile(fileName);

    return app.exec();
}

Classe MainWindow

La classe ` MainWindow ` fournit un écran d’application avec des menus, des actions et une barre d’outils. Elle peut ouvrir un fichier en détectant automatiquement son type de contenu. Elle gère également une liste des fichiers précédemment ouverts, en utilisant ` QSettings ` pour stocker et recharger les paramètres au démarrage. La classe `MainWindow` crée une visionneuse adaptée au fichier ouvert, en fonction de son type de contenu, et prend en charge l’impression d’un document.

Le constructeur de MainWindow initialise l’interface utilisateur créée dans Qt Designer. Le fichier mainwindow.ui fournit une « QTabWidget » à gauche, affichant les signets et les vignettes. À droite, se trouve une « QScrollArea » permettant de visualiser le contenu du fichier.

Classe ViewerFactory

La classe ` ViewerFactory ` gère les visionneuses pour les types de fichiers connus. Ces visionneuses sont implémentées sous forme de plugins. Lorsqu’une instance de `ViewerFactory` est créée, des pointeurs vers la zone d’affichage et la fenêtre principale sont transmis au constructeur :

m_factory.reset(new ViewerFactory(ui->viewArea, this));

ViewerFactory charge tous les plug-ins disponibles lors de sa création. Elle fournit une API publique permettant d’interroger les plug-ins chargés, leurs noms et les types MIME pris en charge :

    using ViewerList = QList<AbstractViewer *>;
    QStringList viewerNames(bool showDefault = false) const;
    ViewerList viewers() const;
    AbstractViewer *findViewer(const QString &viewerName) const;
    AbstractViewer *defaultViewer() const;
    QStringList supportedMimeTypes() const;

La fonction ` viewer() ` renvoie un pointeur vers le plugin adapté à l’ouverture du fichier ` QFile ` passé en argument :

m_viewer = m_factory->viewer(file);

Si les paramètres de l’application contiennent une section dédiée à la visionneuse, celle-ci est transmise à la fonction virtuelle ` restoreState() ` de la visionneuse :

void MainWindow::restoreViewerSettings()
{
    if (!m_viewer)
        return;

    QSettings settings;
    settings.beginGroup(settingsViewers);
    QByteArray viewerSettings = settings.value(m_viewer->viewerName(), QByteArray()).toByteArray();
    settings.endGroup();
    if (!viewerSettings.isEmpty())
        m_viewer->restoreState(viewerSettings);
}

Ensuite, les ressources d’interface utilisateur standard sont transmises à la visionneuse et la zone de défilement principale est configurée pour afficher le widget d’affichage de la visionneuse :

    m_viewer->initViewer(ui->actionBack, ui->actionForward, ui->menuHelp->menuAction(), ui->tabWidget);
    restoreViewerSettings();
    ui->scrollArea->setWidget(m_viewer->widget());
    return true;
}

La classe `AbstractViewer`

AbstractViewer fournit une API généralisée pour afficher, enregistrer et imprimer un document. Les propriétés du document et de la visionneuse peuvent être interrogées :

  • Le document contient-il du contenu ?
  • A-t-il été modifié ?
  • L’aperçu (vignettes ou signets) est-il pris en charge ?

AbstractViewer fournit des méthodes protégées permettant aux classes dérivées de créer des actions et des menus dans la fenêtre principale. Afin d’afficher ces éléments dans la fenêtre principale, ceux-ci sont rattachés à celle-ci. AbstractViewer est chargée de supprimer et de détruire les éléments d’interface utilisateur qu’elle crée. Elle hérite de la classe « QObject » pour implémenter les signaux et les slots.

Signaux

void uiInitialized();

Ce signal est émis une fois que la visionneuse a reçu toutes les informations nécessaires concernant les éléments de l’interface utilisateur de la fenêtre principale.

void printingEnabledChanged(bool enabled);

Ce signal est émis lorsque l’impression du document est activée ou désactivée. Cela se produit après le chargement réussi d’un nouveau document ou, par exemple, lorsque tout le contenu a été supprimé.

void showMessage(const QString &message, int timeout = 8000);

Ce signal est émis pour afficher un message d’état à l’utilisateur.

void documentLoaded(const QString &fileName);

Ce signal informe l’application qu’un document a été chargé avec succès.

Classe TxtViewer

TxtViewer est une visionneuse de texte simple, héritant de la classe AbstractViewer. Elle prend en charge l'édition de fichiers texte, les opérations de copier/couper et coller, l'impression et l'enregistrement des modifications.

Définition de la classe
class TxtViewer : public ViewerInterface
{
    Q_OBJECT
    Q_PLUGIN_METADATA(IID "org.qt-project.Qt.Examples.DocumentViewer.ViewerInterface" FILE "txtviewer.json")
    Q_INTERFACES(ViewerInterface)

La définition de la classe commence par la macro ` Q_OBJECT `, qui gère les signaux et les slots. Elle est suivie des macros ` Q_PLUGIN_METADATA ` et ` Q_INTERFACES `, nécessaires à l'enregistrement du plugin.

La classe hérite de ViewerInterface, qui hérite elle-même de AbstractViewer. La classe ViewerInterface sert à fournir une interface entre l'application de la fenêtre principale et le plugin.

QPluginLoader Elle nécessite également le fichier txtviewer.json, qui doit contenir la clé du plugin :

{ "Keys": [ "txtviewer" ] }

La classe ne définit aucun constructeur, ce qui signifie que seul un constructeur standard sans arguments est disponible. Toutes les autres fonctions, y compris le destructeur, réimplémentent les fonctions virtuelles de ` ViewerInterface`. Elles sont utilisées pour échanger des données, des informations et des instructions avec l'application principale.

public:
    TxtViewer();
    ~TxtViewer() override;
    void init(QFile *file, QWidget *parent, QMainWindow *mainWindow) override;
    QString viewerName() const override { return QLatin1StringView(staticMetaObject.className()); };
    QStringList supportedMimeTypes() const override;
    bool saveDocument() override { return saveFile(m_file.get()); };
    bool saveDocumentAs() override;
    bool hasContent() const override;
    QByteArray saveState() const override { return {}; }
    bool restoreState(QByteArray &) override { return true; }
    bool supportsOverview() const override { return false; }

#ifdef DOCUMENTVIEWER_PRINTSUPPORT
protected:
    void printDocument(QPrinter *printer) const override;
#endif // DOCUMENTVIEWER_PRINTSUPPORT

private slots:
    void setupTxtUi();

private:
    void retranslate() override;
    void openFile();
    bool saveFile (QFile *file);

    QPlainTextEdit *m_textEdit;
    QAction *m_cutAct = nullptr;
    QAction *m_copyAct = nullptr;
    QAction *m_pasteAct = nullptr;
};
Implémentation de la classe TxtViewer
#include "txtviewer.h"

#include <QFileDialog>
#include <QMainWindow>
#include <QMenu>
#include <QMenuBar>
#include <QPlainTextEdit>
#include <QScrollBar>
#include <QToolBar>

#include <QGuiApplication>
#include <QPainter>
#include <QTextDocument>

#include <QDir>

#ifdef DOCUMENTVIEWER_PRINTSUPPORT
#include <QPrinter>
#include <QPrintDialog>
#endif

using namespace Qt::StringLiterals;

TxtViewer::TxtViewer()
    : m_cutAct(new QAction(this)),
      m_copyAct(new QAction(this)),
      m_pasteAct(new QAction(this))
{
    connect(this, &AbstractViewer::uiInitialized, this, &TxtViewer::setupTxtUi);

    const QIcon cutIcon = QIcon::fromTheme(QIcon::ThemeIcon::EditCut,
                                           QIcon(":/demos/documentviewer/images/cut.png"_L1));
    m_cutAct->setIcon(cutIcon);
    m_cutAct->setShortcuts(QKeySequence::Cut);

    const QIcon copyIcon = QIcon::fromTheme(QIcon::ThemeIcon::EditCopy,
                                            QIcon(":/demos/documentviewer/images/copy.png"_L1));
    m_copyAct->setIcon(copyIcon);
    m_copyAct->setShortcuts(QKeySequence::Copy);

    const QIcon pasteIcon = QIcon::fromTheme(QIcon::ThemeIcon::EditPaste,
                                             QIcon(":/demos/documentviewer/images/paste.png"_L1));
    m_pasteAct->setIcon(pasteIcon);
    m_pasteAct->setShortcuts(QKeySequence::Paste);
}

TxtViewer::~TxtViewer() = default;

void TxtViewer::init(QFile *file, QWidget *parent, QMainWindow *mainWindow)
{
    AbstractViewer::init(file, new QPlainTextEdit(parent), mainWindow);
    m_textEdit = qobject_cast<QPlainTextEdit *>(widget());
    setTranslationBaseName("txtviewer"_L1);
}

QStringList TxtViewer::supportedMimeTypes() const
{
    return {"text/plain"_L1};
}

void TxtViewer::setupTxtUi()
{
    QMenu *editMenu = addMenu();
    QToolBar *editToolBar = addToolBar();
#if QT_CONFIG(clipboard)
    connect(m_cutAct, &QAction::triggered, m_textEdit, &QPlainTextEdit::cut);
    editMenu->addAction(m_cutAct);
    editToolBar->addAction(m_cutAct);

    connect(m_copyAct, &QAction::triggered, m_textEdit, &QPlainTextEdit::copy);
    editMenu->addAction(m_copyAct);
    editToolBar->addAction(m_copyAct);

    connect(m_pasteAct, &QAction::triggered, m_textEdit, &QPlainTextEdit::paste);
    editMenu->addAction(m_pasteAct);
    editToolBar->addAction(m_pasteAct);

    menuBar()->addSeparator();

    m_cutAct->setEnabled(false);
    m_copyAct->setEnabled(false);
    connect(m_textEdit, &QPlainTextEdit::copyAvailable, m_cutAct, &QAction::setEnabled);
    connect(m_textEdit, &QPlainTextEdit::copyAvailable, m_copyAct, &QAction::setEnabled);
#endif // QT_CONFIG(clipboard)

    openFile();

    connect(m_textEdit, &QPlainTextEdit::textChanged, this, [&](){
        maybeSetPrintingEnabled(hasContent());
    });

    connect(m_uiAssets.back, &QAction::triggered, m_textEdit, [&](){
        auto *bar = m_textEdit->verticalScrollBar();
        if (bar->value() > bar->minimum())
            bar->setValue(bar->value() - 1);
    });

    connect(m_uiAssets.forward, &QAction::triggered, m_textEdit, [&](){
        auto *bar = m_textEdit->verticalScrollBar();
        if (bar->value() < bar->maximum())
            bar->setValue(bar->value() + 1);
    });

    retranslate();
}

Nous commençons par inclure les fichiers d'en-tête nécessaires pour accéder à toutes les classes utilisées par TxtViewer. Nous incluons également txtviewer.h.

QPrinter et QPrintDialog ne sont inclus que si la prise en charge de l'impression est activée sur le système de compilation.

Notez que ces en-têtes ne sont pas inclus directement dans mainwindow.h. L'inclusion d'en-têtes volumineux dans d'autres fichiers d'en-tête peut avoir un impact sur les performances de la compilation. Dans ce cas précis, cela ne poserait pas de problème, mais il est recommandé de n'inclure que les en-têtes nécessaires afin de réduire au minimum les dépendances.

L’implémentation commence par un destructeur vide. Celui-ci pourrait être complètement omis. Il est recommandé de l’implémenter vide afin d’indiquer aux lecteurs du code qu’aucune action n’est requise dans le destructeur.

Le destructeur est suivi d’une fonction d’initialisation, prenant trois arguments :

  • file, le pointeur vers le fichier à ouvrir et à afficher.
  • parent, pointant vers l’ QWidget e à l’intérieur de laquelle l’éditeur doit être placé.
  • mainWindow, pointant vers la fenêtre principale de l’application, où sont gérés les menus et les barres de menus.

La fonction appelle la fonction d'initialisation de base d'AbstractViewer. Un nouveau widget QPlainTextEdit est créé, qui affichera le contenu du fichier. Ensuite, la fonction de configuration d'TxtViewer est connectée au signal « uiInitialized » de la classe de base.

La fonction suivante renvoie la liste des types MIME pris en charge par la visionneuse de texte. Seul le texte brut est pris en charge.

La dernière fonction d’initialisation ajoute des composants d’interface utilisateur spécifiques au visualiseur, tels que des menus, des icônes, des boutons et des info-bulles. Elle utilise les fonctionnalités fournies par AbstractViewer pour s’assurer que ces composants sont supprimés de la fenêtre principale de l’application dès qu’un autre fichier est affiché avec un autre plugin de visualisation.

void TxtViewer::openFile()
{
    const QString type = tr("open");
    if (!m_file->open(QFile::ReadOnly | QFile::Text)) {
        statusMessage(tr("Cannot read file %1:\n%2.")
                      .arg(QDir::toNativeSeparators(m_file->fileName()),
                           m_file->errorString()), type);
        return;
    }

    QTextStream in(m_file.get());
#if QT_CONFIG(cursor)
    QGuiApplication::setOverrideCursor(Qt::WaitCursor);
#endif
    if (!m_textEdit->toPlainText().isEmpty()) {
        m_textEdit->clear();
        disablePrinting();
    }
    m_textEdit->setPlainText(in.readAll());
#if QT_CONFIG(cursor)
    QGuiApplication::restoreOverrideCursor();
#endif

    statusMessage(tr("File %1 loaded.")
                  .arg(QDir::toNativeSeparators(m_file->fileName())), type);
    maybeEnablePrinting();
}

openFile Elle ouvre un fichier, transfère son contenu dans l’ QPlainTextEdit, puis affiche un message d’état à l’utilisateur, selon que l’ouverture a réussi ou non.

bool TxtViewer::hasContent() const
{
    return (!m_textEdit->toPlainText().isEmpty());
}

#ifdef DOCUMENTVIEWER_PRINTSUPPORT
void TxtViewer::printDocument(QPrinter *printer) const
{
    if (!hasContent())
        return;

    m_textEdit->print(printer);
}
#endif // DOCUMENTVIEWER_PRINTSUPPORT

bool TxtViewer::saveFile(QFile *file)
{
    QString errorMessage;

    QGuiApplication::setOverrideCursor(Qt::WaitCursor);
    if (file->open(QFile::WriteOnly | QFile::Text)) {
        QTextStream out(file);
        out << m_textEdit->toPlainText();
    } else {
        errorMessage = tr("Cannot open file %1 for writing:\n%2.")
                       .arg(QDir::toNativeSeparators(file->fileName())),
                            file->errorString();
    }
    QGuiApplication::restoreOverrideCursor();

    if (!errorMessage.isEmpty()) {
        statusMessage(errorMessage);
        return false;
    }

    statusMessage(tr("File %1 saved")
                  .arg(QDir::toNativeSeparators(file->fileName())));
    return true;
}

bool TxtViewer::saveDocumentAs()
{
    QFileDialog dialog(mainWindow());
    dialog.setWindowModality(Qt::WindowModal);
    dialog.setAcceptMode(QFileDialog::AcceptSave);
    if (dialog.exec() != QDialog::Accepted)
        return false;

    const QStringList &files = dialog.selectedFiles();
    if (files.isEmpty())
        return false;

    //newFile();
    m_file->setFileName(files.first());
    return saveDocument();
}

void TxtViewer::retranslate()
{
    if (toolBars().isEmpty())
        return;
    menus().at(0)->setTitle(tr("&Edit"));
    toolBars().at(0)->setWindowTitle(tr("Edit"));

    m_cutAct->setText(tr("Cu&t"));
    m_cutAct->setStatusTip(tr("Cut the current selection's contents to the clipboard"));
    m_copyAct->setText(tr("&Copy"));
    m_copyAct->setStatusTip(tr("Copy the current selection's contents to the clipboard"));
    m_pasteAct->setText(tr("&Paste"));
    m_pasteAct->setStatusTip(tr("Paste the clipboard's contents into the current selection"));
}

La fonction réimplémentée suivante indique à l’application principale si le plugin de visualisation affiche effectivement du contenu.

Si l’impression est prise en charge sur le système de compilation, la section suivante la met en œuvre.

Les deux dernières réimplémentations fournissent des fonctionnalités permettant d’enregistrer le fichier actuel ou de l’enregistrer sous un nouveau nom.

Classe ImageViewer

ImageViewer affiche les images prises en charge par QImageReader, à l’aide d’un objet QLabel.

Dans le constructeur, nous augmentons la limite d’allocation de ` QImageReader ` afin de prendre en charge des photos plus volumineuses :

ImageViewer::ImageViewer()
    : m_zoomInAct(new QAction(this)),
      m_zoomOutAct(new QAction(this)),
      m_resetZoomAct(new QAction(this)),
      m_formats(imageFormats())
{
    connect(this, &AbstractViewer::uiInitialized, this, &ImageViewer::setupImageUi);
    QImageReader::setAllocationLimit(1024); // MB

    m_zoomInAct->setIcon(QIcon::fromTheme(QIcon::ThemeIcon::ZoomIn));
    m_zoomInAct->setShortcut(QKeySequence::ZoomIn);
    connect(m_zoomInAct, &QAction::triggered, this, &ImageViewer::zoomIn);

    m_zoomOutAct->setIcon(QIcon::fromTheme(QIcon::ThemeIcon::ZoomOut));
    m_zoomOutAct->setShortcut(QKeySequence::ZoomOut);
    connect(m_zoomOutAct, &QAction::triggered, this, &ImageViewer::zoomOut);

    m_resetZoomAct->setIcon(QIcon::fromTheme(QIcon::ThemeIcon::ZoomFitBest));
    m_resetZoomAct->setShortcut(QKeySequence(Qt::ControlModifier | Qt::Key_0));
    connect(m_resetZoomAct, &QAction::triggered, this, &ImageViewer::resetZoom);
}

Dans la fonction openFile(), nous chargeons l’image et déterminons sa taille. Si elle est plus grande que l’écran, nous la redimensionnons à la taille de l’écran, en conservant le rapport d’aspect. Ce calcul doit être effectué en pixels natifs, et le rapport de pixels de l’appareil doit être défini sur la carte de pixels résultante pour que l’image apparaisse nette :

void ImageViewer::openFile()
{
#if QT_CONFIG(cursor)
    QGuiApplication::setOverrideCursor(Qt::WaitCursor);
#endif
    const QString name = m_file->fileName();
    QImageReader reader(name);
    const QImage origImage = reader.read();
    if (origImage.isNull()) {
        statusMessage(tr("Cannot read file %1:\n%2.")
                      .arg(QDir::toNativeSeparators(name),
                           reader.errorString()), tr("open"));
        disablePrinting();
#if QT_CONFIG(cursor)
        QGuiApplication::restoreOverrideCursor();
#endif
        return;
    }

    clear();

    QImage image = origImage.colorSpace().isValid()
        ? origImage.convertedToColorSpace(QColorSpace::SRgb)
        : origImage;

    const auto devicePixelRatio = m_imageLabel->devicePixelRatioF();
    m_imageSize = QSizeF(image.size()) / devicePixelRatio;

    QPixmap pixmap = QPixmap::fromImage(image);
    pixmap.setDevicePixelRatio(devicePixelRatio);
    m_imageLabel->setPixmap(pixmap);

    const QSizeF targetSize = m_imageLabel->parentWidget()->size();
    if (m_imageSize.width() > targetSize.width()
        || m_imageSize.height() > targetSize.height()) {
        m_initialScaleFactor = qMin(targetSize.width() / m_imageSize.width(),
                                    targetSize.height() / m_imageSize.height());
    }
    m_maxScaleFactor = 3 * m_initialScaleFactor;
    m_minScaleFactor = m_initialScaleFactor / 3;
    doSetScaleFactor(m_initialScaleFactor);

    statusMessage(msgOpen(name, origImage));
#if QT_CONFIG(cursor)
    QGuiApplication::restoreOverrideCursor();
#endif

    maybeEnablePrinting();
}

Classe JsonViewer

JsonViewer affiche un fichier JSON dans un ` QTreeView`. En interne, elle charge le contenu d’un fichier dans un ` QJsonDocument ` et l’utilise pour remplir un modèle d’arborescence personnalisé avec ` JsonItemModel`.

Le plugin JSON Viewer montre comment implémenter un modèle d’élément personnalisé héritant de ` QAbstractItemModel`. La classe ` JsonTreeItem ` fournit une API de base permettant de manipuler les données JSON et de les propager vers le ` QJsonDocument` sous-jacent.

JsonViewer utilise les objets de niveau supérieur du document comme signets pour la navigation. D’autres nœuds (clés et valeurs) peuvent être ajoutés en tant que signets supplémentaires, ou supprimés de la liste des signets.

Classe PdfViewer

La classe (et le plugin) ` PdfViewer ` est un fork de l’exemple de widget de visualisation PDF. Elle illustre l’utilisation de ` QScroller ` pour parcourir un document de manière fluide.

Autres classes pertinentes

Classe HoverWatcher

La classe ` HoverWatcher ` définit un curseur personnalisé lorsque la souris survole un widget, et le rétablit lorsque la souris quitte le widget. Afin d’éviter la création de plusieurs instances de `HoverWatcher` pour un même widget, elle est implémentée sous forme de singleton par widget.

HoverWatcher hérite de QObject et considère l’ QWidget qu’il surveille comme le parent de l’instance. Il installe un filtre d’événements pour intercepter les événements de survol sans les consommer :

HoverWatcher::HoverWatcher(QWidget *watched)
    : QObject(watched), m_watched(watched)
{
    Q_ASSERT(watched);
    m_cursorShapes[Entered].emplace(Qt::OpenHandCursor);
    m_cursorShapes[MousePress].emplace(Qt::ClosedHandCursor);
    m_cursorShapes[MouseRelease].emplace(Qt::OpenHandCursor);
    // no default for Left => restore override cursor
    m_watched->installEventFilter(this);
}

L'énumération ` HoverAction ` répertorie les actions auxquelles HoverWatcher réagit :

    enum HoverAction {
        Entered,
        MousePress,
        MouseRelease,
        Left,
        Ignore
    };

Des fonctions statiques permettent de créer des observateurs, de vérifier leur existence pour un QWidget spécifique ou de supprimer un observateur :

    static HoverWatcher *watcher(QWidget *watched);
    static const HoverWatcher *watcher(const QWidget *watched);
    static bool hasWatcher(QWidget *widget);
    static void dismiss(QWidget *watched);

Une forme de curseur peut être définie ou supprimée pour chaque HoverAction. S’il n’y a pas de forme de curseur associée, le curseur de remplacement de l’application est rétabli lorsque l’action est déclenchée.

public slots:
    void setCursorShape(HoverAction type, Qt::CursorShape shape);
    void unSetCursorShape(HoverAction type);

La propriété ` mouseButtons ` contient les boutons de souris à prendre en compte pour une action ` MousePress ` :

    void setMouseButtons(Qt::MouseButtons buttons);
    void setMouseButton(Qt::MouseButton button, bool enable);

Des signaux spécifiques à l’action sont émis après le traitement d’une action :

signals:
    void entered();
    void mousePressed();
    void mouseReleased();
    void left();

Un signal général est émis, qui transmet l’action traitée en tant qu’argument :

void hoverAction(HoverAction action);
Classe `RecentFiles`

RecentFiles est une classe « QStringList » spécialisée dans la gestion d’une liste de fichiers récemment ouverts.

RecentFiles dispose de slots permettant d’ajouter soit un seul fichier, soit plusieurs fichiers en une seule fois. Une entrée est ajoutée à la liste des fichiers récents si le chemin d’accès pointe vers un fichier qui existe et peut être ouvert. Si un fichier figure déjà dans la liste, il est retiré de sa position d’origine et ajouté en haut de la liste.

public slots:
    void addFile(const QString &fileName) { addFile(fileName, EmitPolicy::EmitWhenChanged); }
    void addFiles(const QStringList &fileNames);

Les fichiers sont supprimés de la liste soit par nom, soit par index :

    void removeFile(const QString &fileName) { removeFile(m_files.indexOf(fileName)); }
    void removeFile(qsizetype index) {removeFile(index, RemoveReason::Other); }

Slots implémentant l’enregistrement et la restauration à partir de QSettings:

    void saveSettings(QSettings &settings, const QString &key) const;
    bool restoreFromSettings(QSettings &settings, const QString &key);

Lors de la restauration des paramètres, les fichiers inexistants sont ignorés. La propriété maxFiles détermine le nombre maximal de fichiers récents à stocker (la valeur par défaut est 10).

qsizetype maxFiles();
void setMaxFiles(qsizetype maxFiles);

RecentFiles vérifie qu'un fichier peut être lu avant de l'accepter.

Classe RecentFileMenu

RecentFileMenu est une classe de type « QMenu », spécialisée dans l'affichage d'un objet «RecentFiles» sous forme de sous-menu.

Son constructeur prend en paramètre un pointeur vers une classe parente de type « QObject » et un pointeur vers un objet « RecentFiles », dont il va afficher le contenu. Son signal « fileOpened() », déclenché lorsque l'utilisateur sélectionne un fichier récent dans la liste, transmet le chemin absolu vers ce fichier en tant qu'argument.

Remarque : ` RecentFileMenu ` est détruit soit par son widget parent, soit par l’objet ` RecentFiles ` transmis à son constructeur.

class RecentFileMenu : public QMenu
{
    Q_OBJECT

public:
    explicit RecentFileMenu(QWidget *parent, RecentFiles *recent);

signals:
    void fileOpened(const QString &fileName);
    ...
};

Traductions

L’interface utilisateur de l’application est disponible en anglais et en allemand. La langue par défaut est sélectionnée automatiquement par Qt : l’allemand si la langue du système est l’allemand ; sinon, l’anglais. L’utilisateur peut également changer de langue dans le menu Help > Language. Chaque plugin, tout comme l’application principale, est chargé de charger ses propres traductions de manière indépendante lors de l’exécution.

Intégration CMake

Le fichier CMakeLists.txt de niveau supérieur déclare les langues fournies.

qt_standard_project_setup(REQUIRES 6.8
    I18N_SOURCE_LANGUAGE en
    I18N_TRANSLATED_LANGUAGES de
)

La cible « documentviewer » définit l’application principale. Elle stocke et charge les chaînes localisées pour la cible dans les fichiers docviewer_de.ts et docviewer_en.ts. De plus, elle intègre les « qtbase translations » correspondantes fournies par Qt dans les fichiers de traduction générés, afin que les boîtes de dialogue Qt, comme celle d’impression, soient également correctement traduites :

qt_add_translations(documentviewer
    SOURCE_TARGETS documentviewer abstractviewer
    TS_FILE_BASE docviewer
    MERGE_QT_TRANSLATIONS
    QT_TRANSLATION_CATALOGS qtbase
)

Chaque cible « CMakeLists.txt » au niveau d’un plugin n’appelle la fonction « qt_add_translations » que sur les fichiers source de ce plugin (SOURCE_TARGETS). Le fait de limiter la portée des fichiers de traduction à la cible du plugin évite de devoir analyser à nouveau et retraduire les sources de l’application principale et des autres plugins :

qt_add_translations(txtviewer
    SOURCE_TARGETS txtviewer
    TS_FILE_BASE txtviewer
)
Classe `Translator`

La classe ` Translator ` est un wrapper autour de la méthode ` QTranslator ` de Qt qui gère l’internationalisation à la fois pour l’application principale et pour chaque plugin. Chaque composant (application principale et plugins) dispose de sa propre instance de `Translator`, ce qui permet un changement de langue coordonné à l’échelle de l’application. Au démarrage ou lorsque l’utilisateur sélectionne une nouvelle langue, la méthode ` Translator::install() ` est appelée. Cette méthode utilise ` QTranslator::load()` pour charger les fichiers de traduction en fonction de ` QLocale::uiLanguages()` et du nom de base dans le système de ressources Qt. Si aucune traduction correspondante n’est trouvée, l’anglais est utilisé par défaut.

void Translator::install()
{
    if (m_baseName.isEmpty()) {
        qWarning() << "The basename of the translation is not set. Ignoring.";
       return;
    }
    if (!m_translator.isEmpty())
        qApp->removeTranslator(&m_translator);

   if (m_translator.load(m_trLocale, m_baseName, "_"_L1, ":/i18n/"_L1)
        && qApp->installTranslator(&m_translator)) {
        qInfo() << "Loaded translation" << m_translator.filePath();
    } else {
        if (m_trLocale.language() != QLocale::English) {
            qWarning() << "Failed to load translation" << m_baseName <<
                   "pour la locale" << m_trLocale.name() << ". Retour à la traduction en anglais";
            setLanguage(QLocale::English);
        }
    }
}
Prise en charge des plugins

La classe de base AbstractViewer offre une prise en charge de la traduction afin que chaque plugin puisse gérer ses propres traductions de manière autonome :

  • AbstractViewer::setTranslationBaseName(): initialise un objet Translator, définit son nom de base et l'installe pour charger les traductions par défaut.
    void AbstractViewer::setTranslationBaseName(const QString &baseName)
    {
        m_translator.reset(new Translator);
        m_translator->setBaseName(baseName);
        m_translator->install();
    }
  • AbstractViewer::eventFilter(): installée sur la fenêtre principale dans ` init()`, cette méthode écoute les événements « QEvent::LanguageChange ». Lorsque la locale change, elle réinstalle le traducteur du plugin pour la nouvelle langue et appelle ` retranslate() ` pour actualiser l'ensemble du texte.
    bool AbstractViewer::eventFilter(QObject *, QEvent *event)
    {
        if (event->type() != QEvent::LanguageChange)
            return false;
    
        const QLocale locale;
        if (locale != m_currentLocale) {
            m_currentLocale = locale;
            if (m_translator) {
                m_translator->setLanguage(locale.language());
                m_translator->install();
            }
            retranslate();
        }
        return false;
    }
  • AbstractViewer::retranslate(): une méthode virtuelle que chaque plugin implémente pour retraduire ses propres textes d’interface utilisateur. Par exemple, telle qu’implémentée dans ImageViewer :
        retranslate();
    }

Les widgets appartenant à un plugin et contenant des chaînes traduisibles peuvent gérer directement ` QEvent::LanguageChange `. Par exemple, ` ZoomSelector ` redéfinit ` changeEvent() ` pour retraduire les éléments de sa liste déroulante :

void ZoomSelector::changeEvent(QEvent *event)
{
    if (event->type() == QEvent::LanguageChange)
        retranslate();
    QComboBox::changeEvent(event);
}
Démarrage de l'application
  • Application principale: dans ` main.cpp `, nous chargeons la traduction de l'application avant d'afficher la fenêtre :
        Translator mainTranslator;
        mainTranslator.setBaseName("docviewer"_L1);
        mainTranslator.install();
  • Plugins: chaque plugin appelle la méthode ` AbstractViewer::setTranslationBaseName() ` dans sa fonction ` init() ` pour initialiser un objet ` Translator` avec le nom de son fichier de traduction et installer les traductions de la langue actuelle.
    void ImageViewer::init(QFile *file, QWidget *parent, QMainWindow *mainWindow)
        ...
        setTranslationBaseName("imgviewer"_L1);
        ...
Changement de langue à l'exécution

Le changement de langue à l'exécution peut être déclenché de deux manières :

  1. Changement de langue de l'ensemble du système à l'exécution : le système d'exploitation envoie un événement « QEvent::LocaleChange » à l'application.
  2. Via le menu « Help > Language » : cliquer sur l’élément « QMenu » déclenche la méthode ` MainWindow::onActionSwitchLanguage()`, qui définit les paramètres régionaux par défaut et envoie un événement « QEvent::LocaleChange », afin de suivre le même processus qu’un changement de paramètres régionaux système :
    void MainWindow::onActionSwitchLanguage(QLocale::Language lang)
    {
        QLocale::setDefault(QLocale(lang));
        QEvent event(QEvent::LocaleChange);
        QCoreApplication::sendEvent(this, &event);
    }

Dans les deux cas, MainWindow::changeEvent() gère les événements. QEvent::LocaleChange installe le nouveau traducteur, et le fichier QEvent::LanguageChange ainsi généré retraduit l’interface utilisateur de la fenêtre principale. Les plugins réagissent via leurs filtres d’événements.

void MainWindow::changeEvent(QEvent *event)
{
    switch (event->type()) {
    case QEvent::LanguageChange:
        ui->retranslateUi(this);
        statusBar()->clearMessage();
        break;
    case QEvent::LocaleChange:
        m_translator.setLanguage(QLocale().language());
        m_translator.install();
        break;
    default:
        break;
    }

    QMainWindow::changeEvent(event);
}

Fichiers source

Exemple de projet sur code.qt.io

Voir aussi Tous les exemples Qt.

© 2026 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.