From 15318cad33835e4e2dc620d033e43cd930676cdd Mon Sep 17 00:00:00 2001 From: kergoth Date: Fri, 25 Jan 2002 22:14:26 +0000 Subject: Initial revision --- (limited to 'inputmethods/handwriting') diff --git a/inputmethods/handwriting/.cvsignore b/inputmethods/handwriting/.cvsignore new file mode 100644 index 0000000..285856f --- a/dev/null +++ b/inputmethods/handwriting/.cvsignore @@ -0,0 +1,5 @@ +moc_* +*.moc +Makefile +qimpenprefbase.h +qimpenprefbase.cpp diff --git a/inputmethods/handwriting/Makefile.in b/inputmethods/handwriting/Makefile.in new file mode 100644 index 0000000..aabb5aa --- a/dev/null +++ b/inputmethods/handwriting/Makefile.in @@ -0,0 +1,281 @@ +############################################################################# + +####### Compiler, tools and options + +CXX = $(SYSCONF_CXX) $(QT_CXX_MT) +CXXFLAGS= $(SYSCONF_CXXFLAGS_QT) $(SYSCONF_CXXFLAGS) $(SYSCONF_CXXFLAGS_LIB) +CC = $(SYSCONF_CC) $(QT_C_MT) +CFLAGS = $(SYSCONF_CFLAGS) $(SYSCONF_CFLAGS_LIB) +INCPATH = -I$(QPEDIR)/include +LFLAGS = $(SYSCONF_LFLAGS_QT) $(SYSCONF_RPATH_QT) $(SYSCONF_LFLAGS) $(QT_LFLAGS_MT) +LIBS = $(SUBLIBS) -lqpe $(SYSCONF_LIBS_QT) $(SYSCONF_LIBS_QTAPP) +MOC = $(SYSCONF_MOC) +UIC = $(SYSCONF_UIC) + +####### Target + +DESTDIR = ../../plugins/inputmethods/ +VER_MAJ = 1 +VER_MIN = 0 +VER_PATCH = 0 +TARGET = qhandwriting +TARGET1 = lib$(TARGET).so.$(VER_MAJ) + +####### Files + +HEADERS = qimpenchar.h \ + qimpenprofile.h \ + qimpencombining.h \ + qimpenhelp.h \ + qimpeninput.h \ + qimpenmatch.h \ + qimpensetup.h \ + qimpenstroke.h \ + qimpenwidget.h \ + qimpenwordpick.h \ + handwritingimpl.h +SOURCES = qimpenchar.cpp \ + qimpenprofile.cpp \ + qimpencombining.cpp \ + qimpenhelp.cpp \ + qimpeninput.cpp \ + qimpenmatch.cpp \ + qimpensetup.cpp \ + qimpenstroke.cpp \ + qimpenwidget.cpp \ + qimpenwordpick.cpp \ + handwritingimpl.cpp +OBJECTS = qimpenchar.o \ + qimpenprofile.o \ + qimpencombining.o \ + qimpenhelp.o \ + qimpeninput.o \ + qimpenmatch.o \ + qimpensetup.o \ + qimpenstroke.o \ + qimpenwidget.o \ + qimpenwordpick.o \ + handwritingimpl.o \ + qimpenprefbase.o +INTERFACES = qimpenprefbase.ui +UICDECLS = qimpenprefbase.h +UICIMPLS = qimpenprefbase.cpp +SRCMOC = moc_qimpenhelp.cpp \ + moc_qimpeninput.cpp \ + moc_qimpenmatch.cpp \ + moc_qimpensetup.cpp \ + moc_qimpenwidget.cpp \ + moc_qimpenwordpick.cpp \ + moc_qimpenprefbase.cpp +OBJMOC = moc_qimpenhelp.o \ + moc_qimpeninput.o \ + moc_qimpenmatch.o \ + moc_qimpensetup.o \ + moc_qimpenwidget.o \ + moc_qimpenwordpick.o \ + moc_qimpenprefbase.o + + +####### Implicit rules + +.SUFFIXES: .cpp .cxx .cc .C .c + +.cpp.o: + $(CXX) -c $(CXXFLAGS) $(INCPATH) -o $@ $< + +.cxx.o: + $(CXX) -c $(CXXFLAGS) $(INCPATH) -o $@ $< + +.cc.o: + $(CXX) -c $(CXXFLAGS) $(INCPATH) -o $@ $< + +.C.o: + $(CXX) -c $(CXXFLAGS) $(INCPATH) -o $@ $< + +.c.o: + $(CC) -c $(CFLAGS) $(INCPATH) -o $@ $< + +####### Build rules + + +all: $(DESTDIR)$(SYSCONF_LINK_TARGET) + +$(DESTDIR)$(SYSCONF_LINK_TARGET): $(UICDECLS) $(OBJECTS) $(OBJMOC) $(SUBLIBS) + $(SYSCONF_LINK_LIB) + +moc: $(SRCMOC) + +tmake: + tmake handwriting.pro + +clean: + -rm -f $(OBJECTS) $(OBJMOC) $(SRCMOC) $(UICIMPLS) $(UICDECLS) + -rm -f *~ core + -rm -f allmoc.cpp + +####### Extension Modules + +listpromodules: + @echo + +listallmodules: + @echo + +listaddonpromodules: + @echo + +listaddonentmodules: + @echo + + +REQUIRES= + +####### Sub-libraries + + +###### Combined headers + + + +####### Compile + +qimpenchar.o: qimpenchar.cpp \ + qimpencombining.h \ + qimpenchar.h \ + qimpenstroke.h + +qimpenprofile.o: qimpenprofile.cpp \ + qimpencombining.h \ + qimpenchar.h \ + qimpenstroke.h \ + qimpenprofile.h + +qimpencombining.o: qimpencombining.cpp \ + qimpencombining.h \ + qimpenchar.h \ + qimpenstroke.h + +qimpenhelp.o: qimpenhelp.cpp \ + qimpenwidget.h \ + qimpenchar.h \ + qimpenstroke.h \ + qimpencombining.h \ + qimpenmatch.h \ + qimpenhelp.h \ + qimpenprofile.h + +qimpeninput.o: qimpeninput.cpp \ + qimpenwidget.h \ + qimpenchar.h \ + qimpenstroke.h \ + qimpensetup.h \ + qimpenprofile.h \ + qimpeninput.h \ + qimpencombining.h \ + qimpenwordpick.h \ + qimpenmatch.h \ + qimpenhelp.h + +qimpenmatch.o: qimpenmatch.cpp \ + qimpenmatch.h \ + qimpenchar.h \ + qimpenstroke.h + +qimpensetup.o: qimpensetup.cpp \ + qimpenwidget.h \ + qimpenchar.h \ + qimpenstroke.h \ + qimpenprefbase.h \ + qimpensetup.h \ + qimpenprofile.h + +qimpenstroke.o: qimpenstroke.cpp \ + qimpenstroke.h + +qimpenwidget.o: qimpenwidget.cpp \ + qimpenchar.h \ + qimpenstroke.h \ + qimpenwidget.h + +qimpenwordpick.o: qimpenwordpick.cpp \ + qimpenwordpick.h \ + qimpenmatch.h \ + qimpenchar.h \ + qimpenstroke.h + +handwritingimpl.o: handwritingimpl.cpp \ + qimpeninput.h \ + qimpenprofile.h \ + qimpenchar.h \ + qimpenstroke.h \ + handwritingimpl.h + +qimpenprefbase.h: qimpenprefbase.ui + $(UIC) qimpenprefbase.ui -o $(INTERFACE_DECL_PATH)/qimpenprefbase.h + +qimpenprefbase.cpp: qimpenprefbase.ui + $(UIC) qimpenprefbase.ui -i qimpenprefbase.h -o qimpenprefbase.cpp + +qimpenprefbase.o: qimpenprefbase.cpp \ + qimpenprefbase.h \ + qimpenprefbase.ui + +moc_qimpenhelp.o: moc_qimpenhelp.cpp \ + qimpenhelp.h \ + qimpenchar.h \ + qimpenstroke.h \ + qimpenprofile.h + +moc_qimpeninput.o: moc_qimpeninput.cpp \ + qimpeninput.h \ + qimpenprofile.h \ + qimpenchar.h \ + qimpenstroke.h + +moc_qimpenmatch.o: moc_qimpenmatch.cpp \ + qimpenmatch.h \ + qimpenchar.h \ + qimpenstroke.h + +moc_qimpensetup.o: moc_qimpensetup.cpp \ + qimpensetup.h \ + qimpenprofile.h \ + qimpenchar.h \ + qimpenstroke.h + +moc_qimpenwidget.o: moc_qimpenwidget.cpp \ + qimpenwidget.h \ + qimpenchar.h \ + qimpenstroke.h + +moc_qimpenwordpick.o: moc_qimpenwordpick.cpp \ + qimpenwordpick.h \ + qimpenmatch.h \ + qimpenchar.h \ + qimpenstroke.h + +moc_qimpenprefbase.o: moc_qimpenprefbase.cpp \ + qimpenprefbase.h + +moc_qimpenhelp.cpp: qimpenhelp.h + $(MOC) qimpenhelp.h -o moc_qimpenhelp.cpp + +moc_qimpeninput.cpp: qimpeninput.h + $(MOC) qimpeninput.h -o moc_qimpeninput.cpp + +moc_qimpenmatch.cpp: qimpenmatch.h + $(MOC) qimpenmatch.h -o moc_qimpenmatch.cpp + +moc_qimpensetup.cpp: qimpensetup.h + $(MOC) qimpensetup.h -o moc_qimpensetup.cpp + +moc_qimpenwidget.cpp: qimpenwidget.h + $(MOC) qimpenwidget.h -o moc_qimpenwidget.cpp + +moc_qimpenwordpick.cpp: qimpenwordpick.h + $(MOC) qimpenwordpick.h -o moc_qimpenwordpick.cpp + +moc_qimpenprefbase.cpp: qimpenprefbase.h + $(MOC) qimpenprefbase.h -o moc_qimpenprefbase.cpp + + diff --git a/inputmethods/handwriting/handwriting.pro b/inputmethods/handwriting/handwriting.pro new file mode 100644 index 0000000..999552b --- a/dev/null +++ b/inputmethods/handwriting/handwriting.pro @@ -0,0 +1,33 @@ +TEMPLATE = lib +CONFIG += qt warn_on release +HEADERS = qimpenchar.h \ + qimpenprofile.h \ + qimpencombining.h \ + qimpenhelp.h \ + qimpeninput.h \ + qimpenmatch.h \ + qimpensetup.h \ + qimpenstroke.h \ + qimpenwidget.h \ + qimpenwordpick.h \ + handwritingimpl.h +SOURCES = qimpenchar.cpp \ + qimpenprofile.cpp \ + qimpencombining.cpp \ + qimpenhelp.cpp \ + qimpeninput.cpp \ + qimpenmatch.cpp \ + qimpensetup.cpp \ + qimpenstroke.cpp \ + qimpenwidget.cpp \ + qimpenwordpick.cpp \ + handwritingimpl.cpp +INTERFACES = qimpenprefbase.ui +TARGET = qhandwriting +DESTDIR = ../../plugins/inputmethods +INCLUDEPATH += $(QPEDIR)/include +DEPENDPATH += ../$(QPEDIR)/include ../../taskbar +LIBS += -lqpe +VERSION = 1.0.0 + +TRANSLATIONS += ../../i18n/de/libqhandwriting.ts diff --git a/inputmethods/handwriting/handwritingimpl.cpp b/inputmethods/handwriting/handwritingimpl.cpp new file mode 100644 index 0000000..c39e1aa --- a/dev/null +++ b/inputmethods/handwriting/handwritingimpl.cpp @@ -0,0 +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 +#include +#include +#include "qimpeninput.h" +#include "handwritingimpl.h" + +/* XPM */ +static const char * pen_xpm[] = { +"28 13 9 1", +" c None", +". c #000000", +"+ c #FFE993", +"@ c #8292FF", +"# c #F7C500", +"$ c #C69F00", +"% c #0022FF", +"& c #000F72", +"* c #A3732C", +" . ", +" .+. ", +" .@#$. ", +" .@%&. ", +" .@%&. ", +" . .@%&. ", +" . .@%&. ", +" . .@%&. ", +" ... ... .. .@%&. ", +" . . . . . .*.&. ", +" . . . . . .**. ", +" ... ... .. ... ", +" "}; + +HandwritingImpl::HandwritingImpl() + : input(0), icn(0), ref(0) +{ +} + +HandwritingImpl::~HandwritingImpl() +{ + delete input; + delete icn; +} + +QWidget *HandwritingImpl::inputMethod( QWidget *parent, Qt::WFlags f ) +{ + if ( !input ) + input = new QIMPenInput( parent, "Handwriting", f ); + return input; +} + +void HandwritingImpl::resetState() +{ + if ( input ) + input->resetState(); +} + +QPixmap *HandwritingImpl::icon() +{ + if ( !icn ) + icn = new QPixmap( (const char **)pen_xpm ); + return icn; +} + +QString HandwritingImpl::name() +{ + return qApp->translate( "InputMethods", "Handwriting" ); +} + +void HandwritingImpl::onKeyPress( QObject *receiver, const char *slot ) +{ + if ( input ) + QObject::connect( input, SIGNAL(key(ushort,ushort,ushort,bool,bool)), receiver, slot ); +} + +#ifndef QT_NO_COMPONENT +QRESULT HandwritingImpl::queryInterface( const QUuid &uuid, QUnknownInterface **iface ) +{ + *iface = 0; + if ( uuid == IID_QUnknown ) + *iface = this; + else if ( uuid == IID_InputMethod ) + *iface = this; + + if ( *iface ) + (*iface)->addRef(); + return QS_OK; +} + +Q_EXPORT_INTERFACE() +{ + Q_CREATE_INSTANCE( HandwritingImpl ) +} +#endif diff --git a/inputmethods/handwriting/handwritingimpl.h b/inputmethods/handwriting/handwritingimpl.h new file mode 100644 index 0000000..1215853 --- a/dev/null +++ b/inputmethods/handwriting/handwritingimpl.h @@ -0,0 +1,51 @@ +/********************************************************************** +** 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. +** +**********************************************************************/ +#ifndef HANDWRITINGIMPL_H +#define HANDWRITINGIMPL_H + +#include + +class QIMPenInput; +class QPixmap; + +class HandwritingImpl : public InputMethodInterface +{ +public: + HandwritingImpl(); + virtual ~HandwritingImpl(); + +#ifndef QT_NO_COMPONENT + QRESULT queryInterface( const QUuid&, QUnknownInterface** ); + Q_REFCOUNT +#endif + + virtual QWidget *inputMethod( QWidget *parent, Qt::WFlags f ); + virtual void resetState(); + virtual QPixmap *icon(); + virtual QString name(); + virtual void onKeyPress( QObject *receiver, const char *slot ); + +private: + QIMPenInput *input; + QPixmap *icn; + ulong ref; +}; + +#endif diff --git a/inputmethods/handwriting/qimpenchar.cpp b/inputmethods/handwriting/qimpenchar.cpp new file mode 100644 index 0000000..9c38ec9 --- a/dev/null +++ b/inputmethods/handwriting/qimpenchar.cpp @@ -0,0 +1,505 @@ +/********************************************************************** +** 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 +#include +#include +#include +#include +#include +#include "qimpencombining.h" +#include "qimpenchar.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 ); +#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() ); +#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; + } + + return ok; +} + +/*! + Save this character set. +*/ +bool QIMPenCharSet::save( Domain d ) +{ + if ( filename( d ).isEmpty() ) + return FALSE; + + bool ok = FALSE; + + QString fn = filename( d ); + QString tmpFn = fn + ".new"; + QFile file( tmpFn ); + if ( file.open( IO_WriteOnly|IO_Raw ) ) { + QDataStream ds( &file ); + 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 ) ) ) { + ds << *pc; + } + if ( file.status() != IO_Ok ) + break; + } + 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 = 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() ); + } +*/ + 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/qimpenchar.h b/inputmethods/handwriting/qimpenchar.h new file mode 100644 index 0000000..9a5f687 --- a/dev/null +++ b/inputmethods/handwriting/qimpenchar.h @@ -0,0 +1,157 @@ +/********************************************************************** +** 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. +** +**********************************************************************/ + +#ifndef QIMPENCHAR_H_ +#define QIMPENCHAR_H_ + +#include +#include +#include +#include "qimpenstroke.h" + +struct QIMPenSpecialKeys { + int code; + char *name; +}; + +extern const QIMPenSpecialKeys qimpen_specialKeys[]; + + +class QIMPenChar +{ +public: + QIMPenChar(); + QIMPenChar( const QIMPenChar & ); + + unsigned int character() const { return ch; } + void setCharacter( unsigned int c ) { ch = c; } + + const QString &data() const { return d; } + void setData( const QString &ba ) { d = ba; } + + QString name() const; + bool isEmpty() const { return strokes.isEmpty(); } + unsigned int strokeCount() const { return strokes.count(); } + unsigned int strokeLength( int s ) const; + void clear(); + int match( QIMPenChar *ch ); + const QIMPenStrokeList &penStrokes() { return strokes; } + QPoint startingPoint() const { return strokes.getFirst()->startingPoint(); } + QRect boundingRect(); + + void setFlag( int f ) { flags |= f; } + void clearFlag( int f ) { flags &= ~f; } + bool testFlag( int f ) { return flags & f; } + + enum Flags { System=0x01, Deleted=0x02, CombineRight=0x04, Data=0x08 }; + // Correspond to codes in template files. Do not change values. + enum Mode { ModeBase=0x4000, Caps=0x4001, Shortcut=0x4002, CapsLock=0x4003, + Punctuation=0x4004, Symbol=0x4005, Extended=0x4006 }; + + QIMPenChar &operator=( const QIMPenChar &s ); + + void addStroke( QIMPenStroke * ); + +protected: + unsigned int ch; + QString d; + Q_UINT8 flags; + QIMPenStrokeList strokes; + + friend QDataStream &operator<< (QDataStream &, const QIMPenChar &); + friend QDataStream &operator>> (QDataStream &, QIMPenChar &); +}; + +typedef QList QIMPenCharList; +typedef QListIterator QIMPenCharIterator; + +QDataStream & operator<< (QDataStream & s, const QIMPenChar &ws); +QDataStream & operator>> (QDataStream & s, QIMPenChar &ws); + +struct QIMPenCharMatch +{ + int error; + QIMPenChar *penChar; + + bool operator>( const QIMPenCharMatch &m ); + bool operator<( const QIMPenCharMatch &m ); + bool operator<=( const QIMPenCharMatch &m ); +}; + +typedef QValueList QIMPenCharMatchList; + + +class QIMPenCharSet +{ +public: + QIMPenCharSet(); + QIMPenCharSet( const QString &fn ); + + bool isEmpty() const { return chars.isEmpty(); } + unsigned int count() const { return chars.count(); } + void clear() { chars.clear(); } + + void setDescription( const QString &d ) { desc = d; } + QString description() const { return desc; } + void setTitle( const QString &t ) { csTitle = t; } + QString title() const { return csTitle; } + + QIMPenCharMatchList match( QIMPenChar *ch ); + void addChar( QIMPenChar *ch ); + void removeChar( QIMPenChar *ch ); + QIMPenChar *at( int i ); + + unsigned maximumStrokes() const { return maxStrokes; } + + void up( QIMPenChar *ch ); + void down( QIMPenChar *ch ); + + enum Domain { System, User }; + enum Type { Unknown=0x00, Lower=0x01, Upper=0x02, Combining=0x04, + Numeric=0x08, Punctuation=0x10, Symbol=0x20, Shortcut=0x40 }; + + const QIMPenCharList &characters() const { return chars; } + + void setType( Type t ) { csType = t; } + Type type() const { return csType; } + + const QString &filename( Domain d ) const; + void setFilename( const QString &fn, Domain d=System ); + bool load( const QString &fn, Domain d=System ); + bool save( Domain d=System ); + +protected: + void markDeleted( uint ch ); + +protected: + QString csTitle; + QString desc; + QString sysFilename; + QString userFilename; + Type csType; + unsigned maxStrokes; + QIMPenCharList chars; + QIMPenCharMatchList matches; +}; + +typedef QList QIMPenCharSetList; +typedef QListIterator QIMPenCharSetIterator; + +#endif diff --git a/inputmethods/handwriting/qimpencombining.cpp b/inputmethods/handwriting/qimpencombining.cpp new file mode 100644 index 0000000..30459e7 --- a/dev/null +++ b/inputmethods/handwriting/qimpencombining.cpp @@ -0,0 +1,141 @@ +/********************************************************************** +** 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 +#include +#include +#include +#include +#include "qimpencombining.h" + +static unsigned int combiningSymbols[] = { '\\', '/', '^', '~', '\"', 'o' }; +static unsigned int combiningChars[][7] = { + // \ / ^ ~ " + { 'A', 0x00C0, 0x00C1, 0x00C2, 0x00C3, 0x00C4, 0x00C5 }, + { 'O', 0x00D2, 0x00D3, 0x00D4, 0x00D5, 0x00D6, 0x0000 }, + { 'U', 0x00D9, 0x00DA, 0x00DB, 0x0000, 0x00DC, 0x0000 }, + { 'E', 0x00C8, 0x00C9, 0x00CA, 0x0000, 0x00CB, 0x0000 }, + { 'I', 0x00CC, 0x00CD, 0x00CE, 0x0000, 0x00CF, 0x0000 }, + { 'a', 0x00E0, 0x00E1, 0x00E2, 0x00E3, 0x00E4, 0x00E5 }, + { 'e', 0x00E8, 0x00E9, 0x00EA, 0x0000, 0x00EB, 0x0000 }, + { 'i', 0x00EC, 0x00ED, 0x00EE, 0x0000, 0x00EF, 0x0000 }, + { 'n', 0x0000, 0x0000, 0x0000, 0x00F1, 0x0000, 0x0000 }, + { 'o', 0x00F2, 0x00F3, 0x00F4, 0x00F5, 0x00F6, 0x0000 }, + { 'u', 0x00F9, 0x00FA, 0x00FB, 0x0000, 0x00FC, 0x0000 }, + { 'y', 0x0000, 0x00FD, 0x0000, 0x0000, 0x00FF, 0x0000 }, + { 0, 0, 0, 0, 0, 0, 0 } +}; + + +QIMPenCombining::QIMPenCombining() +{ +} + +QIMPenCombining::QIMPenCombining( const QString &fn ) + : QIMPenCharSet( fn ) +{ +} + +void QIMPenCombining::addCombined( QIMPenCharSet *cs ) +{ + unsigned int count = cs->count(); + QIMPenCharIterator it( cs->characters() ); + for ( ; it.current() && count; ++it, --count ) { + QIMPenChar *pc = it.current(); + if ( pc->testFlag( QIMPenChar::Deleted ) ) + continue; + int charIdx = findCombining( pc->character() ); + if ( charIdx < 0 ) + continue; + for ( int i = 0; i < 6; i++ ) { + if ( combiningChars[charIdx][i+1] ) { + QIMPenCharIterator cit( chars ); + for ( ; cit.current(); ++cit ) { + QIMPenChar *accentPc = cit.current(); + if ( accentPc->character() == combiningSymbols[i] ) { + QIMPenChar *combined = combine( pc, accentPc ); + combined->setCharacter( combiningChars[charIdx][i+1] ); + cs->addChar( combined ); + } + } + } + } + } +} + +int QIMPenCombining::findCombining( unsigned int ch ) const +{ + int i = 0; + while ( combiningChars[i][0] ) { + if ( combiningChars[i][0] == ch ) + return i; + i++; + } + + return -1; +} + +QIMPenChar *QIMPenCombining::combine( QIMPenChar *base, QIMPenChar *accent ) +{ + QRect brect = base->boundingRect(); + QRect arect = accent->boundingRect(); + int offset; + if ( accent->testFlag( QIMPenChar::CombineRight ) ) + offset = brect.left() - arect.left() + brect.width() + 2; + else + offset = brect.left() - arect.left() + (brect.width() - arect.width())/2; + QIMPenChar *combined = 0; + if ( base->character() == 'i' ) { + // Hack to remove the dot from i's when combining. + if ( base->penStrokes().count() > 1 ) { + combined = new QIMPenChar; + QIMPenStrokeIterator it( base->penStrokes() ); + for ( unsigned int i = 0; i < base->penStrokes().count()-1; ++it, i++ ) { + QIMPenStroke *st = new QIMPenStroke( *(it.current()) ); + combined->addStroke( st ); + } + combined->setFlag( QIMPenChar::System ); + } + } + if ( !combined ) + combined = new QIMPenChar( *base ); + QIMPenStrokeIterator it( accent->penStrokes() ); + for ( ; it.current(); ++it ) { + QIMPenStroke *st = new QIMPenStroke( *(it.current()) ); + st->setStartingPoint( st->startingPoint() + QPoint(offset, 0 )); + combined->addStroke( st ); + delete st; + } + + return combined; +} + +QIMPenChar *QIMPenCombining::penChar( int type ) +{ + QIMPenCharIterator it( chars ); + for ( ; it.current(); ++it ) { + QIMPenChar *pc = it.current(); + if ( pc->character() == combiningSymbols[type] ) + return pc; + } + + return 0; +} + diff --git a/inputmethods/handwriting/qimpencombining.h b/inputmethods/handwriting/qimpencombining.h new file mode 100644 index 0000000..778cb8f --- a/dev/null +++ b/inputmethods/handwriting/qimpencombining.h @@ -0,0 +1,41 @@ +/********************************************************************** +** 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. +** +**********************************************************************/ + +#ifndef QIMPENCOMBINING_H_ +#define QIMPENCOMBINING_H_ + +#include +#include "qimpenchar.h" + +class QIMPenCombining : public QIMPenCharSet +{ +public: + QIMPenCombining(); + QIMPenCombining( const QString &fn ); + + void addCombined( QIMPenCharSet * ); + +protected: + int findCombining( unsigned int ch ) const; + QIMPenChar *combine( QIMPenChar *base, QIMPenChar *accent ); + QIMPenChar *penChar( int type ); +}; + +#endif diff --git a/inputmethods/handwriting/qimpenhelp.cpp b/inputmethods/handwriting/qimpenhelp.cpp new file mode 100644 index 0000000..5ee46a2 --- a/dev/null +++ b/inputmethods/handwriting/qimpenhelp.cpp @@ -0,0 +1,410 @@ +/********************************************************************** +** 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 "qimpenwidget.h" +#include "qimpencombining.h" +#include "qimpenmatch.h" +#include "qimpenhelp.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +/* XPM */ +static const char * const left_xpm[] = { +"16 16 2 1", +" c None", +". c #000000", +" ", +" ", +" ", +" . ", +" .. ", +" ... ", +" .... ", +" ..... ", +" ...... ", +" ..... ", +" .... ", +" ... ", +" .. ", +" . ", +" ", +" "}; + + +/* XPM */ +static const char * const right_xpm[] = { +"16 16 2 1", +" c None", +". c #000000", +" ", +" ", +" ", +" . ", +" .. ", +" ... ", +" .... ", +" ..... ", +" ...... ", +" ..... ", +" .... ", +" ... ", +" .. ", +" . ", +" ", +" "}; + +class CharListItem : public QListBoxText +{ +public: + CharListItem( const QString &text, uint c ) + : QListBoxText( text ) + { + _code = c; + } + + uint code() const { return _code; } + +protected: + uint _code; +}; + +HandwritingHelp::HandwritingHelp( QIMPenProfile *p, QWidget *parent, const char *name, WFlags f ) + : QTabWidget( parent, name, f ) +{ + setCaption( tr("Handwriting Help") ); + QTextView *help = new QTextView( this ); + help->setFrameStyle( QFrame::NoFrame ); + help->setText( + tr( "
  • When you start to use the handwriting recogniser " + "write slowly, accurately and firmly." + "
  • Use the guide lines when drawing your characters." + "
  • When drawing a character with multiple strokes, each " + "successive stroke must be drawn before the grayed strokes are erased." + "
  • Practice your handwriting using the handwriting trainer." + "
  • When adding your own character templates make sure they " + "are sufficiently different from other characters' templates." + "
