Česky
Kamil Dudka

emulT9 (C++, Qt4)

File detail

Name:Downloadengine.cpp [Download]
Location: emulT9 > src
Size:19.5 KB
Last modification:2007-08-27 01:13

Source code

#include <QFile>
#include <QTextCursor>
#include <QTextStream>
#include <QSet>
#include "engine.h"
 
/// Default file name to load/save dictionary
const char *FileDictionaryT9::szDefFileName= "emulT9.dict";
 
Engine::Engine(QMainWindow *parent):
  _keyScan (parent)
{
  /// connect KeyScan
  connect (&_keyScan, SIGNAL(keyPressed(int)),      parent,       SLOT(keyPressed()));
  connect (&_keyScan, SIGNAL(keyPressed(int)),      &_typer,      SLOT(keyPressed(int)));
  connect (&_keyScan, SIGNAL(keyPressed(int)),      &_stopWatch,  SLOT(keyPressed()));
 
  /// connect StopWatch and typer
  connect (parent,    SIGNAL(start()),              &_stopWatch,  SLOT(start()));
  connect (parent,    SIGNAL(start()),              &_typer,      SLOT(init()));
  connect (parent,    SIGNAL(stop()),               &_stopWatch,  SLOT(stop()));
  connect (parent,    SIGNAL(stop()),               &_typer,      SLOT(flush()));
  connect (parent,    SIGNAL(reset()),              &_stopWatch,  SLOT(reset()));
  connect (parent,    SIGNAL(reset()),              &_typer,      SLOT(clear()));
  connect (&_typer,   SIGNAL(textSizeChanged(int)), &_stopWatch,  SLOT(textSizeChanged(int)));
}
void Engine::connectToDesign (Ui::MainWindow &design)
{
  _keyScan.connectToDesign (design);    ///< connect KeyScan to main window UI
  _stopWatch.connectToDesign (design);  ///< connect StopWatch to main window UI
  _typer.connectToDesign (design);      ///< connect Typer to main window UI
}
 
KeyScan::KeyScan(QMainWindow *mainWnd):
  _sc4 (QKeySequence("4"), mainWnd),
  _sc6 (QKeySequence("6"), mainWnd)
{
  _sc4.setContext (Qt::ApplicationShortcut);
  _sc6.setContext (Qt::ApplicationShortcut);
}
 
void KeyScan::connectToDesign (Ui::MainWindow &design)
{
  connect (design.buttLeft,   SIGNAL(clicked()), this, SLOT(buttLeftClicked()));
  connect (design.buttC,      SIGNAL(clicked()), this, SLOT(buttCClicked()));
  connect (design.buttRight,  SIGNAL(clicked()), this, SLOT(buttRightClicked()));
  connect (design.butt11,     SIGNAL(clicked()), this, SLOT(butt11Clicked()));
  connect (design.butt12,     SIGNAL(clicked()), this, SLOT(butt12Clicked()));
  connect (design.butt13,     SIGNAL(clicked()), this, SLOT(butt13Clicked()));
  connect (design.butt21,     SIGNAL(clicked()), this, SLOT(butt21Clicked()));
  connect (design.butt22,     SIGNAL(clicked()), this, SLOT(butt22Clicked()));
  connect (design.butt23,     SIGNAL(clicked()), this, SLOT(butt23Clicked()));
  connect (design.butt31,     SIGNAL(clicked()), this, SLOT(butt31Clicked()));
  connect (design.butt32,     SIGNAL(clicked()), this, SLOT(butt32Clicked()));
  connect (design.butt33,     SIGNAL(clicked()), this, SLOT(butt33Clicked()));
  connect (design.butt41,     SIGNAL(clicked()), this, SLOT(butt41Clicked()));
  connect (design.butt42,     SIGNAL(clicked()), this, SLOT(butt42Clicked()));
  connect (design.butt43,     SIGNAL(clicked()), this, SLOT(butt43Clicked()));
 
  connect (&_sc4, SIGNAL(activated()),            design.butt21, SLOT(animateClick()));
  connect (&_sc4, SIGNAL(activatedAmbiguously()), design.butt21, SLOT(animateClick()));
  connect (&_sc6, SIGNAL(activated()),            design.butt23, SLOT(animateClick()));
  connect (&_sc6, SIGNAL(activatedAmbiguously()), design.butt23, SLOT(animateClick()));
}
void KeyScan::buttLeftClicked()   { emit keyPressed (KEY_LEFT);  }
void KeyScan::buttCClicked()      { emit keyPressed (KEY_C);     }
void KeyScan::buttRightClicked()  { emit keyPressed (KEY_RIGHT); }
void KeyScan::butt11Clicked()     { emit keyPressed (KEY_DOT);   }
void KeyScan::butt12Clicked()     { emit keyPressed (KEY_ABC);   }
void KeyScan::butt13Clicked()     { emit keyPressed (KEY_DEF);   }
void KeyScan::butt21Clicked()     { emit keyPressed (KEY_GHI);   }
void KeyScan::butt22Clicked()     { emit keyPressed (KEY_JKL);   }
void KeyScan::butt23Clicked()     { emit keyPressed (KEY_MNO);   }
void KeyScan::butt31Clicked()     { emit keyPressed (KEY_PQRS);  }
void KeyScan::butt32Clicked()     { emit keyPressed (KEY_TUV);   }
void KeyScan::butt33Clicked()     { emit keyPressed (KEY_WXYZ);  }
void KeyScan::butt41Clicked()     { emit keyPressed (KEY_SHIFT); }
void KeyScan::butt42Clicked()     { emit keyPressed (KEY_ROT);   }
void KeyScan::butt43Clicked()     { emit keyPressed (KEY_GAP);   }
 
