00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021 #include "qimpenmatch.h"
00022
00023 #include <qpe/qdawg.h>
00024 #include <qpe/global.h>
00025
00026 #include <qapplication.h>
00027 #include <qtimer.h>
00028 #include <opie2/odebug.h>
00029
00030 #include <limits.h>
00031
00032 #define ERROR_THRESHOLD 200000
00033 #define LOOKAHEAD_ERROR 2500
00034
00035
00036 QIMPenMatch::QIMPenMatch( QObject *parent, const char *name )
00037 : QObject( parent, name )
00038 {
00039 strokes.setAutoDelete( TRUE );
00040 wordChars.setAutoDelete( TRUE );
00041 wordMatches.setAutoDelete( TRUE );
00042
00043 multiTimer = new QTimer( this );
00044 connect( multiTimer, SIGNAL(timeout()), this, SLOT(endMulti()) );
00045
00046 prevMatchChar = 0;
00047 prevMatchError = INT_MAX;
00048 charSet = 0;
00049 multiCharSet = 0;
00050 multiTimeout = 500;
00051 canErase = FALSE;
00052 doWordMatching = true;
00053 }
00054
00055 QIMPenMatch::~QIMPenMatch()
00056 {
00057 }
00058
00059 void QIMPenMatch::setCharSet( QIMPenCharSet *cs )
00060 {
00061 charSet = cs;
00062 }
00063
00064 void QIMPenMatch::beginStroke()
00065 {
00066 multiTimer->stop();
00067 }
00068
00069 void QIMPenMatch::strokeEntered( QIMPenStroke *st )
00070 {
00071 #ifdef DEBUG_QIMPEN
00072 odebug << "---------- new stroke -------------" << oendl;
00073 #endif
00074 strokes.append( new QIMPenStroke( *st ) );
00075
00076 QIMPenChar testChar;
00077 QIMPenStrokeIterator it(strokes);
00078 for ( ; it.current(); ++it ) {
00079 testChar.addStroke( it.current() );
00080 }
00081
00082 QIMPenCharMatchList ml;
00083 if ( strokes.count() > 1 && multiCharSet ) {
00084 #ifdef DEBUG_QIMPEN
00085 odebug << "Matching against multi set" << oendl;
00086 #endif
00087 ml = multiCharSet->match( &testChar );
00088 } else {
00089 #ifdef DEBUG_QIMPEN
00090 odebug << "Matching against single set" << oendl;
00091 #endif
00092 ml = charSet->match( &testChar );
00093 }
00094
00095 processMatches( ml );
00096 }
00097
00098 void QIMPenMatch::processMatches( QIMPenCharMatchList &ml )
00099 {
00100 #ifdef DEBUG_QIMPEN
00101 odebug << "Entering strokes.count() = " << strokes.count() << oendl;
00102 #endif
00103 QIMPenCharMatch candidate1 = { INT_MAX, 0 };
00104 QIMPenCharMatch candidate2 = { INT_MAX, 0 };
00105 QIMPenCharMatchList ml2;
00106
00107 if ( ml.count() ) {
00108
00109 candidate1 = ml.first();
00110 #ifdef DEBUG_QIMPEN
00111 odebug << "Candidate1 = " << candidate1.penChar->character() << oendl;
00112 #endif
00113 }
00114
00115 if ( strokes.count() > 1 ) {
00116
00117 QIMPenChar testChar;
00118 QIMPenStroke *st = strokes.at(strokes.count()-1);
00119 testChar.addStroke( st );
00120 ml2 = charSet->match( &testChar );
00121 if ( ml2.count() ) {
00122 candidate2 = ml2.first();
00123 #ifdef DEBUG_QIMPEN
00124 odebug << "Candidate2 = " << candidate2.penChar->character() << oendl;
00125 #endif
00126 }
00127 }
00128
00129 bool eraseLast = FALSE;
00130 bool output = TRUE;
00131
00132 if ( candidate1.penChar && candidate2.penChar ) {
00133
00134
00135 if ( QMAX(candidate2.error, prevMatchError)*3 < candidate1.error ) {
00136 int i = strokes.count()-1;
00137 while ( i-- ) {
00138 strokes.removeFirst();
00139 emit removeStroke();
00140 }
00141 prevMatchChar = candidate2.penChar;
00142 prevMatchError = candidate2.error;
00143 multiCharSet = charSet;
00144 ml = ml2;
00145 #ifdef DEBUG_QIMPEN
00146 odebug << "** Using Candidate2" << oendl;
00147 #endif
00148 } else {
00149 if ( (prevMatchChar->character() >> 16) != Qt::Key_Backspace &&
00150 (prevMatchChar->character() >> 16) < QIMPenChar::ModeBase )
00151 eraseLast = TRUE;
00152 prevMatchChar = candidate1.penChar;
00153 prevMatchError = candidate1.error;
00154 #ifdef DEBUG_QIMPEN
00155 odebug << "** Using Candidate1, with erase" << oendl;
00156 #endif
00157 }
00158 } else if ( candidate1.penChar ) {
00159 if ( strokes.count() != 1 )
00160 eraseLast = TRUE;
00161 else
00162 multiCharSet = charSet;
00163 prevMatchChar = candidate1.penChar;
00164 prevMatchError = candidate1.error;
00165 #ifdef DEBUG_QIMPEN
00166 odebug << "** Using Candidate1" << oendl;
00167 #endif
00168 } else if ( candidate2.penChar ) {
00169 int i = strokes.count()-1;
00170 while ( i-- ) {
00171 strokes.removeFirst();
00172 emit removeStroke();
00173 }
00174 prevMatchChar = candidate2.penChar;
00175 prevMatchError = candidate2.error;
00176 multiCharSet = charSet;
00177 ml = ml2;
00178 #ifdef DEBUG_QIMPEN
00179 odebug << "** Using Candidate2" << oendl;
00180 #endif
00181 } else {
00182 if ( !ml.count() ) {
00183 #ifdef DEBUG_QIMPEN
00184 odebug << "** Failed" << oendl;
00185 #endif
00186 canErase = FALSE;
00187 } else {
00188 #ifdef DEBUG_QIMPEN
00189 odebug << "Need more strokes" << oendl;
00190 #endif
00191 if ( strokes.count() == 1 )
00192 canErase = FALSE;
00193 multiCharSet = charSet;
00194 }
00195 output = FALSE;
00196 emit noMatch();
00197 }
00198
00199 if ( eraseLast && canErase ) {
00200 #ifdef DEBUG_QIMPEN
00201 odebug << "deleting last" << oendl;
00202 #endif
00203 emit erase();
00204 wordChars.removeLast();
00205 wordEntered.truncate( wordEntered.length() - 1 );
00206 }
00207
00208 if ( output ) {
00209 emit matchedCharacters( ml );
00210 uint code = prevMatchChar->character() >> 16;
00211 if ( code < QIMPenChar::ModeBase ) {
00212 updateWordMatch( ml );
00213 emit keypress( prevMatchChar->character() );
00214 }
00215 canErase = TRUE;
00216 }
00217
00218 if ( strokes.count() )
00219 multiTimer->start( multiTimeout, TRUE );
00220 }
00221
00222 void QIMPenMatch::updateWordMatch( QIMPenCharMatchList &ml )
00223 {
00224 if ( !ml.count() || !doWordMatching )
00225 return;
00226 int ch = ml.first().penChar->character();
00227 QChar qch( ch );
00228 int code = ch >> 16;
00229 if ( qch.isPunct() || qch.isSpace() ||
00230 code == Qt::Key_Enter || code == Qt::Key_Return ||
00231 code == Qt::Key_Tab || code == Qt::Key_Escape ) {
00232
00233 wordChars.clear();
00234 wordMatches.clear();
00235 wordEntered = QString();
00236 } else if ( code == Qt::Key_Backspace ) {
00237
00238 wordChars.removeLast();
00239 wordEntered.truncate( wordEntered.length() - 1 );
00240 matchWords();
00241 } else {
00242 QIMPenChar *matchCh;
00243
00244 wordChars.append( new QIMPenCharMatchList() );
00245 wordEntered += ml.first().penChar->character();
00246
00247 QIMPenCharMatchList::Iterator it;
00248 for ( it = ml.begin(); it != ml.end(); ++it ) {
00249 matchCh = (*it).penChar;
00250
00251 if ( matchCh->penStrokes().count() == strokes.count() ) {
00252 QChar ch(matchCh->character());
00253 if ( !ch.isPunct() && !ch.isSpace() ) {
00254 wordChars.last()->append( QIMPenCharMatch( (*it) ) );
00255 }
00256 }
00257 }
00258 matchWords();
00259 }
00260 if ( !wordMatches.count() || wordMatches.getFirst()->word != wordEntered )
00261 wordMatches.prepend( new MatchWord( wordEntered, 0 ) );
00262 emit matchedWords( wordMatches );
00263 }
00264
00265 void QIMPenMatch::matchWords()
00266 {
00267 if ( wordEntered.length() > 0 ) {
00268
00269 if ( badMatches < 200 )
00270 errorThreshold += (200 - badMatches) * 100;
00271 } else
00272 errorThreshold = ERROR_THRESHOLD;
00273 wordMatches.clear();
00274 goodMatches = 0;
00275 badMatches = 0;
00276 if ( wordChars.count() > 0 ) {
00277 maxGuess = (int)wordChars.count() * 2;
00278 if ( maxGuess < 3 )
00279 maxGuess = 3;
00280 QString str;
00281 scanDict( Global::fixedDawg().root(), 0, str, 0 );
00282
00283
00284
00285
00286
00287
00288 }
00289
00290 wordMatches.sort();
00291 }
00292
00293 void QIMPenMatch::scanDict( const QDawg::Node* n, int ipos, const QString& str, int error )
00294 {
00295 if ( !n )
00296 return;
00297 if ( error / (ipos+1) > errorThreshold )
00298 return;
00299
00300 while (n) {
00301 if ( goodMatches > 20 )
00302 break;
00303 if ( ipos < (int)wordChars.count() ) {
00304 int i;
00305 QChar testCh = QChar(n->letter());
00306 QIMPenCharMatchList::Iterator it;
00307 for ( i = 0, it = wordChars.at(ipos)->begin();
00308 it != wordChars.at(ipos)->end() && i < 8; ++it, i++ ) {
00309 QChar ch( (*it).penChar->character() );
00310 if ( ch == testCh || ( !ipos && ch.lower() == testCh.lower() ) ) {
00311 int newerr = error + (*it).error;
00312 if ( testCh.category() == QChar::Letter_Uppercase )
00313 ch = testCh;
00314 QString newstr( str + ch );
00315 if ( n->isWord() && ipos == (int)wordChars.count() - 1 ) {
00316 wordMatches.append( new MatchWord( newstr, newerr ) );
00317 goodMatches++;
00318 }
00319 scanDict( n->jump(), ipos+1, newstr, newerr );
00320 }
00321 }
00322 } else if ( badMatches < 200 && ipos < maxGuess ) {
00323 int d = ipos - wordChars.count();
00324 int newerr = error + ERROR_THRESHOLD + LOOKAHEAD_ERROR*d;
00325 QString newstr( str + n->letter() );
00326 if ( n->isWord() ) {
00327 wordMatches.append( new MatchWord( newstr, newerr ) );
00328 badMatches++;
00329 }
00330 scanDict( n->jump(), ipos+1, newstr, newerr );
00331 }
00332 n = n->next();
00333 }
00334 }
00335
00336 void QIMPenMatch::backspace()
00337 {
00338 wordChars.removeLast();
00339 wordEntered.truncate( wordEntered.length() - 1 );
00340 matchWords();
00341 if ( !wordMatches.count() || wordMatches.getFirst()->word != wordEntered )
00342 wordMatches.prepend( new MatchWord( wordEntered, 0 ) );
00343 emit matchedWords( wordMatches );
00344 if ( wordEntered.length() )
00345 canErase = TRUE;
00346 }
00347
00348 void QIMPenMatch::endMulti()
00349 {
00350 int i = strokes.count();
00351 while ( i-- )
00352 emit removeStroke();
00353 strokes.clear();
00354 multiCharSet = 0;
00355 }
00356
00357 void QIMPenMatch::resetState()
00358 {
00359 if ( !wordEntered.isEmpty() ) {
00360 wordChars.clear();
00361 wordMatches.clear();
00362 wordEntered = QString();
00363 emit matchedWords( wordMatches );
00364 canErase = FALSE;
00365 }
00366 }