ドキュメントビューア

JSON、テキスト、PDFファイルを表示、印刷するためのウィジェットアプリケーションです。

ドキュメント・ビューアのUIに「開くモードの選択」ポップアップが表示される

Document Viewerは、静的および動的なツールバー、メニュー、アクションを備えたQMainWindow 。さらに、ウィジェット・ベースのアプリケーションにおける以下の機能も紹介します:

  • QSettings を使用して、ユーザー設定を照会および保存し、以前に開いたファイルの履歴を管理する。
  • ウィジェットにカーソルを置いたときのカーソル動作の制御。
  • 動的にロードされるプラグインの作成。
  • 異なる言語へのUIのローカライズ。

例の実行

からサンプルを実行するには Qt Creatorからサンプルを実行するには、Welcome モードを開き、Examples からサンプルを選択します。詳細については、Qt Creator:Tutorialを参照してください:ビルドと実行

アプリケーションとメイン・ウィンドウの作成

アプリケーションとそのメイン・ウィンドウはmain.cpp で構築されます。main()関数は、コマンドライン引数(helpversion、オプションの位置引数file)を処理するためにQCommandLineParser を使用します。ユーザーがアプリケーションの起動時にファイルへのパスを指定した場合、メイン・ウィンドウはそのファイルを開きます:

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();
}

MainWindowクラス

MainWindow クラスは、メニュー、アクション、ツールバーを備えたアプリケーション画面を提供します。ファイルを開くことができ、そのコンテンツ・タイプを自動的に検出します。また、QSettings を使用して、以前に開いたファイルのリストを保持し、起動時に設定を保存して再読み込みします。MainWindowは、コンテンツ・タイプに基づいて、開かれたファイルに適したビューアを作成し、ドキュメントの印刷をサポートします。

MainWindowのコンストラクタは、Qt Designer で作成されたユーザー・インターフェイスを初期化します。mainwindow.ui ファイルは左側にQTabWidget を提供し、ブックマークとサムネイルを表示します。右側には、ファイルの内容を表示するためのQScrollArea があります。

ViewerFactoryクラス

ViewerFactory クラスは、既知のファイルタイプのビューアを管理します。これらのビューアはプラグインとして実装されます。ViewerFactoryのインスタンスが作成されると、ビューエリアとメインウィンドウへのポインタがコンストラクタに渡されます:

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

ViewerFactory は、構築時に利用可能なすべてのプラグインをロードします。ViewerFactory は、ロードされたプラグイン、その名前、およびサポートされる MIME タイプを照会するためのパブリック API を提供します:

    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;

viewer() 関数は、引数として渡されたQFile を開くのに適したプラグインへのポインタを返します:

m_viewer = m_factory->viewer(file);

アプリケーション設定にビューア用のセクションが含まれている場合、それはビューアの仮想restoreState() 関数に渡されます:

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);
}

次に、標準のUIアセットがビューアに渡され、ビューアの表示ウィジェットが表示されるようにメインスクロールエリアが設定されます:

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

AbstractViewerクラス

AbstractViewer は、ドキュメントを表示、保存、印刷するための一般化されたAPIを提供します。ドキュメントとビューアの両方のプロパティを問い合わせることができます:

  • 文書に内容はあるか?
  • 文書に内容はあるか?
  • 概要(サムネイルやブックマーク)はサポートされていますか?

AbstractViewerは、派生クラスがメインウィンドウ上にアクションやメニューを作成するためのプロテクトメソッドを提供します。これらのアセットをメインウィンドウに表示するために、アセットがメインウィンドウの親になります。AbstractViewerは、作成したUIアセットの削除と破棄を行います。シグナルとスロットを実装するために、QObject を継承しています。

シグナル

void uiInitialized();

このシグナルは、ビューアがメインウィンドウ上のUIアセットに関するすべての必要な情報を受け取った後に発行されます。

void printingEnabledChanged(bool enabled);

このシグナルは、ドキュメントの印刷が有効または無効になったときに発行されます。これは、新しいドキュメントが正常にロードされた後、または、例えば、すべてのコンテンツが削除された後に発生します。

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

このシグナルは、ユーザーにステータス・メッセージを表示するために発行されます。

void documentLoaded(const QString &fileName);

このシグナルは、ドキュメントが正常に読み込まれたことをアプリケーションに通知します。

TxtViewerクラス

TxtViewer は、AbstractViewerを継承したシンプルなテキストビューアです。テキストファイルの編集、コピー/カット&ペースト、印刷、変更の保存をサポートしています。

ImageViewerクラス