") ); + + addTab( help, tr("Tips") ); + + HandwritingTrainer *trainer = new HandwritingTrainer( p, this ); + addTab( trainer, tr("Trainer") ); +} + +void HandwritingHelp::showEvent( QShowEvent * ) +{ + Global::hideInputMethod(); +} + +void HandwritingHelp::hideEvent( QHideEvent * ) +{ + Global::showInputMethod(); +} + +//--------------------------------------------------------------------------- + +HandwritingTrainer::HandwritingTrainer( QIMPenProfile *p, QWidget *parent, const char *name ) + : QWidget( parent, name ), profile(p) +{ + QGridLayout *gl = new QGridLayout( this, 5, 2, 0, 4 ); + gl->setRowStretch( 1, 1 ); + gl->setRowStretch( 2, 1 ); + gl->setColStretch( 1, 1 ); + + charSetCombo = new QComboBox( this ); + gl->addMultiCellWidget( charSetCombo, 0, 0, 0, 1 ); + connect( charSetCombo, SIGNAL(activated(int)), SLOT(selectCharSet(int))); + QIMPenCharSetIterator it( profile->charSets() ); + for ( ; it.current(); ++it ) { + charSetCombo->insertItem( it.current()->description() ); + } + + charList = new QListBox( this ); + charList->setHScrollBarMode( QListBox::AlwaysOff ); + charList->setFixedWidth( 80 ); + connect( charList, SIGNAL(highlighted(int)), this, SLOT(selectChar(int)) ); + gl->addMultiCellWidget( charList, 1, 2, 0, 0 ); + + QLabel *help = new QLabel( this ); + help->setAlignment( AlignLeft | AlignVCenter | WordBreak ); + gl->addWidget( help, 1, 1 ); + help->setText( + tr( "Select a character from the list. The writing area on the left " + "shows the reference character. Practice writing in the area on " + "the right.") ); + + result = new QLabel( this ); + gl->addMultiCellWidget( result, 2, 3, 1, 1 ); + + matcher = new QIMPenMatch( this ); + matcher->setCharSet( currentSet ); + connect( matcher, SIGNAL(noMatch()), this, SLOT(noMatch()) ); + connect( matcher, SIGNAL(matchedCharacters(const QIMPenCharMatchList &)), + this, SLOT(matched(const QIMPenCharMatchList &)) ); + + QHBoxLayout *hb = new QHBoxLayout(); + gl->addLayout( hb, 3, 0 ); + prevBtn = new QPushButton( this ); + prevBtn->setPixmap( QPixmap( (const char **)left_xpm ) ); + connect( prevBtn, SIGNAL(clicked()), SLOT(prevChar())); + hb->addWidget( prevBtn ); + + nextBtn = new QPushButton( this ); + nextBtn->setPixmap( QPixmap( (const char **)right_xpm ) ); + connect( nextBtn, SIGNAL(clicked()), SLOT(nextChar())); + hb->addWidget( nextBtn ); + + refPw = new QIMPenWidget( this ); + refPw->setReadOnly( TRUE ); + gl->addWidget( refPw, 4, 0 ); + + pracPw = new QIMPenWidget( this ); + connect( matcher, SIGNAL(removeStroke()), pracPw, SLOT(removeStroke()) ); + connect( pracPw, SIGNAL(beginStroke()), + this, SLOT(beginStroke()) ); + connect( pracPw, SIGNAL(stroke( QIMPenStroke * )), + this, SLOT(strokeEntered( QIMPenStroke * )) ); + connect( pracPw, SIGNAL(beginStroke()), + matcher, SLOT(beginStroke()) ); + connect( pracPw, SIGNAL(stroke( QIMPenStroke * )), + matcher, SLOT(strokeEntered( QIMPenStroke * )) ); + gl->addWidget( pracPw, 4, 1 ); + + redrawTimer = new QTimer( this ); + connect( redrawTimer, SIGNAL(timeout()), this, SLOT(redrawChar()) ); + redrawTimer->start( 5000 ); + + currentSet = 0; + charSetCombo->setCurrentItem( 1 ); + selectCharSet( 1 ); +} + +HandwritingTrainer::~HandwritingTrainer() +{ +} + +void HandwritingTrainer::showEvent( QShowEvent * ) +{ + redrawChar(); + redrawTimer->start( 5000 ); +} + +void HandwritingTrainer::setCurrentChar( QIMPenChar *c ) +{ + currentChar = c; + refPw->showCharacter( currentChar ); + pracPw->clear(); + if ( currentChar ) { + prevBtn->setEnabled( findPrev() != 0 ); + nextBtn->setEnabled( findNext() != 0 ); + } + result->setText( "" ); + redrawTimer->start( 5000 ); +} + +void HandwritingTrainer::selectChar( int i ) +{ + currentChar = 0; + currentCode = ((CharListItem *)charList->item(i))->code(); + QIMPenCharIterator it(currentSet->characters() ); + for ( ; it.current(); ++it ) { + if ( it.current()->character() == currentCode && + !it.current()->testFlag( QIMPenChar::Deleted ) ) { + setCurrentChar( it.current() ); + break; + } + } + if ( !it.current() ) + setCurrentChar( 0 ); +} + +void HandwritingTrainer::selectCharSet( int i ) +{ + if ( currentSet ) { + refPw->removeCharSet( 0 ); + pracPw->removeCharSet( 0 ); + } + currentSet = profile->charSets().at( i ); + fillCharList(); + refPw->insertCharSet( currentSet ); + pracPw->insertCharSet( currentSet ); + matcher->setCharSet( currentSet ); + if ( charList->count() ) { + charList->setSelected( 0, TRUE ); + selectChar(0); + } +} + +void HandwritingTrainer::noMatch() +{ + result->setText( "No match" ); +} + +void HandwritingTrainer::matched( const QIMPenCharMatchList &ml ) +{ + int maxErr = 20000 + (*ml.begin()).penChar->strokeLength(0) * 1000; + int baseErr = (*ml.begin()).penChar->strokeLength(0) * 250; + unsigned int numStrokes = (*ml.begin()).penChar->strokeCount(); + QIMPenCharMatchList::ConstIterator it; + /* + for ( it = ml.begin(); it != ml.end(); ++it ) { + if ( (*it).penChar->strokeCount() == numStrokes ) { + if ( (*it).error > maxErr ) + maxErr = (*it).error; + } + } + */ + int i; + QString res; + QTextStream ts(&res, IO_WriteOnly); + ts << "" << tr("Matched: "); + for ( i = 0, it = ml.begin(); it != ml.end() && i < 4; ++it, i++ ) { + if ( (*it).penChar->strokeCount() == numStrokes ) { + int rate = 100 - ( ((*it).error - baseErr) * 100 ) / maxErr; + if ( it != ml.begin() ) { + if ( rate < -10 ) + continue; + ts << "
"; + ts << tr("Similar to: "); + } + ts << ""; + if ( (*it).penChar->character() == currentChar->character() ) + ts << ""; + ts << Qtopia::escapeString((*it).penChar->name()); + ts << " (" << rateString(rate) << ")"; + if ( (*it).penChar->character() == currentChar->character() ) + ts << ""; + ts << ""; + } + } + ts << "
"; + result->setText( res ); +} + +QString HandwritingTrainer::rateString( int rate ) const +{ + if ( rate < 1 ) + rate = 1; + if ( rate > 100 ) + rate = 100; + return tr("%1%").arg(rate); +} + +void HandwritingTrainer::prevChar() +{ + QIMPenChar *pc = findPrev(); + if ( pc ) + setCurrentChar( pc ); +} + +void HandwritingTrainer::nextChar() +{ + QIMPenChar *pc = findNext(); + if ( pc ) + setCurrentChar( pc ); +} + +void HandwritingTrainer::redrawChar() +{ + if ( currentChar ) + refPw->showCharacter( currentChar ); +} + +void HandwritingTrainer::beginStroke() +{ + redrawTimer->start( 5000 ); +} + +void HandwritingTrainer::strokeEntered( QIMPenStroke * ) +{ + pracPw->greyStroke(); +} + +QIMPenChar *HandwritingTrainer::findPrev() +{ + if ( !currentChar ) + return 0; + QIMPenCharIterator it( currentSet->characters() ); + bool found = FALSE; + for ( it.toLast(); it.current(); --it ) { + if ( !found && it.current() == currentChar ) + found = TRUE; + else if ( found && it.current()->character() == currentCode && + !it.current()->testFlag( QIMPenChar::Deleted ) ) { + return it.current(); + } + } + + return 0; +} + +QIMPenChar *HandwritingTrainer::findNext() +{ + if ( !currentChar ) + return 0; + QIMPenCharIterator it( currentSet->characters() ); + bool found = FALSE; + for ( ; it.current(); ++it ) { + if ( !found && it.current() == currentChar ) + found = TRUE; + else if ( found && it.current()->character() == currentCode && + !it.current()->testFlag( QIMPenChar::Deleted ) ) { + return it.current(); + } + } + + return 0; +} + +void HandwritingTrainer::fillCharList() +{ + charList->clear(); + QIMPenCharIterator it( currentSet->characters() ); + CharListItem *li = 0; + for ( ; it.current(); ++it ) { + uint ch = it.current()->character(); + QString n = it.current()->name(); + if ( !n.isEmpty() ) + li = new CharListItem( n, ch ); + if ( li ) { + CharListItem *i = (CharListItem *)charList->findItem( li->text() ); + if ( !i || i->code() != ch ) { + charList->insertItem( li ); + } else { + delete li; + li = 0; + } + } + } + currentChar = 0; +} + diff --git a/inputmethods/handwriting/qimpenhelp.h b/inputmethods/handwriting/qimpenhelp.h new file mode 100644 index 0000000..07cb035 --- a/dev/null +++ b/inputmethods/handwriting/qimpenhelp.h @@ -0,0 +1,85 @@ +/********************************************************************** +** 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 +#include +#include "qimpenchar.h" +#include "qimpenprofile.h" + +class QListBox; +class QLabel; +class QComboBox; +class QPushButton; +class QTimer; +class QIMPenWidget; +class QIMPenMatch; + +class HandwritingHelp : public QTabWidget +{ + Q_OBJECT +public: + HandwritingHelp( QIMPenProfile *p, QWidget *parent=0, const char *name=0, WFlags f=0 ); + +protected: + virtual void showEvent( QShowEvent * ); + virtual void hideEvent( QHideEvent * ); +}; + +class HandwritingTrainer : public QWidget +{ + Q_OBJECT +public: + HandwritingTrainer( QIMPenProfile *p, QWidget *parent=0, const char *name=0 ); + ~HandwritingTrainer(); + +private slots: + void selectChar( int ); + void selectCharSet( int ); + void noMatch(); + void matched( const QIMPenCharMatchList &ml ); + void prevChar(); + void nextChar(); + void redrawChar(); + void beginStroke(); + void strokeEntered( QIMPenStroke * ); + +private: + virtual void showEvent( QShowEvent * ); + QString rateString( int rate ) const; + void setCurrentChar( QIMPenChar *c ); + void fillCharList(); + QIMPenChar *findPrev(); + QIMPenChar *findNext(); + +private: + QIMPenMatch *matcher; + QIMPenCharSet *currentSet; + QIMPenChar *currentChar; + QIMPenProfile *profile; + uint currentCode; + QIMPenWidget *refPw; + QIMPenWidget *pracPw; + QComboBox *charSetCombo; + QListBox *charList; + QLabel *result; + QPushButton *prevBtn; + QPushButton *nextBtn; + QTimer *redrawTimer; +}; + diff --git a/inputmethods/handwriting/qimpeninput.cpp b/inputmethods/handwriting/qimpeninput.cpp new file mode 100644 index 0000000..6718b26 --- a/dev/null +++ b/inputmethods/handwriting/qimpeninput.cpp @@ -0,0 +1,515 @@ +/********************************************************************** +** 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 "qimpenwidget.h" +#include "qimpensetup.h" +#include "qimpeninput.h" +#include "qimpencombining.h" +#include "qimpenwordpick.h" +#include "qimpenmatch.h" +#include "qimpenhelp.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +// 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 f ) + : QFrame( parent, name, f ), 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( "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 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" ); + 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" ); + if ( mode == SwitchLock ) { +// qDebug( "Switch to normal" ); + pw->changeCharSet( profile->lowercase(), currCharSet ); + mode = Switch; + } else { +// qDebug( "Switch to upper" ); + pw->changeCharSet( profile->uppercase(), currCharSet ); + mode = Switch; + } + } + break; + case QIMPenChar::CapsLock: + if ( profile->style() == QIMPenProfile::ToggleCases ) { +// qDebug( "CapsLock" ); + if ( mode == Switch && + baseSets.at(currCharSet) == profile->uppercase() ) { +// qDebug( "Switch to normal" ); + 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" ); + 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" ); + pw->changeCharSet( profile->punctuation(), currCharSet ); + mode = Switch; + } + break; + case QIMPenChar::Symbol: + if ( profile->symbol() ) { + //qDebug( "Switch to symbol" ); + 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" ); + 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() ); + 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/qimpeninput.h b/inputmethods/handwriting/qimpeninput.h new file mode 100644 index 0000000..b4e4006 --- a/dev/null +++ b/inputmethods/handwriting/qimpeninput.h @@ -0,0 +1,94 @@ +/********************************************************************** +** 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. +** +**********************************************************************/ + +#ifndef _QIMPENINPUT_H_ +#define _QIMPENINPUT_H_ + +#include "qimpenprofile.h" + +#include + +#include +#include +#include + +class QPushButton; +class QTimer; +class QIMPenWidget; +class QIMPenSetup; +class QIMPenWordPick; +class QIMPenMatch; +class HandwritingHelp; + +class QIMPenInput : public QFrame +{ + Q_OBJECT +public: + QIMPenInput( QWidget *parent = 0, const char *name = 0, WFlags f = 0 ); + virtual ~QIMPenInput(); + + void resetState(); + + QSize sizeHint() const; + +signals: + void key( ushort, ushort, ushort, bool, bool ); + +private slots: + void wordPicked( const QString & ); + void selectCharSet( int ); + void beginStroke(); + void strokeEntered( QIMPenStroke *st ); + void matchedCharacters( const QIMPenCharMatchList &cl ); + void keypress( uint scan_uni ); + void erase(); + void help(); + void setup(); + void backspace(); + void enter(); + +private: + void loadProfiles(); + void selectProfile( const QString &name ); + void handleExtended( const QString & ); + void updateWordMatch( QIMPenCharMatchList &ml ); + void matchWords(); + void scanDict( const QDawg::Node* n, int ipos, const QString& str, int error ); + + enum Mode { Normal, Switch, SwitchLock }; + +private: + Mode mode; + QRect prefRect; + QIMPenWidget *pw; + QPushButton *helpBtn; + QPushButton *setupBtn; + QIMPenSetup *setupDlg; + QIMPenMatch *matcher; + QGuardedPtr helpDlg; + QIMPenProfile *profile; + QList profileList; + QIMPenCharSet *shortcutCharSet; + QIMPenCharSetList baseSets; + int currCharSet; + QIMPenWordPick *wordPicker; +}; + +#endif // _QIMPENINPUT_H_ diff --git a/inputmethods/handwriting/qimpenmatch.cpp b/inputmethods/handwriting/qimpenmatch.cpp new file mode 100644 index 0000000..0d3e25a --- a/dev/null +++ b/inputmethods/handwriting/qimpenmatch.cpp @@ -0,0 +1,365 @@ +/********************************************************************** +** 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 +#include + +#include +#include + +#include + +#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 -------------" ); +#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" ); +#endif + ml = multiCharSet->match( &testChar ); + } else { +#ifdef DEBUG_QIMPEN + qDebug( "Matching against single set" ); +#endif + ml = charSet->match( &testChar ); + } + + processMatches( ml ); +} + +void QIMPenMatch::processMatches( QIMPenCharMatchList &ml ) +{ +#ifdef DEBUG_QIMPEN + qDebug( "Entering strokes.count() = %d", strokes.count() ); +#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())) ); +#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())) ); +#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" ); +#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" ); +#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" ); +#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" ); +#endif + } else { + if ( !ml.count() ) { +#ifdef DEBUG_QIMPEN + qDebug( "** Failed" ); +#endif + canErase = FALSE; + } else { +#ifdef DEBUG_QIMPEN + qDebug( "Need more strokes" ); +#endif + if ( strokes.count() == 1 ) + canErase = FALSE; + multiCharSet = charSet; + } + output = FALSE; + emit noMatch(); + } + + if ( eraseLast && canErase ) { +#ifdef DEBUG_QIMPEN + qDebug( "deleting last" ); +#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" ); + wordChars.clear(); + wordMatches.clear(); + wordEntered = QString(); + } else if ( code == Qt::Key_Backspace ) { + //qDebug( "Word Matching: Handle backspace" ); + 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 it( wordMatches); + for ( ; it.current(); ++it ) { + qDebug( QString("Match word: %1").arg(it.current()->word) ); + } +*/ + } + //qDebug( "Possibles: Good %d, total %d", goodMatches, wordMatches.count() ); + 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/qimpenmatch.h b/inputmethods/handwriting/qimpenmatch.h new file mode 100644 index 0000000..d4a730e --- a/dev/null +++ b/inputmethods/handwriting/qimpenmatch.h @@ -0,0 +1,107 @@ +/********************************************************************** +** 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. +** +**********************************************************************/ + +#ifndef _QIMPENMATCH_H_ +#define _QIMPENMATCH_H_ + +#include "qimpenchar.h" + +#include + +#include + +class QTimer; +class QIMPenWidget; +class QIMPenSetup; +class QIMPenWordPick; + +class QIMPenMatch : public QObject +{ + Q_OBJECT +public: + QIMPenMatch( QObject *parent=0, const char *name=0 ); + virtual ~QIMPenMatch(); + + void resetState(); + void backspace(); + void setMultiStrokeTimeout( int t ) { multiTimeout = t; } + + const QString &word() const { return wordEntered; } + + void setWordMatchingEnabled( bool e ) { doWordMatching = e; } + bool isWordMatchingEnabled() const { return doWordMatching; } + + struct MatchWord { + MatchWord( const QString &w, int e ) { word = w; error = e; } + QString word; + int error; + }; + + class MatchWordList : public QList + { + public: + int compareItems( QCollection::Item item1, QCollection::Item item2 ) { + MatchWord *m1 = (MatchWord *)item1; + MatchWord *m2 = (MatchWord *)item2; + return m1->error - m2->error; + } + }; + +public slots: + void setCharSet( QIMPenCharSet * ); + void beginStroke(); + void strokeEntered( QIMPenStroke *st ); + +signals: + void erase(); + void noMatch(); + void removeStroke(); + void keypress( uint ch ); + void matchedCharacters( const QIMPenCharMatchList & ); + void matchedWords( const QIMPenMatch::MatchWordList & ); + +protected slots: + void processMatches( QIMPenCharMatchList &ml ); + void endMulti(); + +protected: + void updateWordMatch( QIMPenCharMatchList &ml ); + void matchWords(); + void scanDict( const QDawg::Node* n, int ipos, const QString& str, int error ); + + QList strokes; + QIMPenChar *prevMatchChar; + int prevMatchError; + QIMPenCharSet *charSet; + QIMPenCharSet *multiCharSet; + QList wordChars; + MatchWordList wordMatches; + QString wordEntered; + bool doWordMatching; + bool canErase; + int errorThreshold; + int goodMatches; + int badMatches; + int maxGuess; + QTimer *multiTimer; + int multiTimeout; +}; + +#endif // _QIMPENINPUT_H_ diff --git a/inputmethods/handwriting/qimpenprefbase.ui b/inputmethods/handwriting/qimpenprefbase.ui new file mode 100644 index 0000000..1639d1a --- a/dev/null +++ b/inputmethods/handwriting/qimpenprefbase.ui @@ -0,0 +1,185 @@ + +QIMPenPrefBase + + QWidget + + name + QIMPenPrefBase + + + geometry + + 0 + 0 + 247 + 280 + + + + caption + Form1 + + + + margin + 11 + + + spacing + 6 + + + QLabel + + name + TextLabel1 + + + text + Multi-stroke character timeout: + + + + QLayoutWidget + + name + Layout2 + + + + margin + 0 + + + spacing + 6 + + + QSlider + + name + multiStrokeSlider + + + minValue + 250 + + + maxValue + 1000 + + + lineStep + 10 + + + pageStep + 50 + + + value + 500 + + + orientation + Horizontal + + + tickmarks + Right + + + + QLabel + + name + multiStrokeLabel + + + minimumSize + + 45 + 0 + + + + text + ms + + + alignment + AlignVCenter|AlignRight + + + hAlign + + + + + + QButtonGroup + + name + inputStyle + + + title + Input areas displayed + + + + margin + 11 + + + spacing + 6 + + + QRadioButton + + name + bothCasesRadio + + + text + Upper and lower case areas + + + + QRadioButton + + name + toggleCaseRadio + + + text + Lower case (toggle Upper case) + + + + + + + name + Spacer2 + + + orientation + Vertical + + + sizeType + Expanding + + + sizeHint + + 20 + 20 + + + + + + diff --git a/inputmethods/handwriting/qimpenprofile.cpp b/inputmethods/handwriting/qimpenprofile.cpp new file mode 100644 index 0000000..4b5bb83 --- a/dev/null +++ b/inputmethods/handwriting/qimpenprofile.cpp @@ -0,0 +1,245 @@ +/********************************************************************** +** 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 "qimpencombining.h" +#include "qimpenprofile.h" + +#include +#include +#include + + +QIMPenProfile::QIMPenProfile( const QString &fn ) + : filename( fn ) +{ + sets.setAutoDelete( true ); + + Config config( filename, Config::File ); + config.setGroup( "Handwriting" ); + + pname = config.readEntry( "Name" ); + pdesc = config.readEntry( "Description" ); + + tstyle = config.readBoolEntry( "CanSelectStyle", false ); + + wordMatch = config.readBoolEntry( "MatchWords", true ); + + config.setGroup( "Settings" ); + + pstyle = BothCases; + QString s = config.readEntry( "Style", "BothCases" ); + if ( s == "ToggleCases" ) + pstyle = ToggleCases; + + msTimeout = config.readNumEntry( "MultiTimeout", 500 ); + + // Read user configuration + Config usrConfig( userConfig() ); + usrConfig.setGroup( "Settings" ); + msTimeout = usrConfig.readNumEntry( "MultiTimeout", msTimeout ); + + if ( tstyle && usrConfig.hasKey( "Style" ) ) { + pstyle = BothCases; + QString s = usrConfig.readEntry( "Style", "BothCases" ); + if ( s == "ToggleCases" ) + pstyle = ToggleCases; + } +} + +void QIMPenProfile::setStyle( Style s ) +{ + if ( tstyle && s != pstyle ) { + pstyle = s; + Config config( userConfig() ); + config.setGroup( "Settings" ); + QString s = pstyle == ToggleCases ? "ToggleCases" : "BothCases"; + config.writeEntry( "Style", s ); + } +} + +void QIMPenProfile::setMultiStrokeTimeout( int t ) +{ + if ( t != msTimeout ) { + msTimeout = t; + Config config( userConfig() ); + config.setGroup( "Settings" ); + config.writeEntry( "MultiTimeout", msTimeout ); + } +} + +QString QIMPenProfile::userConfig() +{ + QString un = filename; + int pos = un.findRev( '/' ); + if ( pos >= 0 ) + un = un.mid( pos + 1 ); + pos = un.find( '.' ); + if ( pos > 0 ) + un.truncate( pos ); + + un = "handwriting-" + un; + + return un; +} + +void QIMPenProfile::loadData() +{ + Config config( filename, Config::File ); + config.setGroup( "CharSets" ); + + QString baseDir = QPEApplication::qpeDir(); + baseDir += "/etc/"; + // accents + QIMPenCombining *combining = 0; + QString s = config.readEntry( "Combining" ); + if ( !s.isEmpty() ) { + combining = new QIMPenCombining( baseDir + "qimpen/" + s ); + if ( combining->isEmpty() ) { + delete combining; + combining = 0; + } + } + // uppercase latin1 + QIMPenCharSet *cs = 0; + s = config.readEntry( "Uppercase" ); + if ( !s.isEmpty() ) { + cs = new QIMPenCharSet( baseDir + "qimpen/" + s ); + cs->load( Global::applicationFileName("qimpen",s), QIMPenCharSet::User ); + if ( !cs->isEmpty() ) { + if ( combining ) + combining->addCombined( cs ); + sets.append( cs ); + } else { + delete cs; + } + } + // lowercase latin1 + s = config.readEntry( "Lowercase" ); + if ( !s.isEmpty() ) { + cs = new QIMPenCharSet( baseDir + "qimpen/" + s ); + cs->load( Global::applicationFileName("qimpen",s), QIMPenCharSet::User ); + if ( !cs->isEmpty() ) { + if ( combining ) + combining->addCombined( cs ); + sets.append( cs ); + } else { + delete cs; + } + } + // numeric (may comtain punctuation and symbols) + s = config.readEntry( "Numeric" ); + if ( !s.isEmpty() ) { + cs = new QIMPenCharSet( baseDir + "qimpen/" + s ); + cs->load( Global::applicationFileName("qimpen",s), QIMPenCharSet::User ); + if ( !cs->isEmpty() ) { + sets.append( cs ); + } else { + delete cs; + } + } + // punctuation + s = config.readEntry( "Punctuation" ); + if ( !s.isEmpty() ) { + cs = new QIMPenCharSet( baseDir + "qimpen/" + s ); + cs->load( Global::applicationFileName("qimpen",s), QIMPenCharSet::User ); + if ( !cs->isEmpty() ) { + sets.append( cs ); + } else { + delete cs; + } + } + // symbol + s = config.readEntry( "Symbol" ); + if ( !s.isEmpty() ) { + cs = new QIMPenCharSet( baseDir + "qimpen/" + s ); + cs->load( Global::applicationFileName("qimpen",s), QIMPenCharSet::User ); + if ( !cs->isEmpty() ) { + sets.append( cs ); + } else { + delete cs; + } + } + // shortcut + s = config.readEntry( "Shortcut" ); + if ( !s.isEmpty() ) { + cs = new QIMPenCharSet( baseDir + "qimpen/" + s ); + cs->load( Global::applicationFileName("qimpen",s), QIMPenCharSet::User ); + if ( !cs->isEmpty() ) { + sets.append( cs ); + } else { + delete cs; + } + } + + if ( combining ) + delete combining; +} + +QIMPenCharSet *QIMPenProfile::uppercase() +{ + return find( QIMPenCharSet::Upper ); +} + +QIMPenCharSet *QIMPenProfile::lowercase() +{ + return find( QIMPenCharSet::Lower ); +} + +QIMPenCharSet *QIMPenProfile::numeric() +{ + return find( QIMPenCharSet::Numeric ); +} + +QIMPenCharSet *QIMPenProfile::punctuation() +{ + return find( QIMPenCharSet::Punctuation ); +} + +QIMPenCharSet *QIMPenProfile::symbol() +{ + return find( QIMPenCharSet::Symbol ); +} + +QIMPenCharSet *QIMPenProfile::shortcut() +{ + return find( QIMPenCharSet::Shortcut ); +} + +QIMPenCharSetList &QIMPenProfile::charSets() +{ + if ( sets.isEmpty() ) + loadData(); + return sets; +} + +QIMPenCharSet *QIMPenProfile::find( QIMPenCharSet::Type t ) +{ + if ( sets.isEmpty() ) + loadData(); + QIMPenCharSetIterator it( sets ); + for ( ; it.current(); ++it ) { + if ( it.current()->type() == t ) + return it.current(); + } + + return 0; +} + + diff --git a/inputmethods/handwriting/qimpenprofile.h b/inputmethods/handwriting/qimpenprofile.h new file mode 100644 index 0000000..4ce4367 --- a/dev/null +++ b/inputmethods/handwriting/qimpenprofile.h @@ -0,0 +1,70 @@ +/********************************************************************** +** 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. +** +**********************************************************************/ + +#ifndef QIMPENPROFILE_H_ +#define QIMPENPROFILE_H_ + +#include "qimpenchar.h" + +class QIMPenProfile +{ +public: + QIMPenProfile( const QString &fn ); + + const QString &name() const { return pname; } + const QString &description() const { return pdesc; } + + enum Style { ToggleCases, BothCases }; + Style style() const { return pstyle; } + void setStyle( Style s ); + + bool canSelectStyle() const { return tstyle; } + + int multiStrokeTimeout() const { return msTimeout; } + void setMultiStrokeTimeout( int t ); + + bool matchWords() const { return wordMatch; } + + QIMPenCharSet *uppercase(); + QIMPenCharSet *lowercase(); + QIMPenCharSet *numeric(); + QIMPenCharSet *punctuation(); + QIMPenCharSet *symbol(); + QIMPenCharSet *shortcut(); + QIMPenCharSet *find( QIMPenCharSet::Type t ); + + QIMPenCharSetList &charSets(); + +private: + QString userConfig(); + void loadData(); + +private: + QIMPenCharSetList sets; + QString filename; + QString pname; + QString pdesc; + Style pstyle; + bool tstyle; + int msTimeout; + bool wordMatch; +}; + +#endif diff --git a/inputmethods/handwriting/qimpensetup.cpp b/inputmethods/handwriting/qimpensetup.cpp new file mode 100644 index 0000000..a6ae3a8 --- a/dev/null +++ b/inputmethods/handwriting/qimpensetup.cpp @@ -0,0 +1,656 @@ +/********************************************************************** +** 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 "qimpenwidget.h" +#include "qimpenprefbase.h" +#include "qimpensetup.h" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +/* XPM */ +static const char * const left_xpm[] = { +"16 16 2 1", +" c None", +". c #000000", +" ", +" ", +" ", +" . ", +" .. ", +" ... ", +" .... ", +" ..... ", +" ...... ", +" ..... ", +" .... ", +" ... ", +" .. ", +" . ", +" ", +" "}; + + +/* XPM */ +static const char * const right_xpm[] = { +"16 16 2 1", +" c None", +". c #000000", +" ", +" ", +" ", +" . ", +" .. ", +" ... ", +" .... ", +" ..... ", +" ...... ", +" ..... ", +" .... ", +" ... ", +" .. ", +" . ", +" ", +" "}; + + + +QIMPenSetup::QIMPenSetup( QIMPenProfile *p, QWidget *parent, + const char *name, bool modal, int WFlags ) + : QDialog( parent, name, modal, WFlags ), profileCombo(0), profile(p) +{ + setCaption( tr("Setup Handwriting Input") ); + + QVBoxLayout *vb = new QVBoxLayout( this ); + +#if 0 + profileList.setAutoDelete( true ); + QHBoxLayout *hb = new QHBoxLayout( vb ); + hb->setMargin( 6 ); + QLabel *l = new QLabel( tr("Character Profile:"), this ); + hb->addWidget( l ); + profileCombo = new QComboBox( this ); + connect( profileCombo, SIGNAL(activated(const QString &)), + this, SLOT(selectProfile(const QString &)) ); + hb->addWidget( profileCombo ); + loadProfiles(); +#else + profileList.append( profile ); +#endif + + QTabWidget *tw = new QTabWidget( this ); + vb->addWidget( tw ); + + pref = new QIMPenPrefBase( this ); + tw->addTab( pref, tr("Preferences") ); + + pref->inputStyle->setExclusive( TRUE ); + + style = profile->style() == QIMPenProfile::ToggleCases ? 1 : 0; + pref->inputStyle->setButton( style ); + connect( pref->inputStyle, SIGNAL(clicked(int)), + this, SLOT(styleClicked(int)) ); + pref->inputStyle->setEnabled( profile->canSelectStyle() ); + + multiTimeout = profile->multiStrokeTimeout(); + pref->multiStrokeSlider->setValue( multiTimeout ); + multiTimeoutChanged( multiTimeout ); + connect( pref->multiStrokeSlider, SIGNAL(valueChanged(int)), + this, SLOT(multiTimeoutChanged(int)) ); + + edit = new QIMPenEdit( p, tw ); + tw->addTab( edit, tr("Customize") ); +} + +void QIMPenSetup::loadProfiles() +{ + 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 ); + profileCombo->insertItem( p->name() ); + if ( p->name() == profile->name() ) { + profileCombo->setCurrentItem( profileCombo->count()-1 ); + profile = p; + } + } +} + +void QIMPenSetup::styleClicked( int id ) +{ + style = id; +} + +void QIMPenSetup::multiTimeoutChanged( int v ) +{ + multiTimeout = v; + pref->multiStrokeLabel->setText( tr("%1 ms").arg(v) ); +} + +void QIMPenSetup::selectProfile( const QString &p ) +{ + if ( p == profile->name() ) + return; + + profile->setStyle( style ? QIMPenProfile::ToggleCases : QIMPenProfile::BothCases ); + profile->setMultiStrokeTimeout( multiTimeout ); + + for ( int i = 0; i < (int)profileList.count(); i++ ) { + if ( profileList.at(i)->name() == p ) { + profile = profileList.at(i); + style = profile->style() == QIMPenProfile::ToggleCases ? 1 : 0; + pref->inputStyle->setButton( style ); + pref->inputStyle->setEnabled( profile->canSelectStyle() ); + multiTimeout = profile->multiStrokeTimeout(); + pref->multiStrokeSlider->setValue( multiTimeout ); + multiTimeoutChanged( multiTimeout ); + edit->setProfile( profile ); + break; + } + } +} + +void QIMPenSetup::accept() +{ + profile->setStyle( style ? QIMPenProfile::ToggleCases : QIMPenProfile::BothCases ); + profile->setMultiStrokeTimeout( multiTimeout ); + // Save current profile + if ( profileCombo ) { + Config config( "handwriting" ); + config.setGroup( "Settings" ); + config.writeEntry( "Profile", profileCombo->currentText() ); + } + // Save charsets + bool ok = TRUE; + for ( int i = 0; i < (int)profileList.count(); i++ ) { + QIMPenProfile *prof = profileList.at(i); + QIMPenCharSetIterator it(prof->charSets()); + for ( ; it.current(); ++it ) { + if ( !(it.current()->save( QIMPenCharSet::User )) ) { + ok = FALSE; + break; + } + } + } + if ( !ok ) { + if ( QMessageBox::critical( 0, tr( "Out of space" ), + tr("Unable to save information.\n" + "Free up some space\n" + "and try again.\n" + "\nQuit anyway?"), + QMessageBox::Yes|QMessageBox::Escape, + QMessageBox::No|QMessageBox::Default ) + != QMessageBox::No ) { + QDialog::accept(); + } + } else { + QDialog::accept(); + } +} + +//--------------------------------------------------------------------------- + +QIMPenInputCharDlg::QIMPenInputCharDlg( QWidget *parent, const char *name, + bool modal, int WFlags) + : QDialog( parent, name, modal, WFlags ) +{ + setCaption( tr("Enter new character") ); + uni = 0; + + QVBoxLayout *vb = new QVBoxLayout( this, 10 ); + + QHBoxLayout *hb = new QHBoxLayout(); + vb->addLayout( hb ); + + QLabel *label = new QLabel( "Character:", this ); + hb->addWidget( label ); + + QComboBox *cb = new QComboBox( TRUE, this ); + connect( cb, SIGNAL(activated(int)), SLOT(setSpecial(int)) ); + connect( cb, SIGNAL(textChanged(const QString &)), + SLOT(setCharacter(const QString &)) ); + addSpecial( cb ); + cb->setEditText( "" ); + hb->addWidget( cb ); + + hb = new QHBoxLayout(); + vb->addLayout( hb ); + + QPushButton *pb = new QPushButton( "OK", this ); + connect( pb, SIGNAL(clicked()), SLOT(accept())); + hb->addWidget( pb ); + pb = new QPushButton( "Cancel", this ); + connect( pb, SIGNAL(clicked()), SLOT(reject())); + hb->addWidget( pb ); + + cb->setFocus(); +} + +void QIMPenInputCharDlg::addSpecial( QComboBox *cb ) +{ + int i = 0; + while ( qimpen_specialKeys[i].code != Key_unknown ) { + cb->insertItem( qimpen_specialKeys[i].name ); + i++; + } +} + +void QIMPenInputCharDlg::setSpecial( int sp ) +{ + uni = qimpen_specialKeys[sp].code << 16; +} + +void QIMPenInputCharDlg::setCharacter( const QString &string ) +{ + uni = string[0].unicode(); +} + +//--------------------------------------------------------------------------- + +class CharListItem : public QListBoxText +{ +public: + CharListItem( const QString &text, uint c ) + : QListBoxText( text ) + { + _code = c; + } + + uint code() const { return _code; } + +protected: + uint _code; +}; + +/*! + \class QIMPenEdit qimpensetup.h + + Class to allow users to input totally useless character definitions + which could match any number of the default set. +*/ + +QIMPenEdit::QIMPenEdit( QIMPenProfile *p, QWidget *parent, + const char *name ) + : QWidget( parent, name ), profile(p) +{ + currentChar = 0; + currentCode = 0; + inputChar = new QIMPenChar(); + + QVBoxLayout *tvb = new QVBoxLayout( this, 5 ); + + QGridLayout *gl = new QGridLayout( tvb, 4, 2 ); + gl->setRowStretch( 1, 1 ); + gl->addRowSpacing( 2, 35 ); + gl->addRowSpacing( 3, 35 ); + + charSetCombo = new QComboBox( this ); + gl->addMultiCellWidget( charSetCombo, 0, 0, 0, 1 ); + connect( charSetCombo, SIGNAL(activated(int)), SLOT(selectCharSet(int))); + QIMPenCharSetIterator it( profile->charSets() ); + for ( ; it.current(); ++it ) { + charSetCombo->insertItem( it.current()->description() ); + } + + charList = new QListBox( this ); + charList->setMinimumHeight( charList->sizeHint().height() ); + connect( charList, SIGNAL(highlighted(int)), SLOT(selectChar(int)) ); + gl->addWidget( charList, 1, 0 ); + + pw = new QIMPenWidget( this ); + pw->setFixedHeight( 75 ); + gl->addMultiCellWidget( pw, 2, 3, 0, 0 ); + connect( pw, SIGNAL(stroke(QIMPenStroke *)), + SLOT(newStroke(QIMPenStroke *)) ); + + QVBoxLayout *vb = new QVBoxLayout(); + gl->addLayout( vb, 1, 1 ); + newBtn = new QPushButton( tr("New..."), this ); + connect( newBtn, SIGNAL(clicked()), SLOT(addNewChar()) ); + vb->addWidget( newBtn ); + + addBtn = new QPushButton( tr("Add"), this ); + connect( addBtn, SIGNAL(clicked()), SLOT(addChar()) ); + vb->addWidget( addBtn ); + + removeBtn = new QPushButton( tr("Remove"), this ); + connect( removeBtn, SIGNAL(clicked()), SLOT(removeChar()) ); + vb->addWidget( removeBtn ); + + QPushButton *pb = new QPushButton( tr("Default"), this ); + connect( pb, SIGNAL(clicked()), SLOT(defaultChars()) ); + vb->addWidget( pb ); + + QHBoxLayout *hb = new QHBoxLayout(); + gl->addLayout( hb, 2, 1 ); + prevBtn = new QPushButton( this ); + prevBtn->setPixmap( QPixmap( (const char **)left_xpm ) ); + connect( prevBtn, SIGNAL(clicked()), SLOT(prevChar())); + hb->addWidget( prevBtn ); + + nextBtn = new QPushButton( this ); + nextBtn->setPixmap( QPixmap( (const char **)right_xpm ) ); + connect( nextBtn, SIGNAL(clicked()), SLOT(nextChar())); + hb->addWidget( nextBtn ); + + pb = new QPushButton( tr("Clear"), this ); + connect( pb, SIGNAL(clicked()), SLOT(clearChar()) ); + gl->addWidget( pb, 3, 1 ); + + //-- +#if !defined(Q_WS_QWS) + hb = new QHBoxLayout( tvb ); + pb = new QPushButton( "OK", this ); + connect( pb, SIGNAL(clicked()), SLOT(accept()) ); + hb->addWidget( pb ); + + pb = new QPushButton( "Cancel", this ); + connect( pb, SIGNAL(clicked()), SLOT(reject()) ); + hb->addWidget( pb ); +#endif + selectCharSet( 0 ); + charList->setFocus(); + + resize( minimumSize() ); + enableButtons(); +} + +void QIMPenEdit::setProfile( QIMPenProfile *p ) +{ + profile = p; + charSetCombo->clear(); + QIMPenCharSetIterator it( profile->charSets() ); + for ( ; it.current(); ++it ) { + charSetCombo->insertItem( it.current()->description() ); + } + selectCharSet( 0 ); + charList->setFocus(); + enableButtons(); +} + +void QIMPenEdit::selectCharSet( QIMPenCharSet *c ) +{ + int i = 0; + QIMPenCharSetIterator it( profile->charSets() ); + for ( ; it.current(); ++it, i++ ) { + if ( it.current() == c ) { + charSetCombo->setCurrentItem( i ); + selectCharSet( i ); + } + } +} + + +/*! + Fill the character list box with the characters. Duplicates are not + inserted. +*/ +void QIMPenEdit::fillCharList() +{ + charList->clear(); + QIMPenCharIterator it( currentSet->characters() ); + CharListItem *li = 0; + for ( ; it.current(); ++it ) { + uint ch = it.current()->character(); + QString n = it.current()->name(); + if ( !n.isEmpty() ) + li = new CharListItem( n, ch ); + if ( li ) { + CharListItem *i = (CharListItem *)charList->findItem( li->text() ); + if ( !i || i->code() != ch ) { + charList->insertItem( li ); + } else { + delete li; + li = 0; + } + } + } + currentChar = 0; +} + +void QIMPenEdit::enableButtons() +{ + bool add = !inputChar->isEmpty(); + newBtn->setEnabled( add ); + addBtn->setEnabled( add ); + removeBtn->setEnabled( currentChar ); +} + +/*! + Find the previous character with the same code as the current one. + returns 0 if there is no previous character. +*/ +QIMPenChar *QIMPenEdit::findPrev() +{ + if ( !currentChar ) + return 0; + QIMPenCharIterator it( currentSet->characters() ); + bool found = FALSE; + for ( it.toLast(); it.current(); --it ) { + if ( !found && it.current() == currentChar ) + found = TRUE; + else if ( found && it.current()->character() == currentCode && + !it.current()->testFlag( QIMPenChar::Deleted ) ) { + return it.current(); + } + } + + return 0; +} + +/*! + Find the next character with the same code as the current one. + returns 0 if there is no next character. +*/ +QIMPenChar *QIMPenEdit::findNext() +{ + if ( !currentChar ) + return 0; + QIMPenCharIterator it( currentSet->characters() ); + bool found = FALSE; + for ( ; it.current(); ++it ) { + if ( !found && it.current() == currentChar ) + found = TRUE; + else if ( found && it.current()->character() == currentCode && + !it.current()->testFlag( QIMPenChar::Deleted ) ) { + return it.current(); + } + } + + return 0; +} + +void QIMPenEdit::setCurrentChar( QIMPenChar *pc ) +{ + currentChar = pc; + pw->showCharacter( currentChar ); + if ( currentChar ) { + prevBtn->setEnabled( findPrev() != 0 ); + nextBtn->setEnabled( findNext() != 0 ); + } +} + +void QIMPenEdit::prevChar() +{ + QIMPenChar *pc = findPrev(); + if ( pc ) + setCurrentChar( pc ); +} + +void QIMPenEdit::nextChar() +{ + QIMPenChar *pc = findNext(); + if ( pc ) + setCurrentChar( pc ); +} + +void QIMPenEdit::clearChar() +{ + inputChar->clear(); + pw->clear(); + enableButtons(); +} + +void QIMPenEdit::selectChar( int i ) +{ + currentChar = 0; + currentCode = ((CharListItem *)charList->item(i))->code(); + QIMPenCharIterator it(currentSet->characters() ); + for ( ; it.current(); ++it ) { + if ( it.current()->character() == currentCode && + !it.current()->testFlag( QIMPenChar::Deleted ) ) { + setCurrentChar( it.current() ); + break; + } + } + if ( !it.current() ) + setCurrentChar( 0 ); + inputChar->clear(); +} + +void QIMPenEdit::selectCharSet( int i ) +{ + if ( currentSet ) + pw->removeCharSet( 0 ); + currentSet = profile->charSets().at( i ); + fillCharList(); + pw->insertCharSet( currentSet ); + inputChar->clear(); + if ( charList->count() ) { + charList->setSelected( 0, TRUE ); + selectChar(0); + } +} + +void QIMPenEdit::addChar() +{ + if ( !inputChar->isEmpty() ) { + QIMPenChar *pc = new QIMPenChar( *inputChar ); + pc->setCharacter( currentCode ); + + // User characters override all matching system characters. + // Copy and mark deleted identical system characters. + QIMPenCharIterator it(currentSet->characters() ); + QIMPenChar *sc = 0; + while ( (sc = it.current()) != 0 ) { + ++it; + if ( sc->character() == currentCode && + sc->testFlag( QIMPenChar::System ) && + !sc->testFlag( QIMPenChar::Deleted ) ) { + QIMPenChar *cc = new QIMPenChar( *sc ); + cc->clearFlag( QIMPenChar::System ); + currentSet->addChar( cc ); + sc->setFlag( QIMPenChar::Deleted ); + } + } + + currentSet->addChar( pc ); + setCurrentChar( pc ); + inputChar->clear(); + } +} + +void QIMPenEdit::addNewChar() +{ + if ( !inputChar->isEmpty() ) { + QIMPenInputCharDlg dlg( 0, 0, TRUE ); + if ( dlg.exec() ) { + currentCode = dlg.unicode(); + addChar(); + fillCharList(); + for ( unsigned int i = 0; i < charList->count(); i++ ) { + CharListItem *li = (CharListItem *)charList->item(i); + if ( li->code() == dlg.unicode() ) { + charList->setSelected( i, TRUE ); + break; + } + } + } + } +} + +void QIMPenEdit::removeChar() +{ + if ( currentChar ) { + QIMPenChar *pc = findPrev(); + if ( !pc ) pc = findNext(); + if ( currentChar->testFlag( QIMPenChar::System ) ) + currentChar->setFlag( QIMPenChar::Deleted ); + else + currentSet->removeChar( currentChar ); + setCurrentChar( pc ); + } +} + +void QIMPenEdit::defaultChars() +{ + if ( currentCode ) { + currentChar = 0; + bool haveSystem = FALSE; + QIMPenCharIterator it(currentSet->characters() ); + for ( ; it.current(); ++it ) { + if ( it.current()->character() == currentCode && + it.current()->testFlag( QIMPenChar::System ) ) { + haveSystem = TRUE; + break; + } + } + if ( haveSystem ) { + it.toFirst(); + while ( it.current() ) { + QIMPenChar *pc = it.current(); + ++it; + if ( pc->character() == currentCode ) { + if ( pc->testFlag( QIMPenChar::System ) ) { + pc->clearFlag( QIMPenChar::Deleted ); + if ( !currentChar ) + currentChar = pc; + } else { + currentSet->removeChar( pc ); + } + } + } + setCurrentChar( currentChar ); + } + } +} + +void QIMPenEdit::newStroke( QIMPenStroke *st ) +{ + inputChar->addStroke( st ); + enableButtons(); +} + diff --git a/inputmethods/handwriting/qimpensetup.h b/inputmethods/handwriting/qimpensetup.h new file mode 100644 index 0000000..5d3064b --- a/dev/null +++ b/inputmethods/handwriting/qimpensetup.h @@ -0,0 +1,124 @@ +/********************************************************************** +** 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 +#include +#include "qimpenprofile.h" + +class QListBox; +class QPushButton; +class QComboBox; +class QIMPenWidget; +class QIMPenEdit; +class QIMPenPrefBase; + +class QIMPenSetup : public QDialog +{ + Q_OBJECT +public: + QIMPenSetup( QIMPenProfile *p, QWidget *parent=0, + const char *name=0, bool modal=FALSE, int WFlags=0 ); + + QIMPenEdit *editor() { return edit; } + +protected: + void loadProfiles(); + virtual void accept(); + +private slots: + void styleClicked( int ); + void multiTimeoutChanged( int ); + void selectProfile( const QString &p ); + +private: + QComboBox *profileCombo; + QIMPenEdit *edit; + QIMPenPrefBase *pref; + int style; + int multiTimeout; + QIMPenProfile *profile; + QList profileList; +}; + +class QIMPenInputCharDlg : public QDialog +{ + Q_OBJECT +public: + QIMPenInputCharDlg( QWidget *parent = 0, const char *name = 0, + bool modal = FALSE, int WFlags = 0 ); + + unsigned int unicode() const { return uni; } + +protected: + void addSpecial( QComboBox *cb ); + +protected slots: + void setSpecial( int sp ); + void setCharacter( const QString &string ); + +protected: + uint uni; +}; + +class QIMPenEdit : public QWidget +{ + Q_OBJECT +public: + QIMPenEdit( QIMPenProfile *p, QWidget *parent=0, + const char *name=0 ); + + void setProfile( QIMPenProfile *p ); + void selectCharSet( QIMPenCharSet *c ); + +protected: + void fillCharList(); + void enableButtons(); + QIMPenChar *findPrev(); + QIMPenChar *findNext(); + void setCurrentChar( QIMPenChar * ); + +protected slots: + void prevChar(); + void nextChar(); + void clearChar(); + void selectChar( int ); + void selectCharSet( int ); + void addChar(); + void addNewChar(); + void removeChar(); + void defaultChars(); + void newStroke( QIMPenStroke * ); + +protected: + QIMPenWidget *pw; + QComboBox *charSetCombo; + QListBox *charList; + QPushButton *newBtn; + QPushButton *addBtn; + QPushButton *removeBtn; + QPushButton *prevBtn; + QPushButton *nextBtn; + uint currentCode; + QIMPenChar *currentChar; + QIMPenChar *inputChar; + QIMPenCharSet *currentSet; + QIMPenProfile *profile; +}; + diff --git a/inputmethods/handwriting/qimpenstroke.cpp b/inputmethods/handwriting/qimpenstroke.cpp new file mode 100644 index 0000000..3567d6d --- a/dev/null +++ b/inputmethods/handwriting/qimpenstroke.cpp @@ -0,0 +1,646 @@ +/********************************************************************** +** 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 +#include +#include +#include +#include +#include "qimpenstroke.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() ); + 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() ); +} + +/*! + 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" ); +#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 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 ); +#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 ); +#endif + return 400000; + } + + err3 = calcError( asig, pen->asig, 0, TRUE ); + if ( err3 > 60 ) { +#ifdef DEBUG_QIMPEN + qDebug( "asig too great: %d", err3 ); +#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 ); +#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 &base, + const QArray &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 QIMPenStroke::scale( const QArray &s, unsigned count, bool t ) +{ + QArray 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 QIMPenStroke::createBase( const QArray a, int e ) +{ + QArray 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 &sig) +{ + QArray 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 ); + for ( unsigned i = 0; i < size; i++ ) { + s >> i8; + ws.links[i].dx = i8; + s >> i8; + ws.links[i].dy = i8; + ws.lastPoint += QPoint( ws.links[i].dx, ws.links[i].dy ); + } + + return s; +} + + diff --git a/inputmethods/handwriting/qimpenstroke.h b/inputmethods/handwriting/qimpenstroke.h new file mode 100644 index 0000000..bd5ee0e --- a/dev/null +++ b/inputmethods/handwriting/qimpenstroke.h @@ -0,0 +1,91 @@ +/********************************************************************** +** 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. +** +**********************************************************************/ + +#ifndef QIMPENSTROKE_H_ +#define QIMPENSTROKE_H_ + +#include +#include +#include + +struct Q_PACKED QIMPenGlyphLink +{ + signed char dx; + signed char dy; +}; + +class QIMPenStroke +{ +public: + QIMPenStroke(); + QIMPenStroke( const QIMPenStroke & ); + + void clear(); + bool isEmpty() const { return links.isEmpty(); } + unsigned int length() const { return links.count(); } + unsigned int match( QIMPenStroke *st ); + const QArray &chain() const { return links; } + QPoint startingPoint() const { return startPoint; } + void setStartingPoint( const QPoint &p ) { startPoint = p; } + QRect boundingRect(); + + QIMPenStroke &operator=( const QIMPenStroke &s ); + + void beginInput( QPoint p ); + bool addPoint( QPoint p ); + void endInput(); + + QArray sig() { createTanSignature(); return tsig; } // for debugging + +protected: + void createSignatures(); + void createTanSignature(); + void createAngleSignature(); + void createDistSignature(); + int calcError( const QArray &base, const QArray &win, + int off, bool t ); + QArray scale( const QArray &s, unsigned count, bool t = FALSE ); + void internalAddPoint( QPoint p ); + QPoint calcCenter(); + int arcTan( int dy, int dx ); + QArray createBase( const QArray a, int e ); + void smooth( QArray &); + +protected: + QPoint startPoint; + QPoint lastPoint; + QArray links; + QArray tsig; + QArray asig; + QArray dsig; + QRect bounding; + + friend QDataStream &operator<< (QDataStream &, const QIMPenStroke &); + friend QDataStream &operator>> (QDataStream &, QIMPenStroke &); +}; + +typedef QList QIMPenStrokeList; +typedef QListIterator QIMPenStrokeIterator; + +QDataStream & operator<< (QDataStream & s, const QIMPenStroke &ws); +QDataStream & operator>> (QDataStream & s, const QIMPenStroke &ws); + +#endif + diff --git a/inputmethods/handwriting/qimpenwidget.cpp b/inputmethods/handwriting/qimpenwidget.cpp new file mode 100644 index 0000000..8f8f582 --- a/dev/null +++ b/inputmethods/handwriting/qimpenwidget.cpp @@ -0,0 +1,446 @@ +/********************************************************************** +** 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 +#include +#include +#include +#include +#include +#include "qimpenchar.h" +#include "qimpenwidget.h" + +#define TITLE_WIDTH 30 // ### magic + +/*! + \class QIMPenWidget qimpenwidget.h + + Draws characters and allows input of characters. +*/ + +QIMPenWidget::QIMPenWidget( QWidget *parent ) + : QWidget( parent ) +{ + charSets.setAutoDelete( TRUE ); + inputStroke = 0; + outputChar = 0; + outputStroke = 0; + mode = Waiting; + currCharSet = 0; + readOnly = FALSE; + strokes.setAutoDelete( TRUE ); + + timer = new QTimer(this); + connect( timer, SIGNAL(timeout()), SLOT(timeout())); + + setBackgroundColor( qApp->palette().color( QPalette::Active, + QColorGroup::Base ) ); + strokeColor = black; + setFixedHeight( 75 ); +} + +void QIMPenWidget::clear() +{ + timer->stop(); + mode = Waiting; + QRect r( dirtyRect ); + QIMPenStrokeIterator it( strokes ); + while ( it.current() ) { + r |= it.current()->boundingRect(); + ++it; + } + outputChar = 0; + outputStroke = 0; + strokes.clear(); + if ( !r.isNull() ) { + r.moveBy( -2, -2 ); + r.setSize( r.size() + QSize( 4, 4 ) ); + repaint( r ); + } else { + repaint(); + } +} + +void QIMPenWidget::removeStroke() +{ + QRect r( dirtyRect ); + QIMPenStroke *st = strokes.getFirst(); + QRect strokeRect; + if ( st ) + strokeRect = st->boundingRect(); + r |= strokeRect; + strokes.removeFirst(); + if ( !r.isNull() ) { + r.moveBy( -2, -2 ); + r.setSize( r.size() + QSize( 4, 4 ) ); + repaint( r ); + } +} + +void QIMPenWidget::greyStroke() +{ + QRect r( dirtyRect ); + QIMPenStroke *st = strokes.getLast(); + QRect strokeRect; + if ( st ) + strokeRect = st->boundingRect(); + r |= strokeRect; + QColor oldCol = strokeColor; + strokeColor = gray; + if ( !r.isNull() ) { + r.moveBy( -2, -2 ); + r.setSize( r.size() + QSize( 4, 4 ) ); + repaint( r ); + } + strokeColor = oldCol; +} + +/*! + Insert a character set into the list. +*/ +void QIMPenWidget::insertCharSet( QIMPenCharSet *cs, int stretch, int pos ) +{ + CharSetEntry *e = new CharSetEntry; + e->cs = cs; + e->stretch = stretch; + if ( pos < 0 ) + pos = charSets.count(); + charSets.insert( pos, e ); + currCharSet = 0; + emit changeCharSet( currCharSet ); + emit changeCharSet( charSets.at(currCharSet)->cs ); + totalStretch = 0; + CharSetEntryIterator it( charSets ); + for ( ; it.current(); ++it ) + totalStretch += it.current()->stretch; + update(); +} + +/*! + Remove a character set from the list. +*/ +void QIMPenWidget::removeCharSet( int pos ) +{ + if ( pos >= 0 && pos < (int)charSets.count() ) { + charSets.remove( pos ); + currCharSet = 0; + if ( charSets.count() ) { + emit changeCharSet( currCharSet ); + emit changeCharSet( charSets.at(currCharSet)->cs ); + } + totalStretch = 0; + CharSetEntryIterator it( charSets ); + for ( ; it.current(); ++it ) + totalStretch += it.current()->stretch; + update(); + } +} + +void QIMPenWidget::changeCharSet( QIMPenCharSet *cs, int pos ) +{ + if ( pos >= 0 && pos < (int)charSets.count() ) { + CharSetEntry *e = new CharSetEntry; + e->cs = cs; + e->stretch = charSets.at(pos)->stretch; + charSets.remove( pos ); + charSets.insert( pos, e ); + if ( pos == currCharSet ) { + emit changeCharSet( charSets.at(currCharSet)->cs ); + } + update(); + } +} + +void QIMPenWidget::clearCharSets() +{ + charSets.clear(); + currCharSet = 0; + update(); +} + +/*! + Display a character. \a speed determines how quickly the character is + drawn. +*/ +void QIMPenWidget::showCharacter( QIMPenChar *ch, int speed ) +{ + outputChar = 0; + outputStroke = 0; + strokes.clear(); + mode = Output; + repaint(); + if ( !ch || ch->isEmpty() ) { + mode = Waiting; + return; + } + + outputChar = ch; + outputStroke = outputChar->penStrokes().getFirst(); + if ( speed < 0 ) speed = 0; + if ( speed > 20 ) speed = 20; + speed = 50 - speed; + pointIndex = 0; + strokeIndex = 0; + lastPoint = outputStroke->startingPoint(); + QRect br( outputChar->boundingRect() ); + lastPoint.setX( (width() - br.width()) / 2 + (lastPoint.x () - br.left()) ); + QPoint offset = lastPoint - outputStroke->startingPoint(); + br.moveBy( offset.x(), offset.y() ); + dirtyRect |= br; + timer->start( speed ); +} + +/*! + Handle drawing/clearing of characters. +*/ +void QIMPenWidget::timeout() +{ + if ( mode == Output ) { + const QArray &chain = outputStroke->chain(); + if ( pointIndex < chain.count() ) { + QPainter paint( this ); + paint.setBrush( Qt::black ); + for ( unsigned i = 0; i < 3 && pointIndex < chain.count(); i++ ) { + lastPoint.rx() += chain[pointIndex].dx; + lastPoint.ry() += chain[pointIndex].dy; + pointIndex++; + paint.drawRect( lastPoint.x()-1, lastPoint.y()-1, 2, 2 ); + } + } + if ( pointIndex >= chain.count() ) { + QIMPenStrokeList strokes = outputChar->penStrokes(); + if ( strokeIndex < (int)strokes.count() - 1 ) { + pointIndex = 0; + strokeIndex++; + outputStroke = strokes.at( strokeIndex ); + lastPoint = outputChar->startingPoint(); + QRect br( outputChar->boundingRect() ); + lastPoint.setX( (width() - br.width()) / 2 + + (lastPoint.x () - br.left()) ); + QPoint off = lastPoint - outputChar->startingPoint(); + lastPoint = outputStroke->startingPoint() + off; + } else { + timer->stop(); + mode = Waiting; + } + } + } else if ( mode == Waiting ) { + QRect r( dirtyRect ); + if ( !r.isNull() ) { + r.moveBy( -2, -2 ); + r.setSize( r.size() + QSize( 4, 4 ) ); + repaint( r ); + } + } +} + +/*! + If the point \a p is over one of the character set titles, switch + to the set and return TRUE. +*/ +bool QIMPenWidget::selectSet( QPoint p ) +{ + if ( charSets.count() ) { + CharSetEntryIterator it( charSets ); + int spos = 0; + int idx = 0; + for ( ; it.current(); ++it, idx++ ) { + int setWidth = width() * it.current()->stretch / totalStretch; + spos += setWidth; + if ( p.x() < spos ) { + if ( idx != currCharSet ) { + currCharSet = idx; + update( 0, 0, width(), 12 ); + emit changeCharSet( currCharSet ); + emit changeCharSet( charSets.at(currCharSet)->cs ); + } + break; + } + } + } + + return FALSE; +} + +/*! + Hopefully returns a sensible size. +*/ +QSize QIMPenWidget::sizeHint() +{ + return QSize( TITLE_WIDTH * charSets.count(), 75 ); +} + +void QIMPenWidget::mousePressEvent( QMouseEvent *e ) +{ + if ( !readOnly && e->button() == LeftButton && mode == Waiting ) { + // if selectSet returns false the click was not over the + // char set selectors. + if ( !selectSet( e->pos() ) ) { + // start of character input + timer->stop(); + if ( outputChar ) { + outputChar = 0; + outputStroke = 0; + repaint(); + } + mode = Input; + lastPoint = e->pos(); + emit beginStroke(); + inputStroke = new QIMPenStroke; + strokes.append( inputStroke ); + inputStroke->beginInput( e->pos() ); + QPainter paint( this ); + paint.setBrush( Qt::black ); + paint.drawRect( lastPoint.x()-1, lastPoint.y()-1, 2, 2 ); + } + } +} + +void QIMPenWidget::mouseReleaseEvent( QMouseEvent *e ) +{ + if ( !readOnly && e->button() == LeftButton && mode == Input ) { + mode = Waiting; + inputStroke->endInput(); + if ( charSets.count() ) + emit stroke( inputStroke ); + inputStroke = 0; + } +} + +void QIMPenWidget::mouseMoveEvent( QMouseEvent *e ) +{ + if ( !readOnly && mode == Input ) { + int dx = QABS( e->pos().x() - lastPoint.x() ); + int dy = QABS( e->pos().y() - lastPoint.y() ); + if ( dx + dy > 1 ) { + if ( inputStroke->addPoint( e->pos() ) ) { + QPainter paint( this ); + paint.setPen( Qt::black ); + paint.setBrush( Qt::black ); + const QArray &chain = inputStroke->chain(); + QPoint p( e->pos() ); + for ( int i = (int)chain.count()-1; i >= 0; i-- ) { + paint.drawRect( p.x()-1, p.y()-1, 2, 2 ); + p.rx() -= chain[i].dx; + p.ry() -= chain[i].dy; + if ( p == lastPoint ) + break; + } + + /* ### use this when thick lines work properly on all devices + paint.setPen( QPen( Qt::black, 2 ) ); + paint.drawLine( lastPoint, e->pos() ); + */ + } + lastPoint = e->pos(); + } + } +} + +void QIMPenWidget::paintEvent( QPaintEvent * ) +{ + QPainter paint( this ); + + // draw guidelines + paint.setPen( Qt::gray ); + paint.drawLine( 0, 0, width(), 0 ); + int y = height() / 3; + paint.drawLine( 0, y, width(), y ); + y *= 2; + paint.setPen( blue ); + paint.drawLine( 0, y, width(), y ); + paint.setPen( Qt::gray ); + + if ( !charSets.count() ) + return; + + // draw the character set titles + QFont selFont( "helvetica", 8, QFont::Bold ); + QFont font( "helvetica", 8 ); + CharSetEntryIterator it( charSets ); + int spos = 0; + for ( ; it.current(); ++it ) { + int setWidth = width() * it.current()->stretch / totalStretch; + spos += setWidth; + if ( it.current() != charSets.getLast() ) { + paint.drawLine( spos, 0, spos, 5 ); + paint.drawLine( spos, height()-1, spos, height()-6 ); + } + paint.setFont( font ); + int w = paint.fontMetrics().width( it.current()->cs->title() ); + int tpos = spos - setWidth / 2; + paint.drawText( tpos - w/2, 0, w, 12, QPainter::AlignCenter, + it.current()->cs->title() ); + } + + // draw any character that should be displayed when repainted. + QPoint off; + const QIMPenStrokeList *stk = 0; + if ( outputChar && mode == Waiting ) { + stk = &outputChar->penStrokes(); + QPoint p( outputChar->startingPoint() ); + QRect br( outputChar->boundingRect() ); + p.setX( (width() - br.width()) / 2 + (p.x () - br.left()) ); + off = p - outputChar->startingPoint(); + } else if ( mode == Waiting ) { + stk = &strokes; + strokeColor = gray; + } + + if ( stk && !stk->isEmpty() ) { + paint.setPen( strokeColor ); + paint.setBrush( strokeColor ); + QIMPenStrokeIterator it( *stk ); + while ( it.current() ) { + QPoint p = it.current()->startingPoint() + off; + paint.drawRect( p.x()-1, p.y()-1, 2, 2 ); + const QArray &chain = it.current()->chain(); + for ( unsigned i = 0; i < chain.count(); i++ ) { + p.rx() += chain[i].dx; + p.ry() += chain[i].dy; + paint.drawRect( p.x()-1, p.y()-1, 2, 2 ); + } + ++it; + if ( it.atLast() && mode == Waiting ) + strokeColor = black; + } + } + + dirtyRect = QRect(); + + // debug +/* + if ( input ) { + QArray sig = input->sig(); + for ( unsigned i = 0; i < sig.count(); i++ ) { + paint.drawPoint( 200 + i, height()/2 - sig[i] / 8 ); + } + } +*/ +} + +void QIMPenWidget::resizeEvent( QResizeEvent *e ) +{ + if ( mode == Output ) + showCharacter( outputChar, 0 ); + + QWidget::resizeEvent( e ); +} + diff --git a/inputmethods/handwriting/qimpenwidget.h b/inputmethods/handwriting/qimpenwidget.h new file mode 100644 index 0000000..98d7f5c --- a/dev/null +++ b/inputmethods/handwriting/qimpenwidget.h @@ -0,0 +1,88 @@ +/********************************************************************** +** 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 +#include +#include "qimpenchar.h" + +class QIMPenWidget : public QWidget +{ + Q_OBJECT +public: + QIMPenWidget( QWidget *parent ); + + void clear(); + void greyStroke(); + void setReadOnly( bool r ) { readOnly = r; } + + void insertCharSet( QIMPenCharSet *cs, int stretch=1, int pos=-1 ); + void removeCharSet( int ); + void changeCharSet( QIMPenCharSet *cs, int pos ); + void clearCharSets(); + void showCharacter( QIMPenChar *, int speed = 10 ); + virtual QSize sizeHint(); + +public slots: + void removeStroke(); + +signals: + void changeCharSet( QIMPenCharSet *cs ); + void changeCharSet( int ); + void beginStroke(); + void stroke( QIMPenStroke *ch ); + +protected slots: + void timeout(); + +protected: + enum Mode { Waiting, Input, Output }; + bool selectSet( QPoint ); + virtual void mousePressEvent( QMouseEvent *e ); + virtual void mouseReleaseEvent( QMouseEvent *e ); + virtual void mouseMoveEvent( QMouseEvent *e ); + virtual void paintEvent( QPaintEvent *e ); + virtual void resizeEvent( QResizeEvent *e ); + + struct CharSetEntry { + QIMPenCharSet *cs; + int stretch; + }; + typedef QList CharSetEntryList; + typedef QListIterator CharSetEntryIterator; + +protected: + Mode mode; + bool autoHide; + bool readOnly; + QPoint lastPoint; + unsigned pointIndex; + int strokeIndex; + int currCharSet; + QTimer *timer; + QColor strokeColor; + QRect dirtyRect; + QIMPenChar *outputChar; + QIMPenStroke *outputStroke; + QIMPenStroke *inputStroke; + QIMPenStrokeList strokes; + CharSetEntryList charSets; + int totalStretch; +}; + diff --git a/inputmethods/handwriting/qimpenwordpick.cpp b/inputmethods/handwriting/qimpenwordpick.cpp new file mode 100644 index 0000000..8ee103d --- a/dev/null +++ b/inputmethods/handwriting/qimpenwordpick.cpp @@ -0,0 +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 +#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 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() ); + emit wordClicked( words[wordIdx] ); + } + clickWord = -1; + repaint(); +} + diff --git a/inputmethods/handwriting/qimpenwordpick.h b/inputmethods/handwriting/qimpenwordpick.h new file mode 100644 index 0000000..376288e --- a/dev/null +++ b/inputmethods/handwriting/qimpenwordpick.h @@ -0,0 +1,49 @@ +/********************************************************************** +** 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 +#include "qimpenmatch.h" + +class QIMPenWordPick : public QFrame +{ + Q_OBJECT +public: + QIMPenWordPick( QWidget *parent = 0, const char *name = 0, WFlags f = 0 ); + + void clear(); + virtual QSize sizeHint() const; + +public slots: + void setWords( const QIMPenMatch::MatchWordList &w ); + +signals: + void wordClicked( const QString & ); + +protected: + int onWord( QPoint p ); + virtual void paintEvent( QPaintEvent * ); + virtual void mousePressEvent( QMouseEvent * ); + virtual void mouseReleaseEvent( QMouseEvent * ); + +private: + QStringList words; + int clickWord; +}; + diff --git a/inputmethods/handwriting/qpe-handwriting.control b/inputmethods/handwriting/qpe-handwriting.control new file mode 100644 index 0000000..f1648c6 --- a/dev/null +++ b/inputmethods/handwriting/qpe-handwriting.control @@ -0,0 +1,9 @@ +Files: plugins/inputmethods/libqhandwriting.so* +Priority: optional +Section: qpe/inputmethods +Maintainer: Martin Jones +Architecture: arm +Version: $QPE_VERSION-3 +Depends: qpe-base ($QPE_VERSION) +Description: Handwriting input method + Handwriting recognition input method for the Qtopia environment. diff --git a/inputmethods/handwriting/qpe-handwriting.postinst b/inputmethods/handwriting/qpe-handwriting.postinst new file mode 100755 index 0000000..c254b01 --- a/dev/null +++ b/inputmethods/handwriting/qpe-handwriting.postinst @@ -0,0 +1,2 @@ +#!/bin/sh +/opt/QtPalmtop/bin/qcop QPE/TaskBar "reloadInputMethods()" diff --git a/inputmethods/handwriting/qpe-handwriting.postrm b/inputmethods/handwriting/qpe-handwriting.postrm new file mode 100755 index 0000000..c254b01 --- a/dev/null +++ b/inputmethods/handwriting/qpe-handwriting.postrm @@ -0,0 +1,2 @@ +#!/bin/sh +/opt/QtPalmtop/bin/qcop QPE/TaskBar "reloadInputMethods()" -- cgit v0.9.0.2