qskinny/src/inputpanel/QskHunspellTextPredictor.cpp

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"