File indexing completed on 2024-05-12 04:58:31
0001 /* ============================================================ 0002 * Falkon - Qt web browser 0003 * Copyright (C) 2010-2018 David Rosca <nowrep@gmail.com> 0004 * 0005 * This program is free software: you can redistribute it and/or modify 0006 * it under the terms of the GNU General Public License as published by 0007 * the Free Software Foundation, either version 3 of the License, or 0008 * (at your option) any later version. 0009 * 0010 * This program is distributed in the hope that it will be useful, 0011 * but WITHOUT ANY WARRANTY; without even the implied warranty of 0012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 0013 * GNU General Public License for more details. 0014 * 0015 * You should have received a copy of the GNU General Public License 0016 * along with this program. If not, see <http://www.gnu.org/licenses/>. 0017 * ============================================================ */ 0018 #include "webview.h" 0019 #include "webpage.h" 0020 #include "mainapplication.h" 0021 #include "qztools.h" 0022 #include "iconprovider.h" 0023 #include "history.h" 0024 #include "pluginproxy.h" 0025 #include "downloadmanager.h" 0026 #include "siteinfo.h" 0027 #include "searchenginesmanager.h" 0028 #include "browsinglibrary.h" 0029 #include "bookmarkstools.h" 0030 #include "settings.h" 0031 #include "qzsettings.h" 0032 #include "enhancedmenu.h" 0033 #include "locationbar.h" 0034 #include "webinspector.h" 0035 #include "scripts.h" 0036 #include "webhittestresult.h" 0037 #include "webscrollbarmanager.h" 0038 0039 #include <iostream> 0040 0041 #include <QDir> 0042 #include <QTimer> 0043 #include <QDesktopServices> 0044 #include <QWebEngineHistory> 0045 #include <QClipboard> 0046 #include <QMimeData> 0047 #include <QWebEngineContextMenuRequest> 0048 #include <QStackedLayout> 0049 #include <QScrollBar> 0050 #include <QPrintDialog> 0051 #include <QPrinter> 0052 #include <QQuickWidget> 0053 #include <QtWebEngineWidgetsVersion> 0054 0055 bool WebView::s_forceContextMenuOnMouseRelease = false; 0056 0057 WebView::WebView(QWidget* parent) 0058 : QWebEngineView(parent) 0059 , m_progress(100) 0060 , m_backgroundActivity(false) 0061 , m_page(nullptr) 0062 , m_firstLoad(false) 0063 { 0064 connect(this, &QWebEngineView::loadStarted, this, &WebView::slotLoadStarted); 0065 connect(this, &QWebEngineView::loadProgress, this, &WebView::slotLoadProgress); 0066 connect(this, &QWebEngineView::loadFinished, this, &WebView::slotLoadFinished); 0067 connect(this, &QWebEngineView::iconChanged, this, &WebView::slotIconChanged); 0068 connect(this, &QWebEngineView::urlChanged, this, &WebView::slotUrlChanged); 0069 connect(this, &QWebEngineView::titleChanged, this, &WebView::slotTitleChanged); 0070 0071 m_currentZoomLevel = zoomLevels().indexOf(100); 0072 0073 setAcceptDrops(true); 0074 installEventFilter(this); 0075 if (parentWidget()) { 0076 parentWidget()->installEventFilter(this); 0077 } 0078 0079 WebInspector::registerView(this); 0080 } 0081 0082 WebView::~WebView() 0083 { 0084 mApp->plugins()->emitWebPageDeleted(m_page); 0085 0086 WebInspector::unregisterView(this); 0087 WebScrollBarManager::instance()->removeWebView(this); 0088 } 0089 0090 QIcon WebView::icon(bool allowNull) const 0091 { 0092 if (!QWebEngineView::icon().isNull()) { 0093 return QWebEngineView::icon(); 0094 } 0095 0096 if (url().scheme() == QLatin1String("ftp")) { 0097 return IconProvider::standardIcon(QStyle::SP_ComputerIcon); 0098 } 0099 0100 if (url().scheme() == QLatin1String("file")) { 0101 return IconProvider::standardIcon(QStyle::SP_DriveHDIcon); 0102 } 0103 0104 return IconProvider::iconForUrl(url(), allowNull); 0105 } 0106 0107 QString WebView::title(bool allowEmpty) const 0108 { 0109 QString title = QWebEngineView::title(); 0110 0111 if (allowEmpty) { 0112 return title; 0113 } 0114 0115 const QUrl u = url().isEmpty() ? m_page->requestedUrl() : url(); 0116 0117 if (title.isEmpty()) { 0118 title = u.host(); 0119 } 0120 0121 if (title.isEmpty()) { 0122 title = u.toString(QUrl::RemoveFragment); 0123 } 0124 0125 if (title.isEmpty() || title == QL1S("about:blank")) { 0126 return tr("Empty Page"); 0127 } 0128 0129 return title; 0130 } 0131 0132 WebPage* WebView::page() const 0133 { 0134 return m_page; 0135 } 0136 0137 void WebView::setPage(WebPage *page) 0138 { 0139 if (m_page == page) { 0140 return; 0141 } 0142 0143 if (m_page) { 0144 if (m_page->isLoading()) { 0145 Q_EMIT m_page->loadProgress(100); 0146 Q_EMIT m_page->loadFinished(true); 0147 } 0148 mApp->plugins()->emitWebPageDeleted(m_page); 0149 } 0150 0151 page->setParent(this); 0152 QWebEngineView::setPage(page); 0153 delete m_page; 0154 m_page = page; 0155 0156 if (m_page->isLoading()) { 0157 Q_EMIT loadStarted(); 0158 Q_EMIT loadProgress(m_page->m_loadProgress); 0159 } 0160 0161 connect(m_page, &WebPage::privacyChanged, this, &WebView::privacyChanged); 0162 connect(m_page, &WebPage::printRequested, this, &WebView::printPage); 0163 0164 // Set default zoom level 0165 zoomReset(); 0166 0167 // Actions needs to be initialized for every QWebEnginePage change 0168 initializeActions(); 0169 0170 // Scrollbars must be added only after QWebEnginePage is set 0171 WebScrollBarManager::instance()->addWebView(this); 0172 0173 Q_EMIT pageChanged(m_page); 0174 mApp->plugins()->emitWebPageCreated(m_page); 0175 } 0176 0177 void WebView::load(const QUrl &url) 0178 { 0179 if (m_page && !m_page->acceptNavigationRequest(url, QWebEnginePage::NavigationTypeTyped, true)) { 0180 return; 0181 } 0182 0183 QWebEngineView::load(url); 0184 0185 if (!m_firstLoad) { 0186 m_firstLoad = true; 0187 WebInspector::pushView(this); 0188 } 0189 } 0190 0191 void WebView::load(const LoadRequest &request) 0192 { 0193 const QUrl reqUrl = request.url(); 0194 0195 if (reqUrl.isEmpty()) 0196 return; 0197 0198 if (reqUrl.scheme() == QL1S("javascript")) { 0199 const QString scriptSource = reqUrl.toString().mid(11); 0200 // Is the javascript source percent encoded or not? 0201 // Looking for % character in source should work in most cases 0202 if (scriptSource.contains(QL1C('%'))) 0203 page()->runJavaScript(QUrl::fromPercentEncoding(scriptSource.toUtf8())); 0204 else 0205 page()->runJavaScript(scriptSource); 0206 return; 0207 } 0208 0209 if (isUrlValid(reqUrl)) { 0210 loadRequest(request); 0211 } 0212 } 0213 0214 bool WebView::isLoading() const 0215 { 0216 return m_progress < 100; 0217 } 0218 0219 int WebView::loadingProgress() const 0220 { 0221 return m_progress; 0222 } 0223 0224 bool WebView::backgroundActivity() const 0225 { 0226 return m_backgroundActivity; 0227 } 0228 0229 int WebView::zoomLevel() const 0230 { 0231 return m_currentZoomLevel; 0232 } 0233 0234 void WebView::setZoomLevel(int level) 0235 { 0236 m_currentZoomLevel = level; 0237 applyZoom(); 0238 } 0239 0240 QPointF WebView::mapToViewport(const QPointF &pos) const 0241 { 0242 return page()->mapToViewport(pos); 0243 } 0244 0245 QRect WebView::scrollBarGeometry(Qt::Orientation orientation) const 0246 { 0247 QScrollBar *s = WebScrollBarManager::instance()->scrollBar(orientation, const_cast<WebView*>(this)); 0248 return s && s->isVisible() ? s->geometry() : QRect(); 0249 } 0250 0251 QWidget *WebView::inputWidget() const 0252 { 0253 return m_rwhvqt ? m_rwhvqt : const_cast<WebView*>(this); 0254 } 0255 0256 // static 0257 bool WebView::isUrlValid(const QUrl &url) 0258 { 0259 // Valid url must have scheme and actually contains something (therefore scheme:// is invalid) 0260 return url.isValid() && !url.scheme().isEmpty() && (!url.host().isEmpty() || !url.path().isEmpty() || url.hasQuery()); 0261 } 0262 0263 // static 0264 QList<int> WebView::zoomLevels() 0265 { 0266 return QList<int>() << 30 << 40 << 50 << 67 << 80 << 90 << 100 0267 << 110 << 120 << 133 << 150 << 170 << 200 0268 << 220 << 233 << 250 << 270 << 285 << 300; 0269 } 0270 0271 // static 0272 bool WebView::forceContextMenuOnMouseRelease() 0273 { 0274 return s_forceContextMenuOnMouseRelease; 0275 } 0276 0277 // static 0278 void WebView::setForceContextMenuOnMouseRelease(bool force) 0279 { 0280 // Windows open context menu on mouse release by default 0281 #ifndef Q_OS_WIN 0282 s_forceContextMenuOnMouseRelease = force; 0283 #endif 0284 } 0285 0286 void WebView::addNotification(QWidget* notif) 0287 { 0288 Q_EMIT showNotification(notif); 0289 } 0290 0291 void WebView::applyZoom() 0292 { 0293 setZoomFactor(qreal(zoomLevels().at(m_currentZoomLevel)) / 100.0); 0294 0295 Q_EMIT zoomLevelChanged(m_currentZoomLevel); 0296 } 0297 0298 void WebView::zoomIn() 0299 { 0300 if (m_currentZoomLevel < zoomLevels().count() - 1) { 0301 m_currentZoomLevel++; 0302 applyZoom(); 0303 } 0304 } 0305 0306 void WebView::zoomOut() 0307 { 0308 if (m_currentZoomLevel > 0) { 0309 m_currentZoomLevel--; 0310 applyZoom(); 0311 } 0312 } 0313 0314 void WebView::zoomReset() 0315 { 0316 if (m_currentZoomLevel != qzSettings->defaultZoomLevel) { 0317 m_currentZoomLevel = qzSettings->defaultZoomLevel; 0318 applyZoom(); 0319 } 0320 } 0321 0322 void WebView::editUndo() 0323 { 0324 triggerPageAction(QWebEnginePage::Undo); 0325 } 0326 0327 void WebView::editRedo() 0328 { 0329 triggerPageAction(QWebEnginePage::Redo); 0330 } 0331 0332 void WebView::editCut() 0333 { 0334 triggerPageAction(QWebEnginePage::Cut); 0335 } 0336 0337 void WebView::editCopy() 0338 { 0339 triggerPageAction(QWebEnginePage::Copy); 0340 } 0341 0342 void WebView::editPaste() 0343 { 0344 triggerPageAction(QWebEnginePage::Paste); 0345 } 0346 0347 void WebView::editSelectAll() 0348 { 0349 triggerPageAction(QWebEnginePage::SelectAll); 0350 } 0351 0352 void WebView::editDelete() 0353 { 0354 QKeyEvent ev(QEvent::KeyPress, Qt::Key_Delete, Qt::NoModifier); 0355 QApplication::sendEvent(this, &ev); 0356 } 0357 0358 void WebView::reloadBypassCache() 0359 { 0360 triggerPageAction(QWebEnginePage::ReloadAndBypassCache); 0361 } 0362 0363 void WebView::back() 0364 { 0365 QWebEngineHistory* history = page()->history(); 0366 0367 if (history->canGoBack()) { 0368 history->back(); 0369 0370 Q_EMIT urlChanged(url()); 0371 } 0372 } 0373 0374 void WebView::forward() 0375 { 0376 QWebEngineHistory* history = page()->history(); 0377 0378 if (history->canGoForward()) { 0379 history->forward(); 0380 0381 Q_EMIT urlChanged(url()); 0382 } 0383 } 0384 0385 void WebView::printPage() 0386 { 0387 Q_ASSERT(m_page); 0388 0389 auto *printer = new QPrinter(); 0390 printer->setCreator(tr("Falkon %1 (%2)").arg(QString::fromLatin1(Qz::VERSION), QString::fromLatin1(Qz::WWWADDRESS))); 0391 printer->setDocName(QzTools::filterCharsFromFilename(title())); 0392 0393 auto *dialog = new QPrintDialog(printer, this); 0394 dialog->setOptions(QAbstractPrintDialog::PrintToFile | QAbstractPrintDialog::PrintShowPageSize); 0395 #ifndef Q_OS_WIN 0396 dialog->setOption(QAbstractPrintDialog::PrintPageRange); 0397 dialog->setOption(QAbstractPrintDialog::PrintCollateCopies); 0398 #endif 0399 0400 if (dialog->exec() == QDialog::Accepted) { 0401 if (dialog->printer()->outputFormat() == QPrinter::PdfFormat) { 0402 m_page->printToPdf(dialog->printer()->outputFileName(), dialog->printer()->pageLayout()); 0403 delete dialog; 0404 } else { 0405 connect(this, &QWebEngineView::printFinished, this, [&dialog](bool success) { 0406 Q_UNUSED(success); 0407 delete dialog; 0408 }); 0409 } 0410 } 0411 } 0412 0413 void WebView::slotLoadStarted() 0414 { 0415 m_progress = 0; 0416 0417 if (title(/*allowEmpty*/true).isEmpty()) { 0418 Q_EMIT titleChanged(title()); 0419 } 0420 } 0421 0422 void WebView::slotLoadProgress(int progress) 0423 { 0424 if (m_progress < 100) { 0425 m_progress = progress; 0426 } 0427 0428 // QtWebEngine sometimes forgets applied zoom factor 0429 if (!qFuzzyCompare(zoomFactor(), zoomLevels().at(m_currentZoomLevel) / 100.0)) { 0430 applyZoom(); 0431 } 0432 } 0433 0434 void WebView::slotLoadFinished(bool ok) 0435 { 0436 m_progress = 100; 0437 0438 if (ok) 0439 mApp->history()->addHistoryEntry(this); 0440 } 0441 0442 void WebView::slotIconChanged() 0443 { 0444 IconProvider::instance()->saveIcon(this); 0445 } 0446 0447 void WebView::slotUrlChanged(const QUrl &url) 0448 { 0449 if (!url.isEmpty() && title(/*allowEmpty*/true).isEmpty()) { 0450 // Don't treat this as background activity change 0451 const bool oldActivity = m_backgroundActivity; 0452 m_backgroundActivity = true; 0453 Q_EMIT titleChanged(title()); 0454 m_backgroundActivity = oldActivity; 0455 } 0456 } 0457 0458 void WebView::slotTitleChanged(const QString &title) 0459 { 0460 Q_UNUSED(title) 0461 0462 if (!isVisible() && !isLoading() && !m_backgroundActivity) { 0463 m_backgroundActivity = true; 0464 Q_EMIT backgroundActivityChanged(m_backgroundActivity); 0465 } 0466 } 0467 0468 void WebView::openUrlInNewWindow() 0469 { 0470 if (auto* action = qobject_cast<QAction*>(sender())) { 0471 mApp->createWindow(Qz::BW_NewWindow, action->data().toUrl()); 0472 } 0473 } 0474 0475 void WebView::sendTextByMail() 0476 { 0477 if (auto* action = qobject_cast<QAction*>(sender())) { 0478 const QUrl mailUrl = QUrl::fromEncoded( 0479 QByteArray("mailto:%20?body=" + 0480 QUrl::toPercentEncoding(action->data().toString()))); 0481 QDesktopServices::openUrl(mailUrl); 0482 } 0483 } 0484 0485 void WebView::sendPageByMail() 0486 { 0487 const QUrl mailUrl = QUrl::fromEncoded(QByteArray( 0488 "mailto:%20?body=" + 0489 QUrl::toPercentEncoding(QString::fromUtf8(url().toEncoded())) + 0490 "&subject=" + QUrl::toPercentEncoding(title()))); 0491 QDesktopServices::openUrl(mailUrl); 0492 } 0493 0494 void WebView::copyLinkToClipboard() 0495 { 0496 if (auto* action = qobject_cast<QAction*>(sender())) { 0497 QApplication::clipboard()->setText(QString::fromUtf8(action->data().toUrl().toEncoded())); 0498 } 0499 } 0500 0501 void WebView::savePageAs() 0502 { 0503 page()->runJavaScript(QSL("document.contentType"), WebPage::SafeJsWorld, [this](const QVariant &res) { 0504 const QSet<QString> webPageTypes = { 0505 QSL("text/html"), 0506 QSL("application/xhtml+xml") 0507 }; 0508 if (res.isNull() || webPageTypes.contains(res.toString())) { 0509 triggerPageAction(QWebEnginePage::SavePage); 0510 } else { 0511 page()->download(url()); 0512 } 0513 }); 0514 } 0515 0516 void WebView::copyImageToClipboard() 0517 { 0518 triggerPageAction(QWebEnginePage::CopyImageToClipboard); 0519 } 0520 0521 void WebView::downloadLinkToDisk() 0522 { 0523 triggerPageAction(QWebEnginePage::DownloadLinkToDisk); 0524 } 0525 0526 void WebView::downloadImageToDisk() 0527 { 0528 triggerPageAction(QWebEnginePage::DownloadImageToDisk); 0529 } 0530 0531 void WebView::downloadMediaToDisk() 0532 { 0533 triggerPageAction(QWebEnginePage::DownloadMediaToDisk); 0534 } 0535 0536 void WebView::openUrlInNewTab(const QUrl &url, Qz::NewTabPositionFlags position) 0537 { 0538 loadInNewTab(url, position); 0539 } 0540 0541 void WebView::openActionUrl() 0542 { 0543 if (auto* action = qobject_cast<QAction*>(sender())) { 0544 load(action->data().toUrl()); 0545 } 0546 } 0547 0548 void WebView::showSource() 0549 { 0550 // view-source: doesn't work on itself and custom schemes 0551 if (url().scheme() == QL1S("view-source") || url().scheme() == QL1S("falkon") || url().scheme() == QL1S("qrc")) { 0552 page()->toHtml([](const QString &html) { 0553 std::cout << html.toLocal8Bit().constData() << std::endl; 0554 }); 0555 return; 0556 } 0557 0558 triggerPageAction(QWebEnginePage::ViewSource); 0559 } 0560 0561 void WebView::showSiteInfo() 0562 { 0563 auto* s = new SiteInfo(this); 0564 s->show(); 0565 } 0566 0567 void WebView::searchSelectedText() 0568 { 0569 SearchEngine engine = mApp->searchEnginesManager()->defaultEngine(); 0570 if (auto* act = qobject_cast<QAction*>(sender())) { 0571 if (act->data().isValid()) { 0572 engine = act->data().value<SearchEngine>(); 0573 } 0574 } 0575 0576 const LoadRequest req = mApp->searchEnginesManager()->searchResult(engine, selectedText()); 0577 loadInNewTab(req, Qz::NT_SelectedTab); 0578 } 0579 0580 void WebView::searchSelectedTextInBackgroundTab() 0581 { 0582 SearchEngine engine = mApp->searchEnginesManager()->defaultEngine(); 0583 if (auto* act = qobject_cast<QAction*>(sender())) { 0584 if (act->data().isValid()) { 0585 engine = act->data().value<SearchEngine>(); 0586 } 0587 } 0588 0589 const LoadRequest req = mApp->searchEnginesManager()->searchResult(engine, selectedText()); 0590 loadInNewTab(req, Qz::NT_NotSelectedTab); 0591 } 0592 0593 void WebView::bookmarkLink() 0594 { 0595 if (auto* action = qobject_cast<QAction*>(sender())) { 0596 if (action->data().isNull()) { 0597 BookmarksTools::addBookmarkDialog(this, url(), title()); 0598 } 0599 else { 0600 const QVariantList bData = action->data().value<QVariantList>(); 0601 const QString bookmarkTitle = bData.at(1).toString().isEmpty() ? title() : bData.at(1).toString(); 0602 0603 BookmarksTools::addBookmarkDialog(this, bData.at(0).toUrl(), bookmarkTitle); 0604 } 0605 } 0606 } 0607 0608 void WebView::openUrlInSelectedTab() 0609 { 0610 if (auto* action = qobject_cast<QAction*>(sender())) { 0611 openUrlInNewTab(action->data().toUrl(), Qz::NT_CleanSelectedTab); 0612 } 0613 } 0614 0615 void WebView::openUrlInBackgroundTab() 0616 { 0617 if (auto* action = qobject_cast<QAction*>(sender())) { 0618 openUrlInNewTab(action->data().toUrl(), Qz::NT_CleanNotSelectedTab); 0619 } 0620 } 0621 0622 void WebView::userDefinedOpenUrlInNewTab(const QUrl &url, bool invert) 0623 { 0624 Qz::NewTabPositionFlags position = qzSettings->newTabPosition; 0625 if (invert) { 0626 if (position & Qz::NT_SelectedTab) { 0627 position &= ~Qz::NT_SelectedTab; 0628 position |= Qz::NT_NotSelectedTab; 0629 } 0630 else { 0631 position &= ~Qz::NT_NotSelectedTab; 0632 position |= Qz::NT_SelectedTab; 0633 } 0634 } 0635 0636 QUrl actionUrl; 0637 0638 if (!url.isEmpty()) { 0639 actionUrl = url; 0640 } 0641 else if (auto* action = qobject_cast<QAction*>(sender())) { 0642 actionUrl = action->data().toUrl(); 0643 } 0644 0645 openUrlInNewTab(actionUrl, position); 0646 } 0647 0648 void WebView::userDefinedOpenUrlInBgTab(const QUrl &url) 0649 { 0650 QUrl actionUrl; 0651 0652 if (!url.isEmpty()) { 0653 actionUrl = url; 0654 } 0655 else if (auto* action = qobject_cast<QAction*>(sender())) { 0656 actionUrl = action->data().toUrl(); 0657 } 0658 0659 userDefinedOpenUrlInNewTab(actionUrl, true); 0660 } 0661 0662 void WebView::showEvent(QShowEvent *event) 0663 { 0664 QWebEngineView::showEvent(event); 0665 0666 if (m_backgroundActivity) { 0667 m_backgroundActivity = false; 0668 Q_EMIT backgroundActivityChanged(m_backgroundActivity); 0669 } 0670 } 0671 0672 void WebView::createContextMenu(QMenu *menu, WebHitTestResult &hitTest) 0673 { 0674 // cppcheck-suppress variableScope 0675 int spellCheckActionCount = 0; 0676 0677 const QWebEngineContextMenuRequest *contextMenuDataPtr = lastContextMenuRequest(); 0678 if (contextMenuDataPtr == NULL) { 0679 return; 0680 } 0681 const QWebEngineContextMenuRequest &contextMenuData = *contextMenuDataPtr; 0682 0683 hitTest.updateWithContextMenuData(contextMenuData); 0684 0685 if (!contextMenuData.misspelledWord().isEmpty()) { 0686 QFont boldFont = menu->font(); 0687 boldFont.setBold(true); 0688 0689 for (const QString &suggestion : contextMenuData.spellCheckerSuggestions()) { 0690 QAction *action = menu->addAction(suggestion); 0691 action->setFont(boldFont); 0692 0693 connect(action, &QAction::triggered, this, [=]() { 0694 page()->replaceMisspelledWord(suggestion); 0695 }); 0696 } 0697 0698 if (menu->actions().isEmpty()) { 0699 menu->addAction(tr("No suggestions"))->setEnabled(false); 0700 } 0701 0702 menu->addSeparator(); 0703 spellCheckActionCount = menu->actions().count(); 0704 } 0705 0706 if (!hitTest.linkUrl().isEmpty() && hitTest.linkUrl().scheme() != QL1S("javascript")) { 0707 createLinkContextMenu(menu, hitTest); 0708 } 0709 0710 if (!hitTest.imageUrl().isEmpty()) { 0711 createImageContextMenu(menu, hitTest); 0712 } 0713 0714 if (!hitTest.mediaUrl().isEmpty()) { 0715 createMediaContextMenu(menu, hitTest); 0716 } 0717 0718 if (hitTest.isContentEditable()) { 0719 // This only checks if the menu is empty (only spellchecker actions added) 0720 if (menu->actions().count() == spellCheckActionCount) { 0721 menu->addAction(pageAction(QWebEnginePage::Undo)); 0722 menu->addAction(pageAction(QWebEnginePage::Redo)); 0723 menu->addSeparator(); 0724 menu->addAction(pageAction(QWebEnginePage::Cut)); 0725 menu->addAction(pageAction(QWebEnginePage::Copy)); 0726 menu->addAction(pageAction(QWebEnginePage::Paste)); 0727 } 0728 0729 if (hitTest.tagName() == QL1S("input")) { 0730 QAction *act = menu->addAction(QString()); 0731 act->setVisible(false); 0732 checkForForm(act, hitTest.pos()); 0733 } 0734 } 0735 0736 if (!selectedText().isEmpty()) { 0737 createSelectedTextContextMenu(menu, hitTest); 0738 } 0739 0740 if (menu->isEmpty()) { 0741 createPageContextMenu(menu); 0742 } 0743 0744 menu->addSeparator(); 0745 mApp->plugins()->populateWebViewMenu(menu, this, hitTest); 0746 } 0747 0748 void WebView::createPageContextMenu(QMenu* menu) 0749 { 0750 QAction* action = menu->addAction(tr("&Back"), this, SLOT(back())); 0751 action->setIcon(IconProvider::standardIcon(QStyle::SP_ArrowBack)); 0752 action->setEnabled(history()->canGoBack()); 0753 0754 action = menu->addAction(tr("&Forward"), this, SLOT(forward())); 0755 action->setIcon(IconProvider::standardIcon(QStyle::SP_ArrowForward)); 0756 action->setEnabled(history()->canGoForward()); 0757 0758 // Special menu for Speed Dial page 0759 if (url().toString() == QL1S("falkon:speeddial")) { 0760 menu->addSeparator(); 0761 menu->addAction(QIcon::fromTheme(QSL("list-add")), tr("&Add New Page"), this, &WebView::addSpeedDial); 0762 menu->addAction(IconProvider::settingsIcon(), tr("&Configure Speed Dial"), this, &WebView::configureSpeedDial); 0763 menu->addSeparator(); 0764 menu->addAction(QIcon::fromTheme(QSL("view-refresh")), tr("Reload All Dials"), this, &WebView::reloadAllSpeedDials); 0765 return; 0766 } 0767 0768 QAction *reloadAction = pageAction(QWebEnginePage::Reload); 0769 action = menu->addAction(reloadAction->icon(), reloadAction->text(), reloadAction, &QAction::trigger); 0770 action->setVisible(reloadAction->isEnabled()); 0771 connect(reloadAction, &QAction::changed, action, [=]() { 0772 action->setVisible(reloadAction->isEnabled()); 0773 }); 0774 0775 QAction *stopAction = pageAction(QWebEnginePage::Stop); 0776 action = menu->addAction(stopAction->icon(), stopAction->text(), stopAction, &QAction::trigger); 0777 action->setVisible(stopAction->isEnabled()); 0778 connect(stopAction, &QAction::changed, action, [=]() { 0779 action->setVisible(stopAction->isEnabled()); 0780 }); 0781 0782 menu->addSeparator(); 0783 menu->addAction(QIcon::fromTheme(QSL("bookmark-new")), tr("Book&mark page"), this, &WebView::bookmarkLink); 0784 menu->addAction(QIcon::fromTheme(QSL("document-save")), tr("&Save page as..."), this, &WebView::savePageAs); 0785 menu->addAction(QIcon::fromTheme(QSL("edit-copy")), tr("&Copy page link"), this, &WebView::copyLinkToClipboard)->setData(url()); 0786 menu->addAction(QIcon::fromTheme(QSL("mail-message-new")), tr("Send page link..."), this, &WebView::sendPageByMail); 0787 menu->addSeparator(); 0788 menu->addAction(QIcon::fromTheme(QSL("edit-select-all")), tr("Select &all"), this, &WebView::editSelectAll); 0789 menu->addSeparator(); 0790 0791 const QString scheme = url().scheme(); 0792 0793 if (scheme != QL1S("view-source") && WebPage::internalSchemes().contains(scheme)) { 0794 menu->addAction(QIcon::fromTheme(QSL("text-html")), tr("Show so&urce code"), this, &WebView::showSource); 0795 } 0796 0797 if (SiteInfo::canShowSiteInfo(url())) 0798 menu->addAction(QIcon::fromTheme(QSL("dialog-information")), tr("Show info ab&out site"), this, &WebView::showSiteInfo); 0799 } 0800 0801 void WebView::createLinkContextMenu(QMenu* menu, const WebHitTestResult &hitTest) 0802 { 0803 menu->addSeparator(); 0804 auto* act = new Action(IconProvider::newTabIcon(), tr("Open link in new &tab")); 0805 act->setData(hitTest.linkUrl()); 0806 connect(act, SIGNAL(triggered()), this, SLOT(userDefinedOpenUrlInNewTab())); 0807 connect(act, SIGNAL(ctrlTriggered()), this, SLOT(userDefinedOpenUrlInBgTab())); 0808 menu->addAction(act); 0809 menu->addAction(IconProvider::newWindowIcon(), tr("Open link in new &window"), this, &WebView::openUrlInNewWindow)->setData(hitTest.linkUrl()); 0810 menu->addAction(IconProvider::privateBrowsingIcon(), tr("Open link in &private window"), mApp, SLOT(startPrivateBrowsing()))->setData(hitTest.linkUrl()); 0811 menu->addSeparator(); 0812 0813 QVariantList bData; 0814 bData << hitTest.linkUrl() << hitTest.linkTitle(); 0815 menu->addAction(QIcon::fromTheme(QSL("bookmark-new")), tr("B&ookmark link"), this, &WebView::bookmarkLink)->setData(bData); 0816 0817 menu->addAction(QIcon::fromTheme(QSL("document-save")), tr("&Save link as..."), this, &WebView::downloadLinkToDisk); 0818 menu->addAction(QIcon::fromTheme(QSL("mail-message-new")), tr("Send link..."), this, &WebView::sendTextByMail)->setData(hitTest.linkUrl().toEncoded()); 0819 menu->addAction(QIcon::fromTheme(QSL("edit-copy")), tr("&Copy link address"), this, &WebView::copyLinkToClipboard)->setData(hitTest.linkUrl()); 0820 menu->addSeparator(); 0821 0822 if (!selectedText().isEmpty()) { 0823 pageAction(QWebEnginePage::Copy)->setIcon(QIcon::fromTheme(QSL("edit-copy"))); 0824 menu->addAction(pageAction(QWebEnginePage::Copy)); 0825 } 0826 } 0827 0828 void WebView::createImageContextMenu(QMenu* menu, const WebHitTestResult &hitTest) 0829 { 0830 menu->addSeparator(); 0831 if (hitTest.imageUrl() != url()) { 0832 auto *act = new Action(tr("Show i&mage")); 0833 act->setData(hitTest.imageUrl()); 0834 connect(act, &QAction::triggered, this, &WebView::openActionUrl); 0835 connect(act, SIGNAL(ctrlTriggered()), this, SLOT(userDefinedOpenUrlInNewTab())); 0836 menu->addAction(act); 0837 } 0838 menu->addAction(tr("Copy image"), this, &WebView::copyImageToClipboard); 0839 menu->addAction(QIcon::fromTheme(QSL("edit-copy")), tr("Copy image ad&dress"), this, &WebView::copyLinkToClipboard)->setData(hitTest.imageUrl()); 0840 menu->addSeparator(); 0841 menu->addAction(QIcon::fromTheme(QSL("document-save")), tr("&Save image as..."), this, &WebView::downloadImageToDisk); 0842 menu->addAction(QIcon::fromTheme(QSL("mail-message-new")), tr("Send image..."), this, &WebView::sendTextByMail)->setData(hitTest.imageUrl().toEncoded()); 0843 menu->addSeparator(); 0844 0845 if (!selectedText().isEmpty()) { 0846 pageAction(QWebEnginePage::Copy)->setIcon(QIcon::fromTheme(QSL("edit-copy"))); 0847 menu->addAction(pageAction(QWebEnginePage::Copy)); 0848 } 0849 } 0850 0851 void WebView::createSelectedTextContextMenu(QMenu* menu, const WebHitTestResult &hitTest) 0852 { 0853 Q_UNUSED(hitTest) 0854 0855 QString selectedText = page()->selectedText(); 0856 0857 menu->addSeparator(); 0858 if (!menu->actions().contains(pageAction(QWebEnginePage::Copy))) { 0859 menu->addAction(pageAction(QWebEnginePage::Copy)); 0860 } 0861 menu->addAction(QIcon::fromTheme(QSL("mail-message-new")), tr("Send text..."), this, &WebView::sendTextByMail)->setData(selectedText); 0862 menu->addSeparator(); 0863 0864 // #379: Remove newlines 0865 QString selectedString = selectedText.trimmed().remove(QLatin1Char('\n')); 0866 if (!selectedString.contains(QLatin1Char('.'))) { 0867 // Try to add .com 0868 selectedString.append(QLatin1String(".com")); 0869 } 0870 QUrl guessedUrl = QUrl::fromUserInput(selectedString); 0871 0872 if (isUrlValid(guessedUrl)) { 0873 auto* act = new Action(QIcon::fromTheme(QSL("document-open-remote")), tr("Go to &web address")); 0874 act->setData(guessedUrl); 0875 0876 connect(act, &QAction::triggered, this, &WebView::openActionUrl); 0877 connect(act, SIGNAL(ctrlTriggered()), this, SLOT(userDefinedOpenUrlInNewTab())); 0878 menu->addAction(act); 0879 } 0880 0881 menu->addSeparator(); 0882 selectedText.truncate(20); 0883 // KDE is displaying newlines in menu actions ... weird -,- 0884 selectedText.replace(QLatin1Char('\n'), QLatin1Char(' ')).replace(QLatin1Char('\t'), QLatin1Char(' ')); 0885 0886 SearchEngine engine = mApp->searchEnginesManager()->defaultEngine(); 0887 auto* act = new Action(engine.icon, tr("Search \"%1 ..\" with %2").arg(selectedText, engine.name)); 0888 connect(act, &QAction::triggered, this, &WebView::searchSelectedText); 0889 connect(act, &Action::ctrlTriggered, this, &WebView::searchSelectedTextInBackgroundTab); 0890 menu->addAction(act); 0891 0892 // Search with ... 0893 Menu* swMenu = new Menu(tr("Search with..."), menu); 0894 swMenu->setCloseOnMiddleClick(true); 0895 SearchEnginesManager* searchManager = mApp->searchEnginesManager(); 0896 const auto engines = searchManager->allEngines(); 0897 for (const SearchEngine &en : engines) { 0898 auto* act = new Action(en.icon, en.name); 0899 act->setData(QVariant::fromValue(en)); 0900 0901 connect(act, &QAction::triggered, this, &WebView::searchSelectedText); 0902 connect(act, &Action::ctrlTriggered, this, &WebView::searchSelectedTextInBackgroundTab); 0903 swMenu->addAction(act); 0904 } 0905 0906 menu->addMenu(swMenu); 0907 } 0908 0909 void WebView::createMediaContextMenu(QMenu *menu, const WebHitTestResult &hitTest) 0910 { 0911 bool paused = hitTest.mediaPaused(); 0912 bool muted = hitTest.mediaMuted(); 0913 0914 menu->addSeparator(); 0915 menu->addAction(paused ? tr("&Play") : tr("&Pause"), this, &WebView::toggleMediaPause)->setIcon(QIcon::fromTheme(paused ? QSL("media-playback-start") : QSL("media-playback-pause"))); 0916 menu->addAction(muted ? tr("Un&mute") : tr("&Mute"), this, &WebView::toggleMediaMute)->setIcon(QIcon::fromTheme(muted ? QSL("audio-volume-muted") : QSL("audio-volume-high"))); 0917 menu->addSeparator(); 0918 menu->addAction(QIcon::fromTheme(QSL("edit-copy")), tr("&Copy Media Address"), this, &WebView::copyLinkToClipboard)->setData(hitTest.mediaUrl()); 0919 menu->addAction(QIcon::fromTheme(QSL("mail-message-new")), tr("&Send Media Address"), this, &WebView::sendTextByMail)->setData(hitTest.mediaUrl().toEncoded()); 0920 menu->addAction(QIcon::fromTheme(QSL("document-save")), tr("Save Media To &Disk"), this, &WebView::downloadMediaToDisk); 0921 } 0922 0923 void WebView::checkForForm(QAction *action, const QPoint &pos) 0924 { 0925 m_clickedPos = mapToViewport(pos); 0926 QPointer<QAction> act = action; 0927 0928 page()->runJavaScript(Scripts::getFormData(m_clickedPos), WebPage::SafeJsWorld, [this, act](const QVariant &res) { 0929 const QVariantMap &map = res.toMap(); 0930 if (!act || map.isEmpty()) 0931 return; 0932 0933 const QUrl url = map.value(QSL("action")).toUrl(); 0934 const QString method = map.value(QSL("method")).toString(); 0935 0936 if (!url.isEmpty() && (method == QL1S("get") || method == QL1S("post"))) { 0937 act->setVisible(true); 0938 act->setIcon(QIcon::fromTheme(QSL("edit-find"), QIcon(QSL(":icons/menu/search-icon.svg")))); 0939 act->setText(tr("Create Search Engine")); 0940 connect(act.data(), &QAction::triggered, this, &WebView::createSearchEngine); 0941 } 0942 }); 0943 } 0944 0945 void WebView::createSearchEngine() 0946 { 0947 page()->runJavaScript(Scripts::getFormData(m_clickedPos), WebPage::SafeJsWorld, [this](const QVariant &res) { 0948 mApp->searchEnginesManager()->addEngineFromForm(res.toMap(), this); 0949 }); 0950 } 0951 0952 void WebView::addSpeedDial() 0953 { 0954 page()->runJavaScript(QSL("addSpeedDial()"), WebPage::SafeJsWorld); 0955 } 0956 0957 void WebView::configureSpeedDial() 0958 { 0959 page()->runJavaScript(QSL("configureSpeedDial()"), WebPage::SafeJsWorld); 0960 } 0961 0962 void WebView::reloadAllSpeedDials() 0963 { 0964 page()->runJavaScript(QSL("reloadAll()"), WebPage::SafeJsWorld); 0965 } 0966 0967 void WebView::toggleMediaPause() 0968 { 0969 triggerPageAction(QWebEnginePage::ToggleMediaPlayPause); 0970 } 0971 0972 void WebView::toggleMediaMute() 0973 { 0974 triggerPageAction(QWebEnginePage::ToggleMediaMute); 0975 } 0976 0977 void WebView::initializeActions() 0978 { 0979 QAction* undoAction = pageAction(QWebEnginePage::Undo); 0980 undoAction->setText(tr("&Undo")); 0981 undoAction->setShortcut(QKeySequence(QSL("Ctrl+Z"))); 0982 undoAction->setShortcutContext(Qt::WidgetWithChildrenShortcut); 0983 undoAction->setIcon(QIcon::fromTheme(QSL("edit-undo"))); 0984 0985 QAction* redoAction = pageAction(QWebEnginePage::Redo); 0986 redoAction->setText(tr("&Redo")); 0987 redoAction->setShortcut(QKeySequence(QSL("Ctrl+Shift+Z"))); 0988 redoAction->setShortcutContext(Qt::WidgetWithChildrenShortcut); 0989 redoAction->setIcon(QIcon::fromTheme(QSL("edit-redo"))); 0990 0991 QAction* cutAction = pageAction(QWebEnginePage::Cut); 0992 cutAction->setText(tr("&Cut")); 0993 cutAction->setShortcut(QKeySequence(QSL("Ctrl+X"))); 0994 cutAction->setShortcutContext(Qt::WidgetWithChildrenShortcut); 0995 cutAction->setIcon(QIcon::fromTheme(QSL("edit-cut"))); 0996 0997 QAction* copyAction = pageAction(QWebEnginePage::Copy); 0998 copyAction->setText(tr("&Copy")); 0999 copyAction->setShortcut(QKeySequence(QSL("Ctrl+C"))); 1000 copyAction->setShortcutContext(Qt::WidgetWithChildrenShortcut); 1001 copyAction->setIcon(QIcon::fromTheme(QSL("edit-copy"))); 1002 1003 QAction* pasteAction = pageAction(QWebEnginePage::Paste); 1004 pasteAction->setText(tr("&Paste")); 1005 pasteAction->setShortcut(QKeySequence(QSL("Ctrl+V"))); 1006 pasteAction->setShortcutContext(Qt::WidgetWithChildrenShortcut); 1007 pasteAction->setIcon(QIcon::fromTheme(QSL("edit-paste"))); 1008 1009 QAction* selectAllAction = pageAction(QWebEnginePage::SelectAll); 1010 selectAllAction->setText(tr("Select All")); 1011 selectAllAction->setShortcut(QKeySequence(QSL("Ctrl+A"))); 1012 selectAllAction->setShortcutContext(Qt::WidgetWithChildrenShortcut); 1013 selectAllAction->setIcon(QIcon::fromTheme(QSL("edit-select-all"))); 1014 1015 QAction* reloadAction = pageAction(QWebEnginePage::Reload); 1016 reloadAction->setText(tr("&Reload")); 1017 reloadAction->setIcon(QIcon::fromTheme(QSL("view-refresh"))); 1018 1019 QAction* stopAction = pageAction(QWebEnginePage::Stop); 1020 stopAction->setText(tr("S&top")); 1021 stopAction->setIcon(QIcon::fromTheme(QSL("process-stop"))); 1022 1023 // Make action shortcuts available for webview 1024 addAction(undoAction); 1025 addAction(redoAction); 1026 addAction(cutAction); 1027 addAction(copyAction); 1028 addAction(pasteAction); 1029 addAction(selectAllAction); 1030 } 1031 1032 void WebView::_wheelEvent(QWheelEvent *event) 1033 { 1034 if (mApp->plugins()->processWheelEvent(Qz::ON_WebView, this, event)) { 1035 event->accept(); 1036 return; 1037 } 1038 1039 if (event->modifiers() & Qt::ControlModifier) { 1040 m_wheelHelper.processEvent(event); 1041 while (WheelHelper::Direction direction = m_wheelHelper.takeDirection()) { 1042 switch (direction) { 1043 case WheelHelper::WheelUp: 1044 case WheelHelper::WheelLeft: 1045 zoomIn(); 1046 break; 1047 1048 case WheelHelper::WheelDown: 1049 case WheelHelper::WheelRight: 1050 zoomOut(); 1051 break; 1052 1053 default: 1054 break; 1055 } 1056 } 1057 event->accept(); 1058 return; 1059 } 1060 1061 m_wheelHelper.reset(); 1062 1063 // QtWebEngine ignores QApplication::wheelScrollLines() and instead always scrolls 3 lines 1064 if (event->spontaneous()) { 1065 const qreal multiplier = QApplication::wheelScrollLines() / 3.0; 1066 if (multiplier != 1.0) { 1067 QWheelEvent e(event->position(), event->globalPosition(), event->pixelDelta(), 1068 event->angleDelta() * multiplier, event->buttons(), 1069 event->modifiers(), event->phase(), event->inverted(), event->source()); 1070 QApplication::sendEvent(m_rwhvqt, &e); 1071 event->accept(); 1072 } 1073 } 1074 } 1075 1076 void WebView::_mousePressEvent(QMouseEvent *event) 1077 { 1078 m_clickedUrl = QUrl(); 1079 m_clickedPos = QPointF(); 1080 1081 if (mApp->plugins()->processMousePress(Qz::ON_WebView, this, event)) { 1082 event->accept(); 1083 return; 1084 } 1085 1086 switch (event->button()) { 1087 case Qt::BackButton: 1088 back(); 1089 event->accept(); 1090 break; 1091 1092 case Qt::ForwardButton: 1093 forward(); 1094 event->accept(); 1095 break; 1096 1097 case Qt::MiddleButton: 1098 m_clickedUrl = page()->hitTestContent(event->position().toPoint()).linkUrl(); 1099 if (!m_clickedUrl.isEmpty()) 1100 event->accept(); 1101 break; 1102 1103 case Qt::LeftButton: 1104 m_clickedUrl = page()->hitTestContent(event->position().toPoint()).linkUrl(); 1105 break; 1106 1107 default: 1108 break; 1109 } 1110 } 1111 1112 void WebView::_mouseReleaseEvent(QMouseEvent *event) 1113 { 1114 if (mApp->plugins()->processMouseRelease(Qz::ON_WebView, this, event)) { 1115 event->accept(); 1116 return; 1117 } 1118 1119 switch (event->button()) { 1120 case Qt::BackButton: 1121 case Qt::ForwardButton: 1122 event->accept(); 1123 break; 1124 1125 case Qt::MiddleButton: 1126 if (!m_clickedUrl.isEmpty()) { 1127 const QUrl link = page()->hitTestContent(event->position().toPoint()).linkUrl(); 1128 if (m_clickedUrl == link && isUrlValid(link)) { 1129 userDefinedOpenUrlInNewTab(link, event->modifiers() & Qt::ShiftModifier); 1130 event->accept(); 1131 } 1132 } 1133 break; 1134 1135 case Qt::LeftButton: 1136 if (!m_clickedUrl.isEmpty()) { 1137 const QUrl link = page()->hitTestContent(event->position().toPoint()).linkUrl(); 1138 if (m_clickedUrl == link && isUrlValid(link)) { 1139 if (event->modifiers() & Qt::ControlModifier) { 1140 userDefinedOpenUrlInNewTab(link, event->modifiers() & Qt::ShiftModifier); 1141 event->accept(); 1142 } 1143 } 1144 } 1145 break; 1146 1147 case Qt::RightButton: 1148 if (s_forceContextMenuOnMouseRelease) { 1149 QContextMenuEvent ev(QContextMenuEvent::Mouse, event->position().toPoint(), event->globalPosition().toPoint(), event->modifiers()); 1150 _contextMenuEvent(&ev); 1151 event->accept(); 1152 } 1153 break; 1154 1155 default: 1156 break; 1157 } 1158 } 1159 1160 void WebView::_mouseMoveEvent(QMouseEvent *event) 1161 { 1162 if (mApp->plugins()->processMouseMove(Qz::ON_WebView, this, event)) { 1163 event->accept(); 1164 } 1165 } 1166 1167 void WebView::_keyPressEvent(QKeyEvent *event) 1168 { 1169 if (mApp->plugins()->processKeyPress(Qz::ON_WebView, this, event)) { 1170 event->accept(); 1171 return; 1172 } 1173 1174 switch (event->key()) { 1175 case Qt::Key_ZoomIn: 1176 zoomIn(); 1177 event->accept(); 1178 break; 1179 1180 case Qt::Key_ZoomOut: 1181 zoomOut(); 1182 event->accept(); 1183 break; 1184 1185 case Qt::Key_Plus: 1186 if (event->modifiers() & Qt::ControlModifier) { 1187 zoomIn(); 1188 event->accept(); 1189 } 1190 break; 1191 1192 case Qt::Key_Minus: 1193 if (event->modifiers() & Qt::ControlModifier) { 1194 zoomOut(); 1195 event->accept(); 1196 } 1197 break; 1198 1199 case Qt::Key_0: 1200 if (event->modifiers() & Qt::ControlModifier) { 1201 zoomReset(); 1202 event->accept(); 1203 } 1204 break; 1205 1206 case Qt::Key_M: 1207 if (event->modifiers() & Qt::ControlModifier) { 1208 page()->setAudioMuted(!page()->isAudioMuted()); 1209 event->accept(); 1210 } 1211 break; 1212 1213 default: 1214 break; 1215 } 1216 } 1217 1218 void WebView::_keyReleaseEvent(QKeyEvent *event) 1219 { 1220 if (mApp->plugins()->processKeyRelease(Qz::ON_WebView, this, event)) { 1221 event->accept(); 1222 } 1223 1224 switch (event->key()) { 1225 case Qt::Key_Escape: 1226 if (isFullScreen()) { 1227 triggerPageAction(QWebEnginePage::ExitFullScreen); 1228 event->accept(); 1229 } 1230 break; 1231 1232 default: 1233 break; 1234 } 1235 } 1236 1237 void WebView::_contextMenuEvent(QContextMenuEvent *event) 1238 { 1239 Q_UNUSED(event) 1240 } 1241 1242 void WebView::resizeEvent(QResizeEvent *event) 1243 { 1244 QWebEngineView::resizeEvent(event); 1245 Q_EMIT viewportResized(size()); 1246 } 1247 1248 void WebView::contextMenuEvent(QContextMenuEvent *event) 1249 { 1250 // Context menu is created in mouseReleaseEvent 1251 if (s_forceContextMenuOnMouseRelease) 1252 return; 1253 1254 const QPoint pos = event->pos(); 1255 const QPoint globalPos = event->globalPos(); 1256 const QContextMenuEvent::Reason reason = event->reason(); 1257 1258 QTimer::singleShot(0, this, [this, pos, globalPos, reason]() { 1259 QContextMenuEvent ev(reason, pos, globalPos); 1260 _contextMenuEvent(&ev); 1261 }); 1262 } 1263 1264 bool WebView::focusNextPrevChild(bool next) 1265 { 1266 return QWebEngineView::focusNextPrevChild(next); 1267 } 1268 1269 void WebView::loadRequest(const LoadRequest &req) 1270 { 1271 QWebEngineView::load(req.webRequest()); 1272 } 1273 1274 bool WebView::eventFilter(QObject *obj, QEvent *event) 1275 { 1276 // Keyboard events are sent to parent widget 1277 if (obj == this && event->type() == QEvent::ParentChange && parentWidget()) { 1278 parentWidget()->installEventFilter(this); 1279 } 1280 1281 // Hack to find widget that receives input events 1282 if (obj == this && event->type() == QEvent::ChildAdded) { 1283 QPointer<QWidget> child = qobject_cast<QWidget*>(static_cast<QChildEvent*>(event)->child()); 1284 QTimer::singleShot(0, this, [=]() { 1285 if (child) { 1286 m_rwhvqt = child; 1287 m_rwhvqt->installEventFilter(this); 1288 if (auto *w = qobject_cast<QQuickWidget*>(m_rwhvqt)) { 1289 w->setClearColor(palette().color(QPalette::Window)); 1290 } 1291 } 1292 }); 1293 } 1294 1295 // Forward events to WebView 1296 #define HANDLE_EVENT(f, t) \ 1297 { \ 1298 bool wasAccepted = event->isAccepted(); \ 1299 event->setAccepted(false); \ 1300 f(static_cast<t*>(event)); \ 1301 bool ret = event->isAccepted(); \ 1302 event->setAccepted(wasAccepted); \ 1303 return ret; \ 1304 } 1305 1306 if (obj == m_rwhvqt) { 1307 switch (event->type()) { 1308 case QEvent::MouseButtonPress: 1309 HANDLE_EVENT(_mousePressEvent, QMouseEvent); 1310 1311 case QEvent::MouseButtonRelease: 1312 HANDLE_EVENT(_mouseReleaseEvent, QMouseEvent); 1313 1314 case QEvent::MouseMove: 1315 HANDLE_EVENT(_mouseMoveEvent, QMouseEvent); 1316 1317 case QEvent::Wheel: 1318 HANDLE_EVENT(_wheelEvent, QWheelEvent); 1319 1320 default: 1321 break; 1322 } 1323 } 1324 1325 if (obj == parentWidget()) { 1326 switch (event->type()) { 1327 case QEvent::KeyPress: 1328 HANDLE_EVENT(_keyPressEvent, QKeyEvent); 1329 1330 case QEvent::KeyRelease: 1331 HANDLE_EVENT(_keyReleaseEvent, QKeyEvent); 1332 1333 default: 1334 break; 1335 } 1336 } 1337 #undef HANDLE_EVENT 1338 1339 // Block already handled events 1340 if (obj == this) { 1341 switch (event->type()) { 1342 case QEvent::KeyPress: 1343 case QEvent::KeyRelease: 1344 case QEvent::MouseButtonPress: 1345 case QEvent::MouseButtonRelease: 1346 case QEvent::MouseMove: 1347 case QEvent::Wheel: 1348 return true; 1349 1350 case QEvent::Hide: 1351 if (isFullScreen()) { 1352 triggerPageAction(QWebEnginePage::ExitFullScreen); 1353 } 1354 break; 1355 1356 default: 1357 break; 1358 } 1359 } 1360 1361 const bool res = QWebEngineView::eventFilter(obj, event); 1362 1363 if (obj == m_rwhvqt) { 1364 switch (event->type()) { 1365 case QEvent::FocusIn: 1366 case QEvent::FocusOut: 1367 Q_EMIT focusChanged(hasFocus()); 1368 break; 1369 1370 default: 1371 break; 1372 } 1373 } 1374 1375 return res; 1376 }