From b2b2692bf2a24ef4a588c0f94cffde0dbe3c94ac Mon Sep 17 00:00:00 2001 From: Lindsay Mathieson Date: Mon, 17 Sep 2012 19:52:53 +0200 Subject: Integrated spell checking - 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. BUG: 305720 CCMAIL: lindsay.mathieson@gmail.com REVIEW: 106417 REVIEWED-BY: adjam --- src/CMakeLists.txt | 6 + src/kspellplugin.cpp | 197 +++++++++++++++++++++++++++++++++ src/kspellplugin.h | 76 +++++++++++++ src/qwebkitplatformplugin.h | 201 ++++++++++++++++++++++++++++++++++ src/rekonq.kcfg | 4 +- src/settings/settings_advanced.ui | 23 +++- src/webview.cpp | 225 +++++++++++++++++++++++++++++++++++--- src/webview.h | 12 ++ 8 files changed, 725 insertions(+), 19 deletions(-) create mode 100644 src/kspellplugin.cpp create mode 100644 src/kspellplugin.h create mode 100644 src/qwebkitplatformplugin.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index c7945398..6c27376f 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -5,6 +5,9 @@ ADD_SUBDIRECTORY( tests ) ### ------- SETTING REKONQ FILES.. +add_definitions(-DQT_STATICPLUGIN) +QT4_WRAP_CPP(wk_HEADERS_MOC qwebkitplatformplugin.h) + SET( rekonq_KDEINIT_SRCS application.cpp @@ -111,6 +114,9 @@ SET( rekonq_KDEINIT_SRCS sync/syncftpsettingswidget.cpp sync/syncgooglesettingswidget.cpp sync/syncoperasettingswidget.cpp + #---------------------------- + kspellplugin.cpp + ${wk_HEADERS_MOC} ) # Nepomuk OPTIONAL src files diff --git a/src/kspellplugin.cpp b/src/kspellplugin.cpp new file mode 100644 index 00000000..d25ddf9f --- /dev/null +++ b/src/kspellplugin.cpp @@ -0,0 +1,197 @@ +/* ============================================================ +* +* This file is a part of the rekonq project +* +* Copyright (C) 2012 by Lindsay Mathieson +* +* +* This program is free software; you can redistribute it and/or +* modify it under the terms of the GNU General Public License as +* published by the Free Software Foundation; either version 2 of +* the License or (at your option) version 3 or any later version +* accepted by the membership of KDE e.V. (or its successor approved +* by the membership of KDE e.V.), which shall act as a proxy +* defined in Section 14 of version 3 of the license. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . +* +* ============================================================ */ + + +#include +#include +#include "kspellplugin.h" +#include + +#include "rekonq.h" + +#define methodDebug() kDebug("KWebSpellChecker: %s", __FUNCTION__) + +///////////////////////////// +// KWebSpellChecker + + +KWebSpellChecker::KWebSpellChecker() +{ + m_speller = new Sonnet::Speller(); + kDebug() << "!!! Client = " << m_speller->defaultClient() << endl; + kDebug() << "!!! Language = " << m_speller->defaultLanguage() << endl; +} + +KWebSpellChecker::~KWebSpellChecker() +{ + delete m_speller; +} + +bool KWebSpellChecker::isContinousSpellCheckingEnabled() const +{ + return ReKonfig::automaticSpellChecking(); +} + +void KWebSpellChecker::toggleContinousSpellChecking() +{ + ReKonfig::setAutomaticSpellChecking(! ReKonfig::automaticSpellChecking()); +} + +void KWebSpellChecker::learnWord(const QString& word) +{ + Q_UNUSED(word); +} + +void KWebSpellChecker::ignoreWordInSpellDocument(const QString& word) +{ + Q_UNUSED(word); +} + +static bool isValidWord(const QString &str) +{ + if (str.isEmpty() || (str.length() == 1 && !str[0].isLetter())) + { + return false; + } + const int length = str.length(); + for (int i = 0; i < length; ++i) + { + if (!str[i].isNumber()) + { + return true; + } + } + // 'str' only contains numbers + return false; +} + +void KWebSpellChecker::checkSpellingOfString(const QString& word, int* misspellingLocation, int* misspellingLength) +{ + // sanity check + if (misspellingLocation == NULL || misspellingLength == NULL) + return; + + *misspellingLocation = -1; + *misspellingLength = 0; + + kDebug() << word << endl; + + QTextBoundaryFinder finder = QTextBoundaryFinder(QTextBoundaryFinder::Word, word); + + QTextBoundaryFinder::BoundaryReasons boundary = finder.boundaryReasons(); + int start = finder.position(), end = finder.position(); + bool inWord = (boundary & QTextBoundaryFinder::StartWord) != 0; + while (finder.toNextBoundary() > 0) + { + boundary = finder.boundaryReasons(); + if ((boundary & QTextBoundaryFinder::EndWord) && inWord) + { + end = finder.position(); + QString str = finder.string().mid(start, end - start); + if (isValidWord(str)) + { +#if 1 + qDebug() << "Word at " << start << " word = '" + << str << "', len = " << str.length(); +#endif + if (m_speller->isMisspelled(str)) + { + *misspellingLocation = start; + *misspellingLength = end - start; + } + return; + } + inWord = false; + } + if ((boundary & QTextBoundaryFinder::StartWord)) + { + start = finder.position(); + inWord = true; + } + } +} + +QString KWebSpellChecker::autoCorrectSuggestionForMisspelledWord(const QString& word) +{ + /* + QStringList words = m_speller->suggest(word); + if (words.size() > 0) + return words[0]; + else + return QString(""); + */ + + + return QString(""); +} + +void KWebSpellChecker::guessesForWord(const QString& word, const QString& context, QStringList& guesses) +{ + Q_UNUSED(context); + + QStringList words = m_speller->suggest(word); + guesses = words; +} + +bool KWebSpellChecker::isGrammarCheckingEnabled() +{ + return false; +} + +void KWebSpellChecker::toggleGrammarChecking() +{ +} + +void KWebSpellChecker::checkGrammarOfString(const QString&, QList&, int* badGrammarLocation, int* badGrammarLength) +{ + Q_UNUSED(badGrammarLocation); + Q_UNUSED(badGrammarLength); +} + + +//////////////////////////////////////////// +// KWebKitPlatformPlugin +KWebKitPlatformPlugin::KWebKitPlatformPlugin() +{ +} + +KWebKitPlatformPlugin::~KWebKitPlatformPlugin() +{ +} + + +bool KWebKitPlatformPlugin::supportsExtension(Extension ext) const +{ + return ext == SpellChecker; +} + +QObject* KWebKitPlatformPlugin::createExtension(Extension) const +{ + return new KWebSpellChecker(); +} + +Q_EXPORT_PLUGIN2(kwebspellchecker, KWebKitPlatformPlugin); +Q_IMPORT_PLUGIN(kwebspellchecker) + diff --git a/src/kspellplugin.h b/src/kspellplugin.h new file mode 100644 index 00000000..faa83106 --- /dev/null +++ b/src/kspellplugin.h @@ -0,0 +1,76 @@ +/* ============================================================ +* +* This file is a part of the rekonq project +* +* Copyright (C) 2012 by Lindsay Mathieson +* +* +* This program is free software; you can redistribute it and/or +* modify it under the terms of the GNU General Public License as +* published by the Free Software Foundation; either version 2 of +* the License or (at your option) version 3 or any later version +* accepted by the membership of KDE e.V. (or its successor approved +* by the membership of KDE e.V.), which shall act as a proxy +* defined in Section 14 of version 3 of the license. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . +* +* ============================================================ */ + + +#ifndef TESTQWEBSPELLCHECKER_H +#define TESTQWEBSPELLCHECKER_H + + +#include +#include +#include +#include "qwebkitplatformplugin.h" + + + + +class KWebSpellChecker : public QWebSpellChecker +{ + Q_OBJECT +public: + Sonnet::Speller *m_speller; + + KWebSpellChecker(); + ~KWebSpellChecker(); + + virtual bool isContinousSpellCheckingEnabled() const; + virtual void toggleContinousSpellChecking(); + virtual void learnWord(const QString& word); + virtual void ignoreWordInSpellDocument(const QString& word); + virtual void checkSpellingOfString(const QString& word, int* misspellingLocation, int* misspellingLength); + virtual QString autoCorrectSuggestionForMisspelledWord(const QString& word); + virtual void guessesForWord(const QString& word, const QString& context, QStringList& guesses); + + virtual bool isGrammarCheckingEnabled(); + virtual void toggleGrammarChecking(); + virtual void checkGrammarOfString(const QString&, QList&, int* badGrammarLocation, int* badGrammarLength); +}; + + +class KWebKitPlatformPlugin : public QObject, public QWebKitPlatformPlugin +{ + Q_OBJECT + Q_INTERFACES(QWebKitPlatformPlugin) + +public: + KWebKitPlatformPlugin(); + ~KWebKitPlatformPlugin(); + + virtual bool supportsExtension(Extension) const; + virtual QObject* createExtension(Extension) const; + +}; + +#endif \ No newline at end of file diff --git a/src/qwebkitplatformplugin.h b/src/qwebkitplatformplugin.h new file mode 100644 index 00000000..a1f25fdb --- /dev/null +++ b/src/qwebkitplatformplugin.h @@ -0,0 +1,201 @@ +/* + * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies) + * Copyright (C) 2012 by Lindsay Mathieson + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + * + */ + +#ifndef QWEBKITPLATFORMPLUGIN_H +#define QWEBKITPLATFORMPLUGIN_H + +/* + * Warning: The contents of this file is not part of the public QtWebKit API + * and may be changed from version to version or even be completely removed. +*/ + +#if defined(WTF_USE_QT_MULTIMEDIA) && WTF_USE_QT_MULTIMEDIA +#include +#endif +#include +#include +#include +#include +#include + +class QWebSelectData +{ +public: + virtual ~QWebSelectData() {} + + enum ItemType { Option, Group, Separator }; + + virtual ItemType itemType(int) const = 0; + virtual QString itemText(int index) const = 0; + virtual QString itemToolTip(int index) const = 0; + virtual bool itemIsEnabled(int index) const = 0; + virtual bool itemIsSelected(int index) const = 0; + virtual int itemCount() const = 0; + virtual bool multiple() const = 0; + virtual QColor backgroundColor() const = 0; + virtual QColor foregroundColor() const = 0; + virtual QColor itemBackgroundColor(int index) const = 0; + virtual QColor itemForegroundColor(int index) const = 0; +}; + +class QWebSelectMethod : public QObject +{ + Q_OBJECT +public: + virtual ~QWebSelectMethod() {} + + virtual void show(const QWebSelectData&) = 0; + virtual void hide() = 0; + virtual void setGeometry(const QRect&) = 0; + virtual void setFont(const QFont&) = 0; + +Q_SIGNALS: + void selectItem(int index, bool allowMultiplySelections, bool shift); + void didHide(); +}; + +class QWebNotificationData +{ +public: + virtual ~QWebNotificationData() {} + + virtual const QString title() const = 0; + virtual const QString message() const = 0; + virtual const QUrl iconUrl() const = 0; + virtual const QUrl openerPageUrl() const = 0; +}; + +class QWebNotificationPresenter : public QObject +{ + Q_OBJECT +public: + QWebNotificationPresenter() {} + virtual ~QWebNotificationPresenter() {} + + virtual void showNotification(const QWebNotificationData*) = 0; + +Q_SIGNALS: + void notificationClosed(); + void notificationClicked(); +}; + +class QWebHapticFeedbackPlayer: public QObject +{ + Q_OBJECT +public: + QWebHapticFeedbackPlayer() {} + virtual ~QWebHapticFeedbackPlayer() {} + + enum HapticStrength + { + None, Weak, Medium, Strong + }; + + enum HapticEvent + { + Press, Release + }; + + virtual void playHapticFeedback(const HapticEvent, const QString& hapticType, const HapticStrength) = 0; +}; + +class QWebTouchModifier : public QObject +{ + Q_OBJECT +public: + virtual ~QWebTouchModifier() {} + + enum PaddingDirection + { + Up, Right, Down, Left + }; + + virtual unsigned hitTestPaddingForTouch(const PaddingDirection) const = 0; +}; + +#if defined(WTF_USE_QT_MULTIMEDIA) && WTF_USE_QT_MULTIMEDIA +class QWebFullScreenVideoHandler : public QObject +{ + Q_OBJECT +public: + QWebFullScreenVideoHandler() {} + virtual ~QWebFullScreenVideoHandler() {} + virtual bool requiresFullScreenForVideoPlayback() const = 0; + +Q_SIGNALS: + void fullScreenClosed(); + +public Q_SLOTS: + virtual void enterFullScreen(QMediaPlayer*) = 0; + virtual void exitFullScreen() = 0; +}; +#endif + +class QWebSpellChecker : public QObject +{ + Q_OBJECT +public: + struct GrammarDetail + { + int location; + int length; + QStringList guesses; + QString userDescription; + }; + + virtual bool isContinousSpellCheckingEnabled() const = 0; + virtual void toggleContinousSpellChecking() = 0; + + virtual void learnWord(const QString& word) = 0; + virtual void ignoreWordInSpellDocument(const QString& word) = 0; + virtual void checkSpellingOfString(const QString& word, int* misspellingLocation, int* misspellingLength) = 0; + virtual QString autoCorrectSuggestionForMisspelledWord(const QString& word) = 0; + virtual void guessesForWord(const QString& word, const QString& context, QStringList& guesses) = 0; + + virtual bool isGrammarCheckingEnabled() = 0; + virtual void toggleGrammarChecking() = 0; + virtual void checkGrammarOfString(const QString&, QList&, int* badGrammarLocation, int* badGrammarLength) = 0; +}; + +class QWebKitPlatformPlugin +{ +public: + virtual ~QWebKitPlatformPlugin() {} + + enum Extension + { + MultipleSelections, + Notifications, + Haptics, + TouchInteraction, + FullScreenVideoPlayer, + SpellChecker + }; + + virtual bool supportsExtension(Extension) const = 0; + virtual QObject* createExtension(Extension) const = 0; +}; + +QT_BEGIN_NAMESPACE +Q_DECLARE_INTERFACE(QWebKitPlatformPlugin, "com.nokia.Qt.WebKit.PlatformPlugin/1.9"); +QT_END_NAMESPACE + +#endif // QWEBKITPLATFORMPLUGIN_H diff --git a/src/rekonq.kcfg b/src/rekonq.kcfg index 1fb3c1c4..18dea24e 100644 --- a/src/rekonq.kcfg +++ b/src/rekonq.kcfg @@ -231,7 +231,6 @@ - @@ -252,6 +251,9 @@ 2 + + true + diff --git a/src/settings/settings_advanced.ui b/src/settings/settings_advanced.ui index 9457ca22..03891e2c 100644 --- a/src/settings/settings_advanced.ui +++ b/src/settings/settings_advanced.ui @@ -6,7 +6,7 @@ 0 0 - 480 + 555 440 @@ -43,7 +43,9 @@ Change them! - + + + @@ -130,6 +132,13 @@ + + + + Automatic Spell Check + + + @@ -148,6 +157,16 @@ + + proxyButton + kcfg_hScrollWheelHistory + kcfg_useFavicon + kcfg_smoothScrolling + kcfg_enableViShortcuts + kcfg_accessKeysEnabled + kcfg_middleClickAction + kcfg_automaticSpellChecking + diff --git a/src/webview.cpp b/src/webview.cpp index 7272e2c2..86be4506 100644 --- a/src/webview.cpp +++ b/src/webview.cpp @@ -64,7 +64,9 @@ #include #include #include - +#include +#include +#include WebView::WebView(QWidget* parent) : KWebView(parent, false) @@ -153,10 +155,102 @@ WebPage *WebView::page() return m_page; } +bool WebView::popupSpellMenu(QContextMenuEvent *event) +{ + // return false if not handled + if (! ReKonfig::automaticSpellChecking()) + return false; + + QWebElement element(m_ContextMenuResult.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_ContextMenuResult = page()->mainFrame()->hitTestContent(event->pos()); MainWindow *mainwindow = rApp->mainWindow(); KMenu menu(this); @@ -171,23 +265,27 @@ void WebView::contextMenuEvent(QContextMenuEvent *event) // Choose right context int resultHit = 0; - if (result.linkUrl().isEmpty()) + if (m_ContextMenuResult.linkUrl().isEmpty()) resultHit = WebView::EmptySelection; else resultHit = WebView::LinkSelection; - if (!result.pixmap().isNull()) + if (!m_ContextMenuResult.pixmap().isNull()) resultHit |= WebView::ImageSelection; - if (result.isContentSelected()) + if (m_ContextMenuResult.isContentSelected()) resultHit = WebView::TextSelection; // -------------------------------------------------------------------------------- // Ok, let's start filling up the menu... // is content editable? Add PASTE - if (result.isContentEditable()) + if (m_ContextMenuResult.isContentEditable()) { + // Check to see if handled by speller + if (popupSpellMenu(event)) + return; + menu.addAction(pageAction(KWebPage::Paste)); menu.addSeparator(); } @@ -253,16 +351,16 @@ void WebView::contextMenuEvent(QContextMenuEvent *event) if (resultHit & WebView::LinkSelection) { // send by mail: link url - sendByMailAction->setData(result.linkUrl()); + sendByMailAction->setData(m_ContextMenuResult.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_ContextMenuResult.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_ContextMenuResult.linkUrl()); connect(a, SIGNAL(triggered(bool)), this, SLOT(openLinkInNewWindow())); menu.addAction(a); @@ -283,13 +381,13 @@ void WebView::contextMenuEvent(QContextMenuEvent *event) if (resultHit & WebView::ImageSelection) { // send by mail: image url - sendByMailAction->setData(result.imageUrl()); + sendByMailAction->setData(m_ContextMenuResult.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_ContextMenuResult.imageUrl()); connect(a, SIGNAL(triggered(Qt::MouseButtons, Qt::KeyboardModifiers)), this, SLOT(viewImage(Qt::MouseButtons, Qt::KeyboardModifiers))); menu.addAction(a); @@ -297,14 +395,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_ContextMenuResult.imageUrl()); connect(a, SIGNAL(triggered(Qt::MouseButtons, Qt::KeyboardModifiers)), this, SLOT(slotCopyImageLocation())); menu.addAction(a); if (rApp->adblockManager()->isEnabled()) { a = new KAction(KIcon("preferences-web-browser-adblock"), i18n("Block image"), this); - a->setData(result.imageUrl()); + a->setData(m_ContextMenuResult.imageUrl()); connect(a, SIGNAL(triggered(Qt::MouseButtons, Qt::KeyboardModifiers)), this, SLOT(blockImage())); menu.addAction(a); } @@ -317,14 +415,14 @@ void WebView::contextMenuEvent(QContextMenuEvent *event) sendByMailAction->setData(selectedText()); sendByMailAction->setText(i18n("Share selected text")); - if (result.isContentEditable()) + if (m_ContextMenuResult.isContentEditable()) { // actions for text selected in field menu.addAction(pageAction(KWebPage::Cut)); } a = pageAction(KWebPage::Copy); - if (!result.linkUrl().isEmpty()) + if (!m_ContextMenuResult.linkUrl().isEmpty()) a->setText(i18n("Copy Text")); //for link else a->setText(i18n("Copy")); @@ -399,7 +497,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_ContextMenuResult.linkUrl()); connect(a, SIGNAL(triggered(bool)), this, SLOT(bookmarkLink())); menu.addAction(a); } @@ -411,6 +509,14 @@ void WebView::contextMenuEvent(QContextMenuEvent *event) menu.addAction(sendByMailAction); menu.addAction(inspectAction); + // SPELL CHECK Actions + if (m_ContextMenuResult.isContentEditable()) + { + menu.addSeparator(); + a = KStandardAction::spelling(this, SLOT(spellCheck()), this); + menu.addAction(a); + } + // finally launch the menu... menu.exec(mapToGlobal(event->pos())); } @@ -655,6 +761,93 @@ void WebView::bookmarkLink() rApp->bookmarkManager()->emitChanged(); } +static QVariant execJScript(QWebHitTestResult result, const QString& script) +{ + QWebElement element(result.element()); + if (element.isNull()) + return QVariant(); + return element.evaluateJavaScript(script); +} + +void WebView::spellCheck() +{ + QString text(execJScript(m_ContextMenuResult, QL1S("this.value")).toString()); + + if (m_ContextMenuResult.isContentSelected()) + { + m_spellTextSelectionStart = qMax(0, execJScript(m_ContextMenuResult, QL1S("this.selectionStart")).toInt()); + m_spellTextSelectionEnd = qMax(0, execJScript(m_ContextMenuResult, 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_ContextMenuResult.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_ContextMenuResult, 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_ContextMenuResult, 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_ContextMenuResult, script); + } +} + void WebView::keyPressEvent(QKeyEvent *event) { diff --git a/src/webview.h b/src/webview.h index 0141925c..8cf16817 100644 --- a/src/webview.h +++ b/src/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); @@ -97,6 +99,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); @@ -133,6 +139,12 @@ private: private: WebPage *m_page; QPoint m_clickPos; + QWebHitTestResult m_ContextMenuResult; + + // Spell Checking + int m_spellTextSelectionStart; + int m_spellTextSelectionEnd; + // Auto Scroll QTimer *const m_autoScrollTimer; -- cgit v1.2.1