From e315ff15daf26cbc70b6f2da50e6ca18081fc7c4 Mon Sep 17 00:00:00 2001 From: Andrea Diamantini Date: Wed, 19 Sep 2012 23:22:13 +0200 Subject: Integrated spell checking for rekonq2 (lindsay's work imported) - inline spell highlighter. This requires WebKit 2.3 to work. - Addition of a suggested replacement word list to the context menu, for the word right clicked on - A standard modeless spell check dialog that works on the current editable text (or selection). Heavily cribbed from Dawit's work on kdewebkitpart. oops... also a codingstyle script round here... :) --- src/webtab/networkaccessmanager.cpp | 22 ++-- src/webtab/previewselectorbar.cpp | 2 +- src/webtab/protocolhandler.cpp | 4 +- src/webtab/protocolhandler.h | 2 +- src/webtab/webpage.cpp | 4 +- src/webtab/webtab.cpp | 2 +- src/webtab/webtab.h | 2 +- src/webtab/webview.cpp | 249 +++++++++++++++++++++++++++++++++--- src/webtab/webview.h | 11 ++ 9 files changed, 259 insertions(+), 39 deletions(-) (limited to 'src/webtab') diff --git a/src/webtab/networkaccessmanager.cpp b/src/webtab/networkaccessmanager.cpp index e817faca..95bfa47d 100644 --- a/src/webtab/networkaccessmanager.cpp +++ b/src/webtab/networkaccessmanager.cpp @@ -85,15 +85,15 @@ static void hideBlockedElements(const QUrl& url, QWebElementCollection& collecti { for (QWebElementCollection::iterator it = collection.begin(); it != collection.end(); ++it) { - const QUrl baseUrl ((*it).webFrame()->baseUrl()); + const QUrl baseUrl((*it).webFrame()->baseUrl()); QString src = (*it).attribute(QL1S("src")); - + if (src.isEmpty()) src = (*it).evaluateJavaScript(QL1S("this.src")).toString(); if (src.isEmpty()) continue; - const QUrl resolvedUrl (baseUrl.resolved(src)); + const QUrl resolvedUrl(baseUrl.resolved(src)); if (url == resolvedUrl) { //kDebug() << "*** HIDING ELEMENT: " << (*it).tagName() << resolvedUrl; @@ -125,11 +125,11 @@ NetworkAccessManager::NetworkAccessManager(QObject *parent) QNetworkReply *NetworkAccessManager::createRequest(Operation op, const QNetworkRequest &req, QIODevice *outgoingData) { bool blocked = false; - + // Handle GET operations with AdBlock if (op == QNetworkAccessManager::GetOperation) blocked = AdBlockManager::self()->blockRequest(req); - + if (!blocked) { if (KProtocolInfo::isHelperProtocol(req.url())) @@ -162,10 +162,10 @@ void NetworkAccessManager::slotFinished(bool ok) if (!ok) return; - if(!AdBlockManager::self()->isEnabled()) + if (!AdBlockManager::self()->isEnabled()) return; - if(!AdBlockManager::self()->isHidingElements()) + if (!AdBlockManager::self()->isHidingElements()) return; QWebFrame* frame = qobject_cast(sender()); @@ -176,10 +176,10 @@ void NetworkAccessManager::slotFinished(bool ok) if (urls.isEmpty()) return; - QWebElementCollection collection = frame->findAllElements(HIDABLE_ELEMENTS); - if (frame->parentFrame()) + QWebElementCollection collection = frame->findAllElements(HIDABLE_ELEMENTS); + if (frame->parentFrame()) collection += frame->parentFrame()->findAllElements(HIDABLE_ELEMENTS); - Q_FOREACH(const QUrl& url, urls) - hideBlockedElements(url, collection); + Q_FOREACH(const QUrl & url, urls) + hideBlockedElements(url, collection); } diff --git a/src/webtab/previewselectorbar.cpp b/src/webtab/previewselectorbar.cpp index 24461e79..b37b000c 100644 --- a/src/webtab/previewselectorbar.cpp +++ b/src/webtab/previewselectorbar.cpp @@ -101,7 +101,7 @@ void PreviewSelectorBar::loadFinished() void PreviewSelectorBar::clicked() { WebTab *tab = qobject_cast(parent()); - + if (tab->page()) { KUrl url = tab->url(); diff --git a/src/webtab/protocolhandler.cpp b/src/webtab/protocolhandler.cpp index bca5a007..ab2f0b8e 100644 --- a/src/webtab/protocolhandler.cpp +++ b/src/webtab/protocolhandler.cpp @@ -99,7 +99,7 @@ ProtocolHandler::ProtocolHandler(QObject *parent) void ProtocolHandler::setWindow(WebWindow *w) { _webwin = w; - _lister->setMainWindow(_webwin); + _lister->setMainWindow(_webwin); } @@ -276,7 +276,7 @@ void ProtocolHandler::showResults(const KFileItemList &list) _webwin->urlBar()->setQUrl(_url); _webwin->view()->setFocus(); - + HistoryManager::self()->addHistoryEntry(_url, _url.prettyUrl()); } } diff --git a/src/webtab/protocolhandler.h b/src/webtab/protocolhandler.h index c4bc6faf..7d685ea4 100644 --- a/src/webtab/protocolhandler.h +++ b/src/webtab/protocolhandler.h @@ -68,7 +68,7 @@ public: bool postHandling(const QNetworkRequest &request, QWebFrame *frame); void setWindow(WebWindow *); - + Q_SIGNALS: void downloadUrl(const KUrl &); diff --git a/src/webtab/webpage.cpp b/src/webtab/webpage.cpp index 385a6ac3..cb4ea373 100644 --- a/src/webtab/webpage.cpp +++ b/src/webtab/webpage.cpp @@ -131,7 +131,7 @@ WebPage::WebPage(QWidget *parent) WebTab *tab = qobject_cast(view->parent()); WebWindow *w = tab->webWindow(); _protHandler.setWindow(w); - + // handling unsupported content... setForwardUnsupportedContent(true); connect(this, SIGNAL(unsupportedContent(QNetworkReply*)), this, SLOT(handleUnsupportedContent(QNetworkReply*))); @@ -425,7 +425,7 @@ void WebPage::handleUnsupportedContent(QNetworkReply *reply) WebView *view = qobject_cast(parent()); WebTab *tab = qobject_cast(view->parent()); tab->setPart(pa, replyUrl); - + // FIXME: Is this enough? } else diff --git a/src/webtab/webtab.cpp b/src/webtab/webtab.cpp index e53d7b48..bc40ecca 100644 --- a/src/webtab/webtab.cpp +++ b/src/webtab/webtab.cpp @@ -133,7 +133,7 @@ WebWindow *WebTab::webWindow() WebWindow *w = qobject_cast(parent()); return w; } - + KUrl WebTab::url() { if (page() && page()->isOnRekonqPage()) diff --git a/src/webtab/webtab.h b/src/webtab/webtab.h index 8b60f0ff..a0e6890d 100644 --- a/src/webtab/webtab.h +++ b/src/webtab/webtab.h @@ -62,7 +62,7 @@ public: WebView *view(); WebPage *page(); WebWindow *webWindow(); - + inline int progress() const { return m_progress; diff --git a/src/webtab/webview.cpp b/src/webtab/webview.cpp index 06412198..5cb5e81d 100644 --- a/src/webtab/webview.cpp +++ b/src/webtab/webview.cpp @@ -54,6 +54,10 @@ #include #include +#include +#include +#include + // Qt Includes #include #include @@ -68,6 +72,19 @@ #include +// needed for the spellCheck +static QVariant execJScript(QWebHitTestResult result, const QString& script) +{ + QWebElement element(result.element()); + if (element.isNull()) + return QVariant(); + return element.evaluateJavaScript(script); +} + + +// -------------------------------------------------------------------------------------------------- + + WebView::WebView(QWidget* parent) : KWebView(parent, false) , m_autoScrollTimer(new QTimer(this)) @@ -141,9 +158,103 @@ WebPage *WebView::page() } +bool WebView::popupSpellMenu(QContextMenuEvent *event) +{ + // return false if not handled + if (!ReKonfig::automaticSpellChecking()) + return false; + + QWebElement element(m_contextMenuHitResult.element()); + if (element.isNull()) + return false; + + int selStart = element.evaluateJavaScript("this.selectionStart").toInt(); + int selEnd = element.evaluateJavaScript("this.selectionEnd").toInt(); + if (selEnd != selStart) + return false; // selection, handle normally + + // No selection - Spell Checking only + // Get word + QString text = element.evaluateJavaScript("this.value").toString(); + QRegExp ws("\\b"); + int s1 = text.lastIndexOf(ws, selStart); + int s2 = text.indexOf(ws, selStart); + QString word = text.mid(s1, s2 - s1).trimmed(); + + // sanity check + if (word.isEmpty()) + return false; + + kDebug() << s1 << ":" << s2 << ":" << word << ":"; + Sonnet::Speller spellor; + if (spellor.isCorrect(word)) + return false; // no need to popup spell menu + + // find alternates + QStringList words = spellor.suggest(word); + + // Construct popup menu + QMenu mnu(this); + + // Add alternates + if (words.isEmpty()) + { + QAction *a = mnu.addAction(i18n("No suggestions for ") + word); + a->setEnabled(false); + } + else + { + QStringListIterator it(words); + while (it.hasNext()) + { + QString w = it.next(); + QAction *aWord = mnu.addAction(w); + aWord->setData(w); + } + } + + // Add dictionary options + mnu.addSeparator(); + QAction *aIgnore = mnu.addAction(i18n("Ignore")); + QAction *aAddToDict = mnu.addAction(i18n("Add to Dictionary")); + + QAction *aSpellChoice = mnu.exec(event->globalPos()); + if (aSpellChoice) + { + if (aSpellChoice == aAddToDict) + spellor.addToPersonal(word); + else if (aSpellChoice == aIgnore) + { + // Ignore :) + } + else + { + // Choose a replacement word + QString w = aSpellChoice->data().toString(); + if (!w.isEmpty()) + { + // replace word + QString script(QL1S("this.value=this.value.substring(0,")); + script += QString::number(s1); + script += QL1S(") + \""); + script += w; + script += QL1S("\" + this.value.substring("); + script += QString::number(s2); + script += QL1S(")"); + element.evaluateJavaScript(script); + // reposition cursor + element.evaluateJavaScript("this.selectionEnd=this.selectionStart=" + QString::number(selStart) + ";"); + } + } + } + + return true; +} + + void WebView::contextMenuEvent(QContextMenuEvent *event) { - QWebHitTestResult result = page()->mainFrame()->hitTestContent(event->pos()); + m_contextMenuHitResult = page()->mainFrame()->hitTestContent(event->pos()); WebTab *tab = qobject_cast(parent()); WebWindow *webwin = tab->webWindow(); @@ -159,23 +270,27 @@ void WebView::contextMenuEvent(QContextMenuEvent *event) // Choose right context int resultHit = 0; - if (result.linkUrl().isEmpty()) + if (m_contextMenuHitResult.linkUrl().isEmpty()) resultHit = WebView::EmptySelection; else resultHit = WebView::LinkSelection; - if (!result.pixmap().isNull()) + if (!m_contextMenuHitResult.pixmap().isNull()) resultHit |= WebView::ImageSelection; - if (result.isContentSelected()) + if (m_contextMenuHitResult.isContentSelected()) resultHit = WebView::TextSelection; // -------------------------------------------------------------------------------- // Ok, let's start filling up the menu... // is content editable? Add PASTE - if (result.isContentEditable()) + if (m_contextMenuHitResult.isContentEditable()) { + // Check to see if handled by speller + if (popupSpellMenu(event)) + return; + menu.addAction(pageAction(KWebPage::Paste)); menu.addSeparator(); } @@ -241,16 +356,16 @@ void WebView::contextMenuEvent(QContextMenuEvent *event) if (resultHit & WebView::LinkSelection) { // send by mail: link url - sendByMailAction->setData(result.linkUrl()); + sendByMailAction->setData(m_contextMenuHitResult.linkUrl()); sendByMailAction->setText(i18n("Share link")); a = new KAction(KIcon("tab-new"), i18n("Open in New &Tab"), this); - a->setData(result.linkUrl()); + a->setData(m_contextMenuHitResult.linkUrl()); connect(a, SIGNAL(triggered(bool)), this, SLOT(openLinkInNewTab())); menu.addAction(a); a = new KAction(KIcon("window-new"), i18n("Open in New &Window"), this); - a->setData(result.linkUrl()); + a->setData(m_contextMenuHitResult.linkUrl()); connect(a, SIGNAL(triggered(bool)), this, SLOT(openLinkInNewWindow())); menu.addAction(a); @@ -271,13 +386,13 @@ void WebView::contextMenuEvent(QContextMenuEvent *event) if (resultHit & WebView::ImageSelection) { // send by mail: image url - sendByMailAction->setData(result.imageUrl()); + sendByMailAction->setData(m_contextMenuHitResult.imageUrl()); sendByMailAction->setText(i18n("Share image link")); menu.addSeparator(); a = new KAction(KIcon("view-preview"), i18n("&View Image"), this); - a->setData(result.imageUrl()); + a->setData(m_contextMenuHitResult.imageUrl()); connect(a, SIGNAL(triggered(Qt::MouseButtons, Qt::KeyboardModifiers)), this, SLOT(viewImage(Qt::MouseButtons, Qt::KeyboardModifiers))); menu.addAction(a); @@ -285,14 +400,14 @@ void WebView::contextMenuEvent(QContextMenuEvent *event) menu.addAction(pageAction(KWebPage::DownloadImageToDisk)); a = new KAction(KIcon("view-media-visualization"), i18n("&Copy Image Location"), this); - a->setData(result.imageUrl()); + a->setData(m_contextMenuHitResult.imageUrl()); connect(a, SIGNAL(triggered(Qt::MouseButtons, Qt::KeyboardModifiers)), this, SLOT(slotCopyImageLocation())); menu.addAction(a); if (AdBlockManager::self()->isEnabled()) { a = new KAction(KIcon("preferences-web-browser-adblock"), i18n("Block image"), this); - a->setData(result.imageUrl()); + a->setData(m_contextMenuHitResult.imageUrl()); connect(a, SIGNAL(triggered(Qt::MouseButtons, Qt::KeyboardModifiers)), this, SLOT(blockImage())); menu.addAction(a); } @@ -305,22 +420,22 @@ void WebView::contextMenuEvent(QContextMenuEvent *event) sendByMailAction->setData(selectedText()); sendByMailAction->setText(i18n("Share selected text")); - if (result.isContentEditable()) + if (m_contextMenuHitResult.isContentEditable()) { // actions for text selected in field menu.addAction(pageAction(KWebPage::Cut)); } a = pageAction(KWebPage::Copy); - if (!result.linkUrl().isEmpty()) + if (!m_contextMenuHitResult.linkUrl().isEmpty()) a->setText(i18n("Copy Text")); //for link else a->setText(i18n("Copy")); menu.addAction(a); if (selectedText().contains('.') - && selectedText().indexOf('.') < selectedText().length() - && !selectedText().trimmed().contains(" ") + && selectedText().indexOf('.') < selectedText().length() + && !selectedText().trimmed().contains(" ") ) { QString text = selectedText(); @@ -336,13 +451,16 @@ void WebView::contextMenuEvent(QContextMenuEvent *event) truncatedUrl.truncate(truncateSize); truncatedUrl += QL1S("..."); } - //open selected text url in a new tab - QAction * const openInNewTabAction = new KAction(KIcon("tab-new"), i18n("Open '%1' in New Tab", truncatedUrl), this); + + // open selected text url in a new tab + QAction * const openInNewTabAction = new KAction(KIcon("tab-new"), + i18n("Open '%1' in New Tab", truncatedUrl), this); openInNewTabAction->setData(QUrl(urlLikeText)); connect(openInNewTabAction, SIGNAL(triggered(bool)), this, SLOT(openLinkInNewTab())); menu.addAction(openInNewTabAction); //open selected text url in a new window - QAction * const openInNewWindowAction = new KAction(KIcon("window-new"), i18n("Open '%1' in New Window", truncatedUrl), this); + QAction * const openInNewWindowAction = new KAction(KIcon("window-new"), + i18n("Open '%1' in New Window", truncatedUrl), this); openInNewWindowAction->setData(QUrl(urlLikeText)); connect(openInNewWindowAction, SIGNAL(triggered(bool)), this, SLOT(openLinkInNewWindow())); menu.addAction(openInNewWindowAction); @@ -388,7 +506,7 @@ void WebView::contextMenuEvent(QContextMenuEvent *event) if (resultHit & WebView::LinkSelection) { a = new KAction(KIcon("bookmark-new"), i18n("&Bookmark link"), this); - a->setData(result.linkUrl()); + a->setData(m_contextMenuHitResult.linkUrl()); connect(a, SIGNAL(triggered(bool)), this, SLOT(bookmarkLink())); menu.addAction(a); } @@ -400,6 +518,14 @@ void WebView::contextMenuEvent(QContextMenuEvent *event) menu.addAction(sendByMailAction); menu.addAction(inspectAction); + // SPELL CHECK Actions + if (m_contextMenuHitResult.isContentEditable()) + { + menu.addSeparator(); + a = KStandardAction::spelling(this, SLOT(spellCheck()), this); + menu.addAction(a); + } + // finally launch the menu... menu.exec(mapToGlobal(event->pos())); } @@ -1256,3 +1382,86 @@ void WebView::mouseReleaseEvent(QMouseEvent *event) QWebView::mouseReleaseEvent(event); } + + +void WebView::spellCheck() +{ + QString text(execJScript(m_contextMenuHitResult, QL1S("this.value")).toString()); + + if (m_contextMenuHitResult.isContentSelected()) + { + m_spellTextSelectionStart = qMax(0, execJScript(m_contextMenuHitResult, QL1S("this.selectionStart")).toInt()); + m_spellTextSelectionEnd = qMax(0, execJScript(m_contextMenuHitResult, QL1S("this.selectionEnd")).toInt()); + text = text.mid(m_spellTextSelectionStart, (m_spellTextSelectionEnd - m_spellTextSelectionStart)); + } + else + { + m_spellTextSelectionStart = 0; + m_spellTextSelectionEnd = 0; + } + + if (text.isEmpty()) + { + return; + } + + Sonnet::Dialog* spellDialog = new Sonnet::Dialog(new Sonnet::BackgroundChecker(this), this); + + spellDialog->showSpellCheckCompletionMessage(true); + connect(spellDialog, SIGNAL(replace(QString, int, QString)), this, SLOT(spellCheckerCorrected(QString, int, QString))); + connect(spellDialog, SIGNAL(misspelling(QString, int)), this, SLOT(spellCheckerMisspelling(QString, int))); + if (m_contextMenuHitResult.isContentSelected()) + connect(spellDialog, SIGNAL(done(QString)), this, SLOT(slotSpellCheckDone(QString))); + spellDialog->setBuffer(text); + spellDialog->show(); +} + + +void WebView::spellCheckerCorrected(const QString& original, int pos, const QString& replacement) +{ + // Adjust the selection end... + if (m_spellTextSelectionEnd > 0) + { + m_spellTextSelectionEnd += qMax(0, (replacement.length() - original.length())); + } + + const int index = pos + m_spellTextSelectionStart; + QString script(QL1S("this.value=this.value.substring(0,")); + script += QString::number(index); + script += QL1S(") + \""); + script += replacement; + script += QL1S("\" + this.value.substring("); + script += QString::number(index + original.length()); + script += QL1S(")"); + + //kDebug() << "**** script:" << script; + execJScript(m_contextMenuHitResult, script); +} + + +void WebView::spellCheckerMisspelling(const QString& text, int pos) +{ + // kDebug() << text << pos; + QString selectionScript(QL1S("this.setSelectionRange(")); + selectionScript += QString::number(pos + m_spellTextSelectionStart); + selectionScript += QL1C(','); + selectionScript += QString::number(pos + text.length() + m_spellTextSelectionStart); + selectionScript += QL1C(')'); + execJScript(m_contextMenuHitResult, selectionScript); +} + + +void WebView::slotSpellCheckDone(const QString&) +{ + // Restore the text selection if one was present before we started the + // spell check. + if (m_spellTextSelectionStart > 0 || m_spellTextSelectionEnd > 0) + { + QString script(QL1S("; this.setSelectionRange(")); + script += QString::number(m_spellTextSelectionStart); + script += QL1C(','); + script += QString::number(m_spellTextSelectionEnd); + script += QL1C(')'); + execJScript(m_contextMenuHitResult, script); + } +} diff --git a/src/webtab/webview.h b/src/webtab/webview.h index b1648d0d..5487a59f 100644 --- a/src/webtab/webview.h +++ b/src/webtab/webview.h @@ -39,6 +39,7 @@ #include #include #include +#include // Forward Declarations class WebPage; @@ -72,6 +73,7 @@ public: const QByteArray & body = QByteArray()); protected: + bool popupSpellMenu(QContextMenuEvent *event); void contextMenuEvent(QContextMenuEvent *event); void mouseMoveEvent(QMouseEvent *event); @@ -95,6 +97,10 @@ private Q_SLOTS: void openLinkInNewWindow(); void openLinkInNewTab(); void bookmarkLink(); + void spellCheck(); + void spellCheckerCorrected(const QString& original, int pos, const QString& replacement); + void spellCheckerMisspelling(const QString& text, int pos); + void slotSpellCheckDone(const QString&); void sendByMail(); void viewImage(Qt::MouseButtons buttons, Qt::KeyboardModifiers modifiers); @@ -130,6 +136,11 @@ private: private: QPoint m_clickPos; + QWebHitTestResult m_contextMenuHitResult; + + // Spell Checking + int m_spellTextSelectionStart; + int m_spellTextSelectionEnd; // Auto Scroll QTimer *const m_autoScrollTimer; -- cgit v1.2.1