StopWatch::StopWatch():
    _iTicks(0), _iHits(0), _iTextSize(0)
{
  connect (&_qTimer, SIGNAL(timeout()), this, SLOT(tick()));
}
void StopWatch::connectToDesign (Ui::MainWindow &design)
{
  /// save pointers to managed widgets
  _labelTime= design.labelTime;
  _labelHitsPerMin= design.labelHitsPerMin;
  _labelCharsPerMin= design.labelCharsPerMin;
  _textEdit= design.textEdit;
}
void StopWatch::start()
{
  _qTimer.start (iPeriod);
}
void StopWatch::stop()
{
  _qTimer.stop();
}
void StopWatch::reset()
{
  _iTicks=0;
  _iHits=0;
  update();
}
void StopWatch::tick()
{
  _iTicks++;
  update();
}
void StopWatch::keyPressed()
{
  _iHits++;
}
void StopWatch::textSizeChanged (int iSize)
{
  _iTextSize= iSize;
}
 
/// draw statistics
void StopWatch::update()
{
  /// draw time elapsed
  if (_iTicks) {
    int iTicks= _iTicks;
    const int iTenths= iTicks%10; iTicks/= 10;
    const int iSecs= iTicks%60; iTicks/=60;
    const int iMins= iTicks;
    _labelTime->setText (QString(tr("<b>%1:%2</b>.%3")).arg(iMins,2,10,QChar('0')).arg(iSecs,2,10,QChar('0')).arg(iTenths,1));
  } else
    _labelTime->setText (QString());
 
  /// draw hits/min
  QString hitsPerMin= (_iTicks && _iHits) ?
    QString (tr("<b>%1</b> Hits/min")).arg (static_cast<double>(_iHits)/_iTicks*600,0,'f',1):
    "";
  _labelHitsPerMin->setText (hitsPerMin);
 
  /// draw letters /min
  QString charsPerMin= (_iTicks && _iTextSize) ?
    QString (tr("<b>%1</b> Letters/min")).arg (static_cast<double>(_iTextSize)/_iTicks*600,0,'f',1):
    "";
  _labelCharsPerMin->setText (charsPerMin);
}
 
AlphaSwitcher::AlphaSwitcher()
{
  init();
}
void AlphaSwitcher::init()
{
  _state= AS_FIRST;
}
void AlphaSwitcher::toggle()
{
  switch (_state) {
    case AS_LOW:    _state=AS_FIRST;  break;
    case AS_FIRST:  _state=AS_HIGH;   break;
    case AS_HIGH:   _state=AS_LOW;    break;
  }
}
void AlphaSwitcher::notify()
{
  if (_state== AS_FIRST)
    _state= AS_LOW;
}
QString AlphaSwitcher::getStatus() const
{
  switch (_state) {
    default:
    case AS_LOW:    return "abc";
    case AS_FIRST:  return "Abc";
    case AS_HIGH:   return "ABC";
  }
}
QChar AlphaSwitcher::translate (QChar c)
{
  if (!c.isLetter ())
    ///< Do not translate non-alpha characters.
    return c;
 
  if (_state== AS_LOW)
    return c.toLower();
  else
    return c.toUpper();
}
 
