summaryrefslogtreecommitdiff
path: root/inputmethods/handwriting/qimpenmatch.cpp
Unidiff
Diffstat (limited to 'inputmethods/handwriting/qimpenmatch.cpp') (more/less context) (ignore whitespace changes)
-rw-r--r--inputmethods/handwriting/qimpenmatch.cpp365
1 files changed, 365 insertions, 0 deletions
diff --git a/inputmethods/handwriting/qimpenmatch.cpp b/inputmethods/handwriting/qimpenmatch.cpp
new file mode 100644
index 0000000..0d3e25a
--- a/dev/null
+++ b/inputmethods/handwriting/qimpenmatch.cpp
@@ -0,0 +1,365 @@
1/**********************************************************************
2** Copyright (C) 2000 Trolltech AS. All rights reserved.
3**
4** This file is part of Qtopia Environment.
5**
6** This file may be distributed and/or modified under the terms of the
7** GNU General Public License version 2 as published by the Free Software
8** Foundation and appearing in the file LICENSE.GPL included in the
9** packaging of this file.
10**
11** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
12** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
13**
14** See http://www.trolltech.com/gpl/ for GPL licensing information.
15**
16** Contact info@trolltech.com if any conditions of this licensing are
17** not clear to you.
18**
19**********************************************************************/
20
21#include "qimpenmatch.h"
22
23#include <qpe/qdawg.h>
24#include <qpe/global.h>
25
26#include <qapplication.h>
27#include <qtimer.h>
28
29#include <limits.h>
30
31 #define ERROR_THRESHOLD 200000
32 #define LOOKAHEAD_ERROR 2500
33//#define DEBUG_QIMPEN
34
35QIMPenMatch::QIMPenMatch( QObject *parent, const char *name )
36 : QObject( parent, name )
37{
38 strokes.setAutoDelete( TRUE );
39 wordChars.setAutoDelete( TRUE );
40 wordMatches.setAutoDelete( TRUE );
41
42 multiTimer = new QTimer( this );
43 connect( multiTimer, SIGNAL(timeout()), this, SLOT(endMulti()) );
44
45 prevMatchChar = 0;
46 prevMatchError = INT_MAX;
47 charSet = 0;
48 multiCharSet = 0;
49 multiTimeout = 500;
50 canErase = FALSE;
51 doWordMatching = true;
52}
53
54QIMPenMatch::~QIMPenMatch()
55{
56}
57
58void QIMPenMatch::setCharSet( QIMPenCharSet *cs )
59{
60 charSet = cs;
61}
62
63void QIMPenMatch::beginStroke()
64{
65 multiTimer->stop();
66}
67
68void QIMPenMatch::strokeEntered( QIMPenStroke *st )
69{
70#ifdef DEBUG_QIMPEN
71 qDebug( "---------- new stroke -------------" );
72#endif
73 strokes.append( new QIMPenStroke( *st ) );
74
75 QIMPenChar testChar;
76 QIMPenStrokeIterator it(strokes);
77 for ( ; it.current(); ++it ) {
78 testChar.addStroke( it.current() );
79 }
80
81 QIMPenCharMatchList ml;
82 if ( strokes.count() > 1 && multiCharSet ) {
83#ifdef DEBUG_QIMPEN
84 qDebug( "Matching against multi set" );
85#endif
86 ml = multiCharSet->match( &testChar );
87 } else {
88#ifdef DEBUG_QIMPEN
89 qDebug( "Matching against single set" );
90#endif
91 ml = charSet->match( &testChar );
92 }
93
94 processMatches( ml );
95}
96
97void QIMPenMatch::processMatches( QIMPenCharMatchList &ml )
98{
99#ifdef DEBUG_QIMPEN
100 qDebug( "Entering strokes.count() = %d", strokes.count() );
101#endif
102 QIMPenCharMatch candidate1 = { INT_MAX, 0 };
103 QIMPenCharMatch candidate2 = { INT_MAX, 0 };
104 QIMPenCharMatchList ml2;
105
106 if ( ml.count() ) {//&&
107 // ml.first().penChar->penStrokes().count() == strokes.count() ) {
108 candidate1 = ml.first();
109#ifdef DEBUG_QIMPEN
110 qDebug( QString("Candidate1 = %1").arg(QChar(candidate1.penChar->character())) );
111#endif
112 }
113
114 if ( strokes.count() > 1 ) {
115 // See if the last stroke can match a new character
116 QIMPenChar testChar;
117 QIMPenStroke *st = strokes.at(strokes.count()-1);
118 testChar.addStroke( st );
119 ml2 = charSet->match( &testChar );
120 if ( ml2.count() ) {
121 candidate2 = ml2.first();
122#ifdef DEBUG_QIMPEN
123 qDebug( QString("Candidate2 = %1").arg(QChar(candidate2.penChar->character())) );
124#endif
125 }
126 }
127
128 bool eraseLast = FALSE;
129 bool output = TRUE;
130
131 if ( candidate1.penChar && candidate2.penChar ) {
132 // Hmmm, a multi-stroke or a new character are both possible.
133 // Bias the multi-stroke case.
134 if ( QMAX(candidate2.error, prevMatchError)*3 < candidate1.error ) {
135 int i = strokes.count()-1;
136 while ( i-- ) {
137 strokes.removeFirst();
138 emit removeStroke();
139 }
140 prevMatchChar = candidate2.penChar;
141 prevMatchError = candidate2.error;
142 multiCharSet = charSet;
143 ml = ml2;
144#ifdef DEBUG_QIMPEN
145 qDebug( "** Using Candidate2" );
146#endif
147 } else {
148 if ( (prevMatchChar->character() >> 16) != Qt::Key_Backspace &&
149 (prevMatchChar->character() >> 16) < QIMPenChar::ModeBase )
150 eraseLast = TRUE;
151 prevMatchChar = candidate1.penChar;
152 prevMatchError = candidate1.error;
153#ifdef DEBUG_QIMPEN
154 qDebug( "** Using Candidate1, with erase" );
155#endif
156 }
157 } else if ( candidate1.penChar ) {
158 if ( strokes.count() != 1 )
159 eraseLast = TRUE;
160 else
161 multiCharSet = charSet;
162 prevMatchChar = candidate1.penChar;
163 prevMatchError = candidate1.error;
164#ifdef DEBUG_QIMPEN
165 qDebug( "** Using Candidate1" );
166#endif
167 } else if ( candidate2.penChar ) {
168 int i = strokes.count()-1;
169 while ( i-- ) {
170 strokes.removeFirst();
171 emit removeStroke();
172 }
173 prevMatchChar = candidate2.penChar;
174 prevMatchError = candidate2.error;
175 multiCharSet = charSet;
176 ml = ml2;
177#ifdef DEBUG_QIMPEN
178 qDebug( "** Using Candidate2" );
179#endif
180 } else {
181 if ( !ml.count() ) {
182#ifdef DEBUG_QIMPEN
183 qDebug( "** Failed" );
184#endif
185 canErase = FALSE;
186 } else {
187#ifdef DEBUG_QIMPEN
188 qDebug( "Need more strokes" );
189#endif
190 if ( strokes.count() == 1 )
191 canErase = FALSE;
192 multiCharSet = charSet;
193 }
194 output = FALSE;
195 emit noMatch();
196 }
197
198 if ( eraseLast && canErase ) {
199#ifdef DEBUG_QIMPEN
200 qDebug( "deleting last" );
201#endif
202 emit erase();
203 wordChars.removeLast();
204 wordEntered.truncate( wordEntered.length() - 1 );
205 }
206
207 if ( output ) {
208 emit matchedCharacters( ml );
209 uint code = prevMatchChar->character() >> 16;
210 if ( code < QIMPenChar::ModeBase ) {
211 updateWordMatch( ml );
212 emit keypress( prevMatchChar->character() );
213 }
214 canErase = TRUE;
215 }
216
217 if ( strokes.count() )
218 multiTimer->start( multiTimeout, TRUE );
219}
220
221void QIMPenMatch::updateWordMatch( QIMPenCharMatchList &ml )
222{
223 if ( !ml.count() || !doWordMatching )
224 return;
225 int ch = ml.first().penChar->character();
226 QChar qch( ch );
227 int code = ch >> 16;
228 if ( qch.isPunct() || qch.isSpace() ||
229 code == Qt::Key_Enter || code == Qt::Key_Return ||
230 code == Qt::Key_Tab || code == Qt::Key_Escape ) {
231 //qDebug( "Word Matching: Clearing word" );
232 wordChars.clear();
233 wordMatches.clear();
234 wordEntered = QString();
235 } else if ( code == Qt::Key_Backspace ) {
236 //qDebug( "Word Matching: Handle backspace" );
237 wordChars.removeLast();
238 wordEntered.truncate( wordEntered.length() - 1 );
239 matchWords();
240 } else {
241 QIMPenChar *matchCh;
242
243 wordChars.append( new QIMPenCharMatchList() );
244 wordEntered += ml.first().penChar->character();
245
246 QIMPenCharMatchList::Iterator it;
247 for ( it = ml.begin(); it != ml.end(); ++it ) {
248 matchCh = (*it).penChar;
249
250 if ( matchCh->penStrokes().count() == strokes.count() ) {
251 QChar ch(matchCh->character());
252 if ( !ch.isPunct() && !ch.isSpace() ) {
253 wordChars.last()->append( QIMPenCharMatch( (*it) ) );
254 }
255 }
256 }
257 matchWords();
258 }
259 if ( !wordMatches.count() || wordMatches.getFirst()->word != wordEntered )
260 wordMatches.prepend( new MatchWord( wordEntered, 0 ) );
261 emit matchedWords( wordMatches );
262}
263
264void QIMPenMatch::matchWords()
265{
266 if ( wordEntered.length() > 0 ) {
267 // more leaniency if we don't have many matches
268 if ( badMatches < 200 )
269 errorThreshold += (200 - badMatches) * 100;
270 } else
271 errorThreshold = ERROR_THRESHOLD;
272 wordMatches.clear();
273 goodMatches = 0;
274 badMatches = 0;
275 if ( wordChars.count() > 0 ) {
276 maxGuess = (int)wordChars.count() * 2;
277 if ( maxGuess < 3 )
278 maxGuess = 3;
279 QString str;
280 scanDict( Global::fixedDawg().root(), 0, str, 0 );
281/*
282 QListIterator<MatchWord> it( wordMatches);
283 for ( ; it.current(); ++it ) {
284 qDebug( QString("Match word: %1").arg(it.current()->word) );
285 }
286*/
287 }
288 //qDebug( "Possibles: Good %d, total %d", goodMatches, wordMatches.count() );
289 wordMatches.sort();
290}
291
292void QIMPenMatch::scanDict( const QDawg::Node* n, int ipos, const QString& str, int error )
293{
294 if ( !n )
295 return;
296 if ( error / (ipos+1) > errorThreshold )
297 return;
298
299 while (n) {
300 if ( goodMatches > 20 )
301 break;
302 if ( ipos < (int)wordChars.count() ) {
303 int i;
304 QChar testCh = QChar(n->letter());
305 QIMPenCharMatchList::Iterator it;
306 for ( i = 0, it = wordChars.at(ipos)->begin();
307 it != wordChars.at(ipos)->end() && i < 8; ++it, i++ ) {
308 QChar ch( (*it).penChar->character() );
309 if ( ch == testCh || ( !ipos && ch.lower() == testCh.lower() ) ) {
310 int newerr = error + (*it).error;
311 if ( testCh.category() == QChar::Letter_Uppercase )
312 ch = testCh;
313 QString newstr( str + ch );
314 if ( n->isWord() && ipos == (int)wordChars.count() - 1 ) {
315 wordMatches.append( new MatchWord( newstr, newerr ) );
316 goodMatches++;
317 }
318 scanDict( n->jump(), ipos+1, newstr, newerr );
319 }
320 }
321 } else if ( badMatches < 200 && ipos < maxGuess ) {
322 int d = ipos - wordChars.count();
323 int newerr = error + ERROR_THRESHOLD + LOOKAHEAD_ERROR*d;
324 QString newstr( str + n->letter() );
325 if ( n->isWord() ) {
326 wordMatches.append( new MatchWord( newstr, newerr ) );
327 badMatches++;
328 }
329 scanDict( n->jump(), ipos+1, newstr, newerr );
330 }
331 n = n->next();
332 }
333}
334
335void QIMPenMatch::backspace()
336{
337 wordChars.removeLast();
338 wordEntered.truncate( wordEntered.length() - 1 );
339 matchWords();
340 if ( !wordMatches.count() || wordMatches.getFirst()->word != wordEntered )
341 wordMatches.prepend( new MatchWord( wordEntered, 0 ) );
342 emit matchedWords( wordMatches );
343 if ( wordEntered.length() )
344 canErase = TRUE;
345}
346
347void QIMPenMatch::endMulti()
348{
349 int i = strokes.count();
350 while ( i-- )
351 emit removeStroke();
352 strokes.clear();
353 multiCharSet = 0;
354}
355
356void QIMPenMatch::resetState()
357{
358 if ( !wordEntered.isEmpty() ) {
359 wordChars.clear();
360 wordMatches.clear();
361 wordEntered = QString();
362 emit matchedWords( wordMatches );
363 canErase = FALSE;
364 }
365}