browserwindow.cpp Example File

webenginewidgets/simplebrowser/browserwindow.cpp
 /****************************************************************************
 **
 ** Copyright (C) 2016 The Qt Company Ltd.
 ** Contact: https://www.qt.io/licensing/
 **
 ** This file is part of the examples of the Qt Toolkit.
 **
 ** $QT_BEGIN_LICENSE:BSD$
 ** Commercial License Usage
 ** Licensees holding valid commercial Qt licenses may use this file in
 ** accordance with the commercial license agreement provided with the
 ** Software or, alternatively, in accordance with the terms contained in
 ** a written agreement between you and The Qt Company. For licensing terms
 ** and conditions see https://www.qt.io/terms-conditions. For further
 ** information use the contact form at https://www.qt.io/contact-us.
 **
 ** BSD License Usage
 ** Alternatively, you may use this file under the terms of the BSD license
 ** as follows:
 **
 ** "Redistribution and use in source and binary forms, with or without
 ** modification, are permitted provided that the following conditions are
 ** met:
 **   * Redistributions of source code must retain the above copyright
 **     notice, this list of conditions and the following disclaimer.
 **   * Redistributions in binary form must reproduce the above copyright
 **     notice, this list of conditions and the following disclaimer in
 **     the documentation and/or other materials provided with the
 **     distribution.
 **   * Neither the name of The Qt Company Ltd nor the names of its
 **     contributors may be used to endorse or promote products derived
 **     from this software without specific prior written permission.
 **
 **
 ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
 **
 ** $QT_END_LICENSE$
 **
 ****************************************************************************/

 #include "browser.h"
 #include "browserwindow.h"
 #include "downloadmanagerwidget.h"
 #include "tabwidget.h"
 #include "webview.h"
 #include <QApplication>
 #include <QCloseEvent>
 #include <QDesktopWidget>
 #include <QEvent>
 #include <QFileDialog>
 #include <QInputDialog>
 #include <QMenuBar>
 #include <QMessageBox>
 #include <QProgressBar>
 #include <QScreen>
 #include <QStatusBar>
 #include <QToolBar>
 #include <QVBoxLayout>
 #if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
 #include <QWebEngineFindTextResult>
 #endif
 #include <QWebEngineProfile>

 BrowserWindow::BrowserWindow(Browser *browser, QWebEngineProfile *profile, bool forDevTools)
     : m_browser(browser)
     , m_profile(profile)
     , m_tabWidget(new TabWidget(profile, this))
     , m_progressBar(nullptr)
     , m_historyBackAction(nullptr)
     , m_historyForwardAction(nullptr)
     , m_stopAction(nullptr)
     , m_reloadAction(nullptr)
     , m_stopReloadAction(nullptr)
     , m_urlLineEdit(nullptr)
     , m_favAction(nullptr)
 {
     setAttribute(Qt::WA_DeleteOnClose, true);
     setFocusPolicy(Qt::ClickFocus);

     if (!forDevTools) {
         m_progressBar = new QProgressBar(this);

         QToolBar *toolbar = createToolBar();
         addToolBar(toolbar);
         menuBar()->addMenu(createFileMenu(m_tabWidget));
         menuBar()->addMenu(createEditMenu());
         menuBar()->addMenu(createViewMenu(toolbar));
         menuBar()->addMenu(createWindowMenu(m_tabWidget));
         menuBar()->addMenu(createHelpMenu());
     }

     QWidget *centralWidget = new QWidget(this);
     QVBoxLayout *layout = new QVBoxLayout;
     layout->setSpacing(0);
     layout->setContentsMargins(0, 0, 0, 0);
     if (!forDevTools) {
         addToolBarBreak();

         m_progressBar->setMaximumHeight(1);
         m_progressBar->setTextVisible(false);
         m_progressBar->setStyleSheet(QStringLiteral("QProgressBar {border: 0px} QProgressBar::chunk {background-color: #da4453}"));

         layout->addWidget(m_progressBar);
     }

     layout->addWidget(m_tabWidget);
     centralWidget->setLayout(layout);
     setCentralWidget(centralWidget);

     connect(m_tabWidget, &TabWidget::titleChanged, this, &BrowserWindow::handleWebViewTitleChanged);
     if (!forDevTools) {
         connect(m_tabWidget, &TabWidget::linkHovered, [this](const QString& url) {
             statusBar()->showMessage(url);
         });
         connect(m_tabWidget, &TabWidget::loadProgress, this, &BrowserWindow::handleWebViewLoadProgress);
         connect(m_tabWidget, &TabWidget::webActionEnabledChanged, this, &BrowserWindow::handleWebActionEnabledChanged);
         connect(m_tabWidget, &TabWidget::urlChanged, [this](const QUrl &url) {
             m_urlLineEdit->setText(url.toDisplayString());
         });
         connect(m_tabWidget, &TabWidget::favIconChanged, m_favAction, &QAction::setIcon);
         connect(m_tabWidget, &TabWidget::devToolsRequested, this, &BrowserWindow::handleDevToolsRequested);
         connect(m_urlLineEdit, &QLineEdit::returnPressed, [this]() {
             m_tabWidget->setUrl(QUrl::fromUserInput(m_urlLineEdit->text()));
         });
 #if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
         connect(m_tabWidget, &TabWidget::findTextFinished, this, &BrowserWindow::handleFindTextFinished);
 #endif

         QAction *focusUrlLineEditAction = new QAction(this);
         addAction(focusUrlLineEditAction);
         focusUrlLineEditAction->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_L));
         connect(focusUrlLineEditAction, &QAction::triggered, this, [this] () {
             m_urlLineEdit->setFocus(Qt::ShortcutFocusReason);
         });
     }

     handleWebViewTitleChanged(QString());
     m_tabWidget->createTab();
 }

 QSize BrowserWindow::sizeHint() const
 {
     QRect desktopRect = QApplication::primaryScreen()->geometry();
     QSize size = desktopRect.size() * qreal(0.9);
     return size;
 }

 QMenu *BrowserWindow::createFileMenu(TabWidget *tabWidget)
 {
     QMenu *fileMenu = new QMenu(tr("&File"));
     fileMenu->addAction(tr("&New Window"), this, &BrowserWindow::handleNewWindowTriggered, QKeySequence::New);
     fileMenu->addAction(tr("New &Incognito Window"), this, &BrowserWindow::handleNewIncognitoWindowTriggered);

     QAction *newTabAction = new QAction(tr("New &Tab"), this);
     newTabAction->setShortcuts(QKeySequence::AddTab);
     connect(newTabAction, &QAction::triggered, this, [this]() {
         m_tabWidget->createTab();
         m_urlLineEdit->setFocus();
     });
     fileMenu->addAction(newTabAction);

     fileMenu->addAction(tr("&Open File..."), this, &BrowserWindow::handleFileOpenTriggered, QKeySequence::Open);
     fileMenu->addSeparator();

     QAction *closeTabAction = new QAction(tr("&Close Tab"), this);
     closeTabAction->setShortcuts(QKeySequence::Close);
     connect(closeTabAction, &QAction::triggered, [tabWidget]() {
         tabWidget->closeTab(tabWidget->currentIndex());
     });
     fileMenu->addAction(closeTabAction);

     QAction *closeAction = new QAction(tr("&Quit"),this);
     closeAction->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_Q));
     connect(closeAction, &QAction::triggered, this, &QWidget::close);
     fileMenu->addAction(closeAction);

     connect(fileMenu, &QMenu::aboutToShow, [this, closeAction]() {
         if (m_browser->windows().count() == 1)
             closeAction->setText(tr("&Quit"));
         else
             closeAction->setText(tr("&Close Window"));
     });
     return fileMenu;
 }

 QMenu *BrowserWindow::createEditMenu()
 {
     QMenu *editMenu = new QMenu(tr("&Edit"));
     QAction *findAction = editMenu->addAction(tr("&Find"));
     findAction->setShortcuts(QKeySequence::Find);
     connect(findAction, &QAction::triggered, this, &BrowserWindow::handleFindActionTriggered);

     QAction *findNextAction = editMenu->addAction(tr("Find &Next"));
     findNextAction->setShortcut(QKeySequence::FindNext);
     connect(findNextAction, &QAction::triggered, [this]() {
         if (!currentTab() || m_lastSearch.isEmpty())
             return;
         currentTab()->findText(m_lastSearch);
     });

     QAction *findPreviousAction = editMenu->addAction(tr("Find &Previous"));
     findPreviousAction->setShortcut(QKeySequence::FindPrevious);
     connect(findPreviousAction, &QAction::triggered, [this]() {
         if (!currentTab() || m_lastSearch.isEmpty())
             return;
         currentTab()->findText(m_lastSearch, QWebEnginePage::FindBackward);
     });

     return editMenu;
 }

 QMenu *BrowserWindow::createViewMenu(QToolBar *toolbar)
 {
     QMenu *viewMenu = new QMenu(tr("&View"));
     m_stopAction = viewMenu->addAction(tr("&Stop"));
     QList<QKeySequence> shortcuts;
     shortcuts.append(QKeySequence(Qt::CTRL | Qt::Key_Period));
     shortcuts.append(Qt::Key_Escape);
     m_stopAction->setShortcuts(shortcuts);
     connect(m_stopAction, &QAction::triggered, [this]() {
         m_tabWidget->triggerWebPageAction(QWebEnginePage::Stop);
     });

     m_reloadAction = viewMenu->addAction(tr("Reload Page"));
     m_reloadAction->setShortcuts(QKeySequence::Refresh);
     connect(m_reloadAction, &QAction::triggered, [this]() {
         m_tabWidget->triggerWebPageAction(QWebEnginePage::Reload);
     });

     QAction *zoomIn = viewMenu->addAction(tr("Zoom &In"));
     zoomIn->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_Plus));
     connect(zoomIn, &QAction::triggered, [this]() {
         if (currentTab())
             currentTab()->setZoomFactor(currentTab()->zoomFactor() + 0.1);
     });

     QAction *zoomOut = viewMenu->addAction(tr("Zoom &Out"));
     zoomOut->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_Minus));
     connect(zoomOut, &QAction::triggered, [this]() {
         if (currentTab())
             currentTab()->setZoomFactor(currentTab()->zoomFactor() - 0.1);
     });

     QAction *resetZoom = viewMenu->addAction(tr("Reset &Zoom"));
     resetZoom->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_0));
     connect(resetZoom, &QAction::triggered, [this]() {
         if (currentTab())
             currentTab()->setZoomFactor(1.0);
     });

     viewMenu->addSeparator();
     QAction *viewToolbarAction = new QAction(tr("Hide Toolbar"),this);
     viewToolbarAction->setShortcut(tr("Ctrl+|"));
     connect(viewToolbarAction, &QAction::triggered, [toolbar,viewToolbarAction]() {
         if (toolbar->isVisible()) {
             viewToolbarAction->setText(tr("Show Toolbar"));
             toolbar->close();
         } else {
             viewToolbarAction->setText(tr("Hide Toolbar"));
             toolbar->show();
         }
     });
     viewMenu->addAction(viewToolbarAction);

     QAction *viewStatusbarAction = new QAction(tr("Hide Status Bar"), this);
     viewStatusbarAction->setShortcut(tr("Ctrl+/"));
     connect(viewStatusbarAction, &QAction::triggered, [this, viewStatusbarAction]() {
         if (statusBar()->isVisible()) {
             viewStatusbarAction->setText(tr("Show Status Bar"));
             statusBar()->close();
         } else {
             viewStatusbarAction->setText(tr("Hide Status Bar"));
             statusBar()->show();
         }
     });
     viewMenu->addAction(viewStatusbarAction);
     return viewMenu;
 }

 QMenu *BrowserWindow::createWindowMenu(TabWidget *tabWidget)
 {
     QMenu *menu = new QMenu(tr("&Window"));

     QAction *nextTabAction = new QAction(tr("Show Next Tab"), this);
     QList<QKeySequence> shortcuts;
     shortcuts.append(QKeySequence(Qt::CTRL | Qt::Key_BraceRight));
     shortcuts.append(QKeySequence(Qt::CTRL | Qt::Key_PageDown));
     shortcuts.append(QKeySequence(Qt::CTRL | Qt::Key_BracketRight));
     shortcuts.append(QKeySequence(Qt::CTRL | Qt::Key_Less));
     nextTabAction->setShortcuts(shortcuts);
     connect(nextTabAction, &QAction::triggered, tabWidget, &TabWidget::nextTab);

     QAction *previousTabAction = new QAction(tr("Show Previous Tab"), this);
     shortcuts.clear();
     shortcuts.append(QKeySequence(Qt::CTRL | Qt::Key_BraceLeft));
     shortcuts.append(QKeySequence(Qt::CTRL | Qt::Key_PageUp));
     shortcuts.append(QKeySequence(Qt::CTRL | Qt::Key_BracketLeft));
     shortcuts.append(QKeySequence(Qt::CTRL | Qt::Key_Greater));
     previousTabAction->setShortcuts(shortcuts);
     connect(previousTabAction, &QAction::triggered, tabWidget, &TabWidget::previousTab);

     connect(menu, &QMenu::aboutToShow, [this, menu, nextTabAction, previousTabAction]() {
         menu->clear();
         menu->addAction(nextTabAction);
         menu->addAction(previousTabAction);
         menu->addSeparator();

         QVector<BrowserWindow*> windows = m_browser->windows();
         int index(-1);
         for (auto window : windows) {
             QAction *action = menu->addAction(window->windowTitle(), this, &BrowserWindow::handleShowWindowTriggered);
             action->setData(++index);
             action->setCheckable(true);
             if (window == this)
                 action->setChecked(true);
         }
     });
     return menu;
 }

 QMenu *BrowserWindow::createHelpMenu()
 {
     QMenu *helpMenu = new QMenu(tr("&Help"));
     helpMenu->addAction(tr("About &Qt"), qApp, QApplication::aboutQt);
     return helpMenu;
 }

 QToolBar *BrowserWindow::createToolBar()
 {
     QToolBar *navigationBar = new QToolBar(tr("Navigation"));
     navigationBar->setMovable(false);
     navigationBar->toggleViewAction()->setEnabled(false);

     m_historyBackAction = new QAction(this);
     QList<QKeySequence> backShortcuts = QKeySequence::keyBindings(QKeySequence::Back);
     for (auto it = backShortcuts.begin(); it != backShortcuts.end();) {
         // Chromium already handles navigate on backspace when appropriate.
         if ((*it)[0] == Qt::Key_Backspace)
             it = backShortcuts.erase(it);
         else
             ++it;
     }
     // For some reason Qt doesn't bind the dedicated Back key to Back.
     backShortcuts.append(QKeySequence(Qt::Key_Back));
     m_historyBackAction->setShortcuts(backShortcuts);
     m_historyBackAction->setIconVisibleInMenu(false);
     m_historyBackAction->setIcon(QIcon(QStringLiteral(":go-previous.png")));
     m_historyBackAction->setToolTip(tr("Go back in history"));
     connect(m_historyBackAction, &QAction::triggered, [this]() {
         m_tabWidget->triggerWebPageAction(QWebEnginePage::Back);
     });
     navigationBar->addAction(m_historyBackAction);

     m_historyForwardAction = new QAction(this);
     QList<QKeySequence> fwdShortcuts = QKeySequence::keyBindings(QKeySequence::Forward);
     for (auto it = fwdShortcuts.begin(); it != fwdShortcuts.end();) {
         if (((*it)[0] & Qt::Key_unknown) == Qt::Key_Backspace)
             it = fwdShortcuts.erase(it);
         else
             ++it;
     }
     fwdShortcuts.append(QKeySequence(Qt::Key_Forward));
     m_historyForwardAction->setShortcuts(fwdShortcuts);
     m_historyForwardAction->setIconVisibleInMenu(false);
     m_historyForwardAction->setIcon(QIcon(QStringLiteral(":go-next.png")));
     m_historyForwardAction->setToolTip(tr("Go forward in history"));
     connect(m_historyForwardAction, &QAction::triggered, [this]() {
         m_tabWidget->triggerWebPageAction(QWebEnginePage::Forward);
     });
     navigationBar->addAction(m_historyForwardAction);

     m_stopReloadAction = new QAction(this);
     connect(m_stopReloadAction, &QAction::triggered, [this]() {
         m_tabWidget->triggerWebPageAction(QWebEnginePage::WebAction(m_stopReloadAction->data().toInt()));
     });
     navigationBar->addAction(m_stopReloadAction);

     m_urlLineEdit = new QLineEdit(this);
     m_favAction = new QAction(this);
     m_urlLineEdit->addAction(m_favAction, QLineEdit::LeadingPosition);
     m_urlLineEdit->setClearButtonEnabled(true);
     navigationBar->addWidget(m_urlLineEdit);

     auto downloadsAction = new QAction(this);
     downloadsAction->setIcon(QIcon(QStringLiteral(":go-bottom.png")));
     downloadsAction->setToolTip(tr("Show downloads"));
     navigationBar->addAction(downloadsAction);
     connect(downloadsAction, &QAction::triggered, [this]() {
         m_browser->downloadManagerWidget().show();
     });

     return navigationBar;
 }

 void BrowserWindow::handleWebActionEnabledChanged(QWebEnginePage::WebAction action, bool enabled)
 {
     switch (action) {
     case QWebEnginePage::Back:
         m_historyBackAction->setEnabled(enabled);
         break;
     case QWebEnginePage::Forward:
         m_historyForwardAction->setEnabled(enabled);
         break;
     case QWebEnginePage::Reload:
         m_reloadAction->setEnabled(enabled);
         break;
     case QWebEnginePage::Stop:
         m_stopAction->setEnabled(enabled);
         break;
     default:
         qWarning("Unhandled webActionChanged signal");
     }
 }

 void BrowserWindow::handleWebViewTitleChanged(const QString &title)
 {
     QString suffix = m_profile->isOffTheRecord()
         ? tr("Qt Simple Browser (Incognito)")
         : tr("Qt Simple Browser");

     if (title.isEmpty())
         setWindowTitle(suffix);
     else
         setWindowTitle(title + " - " + suffix);
 }

 void BrowserWindow::handleNewWindowTriggered()
 {
     BrowserWindow *window = m_browser->createWindow();
     window->m_urlLineEdit->setFocus();
 }

 void BrowserWindow::handleNewIncognitoWindowTriggered()
 {
     BrowserWindow *window = m_browser->createWindow(/* offTheRecord: */ true);
     window->m_urlLineEdit->setFocus();
 }

 void BrowserWindow::handleFileOpenTriggered()
 {
     QUrl url = QFileDialog::getOpenFileUrl(this, tr("Open Web Resource"), QString(),
                                                 tr("Web Resources (*.html *.htm *.svg *.png *.gif *.svgz);;All files (*.*)"));
     if (url.isEmpty())
         return;
     currentTab()->setUrl(url);
 }

 void BrowserWindow::handleFindActionTriggered()
 {
     if (!currentTab())
         return;
     bool ok = false;
     QString search = QInputDialog::getText(this, tr("Find"),
                                            tr("Find:"), QLineEdit::Normal,
                                            m_lastSearch, &ok);
     if (ok && !search.isEmpty()) {
         m_lastSearch = search;
 #if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
         currentTab()->findText(m_lastSearch);
 #else
         currentTab()->findText(m_lastSearch, 0, [this](bool found) {
             if (!found)
                 statusBar()->showMessage(tr("\"%1\" not found.").arg(m_lastSearch));
         });
 #endif
     }
 }

 void BrowserWindow::closeEvent(QCloseEvent *event)
 {
     if (m_tabWidget->count() > 1) {
         int ret = QMessageBox::warning(this, tr("Confirm close"),
                                        tr("Are you sure you want to close the window ?\n"
                                           "There are %1 tabs open.").arg(m_tabWidget->count()),
                                        QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
         if (ret == QMessageBox::No) {
             event->ignore();
             return;
         }
     }
     event->accept();
     deleteLater();
 }

 TabWidget *BrowserWindow::tabWidget() const
 {
     return m_tabWidget;
 }

 WebView *BrowserWindow::currentTab() const
 {
     return m_tabWidget->currentWebView();
 }

 void BrowserWindow::handleWebViewLoadProgress(int progress)
 {
     static QIcon stopIcon(QStringLiteral(":process-stop.png"));
     static QIcon reloadIcon(QStringLiteral(":view-refresh.png"));

     if (0 < progress && progress < 100) {
         m_stopReloadAction->setData(QWebEnginePage::Stop);
         m_stopReloadAction->setIcon(stopIcon);
         m_stopReloadAction->setToolTip(tr("Stop loading the current page"));
         m_progressBar->setValue(progress);
     } else {
         m_stopReloadAction->setData(QWebEnginePage::Reload);
         m_stopReloadAction->setIcon(reloadIcon);
         m_stopReloadAction->setToolTip(tr("Reload the current page"));
         m_progressBar->setValue(0);
     }
 }

 void BrowserWindow::handleShowWindowTriggered()
 {
     if (QAction *action = qobject_cast<QAction*>(sender())) {
         int offset = action->data().toInt();
         QVector<BrowserWindow*> windows = m_browser->windows();
         windows.at(offset)->activateWindow();
         windows.at(offset)->currentTab()->setFocus();
     }
 }

 void BrowserWindow::handleDevToolsRequested(QWebEnginePage *source)
 {
     source->setDevToolsPage(m_browser->createDevToolsWindow()->currentTab()->page());
     source->triggerAction(QWebEnginePage::InspectElement);
 }

 #if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
 void BrowserWindow::handleFindTextFinished(const QWebEngineFindTextResult &result)
 {
     if (result.numberOfMatches() == 0) {
         statusBar()->showMessage(tr("\"%1\" not found.").arg(m_lastSearch));
     } else {
         statusBar()->showMessage(tr("\"%1\" found: %2/%3").arg(m_lastSearch,
                                                                QString::number(result.activeMatch()),
                                                                QString::number(result.numberOfMatches())));
     }
 }
 #endif