208 lines
5.2 KiB
C++
208 lines
5.2 KiB
C++
/******************************************************************************
|
|
* QSkinny - Copyright (C) 2016 Uwe Rathmann
|
|
* SPDX-License-Identifier: BSD-3-Clause
|
|
*****************************************************************************/
|
|
|
|
#include "QskHunspellTextPredictor.h"
|
|
|
|
#include <qlocale.h>
|
|
#include <qtimer.h>
|
|
#include <qfile.h>
|
|
#include <qdir.h>
|
|
#include <qdebug.h>
|
|
|
|
#include <hunspell/hunspell.h>
|
|
|
|
#if QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 )
|
|
|
|
#include <qtextcodec.h>
|
|
|
|
namespace
|
|
{
|
|
class StringConverter
|
|
{
|
|
public:
|
|
StringConverter( const QByteArray& encoding )
|
|
: m_codec( QTextCodec::codecForName( encoding ) )
|
|
{
|
|
}
|
|
|
|
inline QString fromHunspell( const char* text ) const
|
|
{
|
|
if ( m_codec )
|
|
return m_codec->toUnicode( text );
|
|
|
|
return QString::fromUtf8( text );
|
|
}
|
|
|
|
inline QByteArray toHunspell( const QString& text ) const
|
|
{
|
|
if ( m_codec )
|
|
return m_codec->fromUnicode( text );
|
|
|
|
return text.toUtf8();
|
|
}
|
|
private:
|
|
QTextCodec* m_codec;
|
|
};
|
|
}
|
|
|
|
#else
|
|
|
|
#include <qstringconverter.h>
|
|
|
|
namespace
|
|
{
|
|
class StringConverter
|
|
{
|
|
public:
|
|
StringConverter( const QByteArray& encoding )
|
|
: m_decoder( encoding )
|
|
, m_encoder( encoding )
|
|
{
|
|
}
|
|
|
|
inline QString fromHunspell( const char* text ) const
|
|
{
|
|
if ( m_decoder.isValid() )
|
|
return m_decoder.decode( text );
|
|
|
|
return QString::fromUtf8( text );
|
|
}
|
|
|
|
inline QByteArray toHunspell( const QString& text ) const
|
|
{
|
|
if ( m_encoder.isValid() )
|
|
return m_encoder.encode( text );
|
|
|
|
return text.toUtf8();
|
|
}
|
|
|
|
private:
|
|
mutable QStringDecoder m_decoder;
|
|
mutable QStringEncoder m_encoder;
|
|
};
|
|
}
|
|
|
|
#endif
|
|
|
|
class QskHunspellTextPredictor::PrivateData
|
|
{
|
|
public:
|
|
Hunhandle* hunspellHandle = nullptr;
|
|
QByteArray hunspellEncoding;
|
|
QStringList candidates;
|
|
QLocale locale;
|
|
};
|
|
|
|
QskHunspellTextPredictor::QskHunspellTextPredictor(
|
|
const QLocale& locale, QObject* object )
|
|
: Inherited( object )
|
|
, m_data( new PrivateData() )
|
|
{
|
|
m_data->locale = locale;
|
|
|
|
// make sure we call virtual functions:
|
|
QMetaObject::invokeMethod( this,
|
|
&QskHunspellTextPredictor::loadDictionaries, Qt::QueuedConnection );
|
|
}
|
|
|
|
QskHunspellTextPredictor::~QskHunspellTextPredictor()
|
|
{
|
|
Hunspell_destroy( m_data->hunspellHandle );
|
|
}
|
|
|
|
void QskHunspellTextPredictor::reset()
|
|
{
|
|
if ( !m_data->candidates.isEmpty() )
|
|
{
|
|
m_data->candidates.clear();
|
|
Q_EMIT predictionChanged( QString(), {} );
|
|
}
|
|
}
|
|
|
|
QPair< QString, QString > QskHunspellTextPredictor::affAndDicFile(
|
|
const QString& path, const QLocale& locale )
|
|
{
|
|
QString prefix = QStringLiteral( "%1/%2" ).arg( path, locale.name() );
|
|
QString affFile = prefix + QStringLiteral( ".aff" );
|
|
QString dicFile = prefix + QStringLiteral( ".dic" );
|
|
|
|
if( QFile::exists( affFile ) && QFile::exists( dicFile ) )
|
|
{
|
|
return qMakePair( affFile, dicFile );
|
|
}
|
|
else
|
|
{
|
|
return {};
|
|
}
|
|
}
|
|
|
|
void QskHunspellTextPredictor::loadDictionaries()
|
|
{
|
|
const auto userPaths = QString::fromUtf8( qgetenv( "QSK_HUNSPELL_PATH" ) );
|
|
|
|
auto paths = userPaths.split( QDir::listSeparator(), Qt::SkipEmptyParts );
|
|
|
|
#if !defined( Q_OS_WIN32 )
|
|
paths += QStringLiteral( "/usr/share/hunspell" );
|
|
paths += QStringLiteral( "/usr/share/myspell/dicts" );
|
|
#endif
|
|
|
|
for( const auto& path : paths )
|
|
{
|
|
auto files = affAndDicFile( path, m_data->locale );
|
|
|
|
if( !files.first.isEmpty() && !files.second.isEmpty() )
|
|
{
|
|
m_data->hunspellHandle = Hunspell_create( files.first.toUtf8(), files.second.toUtf8() );
|
|
m_data->hunspellEncoding = Hunspell_get_dic_encoding( m_data->hunspellHandle );
|
|
break;
|
|
}
|
|
}
|
|
|
|
if( !m_data->hunspellHandle )
|
|
{
|
|
qWarning() << "could not find Hunspell files for locale" << m_data->locale
|
|
<< "in the following directories:" << paths
|
|
<< ". Consider setting QSK_HUNSPELL_PATH to the directory "
|
|
<< "containing Hunspell .aff and .dic files.";
|
|
}
|
|
}
|
|
|
|
void QskHunspellTextPredictor::request( const QString& text )
|
|
{
|
|
if( !m_data->hunspellHandle )
|
|
{
|
|
Q_EMIT predictionChanged( text, {} );
|
|
return;
|
|
}
|
|
|
|
StringConverter converter( m_data->hunspellEncoding );
|
|
|
|
char** suggestions;
|
|
|
|
const int count = Hunspell_suggest( m_data->hunspellHandle,
|
|
&suggestions, converter.toHunspell( text ).constData() );
|
|
|
|
QStringList candidates;
|
|
candidates.reserve( count );
|
|
|
|
for ( int i = 0; i < count; i++ )
|
|
{
|
|
const auto suggestion = converter.fromHunspell( suggestions[ i ] );
|
|
|
|
if ( suggestion.startsWith( text ) )
|
|
candidates.prepend( suggestion );
|
|
else
|
|
candidates.append( suggestion );
|
|
}
|
|
|
|
Hunspell_free_list( m_data->hunspellHandle, &suggestions, count );
|
|
|
|
m_data->candidates = candidates;
|
|
Q_EMIT predictionChanged( text, m_data->candidates );
|
|
}
|
|
|
|
#include "moc_QskHunspellTextPredictor.cpp"
|