Rotator::Rotator (const QString &str):
  _str(str), _iNow(0)
{
}
void Rotator::reset()
{
  _iNow= 0;
}
void Rotator::rotate()
{
  if (++_iNow >= _str.length())
    _iNow=0;
}
QChar Rotator::getCurrent()
{
  return _str[_iNow];
}
 
TyperWrapper::TyperWrapper():
  _currentTyper(0), _iTimer(0),
  _bSuggest(false), _bAtEnd(false),
  _lastKey(0), _iSelBegin(0), _iSelEnd(0), _iTextSize(0)
{
  /// Timer initializaton
  _timer.setSingleShot (true);
  connect (&_timer, SIGNAL(timeout()), this, SLOT(flush()));
}
TyperWrapper::~TyperWrapper()
{
  delete _currentTyper;
}
void TyperWrapper::connectToDesign(Ui::MainWindow &design)
{
  /// Save pointers to managed widgets.
  _textEdit= design.textEdit;
  _labelEngineStatus= design.labelEngineStatus;
  /// Typer switching initialization
  connect (design.cbT9, SIGNAL(toggled(bool)), this, SLOT(setT9(bool)));
  setT9 (true);
  /// Show extra carret (text is empty now)
  showCarret();
}
void TyperWrapper::setTimer (int iTimer)
{
  _iTimer= iTimer;
}
void TyperWrapper::putSuggest (const QString &str)
{
  hideCarret();
 
  _iSelEnd= _iSelBegin + str.length();
  QTextCursor cursor= _textEdit-> textCursor();
  cursor.insertText (str);
  cursor.setPosition (_iSelBegin, QTextCursor::MoveAnchor);
  cursor.setPosition (_iSelEnd, QTextCursor::KeepAnchor);
  _textEdit-> setTextCursor(cursor);
 
  _bSuggest= true;
  _strSuggest= str;
}
namespace {
  inline bool isKeyAlpha (int key) { return KEY_ABC<= key && key <= KEY_WXYZ; }
}
void TyperWrapper::keyPressed (int iKey)
{
  switch (iKey) {
    case KEY_LEFT: {
      ///< Move left if possible.
      flush();
      if (0< _iSelBegin)
        _iSelEnd= --_iSelBegin;
      hideCarret();
      showCarret();
      break;
    }
    case KEY_RIGHT: {
      ///< Move right if possible.
      if (_bSuggest) {
        flush();
        break;
      }
      hideCarret();
      const int iTextLength= (_textEdit->toPlainText()).length();
      if (iTextLength> _iSelBegin)
        _iSelEnd= ++_iSelBegin;
      showCarret();
      break;
    }
    case KEY_SHIFT:
      /// Switch upper/lower-case
      _alphaSwitch.toggle();
      updateStatus();
      break;
 
    case KEY_GAP:
      /// Insert gap.
      if (_lastKey== KEY_GAP) {
        _strSuggest.append (" ");
        putSuggest (_strSuggest);
        break;
      }
      flush();
      putSuggest (" ");
      break;
 
    case KEY_C: {
      ///< Delete if possible.
      if (!_bSuggest) {
        flush();
        QTextCursor cursor= _textEdit-> textCursor();
        if (cursor.atStart()) {
          ///< Delete not possible
          /// Try back-space.
          keyPressed (KEY_RIGHT);
          cursor= _textEdit->textCursor();
        }
        cursor.deletePreviousChar();
        _iSelBegin= cursor.anchor();
        _iSelEnd= cursor.position();
        _textEdit-> setTextCursor (cursor);
        break;
      }
    }
    default:
      if (
          _lastKey== KEY_GAP ||
          (iKey==KEY_DOT && isKeyAlpha(_lastKey)) ||
          (isKeyAlpha(iKey) && _lastKey==KEY_DOT))
        ///< Write gap(s) now.
        flush();
 
      /// Delegate key event to typer engine.
      _currentTyper-> keyPressed (static_cast<EKey>(iKey));
      startTimer();
  }
  ///< Save key code for next wheel
  switch (iKey) {
    case KEY_SHIFT:
    case KEY_ROT:
      break;
    default:
      _lastKey= iKey;
      advertiseTextSize();
  }
}
void TyperWrapper::init()
{
  // TODO: Put something meaningful here?
}
void TyperWrapper::flush()
{
  _timer.stop();
  if (_bSuggest) {
    /// Destroy selection
    _iSelBegin= _iSelEnd;
    QTextCursor cursor= _textEdit-> textCursor();
    cursor.setPosition (_iSelBegin, QTextCursor::MoveAnchor);
    _textEdit-> setTextCursor(cursor);
  }
  _bSuggest= false;
  showCarret();
 
  _currentTyper-> flush();
}
void TyperWrapper::clear()
{
  flush();
  _alphaSwitch.init();
  updateStatus();
 
  _textEdit-> selectAll();
  _textEdit-> insertPlainText ("");
  _iSelBegin= _iSelEnd= 0;
  _lastKey= KEY_NULL;
  _bSuggest= false;
  _bAtEnd= false;
  _strSuggest= "";
  showCarret();
}
void TyperWrapper::setT9 (bool bT9)
{
  if (_currentTyper) {
    /// Destroy current typer.
    flush();
    delete _currentTyper;
  }
  if (bT9)
    _currentTyper= new TyperT9(this);
  else
    _currentTyper= new TyperStd(this);
  updateStatus();
}
void TyperWrapper::showCarret()
{
  /// Show extra carret
  QTextCharFormat fUnderline;
  fUnderline.setFontUnderline (true);
  QTextCursor cursor= _textEdit-> textCursor();
  cursor.setPosition (_iSelBegin, QTextCursor::MoveAnchor);
  if (!_bAtEnd &&cursor.atEnd()) {
    _bAtEnd= true;
    cursor.insertText (" ");
    cursor.setPosition (_iSelBegin, QTextCursor::MoveAnchor);
  }
  cursor.movePosition (QTextCursor::NextCharacter, QTextCursor::KeepAnchor);
  cursor.setCharFormat (fUnderline);
  cursor.setPosition (_iSelBegin, QTextCursor::MoveAnchor);
  _textEdit-> setTextCursor (cursor);
}
void TyperWrapper::hideCarret()
{
  /// Hide extra carret
  QTextCharFormat fDef;
  QTextCursor cursor= _textEdit-> textCursor();
  if (_bAtEnd) {
    cursor.deleteChar();
    _bAtEnd= false;
  }
  /*cursor.select (QTextCursor::Document);*/
  cursor.movePosition (QTextCursor::Start, QTextCursor::MoveAnchor);
  cursor.movePosition (QTextCursor::End, QTextCursor::KeepAnchor);
 
  cursor.setCharFormat (fDef);
  cursor.setPosition (_iSelBegin, QTextCursor::MoveAnchor);
  cursor.setPosition (_iSelEnd, QTextCursor::KeepAnchor);
  _textEdit-> setTextCursor (cursor);
}
void TyperWrapper::updateStatus()
{
  /// Update engine status label
  QString status= _currentTyper-> getStatusPrefix();
  status.append ("<b>");
  status.append (_alphaSwitch.getStatus());
  status.append ("</b>");
  _labelEngineStatus-> setText (status);
}
void TyperWrapper::startTimer()
{
  if (_iTimer<=0)
    ///< Timer disabled
    return;
  _timer.start (_iTimer);
}
void TyperWrapper::advertiseTextSize()
{
  int iSize= (_textEdit-> toPlainText()).length();
  if (_bAtEnd)
    iSize--;
  if (iSize== _iTextSize)
    return;
 
  _iTextSize= iSize;
  emit textSizeChanged (iSize);
}
void TyperWrapper::notify()
{
  _alphaSwitch.notify();
  updateStatus();
}
 
