Sur cette page

Visionneuse de documents

Une application Widgets pour afficher et imprimer des fichiers JSON, texte et PDF.

L'interface utilisateur de la visionneuse de documents affiche une fenêtre contextuelle "Sélectionner le mode d'ouverture".

Document Viewer montre comment utiliser un site QMainWindow avec des barres d'outils statiques et dynamiques, des menus et des actions. En outre, il démontre les fonctionnalités suivantes dans les applications basées sur des widgets :

  • Utilisation de QSettings pour interroger et enregistrer les préférences de l'utilisateur et gérer 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

Pour exécuter l'exemple à partir de Qt Creatorouvrez le mode Welcome et sélectionnez l'exemple à partir de Examples. Pour plus d'informations, voir Qt Creator: Tutoriel : Construire et exécuter.

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

L'application et sa fenêtre principale sont construites à l'adresse main.cpp. La fonction main() utilise QCommandLineParser pour traiter les arguments de la ligne de commande - help, version et un argument de position 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 maintient également une liste des fichiers précédemment ouverts, en utilisant QSettings pour stocker et recharger les paramètres au moment du lancement. La fenêtre principale crée une visionneuse adaptée au fichier ouvert, en fonction de son type de contenu, et permet d'imprimer un document.

Le constructeur de MainWindow initialise l'interface utilisateur créée dans Qt Designer. Le fichier mainwindow.ui comporte une fenêtre QTabWidget sur la gauche, qui affiche des signets et des vignettes. À droite, une fenêtre QScrollArea permet de visualiser le contenu d'un 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 modules d'extension. Lorsqu'une instance de ViewerFactory est créée, les pointeurs vers la zone de visualisation et la fenêtre principale sont transmis au constructeur :

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

ViewerFactory charge tous les plugins disponibles lors de la construction. Il fournit une API publique pour interroger les plugins 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 approprié pour ouvrir la fenêtre QFile passée en argument :

m_viewer = m_factory->viewer(file);

Si les paramètres de l'application contiennent une section pour le visualiseur, celle-ci est transmise à la fonction virtuelle restoreState() du visualiseur :

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 éléments standard de l'interface utilisateur sont transmis au visualiseur et la zone de défilement principale est définie pour afficher le widget d'affichage du visualiseur :

    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 visualiser, enregistrer et imprimer un document. Les propriétés du document et de la visionneuse peuvent être interrogées :

  • Le document a-t-il un contenu ?
  • A-t-il été modifié ?
  • Une vue d'ensemble (vignettes ou signets) est-elle possible ?

AbstractViewer fournit des méthodes protégées pour les classes dérivées afin de créer des actions et des menus sur la fenêtre principale. Afin d'afficher ces éléments sur la fenêtre principale, ils lui sont rattachés par un lien de parenté. AbstractViewer est responsable de la suppression et de la destruction des éléments d'interface utilisateur qu'il crée. Il hérite de QObject pour implémenter les signaux et les slots.

Signaux

void uiInitialized();

Ce signal est émis après qu'un visualiseur a reçu toutes les informations nécessaires sur les éléments d'interface utilisateur de la fenêtre principale.

void printingEnabledChanged(bool enabled);

Ce signal est émis lorsque l'impression de documents est activée ou désactivée. Cela se produit lorsqu'un nouveau document a été chargé avec succès 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.

La classe TxtViewer

TxtViewer est une simple visionneuse de texte, héritant de AbstractViewer. Elle permet d'éditer des fichiers texte, de copier/couper et coller, d'imprimer et d'enregistrer les 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 emplacements. Elle est suivie par les macros Q_PLUGIN_METADATA et Q_INTERFACES qui sont nécessaires pour enregistrer le plugin.

La classe hérite de ViewerInterface, qui hérite de AbstractViewer. La classe ViewerInterface est utilisée pour fournir une interface entre l'application de la fenêtre principale et le plugin.

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

{ "Keys": [ "txtviewer" ] }

La classe ne définit pas de 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; }
    void retranslate() override;

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

