QSkinny 0.8.0
C++/Qt UI toolkit
Loading...
Searching...
No Matches
QskHunspellTextPredictor.cpp
1/******************************************************************************
2 * QSkinny - Copyright (C) The authors
3 * SPDX-License-Identifier: BSD-3-Clause
4 *****************************************************************************/
5
6#include "QskHunspellTextPredictor.h"
7
8#include <qlocale.h>
9#include <qtimer.h>
10#include <qfile.h>
11#include <qdir.h>
12#include <qdebug.h>
13
14#include <hunspell/hunspell.h>
15
16#if QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 )
17
18#include <qtextcodec.h>
19
20namespace
21{
22 class StringConverter
23 {
24 public:
25 StringConverter( const QByteArray& encoding )
26 : m_codec( QTextCodec::codecForName( encoding ) )
27 {
28 }
29
30 inline QString fromHunspell( const char* text ) const
31 {
32 if ( m_codec )
33 return m_codec->toUnicode( text );
34
35 return QString::fromUtf8( text );
36 }
37
38 inline QByteArray toHunspell( const QString& text ) const
39 {
40 if ( m_codec )
41 return m_codec->fromUnicode( text );
42
43 return text.toUtf8();
44 }
45 private:
46 QTextCodec* m_codec;
47 };
48}
49
50#else
51
52#include <qstringconverter.h>
53
54namespace
55{
56 class StringConverter
57 {
58 public:
59 StringConverter( const QByteArray& encoding )
60 : m_decoder( encoding )
61 , m_encoder( encoding )
62 {
63 }
64
65 inline QString fromHunspell( const char* text ) const
66 {
67 if ( m_decoder.isValid() )
68 return m_decoder.decode( text );
69
70 return QString::fromUtf8( text );
71 }
72
73 inline QByteArray toHunspell( const QString& text ) const
74 {
75 if ( m_encoder.isValid() )
76 return m_encoder.encode( text );
77
78 return text.toUtf8();
79 }
80
81 private:
82 mutable QStringDecoder m_decoder;
83 mutable QStringEncoder m_encoder;
84 };
85}
86
87#endif
88
89class QskHunspellTextPredictor::PrivateData
90{
91 public:
92 Hunhandle* hunspellHandle = nullptr;
93 QByteArray hunspellEncoding;
94 QStringList candidates;
95 QLocale locale;
96};
97
98QskHunspellTextPredictor::QskHunspellTextPredictor(
99 const QLocale& locale, QObject* object )
100 : Inherited( object )
101 , m_data( new PrivateData() )
102{
103 m_data->locale = locale;
104
105 // make sure we call virtual functions:
106 QMetaObject::invokeMethod( this,
107 &QskHunspellTextPredictor::loadDictionaries, Qt::QueuedConnection );
108}
109
110QskHunspellTextPredictor::~QskHunspellTextPredictor()
111{
112 Hunspell_destroy( m_data->hunspellHandle );
113}
114
115void QskHunspellTextPredictor::reset()
116{
117 if ( !m_data->candidates.isEmpty() )
118 {
119 m_data->candidates.clear();
120 Q_EMIT predictionChanged( QString(), {} );
121 }
122}
123
124QPair< QString, QString > QskHunspellTextPredictor::affAndDicFile(
125 const QString& path, const QLocale& locale )
126{
127 QString prefix = QStringLiteral( "%1/%2" ).arg( path, locale.name() );
128 QString affFile = prefix + QStringLiteral( ".aff" );
129 QString dicFile = prefix + QStringLiteral( ".dic" );
130
131 if( QFile::exists( affFile ) && QFile::exists( dicFile ) )
132 {
133 return qMakePair( affFile, dicFile );
134 }
135 else
136 {
137 return {};
138 }
139}
140
141void QskHunspellTextPredictor::loadDictionaries()
142{
143 const auto userPaths = QString::fromUtf8( qgetenv( "QSK_HUNSPELL_PATH" ) );
144
145 auto paths = userPaths.split( QDir::listSeparator(), Qt::SkipEmptyParts );
146
147#if !defined( Q_OS_WIN32 )
148 paths += QStringLiteral( "/usr/share/hunspell" );
149 paths += QStringLiteral( "/usr/share/myspell/dicts" );
150#endif
151
152 for( const auto& path : paths )
153 {
154 auto files = affAndDicFile( path, m_data->locale );
155
156 if( !files.first.isEmpty() && !files.second.isEmpty() )
157 {
158 m_data->hunspellHandle = Hunspell_create( files.first.toUtf8(), files.second.toUtf8() );
159 m_data->hunspellEncoding = Hunspell_get_dic_encoding( m_data->hunspellHandle );
160 break;
161 }
162 }
163
164 if( !m_data->hunspellHandle )
165 {
166 qWarning() << "could not find Hunspell files for locale" << m_data->locale
167 << "in the following directories:" << paths
168 << ". Consider setting QSK_HUNSPELL_PATH to the directory "
169 << "containing Hunspell .aff and .dic files.";
170 }
171}
172
173void QskHunspellTextPredictor::request( const QString& text )
174{
175 if( !m_data->hunspellHandle )
176 {
177 Q_EMIT predictionChanged( text, {} );
178 return;
179 }
180
181 StringConverter converter( m_data->hunspellEncoding );
182
183 char** suggestions;
184
185 const int count = Hunspell_suggest( m_data->hunspellHandle,
186 &suggestions, converter.toHunspell( text ).constData() );
187
188 QStringList candidates;
189 candidates.reserve( count );
190
191 for ( int i = 0; i < count; i++ )
192 {
193 const auto suggestion = converter.fromHunspell( suggestions[ i ] );
194
195 if ( suggestion.startsWith( text ) )
196 candidates.prepend( suggestion );
197 else
198 candidates.append( suggestion );
199 }
200
201 Hunspell_free_list( m_data->hunspellHandle, &suggestions, count );
202
203 m_data->candidates = candidates;
204 Q_EMIT predictionChanged( text, m_data->candidates );
205}
206
207#include "moc_QskHunspellTextPredictor.cpp"