diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/messageview.cpp | 42 | ||||
| -rw-r--r-- | src/messageview.h | 12 |
2 files changed, 54 insertions, 0 deletions
diff --git a/src/messageview.cpp b/src/messageview.cpp index 8a1976d..5f7c9d2 100644 --- a/src/messageview.cpp +++ b/src/messageview.cpp @@ -153,6 +153,12 @@ MessageView::MessageView(QWidget *parent) settings->setAttribute(QWebEngineSettings::PluginsEnabled, false); settings->setAttribute(QWebEngineSettings::FullScreenSupportEnabled, false); + // Item 85: a selection in the body is searchable. CustomContextMenu so the + // page's standard entries survive and the search is added to them. + m_view->setContextMenuPolicy(Qt::CustomContextMenu); + connect(m_view, &QWidget::customContextMenuRequested, + this, &MessageView::showBodyContextMenu); + // Ctrl+wheel zoom. The filter goes on the application rather than on // m_view: the wheel event is delivered to an internal QQuickWidget the // view creates lazily, so there is no child to filter at this point and a @@ -569,6 +575,42 @@ void MessageView::showHeaderContextMenu(const QPoint &pos) menu.exec(m_headerLabel->mapToGlobal(pos)); } +SearchOffer MessageView::selectionSearchOffer(const QString &selectedText) const +{ + const QString query = SearchTerm::quote(selectedText); + if (query.isEmpty()) + return {}; + + constexpr int kMaxLabel = 40; + const QString shown = selectedText.simplified(); + return { shown.size() > kMaxLabel + ? shown.left(kMaxLabel) + QStringLiteral("...") + : shown, + query }; +} + +void MessageView::showBodyContextMenu(const QPoint &pos) +{ + // The page's own menu first: copy, select all and the rest stay exactly as + // they were. This adds to that menu rather than replacing it. + QMenu *menu = m_view->createStandardContextMenu(); + if (!menu) + menu = new QMenu(this); + menu->setAttribute(Qt::WA_DeleteOnClose); + + // selectedText() reads the selection out of the render process with no + // script injection. JavaScript is disabled in this profile and stays so. + const SearchOffer offer = selectionSearchOffer(m_view->page()->selectedText()); + if (!offer.query.isEmpty()) { + menu->addSeparator(); + addSearchEntries(menu, { offer }); + } + + // popup() rather than exec(): the menu owns itself via WA_DeleteOnClose and + // must not block this handler. + menu->popup(m_view->mapToGlobal(pos)); +} + void MessageView::showDetailsDialog() { if (m_items.isEmpty()) diff --git a/src/messageview.h b/src/messageview.h index 26ed216..3e600f0 100644 --- a/src/messageview.h +++ b/src/messageview.h @@ -140,6 +140,15 @@ public: /// parsing it back: rich text does not survive a second parse. QList<SearchOffer> headerSearchOffers() const { return m_headerOffers; } + /// The offer for a body selection, its query empty when there is nothing + /// usable selected. + /// + /// Takes the text rather than reading the page, so the quoting is testable + /// without a live web engine and a rendered document. A selection is + /// arbitrary prose and can carry quotes, newlines and query syntax, none + /// of which notmuch reports as an error. + SearchOffer selectionSearchOffer(const QString &selectedText) const; + public slots: void toggleHtml(); void loadRemoteContent(); @@ -235,6 +244,9 @@ private: /// Builds and pops the header's menu at `pos`, in the label's coordinates. void showHeaderContextMenu(const QPoint &pos); + /// Builds and pops the web view's menu, keeping its standard entries. + void showBodyContextMenu(const QPoint &pos); + /// Appends a "Search for ..." submenu per offer, each holding the replace /// and the narrow operation. /// |