private slots:
    void setupTxtUi();

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

    QPlainTextEdit *m_textEdit;
    QMenu *m_editMenu = nullptr;
    QToolBar *m_editToolBar = nullptr;
    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()
{
    connect(this, &AbstractViewer::uiInitialized, this, &TxtViewer::setupTxtUi);
}

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()
{
    m_editMenu = addMenu(tr("&Edit"));
    m_editToolBar = addToolBar(tr("Edit"));
#if QT_CONFIG(clipboard)
    const QIcon cutIcon = QIcon::fromTheme(QIcon::ThemeIcon::EditCut,
                                           QIcon(":/demos/documentviewer/images/cut.png"_L1));
    m_cutAct = new QAction(cutIcon, tr("Cu&t"), this);
    m_cutAct->setShortcuts(QKeySequence::Cut);
    m_cutAct->setStatusTip(tr("Cut the current selection's contents to the "
                            "clipboard"));
    connect(m_cutAct, &QAction::triggered, m_textEdit, &QPlainTextEdit::cut);
    m_editMenu->addAction(m_cutAct);
    m_editToolBar->addAction(m_cutAct);

    const QIcon copyIcon = QIcon::fromTheme(QIcon::ThemeIcon::EditCopy,
                                            QIcon(":/demos/documentviewer/images/copy.png"_L1));
    m_copyAct = new QAction(copyIcon, tr("&Copy"), this);
    m_copyAct->setShortcuts(QKeySequence::Copy);
    m_copyAct->setStatusTip(tr("Copy the current selection's contents to the "
                             "clipboard"));
    connect(m_copyAct, &QAction::triggered, m_textEdit, &QPlainTextEdit::copy);
    m_editMenu->addAction(m_copyAct);
    m_editToolBar->addAction(m_copyAct);

    const QIcon pasteIcon = QIcon::fromTheme(QIcon::ThemeIcon::EditPaste,
                                             QIcon(":/demos/documentviewer/images/paste.png"_L1));
    m_pasteAct = new QAction(pasteIcon, tr("&Paste"), this);
    m_pasteAct->setShortcuts(QKeySequence::Paste);
    m_pasteAct->setStatusTip(tr("Paste the clipboard's contents into the current "
                              "selection"));
    connect(m_pasteAct, &QAction::triggered, m_textEdit, &QPlainTextEdit::paste);
    m_editMenu->addAction(m_pasteAct);
    m_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);
    });
}

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 de gros en-têtes dans d'autres fichiers d'en-tête peut avoir un impact sur les performances de la compilation. Dans ce cas, cela ne pose pas de problème, mais il est préférable de n'inclure que les en-têtes nécessaires pour réduire les dépendances au minimum.

L'implémentation commence par un destructeur vide. Il pourrait être complètement omis. C'est une bonne pratique de l'implémenter vide afin d'indiquer aux lecteurs de code que rien ne doit être fait dans le destructeur.

Le destructeur est suivi d'une fonction d'initialisation, qui prend trois arguments :

  • filele pointeur sur le fichier à ouvrir et à afficher.
  • parentle pointeur sur le fichier à ouvrir et à afficher, pointant vers le site QWidget à l'intérieur duquel l'éditeur doit être placé.
  • mainWindowLe destructeur est suivi d'une fonction d'initialisation qui prend trois arguments : , le pointeur sur le fichier à ouvrir et à afficher.

La fonction appelle la fonction init de base de AbstractViewer. Un nouveau widget QPlainTextEdit est créé, qui affichera le contenu du fichier. Ensuite, la fonction setup de 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 le visualiseur de texte. Seul le texte simple est pris en charge.

