browserwindow.cpp Example File

webenginewidgets/simplebrowser/browserwindow.cpp

  /****************************************************************************
  **
  ** Copyright (C) 2016 The Qt Company Ltd.
  ** Contact: http://www.qt.io/licensing/
  **
  ** This file is part of the examples of the Qt Toolkit.
  **
  ** $QT_BEGIN_LICENSE:BSD$
  ** 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 "tabwidget.h"
  #include "urllineedit.h"
  #include "webview.h"
  #include <QApplication>
  #include <QCloseEvent>
  #include <QDesktopWidget>
  #include <QFileDialog>
  #include <QMenuBar>
  #include <QMessageBox>
  #include <QProgressBar>
  #include <QStatusBar>
  #include <QToolBar>
  #include <QVBoxLayout>

  BrowserWindow::BrowserWindow(QWidget *parent, Qt::WindowFlags flags)
      : QMainWindow(parent, flags)
      , m_tabWidget(new TabWidget(this))
      , m_progressBar(new QProgressBar(this))
      , m_historyBackAction(nullptr)
      , m_historyForwardAction(nullptr)
      , m_stopAction(nullptr)
      , m_reloadAction(nullptr)
      , m_stopReloadAction(nullptr)
      , m_urlLineEdit(new UrlLineEdit(this))
  {
      setToolButtonStyle(Qt::ToolButtonFollowStyle);
      setAttribute(Qt::WA_DeleteOnClose, true);

      QToolBar *toolbar = createToolBar();
      addToolBar(toolbar);
      menuBar()->addMenu(createFileMenu(m_tabWidget));
      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->setMargin(0);
      addToolBarBreak();

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

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

      connect(m_tabWidget, &TabWidget::titleChanged, this, &BrowserWindow::handleWebViewTitleChanged);
      connect(m_tabWidget, &TabWidget::linkHovered, [this](const QString& url) {
          statusBar()->showMessage(url);
      });
      connect(m_tabWidget, &TabWidget::loadProgress, this, &BrowserWindow::handleWebViewLoadProgress);
      connect(m_tabWidget, &TabWidget::urlChanged, this, &BrowserWindow::handleWebViewUrlChanged);
      connect(m_tabWidget, &TabWidget::iconChanged, this, &BrowserWindow::handleWebViewIconChanged);
      connect(m_tabWidget, &TabWidget::webActionEnabledChanged, this, &BrowserWindow::handleWebActionEnabledChanged);
      connect(m_urlLineEdit, &QLineEdit::returnPressed, this, [this]() {
          m_urlLineEdit->setFavIcon(QIcon(QStringLiteral(":defaulticon.png")));
          loadPage(m_urlLineEdit->url());
      });

      m_urlLineEdit->setFavIcon(QIcon(QStringLiteral(":defaulticon.png")));

      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(tr("Qt Simple Browser"));
      m_tabWidget->createTab();
  }

  BrowserWindow::~BrowserWindow()
  {
  }

  QSize BrowserWindow::sizeHint() const
  {
      QRect desktopRect = QApplication::desktop()->screenGeometry();
      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);

      QAction *newTabAction = new QAction(QIcon(QLatin1String(":addtab.png")), tr("New &Tab"), this);
      newTabAction->setShortcuts(QKeySequence::AddTab);
      newTabAction->setIconVisibleInMenu(false);
      connect(newTabAction, &QAction::triggered, tabWidget, &TabWidget::createTab);
      fileMenu->addAction(newTabAction);

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

      QAction *closeTabAction = new QAction(QIcon(QLatin1String(":closetab.png")), tr("&Close Tab"), this);
      closeTabAction->setShortcuts(QKeySequence::Close);
      closeTabAction->setIconVisibleInMenu(false);
      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, [closeAction]() {
          if (Browser::instance().windows().count() == 1)
              closeAction->setText(tr("&Quit"));
          else
              closeAction->setText(tr("&Close Window"));
      });
      return fileMenu;
  }

  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 = Browser::instance().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->setAllowedAreas(Qt::TopToolBarArea | Qt::BottomToolBarArea);
      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")));
      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")));
      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);
      navigationBar->addWidget(m_urlLineEdit);
      int size = m_urlLineEdit->sizeHint().height();
      navigationBar->setIconSize(QSize(size, size));
      return navigationBar;
  }

  void BrowserWindow::handleWebViewIconChanged(const QIcon &icon)
  {
      m_urlLineEdit->setFavIcon(icon);
  }

  void BrowserWindow::handleWebViewUrlChanged(const QUrl &url)
  {
      m_urlLineEdit->setUrl(url);
      if (url.isEmpty())
          m_urlLineEdit->setFocus();
  }

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

  void BrowserWindow::handleWebViewTitleChanged(const QString &title)
  {
      if (title.isEmpty())
          setWindowTitle(tr("Qt Simple Browser"));
      else
          setWindowTitle(tr("%1 - Qt Simple Browser").arg(title));
  }

  void BrowserWindow::handleNewWindowTriggered()
  {
      BrowserWindow *window = new BrowserWindow();
      Browser::instance().addWindow(window);
      window->loadHomePage();
  }

  void BrowserWindow::handleFileOpenTriggered()
  {
      QString file = QFileDialog::getOpenFileName(this, tr("Open Web Resource"), QString(),
                                                  tr("Web Resources (*.html *.htm *.svg *.png *.gif *.svgz);;All files (*.*)"));
      if (file.isEmpty())
          return;
      loadPage(file);
  }

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

  void BrowserWindow::loadHomePage()
  {
      loadPage(QStringLiteral("http://www.qt.io"));
  }

  void BrowserWindow::loadPage(const QString &page)
  {
      loadPage(QUrl::fromUserInput(page));
  }

  void BrowserWindow::loadPage(const QUrl &url)
  {
      if (url.isValid()) {
          m_urlLineEdit->setUrl(url);
          m_tabWidget->setUrl(url);
      }
  }

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

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