summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorLindsay Mathieson <lindsay.mathieson@gmail.com>2012-09-17 19:52:53 +0200
committerAndrea Diamantini <adjam7@gmail.com>2012-09-17 19:56:40 +0200
commitb2b2692bf2a24ef4a588c0f94cffde0dbe3c94ac (patch)
tree43deb1c344798373dd2f62de45c36afbef8245ea /src
parentSVN_SILENT made messages (.desktop file) (diff)
downloadrekonq-b2b2692bf2a24ef4a588c0f94cffde0dbe3c94ac.tar.xz
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
Diffstat (limited to 'src')
-rw-r--r--src/CMakeLists.txt6
-rw-r--r--src/kspellplugin.cpp197
-rw-r--r--src/kspellplugin.h76
-rw-r--r--src/qwebkitplatformplugin.h201
-rw-r--r--src/rekonq.kcfg4
-rw-r--r--src/settings/settings_advanced.ui23
-rw-r--r--src/webview.cpp225
-rw-r--r--src/webview.h12
8 files changed, 725 insertions, 19 deletions
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 <lindsay dot mathieson at gmail dot com>
+*
+*
+* 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 <http://www.gnu.org/licenses/>.
+*
+* ============================================================ */
+
+
+#include <stdio.h>
+#include <KDebug>
+#include "kspellplugin.h"
+#include <QTextBoundaryFinder>
+
+#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<GrammarDetail>&, 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 <lindsay dot mathieson at gmail dot com>
+*
+*
+* 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 <http://www.gnu.org/licenses/>.
+*
+* ============================================================ */
+
+
+#ifndef TESTQWEBSPELLCHECKER_H
+#define TESTQWEBSPELLCHECKER_H
+
+
+#include <QtGlobal>
+#include <QtPlugin>
+#include <sonnet/speller.h>
+#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<GrammarDetail>&, 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 <lindsay dot mathieson at gmail dot com>
+ *
+ * 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 <QMediaPlayer>
+#endif
+#include <QtCore/QObject>
+#include <QtCore/QRect>
+#include <QtCore/QUrl>
+#include <QtGui/QColor>
+#include <QtGui/QFont>
+
+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<GrammarDetail>&, 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 @@
</entry>
</group>
-
<!-- Advanced Settings -->
<group name="Advanced">
<entry name="hScrollWheelHistory" type="Bool">
@@ -252,6 +251,9 @@
<entry name="middleClickAction" type="Int">
<default>2</default>
</entry>
+ <entry name="automaticSpellChecking" type="Bool">
+ <default>true</default>
+ </entry>
</group>
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 @@
<rect>
<x>0</x>
<y>0</y>
- <width>480</width>
+ <width>555</width>
<height>440</height>
</rect>
</property>
@@ -43,7 +43,9 @@
<string>Change them!</string>
</property>
<property name="icon">
- <iconset theme="preferences-system-network"/>
+ <iconset theme="preferences-system-network">
+ <normaloff/>
+ </iconset>
</property>
</widget>
</item>
@@ -130,6 +132,13 @@
</item>
</layout>
</item>
+ <item>
+ <widget class="QCheckBox" name="kcfg_automaticSpellChecking">
+ <property name="text">
+ <string>Automatic Spell Check</string>
+ </property>
+ </widget>
+ </item>
</layout>
</widget>
</item>
@@ -148,6 +157,16 @@
</item>
</layout>
</widget>
+ <tabstops>
+ <tabstop>proxyButton</tabstop>
+ <tabstop>kcfg_hScrollWheelHistory</tabstop>
+ <tabstop>kcfg_useFavicon</tabstop>
+ <tabstop>kcfg_smoothScrolling</tabstop>
+ <tabstop>kcfg_enableViShortcuts</tabstop>
+ <tabstop>kcfg_accessKeysEnabled</tabstop>
+ <tabstop>kcfg_middleClickAction</tabstop>
+ <tabstop>kcfg_automaticSpellChecking</tabstop>
+ </tabstops>
<resources/>
<connections/>
</ui>
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 <QWebFrame>
#include <QWebHistory>
#include <QNetworkRequest>
-
+#include <sonnet/speller.h>
+#include <Sonnet/Dialog>
+#include <sonnet/backgroundchecker.h>
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 <QTime>
#include <QPoint>
#include <QPixmap>
+#include <QWebHitTestResult>
// 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;