La dernière fonction d'initialisation ajoute des composants d'interface utilisateur spécifiques à la visionneuse, tels que des menus, des icônes, des boutons et des infobulles. Elle utilise la fonctionnalité fournie par AbstractViewer pour s'assurer que ces composants sont supprimés de la fenêtre principale de l'application, une fois 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 ouvre un fichier, transfère son contenu dans le site QPlainTextEdit et affiche un message d'état à l'intention de 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 (m_editMenu)
        m_editMenu->setTitle(tr("&Edit"));
    if (m_editToolBar)
        m_editToolBar->setWindowTitle(tr("Edit"));
    if (m_cutAct) {
        m_cutAct->setText(tr("Cu&t"));
        m_cutAct->setStatusTip(tr("Cut the current selection's contents to the "
                                "clipboard"));
    }
    if (m_copyAct) {
        m_copyAct->setText(tr("&Copy"));
        m_copyAct->setStatusTip(tr("Copy the current selection's contents to the "
                                 "clipboard"));
    }
    if (m_pasteAct) {
        m_pasteAct->setText(tr("&Paste"));
        m_pasteAct->setStatusTip(tr("Paste the clipboard's contents into the current "
                                  "selection"));
    }
}

La fonction suivante réimplémentée indique à l'application principale si le plugin de visualisation affiche ou non le contenu.

Si l'impression est prise en charge par le système de compilation, la section suivante l'implémente.

Les deux dernières réimplémentations permettent d'enregistrer le fichier actuel ou de l'enregistrer sous un nouveau nom.

Classe ImageViewer

ImageViewer affiche des images comme le prévoit QImageReader, en utilisant un QLabel.

Dans le constructeur, nous augmentons la limite d'allocation de QImageReader pour permettre l'affichage de photos plus grandes :

ImageViewer::ImageViewer() : m_formats(imageFormats())
{
    connect(this, &AbstractViewer::uiInitialized, this, &ImageViewer::setupImageUi);
    QImageReader::setAllocationLimit(1024); // MB
}

Dans la fonction openFile(), nous chargeons l'image et déterminons sa taille. Si elle est plus grande que l'écran, nous la réduisons à la taille de l'écran, en conservant le rapport hauteur/largeur. Ce calcul doit être effectué en pixels natifs, et le rapport de pixel de l'appareil doit être défini sur la pixmap résultante pour qu'elle 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 une page QTreeView. En interne, elle charge le contenu d'un fichier dans une page QJsonDocument et l'utilise pour remplir un modèle d'arbre personnalisé à l'aide de la page JsonItemModel.

Le plugin JSONViewer montre comment mettre en œuvre un modèle d'élément personnalisé hérité de QAbstractItemModel. La classe JsonTreeItem fournit une API de base pour la manipulation des données JSON et leur propagation vers le modèle sous-jacent QJsonDocument.

JsonViewer utilise les objets de premier niveau 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 PdfViewer (et le plugin) est un dérivé du PDF Viewer Widget Example. Elle démontre l'utilisation de QScroller pour parcourir un document en douceur.

Autres classes pertinentes

Classe HoverWatcher

La classe HoverWatcher définit un curseur de substitution lorsque la souris passe au-dessus d'un widget, et le rétablit lorsqu'elle quitte le widget. Pour éviter que plusieurs instances de HoverWatcher soient créées pour le même widget, elle est implémentée en tant que singleton par widget.

HoverWatcher hérite de QObject et prend le QWidget qu'il surveille comme 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 dresse la liste des actions auxquelles HoverWatcher réagit :

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

Les fonctions statiques créent des observateurs, vérifient leur existence pour une adresse QWidget spécifique ou suppriment 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 non pour chaque HoverAction. S'il n'y a pas de forme de curseur associée, le curseur prioritaire 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 prend l'action traitée comme argument :

void hoverAction(HoverAction action);
La classe RecentFiles

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

RecentFiles dispose d'emplacements permettant d'ajouter un seul fichier ou 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 supprimé de sa position initiale et ajouté au début de la liste.

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

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

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

Les emplacements qui permettent de sauvegarder et de restaurer à 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 contient la quantité maximale de fichiers récents à stocker (10 par défaut).

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

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

La classe RecentFileMenu

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

Son constructeur prend un pointeur vers un parent QObject et un pointeur vers un objet RecentFiles, dont il visualisera le contenu. Son signal fileOpened(), déclenché lorsque l'utilisateur sélectionne un fichier récent dans la liste, prend en argument le chemin absolu du fichier.

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 : German si la langue du système est l'allemand ; sinon, c'est l'anglais. L'utilisateur peut également changer de langue dans le menu Help > Language. Chaque plugin, ainsi que l'application principale, est indépendamment responsable du chargement de ses propres traductions pendant l'exécution.

