Revision control

++ // Copyright (C) 2023 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
++
++ #include "mainwindow.h"
++ #include "ui_mainwindow.h"
++ #include "viewerfactory.h"
++ #include "abstractviewer.h"
++ #include "recentfiles.h"
++ #include "recentfilemenu.h"
++ #include "translator.h"
++
++ #include <QDragEnterEvent>
++ #include <QDragMoveEvent>
++ #include <QDropEvent>
++ #include <QFileDialog>
++ #include <QToolButton>
++ #include <QMessageBox>
++ #include <QMimeData>
++
++ #include <QDir>
++ #include <QSettings>
++
++ using namespace Qt::StringLiterals;
++
++ MainWindow::MainWindow(Translator &translator, QWidget *parent)
++ : QMainWindow(parent), ui(new Ui::MainWindow), m_translator(translator)
++ {
++ ui->setupUi(this);
++ connect(ui->actionOpen, &QAction::triggered, this, &MainWindow::onActionOpenTriggered);
++ connect(ui->actionAbout, &QAction::triggered, this, &MainWindow::onActionAboutTriggered);
++ connect(ui->actionAboutQt, &QAction::triggered, this, &MainWindow::onActionAboutQtTriggered);
++ connect(ui->actionDeutsch, &QAction::triggered, this,
++ [this] { onActionSwitchLanguage(QLocale::German); });
++ connect(ui->actionEnglish, &QAction::triggered, this,
++ [this] { onActionSwitchLanguage(QLocale::English); });
++
++ m_recentFiles.reset(new RecentFiles(ui->actionRecent));
++ connect(m_recentFiles.get(), &RecentFiles::countChanged, this, [&](int count){
++ ui->actionRecent->setText(tr("%n recent files", nullptr, count));
++ });
++
++ setAcceptDrops(true);
++
++ readSettings();
++ m_factory.reset(new ViewerFactory(ui->viewArea, this));
++ const QStringList &viewers = m_factory->viewerNames();
++
++ const QString msg = tr("Available viewers: %1").arg(viewers.join(", "_L1));
++ statusBar()->showMessage(msg);
++
++ auto *menu = new RecentFileMenu(this, m_recentFiles.get());
++ ui->actionRecent->setMenu(menu);
++ connect(menu, &RecentFileMenu::fileOpened, this, &MainWindow::openFile);
++ QWidget *w = ui->mainToolBar->widgetForAction(ui->actionRecent);
++ auto *button = qobject_cast<QToolButton *>(w);
++ if (button)
++ connect(ui->actionRecent, &QAction::triggered, button, &QToolButton::showMenu);
++ }
++
++ bool MainWindow::hasPlugins() const
++ {
++ return m_factory ? !m_factory->viewers().isEmpty() : false;
++ }
++
++ MainWindow::~MainWindow()
++ {
++ saveSettings();
++ }
++
++ void MainWindow::onActionSwitchLanguage(QLocale::Language lang)
++ {
++ QLocale::setDefault(QLocale(lang));
++ QEvent event(QEvent::LocaleChange);
++ QCoreApplication::sendEvent(this, &event);
++ }
++
++ void MainWindow::onActionOpenTriggered()
++ {
++ QFileDialog fileDialog(this, tr("Open Document"), m_currentDir.absolutePath());
++ fileDialog.setOptions(QFileDialog::DontUseNativeDialog);
++ while (fileDialog.exec() == QDialog::Accepted
++ && !openFile(fileDialog.selectedFiles().constFirst())) {
++ }
++ }
++
++ bool MainWindow::openFile(const QString &fileName)
++ {
++ QFile *file = new QFile(fileName);
++ if (!file->exists()) {
++ statusBar()->showMessage(tr("File %1 could not be opened")
++ .arg(QDir::toNativeSeparators(fileName)));
++ delete file;
++ return false;
++ }
++
++ QFileInfo fileInfo(*file);
++ m_currentDir = fileInfo.dir();
++ m_recentFiles->addFile(fileInfo.absoluteFilePath());
++
++ // If a viewer is already open, clean it up and save its settings
++ resetViewer();
++ m_viewer = m_factory->viewer(file);
++ if (!m_viewer) {
++ statusBar()->showMessage(tr("File %1 can't be opened.")
++ .arg(QDir::toNativeSeparators(fileName)));
++ return false;
++ }
++
++ for (const QMetaObject::Connection &connection : m_viewerConnections)
++ disconnect(connection);
++
++ m_viewerConnections = {
++ connect(m_viewer, &AbstractViewer::printingEnabledChanged, ui->actionPrint,
++ &QAction::setEnabled),
++ connect(ui->actionPrint, &QAction::triggered, m_viewer, &AbstractViewer::print),
++ connect(m_viewer, &AbstractViewer::showMessage, statusBar(), &QStatusBar::showMessage)
++ };
++ m_viewer->initViewer(ui->actionBack, ui->actionForward, ui->menuHelp->menuAction(), ui->tabWidget);
++ restoreViewerSettings();
++ ui->scrollArea->setWidget(m_viewer->widget());
++ return true;
++ }
++
++ bool MainWindow::openData(const QByteArray &data, const QString &mimeType)
++ {
++ resetViewer();
++
++ m_viewer = m_factory->viewer(data, mimeType);
++
++ for (const QMetaObject::Connection &connection : m_viewerConnections)
++ disconnect(connection);
++
++ m_viewerConnections = {
++ connect(m_viewer, &AbstractViewer::printingEnabledChanged, ui->actionPrint,
++ &QAction::setEnabled),
++ connect(ui->actionPrint, &QAction::triggered, m_viewer, &AbstractViewer::print),
++ connect(m_viewer, &AbstractViewer::showMessage, statusBar(), &QStatusBar::showMessage)
++ };
++
++ m_viewer->initViewer(ui->actionBack, ui->actionForward, ui->menuHelp->menuAction(), ui->tabWidget);
++ restoreViewerSettings();
++ ui->scrollArea->setWidget(m_viewer->widget());
++ return true;
++ }
++
++ void MainWindow::dragEnterEvent(QDragEnterEvent *event)
++ {
++ event->acceptProposedAction();
++ return;
++ }
++
++ void MainWindow::dragMoveEvent(QDragMoveEvent *event)
++ {
++ event->acceptProposedAction();
++ return;
++ }
++
++ void MainWindow::dropEvent(QDropEvent *event)
++ {
++ const QMimeData *mime = event->mimeData();
++
++ if (mime->hasUrls()) {
++ for (const QUrl &url : mime->urls()) {
++ if (url.isLocalFile()) {
++ if (openFile(url.toLocalFile())) {
++ event->acceptProposedAction();
++ return;
++ }
++ }
++ }
++ }
++
++ for (const QString &format : mime->formats()) {
++ QByteArray data = mime->data(format);
++
++ #ifdef Q_OS_WIN
++ if (format.contains(QStringLiteral("FileContents"))) {
++ for (int i = 0; ; ++i) {
++ QString indexed = format + QStringLiteral(";index=") + QString::number(i);
++ data = mime->data(indexed);
++
++ if (data.isEmpty())
++ break;
++
++ openData(data, QStringLiteral("application/octet-stream"));
++ event->acceptProposedAction();
++ return;
++ }
++ }
++ #endif
++
++ if (data.isEmpty())
++ continue;
++
++ else if (format.contains(QStringLiteral("image/"))) {
++ openData(data, format);
++ event->acceptProposedAction();
++ return;
++ }
++
++ else if (mime->hasImage())
++ continue;
++
++ else if (format == QStringLiteral("application/json") ||
++ format == QStringLiteral("text/plain")) {
++ QString detectedFormat = format;
++
++ if (format == QStringLiteral("text/plain")) {
++ QByteArray trimmed = data.trimmed();
++
++ if (trimmed.startsWith('{') || trimmed.startsWith('[')) {
++ detectedFormat = QStringLiteral("application/json");
++ }
++ }
++
++ openData(data, detectedFormat);
++ event->acceptProposedAction();
++ return;
++ }
++
++ }
++ event->ignore();
++ }
++
++ 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);
++ }
++
++ void MainWindow::onActionAboutTriggered()
++ {
++ const QString viewerNames = m_factory->viewerNames().join(", "_L1);
++ const QString mimeTypes = m_factory->supportedMimeTypes().join(u'\n');
++ QString text = tr("A Widgets application to display and print JSON, "
++ "text and PDF files. Demonstrates various features to use "
++ "in widget applications: Using QSettings, query and save "
++ "user preferences, manage file histories and control cursor "
++ "behavior when hovering over widgets.\n\n"
++ "This version has loaded the following plugins:\n%1\n"
++ "\n\nIt supports the following mime types:\n%2")
++ .arg(viewerNames, mimeTypes);
++
++ if (auto *def = m_factory->defaultViewer())
++ text += tr("\n\nOther mime types will be displayed with %1.").arg(def->viewerName());
++
++ QMessageBox::about(this, tr("About Document Viewer Demo"), text);
++ }
++
++ void MainWindow::onActionAboutQtTriggered()
++ {
++ QMessageBox::aboutQt(this);
++ }
++
++ void MainWindow::readSettings()
++ {
++ QSettings settings;
++
++ // Restore working directory
++ if (settings.contains(settingsDir))
++ m_currentDir = QDir(settings.value(settingsDir).toString());
++ else
++ m_currentDir = QDir::current();
++
++ // Restore QMainWindow state
++ if (settings.contains(settingsMainWindow)) {
++ QByteArray mainWindowState = settings.value(settingsMainWindow).toByteArray();
++ restoreState(mainWindowState);
++ }
++
++ // Restore recent files
++ m_recentFiles->restoreFromSettings(settings, settingsFiles);
++ }
++
++ void MainWindow::saveSettings() const
++ {
++ QSettings settings;
++
++ // Save working directory
++ settings.setValue(settingsDir, m_currentDir.absolutePath());
++
++ // Save QMainWindow state
++ settings.setValue(settingsMainWindow, saveState());
++
++ // Save recent files
++ m_recentFiles->saveSettings(settings, settingsFiles);
++
++ settings.sync();
++ }
++
++ void MainWindow::saveViewerSettings() const
++ {
++ if (!m_viewer)
++ return;
++
++ QSettings settings;
++ settings.beginGroup(settingsViewers);
++ settings.setValue(m_viewer->viewerName(), m_viewer->saveState());
++ settings.endGroup();
++ settings.sync();
++ }
++
++ void MainWindow::resetViewer() const
++ {
++ if (!m_viewer)
++ return;
++
++ saveViewerSettings();
++ m_viewer->cleanup();
++ }
++
++ 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);
++ }
++