-rw-r--r-- | inputmethods/handwriting/qimpenchar.cpp | 9 | ||||
-rw-r--r-- | inputmethods/handwriting/qimpeninput.cpp | 24 | ||||
-rw-r--r-- | inputmethods/handwriting/qimpenmatch.cpp | 35 | ||||
-rw-r--r-- | inputmethods/handwriting/qimpenstroke.cpp | 15 | ||||
-rw-r--r-- | inputmethods/handwriting/qimpenwordpick.cpp | 2 |
5 files changed, 45 insertions, 40 deletions
diff --git a/inputmethods/handwriting/qimpenchar.cpp b/inputmethods/handwriting/qimpenchar.cpp index 929f370..db5d135 100644 --- a/inputmethods/handwriting/qimpenchar.cpp +++ b/inputmethods/handwriting/qimpenchar.cpp @@ -1,511 +1,512 @@ /********************************************************************** ** Copyright (C) 2000 Trolltech AS. All rights reserved. ** ** This file is part of Qtopia Environment. ** ** This file may be distributed and/or modified under the terms of the ** GNU General Public License version 2 as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL included in the ** packaging of this file. ** ** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE ** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. ** ** See http://www.trolltech.com/gpl/ for GPL licensing information. ** ** Contact info@trolltech.com if any conditions of this licensing are ** not clear to you. ** **********************************************************************/ #include <qfile.h> #include <qtl.h> #include <math.h> #include <limits.h> #include <errno.h> #include <qdatastream.h> #include "qimpencombining.h" #include "qimpenchar.h" +#include "opie2/odebug.h" #define QIMPEN_MATCH_THRESHOLD 200000 const QIMPenSpecialKeys qimpen_specialKeys[] = { { Qt::Key_Escape, "[Esc]" }, { Qt::Key_Tab, "[Tab]" }, { Qt::Key_Backspace, "[BackSpace]" }, { Qt::Key_Return, "[Return]" }, { QIMPenChar::Caps, "[Uppercase]" }, { QIMPenChar::CapsLock, "[Caps Lock]" }, { QIMPenChar::Shortcut, "[Shortcut]" }, { QIMPenChar::Punctuation, "[Punctuation]" }, { QIMPenChar::Symbol, "[Symbol]" }, { QIMPenChar::Extended, "[Extended]" }, { Qt::Key_unknown, 0 } }; /*! \class QIMPenChar qimpenchar.h Handles a single character. Can calculate closeness of match to another character. */ QIMPenChar::QIMPenChar() { flags = 0; strokes.setAutoDelete( TRUE ); } QIMPenChar::QIMPenChar( const QIMPenChar &chr ) { strokes.setAutoDelete( TRUE ); ch = chr.ch; flags = chr.flags; d = chr.d; QIMPenStrokeIterator it( chr.strokes ); while ( it.current() ) { strokes.append( new QIMPenStroke( *it.current() ) ); ++it; } } QIMPenChar &QIMPenChar::operator=( const QIMPenChar &chr ) { strokes.clear(); ch = chr.ch; flags = chr.flags; d = chr.d; QIMPenStrokeIterator it( chr.strokes ); while ( it.current() ) { strokes.append( new QIMPenStroke( *it.current() ) ); ++it; } return *this; } QString QIMPenChar::name() const { QString n; if ( (ch & 0x0000FFFF) == 0 ) { int code = ch >> 16; for ( int i = 0; qimpen_specialKeys[i].code != Qt::Key_unknown; i++ ) { if ( qimpen_specialKeys[i].code == code ) { n = qimpen_specialKeys[i].name; break; } } } else { n = QChar( ch & 0x0000FFFF ); } return n; } void QIMPenChar::clear() { ch = 0; flags = 0; d = QString::null; strokes.clear(); } unsigned int QIMPenChar::strokeLength( int s ) const { QIMPenStrokeIterator it( strokes ); while ( it.current() && s ) { ++it; --s; } if ( it.current() ) return it.current()->length(); return 0; } /*! Add a stroke to the character */ void QIMPenChar::addStroke( QIMPenStroke *st ) { QIMPenStroke *stroke = new QIMPenStroke( *st ); strokes.append( stroke ); } /*! Return an indicator of the closeness of this character to \a pen. Lower value is better. */ int QIMPenChar::match( QIMPenChar *pen ) { /* if ( strokes.count() > pen->strokes.count() ) return INT_MAX; */ int err = 0; int maxErr = 0; int diff = 0; QIMPenStrokeIterator it1( strokes ); QIMPenStrokeIterator it2( pen->strokes ); err = it1.current()->match( it2.current() ); if ( err > maxErr ) maxErr = err; ++it1; ++it2; while ( err < 400000 && it1.current() && it2.current() ) { QPoint p1 = it1.current()->boundingRect().center() - strokes.getFirst()->boundingRect().center(); QPoint p2 = it2.current()->boundingRect().center() - pen->strokes.getFirst()->boundingRect().center(); int xdiff = QABS( p1.x() - p2.x() ) - 6; int ydiff = QABS( p1.y() - p2.y() ) - 5; if ( xdiff < 0 ) xdiff = 0; if ( ydiff < 0 ) ydiff = 0; if ( xdiff > 10 || ydiff > 10 ) { // not a chance #ifdef DEBUG_QIMPEN - qDebug( "char %c, stroke starting pt diff excessive", pen->ch ); + odebug << "char " << pen->ch <<", stroke starting pt diff excessive" << oendl; #endif return INT_MAX; } diff += xdiff*xdiff + ydiff*ydiff; err = it1.current()->match( it2.current() ); if ( err > maxErr ) maxErr = err; ++it1; ++it2; } maxErr += diff * diff * 6; // magic weighting :) #ifdef DEBUG_QIMPEN - qDebug( "char: %c, maxErr %d, diff %d, (%d)", pen->ch, maxErr, diff, strokes.count() ); + odebug << "char: " << pen->ch << ", maxErr " << maxErr << ", diff " << diff << ", " << strokes.count() << oendl; #endif return maxErr; } /*! Return the bounding rect of this character. It may have sides with negative coords since its origin is where the user started drawing the character. */ QRect QIMPenChar::boundingRect() { QRect br; QIMPenStroke *st = strokes.first(); while ( st ) { br |= st->boundingRect(); st = strokes.next(); } return br; } /*! Write the character's data to the stream. */ QDataStream &operator<< (QDataStream &s, const QIMPenChar &ws) { s << ws.ch; s << ws.flags; if ( ws.flags & QIMPenChar::Data ) s << ws.d; s << ws.strokes.count(); QIMPenStrokeIterator it( ws.strokes ); while ( it.current() ) { s << *it.current(); ++it; } return s; } /*! Read the character's data from the stream. */ QDataStream &operator>> (QDataStream &s, QIMPenChar &ws) { s >> ws.ch; s >> ws.flags; if ( ws.flags & QIMPenChar::Data ) s >> ws.d; unsigned size; s >> size; for ( unsigned i = 0; i < size; i++ ) { QIMPenStroke *st = new QIMPenStroke(); s >> *st; ws.strokes.append( st ); } return s; } //=========================================================================== bool QIMPenCharMatch::operator>( const QIMPenCharMatch &m ) { return error > m.error; } bool QIMPenCharMatch::operator<( const QIMPenCharMatch &m ) { return error < m.error; } bool QIMPenCharMatch::operator<=( const QIMPenCharMatch &m ) { return error <= m.error; } //=========================================================================== /*! \class QIMPenCharSet qimpenchar.h Maintains a set of related characters. */ QIMPenCharSet::QIMPenCharSet() { chars.setAutoDelete( TRUE ); desc = "Unnamed"; csTitle = "abc"; csType = Unknown; maxStrokes = 0; } /*! Construct and load a characters set from file \a fn. */ QIMPenCharSet::QIMPenCharSet( const QString &fn ) { chars.setAutoDelete( TRUE ); desc = "Unnamed"; csTitle = "abc"; csType = Unknown; maxStrokes = 0; load( fn, System ); } const QString &QIMPenCharSet::filename( Domain d ) const { if ( d == System ) return sysFilename; else return userFilename; } void QIMPenCharSet::setFilename( const QString &fn, Domain d ) { if ( d == System ) sysFilename = fn; else if ( d == User ) userFilename = fn; } /*! Load a character set from file \a fn. */ bool QIMPenCharSet::load( const QString &fn, Domain d ) { setFilename( fn, d ); bool ok = FALSE; QFile file( fn ); if ( file.open( IO_ReadOnly ) ) { QDataStream ds( &file ); QString version; ds >> version; ds >> csTitle; ds >> desc; int major = version.mid( 4, 1 ).toInt(); int minor = version.mid( 6 ).toInt(); if ( major >= 1 && minor > 0 ) { ds >> (Q_INT8 &)csType; } else { if ( csTitle == "abc" ) csType = Lower; else if ( csTitle == "ABC" ) csType = Upper; else if ( csTitle == "123" ) csType = Numeric; else if ( fn == "Combining" ) csType = Combining; } while ( !ds.atEnd() ) { QIMPenChar *pc = new QIMPenChar; ds >> *pc; if ( d == User ) markDeleted( pc->character() ); // override system addChar( pc ); } if ( file.status() == IO_Ok ) ok = TRUE; } setHidden ( false ); return ok; } /*! Save this character set. */ bool QIMPenCharSet::save( Domain d ) { if ( filename( d ).isEmpty() ) return FALSE; if ( hidden() ) return TRUE; bool ok = FALSE; QString fn = filename( d ); QString tmpFn = fn + ".new"; QFile file( tmpFn ); if ( file.open( IO_WriteOnly|IO_Raw ) ) { QByteArray buf; QDataStream ds( buf, IO_WriteOnly ); ds << QString( "QPT 1.1" ); ds << csTitle; ds << desc; ds << (Q_INT8)csType; QIMPenCharIterator ci( chars ); for ( ; ci.current(); ++ci ) { QIMPenChar *pc = ci.current(); if ( ( ( (d == System) && pc->testFlag( QIMPenChar::System ) ) || ( (d == User) && !pc->testFlag( QIMPenChar::System ) ) ) && ( !pc->testFlag (QIMPenChar::Combined ) ) ) { ds << *pc; } } file.writeBlock( buf ); file.close(); if ( file.status() == IO_Ok ) ok = TRUE; } if ( ok ) { if ( ::rename( tmpFn.latin1(), fn.latin1() ) < 0 ) { qWarning( "problem renaming file %s to %s, errno: %d", tmpFn.latin1(), fn.latin1(), errno ); // remove the tmp file, otherwise, it will just lay around... QFile::remove( tmpFn.latin1() ); ok = FALSE; } } return ok; } QIMPenChar *QIMPenCharSet::at( int i ) { return chars.at(i); } void QIMPenCharSet::markDeleted( uint ch ) { QIMPenCharIterator ci( chars ); for ( ; ci.current(); ++ci ) { QIMPenChar *pc = ci.current(); if ( pc->character() == ch && pc->testFlag( QIMPenChar::System ) ) pc->setFlag( QIMPenChar::Deleted ); } } /*! Find the best matches for \a ch in this character set. */ QIMPenCharMatchList QIMPenCharSet::match( QIMPenChar *ch ) { QIMPenCharMatchList matches; QIMPenCharIterator ci( chars ); for ( ; ci.current(); ++ci ) { QIMPenChar *tmplChar = ci.current(); if ( tmplChar->testFlag( QIMPenChar::Deleted ) ) { continue; } int err; if ( ch->penStrokes().count() <= tmplChar->penStrokes().count() ) { err = ch->match( tmplChar ); if ( err <= QIMPEN_MATCH_THRESHOLD ) { if (tmplChar->penStrokes().count() != ch->penStrokes().count()) err = QMIN(err*3, QIMPEN_MATCH_THRESHOLD); QIMPenCharMatchList::Iterator it; for ( it = matches.begin(); it != matches.end(); ++it ) { if ( (*it).penChar->character() == tmplChar->character() && (*it).penChar->penStrokes().count() == tmplChar->penStrokes().count() ) { if ( (*it).error > err ) (*it).error = err; break; } } if ( it == matches.end() ) { QIMPenCharMatch m; m.error = err; m.penChar = tmplChar; matches.append( m ); } } } } qHeapSort( matches ); /* QIMPenCharMatchList::Iterator it; for ( it = matches.begin(); it != matches.end(); ++it ) { - qDebug( "Match: \'%c\', error %d, strokes %d", (*it).penChar->character(), - (*it).error, (*it).penChar->penStrokes().count() ); + + odebug << "Match: \'" << (*it).penChar->character() "\', error " << (*it).error ", strokes " <<(*it).penChar->penStrokes().count() << oendl; } */ return matches; } /*! Add a character \a ch to this set. QIMPenCharSet will delete this character when it is no longer needed. */ void QIMPenCharSet::addChar( QIMPenChar *ch ) { if ( ch->penStrokes().count() > maxStrokes ) maxStrokes = ch->penStrokes().count(); chars.append( ch ); } /*! Remove a character by reference \a ch from this set. QIMPenCharSet will delete this character. */ void QIMPenCharSet::removeChar( QIMPenChar *ch ) { chars.remove( ch ); } /*! Move the character up the list of characters. */ void QIMPenCharSet::up( QIMPenChar *ch ) { int idx = chars.findRef( ch ); if ( idx > 0 ) { chars.take(); chars.insert( idx - 1, ch ); } } /*! Move the character down the list of characters. */ void QIMPenCharSet::down( QIMPenChar *ch ) { int idx = chars.findRef( ch ); if ( idx >= 0 && idx < (int)chars.count() - 1 ) { chars.take(); chars.insert( idx + 1, ch ); } } diff --git a/inputmethods/handwriting/qimpeninput.cpp b/inputmethods/handwriting/qimpeninput.cpp index d073cdf..6ea1bb4 100644 --- a/inputmethods/handwriting/qimpeninput.cpp +++ b/inputmethods/handwriting/qimpeninput.cpp @@ -1,515 +1,517 @@ /********************************************************************** ** Copyright (C) 2000-2002 Trolltech AS. All rights reserved. ** ** This file is part of the Qtopia Environment. ** ** This file may be distributed and/or modified under the terms of the ** GNU General Public License version 2 as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL included in the ** packaging of this file. ** ** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE ** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. ** ** See http://www.trolltech.com/gpl/ for GPL licensing information. ** ** Contact info@trolltech.com if any conditions of this licensing are ** not clear to you. ** **********************************************************************/ #include "qimpenwidget.h" #include "qimpensetup.h" #include "qimpeninput.h" #include "qimpencombining.h" #include "qimpenwordpick.h" #include "qimpenmatch.h" #include "qimpenhelp.h" #include <qpe/qpeapplication.h> #include <qpe/qdawg.h> #include <qpe/config.h> #include <qpe/global.h> #include <qlayout.h> #include <qpushbutton.h> #include <qlabel.h> #include <qtimer.h> #include <qdir.h> +#include <opie2/odebug.h> #include <limits.h> // We'll use little pixmaps for the buttons to save screen space. /* XPM */ static const char * const pen_xpm[] = { "12 12 4 1", " c None", ". c #000000", "+ c #FFFFFF", "@ c #808080", " . ", " .+. ", " ..@@.", " .+@.. ", " .+@@. ", " .+@@. ", " .+@@. ", " .@.@. ", " .@@. ", " .... ", " .. ", " "}; /* XPM */ static char * bs_xpm[] = { "12 12 5 1", " c None", ". c #333333", "+ c #000000", "@ c #FFFFFF", "# c #666666", " ", " ", " ", " . ", " ++ ", " +@#+++++. ", " +@@@@@@@@+ ", " +@#+++++. ", " ++ ", " . ", " ", " "}; /* XPM */ static char * enter_xpm[] = { "12 12 5 1", " c None", ". c #333333", "+ c #000000", "@ c #FFFFFF", "# c #666666", " ", " .+. ", " +@+ ", " . +@+ ", " ++ +@+ ", " +@#++++@+ ", " +@@@@@@@@+ ", " +@#+++++. ", " ++ ", " . ", " ", " "}; /* XPM */ static char * help_xpm[] = { "12 12 5 1", " c None", ". c #000000", "+ c #FFFFFF", "@ c #666666", "# c #333333", " ", " ... ", " .+++. ", " .+..@+. ", " #.# .+. ", " .+. ", " .+. ", " .+. ", " .+. ", " #.# ", " .+. ", " #.# "}; /*! \class QIMPenInput qimpeninput.h Pen input widget. */ QIMPenInput::QIMPenInput( QWidget *parent, const char *name, WFlags wf ) : QFrame( parent, name, wf ), helpDlg(0), profile(0) { setFrameStyle( Box | Plain ); profileList.setAutoDelete( true ); matcher = new QIMPenMatch( this ); connect( matcher, SIGNAL(keypress(uint)), this, SLOT(keypress(uint)) ); connect( matcher, SIGNAL(erase()), this, SLOT(erase()) ); QGridLayout *gl = new QGridLayout( this, 5, 2, 1, 0 ); gl->setColStretch( 0, 1 ); wordPicker = new QIMPenWordPick( this ); connect( wordPicker, SIGNAL(wordClicked(const QString&)), this, SLOT(wordPicked(const QString&)) ); connect( matcher, SIGNAL(matchedCharacters(const QIMPenCharMatchList&)), this, SLOT(matchedCharacters(const QIMPenCharMatchList&)) ); connect( matcher, SIGNAL(matchedWords(const QIMPenMatch::MatchWordList&)), wordPicker, SLOT(setWords(const QIMPenMatch::MatchWordList&)) ); QFont f("smallsmooth",9); QFontInfo fi( f ); wordPicker->setFont( f ); wordPicker->setBackgroundColor( white ); gl->addMultiCellWidget( wordPicker, 0, 0, 0, 1 ); if ( !Global::fixedDawg().root() || !matcher->isWordMatchingEnabled() ) wordPicker->hide(); pw = new QIMPenWidget( this ); gl->addMultiCellWidget( pw, 1, 4, 0, 0 ); int bh = pw->sizeHint().height()/4; QPushButton *b = new QPushButton( this ); b->setFocusPolicy( NoFocus ); b->setPixmap( QPixmap( (const char **)bs_xpm ) ); b->setFixedHeight(pw->sizeHint().height()-3*bh); // left-over space goes here b->setAutoRepeat( TRUE ); gl->addWidget( b, 1, 1 ); connect( b, SIGNAL(clicked()), SLOT(backspace())); b = new QPushButton( this ); b->setFocusPolicy( NoFocus ); b->setPixmap( QPixmap( (const char **)enter_xpm ) ); b->setFixedHeight(bh); b->setAutoRepeat( TRUE ); gl->addWidget( b, 2, 1 ); connect( b, SIGNAL(clicked()), SLOT(enter())); helpBtn = new QPushButton( this ); helpBtn->setFocusPolicy( NoFocus ); helpBtn->setPixmap( QPixmap( (const char **)help_xpm ) ); helpBtn->setFixedHeight(bh); gl->addWidget( helpBtn, 3, 1 ); connect( helpBtn, SIGNAL(clicked()), SLOT(help())); QPixmap pm( (const char **)pen_xpm ); setupBtn = new QPushButton( this ); setupBtn->setFocusPolicy( NoFocus ); setupBtn->setPixmap( pm ); setupBtn->setFixedHeight(bh); gl->addWidget( setupBtn, 4, 1 ); connect( setupBtn, SIGNAL(clicked()), SLOT(setup())); connect( matcher, SIGNAL(removeStroke()), pw, SLOT(removeStroke()) ); connect( pw, SIGNAL(changeCharSet(QIMPenCharSet*)), matcher, SLOT(setCharSet(QIMPenCharSet*)) ); connect( pw, SIGNAL(changeCharSet(int)), this, SLOT(selectCharSet(int)) ); connect( pw, SIGNAL(beginStroke()), matcher, SLOT(beginStroke()) ); connect( pw, SIGNAL(stroke(QIMPenStroke*)), this, SLOT(strokeEntered(QIMPenStroke*)) ); connect( pw, SIGNAL(stroke(QIMPenStroke*)), matcher, SLOT(strokeEntered(QIMPenStroke*)) ); shortcutCharSet = 0; currCharSet = 0; setupDlg = 0; profile = 0; mode = Normal; loadProfiles(); } QIMPenInput::~QIMPenInput() { delete (HandwritingHelp*) helpDlg; } QSize QIMPenInput::sizeHint() const { int fw = frameWidth(); int ps = wordPicker->isHidden() ? 0 : wordPicker->sizeHint().height(); return pw->sizeHint() + QSize( fw*2, fw*2+ps ); } void QIMPenInput::loadProfiles() { profileList.clear(); profile = 0; delete shortcutCharSet; shortcutCharSet = new QIMPenCharSet(); shortcutCharSet->setTitle( tr("Shortcut") ); QString path = QPEApplication::qpeDir() + "etc/qimpen"; QDir dir( path, "*.conf" ); QStringList list = dir.entryList(); QStringList::Iterator it; for ( it = list.begin(); it != list.end(); ++it ) { QIMPenProfile *p = new QIMPenProfile( path + "/" + *it ); profileList.append( p ); if ( p->shortcut() ) { QIMPenCharIterator it( p->shortcut()->characters() ); for ( ; it.current(); ++it ) { shortcutCharSet->addChar( new QIMPenChar(*it.current()) ); } } } Config config( "handwriting" ); config.setGroup( "Settings" ); QString prof = config.readEntry( "Profile", "Default" ); selectProfile( prof ); } void QIMPenInput::selectProfile( const QString &name ) { QListIterator<QIMPenProfile> it( profileList ); for ( ; it.current(); ++it ) { if ( it.current()->name() == name ) { profile = it.current(); break; } } if ( !it.current() ) return; pw->clearCharSets(); baseSets.clear(); matcher->setMultiStrokeTimeout( profile->multiStrokeTimeout() ); matcher->setWordMatchingEnabled( profile->matchWords() ); if ( !Global::fixedDawg().root() || !matcher->isWordMatchingEnabled() ) wordPicker->hide(); else wordPicker->show(); if ( profile->uppercase() && profile->style() == QIMPenProfile::BothCases ) { baseSets.append( profile->uppercase() ); pw->insertCharSet( profile->uppercase() ); } if ( profile->lowercase() ) { baseSets.append( profile->lowercase() ); pw->insertCharSet( profile->lowercase(), profile->style() == QIMPenProfile::BothCases ? 1 : 2 ); } if ( profile->numeric() ) { baseSets.append( profile->numeric() ); pw->insertCharSet( profile->numeric() ); } if ( helpDlg ) delete (HandwritingHelp*) helpDlg; } void QIMPenInput::wordPicked( const QString &w ) { int bs = matcher->word().length(); for ( int i = 0; i < bs; i++ ) keypress( Qt::Key_Backspace << 16 ); for ( unsigned int i = 0; i < w.length(); i++ ) keypress( w[i].unicode() ); matcher->resetState(); wordPicker->clear(); } void QIMPenInput::selectCharSet( int idx ) { if ( mode == Switch ) { - //qDebug( "Switch back to normal" ); + //odebug << "Switch back to normal" << oendl; pw->changeCharSet( baseSets.at(currCharSet), currCharSet ); mode = Normal; } currCharSet = idx; } void QIMPenInput::beginStroke() { } void QIMPenInput::strokeEntered( QIMPenStroke * ) { pw->greyStroke(); } void QIMPenInput::erase() { keypress( Qt::Key_Backspace << 16 ); } void QIMPenInput::matchedCharacters( const QIMPenCharMatchList &cl ) { const QIMPenChar *ch = cl.first().penChar; int scan = ch->character() >> 16; if ( scan < QIMPenChar::ModeBase ) return; // We matched a special character... switch ( scan ) { case QIMPenChar::Caps: if ( profile->style() == QIMPenProfile::ToggleCases ) { -// qDebug( "Caps" ); +// odebug << "Caps" << oendl; +// if ( mode == SwitchLock ) { -// qDebug( "Switch to normal" ); +// odebug << "Switch to normal" << oendl; pw->changeCharSet( profile->lowercase(), currCharSet ); mode = Switch; } else { -// qDebug( "Switch to upper" ); +// odebug << "Switch to upper" << oendl; pw->changeCharSet( profile->uppercase(), currCharSet ); mode = Switch; } } break; case QIMPenChar::CapsLock: if ( profile->style() == QIMPenProfile::ToggleCases ) { -// qDebug( "CapsLock" ); +// odebug << "CapsLock" << oendl; if ( mode == Switch && baseSets.at(currCharSet) == profile->uppercase() ) { -// qDebug( "Switch to normal" ); +// odebug << "Switch to normal" << oendl; pw->changeCharSet( profile->lowercase(), currCharSet ); // change our base set back to lower. baseSets.remove( currCharSet ); baseSets.insert( currCharSet, profile->lowercase() ); mode = Normal; } else { -// qDebug( "Switch to caps lock" ); +// odebug << "Switch to caps lock" << oendl; pw->changeCharSet( profile->uppercase(), currCharSet ); // change our base set to upper. baseSets.remove( currCharSet ); baseSets.insert( currCharSet, profile->uppercase() ); mode = SwitchLock; } } break; case QIMPenChar::Punctuation: if ( profile->punctuation() ) { - //qDebug( "Switch to punctuation" ); + //odebug << "Switch to punctuation" << oendl; pw->changeCharSet( profile->punctuation(), currCharSet ); mode = Switch; } break; case QIMPenChar::Symbol: if ( profile->symbol() ) { - //qDebug( "Switch to symbol" ); + //odebug << "Switch to symbol" << oendl ; pw->changeCharSet( profile->symbol(), currCharSet ); mode = Switch; } break; case QIMPenChar::Shortcut: if ( shortcutCharSet ) { pw->changeCharSet( shortcutCharSet, currCharSet ); mode = Switch; } break; case QIMPenChar::Extended: handleExtended( ch->data() ); break; } } void QIMPenInput::keypress( uint scan_uni ) { int scan = scan_uni >> 16; if ( !scan ) { if ( scan_uni >= 'a' && scan_uni <= 'z' ) { scan = Qt::Key_A + scan_uni - 'a'; } else if ( scan_uni >= 'A' && scan_uni <= 'Z' ) { scan = Qt::Key_A + scan_uni - 'A'; } else if ( scan_uni == ' ' ) { scan = Qt::Key_Space; } } switch ( scan ) { case Key_Tab: scan_uni = 9; break; case Key_Return: scan_uni = 13; break; case Key_Backspace: scan_uni = 8; break; case Key_Escape: scan_uni = 27; break; default: break; } if ( mode == Switch ) { -// qDebug( "Switch back to normal" ); +// odebug << "Switch back to normal" << oendl ; pw->changeCharSet( baseSets.at(currCharSet), currCharSet ); if ( baseSets.at(currCharSet) == profile->uppercase() ) mode = SwitchLock; else mode = Normal; } emit key( scan_uni&0xffff, scan, 0, true, false ); emit key( scan_uni&0xffff, scan, 0, false, false ); } void QIMPenInput::handleExtended( const QString &ex ) { if ( ex.find( "Select" ) == 0 ) { QString set = ex.mid( 7 ); - qDebug( "Select new profile: %s", set.latin1() ); + odebug << "Select new profile: " << set.latin1() << oendl; selectProfile( set ); } } void QIMPenInput::help() { if ( helpDlg ) delete (HandwritingHelp*) helpDlg; helpDlg = new HandwritingHelp( profile, 0, 0, WDestructiveClose ); helpDlg->showMaximized(); helpDlg->show(); helpDlg->raise(); } /*! Open the setup dialog */ void QIMPenInput::setup() { if ( !setupDlg ) { // We are working with our copy of the char sets here. setupDlg = new QIMPenSetup( profile, 0, 0, TRUE ); setupDlg->editor()->selectCharSet( profile->charSets().at(1) ); // lower case? This is crap. if ( qApp->desktop()->width() < 640 ) setupDlg->showMaximized(); Global::hideInputMethod(); setupDlg->exec(); loadProfiles(); delete setupDlg; setupDlg = 0; Global::showInputMethod(); } else { setupDlg->raise(); } } void QIMPenInput::backspace() { keypress( Qt::Key_Backspace << 16 ); matcher->backspace(); } void QIMPenInput::enter() { keypress( Qt::Key_Return << 16 ); matcher->resetState(); } void QIMPenInput::resetState() { matcher->resetState(); } diff --git a/inputmethods/handwriting/qimpenmatch.cpp b/inputmethods/handwriting/qimpenmatch.cpp index 0d3e25a..a0448b6 100644 --- a/inputmethods/handwriting/qimpenmatch.cpp +++ b/inputmethods/handwriting/qimpenmatch.cpp @@ -1,365 +1,366 @@ /********************************************************************** ** Copyright (C) 2000 Trolltech AS. All rights reserved. ** ** This file is part of Qtopia Environment. ** ** This file may be distributed and/or modified under the terms of the ** GNU General Public License version 2 as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL included in the ** packaging of this file. ** ** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE ** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. ** ** See http://www.trolltech.com/gpl/ for GPL licensing information. ** ** Contact info@trolltech.com if any conditions of this licensing are ** not clear to you. ** **********************************************************************/ #include "qimpenmatch.h" #include <qpe/qdawg.h> #include <qpe/global.h> #include <qapplication.h> #include <qtimer.h> +#include <opie2/odebug.h> #include <limits.h> #define ERROR_THRESHOLD 200000 #define LOOKAHEAD_ERROR 2500 //#define DEBUG_QIMPEN QIMPenMatch::QIMPenMatch( QObject *parent, const char *name ) : QObject( parent, name ) { strokes.setAutoDelete( TRUE ); wordChars.setAutoDelete( TRUE ); wordMatches.setAutoDelete( TRUE ); multiTimer = new QTimer( this ); connect( multiTimer, SIGNAL(timeout()), this, SLOT(endMulti()) ); prevMatchChar = 0; prevMatchError = INT_MAX; charSet = 0; multiCharSet = 0; multiTimeout = 500; canErase = FALSE; doWordMatching = true; } QIMPenMatch::~QIMPenMatch() { } void QIMPenMatch::setCharSet( QIMPenCharSet *cs ) { charSet = cs; } void QIMPenMatch::beginStroke() { multiTimer->stop(); } void QIMPenMatch::strokeEntered( QIMPenStroke *st ) { #ifdef DEBUG_QIMPEN - qDebug( "---------- new stroke -------------" ); + odebug << "---------- new stroke -------------" << oendl; #endif strokes.append( new QIMPenStroke( *st ) ); QIMPenChar testChar; QIMPenStrokeIterator it(strokes); for ( ; it.current(); ++it ) { testChar.addStroke( it.current() ); } QIMPenCharMatchList ml; if ( strokes.count() > 1 && multiCharSet ) { #ifdef DEBUG_QIMPEN - qDebug( "Matching against multi set" ); + odebug << "Matching against multi set" << oendl; #endif ml = multiCharSet->match( &testChar ); } else { #ifdef DEBUG_QIMPEN - qDebug( "Matching against single set" ); + odebug << "Matching against single set" << oendl; #endif ml = charSet->match( &testChar ); } processMatches( ml ); } void QIMPenMatch::processMatches( QIMPenCharMatchList &ml ) { #ifdef DEBUG_QIMPEN - qDebug( "Entering strokes.count() = %d", strokes.count() ); + odebug << "Entering strokes.count() = " << strokes.count() << oendl; #endif QIMPenCharMatch candidate1 = { INT_MAX, 0 }; QIMPenCharMatch candidate2 = { INT_MAX, 0 }; QIMPenCharMatchList ml2; if ( ml.count() ) {//&& // ml.first().penChar->penStrokes().count() == strokes.count() ) { candidate1 = ml.first(); #ifdef DEBUG_QIMPEN - qDebug( QString("Candidate1 = %1").arg(QChar(candidate1.penChar->character())) ); + odebug << "Candidate1 = " << candidate1.penChar->character() << oendl; #endif } if ( strokes.count() > 1 ) { // See if the last stroke can match a new character QIMPenChar testChar; QIMPenStroke *st = strokes.at(strokes.count()-1); testChar.addStroke( st ); ml2 = charSet->match( &testChar ); if ( ml2.count() ) { candidate2 = ml2.first(); #ifdef DEBUG_QIMPEN - qDebug( QString("Candidate2 = %1").arg(QChar(candidate2.penChar->character())) ); + odebug << "Candidate2 = " << candidate2.penChar->character() << oendl; #endif } } bool eraseLast = FALSE; bool output = TRUE; if ( candidate1.penChar && candidate2.penChar ) { // Hmmm, a multi-stroke or a new character are both possible. // Bias the multi-stroke case. if ( QMAX(candidate2.error, prevMatchError)*3 < candidate1.error ) { int i = strokes.count()-1; while ( i-- ) { strokes.removeFirst(); emit removeStroke(); } prevMatchChar = candidate2.penChar; prevMatchError = candidate2.error; multiCharSet = charSet; ml = ml2; #ifdef DEBUG_QIMPEN - qDebug( "** Using Candidate2" ); + odebug << "** Using Candidate2" << oendl; #endif } else { if ( (prevMatchChar->character() >> 16) != Qt::Key_Backspace && (prevMatchChar->character() >> 16) < QIMPenChar::ModeBase ) eraseLast = TRUE; prevMatchChar = candidate1.penChar; prevMatchError = candidate1.error; #ifdef DEBUG_QIMPEN - qDebug( "** Using Candidate1, with erase" ); + odebug << "** Using Candidate1, with erase" << oendl; #endif } } else if ( candidate1.penChar ) { if ( strokes.count() != 1 ) eraseLast = TRUE; else multiCharSet = charSet; prevMatchChar = candidate1.penChar; prevMatchError = candidate1.error; #ifdef DEBUG_QIMPEN - qDebug( "** Using Candidate1" ); + odebug << "** Using Candidate1" << oendl; #endif } else if ( candidate2.penChar ) { int i = strokes.count()-1; while ( i-- ) { strokes.removeFirst(); emit removeStroke(); } prevMatchChar = candidate2.penChar; prevMatchError = candidate2.error; multiCharSet = charSet; ml = ml2; #ifdef DEBUG_QIMPEN - qDebug( "** Using Candidate2" ); + odebug << "** Using Candidate2" << oendl; #endif } else { if ( !ml.count() ) { #ifdef DEBUG_QIMPEN - qDebug( "** Failed" ); + odebug << "** Failed" << oendl; #endif canErase = FALSE; } else { #ifdef DEBUG_QIMPEN - qDebug( "Need more strokes" ); + odebug << "Need more strokes" << oendl; #endif if ( strokes.count() == 1 ) canErase = FALSE; multiCharSet = charSet; } output = FALSE; emit noMatch(); } if ( eraseLast && canErase ) { #ifdef DEBUG_QIMPEN - qDebug( "deleting last" ); + odebug << "deleting last" << oendl; #endif emit erase(); wordChars.removeLast(); wordEntered.truncate( wordEntered.length() - 1 ); } if ( output ) { emit matchedCharacters( ml ); uint code = prevMatchChar->character() >> 16; if ( code < QIMPenChar::ModeBase ) { updateWordMatch( ml ); emit keypress( prevMatchChar->character() ); } canErase = TRUE; } if ( strokes.count() ) multiTimer->start( multiTimeout, TRUE ); } void QIMPenMatch::updateWordMatch( QIMPenCharMatchList &ml ) { if ( !ml.count() || !doWordMatching ) return; int ch = ml.first().penChar->character(); QChar qch( ch ); int code = ch >> 16; if ( qch.isPunct() || qch.isSpace() || code == Qt::Key_Enter || code == Qt::Key_Return || code == Qt::Key_Tab || code == Qt::Key_Escape ) { -// qDebug( "Word Matching: Clearing word" ); +// odebug << "Word Matching: Clearing word" << oendl; wordChars.clear(); wordMatches.clear(); wordEntered = QString(); } else if ( code == Qt::Key_Backspace ) { - //qDebug( "Word Matching: Handle backspace" ); + //odebug << "Word Matching: Handle backspace" << oendl; wordChars.removeLast(); wordEntered.truncate( wordEntered.length() - 1 ); matchWords(); } else { QIMPenChar *matchCh; wordChars.append( new QIMPenCharMatchList() ); wordEntered += ml.first().penChar->character(); QIMPenCharMatchList::Iterator it; for ( it = ml.begin(); it != ml.end(); ++it ) { matchCh = (*it).penChar; if ( matchCh->penStrokes().count() == strokes.count() ) { QChar ch(matchCh->character()); if ( !ch.isPunct() && !ch.isSpace() ) { wordChars.last()->append( QIMPenCharMatch( (*it) ) ); } } } matchWords(); } if ( !wordMatches.count() || wordMatches.getFirst()->word != wordEntered ) wordMatches.prepend( new MatchWord( wordEntered, 0 ) ); emit matchedWords( wordMatches ); } void QIMPenMatch::matchWords() { if ( wordEntered.length() > 0 ) { // more leaniency if we don't have many matches if ( badMatches < 200 ) errorThreshold += (200 - badMatches) * 100; } else errorThreshold = ERROR_THRESHOLD; wordMatches.clear(); goodMatches = 0; badMatches = 0; if ( wordChars.count() > 0 ) { maxGuess = (int)wordChars.count() * 2; if ( maxGuess < 3 ) maxGuess = 3; QString str; scanDict( Global::fixedDawg().root(), 0, str, 0 ); /* QListIterator<MatchWord> it( wordMatches); for ( ; it.current(); ++it ) { - qDebug( QString("Match word: %1").arg(it.current()->word) ); + odebug << "Match word: " << it.current()->word << oendl; } */ } - //qDebug( "Possibles: Good %d, total %d", goodMatches, wordMatches.count() ); + //odebug << "Possibles: Good " << goodMatches << ", total " << wordMatches.count() << oendl; wordMatches.sort(); } void QIMPenMatch::scanDict( const QDawg::Node* n, int ipos, const QString& str, int error ) { if ( !n ) return; if ( error / (ipos+1) > errorThreshold ) return; while (n) { if ( goodMatches > 20 ) break; if ( ipos < (int)wordChars.count() ) { int i; QChar testCh = QChar(n->letter()); QIMPenCharMatchList::Iterator it; for ( i = 0, it = wordChars.at(ipos)->begin(); it != wordChars.at(ipos)->end() && i < 8; ++it, i++ ) { QChar ch( (*it).penChar->character() ); if ( ch == testCh || ( !ipos && ch.lower() == testCh.lower() ) ) { int newerr = error + (*it).error; if ( testCh.category() == QChar::Letter_Uppercase ) ch = testCh; QString newstr( str + ch ); if ( n->isWord() && ipos == (int)wordChars.count() - 1 ) { wordMatches.append( new MatchWord( newstr, newerr ) ); goodMatches++; } scanDict( n->jump(), ipos+1, newstr, newerr ); } } } else if ( badMatches < 200 && ipos < maxGuess ) { int d = ipos - wordChars.count(); int newerr = error + ERROR_THRESHOLD + LOOKAHEAD_ERROR*d; QString newstr( str + n->letter() ); if ( n->isWord() ) { wordMatches.append( new MatchWord( newstr, newerr ) ); badMatches++; } scanDict( n->jump(), ipos+1, newstr, newerr ); } n = n->next(); } } void QIMPenMatch::backspace() { wordChars.removeLast(); wordEntered.truncate( wordEntered.length() - 1 ); matchWords(); if ( !wordMatches.count() || wordMatches.getFirst()->word != wordEntered ) wordMatches.prepend( new MatchWord( wordEntered, 0 ) ); emit matchedWords( wordMatches ); if ( wordEntered.length() ) canErase = TRUE; } void QIMPenMatch::endMulti() { int i = strokes.count(); while ( i-- ) emit removeStroke(); strokes.clear(); multiCharSet = 0; } void QIMPenMatch::resetState() { if ( !wordEntered.isEmpty() ) { wordChars.clear(); wordMatches.clear(); wordEntered = QString(); emit matchedWords( wordMatches ); canErase = FALSE; } } diff --git a/inputmethods/handwriting/qimpenstroke.cpp b/inputmethods/handwriting/qimpenstroke.cpp index 3567d6d..14e435a 100644 --- a/inputmethods/handwriting/qimpenstroke.cpp +++ b/inputmethods/handwriting/qimpenstroke.cpp @@ -1,634 +1,635 @@ /********************************************************************** ** Copyright (C) 2000 Trolltech AS. All rights reserved. ** ** This file is part of Qtopia Environment. ** ** This file may be distributed and/or modified under the terms of the ** GNU General Public License version 2 as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL included in the ** packaging of this file. ** ** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE ** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. ** ** See http://www.trolltech.com/gpl/ for GPL licensing information. ** ** Contact info@trolltech.com if any conditions of this licensing are ** not clear to you. ** **********************************************************************/ #include <qfile.h> #include <qtl.h> #include <math.h> #include <limits.h> #include <qdatastream.h> #include "qimpenstroke.h" +#include "opie2/odebug.h" #define QIMPEN_CORRELATION_POINTS 25 //#define DEBUG_QIMPEN /*! \class QIMPenStroke qimpenstroke.h Handles a single stroke. Can calculate closeness of match to another stroke. */ QIMPenStroke::QIMPenStroke() { } QIMPenStroke::QIMPenStroke( const QIMPenStroke &st ) { startPoint = st.startPoint; lastPoint = st.lastPoint; links = st.links.copy(); } QIMPenStroke &QIMPenStroke::operator=( const QIMPenStroke &s ) { clear(); - //qDebug( "copy strokes %d", s.links.count() ); + //odebug << "copy strokes " << s.links.count() << oendl; startPoint = s.startPoint; lastPoint = s.lastPoint; links = s.links.copy(); return *this; } void QIMPenStroke::clear() { startPoint = QPoint(0,0); lastPoint = QPoint( 0, 0 ); links.resize( 0 ); tsig.resize( 0 ); dsig.resize( 0 ); asig.resize( 0 ); } /*! Begin inputting a new stroke. */ void QIMPenStroke::beginInput( QPoint p ) { clear(); startPoint = p; bounding = QRect(); internalAddPoint( p ); } /*! Add a point to the stroke's shape. Returns TRUE if the point was successfully added. */ bool QIMPenStroke::addPoint( QPoint p ) { if ( links.count() > 500 ) // sanity check (that the user is sane). return FALSE; int dx = p.x() - lastPoint.x(); int dy = p.y() - lastPoint.y(); if ( QABS( dx ) > 1 || QABS( dy ) > 1 ) { // The point is not adjacent to the previous point, so we fill // in with a straight line. Some kind of non-linear // interpolation might be better. int x = lastPoint.x(); int y = lastPoint.y(); int ix = 1; int iy = 1; if ( dx < 0 ) { ix = -1; dx = -dx; } if ( dy < 0 ) { iy = -1; dy = -dy; } int d = 0; if ( dx < dy ) { d = dx; do { y += iy; d += dx; if ( d > dy ) { x += ix; d -= dy; } internalAddPoint( QPoint( x, y ) ); } while ( y != p.y() ); } else { d = dy; do { x += ix; d += dy; if ( d > dx ) { y += iy; d -= dx; } internalAddPoint( QPoint( x, y ) ); } while ( x != p.x() ); } } else { internalAddPoint( p ); } return TRUE; } /*! Finish inputting a stroke. */ void QIMPenStroke::endInput() { if ( links.count() < 3 ) { QIMPenGlyphLink gl; links.resize(1); gl.dx = 1; gl.dy = 0; links[0] = gl; } - //qDebug("Points: %d", links.count() ); + //odebug << "Points: " << links.count() << oendl; } /*! Return an indicator of the closeness of this stroke to \a pen. Lower value is better. */ unsigned int QIMPenStroke::match( QIMPenStroke *pen ) { double lratio; if ( links.count() > pen->links.count() ) lratio = (links.count()+2) / (pen->links.count()+2); else lratio = (pen->links.count()+2) / (links.count()+2); lratio -= 1.0; if ( lratio > 2.0 ) { #ifdef DEBUG_QIMPEN - qDebug( "stroke length too different" ); + odebug << "stroke length too different" << oendl; #endif return 400000; } createSignatures(); pen->createSignatures(); // Starting point offset int vdiff = QABS(startPoint.y() - pen->startPoint.y()); // Insanely offset? if ( vdiff > 18 ) { return 400000; } vdiff -= 4; if ( vdiff < 0 ) vdiff = 0; // Ending point offset int evdiff = QABS(lastPoint.y() - pen->lastPoint.y()); // Insanely offset? if ( evdiff > 20 ) { return 400000; } evdiff -= 5; if ( evdiff < 0 ) evdiff = 0; // do a correlation with the three available signatures. int err1 = INT_MAX; int err2 = INT_MAX; int err3 = INT_MAX; // base has extra points at the start and end to enable // correlation of a sliding window with the pen supplied. QArray<int> base = createBase( tsig, 2 ); for ( int i = 0; i < 4; i++ ) { int e = calcError( base, pen->tsig, i, TRUE ); if ( e < err1 ) err1 = e; } if ( err1 > 40 ) { // no need for more matching #ifdef DEBUG_QIMPEN - qDebug( "tsig too great: %d", err1 ); + odebug << "tsig too great: " << err1 << oendl; #endif return 400000; } // maybe a sliding window is worthwhile for these too. err2 = calcError( dsig, pen->dsig, 0, FALSE ); if ( err2 > 100 ) { #ifdef DEBUG_QIMPEN - qDebug( "dsig too great: %d", err2 ); + odebug << "dsig too great: " << err2 << oendl; #endif return 400000; } err3 = calcError( asig, pen->asig, 0, TRUE ); if ( err3 > 60 ) { #ifdef DEBUG_QIMPEN - qDebug( "asig too great: %d", err3 ); + odebug << "asig too great: " << err3 << oendl; #endif return 400000; } // Some magic numbers here - the addition reduces the weighting of // the error and compensates for the different error scales. I // consider the tangent signature to be the best indicator, so it // has the most weight. This ain't rocket science. // Basically, these numbers are the tuning factors. unsigned int err = (err1+1) * ( err2 + 60 ) * ( err3 + 20 ) + vdiff * 1000 + evdiff * 500 + (unsigned int)(lratio * 5000.0); #ifdef DEBUG_QIMPEN - qDebug( "err %d ( %d, %d, %d, %d)", err, err1, err2, err3, vdiff ); + odebug << "err " << err << "( " << err1 << ", " << err2 << ", " << err3 << ", " << vdiff << oendl; #endif return err; } /*! Return the bounding rect of this stroke. */ QRect QIMPenStroke::boundingRect() { if ( !bounding.isValid() ) { int x = startPoint.x(); int y = startPoint.y(); bounding = QRect( x, y, 1, 1 ); for ( unsigned i = 0; i < links.count(); i++ ) { x += links[i].dx; y += links[i].dy; if ( x < bounding.left() ) bounding.setLeft( x ); if ( x > bounding.right() ) bounding.setRight( x ); if ( y < bounding.top() ) bounding.setTop( y ); if ( y > bounding.bottom() ) bounding.setBottom( y ); } } return bounding; } /*! Perform a correlation of the supplied arrays. \a base should have win.count() + 2 * off points to enable sliding \a win over the \a base data. If \a t is TRUE, the comparison takes into account the circular nature of the angular data. Returns the best (lowest error) match. */ int QIMPenStroke::calcError( const QArray<int> &base, const QArray<int> &win, int off, bool t ) { int err = 0; for ( unsigned i = 0; i < win.count(); i++ ) { int d = QABS( base[i+off] - win[i] ); if ( t && d > 128 ) d -= 256; err += QABS( d ); } err /= win.count(); return err; } /*! Creates signatures used in matching if not already created. */ void QIMPenStroke::createSignatures() { if ( tsig.isEmpty() ) createTanSignature(); if ( asig.isEmpty() ) createAngleSignature(); if ( dsig.isEmpty() ) createDistSignature(); } /*! Create a signature of the tangents to the user's stroke. */ void QIMPenStroke::createTanSignature() { int dist = 5; // number of points to include in calculation if ( (int)links.count() <= dist ) { tsig.resize(1); int dx = 0; int dy = 0; for ( unsigned j = 0; j < links.count(); j++ ) { dx += links[j].dx; dy += links[j].dy; } tsig[0] = arcTan( dy, dx ); } else { tsig.resize( (links.count()-dist+1) / 2 ); int idx = 0; for ( unsigned i = 0; i < links.count() - dist; i += 2 ) { int dx = 0; int dy = 0; for ( int j = 0; j < dist; j++ ) { dx += links[i+j].dx; dy += links[i+j].dy; } tsig[idx++] = arcTan( dy, dx ); } } tsig = scale( tsig, QIMPEN_CORRELATION_POINTS, TRUE ); // smooth(tsig); } /*! Create a signature of the change in angle. */ void QIMPenStroke::createAngleSignature() { QPoint c = calcCenter(); int dist = 3; // number of points to include in calculation if ( (int)links.count() <= dist ) { asig.resize(1); asig[0] = 1; } else { asig.resize( links.count() ); QPoint current(0, 0); int idx = 0; for ( unsigned i = 0; i < links.count(); i++ ) { int dx = c.x() - current.x(); int dy = c.y() - current.y(); int md = QMAX( QABS(dx), QABS(dy) ); if ( md > 5 ) { dx = dx * 5 / md; dy = dy * 5 / md; } asig[idx++] = arcTan( dy, dx ); current += QPoint( links[i].dx, links[i].dy ); } } asig = scale( asig, QIMPEN_CORRELATION_POINTS, TRUE ); /* if ( tsig.isEmpty() ) createTanSignature(); if ( tsig.count() < 5 ) { asig.resize( 1 ); asig[0] = 0; } else { asig.resize( tsig.count() - 5 ); for ( unsigned i = 0; i < asig.count(); i++ ) { asig[i] = QABS(tsig[i] - tsig[i+5]); } } */ } /*! Create a signature of the distance from the char's center of gravity to its points. */ void QIMPenStroke::createDistSignature() { dsig.resize( (links.count()+1)/2 ); QPoint c = calcCenter(); QPoint pt( 0, 0 ); int minval = INT_MAX; int maxval = 0; int idx = 0; for ( unsigned i = 0; i < links.count(); i += 2 ) { int dx = c.x() - pt.x(); int dy = c.y() - pt.y(); if ( dx == 0 && dy == 0 ) dsig[idx] = 0; else dsig[idx] = dx*dx + dy*dy; if ( dsig[idx] > maxval ) maxval = dsig[idx]; if ( dsig[idx] < minval ) minval = dsig[idx]; pt.rx() += links[i].dx; pt.ry() += links[i].dy; idx++; } // normalise 0-255 int div = maxval - minval; if ( div == 0 ) div = 1; for ( unsigned i = 0; i < dsig.count(); i++ ) { dsig[i] = (dsig[i] - minval ) * 255 / div; } dsig = scale( dsig, QIMPEN_CORRELATION_POINTS ); } /*! Scale the points in a array to \a count points. This is braindead at the moment (no smooth scaling) and fixing this is probably one of the simpler ways to improve performance. */ QArray<int> QIMPenStroke::scale( const QArray<int> &s, unsigned count, bool t ) { QArray<int> d(count); unsigned si = 0; if ( s.count() > count ) { unsigned next = 0; for ( unsigned i = 0; i < count; i++ ) { next = (i+1) * s.count() / count; int maxval = 0; if ( t ) { for ( unsigned j = si; j < next; j++ ) { maxval = s[j] > maxval ? s[j] : maxval; } } int sum = 0; for ( unsigned j = si; j < next; j++ ) { if ( t && maxval - s[j] > 128 ) sum += 256; sum += s[j]; } d[i] = sum / (next-si); if ( t && d[i] > 256 ) d[i] %= 256; si = next; } } else { for ( unsigned i = 0; i < count; i++ ) { si = i * s.count() / count; d[i] = s[si]; } } return d; } /*! Add another point to the stroke's shape. */ void QIMPenStroke::internalAddPoint( QPoint p ) { if ( p == lastPoint ) return; if ( !lastPoint.isNull() ) { QIMPenGlyphLink gl; gl.dx = p.x() - lastPoint.x(); gl.dy = p.y() - lastPoint.y(); links.resize( links.size() + 1 ); //### resize by 1 is bad links[links.size() - 1] = gl; } lastPoint = p; bounding = QRect(); } /*! Calculate the center of gravity of the stroke. */ QPoint QIMPenStroke::calcCenter() { QPoint pt( 0, 0 ); int ax = 0; int ay = 0; for ( unsigned i = 0; i < links.count(); i++ ) { pt.rx() += links[i].dx; pt.ry() += links[i].dy; ax += pt.x(); ay += pt.y(); } ax /= (int)links.count(); ay /= (int)links.count(); return QPoint( ax, ay ); } /*! Calculate the arctan of the lengths supplied. The angle returned is in the range 0-255. \a dy and \a dx MUST be in the range 0-5 - I dont even check :-P */ int QIMPenStroke::arcTan( int dy, int dx ) { if ( dx == 0 ) { if ( dy >= 0 ) return 64; else return 192; } if ( dy == 0 ) { if ( dx >= 0 ) return 0; else return 128; } static int table[5][5] = { { 32, 19, 13, 10, 8 }, { 45, 32, 24, 19, 16 }, { 51, 40, 32, 26, 22 }, { 54, 45, 37, 32, 27 }, { 56, 49, 42, 37, 32 } }; if ( dy > 0 ) { if ( dx > 0 ) return table[dy-1][dx-1]; else return 128 - table[dy-1][QABS(dx)-1]; } else { if ( dx > 0 ) return 256 - table[QABS(dy)-1][dx-1]; else return 128 + table[QABS(dy)-1][QABS(dx)-1]; } return 0; } /*! Silly name. Create an array that has \a e points extra at the start and end to enable a sliding correlation to be performed. */ QArray<int> QIMPenStroke::createBase( const QArray<int> a, int e ) { QArray<int> ra( a.count() + 2*e ); for ( int i = 0; i < e; i++ ) { ra[i] = a[e - i - 1]; ra[a.count() + i] = a[a.count() - i - 1]; } for ( unsigned i = 0; i < a.count(); i++ ) { ra[i+e] = a[i]; } return ra; } /*! Smooth the points in an array. Probably a bad idea. */ void QIMPenStroke::smooth( QArray<int> &sig) { QArray<int> nsig = sig.copy(); int a; for ( unsigned i = 1; i < sig.count()-2; i++ ) { a = 0; for ( int j = -1; j <= 1; j++ ) { a += sig[ i + j ]; } nsig[i] = a / 3; } sig = nsig; } /*! Write the character's data to the stream. */ QDataStream &operator<< (QDataStream &s, const QIMPenStroke &ws) { s << ws.startPoint; s << ws.links.count(); for ( unsigned i = 0; i < ws.links.count(); i++ ) { s << (Q_INT8)ws.links[i].dx; s << (Q_INT8)ws.links[i].dy; } return s; } /*! Read the character's data from the stream. */ QDataStream &operator>> (QDataStream &s, QIMPenStroke &ws) { Q_INT8 i8; s >> ws.startPoint; ws.lastPoint = ws.startPoint; unsigned size; s >> size; ws.links.resize( size ); diff --git a/inputmethods/handwriting/qimpenwordpick.cpp b/inputmethods/handwriting/qimpenwordpick.cpp index 8ee103d..39745c6 100644 --- a/inputmethods/handwriting/qimpenwordpick.cpp +++ b/inputmethods/handwriting/qimpenwordpick.cpp @@ -1,113 +1,113 @@ /********************************************************************** ** Copyright (C) 2000 Trolltech AS. All rights reserved. ** ** This file is part of Qtopia Environment. ** ** This file may be distributed and/or modified under the terms of the ** GNU General Public License version 2 as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL included in the ** packaging of this file. ** ** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE ** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. ** ** See http://www.trolltech.com/gpl/ for GPL licensing information. ** ** Contact info@trolltech.com if any conditions of this licensing are ** not clear to you. ** **********************************************************************/ #include <qpainter.h> #include "qimpenwordpick.h" QIMPenWordPick::QIMPenWordPick( QWidget *parent, const char *name, WFlags f ) : QFrame( parent, name, f ) { clickWord = -1; setSizePolicy( QSizePolicy( QSizePolicy::Minimum, QSizePolicy::Fixed ) ); } void QIMPenWordPick::clear() { words.clear(); repaint(); } QSize QIMPenWordPick::sizeHint() const { return QSize( -1, font().pixelSize()+2 ); } void QIMPenWordPick::setWords( const QIMPenMatch::MatchWordList &w ) { words.clear(); QListIterator<QIMPenMatch::MatchWord> it( w ); for ( ; it.current(); ++it ) { words.append( it.current()->word ); } repaint(); } int QIMPenWordPick::onWord( QPoint p ) { int x = 2; int idx = 0; for ( QStringList::Iterator it = words.begin(); it != words.end(); ++it ) { QString word = *it; int w = fontMetrics().width( word ); if ( x + w > width() ) break; if ( p.x() > x-2 && p.x() < x + w + 2 ) return idx; x += w + 5; if ( !idx ) x += 3; idx++; } return -1; } void QIMPenWordPick::paintEvent( QPaintEvent * ) { QPainter p(this); int x = 2; int h = p.fontMetrics().ascent() + 1; int idx = 0; for ( QStringList::Iterator it = words.begin(); it != words.end(); ++it ) { QString word = *it; int w = p.fontMetrics().width( word ); if ( x + w > width() ) break; if ( idx == clickWord ) { p.fillRect( x, 0, w, height(), black ); p.setPen( white ); } else { p.setPen( colorGroup().text() ); } p.drawText( x, h, word ); x += w + 5; if ( !idx ) x += 3; idx++; } } void QIMPenWordPick::mousePressEvent( QMouseEvent *e ) { clickWord = onWord( e->pos() ); repaint(); } void QIMPenWordPick::mouseReleaseEvent( QMouseEvent *e ) { int wordIdx = onWord( e->pos() ); if ( wordIdx >= 0 && wordIdx == clickWord ) { - //qDebug( "Clicked %s", words[wordIdx].latin1() ); + //odebug << "Clicked " << words[wordIdx].latin1() << oendl; emit wordClicked( words[wordIdx] ); } clickWord = -1; repaint(); } |