TyperStd::TyperStd (TyperWrapper *parent):
  _parent(parent), _lastKey(KEY_NULL), _currentRotator(0)
{
}
TyperStd::~TyperStd()
{
  delete _currentRotator;
}
void TyperStd::keyPressed (EKey key)
{
  if (_currentRotator && key== _lastKey) {
    ///< Rotate
    _currentRotator-> rotate();
    putSuggest();
    return;
  }
  if (key== KEY_C) {
    ///< Delete last
    _parent-> putSuggest("");
    flush();
  }
 
  if (_currentRotator)
    ///< Write now last character
    _parent->flush();
 
  /// Create new rotator
  switch (key) {
    case KEY_DOT:   _currentRotator= new Rotator (".,?-:10()@;"); break;
    case KEY_ABC:   _currentRotator= new Rotator ("abc2"); break;
    case KEY_DEF:   _currentRotator= new Rotator ("def3"); break;
    case KEY_GHI:   _currentRotator= new Rotator ("ghi4"); break;
    case KEY_JKL:   _currentRotator= new Rotator ("jkl5"); break;
    case KEY_MNO:   _currentRotator= new Rotator ("mno6"); break;
    case KEY_PQRS:  _currentRotator= new Rotator ("pqrs7"); break;
    case KEY_TUV:   _currentRotator= new Rotator ("tuv8"); break;
    case KEY_WXYZ:  _currentRotator= new Rotator ("wxyz9"); break;
    default: return;
  }
  putSuggest();
  _parent-> notify();
  _lastKey= key;
}
void TyperStd::flush()
{
  delete _currentRotator;
  _currentRotator= 0;
}
QString TyperStd::getStatusPrefix()
{
  return QString();   ///< No extra status
}
void TyperStd::putSuggest()
{
  _parent->putSuggest (_parent->translate (_currentRotator-> getCurrent()));
}
 
