summaryrefslogtreecommitdiff
Side-by-side diff
Diffstat (more/less context) (ignore whitespace changes)
-rw-r--r--inputmethods/handwriting/qimpenchar.cpp9
-rw-r--r--inputmethods/handwriting/qimpeninput.cpp24
-rw-r--r--inputmethods/handwriting/qimpenmatch.cpp35
-rw-r--r--inputmethods/handwriting/qimpenstroke.cpp15
-rw-r--r--inputmethods/handwriting/qimpenwordpick.cpp2
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();
}