/* * This file is part of smolbote. It's copyrighted by the contributors recorded * in the version control history of the file, available from its original * location: https://neueland.iserlohn-fortress.net/gitea/aqua/smolbote * * SPDX-License-Identifier: GPL-3.0 */ #include "webview.h" #include "browser.h" #include "conf.hpp" #include "subwindow/subwindow.h" #include "wallet/wallet.h" #include "webpage.h" #include "webprofile.h" #include "webprofilemanager.h" #include #include #include #include #include #include #include #include #include #include #include inline QAction *historyAction(QWebEngineView *view, const QWebEngineHistoryItem &item) { QAction *action = new QAction(view); if(item.title().isEmpty()) action->setText(item.url().toString()); else action->setText(QObject::tr("%1 (%2)").arg(item.title(), item.url().toString())); QObject::connect(action, &QAction::triggered, view, [view, item]() { view->history()->goToItem(item); }); return action; } WebView::WebView(WebProfile *profile, QWidget *parent) : QWebEngineView(parent) { Q_CHECK_PTR(profile); m_profile = profile; setPage(new WebPage(profile, this)); m_parentWindow = qobject_cast(parent); // load status and progress connect(this, &QWebEngineView::loadStarted, this, [this]() { m_loaded = false; }); connect(this, &QWebEngineView::loadFinished, this, [this]() { m_loaded = true; }); #ifdef CONFIG_QTBUG_65223 connect(this, &QWebEngineView::loadProgress, this, [this](int progress) { if(progress == 100) { emit loadFinished(true); } }); #endif } void WebView::setProfile(WebProfile *profile) { m_profile = profile; const auto url = this->url(); setPage(new WebPage(profile, this)); this->load(url); } bool WebView::isLoaded() const { return m_loaded; } void WebView::search(const QString &term) { const QString searchUrl = m_profile->search().arg(QString(QUrl::toPercentEncoding(term))); load(searchUrl); } WebView *WebView::createWindow(QWebEnginePage::WebWindowType type) { Q_CHECK_PTR(m_parentWindow); // parent Window has been found auto index = m_parentWindow->addTab(); WebView *view = m_parentWindow->view(index); switch(type) { case QWebEnginePage::WebBrowserWindow: // a complete web browser window break; case QWebEnginePage::WebBrowserTab: // a web browser tab m_parentWindow->setCurrentTab(index); break; case QWebEnginePage::WebDialog: // a window without decorations break; case QWebEnginePage::WebBrowserBackgroundTab: // a web browser tab, but don't swap to it break; } return view; } void WebView::contextMenuEvent(QContextMenuEvent *event) { QMenu *menu = new QMenu(this); const auto ctxdata = page()->contextMenuData(); // back, forward, reload, mute buttons, added to all variants of the context menu { auto *navButtons = new QWidgetAction(this); auto *buttons = new QWidget(this); auto *buttonsLayout = new QHBoxLayout(); buttonsLayout->setContentsMargins(8, 0, 8, 0); buttonsLayout->setSpacing(2); auto *backButton = new QToolButton(this); backButton->setEnabled(history()->canGoBack()); backButton->setIcon(style()->standardIcon(QStyle::SP_ArrowBack)); connect(backButton, &QToolButton::clicked, this, [this, menu]() { this->back(); menu->close(); }); buttonsLayout->addWidget(backButton); auto *forwardButton = new QToolButton(this); forwardButton->setEnabled(history()->canGoForward()); forwardButton->setIcon(style()->standardIcon(QStyle::SP_ArrowForward)); connect(forwardButton, &QToolButton::clicked, this, [this, menu]() { this->forward(); menu->close(); }); buttonsLayout->addWidget(forwardButton); auto *refreshButton = new QToolButton(this); refreshButton->setIcon(style()->standardIcon(QStyle::SP_BrowserReload)); connect(refreshButton, &QToolButton::clicked, this, [this, menu]() { this->reload(); menu->close(); }); buttonsLayout->addWidget(refreshButton); buttonsLayout->addStretch(); auto *muteButton = new QToolButton(this); muteButton->setCheckable(true); muteButton->setChecked(this->page()->isAudioMuted()); QIcon muteIcon; muteIcon.addPixmap(style()->standardPixmap(QStyle::SP_MediaVolume), QIcon::Normal, QIcon::Off); muteIcon.addPixmap(style()->standardPixmap(QStyle::SP_MediaVolumeMuted), QIcon::Normal, QIcon::On); muteButton->setIcon(muteIcon); connect(muteButton, &QToolButton::clicked, this, [this, menu](bool checked) { this->page()->setAudioMuted(checked); menu->close(); }); buttonsLayout->addWidget(muteButton); buttons->setLayout(buttonsLayout); navButtons->setDefaultWidget(buttons); menu->addAction(navButtons); menu->addSeparator(); } if(ctxdata.mediaType() == QWebEngineContextMenuData::MediaTypeNone) { auto *backMenu = new QMenu(tr("Back"), this); if(!history()->canGoBack()) { backMenu->setEnabled(false); } else { connect(backMenu, &QMenu::aboutToShow, this, [this, backMenu]() { backMenu->clear(); const auto backItems = history()->backItems(10); for(const QWebEngineHistoryItem &item : backItems) { backMenu->addAction(historyAction(this, item)); } }); } menu->addMenu(backMenu); auto *forwardMenu = new QMenu(tr("Forward"), this); if(!history()->canGoForward()) { forwardMenu->setEnabled(false); } else { connect(forwardMenu, &QMenu::aboutToShow, this, [this, forwardMenu]() { forwardMenu->clear(); const auto forwardItems = history()->forwardItems(10); for(const QWebEngineHistoryItem &item : forwardItems) { forwardMenu->addAction(historyAction(this, item)); } }); } menu->addMenu(forwardMenu); connect(menu->addAction(tr("Reload")), &QAction::triggered, this, [this]() { page()->triggerAction(QWebEnginePage::Reload); }); connect(menu->addAction(tr("Reload and bypass Cache")), &QAction::triggered, this, [this]() { page()->triggerAction(QWebEnginePage::ReloadAndBypassCache); }); menu->addSeparator(); connect(menu->addAction(tr("Select All")), &QAction::triggered, this, [this]() { page()->triggerAction(QWebEnginePage::SelectAll); }); connect(menu->addAction(tr("Clear Selection")), &QAction::triggered, this, [this]() { page()->triggerAction(QWebEnginePage::Unselect); }); connect(menu->addAction(tr("Copy to clipboard")), &QAction::triggered, this, [this]() { page()->triggerAction(QWebEnginePage::Copy); }); } else if(ctxdata.mediaType() == QWebEngineContextMenuData::MediaTypeImage) { connect(menu->addAction(tr("Copy image to clipboard")), &QAction::triggered, this, [this]() { page()->triggerAction(QWebEnginePage::CopyImageToClipboard); }); connect(menu->addAction(tr("Copy image URL to clipboard")), &QAction::triggered, this, [this]() { page()->triggerAction(QWebEnginePage::CopyImageUrlToClipboard); }); if(!ctxdata.mediaUrl().isEmpty()) { if(this->url() != ctxdata.mediaUrl()) { connect(menu->addAction(tr("Open image")), &QAction::triggered, this, [this, ctxdata]() { load(ctxdata.mediaUrl()); }); connect(menu->addAction(tr("Open image in new tab")), &QAction::triggered, this, [this, ctxdata]() { createWindow(QWebEnginePage::WebBrowserTab)->load(ctxdata.mediaUrl()); }); } connect(menu->addAction(tr("Save image")), &QAction::triggered, this, [this, ctxdata]() { page()->download(ctxdata.mediaUrl()); }); } } else { delete menu; menu = page()->createStandardContextMenu(); } if(!ctxdata.linkUrl().isEmpty()) { menu->addSeparator(); connect(menu->addAction(tr("Open link in new tab")), &QAction::triggered, this, [this, ctxdata]() { createWindow(QWebEnginePage::WebBrowserTab)->load(ctxdata.linkUrl()); }); auto *newTabMenu = menu->addMenu(tr("Open link in new tab with profile")); dynamic_cast(qApp)->getProfileManager()->profileMenu(newTabMenu, [this, ctxdata](WebProfile *profile) { auto *view = this->createWindow(QWebEnginePage::WebBrowserTab); view->setProfile(profile); view->load(ctxdata.linkUrl()); }); connect(menu->addAction(tr("Open link in new window")), &QAction::triggered, this, [this, ctxdata]() { createWindow(QWebEnginePage::WebBrowserWindow)->load(ctxdata.linkUrl()); }); connect(menu->addAction(tr("Copy link address")), &QAction::triggered, this, [this]() { page()->triggerAction(QWebEnginePage::CopyLinkToClipboard); }); } // zoom widget { menu->addSeparator(); auto *zoomSlider = new QSlider(Qt::Horizontal); zoomSlider->setMinimum(5); zoomSlider->setMaximum(50); zoomSlider->setValue(zoomFactor() * 10); auto *zoomAction = menu->addAction(tr("Zoom: %1x").arg(zoomFactor())); connect(zoomAction, &QAction::triggered, this, [zoomSlider]() { zoomSlider->setValue(10); }); connect(zoomSlider, &QSlider::valueChanged, this, [this, zoomAction](int value) { zoomAction->setText(tr("Zoom: %1x").arg(static_cast(value) / 10)); setZoomFactor(static_cast(value) / 10); }); auto *zoomWidgetAction = new QWidgetAction(this); zoomWidgetAction->setDefaultWidget(zoomSlider); menu->addAction(zoomWidgetAction); } #ifdef QT_DEBUG { menu->addSeparator(); auto *autofillAction = menu->addAction(tr("Autofill form")); connect(autofillAction, &QAction::triggered, this, [this]() { Wallet::autocompleteForm(this); }); }; #endif menu->setMinimumWidth(250); menu->exec(event->globalPos()); }