// For license of this file, see /LICENSE.md. #include "gui/webviewers/qtextbrowser/textbrowserviewer.h" #include "gui/messagebox.h" #include "gui/webbrowser.h" #include "miscellaneous/application.h" #include "miscellaneous/externaltool.h" #include "miscellaneous/iconfactory.h" #include "network-web/webfactory.h" #include #include #include TextBrowserViewer::TextBrowserViewer(QWidget* parent) : QTextBrowser(parent) { setAutoFillBackground(true); setFrameShape(QFrame::Shape::StyledPanel); setFrameShadow(QFrame::Shadow::Plain); setTabChangesFocus(true); setOpenLinks(false); viewport()->setAutoFillBackground(true); connect(this, &QTextBrowser::anchorClicked, this, &TextBrowserViewer::onAnchorClicked); connect(this, QOverload::of(&QTextBrowser::highlighted), this, &TextBrowserViewer::linkMouseHighlighted); } QVariant TextBrowserViewer::loadResource(int type, const QUrl& name) { return {}; } QSize TextBrowserViewer::sizeHint() const { auto doc_size = document()->size().toSize(); doc_size.setHeight(doc_size.height() + contentsMargins().top() + contentsMargins().bottom()); return doc_size; } QPair TextBrowserViewer::prepareHtmlForMessage(const QList& messages, RootItem* selected_item) const { QString html; for (const Message& message : messages) { html += QString("

%1