void RotatorT9::add (const QString &str)
{
  _list.append (str);
  reset();
}
void RotatorT9::rotate()
{
  if (++_now== _list.end())
    reset();
}
QString RotatorT9::getCurrent()
{
  if (empty())
    return QString();
  else
    return *_now;
}
 
QChar DictionaryT9::keyToHashChar (EKey key)
{
  switch (key) {
    case KEY_DOT:  return '1';
    case KEY_ABC:  return '2';
    case KEY_DEF:  return '3';
    case KEY_GHI:  return '4';
    case KEY_JKL:  return '5';
    case KEY_MNO:  return '6';
    case KEY_PQRS: return '7';
    case KEY_TUV:  return '8';
    case KEY_WXYZ: return '9';
    default:       return '0';
  }
}
QChar DictionaryT9::charToHashChar (QChar c)
{
 
  if (c >= '1' && c <= '9')
    ///< Digit
    return c;
 
  if (QString (".,?-:0()@;+*/").contains (c))
    ///< No alpha nor digit
    return '1';
 
  /// Try interpret as alpha
  c= c.toUpper();
  if (c >= 'A' && c <= 'C') return '2';
  if (c >= 'D' && c <= 'F') return '3';
  if (c >= 'G' && c <= 'I') return '4';
  if (c >= 'J' && c <= 'L') return '5';
  if (c >= 'M' && c <= 'O') return '6';
  if (c >= 'P' && c <= 'S') return '7';
  if (c >= 'T' && c <= 'V') return '8';
  if (c >= 'W' && c <= 'Z') return '9';
 
  /// Non-writable character?
  return '0';
}
QString DictionaryT9::wordToHash (const QString &word)
{
  int iLength= word.length();
  QString hash;
  for (int i=0; i<iLength; i++)
    hash.append (charToHashChar (word [i]));
  return hash;
}
void DictionaryT9::add (const QString &word)
{
  _hashTable [wordToHash (word)].insert (word);
}
RotatorT9 *DictionaryT9::createRotator (const QString &hash)
{
  int iHashLength= hash.length();
  /// Build set of suggested strings for given hash
  QSet<QString> set;
 
  /// TODO: Fix effectivity problem (sequentially pass trough hash table)
  for (THashTable::const_iterator iHashItem= _hashTable.begin(); iHashItem!= _hashTable.end(); iHashItem++) {
    QString hashCurrent= iHashItem.key();
    hashCurrent.truncate (iHashLength);
    if (hashCurrent!= hash)
      ///< Hash not match
      continue;
 
    const TSet &list= iHashItem.value();
    for (TSet::const_iterator i= list.begin(); i!= list.end(); i++) {
      QString val= *i;
      val.truncate (iHashLength);
      set.insert (val);            ///< Insert suggested string to set
    }
  }
  if (set.empty())
    ///< No sugested string found for given hash
    return 0;
 
  /// Create rotator from set
  RotatorT9 *r= new RotatorT9;
  for (QSet<QString>::const_iterator i= set.begin(); i!= set.end(); i++)
    r-> add (*i);
  return r;
}
 
