/**************************************************************************** ** ** 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 #include #include #include #include #include #include #include #include #include #include #include #include #if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) #include #endif #include 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 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 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 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 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 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(sender())) { int offset = action->data().toInt(); QVector 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