ImageViewer は、QImageReader でサポートされているように、QLabel を使用して画像を表示します。

コンストラクタでは、QImageReader の割り当て制限を増やして、より大きな写真を表示できるようにしています:

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

openFile()関数では、画像をロードし、そのサイズを決定します。画像が画面より大きい場合は、アスペクト比を維持したまま画面サイズに縮小します。この計算はネイティブ・ピクセルで行う必要があり、結果のピクセルマップを鮮明に表示するには、デバイスのピクセル比を設定する必要があります:

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();
}

JsonViewerクラス

JsonViewer は、JSON ファイルをQTreeView に表示します。内部的には、ファイルのコンテンツをQJsonDocument にロードし、それを使用してカスタム ツリー モデルにJsonItemModel を入力します。

JSONビューアプラグインは、QAbstractItemModel から継承されたカスタムアイテムモデルを実装する方法を示しています。JsonTreeItem クラスは、JSONデータを操作し、それを基礎となるQJsonDocument に伝搬するための基本的なAPIを提供します。

JsonViewer は、ドキュメントの最上位オブジェクトをナビゲーションのブックマークとして使用します。他のノード(キーと値)は、追加のブックマークとして追加したり、ブックマークリストから削除したりすることができます。

PdfViewerクラス

PdfViewer クラス(とプラグイン)はPDF Viewer Widget Example のフォークです。これは、ドキュメントをスムーズにフリックするためのQScroller の使い方を示しています。

その他の関連クラス

HoverWatcherクラス

HoverWatcher クラスは、マウスをウィジェットの上に置くとオーバーライドカーソルを設定し、離れると元に戻します。同じウィジェットに複数のHoverWatcherインスタンスが生成されるのを防ぐため、ウィジェットごとにシングルトンとして実装されています。

HoverWatcher はQObject を継承し、ウォッチするQWidget をインスタンスの親とします。ホバーイベントを消費せずにインターセプトするイベントフィルタをインストールします:

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);
}

HoverAction enumは、HoverWatcherが反応するアクションをリストしています:

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

静的関数は、ウォッチャーを作成したり、特定のQWidget に対してウォッチャーの存在をチェックしたり、ウォッチャーを解除したりします:

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

カーソル形状は、各HoverActionに対して設定または解除することができます。関連するカーソル形状がない場合、アクションがトリガーされると、アプリケーションのオーバーライドカーソルが復元されます。

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

mouseButtons プロパティは、MousePress アクションを考慮するマウスボタンを保持します:

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

アクション固有のシグナルは、アクションを処理した後に発せられる:

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

一般的なシグナルは、処理されたアクションを引数として渡します:

void hoverAction(HoverAction action);
RecentFilesクラス

RecentFiles は、最近開いたファイルのリストを管理することに特化したQStringList

RecentFilesは、単一のファイルまたは複数のファイルを一度に追加するスロットを持っています。パスが存在し、開くことができるファイルを指している場合、エントリが最近開いたファイルのリストに追加されます。ファイルがすでにリストにある場合、そのファイルは元の位置から取り除かれ、一番上に追加される。

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

ファイルは、名前またはインデックスによってリストから削除される:

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

QSettings からの保存と復元を実行するスロット:

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

設定をリストアする場合、存在しないファイルは無視されます。maxFiles プロパティは、保存する最近のファイルの最大量を保持します(デフォルトは10)。

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

RecentFiles は、ファイルを受け入れる前に、そのファイルが読み込めるかどうかを確認します。

RecentFileMenu クラス

RecentFileMenu は、RecentFilesオブジェクトをサブメニューとして表示することに特化したQMenu

コンストラクタは、親クラス(QObject )へのポインタと、表示するRecentFilesオブジェクトへのポインタを受け取ります。fileOpened() シグナルは、ユーザが最近のファイルを選択したときに発生し、引数としてファイルの絶対パスを渡します。

注: RecentFileMenu は、親ウィジェットか、コンストラクタに渡されたRecentFiles オブジェクトによって破棄されます。

