1
mirror of https://invent.kde.org/network/falkon.git synced 2024-12-20 10:46:35 +01:00

[spellcheck] Added context menu options to replace misspelled words.

And also to add word into user dictionary.
This commit is contained in:
nowrep 2013-02-05 20:43:53 +01:00
parent 074bd928c7
commit c3a0e1f13a
11 changed files with 262 additions and 58 deletions

View File

@ -53,7 +53,10 @@
#include "checkboxdialog.h"
#include "registerqappassociation.h"
#include "html5permissions/html5permissionsmanager.h"
#ifdef USE_HUNSPELL
#include "qtwebkit/spellcheck/speller.h"
#endif
#ifdef Q_OS_MAC
#include <QFileOpenEvent>
@ -93,7 +96,9 @@ MainApplication::MainApplication(int &argc, char** argv)
, m_restoreManager(0)
, m_proxyStyle(0)
, m_html5permissions(0)
#ifdef USE_HUNSPELL
, m_speller(0)
#endif
, m_dbWriter(new DatabaseWriter(this))
, m_uaManager(new UserAgentManager)
, m_isPrivateSession(false)
@ -498,11 +503,47 @@ QList<QupZilla*> MainApplication::mainWindows()
return list;
}
QString MainApplication::currentLanguage()
bool MainApplication::isClosing() const
{
return m_isClosing;
}
bool MainApplication::isPrivateSession() const
{
return m_isPrivateSession;
}
bool MainApplication::isStartingAfterCrash() const
{
return m_startingAfterCrash;
}
int MainApplication::windowCount() const
{
return m_mainWindows.count();
}
QString MainApplication::currentLanguageFile() const
{
return m_activeLanguage;
}
QString MainApplication::currentLanguage() const
{
QString lang = m_activeLanguage;
if (lang.isEmpty()) {
return "en_US";
}
return lang.left(lang.length() - 3);
}
QString MainApplication::currentProfilePath() const
{
return m_activeProfil;
}
void MainApplication::sendMessages(Qz::AppMessageType mes, bool state)
{
emit message(mes, state);
@ -626,7 +667,11 @@ void MainApplication::connectDatabase()
void MainApplication::translateApp()
{
Settings settings;
const QString &file = settings.value("Language/language", QLocale::system().name()).toString();
QString file = settings.value("Language/language", QLocale::system().name()).toString();
if (!file.isEmpty() && !file.endsWith(QLatin1String(".qm"))) {
file.append(".qm");
}
QTranslator* app = new QTranslator(this);
app->load(file, TRANSLATIONSDIR);
@ -821,13 +866,15 @@ HTML5PermissionsManager* MainApplication::html5permissions()
return m_html5permissions;
}
#ifdef USE_HUNSPELL
Speller* MainApplication::speller()
{
if (!m_speller) {
m_speller = new Speller();
m_speller = new Speller(this);
}
return m_speller;
}
#endif
void MainApplication::startPrivateBrowsing()
{
@ -1104,5 +1151,4 @@ QString MainApplication::tempPath() const
MainApplication::~MainApplication()
{
delete m_uaManager;
delete m_speller;
}

View File

@ -76,14 +76,15 @@ public:
QList<QupZilla*> mainWindows();
inline static MainApplication* getInstance() { return static_cast<MainApplication*>(QCoreApplication::instance()); }
inline QString currentProfilePath() { return m_activeProfil; }
inline bool isPrivateSession() { return m_isPrivateSession; }
inline bool isClosing() { return m_isClosing; }
inline bool isStartingAfterCrash() { return m_startingAfterCrash; }
inline int windowCount() { return m_mainWindows.count(); }
static MainApplication* getInstance() { return static_cast<MainApplication*>(QCoreApplication::instance()); }
QString currentLanguage();
bool isClosing() const;
bool isPrivateSession() const;
bool isStartingAfterCrash() const;
int windowCount() const;
QString currentLanguageFile() const;
QString currentLanguage() const;
QString currentProfilePath() const;
bool checkSettingsDir();
void destroyRestoreManager();
@ -109,7 +110,9 @@ public:
QNetworkDiskCache* networkCache();
DesktopNotificationsFactory* desktopNotifications();
HTML5PermissionsManager* html5permissions();
#ifdef USE_HUNSPELL
Speller* speller();
#endif
DatabaseWriter* dbWriter() { return m_dbWriter; }
UserAgentManager* uaManager() { return m_uaManager; }
@ -170,7 +173,9 @@ private:
RestoreManager* m_restoreManager;
ProxyStyle* m_proxyStyle;
HTML5PermissionsManager* m_html5permissions;
#ifdef USE_HUNSPELL
Speller* m_speller;
#endif
DatabaseWriter* m_dbWriter;
UserAgentManager* m_uaManager;

View File

@ -218,7 +218,7 @@ PluginInterface* Plugins::initPlugin(PluginInterface* interface, QPluginLoader*
return 0;
}
qApp->installTranslator(interface->getTranslator(mApp->currentLanguage()));
qApp->installTranslator(interface->getTranslator(mApp->currentLanguageFile()));
return interface;
}

View File

@ -1,6 +1,6 @@
HEADERS += $$PWD/qtwebkitplugin.h \
$$PWD/notifications/notificationpresenter.h \
$$[QT_INSTALL_HEADERS]/QtWebKit/qwebkitplatformplugin.h
$$[QT_INSTALL_HEADERS]/QtWebKit/qwebkitplatformplugin.h \
SOURCES += $$PWD/qtwebkitplugin.cpp \
@ -11,9 +11,13 @@ DEFINES *= QT_STATICPLUGIN
unix:contains(DEFINES, USE_QTWEBKIT_2_3):system(pkg-config --exists hunspell) {
HEADERS += $$PWD/spellcheck/spellcheck.h \
$$PWD/spellcheck/speller.h \
$$PWD/spellcheck/spellcheckdialog.h \
SOURCES += $$PWD/spellcheck/spellcheck.cpp \
$$PWD/spellcheck/speller.cpp \
$$PWD/spellcheck/spellcheckdialog.cpp \
FORMS += $$PWD/spellcheck/spellcheckdialog.ui
DEFINES *= USE_HUNSPELL
LIBS += $$system(pkg-config --libs hunspell)

View File

@ -30,7 +30,7 @@ SpellCheck::SpellCheck()
bool SpellCheck::isContinousSpellCheckingEnabled() const
{
return true;
return m_speller->isEnabled();
}
void SpellCheck::toggleContinousSpellChecking()
@ -50,7 +50,7 @@ void SpellCheck::ignoreWordInSpellDocument(const QString &word)
void SpellCheck::checkSpellingOfString(const QString &word,
int* misspellingLocation, int* misspellingLength)
{
if (misspellingLocation == NULL || misspellingLength == NULL || !m_speller) {
if (misspellingLocation == NULL || misspellingLength == NULL) {
return;
}
@ -69,7 +69,7 @@ void SpellCheck::checkSpellingOfString(const QString &word,
if (endOfWord(boundary, finder.type()) && inWord) {
end = finder.position();
QString str = finder.string().mid(start, end - start);
if (isValidWord(str)) {
if (Speller::isValidWord(str)) {
if (m_speller->isMisspelled(str)) {
*misspellingLocation = start;
*misspellingLength = end - start;
@ -133,21 +133,6 @@ void SpellCheck::checkGrammarOfString(const QString &, QList<GrammarDetail> &,
Q_UNUSED(badGrammarLength);
}
bool SpellCheck::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;
}
bool SpellCheck::endOfWord(const QTextBoundaryFinder::BoundaryReasons &reasons,
const QTextBoundaryFinder::BoundaryType &type)
{

View File

@ -47,7 +47,6 @@ public:
int* badGrammarLocation, int* badGrammarLength);
private:
bool isValidWord(const QString &str);
bool endOfWord(const QTextBoundaryFinder::BoundaryReasons &reasons,
const QTextBoundaryFinder::BoundaryType &type);
bool startOfWord(const QTextBoundaryFinder::BoundaryReasons &reasons,

View File

@ -16,6 +16,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* ============================================================ */
#include "speller.h"
#include "spellcheckdialog.h"
#include "settings.h"
#include "mainapplication.h"
#include "qztools.h"
@ -27,11 +28,16 @@
#include <QLocale>
#include <QDebug>
#include <QDirIterator>
#include <QWebHitTestResult>
#include <QTextBoundaryFinder>
#include <QTextStream>
#include <QMenu>
#include <hunspell/hunspell.hxx>
Speller::Speller()
: m_textCodec(0)
Speller::Speller(QObject* parent)
: QObject(parent)
, m_textCodec(0)
, m_hunspell(0)
, m_enabled(false)
{
@ -45,6 +51,7 @@ bool Speller::isEnabled() const
void Speller::loadSettings()
{
Settings settings;
settings.beginGroup("SpellCheck");
m_enabled = settings.value("enabled", true).toBool();
@ -83,7 +90,18 @@ void Speller::initialize()
m_textCodec = QTextCodec::codecForName(m_hunspell->get_dic_encoding());
if (m_userDictionary.exists()) {
m_hunspell->add_dic(m_userDictionary.fileName().toLocal8Bit().constData());
if (!m_userDictionary.open(QFile::ReadOnly)) {
qWarning() << "SpellCheck: Cannot open" << m_userDictionary.fileName() << "for reading!";
}
else {
QString word;
QTextStream stream(&m_userDictionary);
while (!stream.atEnd()) {
stream >> word;
putWord(word);
}
}
m_userDictionary.close();
}
qDebug() << "SpellCheck: Language =" << language().code;
@ -98,8 +116,7 @@ QList<Speller::Language> Speller::availableLanguages() const
{
QList<Language> languages;
QDirIterator it(m_dictionaryPath, QStringList("*.dic"),
QDir::Files, QDirIterator::FollowSymlinks | QDirIterator::Subdirectories);
QDirIterator it(m_dictionaryPath, QStringList("*.dic"), QDir::Files);
while (it.hasNext()) {
const QString affFilePath = it.next().replace(QLatin1String(".dic"), QLatin1String(".aff"));
@ -112,20 +129,76 @@ QList<Speller::Language> Speller::availableLanguages() const
lang.code = it.fileInfo().baseName();
lang.name = nameForLanguage(lang.code);
if (!languages.contains(lang)) {
languages.append(lang);
}
}
return languages;
}
void Speller::learnWord(const QString &word)
QString Speller::dictionaryPath() const
{
if (!m_hunspell || !m_textCodec) {
return m_dictionaryPath;
}
void Speller::showSettings(QWidget* parent)
{
SpellCheckDialog dialog(parent);
if (dialog.exec() == QDialog::Accepted) {
loadSettings();
}
}
void Speller::populateContextMenu(QMenu* menu, const QWebHitTestResult &hitTest)
{
m_element = hitTest.element();
if (m_element.isNull()) {
return;
}
const char* encodedWord = m_textCodec->fromUnicode(word).constData();
m_hunspell->add(encodedWord);
const QString text = m_element.evaluateJavaScript("this.value").toString();
const int pos = m_element.evaluateJavaScript("this.selectionStart").toInt() + 1;
QTextBoundaryFinder finder = QTextBoundaryFinder(QTextBoundaryFinder::Word, text);
finder.setPosition(pos);
m_startPos = finder.toPreviousBoundary();
m_endPos = finder.toNextBoundary();
const QString &word = text.mid(m_startPos, m_endPos - m_startPos).trimmed();
if (!isValidWord(word) || !isMisspelled(word)) {
return;
}
const int limit = 6;
QStringList suggests = suggest(word);
int count = suggests.count() > limit ? limit : suggests.count();
QFont boldFont = menu->font();
boldFont.setBold(true);
for (int i = 0; i < count; ++i) {
QAction* act = menu->addAction(suggests.at(i), this, SLOT(replaceWord()));
act->setData(suggests.at(i));
act->setFont(boldFont);
}
if (count == 0) {
menu->addAction(tr("No suggestions"))->setEnabled(false);
}
menu->addAction(tr("Add to dictionary"), this, SLOT(addToDictionary()))->setData(word);
menu->addSeparator();
}
void Speller::addToDictionary()
{
if (QAction* act = qobject_cast<QAction*>(sender())) {
QString word = act->data().toString();
putWord(word);
if (!m_userDictionary.open(QFile::WriteOnly | QFile::Append)) {
qWarning() << "SpellCheck: Cannot open file" << m_userDictionary.fileName() << "for writing!";
@ -136,6 +209,40 @@ void Speller::learnWord(const QString &word)
m_userDictionary.write("\n");
m_userDictionary.close();
}
}
void Speller::replaceWord()
{
if (m_element.isNull()) {
return;
}
if (QAction* act = qobject_cast<QAction*>(sender())) {
QString word = act->data().toString();
QString text = m_element.evaluateJavaScript("this.value").toString();
const int cursorPos = m_element.evaluateJavaScript("this.selectionStart").toInt();
text.replace(m_startPos, m_endPos - m_startPos, word);
text.replace(QLatin1Char('\\'), QLatin1String("\\\\"));
text.replace(QLatin1Char('\''), QLatin1String("\\'"));
word.replace(QLatin1Char('\\'), QLatin1String("\\\\"));
word.replace(QLatin1Char('\''), QLatin1String("\\'"));
m_element.evaluateJavaScript(QString("this.value='%1'").arg(text));
m_element.evaluateJavaScript(QString("this.selectionStart=this.selectionEnd=%1").arg(cursorPos));
}
}
void Speller::putWord(const QString &word)
{
if (!m_hunspell || !m_textCodec) {
return;
}
const char* encodedWord = m_textCodec->fromUnicode(word).constData();
m_hunspell->add(encodedWord);
}
bool Speller::isMisspelled(const QString &string)
{
@ -166,6 +273,23 @@ QStringList Speller::suggest(const QString &word)
return suggests;
}
bool Speller::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;
}
}
return false;
}
bool Speller::dictionaryExists(const QString &path) const
{
return QFile(path + ".dic").exists() &&

View File

@ -18,21 +18,34 @@
#ifndef SPELLER_H
#define SPELLER_H
#include <QWebElement>
#include <QStringList>
#include <QFile>
class QTextCodec;
class Hunspell;
class Speller
class QMenu;
class QWebHitTestResult;
class Speller : public QObject
{
Q_OBJECT
public:
struct Language {
QString code;
QString name;
bool operator==(const Language &other) {
return this->name == other.name &&
this->name.left(2) == other.name.left(2);
// Compare only first two chars of name.
// So "cs_CZ - CzechRepublic" == "cs - CzechRepublic"
}
};
explicit Speller();
explicit Speller(QObject* parent);
~Speller();
bool isEnabled() const;
@ -41,13 +54,22 @@ public:
Language language() const;
QList<Language> availableLanguages() const;
void learnWord(const QString &word);
QString dictionaryPath() const;
void showSettings(QWidget* parent);
void populateContextMenu(QMenu* menu, const QWebHitTestResult &hitTest);
bool isMisspelled(const QString &string);
QStringList suggest(const QString &word);
static bool isValidWord(const QString &str);
private slots:
void addToDictionary();
void replaceWord();
private:
void initialize();
void putWord(const QString &word);
bool dictionaryExists(const QString &path) const;
QString getDictionaryPath() const;
@ -60,6 +82,11 @@ private:
QFile m_userDictionary;
Language m_language;
bool m_enabled;
// Replacing word
QWebElement m_element;
int m_startPos;
int m_endPos;
};
#endif // SPELLER_H

View File

@ -372,8 +372,8 @@ Preferences::Preferences(QupZilla* mainClass, QWidget* parent)
//OTHER
//Languages
QString activeLanguage;
if (!mApp->currentLanguage().isEmpty()) {
activeLanguage = mApp->currentLanguage();
if (!mApp->currentLanguageFile().isEmpty()) {
activeLanguage = mApp->currentLanguageFile();
QLocale locale(activeLanguage);
QString country = QLocale::countryToString(locale.country());
QString language = QLocale::languageToString(locale.language());

View File

@ -1,6 +1,6 @@
/* ============================================================
* QupZilla - WebKit based browser
* Copyright (C) 2010-2012 David Rosca <nowrep@gmail.com>
* Copyright (C) 2010-2013 David Rosca <nowrep@gmail.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
@ -334,7 +334,7 @@ CertificateInfoWidget::CertificateInfoWidget(const QSslCertificate &cert, QWidge
ui->issuedByO->setText(showCertInfo(cert.issuerInfo(QSslCertificate::Organization)));
ui->issuedByOU->setText(showCertInfo(cert.issuerInfo(QSslCertificate::OrganizationalUnitName)));
//Validity
QLocale locale = QLocale(mApp->currentLanguage());
QLocale locale = QLocale(mApp->currentLanguageFile());
ui->validityIssuedOn->setText(locale.toString(cert.effectiveDate(), "dddd d. MMMM yyyy"));
ui->validityExpiresOn->setText(locale.toString(cert.expiryDate(), "dddd d. MMMM yyyy"));
}

View File

@ -34,6 +34,10 @@
#include "qzsettings.h"
#include "enhancedmenu.h"
#ifdef USE_HUNSPELL
#include "qtwebkit/spellcheck/speller.h"
#endif
#include <QDir>
#include <QTimer>
#include <QDesktopServices>
@ -753,6 +757,16 @@ void WebView::createContextMenu(QMenu* menu, const QWebHitTestResult &hitTest, c
m_actionStop->setEnabled(isLoading());
}
int spellCheckActionCount = 0;
#ifdef USE_HUNSPELL
// Show spellcheck menu as the first
if (hitTest.isContentEditable() && !hitTest.isContentSelected()) {
mApp->speller()->populateContextMenu(menu, hitTest);
spellCheckActionCount = menu->actions().count();
}
#endif
if (!hitTest.linkUrl().isEmpty() && hitTest.linkUrl().scheme() != QLatin1String("javascript")) {
createLinkContextMenu(menu, hitTest);
}
@ -766,7 +780,7 @@ void WebView::createContextMenu(QMenu* menu, const QWebHitTestResult &hitTest, c
}
if (hitTest.isContentEditable()) {
if (menu->isEmpty()) {
if (menu->actions().count() == spellCheckActionCount) {
QMenu* pageMenu = page()->createStandardContextMenu();
int i = 0;
@ -939,7 +953,7 @@ void WebView::createSelectedTextContextMenu(QMenu* menu, const QWebHitTestResult
menu->addAction(QIcon::fromTheme("mail-message-new"), tr("Send text..."), this, SLOT(sendLinkByMail()))->setData(selectedText);
menu->addSeparator();
QString langCode = mApp->currentLanguage().left(2);
QString langCode = mApp->currentLanguageFile().left(2);
QUrl googleTranslateUrl = QUrl(QString("http://translate.google.com/#auto|%1|%2").arg(langCode, selectedText));
Action* gtwact = new Action(QIcon(":icons/sites/translate.png"), tr("Google Translate"));
gtwact->setData(googleTranslateUrl);