Intégration de CMake

Le fichier CMakeLists.txt de premier niveau déclare les langues livrées.

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. En outre, elle fusionne les qtbase translations respectifs fournis par Qtt dans les fichiers de traduction générés, de sorte que les boîtes de dialogue Qt telles que la boîte de dialogue d'impression sont également correctement traduites :

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

Chaque niveau de plugin CMakeLists.txt invoque qt_add_translations uniquement sur les fichiers sources de ce plugin (SOURCE_TARGETS). Le fait d'adapter les fichiers de traduction à la cible du plugin permet d'éviter de scanner et de retraduire les sources de l'application principale et des autres plugins :

qt_add_translations(txtviewer
    SOURCE_TARGETS txtviewer
    TS_FILE_BASE txtviewer
)
Classe de traducteur

La classe Translator est une enveloppe autour de QTranslator de Qt qui gère l'internationalisation à la fois pour l'application principale et pour chaque plugin. Chaque composant (application principale et plugins) possède sa propre instance de Translator, ce qui permet de coordonner le changement de langue dans l'ensemble de l'application. Au démarrage ou lorsque l'utilisateur sélectionne une nouvelle langue, Translator::install() est appelé. 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 rétabli.

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()<< ". Retrouver la traduction anglaise"; setLanguage(QLocale::English) ; } }
Support des plugins

La classe de base AbstractViewer fournit trois méthodes permettant à chaque plugin de gérer sa propre traduction :

  • AbstractViewer::setTranslationBaseName()La classe de base Translator: initialise un objet , 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::updateTranslation()La classe de base install() appelle sur le traducteur existant pour installer les nouvelles traductions, puis appelle retranslate() pour actualiser tout le texte.
    void AbstractViewer::updateTranslation(QLocale::Language lang)
    {
        if (m_translator) {
            m_translator->setLanguage(lang);
            m_translator->install();
            retranslate();
        }
    }
  • AbstractViewer::retranslate()Méthode virtuelle : méthode virtuelle que chaque plugin implémente pour retraduire ses propres textes d'interface utilisateur. Par exemple, telle que réimplémentée dans ImageViewer :
    void ImageViewer::retranslate()
    {
        m_toolBar->setWindowTitle(tr("Images"));
        m_zoomInAct->setText(tr("Zoom &In"));
        m_zoomOutAct->setText(tr("Zoom &Out"));
        m_resetZoomAct->setText(tr("Reset Zoom"));
    }
Démarrage de l'application
  • Application principale: sur 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 AbstractViewer::setTranslationBaseName() dans sa fonction init() pour initialiser un traducteur avec le nom de son fichier de traduction et installer les traductions de la langue courante.
    void ImageViewer::init(QFile *file, QWidget *parent, QMainWindow *mainWindow)
        ...
        setTranslationBaseName("imgviewer"_L1);
        ...
Changement de langue en cours d'exécution

Le changement de langue en cours d'exécution peut se faire de deux manières :

  1. En utilisant le menu Help > Language: Cliquer sur l'élément QMenu déclenche MainWindow::onActionSwitchLanguage(), qui installera la nouvelle langue et retraduira l'application principale et les plugins :
    void MainWindow::onActionSwitchLanguage(QLocale::Language lang)
    {
        m_translator.setLanguage(lang);
        m_translator.install();
        ui->retranslateUi(this);
        const auto viewerList = m_factory->viewers();
        for (AbstractViewer *viewer : viewerList)
            viewer->updateTranslation(lang);
        statusBar()->clearMessage();
    }
  2. En changeant la langue de l'ensemble du système au moment de l'exécution : L'application peut réagir en écoutant les événements et en appelant MainWindow::onActionSwitchLanguage() sur l'événement QEvent::LocaleChange.
    void MainWindow::changeEvent(QEvent *event)
    {
        if (event->type() == QEvent::LocaleChange)
            onActionSwitchLanguage(QLocale::system().language());
    
        QMainWindow::changeEvent(event);
    }

Fichiers sources

Exemple de projet @ 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.