Main Page | Namespace List | Class Hierarchy | Alphabetical List | Class List | Directories | File List | Namespace Members | Class Members | File Members | Related Pages

qimpenmatch.cpp

Go to the documentation of this file.
00001 /**********************************************************************
00002 ** Copyright (C) 2000 Trolltech AS.  All rights reserved.
00003 **
00004 ** This file is part of Qtopia Environment.
00005 **
00006 ** This file may be distributed and/or modified under the terms of the
00007 ** GNU General Public License version 2 as published by the Free Software
00008 ** Foundation and appearing in the file LICENSE.GPL included in the
00009 ** packaging of this file.
00010 **
00011 ** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
00012 ** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
00013 **
00014 ** See http://www.trolltech.com/gpl/ for GPL licensing information.
00015 **
00016 ** Contact info@trolltech.com if any conditions of this licensing are
00017 ** not clear to you.
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 //#define DEBUG_QIMPEN
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 //       ml.first().penChar->penStrokes().count() == strokes.count() ) {
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         // See if the last stroke can match a new character
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         // Hmmm, a multi-stroke or a new character are both possible.
00134         // Bias the multi-stroke case.
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 //      odebug << "Word Matching: Clearing word" << oendl;
00233         wordChars.clear();
00234         wordMatches.clear();
00235         wordEntered = QString();
00236     } else if ( code == Qt::Key_Backspace ) {
00237         //odebug << "Word Matching: Handle backspace" << oendl;
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         // more leaniency if we don't have many matches
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         QListIterator<MatchWord> it( wordMatches);
00284         for ( ; it.current(); ++it ) {
00285             odebug << "Match word: " << it.current()->word << oendl;
00286         }
00287 */
00288     }
00289     //odebug << "Possibles: Good " << goodMatches << ", total " << wordMatches.count() << oendl;
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 }

Generated on Sat Nov 5 16:16:01 2005 for OPIE by  doxygen 1.4.2