void FileDictionaryT9::load (const char *szFileName)
{
  QFile file (szFileName);
  if (!file.open (QIODevice::ReadOnly))
    return;
 
  QString word;
  for (QTextStream stream (&file); !stream.atEnd(); stream >> word)
    add (word);
  file.close();
}
void FileDictionaryT9::save (const char *szFileName)
{
  QFile file (szFileName);
  if (!file.open (QIODevice::WriteOnly))
    return;
 
  QTextStream stream (&file);
  for (THashTable::const_iterator iList= _hashTable.begin(); iList!= _hashTable.end(); iList++) {
    const TSet &list= *iList;
    for (TSet::const_iterator i= list.begin(); i!= list.end(); i++)
      stream << *i << "\n";
  }
  file.close();
}
 
AutoLoadSaveDictionaryT9::AutoLoadSaveDictionaryT9()
{
  /// load base set
  const QString baseSet ("abcdefghijklmnopqrstuvwxyz0123456789.,?-:()@;*+/");
  const int iLength= baseSet.length();
  for (int i=0; i< iLength; i++)
    add (QString (baseSet [i]));
 
  /// load dictionary from default location
  load();
 
  /// load dictionary from files given in command-line arguments
  const QStringList args (QCoreApplication::arguments());
  const QStringList fileList (args.filter (QRegExp ("^[^-]")));
  for (QStringList::const_iterator i= fileList.begin(); i!= fileList.end(); i++)
    load ((*i).toAscii());
}
AutoLoadSaveDictionaryT9::~AutoLoadSaveDictionaryT9()
{
  /// This can be useful in future
  //save();
}
 
TyperT9::TyperT9 (TyperWrapper *parent):
  _parent(parent),
  _dict(parent->_dictT9),
  _currentRotator(0)
{
}
TyperT9::~TyperT9()
{
  delete _currentRotator;
}
void TyperT9::keyPressed(EKey key)
{
  switch (key) {
    case KEY_ROT:
      ///< Rotation
      if (!_currentRotator)
        return;
      _currentRotator-> rotate();
      break;
 
    case KEY_C:
      ///< Backspace
      _hashString.chop (1);
      _isUpperVector.remove (_isUpperVector.size()-1);
      if (_hashString.isEmpty()) {
        _parent-> putSuggest ("");
        _parent-> flush();
        return;
      }
      delete _currentRotator;
      _currentRotator= _dict.createRotator (_hashString);
      break;
 
    default:
      ///< Key pressed (incremental search)
      _hashString.append (DictionaryT9::keyToHashChar (key));
      _isUpperVector.append (_parent-> isUpper());
      delete _currentRotator;
      _currentRotator= _dict.createRotator (_hashString);
      if (!_currentRotator) {
        _hashString.chop (1);
        _isUpperVector.remove (_isUpperVector.size()-1);
      } else {
        _parent-> notify();
      }
 
  }
  if (_currentRotator)
    ///< Display suggestion to user
    putSuggest();
}
void TyperT9::flush()
{
  /// Stop rotating now.
  delete _currentRotator;
  _currentRotator=0;
  _hashString.clear();
  _isUpperVector.clear();
}
QString TyperT9::getStatusPrefix()
{
  return "<font color=\"#E00000\">T9</font> ";
}
void TyperT9::putSuggest()
{
  QString in= (_currentRotator-> getCurrent()).toLower();
  QString out;
  int iLength= _isUpperVector.size();
  for (int i=0; i< iLength; i++) {
    QChar c= in[i];
    out.append (_isUpperVector[i] ? c.toUpper() : c);
  }
  _parent -> putSuggest (out);
}