class RecentFileMenu : public QMenu
{
    Q_OBJECT

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

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

翻訳

アプリケーションのユーザー・インターフェースは、英語とドイツ語で利用できます。デフォルトの言語は Qt によって自動選択されます:システム言語がドイツ語であればドイツ語、そうでなければ英語です。また、Help >Language メニューで言語を切り替えることができます。各プラグインは、メインアプリケーションと同様に、実行時に独自の翻訳を読み込む責任があります。

CMakeの統合

トップレベルのCMakeLists.txtは、出荷された言語を宣言しています。

qt_standard_project_setup(REQUIRES 6.8
    I18N_SOURCE_LANGUAGE en
    I18N_TRANSLATED_LANGUAGES de
)

documentviewer ターゲットはメインアプリケーションを定義します。ローカライズされた文字列はdocviewer_de.tsとdocviewer_en.tsファイルに格納され、読み込まれます。さらに、印刷ダイアログのようなQtダイアログも適切に翻訳されるように、Qtが提供するそれぞれのqtbase translations を生成された翻訳ファイルにマージします:

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

各プラグインのレベルCMakeLists.txt は、そのプラグインのソースファイル (SOURCE_TARGETS) に対してのみqt_add_translations を呼び出します。翻訳ファイルをプラグインターゲットにスコープすることで、メインアプリケーションや他のプラグインのソースを再スキャンして再翻訳することを防ぎます:

qt_add_translations(txtviewer
    SOURCE_TARGETS txtviewer
    TS_FILE_BASE txtviewer
)
トランスレータクラス

Translator クラスは Qt のQTranslator のラッパーで、メインアプリケーションと各プラグインの国際化を管理します。各コンポーネント(メインアプリケーションとプラグイン)は独自の Translator インスタンスを持っており、アプリケーション全体の言語切り替えを調整することができます。起動時またはユーザーが新しい言語を選択すると、Translator::install() が呼び出されます。このメソッドではQTranslator::load() を使用して、QLocale::uiLanguages() と Qt リソースシステムの basename に基づいて翻訳ファイルを読み込みます。一致する翻訳が見つからない場合は、英語にフォールバックします。

voidTranslator::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()!= ::英語) {if(m_trLocale.language()!= ::英語) QLocale::英語) {。            qWarning() << "Failed to load translation" << m_baseName <<
                   "ロケール用"<<m_trLocale.name()<< ".英語翻訳にフォールバック"; setLanguage(QLocale::English); } }
プラグイン対応

AbstractViewer 基本クラスは3つのメソッドを提供し、各プラグインが独自の翻訳を管理できるようにしています:

  • AbstractViewer::setTranslationBaseName():Translator オブジェクトを初期化し、そのベース名を設定し、デフォルトの翻訳を読み込むためにインストールします。
    void AbstractViewer::setTranslationBaseName(const QString &baseName)
    {
        m_translator.reset(new Translator);
        m_translator->setBaseName(baseName);
        m_translator->install();
    }
  • AbstractViewer::updateTranslation(): 既存の Translator でinstall() を呼び出して新しい翻訳をインストールし、retranslate() を呼び出してすべてのテキストをリフレッシュします。
    void AbstractViewer::updateTranslation(QLocale::Language lang)
    {
        if (m_translator) {
            m_translator->setLanguage(lang);
            m_translator->install();
            retranslate();
        }
    }
  • AbstractViewer::retranslate()各プラグインが自身のUIテキストを再翻訳するために実装する仮想メソッドです。例えば、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"));
    }
アプリケーション起動
  • メインアプリケーションmain.cpp では、ウィンドウを表示する前にアプリケーションの翻訳をロードします:
        Translator mainTranslator;
        mainTranslator.setBaseName("docviewer"_L1);
        mainTranslator.install();
  • プラグイン: 各プラグインはinit() 関数の中でAbstractViewer::setTranslationBaseName() を呼び出し、翻訳ファイル名でTranslatorを初期化し、現在の言語の翻訳をインストールします。
    void ImageViewer::init(QFile *file, QWidget *parent, QMainWindow *mainWindow)
        ...
        setTranslationBaseName("imgviewer"_L1);
        ...
ランタイムの言語切り替え

ランタイム言語の切り替えは、2つの方法で行うことができます:

  1. メニューHelp >Language:QMenu アイテムをクリックするとMainWindow::onActionSwitchLanguage() がトリガーされ、新しい言語がインストールされ、メインアプリケーションとプラグインが再翻訳されます:
    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. 実行時にシステム全体の言語を切り替えます:アプリケーションはイベントをリッスンし、QEvent::LocaleChange イベントでMainWindow::onActionSwitchLanguage() を呼び出すことで、これに反応することができます。
    void MainWindow::changeEvent(QEvent *event)
    {
        if (event->type() == QEvent::LocaleChange)
            onActionSwitchLanguage(QLocale::system().language());
    
        QMainWindow::changeEvent(event);
    }

ソース・ファイル

サンプルプロジェクト @ code.qt.io

すべてのQtサンプルも参照してください

© 2025 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.