").arg(message.m_title); if (!message.m_url.isEmpty()) { html += QString("[url] %1
").arg(message.m_url); } for (const Enclosure& enc : message.m_enclosures) { html += QString("[%2] %1
").arg(enc.m_url, enc.m_mimeType); } QRegularExpression imgTagRegex("\\]*src\\s*=\\s*[\"\']([^\"\']*)[\"\'][^\\>]*\\>", QRegularExpression::PatternOption::CaseInsensitiveOption | QRegularExpression::PatternOption::InvertedGreedinessOption); QRegularExpressionMatchIterator i = imgTagRegex.globalMatch(message.m_contents); QString pictures_html; while (i.hasNext()) { QRegularExpressionMatch match = i.next(); pictures_html += QString("
[%1] %2").arg(tr("image"), match.captured(1)); } /*if (qApp->settings()->value(GROUP(Messages), SETTING(Messages::DisplayImagePlaceholders)).toBool()) { html += message.m_contents; } else {*/ QString cnts = message.m_contents; html += cnts; // html += cnts.replace(imgTagRegex, QString()); //} html += pictures_html; } // TODO: If FgInteresting not defined by the skin // use current pallette/Highlight color perhaps. return { QSL("" "" "%1" "") .arg(html, qApp->skins()->currentSkin().colorForModel(SkinEnums::PaletteColors::FgInteresting).value().name()), QUrl()}; } void TextBrowserViewer::bindToBrowser(WebBrowser* browser) { installEventFilter(browser); browser->m_actionBack = new QAction(this); browser->m_actionForward = new QAction(this); browser->m_actionReload = new QAction(this); browser->m_actionStop = new QAction(this); browser->m_actionBack->setEnabled(false); browser->m_actionForward->setEnabled(false); browser->m_actionReload->setEnabled(false); // TODO: When clicked "Stop", save the "Stop" state and return {} from "handleResource(...)" // right away. browser->m_actionStop->setEnabled(false); // TODO: add "Open in new tab" to context menu. } void TextBrowserViewer::findText(const QString& text, bool backwards) { if (!text.isEmpty()) { QTextBrowser::find(text, backwards ? QTextDocument::FindFlag::FindBackward : QTextDocument::FindFlag(0)); } else { textCursor().clearSelection(); moveCursor(QTextCursor::MoveOperation::Left); } } void TextBrowserViewer::setUrl(const QUrl& url) {} void TextBrowserViewer::setHtml(const QString& html, const QUrl& base_url) { m_currentUrl = base_url; QTextBrowser::setHtml(html); emit pageTitleChanged(documentTitle()); emit pageUrlChanged(base_url); } QString TextBrowserViewer::html() const { return QTextBrowser::toHtml(); } QUrl TextBrowserViewer::url() const { return m_currentUrl; } void TextBrowserViewer::clear() { setHtml({}); } void TextBrowserViewer::loadMessages(const QList& messages, RootItem* root) { m_root = root; auto html_messages = prepareHtmlForMessage(messages, root); setHtml(html_messages.first, html_messages.second); emit loadingFinished(true); } double TextBrowserViewer::verticalScrollBarPosition() const { return verticalScrollBar()->value(); } void TextBrowserViewer::setVerticalScrollBarPosition(double pos) { verticalScrollBar()->setValue(int(pos)); } void TextBrowserViewer::applyFont(const QFont& fon) { setFont(fon); } qreal TextBrowserViewer::zoomFactor() const { return font().pointSize() / 12.0; } void TextBrowserViewer::setZoomFactor(qreal zoom_factor) { auto fon = font(); fon.setPointSize(fon.pointSize() * zoom_factor); } void TextBrowserViewer::contextMenuEvent(QContextMenuEvent* event) { event->accept(); auto* menu = createStandardContextMenu(); if (menu == nullptr) { return; } auto anchor = anchorAt(event->pos()); if (!anchor.isEmpty()) { QFileIconProvider icon_provider; QMenu* menu_ext_tools = new QMenu(tr("Open with external tool"), menu); auto tools = ExternalTool::toolsFromSettings(); menu_ext_tools->setIcon(qApp->icons()->fromTheme(QSL("document-open"))); for (const ExternalTool& tool : qAsConst(tools)) { QAction* act_tool = new QAction(QFileInfo(tool.executable()).fileName(), menu_ext_tools); act_tool->setIcon(icon_provider.icon(QFileInfo(tool.executable()))); act_tool->setToolTip(tool.executable()); act_tool->setData(QVariant::fromValue(tool)); menu_ext_tools->addAction(act_tool); connect(act_tool, &QAction::triggered, this, [act_tool, anchor]() { act_tool->data().value().run(anchor); }); } if (menu_ext_tools->actions().isEmpty()) { QAction* act_not_tools = new QAction("No external tools activated"); act_not_tools->setEnabled(false); menu_ext_tools->addAction(act_not_tools); } menu->addMenu(menu_ext_tools); } menu->popup(event->globalPos()); } void TextBrowserViewer::resizeEvent(QResizeEvent* event) { // Notify parents about changed geometry. updateGeometry(); QTextBrowser::resizeEvent(event); } void TextBrowserViewer::onAnchorClicked(const QUrl& url) { if (!url.isEmpty()) { bool open_externally_now = qApp->settings()->value(GROUP(Browser), SETTING(Browser::OpenLinksInExternalBrowserRightAway)).toBool(); if (open_externally_now) { qApp->web()->openUrlInExternalBrowser(url.toString()); } else { // User clicked some URL. Open it in external browser or download? MsgBox box(qApp->mainFormWidget()); box.setText(tr("You clicked some link. You can download the link contents or open it in external web browser.")); box.setInformativeText(tr("What action do you want to take?")); box.setDetailedText(url.toString()); QAbstractButton* btn_open = box.addButton(tr("Open in external browser"), QMessageBox::ButtonRole::ActionRole); QAbstractButton* btn_download = box.addButton(tr("Download"), QMessageBox::ButtonRole::ActionRole); QAbstractButton* btn_cancel = box.addButton(QMessageBox::StandardButton::Cancel); bool always; MsgBox::setCheckBox(&box, tr("Always open links in external browser."), &always); box.setDefaultButton(QMessageBox::StandardButton::Cancel); box.exec(); if (box.clickedButton() != box.button(QMessageBox::StandardButton::Cancel)) { // Store selected checkbox value. qApp->settings()->setValue(GROUP(Browser), Browser::OpenLinksInExternalBrowserRightAway, always); } if (box.clickedButton() == btn_open) { qApp->web()->openUrlInExternalBrowser(url.toString()); } else if (box.clickedButton() == btn_download) { qApp->downloadManager()->download(url); } btn_download->deleteLater(); btn_open->deleteLater(); btn_cancel->deleteLater(); } } else { MsgBox::show(qApp->mainFormWidget(), QMessageBox::Warning, tr("Incorrect link"), tr("Selected hyperlink is invalid.")); } }