Logo Search packages:      
Sourcecode: ibus-unikey version File versions  Download package

ukengine.cpp

// -*- mode:c++; tab-width:4; c-basic-offset:4; indent-tabs-mode:nil -*-
/* Unikey Vietnamese Input Method
 * Copyright (C) 2000-2005 Pham Kim Long
 * Contact:
 *   unikey@gmail.com
 *   UniKey project: http://unikey.org
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 */

#include <stdlib.h>
#include <string.h>
#include <iostream>
#include "keycons.h"

/*
#if defined(_WIN32)
#include "keyhook.h"
#endif
*/

#include "vnlexi.h"
#include "ukengine.h"

#include "charset.h"

using namespace std;

#define ENTER_CHAR 13
#define IS_ODD(x) (x & 1)
#define IS_EVEN(x) (!(x & 1))

#define IS_STD_VN_LOWER(x) ((x) >= VnStdCharOffset && (x) < (VnStdCharOffset + TOTAL_ALPHA_VNCHARS) && IS_ODD(x))
#define IS_STD_VN_UPPER(x) ((x) >= VnStdCharOffset && (x) < (VnStdCharOffset + TOTAL_ALPHA_VNCHARS) && IS_EVEN(x))

bool IsVnVowel[vnl_lastChar];

extern VnLexiName AZLexiUpper[]; //defined in inputproc.cpp
extern VnLexiName AZLexiLower[];

//see vnconv/data.cpp for explanation of these characters
unsigned char SpecialWesternChars[] = {
  0x80, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88,
  0x89, 0x8A, 0x8B, 0x8C, 0x8E, 0x91, 0x92, 0x93,
  0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0x9B,
  0x9C, 0x9E, 0x9F, 0x00};

StdVnChar IsoStdVnCharMap[256];

inline StdVnChar IsoToStdVnChar(int keyCode)
{
    return (keyCode < 256)? IsoStdVnCharMap[keyCode] : keyCode;
}

00068 struct VowelSeqInfo {
    int len;
    int complete;
    int conSuffix; //allow consonnant suffix
    VnLexiName v[3];
    VowelSeq sub[3];

    int roofPos;
    VowelSeq withRoof;

    int hookPos;
    VowelSeq withHook; //hook & bowl
};

VowelSeqInfo VSeqList[] = {
    {1, 1, 1, {vnl_a, vnl_nonVnChar, vnl_nonVnChar}, {vs_a, vs_nil, vs_nil}, -1, vs_ar, -1, vs_ab},
    {1, 1, 1, {vnl_ar, vnl_nonVnChar, vnl_nonVnChar}, {vs_ar, vs_nil, vs_nil}, 0, vs_nil, -1, vs_ab},
    {1, 1, 1, {vnl_ab, vnl_nonVnChar, vnl_nonVnChar}, {vs_ab, vs_nil, vs_nil}, -1, vs_ar, 0, vs_nil},
    {1, 1, 1, {vnl_e, vnl_nonVnChar, vnl_nonVnChar}, {vs_e, vs_nil, vs_nil}, -1, vs_er, -1, vs_nil},
    {1, 1, 1, {vnl_er, vnl_nonVnChar, vnl_nonVnChar}, {vs_er, vs_nil, vs_nil}, 0, vs_nil, -1, vs_nil},
    {1, 1, 1, {vnl_i, vnl_nonVnChar, vnl_nonVnChar}, {vs_i, vs_nil, vs_nil}, -1, vs_nil, -1, vs_nil},
    {1, 1, 1, {vnl_o, vnl_nonVnChar, vnl_nonVnChar}, {vs_o, vs_nil, vs_nil}, -1, vs_or, -1, vs_oh},
    {1, 1, 1, {vnl_or, vnl_nonVnChar, vnl_nonVnChar}, {vs_or, vs_nil, vs_nil}, 0, vs_nil, -1, vs_oh},
    {1, 1, 1, {vnl_oh, vnl_nonVnChar, vnl_nonVnChar}, {vs_oh, vs_nil, vs_nil}, -1, vs_or, 0, vs_nil},
    {1, 1, 1, {vnl_u, vnl_nonVnChar, vnl_nonVnChar}, {vs_u, vs_nil, vs_nil}, -1, vs_nil, -1, vs_uh},
    {1, 1, 1, {vnl_uh, vnl_nonVnChar, vnl_nonVnChar}, {vs_uh, vs_nil, vs_nil}, -1, vs_nil, 0, vs_nil},
    {1, 1, 1, {vnl_y, vnl_nonVnChar, vnl_nonVnChar}, {vs_y, vs_nil, vs_nil}, -1, vs_nil, -1, vs_nil},
    {2, 1, 0, {vnl_a, vnl_i, vnl_nonVnChar}, {vs_a, vs_ai, vs_nil}, -1, vs_nil, -1, vs_nil},
    {2, 1, 0, {vnl_a, vnl_o, vnl_nonVnChar}, {vs_a, vs_ao, vs_nil}, -1, vs_nil, -1, vs_nil},
    {2, 1, 0, {vnl_a, vnl_u, vnl_nonVnChar}, {vs_a, vs_au, vs_nil}, -1, vs_aru, -1, vs_nil},
    {2, 1, 0, {vnl_a, vnl_y, vnl_nonVnChar}, {vs_a, vs_ay, vs_nil}, -1, vs_ary, -1, vs_nil},
    {2, 1, 0, {vnl_ar, vnl_u, vnl_nonVnChar}, {vs_ar, vs_aru, vs_nil}, 0, vs_nil, -1, vs_nil},
    {2, 1, 0, {vnl_ar, vnl_y, vnl_nonVnChar}, {vs_ar, vs_ary, vs_nil}, 0, vs_nil, -1, vs_nil},
    {2, 1, 0, {vnl_e, vnl_o, vnl_nonVnChar}, {vs_e, vs_eo, vs_nil}, -1, vs_nil, -1, vs_nil},
    {2, 0, 0, {vnl_e, vnl_u, vnl_nonVnChar}, {vs_e, vs_eu, vs_nil}, -1, vs_eru, -1, vs_nil},
    {2, 1, 0, {vnl_er, vnl_u, vnl_nonVnChar}, {vs_er, vs_eru, vs_nil}, 0, vs_nil, -1, vs_nil},
    {2, 1, 0, {vnl_i, vnl_a, vnl_nonVnChar}, {vs_i, vs_ia, vs_nil}, -1, vs_nil, -1, vs_nil},
    {2, 0, 1, {vnl_i, vnl_e, vnl_nonVnChar}, {vs_i, vs_ie, vs_nil}, -1, vs_ier, -1, vs_nil},
    {2, 1, 1, {vnl_i, vnl_er, vnl_nonVnChar}, {vs_i, vs_ier, vs_nil}, 1, vs_nil, -1, vs_nil},
    {2, 1, 0, {vnl_i, vnl_u, vnl_nonVnChar}, {vs_i, vs_iu, vs_nil}, -1, vs_nil, -1, vs_nil},
    {2, 1, 1, {vnl_o, vnl_a, vnl_nonVnChar}, {vs_o, vs_oa, vs_nil}, -1, vs_nil, -1, vs_oab},
    {2, 1, 1, {vnl_o, vnl_ab, vnl_nonVnChar}, {vs_o, vs_oab, vs_nil}, -1, vs_nil, 1, vs_nil},
    {2, 1, 1, {vnl_o, vnl_e, vnl_nonVnChar}, {vs_o, vs_oe, vs_nil}, -1, vs_nil, -1, vs_nil},
    {2, 1, 0, {vnl_o, vnl_i, vnl_nonVnChar}, {vs_o, vs_oi, vs_nil}, -1, vs_ori, -1, vs_ohi},
    {2, 1, 0, {vnl_or, vnl_i, vnl_nonVnChar}, {vs_or, vs_ori, vs_nil}, 0, vs_nil, -1, vs_ohi},
    {2, 1, 0, {vnl_oh, vnl_i, vnl_nonVnChar}, {vs_oh, vs_ohi, vs_nil}, -1, vs_ori, 0, vs_nil},
    {2, 1, 1, {vnl_u, vnl_a, vnl_nonVnChar}, {vs_u, vs_ua, vs_nil}, -1, vs_uar, -1, vs_uha},
    {2, 1, 1, {vnl_u, vnl_ar, vnl_nonVnChar}, {vs_u, vs_uar, vs_nil}, 1, vs_nil, -1, vs_nil},
    {2, 0, 1, {vnl_u, vnl_e, vnl_nonVnChar}, {vs_u, vs_ue, vs_nil}, -1, vs_uer, -1, vs_nil},
    {2, 1, 1, {vnl_u, vnl_er, vnl_nonVnChar}, {vs_u, vs_uer, vs_nil}, 1, vs_nil, -1, vs_nil},
    {2, 1, 0, {vnl_u, vnl_i, vnl_nonVnChar}, {vs_u, vs_ui, vs_nil}, -1, vs_nil, -1, vs_uhi},
    {2, 0, 1, {vnl_u, vnl_o, vnl_nonVnChar}, {vs_u, vs_uo, vs_nil}, -1, vs_uor, -1, vs_uho},
    {2, 1, 1, {vnl_u, vnl_or, vnl_nonVnChar}, {vs_u, vs_uor, vs_nil}, 1, vs_nil, -1, vs_uoh},
    {2, 1, 1, {vnl_u, vnl_oh, vnl_nonVnChar}, {vs_u, vs_uoh, vs_nil}, -1, vs_uor, 1, vs_uhoh},
    {2, 0, 0, {vnl_u, vnl_u, vnl_nonVnChar}, {vs_u, vs_uu, vs_nil}, -1, vs_nil, -1, vs_uhu},
    {2, 1, 1, {vnl_u, vnl_y, vnl_nonVnChar}, {vs_u, vs_uy, vs_nil}, -1, vs_nil, -1, vs_nil},
    {2, 1, 0, {vnl_uh, vnl_a, vnl_nonVnChar}, {vs_uh, vs_uha, vs_nil}, -1, vs_nil, 0, vs_nil},
    {2, 1, 0, {vnl_uh, vnl_i, vnl_nonVnChar}, {vs_uh, vs_uhi, vs_nil}, -1, vs_nil, 0, vs_nil},
    {2, 0, 1, {vnl_uh, vnl_o, vnl_nonVnChar}, {vs_uh, vs_uho, vs_nil}, -1, vs_nil, 0, vs_uhoh},
    {2, 1, 1, {vnl_uh, vnl_oh, vnl_nonVnChar}, {vs_uh, vs_uhoh, vs_nil}, -1, vs_nil, 0, vs_nil},
    {2, 1, 0, {vnl_uh, vnl_u, vnl_nonVnChar}, {vs_uh, vs_uhu, vs_nil}, -1, vs_nil, 0, vs_nil},
    {2, 0, 1, {vnl_y, vnl_e, vnl_nonVnChar}, {vs_y, vs_ye, vs_nil}, -1, vs_yer, -1, vs_nil},
    {2, 1, 1, {vnl_y, vnl_er, vnl_nonVnChar}, {vs_y, vs_yer, vs_nil}, 1, vs_nil, -1, vs_nil},
    {3, 0, 0, {vnl_i, vnl_e, vnl_u}, {vs_i, vs_ie, vs_ieu}, -1, vs_ieru, -1, vs_nil},
    {3, 1, 0, {vnl_i, vnl_er, vnl_u}, {vs_i, vs_ier, vs_ieru}, 1, vs_nil, -1, vs_nil},
    {3, 1, 0, {vnl_o, vnl_a, vnl_i}, {vs_o, vs_oa, vs_oai}, -1, vs_nil, -1, vs_nil},
    {3, 1, 0, {vnl_o, vnl_a, vnl_y}, {vs_o, vs_oa, vs_oay}, -1, vs_nil, -1, vs_nil},  
    {3, 1, 0, {vnl_o, vnl_e, vnl_o}, {vs_o, vs_oe, vs_oeo}, -1, vs_nil, -1, vs_nil},
    {3, 0, 0, {vnl_u, vnl_a, vnl_y}, {vs_u, vs_ua, vs_uay}, -1, vs_uary, -1, vs_nil},
    {3, 1, 0, {vnl_u, vnl_ar, vnl_y}, {vs_u, vs_uar, vs_uary}, 1, vs_nil, -1, vs_nil},
    {3, 0, 0, {vnl_u, vnl_o, vnl_i}, {vs_u, vs_uo, vs_uoi}, -1, vs_uori, -1, vs_uhoi},
    {3, 0, 0, {vnl_u, vnl_o, vnl_u}, {vs_u, vs_uo, vs_uou}, -1, vs_nil, -1, vs_uhou},
    {3, 1, 0, {vnl_u, vnl_or, vnl_i}, {vs_u, vs_uor, vs_uori}, 1, vs_nil, -1, vs_uohi},
    {3, 0, 0, {vnl_u, vnl_oh, vnl_i}, {vs_u, vs_uoh, vs_uohi}, -1, vs_uori, 1, vs_uhohi},
    {3, 0, 0, {vnl_u, vnl_oh, vnl_u}, {vs_u, vs_uoh, vs_uohu}, -1, vs_nil, 1, vs_uhohu},
    {3, 1, 0, {vnl_u, vnl_y, vnl_a}, {vs_u, vs_uy, vs_uya}, -1, vs_nil, -1, vs_nil},
    {3, 0, 1, {vnl_u, vnl_y, vnl_e}, {vs_u, vs_uy, vs_uye}, -1, vs_uyer, -1, vs_nil},
    {3, 1, 1, {vnl_u, vnl_y, vnl_er}, {vs_u, vs_uy, vs_uyer}, 2, vs_nil, -1, vs_nil},
    {3, 1, 0, {vnl_u, vnl_y, vnl_u}, {vs_u, vs_uy, vs_uyu}, -1, vs_nil, -1, vs_nil},
    {3, 0, 0, {vnl_uh, vnl_o, vnl_i}, {vs_uh, vs_uho, vs_uhoi}, -1, vs_nil, 0, vs_uhohi},
    {3, 0, 0, {vnl_uh, vnl_o, vnl_u}, {vs_uh, vs_uho, vs_uhou}, -1, vs_nil, 0, vs_uhohu},
    {3, 1, 0, {vnl_uh, vnl_oh, vnl_i}, {vs_uh, vs_uhoh, vs_uhohi}, -1, vs_nil, 0, vs_nil},
    {3, 1, 0, {vnl_uh, vnl_oh, vnl_u}, {vs_uh, vs_uhoh, vs_uhohu}, -1, vs_nil, 0, vs_nil},
    {3, 0, 0, {vnl_y, vnl_e, vnl_u}, {vs_y, vs_ye, vs_yeu}, -1, vs_yeru, -1, vs_nil},
    {3, 1, 0, {vnl_y, vnl_er, vnl_u}, {vs_y, vs_yer, vs_yeru}, 1, vs_nil, -1, vs_nil}
};

00155 struct ConSeqInfo {
    int len;
    VnLexiName c[3];
    bool suffix;
};

ConSeqInfo CSeqList[] = {
    {1, {vnl_b, vnl_nonVnChar, vnl_nonVnChar}, false},
    {1, {vnl_c, vnl_nonVnChar, vnl_nonVnChar}, true},
    {2, {vnl_c, vnl_h, vnl_nonVnChar}, true},
    {1, {vnl_d, vnl_nonVnChar, vnl_nonVnChar}, false},
    {1, {vnl_dd, vnl_nonVnChar, vnl_nonVnChar}, false},
    {2, {vnl_d, vnl_z, vnl_nonVnChar}, false},
    {1, {vnl_g, vnl_nonVnChar, vnl_nonVnChar}, false},
    {2, {vnl_g, vnl_h, vnl_nonVnChar}, false},
    {2, {vnl_g, vnl_i, vnl_nonVnChar}, false},
    {3, {vnl_g, vnl_i, vnl_n}, false},
    {1, {vnl_h, vnl_nonVnChar, vnl_nonVnChar}, false},
    {1, {vnl_k, vnl_nonVnChar, vnl_nonVnChar}, false},
    {2, {vnl_k, vnl_h, vnl_nonVnChar}, false},
    {1, {vnl_l, vnl_nonVnChar, vnl_nonVnChar}, false},
    {1, {vnl_m, vnl_nonVnChar, vnl_nonVnChar}, true},
    {1, {vnl_n, vnl_nonVnChar, vnl_nonVnChar}, true},
    {2, {vnl_n, vnl_g, vnl_nonVnChar}, true},
    {3, {vnl_n, vnl_g, vnl_h}, false},
    {2, {vnl_n, vnl_h, vnl_nonVnChar}, true},
    {1, {vnl_p, vnl_nonVnChar, vnl_nonVnChar}, true},
    {2, {vnl_p, vnl_h, vnl_nonVnChar}, false},
    {1, {vnl_q, vnl_nonVnChar, vnl_nonVnChar}, false},
    {2, {vnl_q, vnl_u, vnl_nonVnChar}, false},
    {1, {vnl_r, vnl_nonVnChar, vnl_nonVnChar}, false},
    {1, {vnl_s, vnl_nonVnChar, vnl_nonVnChar}, false},
    {1, {vnl_t, vnl_nonVnChar, vnl_nonVnChar}, true},
    {2, {vnl_t, vnl_h, vnl_nonVnChar}, false},
    {2, {vnl_t, vnl_r, vnl_nonVnChar}, false},
    {1, {vnl_v, vnl_nonVnChar, vnl_nonVnChar}, false},
    {1, {vnl_x, vnl_nonVnChar, vnl_nonVnChar}, false}
};

const int VSeqCount = sizeof(VSeqList)/sizeof(VowelSeqInfo);
00195 struct VSeqPair {
    VnLexiName v[3];
    VowelSeq vs;
};
VSeqPair SortedVSeqList[VSeqCount];

const int CSeqCount = sizeof(CSeqList)/sizeof(ConSeqInfo);
00202 struct CSeqPair {
    VnLexiName c[3];
    ConSeq cs;
};
CSeqPair SortedCSeqList[CSeqCount];

00208 struct VCPair {
    VowelSeq v;
    ConSeq c;
};

VCPair VCPairList [] = {
  {vs_a, cs_c}, {vs_a, cs_ch}, {vs_a, cs_m}, {vs_a, cs_n}, {vs_a, cs_ng},
                {vs_a, cs_nh}, {vs_a, cs_p}, {vs_a, cs_t},
  {vs_ar, cs_c}, {vs_ar, cs_m}, {vs_ar, cs_n}, {vs_ar, cs_ng}, {vs_ar, cs_p}, {vs_ar, cs_t},
  {vs_ab, cs_c}, {vs_ab, cs_m}, {vs_ab, cs_n}, {vs_ab, cs_ng}, {vs_ab, cs_p}, {vs_ab, cs_t},

  {vs_e, cs_c}, {vs_e, cs_ch}, {vs_e, cs_m}, {vs_e, cs_n}, {vs_e, cs_ng},
                {vs_e, cs_nh}, {vs_e, cs_p}, {vs_e, cs_t},
  {vs_er, cs_c}, {vs_er, cs_ch}, {vs_er, cs_m}, {vs_er, cs_n}, {vs_er, cs_nh},
                {vs_er, cs_p}, {vs_er, cs_t},

  {vs_i, cs_c}, {vs_i, cs_ch}, {vs_i, cs_m}, {vs_i, cs_n}, {vs_i, cs_nh}, {vs_i, cs_p}, {vs_i, cs_t},

  {vs_o, cs_c}, {vs_o, cs_m}, {vs_o, cs_n}, {vs_o, cs_ng}, {vs_o, cs_p}, {vs_o, cs_t},
  {vs_or, cs_c}, {vs_or, cs_m}, {vs_or, cs_n}, {vs_or, cs_ng}, {vs_or, cs_p}, {vs_or, cs_t},
  {vs_oh, cs_m}, {vs_oh, cs_n}, {vs_oh, cs_p}, {vs_oh, cs_t},

  {vs_u, cs_c}, {vs_u, cs_m}, {vs_u, cs_n}, {vs_u, cs_ng}, {vs_u, cs_p}, {vs_u, cs_t},
  {vs_uh, cs_c}, {vs_uh, cs_m}, {vs_uh, cs_n}, {vs_uh, cs_ng}, {vs_uh, cs_t},

  {vs_y, cs_t},
  {vs_ie, cs_c}, {vs_ie, cs_m}, {vs_ie, cs_n}, {vs_ie, cs_ng}, {vs_ie, cs_p}, {vs_ie, cs_t},
  {vs_ier, cs_c}, {vs_ier, cs_m}, {vs_ier, cs_n}, {vs_ier, cs_ng}, {vs_ier, cs_p}, {vs_ier, cs_t},

  {vs_oa, cs_c}, {vs_oa, cs_ch}, {vs_oa, cs_m}, {vs_oa, cs_n}, {vs_oa, cs_ng},
                 {vs_oa, cs_nh}, {vs_oa, cs_p}, {vs_oa, cs_t},
  {vs_oab, cs_c}, {vs_oab, cs_m}, {vs_oab, cs_n}, {vs_oab, cs_ng}, {vs_oab, cs_t},

  {vs_oe, cs_n}, {vs_oe, cs_t},

  {vs_ua, cs_n}, {vs_ua, cs_ng}, {vs_ua, cs_t},
  {vs_uar, cs_n}, {vs_uar, cs_ng}, {vs_uar, cs_t},

  {vs_ue, cs_c}, {vs_ue, cs_ch}, {vs_ue, cs_n}, {vs_ue, cs_nh},
  {vs_uer, cs_c}, {vs_uer, cs_ch}, {vs_uer, cs_n}, {vs_uer, cs_nh},

  {vs_uo, cs_c}, {vs_uo, cs_m}, {vs_uo, cs_n}, {vs_uo, cs_ng}, {vs_uo, cs_p}, {vs_uo, cs_t},
  {vs_uor, cs_c}, {vs_uor, cs_m}, {vs_uor, cs_n}, {vs_uor, cs_ng}, {vs_uor, cs_t},
  {vs_uho, cs_c}, {vs_uho, cs_m}, {vs_uho, cs_n}, {vs_uho, cs_ng}, {vs_uho, cs_p}, {vs_uho, cs_t},
  {vs_uhoh, cs_c}, {vs_uhoh, cs_m}, {vs_uhoh, cs_n}, {vs_uhoh, cs_ng}, {vs_uhoh, cs_p}, {vs_uhoh, cs_t},

  {vs_uy, cs_c}, {vs_uy, cs_ch}, {vs_uy, cs_n}, {vs_uy, cs_nh}, {vs_uy, cs_p}, {vs_uy, cs_t},

  {vs_ye, cs_m}, {vs_ye, cs_n}, {vs_ye, cs_ng}, {vs_ye, cs_p}, {vs_ye, cs_t},
  {vs_yer, cs_m}, {vs_yer, cs_n}, {vs_yer, cs_ng}, {vs_yer, cs_t},

  {vs_uye, cs_n}, {vs_uye, cs_t},
  {vs_uyer, cs_n}, {vs_uyer, cs_t}

};

const int VCPairCount = sizeof(VCPairList)/sizeof(VCPair);

//TODO: auto-complete: e.g. luan -> lua^n

typedef int (UkEngine::* UkKeyProc)(UkKeyEvent & ev);

UkKeyProc UkKeyProcList[vneCount] = {
    &UkEngine::processRoof,    //vneRoofAll
    &UkEngine::processRoof,    //vneRoof_a
    &UkEngine::processRoof,    //vneRoof_e
    &UkEngine::processRoof,    //vneRoof_o
    &UkEngine::processHook,    //vneHookAll
    &UkEngine::processHook,    //vneHook_uo
    &UkEngine::processHook,    //vneHook_u
    &UkEngine::processHook,    //vneHook_o
    &UkEngine::processHook,    //vneBowl
    &UkEngine::processDd,      //vneDd
    &UkEngine::processTone,    //vneTone0
    &UkEngine::processTone,    //vneTone1
    &UkEngine::processTone,    //vneTone2
    &UkEngine::processTone,    //vneTone3
    &UkEngine::processTone,    //vneTone4
    &UkEngine::processTone,    //vneTone5
    &UkEngine::processTelexW,  //vne_telex_w
    &UkEngine::processMapChar, //vneMapChar
    &UkEngine::processEscChar, //vneEscChar
    &UkEngine::processAppend   //vneNormal
};


VowelSeq lookupVSeq(VnLexiName v1, VnLexiName v2 = vnl_nonVnChar, VnLexiName v3 = vnl_nonVnChar);
ConSeq lookupCSeq(VnLexiName c1, VnLexiName c2 = vnl_nonVnChar, VnLexiName c3 = vnl_nonVnChar);

bool UkEngine::m_classInit = false;

//------------------------------------------------
int tripleVowelCompare(const void *p1, const void *p2)
{
    VSeqPair *t1 = (VSeqPair *)p1;
    VSeqPair *t2 = (VSeqPair *)p2;

    for (int i=0; i<3; i++) {
        if (t1->v[i] < t2->v[i])
            return -1;
        if (t1->v[i] > t2->v[i])
            return 1;
    }
    return 0;
}

//------------------------------------------------
int tripleConCompare(const void *p1, const void *p2)
{
    CSeqPair *t1 = (CSeqPair *)p1;
    CSeqPair *t2 = (CSeqPair *)p2;

    for (int i=0; i<3; i++) {
        if (t1->c[i] < t2->c[i])
            return -1;
        if (t1->c[i] > t2->c[i])
            return 1;
    }
    return 0;
}

//------------------------------------------------
int VCPairCompare(const void *p1, const void *p2)
{
    VCPair *t1 = (VCPair *)p1;
    VCPair *t2 = (VCPair *)p2;

    if (t1->v < t2->v)
        return -1;
    if (t1->v > t2->v)
      return 1;
  
    if (t1->c < t2->c)
        return -1;
    if (t1->c > t2->c)
        return 1;
    return 0;
}

//----------------------------------------------------------
bool isValidCV(ConSeq c, VowelSeq v)
{
    if (c == cs_nil || v == vs_nil)
        return true;

    VowelSeqInfo & vInfo = VSeqList[v];

    if ((c == cs_gi && vInfo.v[0] == vnl_i) ||
        (c == cs_qu && vInfo.v[0] == vnl_u))
        return false; // gi doesn't go with i, qu doesn't go with u
  
    if (c == cs_k) {
        // k can only go with the following vowel sequences
        static VowelSeq kVseq[] = {vs_e, vs_i, vs_y, vs_er, vs_eo, vs_eu, 
                                   vs_eru, vs_ia, vs_ie, vs_ier, vs_ieu, vs_ieru, vs_nil};
        int i;
        for (i=0; kVseq[i] != vs_nil && kVseq[i] != v; i++);
        return (kVseq[i] != vs_nil);
    }

    //More checks
    return true;
}

//----------------------------------------------------------
bool isValidVC(VowelSeq v, ConSeq c)
{
    if (v == vs_nil || c == cs_nil)
        return true;

    VowelSeqInfo & vInfo = VSeqList[v];
    if (!vInfo.conSuffix)
        return false;

    ConSeqInfo & cInfo = CSeqList[c];
    if (!cInfo.suffix)
        return false;

    VCPair p;
    p.v = v;
    p.c = c;
    if (bsearch(&p, VCPairList, VCPairCount, sizeof(VCPair), VCPairCompare))
        return true;

    return false;
}

//----------------------------------------------------------
bool isValidCVC(ConSeq c1, VowelSeq v, ConSeq c2)
{
    if (v == vs_nil)
        return (c1 == cs_nil || c2 != cs_nil);

    if (c1 == cs_nil)
        return isValidVC(v, c2);

    if (c2 == cs_nil)
        return isValidCV(c1, v);

    bool okCV = isValidCV(c1, v);
    bool okVC = isValidVC(v, c2);

    if (okCV && okVC)
        return true;

    if (!okVC) {
        //check some exceptions: vc fails but cvc passes

        // quyn, quynh
        if (c1 == cs_qu && v == vs_y && (c2 == cs_n || c2 == cs_nh))
            return true;

        // gieng, gie^ng
        if (c1 == cs_gi && (v == vs_e || v == vs_er) && (c2 == cs_n || c2 == cs_ng))
            return true;
    }
    return false;
}

//------------------------------------------------
void engineClassInit()
{
    int i, j;

    for (i=0; i < VSeqCount; i++) {
        for (j=0; j<3; j++)
            SortedVSeqList[i].v[j] = VSeqList[i].v[j];
        SortedVSeqList[i].vs = (VowelSeq)i;
    }

    for (i=0; i < CSeqCount; i++) {
        for (j=0; j<3; j++)
            SortedCSeqList[i].c[j] = CSeqList[i].c[j];
        SortedCSeqList[i].cs = (ConSeq)i;
    }

    qsort(SortedVSeqList, VSeqCount, sizeof(VSeqPair), tripleVowelCompare);
    qsort(SortedCSeqList, CSeqCount, sizeof(CSeqPair), tripleConCompare);
    qsort(VCPairList, VCPairCount, sizeof(VCPair), VCPairCompare);

    for (i=0; i<vnl_lastChar; i++)
        IsVnVowel[i] = true;

    unsigned char ch;
    for (ch='a'; ch <= 'z'; ch++) {
        if (ch != 'a' && ch != 'e' && ch != 'i' &&
            ch != 'o' && ch != 'u' && ch != 'y') {
            IsVnVowel[AZLexiLower[ch-'a']] = false;
            IsVnVowel[AZLexiUpper[ch-'a']] = false;
        }
    }
    IsVnVowel[vnl_dd] = false;
    IsVnVowel[vnl_DD] = false;
}

//------------------------------------------------
VowelSeq lookupVSeq(VnLexiName v1, VnLexiName v2, VnLexiName v3)
{
    VSeqPair key;
    key.v[0] = v1;
    key.v[1] = v2;
    key.v[2] = v3;

    VSeqPair *pInfo = (VSeqPair *)bsearch(&key, SortedVSeqList, VSeqCount, sizeof(VSeqPair), tripleVowelCompare);
    if (pInfo == 0)
        return vs_nil;
    return pInfo->vs;
}

//------------------------------------------------
ConSeq lookupCSeq(VnLexiName c1, VnLexiName c2, VnLexiName c3)
{
    CSeqPair key;
    key.c[0] = c1;
    key.c[1] = c2;
    key.c[2] = c3;

    CSeqPair *pInfo = (CSeqPair *)bsearch(&key, SortedCSeqList, CSeqCount, sizeof(CSeqPair), tripleConCompare);
    if (pInfo == 0)
        return cs_nil;
    return pInfo->cs;
}

//------------------------------------------------------------------
int UkEngine::processRoof(UkKeyEvent & ev)
{
    if (!m_pCtrl->vietKey || m_current < 0 || m_buffer[m_current].vOffset < 0)
        return processAppend(ev);

    VnLexiName target;
    switch (ev.evType) {
    case vneRoof_a:
        target = vnl_ar;
        break;
    case vneRoof_e:
        target = vnl_er;
        break;
    case vneRoof_o:
        target = vnl_or;
        break;
    default:
        target = vnl_nonVnChar;
    }


    VowelSeq vs, newVs;
    int i, vStart, vEnd;
    int curTonePos, newTonePos, tone;
    int changePos;
    bool roofRemoved = false;

    vEnd = m_current - m_buffer[m_current].vOffset;
    vs = m_buffer[vEnd].vseq;
    vStart = vEnd - (VSeqList[vs].len - 1);
    curTonePos = vStart + getTonePosition(vs, vEnd == m_current);
    tone = m_buffer[curTonePos].tone;

    bool doubleChangeUO = false;
    if (vs == vs_uho || vs == vs_uhoh || vs == vs_uhoi || vs == vs_uhohi) {
        //special cases: u+o+ -> uo^, u+o -> uo^, u+o+i -> uo^i, u+oi -> uo^i
        newVs = lookupVSeq(vnl_u, vnl_or, VSeqList[vs].v[2]);
        doubleChangeUO = true;
    }
    else {
        newVs = VSeqList[vs].withRoof;
    }

    VowelSeqInfo *pInfo;

    if (newVs == vs_nil) {
        if (VSeqList[vs].roofPos == -1)
            return processAppend(ev); //roof is not applicable
    
        //a roof already exists -> undo roof
        VnLexiName curCh = m_buffer[vStart + VSeqList[vs].roofPos].vnSym;
        if (target != vnl_nonVnChar && curCh != target)
            return processAppend(ev); //specific roof and the roof character don't match

        VnLexiName newCh = (curCh == vnl_ar)? vnl_a : ((curCh == vnl_er)? vnl_e : vnl_o);
        changePos = vStart + VSeqList[vs].roofPos;

        if (!m_pCtrl->options.freeMarking && changePos != m_current)
            return processAppend(ev);

        markChange(changePos);
        m_buffer[changePos].vnSym = newCh;

        if (VSeqList[vs].len == 3)
            newVs = lookupVSeq(m_buffer[vStart].vnSym, m_buffer[vStart+1].vnSym, m_buffer[vStart+2].vnSym);
        else if (VSeqList[vs].len == 2)
            newVs = lookupVSeq(m_buffer[vStart].vnSym, m_buffer[vStart+1].vnSym);
        else
            newVs = lookupVSeq(m_buffer[vStart].vnSym);

        pInfo = &VSeqList[newVs];
        roofRemoved = true;
    }
    else {
        pInfo = &VSeqList[newVs];
        if (target != vnl_nonVnChar &&  pInfo->v[pInfo->roofPos] != target)
            return processAppend(ev);

        //check validity of new VC and CV
        bool valid = true;
        ConSeq c1 = cs_nil;
        ConSeq c2 = cs_nil;
        if (m_buffer[m_current].c1Offset != -1)
            c1 = m_buffer[m_current-m_buffer[m_current].c1Offset].cseq;
        
        if (m_buffer[m_current].c2Offset != -1)
            c2 = m_buffer[m_current-m_buffer[m_current].c2Offset].cseq;

        valid = isValidCVC(c1, newVs, c2);
        if (!valid)
            return processAppend(ev);

        if (doubleChangeUO) {
            changePos = vStart;
        }
        else {
            changePos = vStart + pInfo->roofPos;
        }
        if (!m_pCtrl->options.freeMarking && changePos != m_current)
            return processAppend(ev);
        markChange(changePos);
        if (doubleChangeUO) {
            m_buffer[vStart].vnSym = vnl_u;
            m_buffer[vStart+1].vnSym = vnl_or;
        }
        else {
            m_buffer[changePos].vnSym = pInfo->v[pInfo->roofPos];
        }
    }

    for (i=0; i < pInfo->len; i++) { //update sub-sequences
        m_buffer[vStart+i].vseq = pInfo->sub[i];
    }

    //check if tone re-position is needed
    newTonePos = vStart + getTonePosition(newVs, vEnd == m_current);
    /* //For now, users don't seem to like the following processing, thus commented out
    if (roofRemoved && tone != 0 &&
        (!pInfo->complete || changePos == curTonePos)) {
        //remove tone if the vowel sequence becomes incomplete as a result of roof removal OR
        //if removed roof is at the same position as the current tone
        markChange(curTonePos);
        m_buffer[curTonePos].tone = 0;
    } else
    */
    if (curTonePos != newTonePos && tone != 0) {
        markChange(newTonePos);
        m_buffer[newTonePos].tone = tone;
        markChange(curTonePos);
        m_buffer[curTonePos].tone = 0;
    }

    if (roofRemoved) {
        m_singleMode = false;
        processAppend(ev);
        m_reverted = true;
    }

    return 1;
}

//------------------------------------------------------------------
// can only be called from processHook
//------------------------------------------------------------------
int UkEngine::processHookWithUO(UkKeyEvent & ev)
{
    VowelSeq vs, newVs;
    int i, vStart, vEnd;
    int curTonePos, newTonePos, tone;
    bool hookRemoved = false;
    bool removeWithUndo = true;
    bool toneRemoved = false;

    VnLexiName *v;

    if (!m_pCtrl->options.freeMarking && m_buffer[m_current].vOffset != 0)
        return processAppend(ev);    

    vEnd = m_current - m_buffer[m_current].vOffset;
    vs = m_buffer[vEnd].vseq;
    vStart = vEnd - (VSeqList[vs].len - 1);
    v = VSeqList[vs].v;
    curTonePos = vStart + getTonePosition(vs, vEnd == m_current);
    tone = m_buffer[curTonePos].tone;

    switch (ev.evType) {
    case vneHook_u:
        if (v[0] == vnl_u) {
            newVs = VSeqList[vs].withHook;
            markChange(vStart);
            m_buffer[vStart].vnSym = vnl_uh;
        }
        else {// v[0] = vnl_uh, -> uo
            newVs = lookupVSeq(vnl_u, vnl_o, v[2]);
            markChange(vStart);
            m_buffer[vStart].vnSym = vnl_u;
            m_buffer[vStart+1].vnSym = vnl_o;
            hookRemoved = true;
            toneRemoved =  (m_buffer[vStart].tone != 0);
        }
        break;
    case vneHook_o:
        if (v[1] == vnl_o || v[1] == vnl_or) {
            if (vEnd == m_current && VSeqList[vs].len == 2 && 
                m_buffer[m_current].form == vnw_cv && m_buffer[m_current-2].cseq == cs_th)
            {
                // o|o^ -> o+
                newVs = VSeqList[vs].withHook;
                markChange(vStart+1);
                m_buffer[vStart+1].vnSym = vnl_oh;
            }
            else {
                newVs = lookupVSeq(vnl_uh, vnl_oh, v[2]);
                if (v[0] == vnl_u) {
                    markChange(vStart);
                    m_buffer[vStart].vnSym = vnl_uh;
                    m_buffer[vStart+1].vnSym = vnl_oh;
                }
                else {
                    markChange(vStart+1);
                    m_buffer[vStart+1].vnSym = vnl_oh;
                }
            }
        }
        else {// v[1] = vnl_oh, -> uo
            newVs = lookupVSeq(vnl_u, vnl_o, v[2]);
            if (v[0] == vnl_uh) {
                markChange(vStart);
                m_buffer[vStart].vnSym = vnl_u;
                m_buffer[vStart+1].vnSym = vnl_o;
            }
            else {
                markChange(vStart+1);
                m_buffer[vStart+1].vnSym = vnl_o;
            }
            hookRemoved = true;
            toneRemoved = (m_buffer[vStart+1].tone != 0);
        }
        break;
    default:  //vneHookAll, vneHookUO:
        if (v[0] == vnl_u) {
            if (v[1] == vnl_o || v[1] == vnl_or) { 
                //uo -> uo+ if prefixed by "th"
                if ((vs == vs_uo || vs == vs_uor) && vEnd == m_current && 
                    m_buffer[m_current].form == vnw_cv && m_buffer[m_current-2].cseq == cs_th) 
                {
                    newVs = vs_uoh;
                    markChange(vStart+1);
                    m_buffer[vStart+1].vnSym = vnl_oh;
                }
                else {
                    //uo -> u+o+
                    newVs = VSeqList[vs].withHook;
                    markChange(vStart);
                    m_buffer[vStart].vnSym = vnl_uh;
                    newVs = VSeqList[newVs].withHook;
                    m_buffer[vStart+1].vnSym = vnl_oh;
                }
            }
            else {//uo+ -> u+o+
                newVs = VSeqList[vs].withHook;
                markChange(vStart);
                m_buffer[vStart].vnSym = vnl_uh;
            }
        }
        else {//v[0] == vnl_uh
            if (v[1] == vnl_o) { // u+o -> u+o+
                newVs = VSeqList[vs].withHook;
                markChange(vStart+1);
                m_buffer[vStart+1].vnSym = vnl_oh;
            }
            else { //v[1] == vnl_oh, u+o+ -> uo
                newVs = lookupVSeq(vnl_u, vnl_o, v[2]); //vs_uo;
                markChange(vStart);
                m_buffer[vStart].vnSym = vnl_u;
                m_buffer[vStart+1].vnSym = vnl_o;
                hookRemoved = true;
                toneRemoved = (m_buffer[vStart].tone != 0 || m_buffer[vStart+1].tone != 0);
            }
        }
        break;
    }

    VowelSeqInfo *p = &VSeqList[newVs];
    for (i=0; i < p->len; i++) { //update sub-sequences
        m_buffer[vStart+i].vseq = p->sub[i];
    }

    //check if tone re-position is needed
    newTonePos = vStart + getTonePosition(newVs, vEnd == m_current);
    /* //For now, users don't seem to like the following processing, thus commented out
    if (hookRemoved && tone != 0 && (!p->complete || toneRemoved)) {
        //remove tone if the vowel sequence becomes incomplete as a result of hook removal
        //OR if a removed hook is at the same position as the current tone
        markChange(curTonePos);
        m_buffer[curTonePos].tone = 0;
    }
    else 
    */
    if (curTonePos != newTonePos && tone != 0) {
        markChange(newTonePos);
        m_buffer[newTonePos].tone = tone;
        markChange(curTonePos);
        m_buffer[curTonePos].tone = 0;
    }

    if (hookRemoved && removeWithUndo) {
        m_singleMode = false;
        processAppend(ev);
        m_reverted = true;
    }

    return 1;
}

//------------------------------------------------------------------
int UkEngine::processHook(UkKeyEvent & ev)
{
    if (!m_pCtrl->vietKey || m_current < 0 || m_buffer[m_current].vOffset < 0)
        return processAppend(ev);

    VowelSeq vs, newVs;
    int i, vStart, vEnd;
    int curTonePos, newTonePos, tone;
    int changePos;
    bool hookRemoved = false;
    VowelSeqInfo *pInfo;
    VnLexiName *v;

    vEnd = m_current - m_buffer[m_current].vOffset;
    vs = m_buffer[vEnd].vseq;

    v = VSeqList[vs].v;
  
    if (VSeqList[vs].len > 1 && 
        ev.evType != vneBowl &&
        (v[0] == vnl_u || v[0] == vnl_uh) &&
        (v[1] == vnl_o || v[1] == vnl_oh || v[1] == vnl_or))
        return processHookWithUO(ev);

    vStart = vEnd - (VSeqList[vs].len - 1);
    curTonePos = vStart + getTonePosition(vs, vEnd == m_current);
    tone = m_buffer[curTonePos].tone;

    newVs = VSeqList[vs].withHook;
    if (newVs == vs_nil) {
        if (VSeqList[vs].hookPos == -1)
            return processAppend(ev); //hook is not applicable

        //a hook already exists -> undo hook
        VnLexiName curCh = m_buffer[vStart + VSeqList[vs].hookPos].vnSym;
        VnLexiName newCh = (curCh == vnl_ab)? vnl_a : ((curCh == vnl_uh)? vnl_u : vnl_o);
        changePos = vStart + VSeqList[vs].hookPos;
        if (!m_pCtrl->options.freeMarking && changePos != m_current)
            return processAppend(ev);

        switch (ev.evType) {
        case vneHook_u:
            if (curCh != vnl_uh)
                return processAppend(ev);
            break;
        case vneHook_o:
            if (curCh != vnl_oh)
                return processAppend(ev);
            break;
        case vneBowl:
            if (curCh != vnl_ab)
                return processAppend(ev);
            break;
        default:
            if (ev.evType == vneHook_uo && curCh == vnl_ab)
                return processAppend(ev);
        }

        markChange(changePos);
        m_buffer[changePos].vnSym = newCh;

        if (VSeqList[vs].len == 3)
            newVs = lookupVSeq(m_buffer[vStart].vnSym, m_buffer[vStart+1].vnSym, m_buffer[vStart+2].vnSym);
        else if (VSeqList[vs].len == 2)
            newVs = lookupVSeq(m_buffer[vStart].vnSym, m_buffer[vStart+1].vnSym);
        else
            newVs = lookupVSeq(m_buffer[vStart].vnSym);

        pInfo = &VSeqList[newVs];
        hookRemoved = true;
    }
    else {
        pInfo = &VSeqList[newVs];

        switch (ev.evType) {
        case vneHook_u:
            if (pInfo->v[pInfo->hookPos] != vnl_uh)
                return processAppend(ev);
            break;
        case vneHook_o:
            if (pInfo->v[pInfo->hookPos] != vnl_oh)
                return processAppend(ev);
            break;
        case vneBowl:
            if (pInfo->v[pInfo->hookPos] != vnl_ab)
                return processAppend(ev);
            break;
        default: //vneHook_uo, vneHookAll
            if (ev.evType == vneHook_uo && pInfo->v[pInfo->hookPos] == vnl_ab)
                return processAppend(ev);
        }

        //check validity of new VC and CV
        bool valid = true;
        ConSeq c1 = cs_nil;
        ConSeq c2 = cs_nil;
        if (m_buffer[m_current].c1Offset != -1)
            c1 = m_buffer[m_current-m_buffer[m_current].c1Offset].cseq;
        
        if (m_buffer[m_current].c2Offset != -1)
            c2 = m_buffer[m_current-m_buffer[m_current].c2Offset].cseq;

        valid = isValidCVC(c1, newVs, c2);

        if (!valid)
            return processAppend(ev);

        changePos = vStart + pInfo->hookPos;
        if (!m_pCtrl->options.freeMarking && changePos != m_current)
            return processAppend(ev);

        markChange(changePos);
        m_buffer[changePos].vnSym = pInfo->v[pInfo->hookPos];
    }
   
    for (i=0; i < pInfo->len; i++) { //update sub-sequences
        m_buffer[vStart+i].vseq = pInfo->sub[i];
    }

    //check if tone re-position is needed
    newTonePos = vStart + getTonePosition(newVs, vEnd == m_current);
    /* //For now, users don't seem to like the following processing, thus commented out
    if (hookRemoved && tone != 0 && 
        (!pInfo->complete || (hookRemoved && curTonePos == changePos))) {
        //remove tone if the vowel sequence becomes incomplete as a result of hook removal
        //OR if a removed hook was at the same position as the current tone
        markChange(curTonePos);
        m_buffer[curTonePos].tone = 0;
    }
    else */
    if (curTonePos != newTonePos && tone != 0) {
        markChange(newTonePos);
        m_buffer[newTonePos].tone = tone;
        markChange(curTonePos);
        m_buffer[curTonePos].tone = 0;
    }

    if (hookRemoved) {
        m_singleMode = false;
        processAppend(ev);
        m_reverted = true;
    }

    return 1;
}

//----------------------------------------------------------
int UkEngine::getTonePosition(VowelSeq vs, bool terminated)
{
    VowelSeqInfo & info = VSeqList[vs];
    if (info.len == 1)
        return 0;

    if (info.roofPos != -1)
        return info.roofPos;
    if (info.hookPos != -1) {
        if (vs == vs_uhoh || vs == vs_uhohi || vs == vs_uhohu) //u+o+, u+o+u, u+o+i
            return 1;
        return info.hookPos;
    }
  
    if (info.len == 3)
        return 1;

    if (m_pCtrl->options.modernStyle &&
        (vs == vs_oa || vs == vs_oe ||vs == vs_uy))
        return 1;

    return terminated ? 0 : 1;
}

//----------------------------------------------------------
int UkEngine::processTone(UkKeyEvent & ev)
{
    if (m_current < 0 || !m_pCtrl->vietKey)
        return processAppend(ev);

    if (m_buffer[m_current].form == vnw_c && 
        (m_buffer[m_current].cseq == cs_gi || m_buffer[m_current].cseq == cs_gin)) {
        int p = (m_buffer[m_current].cseq == cs_gi)? m_current : m_current - 1;
        if (m_buffer[p].tone == 0 && ev.tone == 0)
            return processAppend(ev);
        markChange(p);
        if (m_buffer[p].tone == ev.tone) {
            m_buffer[p].tone = 0;
            m_singleMode = false;
            processAppend(ev);
            m_reverted = true;
            return 1;
        }
        m_buffer[p].tone = ev.tone;
        return 1;
    }

    if (m_buffer[m_current].vOffset < 0)
        return processAppend(ev);

    int vEnd;
    VowelSeq vs;

    vEnd = m_current - m_buffer[m_current].vOffset;
    vs = m_buffer[vEnd].vseq;
    VowelSeqInfo & info = VSeqList[vs];
    if (m_pCtrl->options.spellCheckEnabled && !m_pCtrl->options.freeMarking && !info.complete)
        return processAppend(ev);

    if (m_buffer[m_current].form == vnw_vc || m_buffer[m_current].form == vnw_cvc) {
        ConSeq cs = m_buffer[m_current].cseq;
        if ((cs == cs_c || cs == cs_ch || cs == cs_p || cs == cs_t) &&
            (ev.tone == 2 || ev.tone == 3 || ev.tone == 4))
            return processAppend(ev); // c, ch, p, t suffixes don't allow ` ? ~
    }
      
    int toneOffset = getTonePosition(vs, vEnd == m_current);
    int tonePos = vEnd - (info.len -1 ) + toneOffset;
    if (m_buffer[tonePos].tone == 0 && ev.tone == 0)
        return processAppend(ev);

    if (m_buffer[tonePos].tone == ev.tone) {
        markChange(tonePos);
        m_buffer[tonePos].tone = 0;
        m_singleMode = false;
        processAppend(ev);
        m_reverted = true;
        return 1;
    }

    markChange(tonePos);
    m_buffer[tonePos].tone = ev.tone;
    return 1;
}

//----------------------------------------------------------
int UkEngine::processDd(UkKeyEvent & ev)
{
    if (!m_pCtrl->vietKey || m_current < 0)
        return processAppend(ev);
    
    int pos;

    // we want to allow dd even in non-vn sequence, because dd is used a lot in abbreviation
    // we allow dd only if preceding character is not a vowel
    if (m_buffer[m_current].form == vnw_nonVn && 
        m_buffer[m_current].vnSym == vnl_d &&
        (m_buffer[m_current-1].vnSym == vnl_nonVnChar ||!IsVnVowel[m_buffer[m_current-1].vnSym]))
    {
        m_singleMode = true;
        pos = m_current;
        markChange(pos);
        m_buffer[pos].cseq = cs_dd;
        m_buffer[pos].vnSym = vnl_dd;
        m_buffer[pos].form = vnw_c;
        m_buffer[pos].c1Offset = 0;
        m_buffer[pos].c2Offset = -1;
        m_buffer[pos].vOffset = -1;
        return 1;
    }

    if (m_buffer[m_current].c1Offset < 0) {
        return processAppend(ev);
    }

    pos = m_current - m_buffer[m_current].c1Offset;
    if (!m_pCtrl->options.freeMarking && pos != m_current)
        return processAppend(ev);

    if (m_buffer[pos].cseq == cs_d) {
        markChange(pos);
        m_buffer[pos].cseq = cs_dd;
        m_buffer[pos].vnSym = vnl_dd;
        //never spellcheck a word which starts with dd, because it's used alot in abbreviation
        m_singleMode = true;
        return 1;
    }

    if (m_buffer[pos].cseq == cs_dd) {
        //undo dd
        markChange(pos);
        m_buffer[pos].cseq = cs_d;
        m_buffer[pos].vnSym = vnl_d;
        m_singleMode = false;
        processAppend(ev);
        m_reverted = true;
        return 1;
    }
  
    return processAppend(ev);
}

//----------------------------------------------------------
VnLexiName changeCase(VnLexiName x)
{
    if (x == vnl_nonVnChar)
        return x;
    if (!(x & 0x01))
        return (VnLexiName)(x+1);
    return (VnLexiName)(x-1);
}

//----------------------------------------------------------
inline VnLexiName vnToLower(VnLexiName x)
{
    if (x == vnl_nonVnChar)
        return x;
    if (!(x & 0x01)) //even
        return (VnLexiName)(x+1);
    return x;
}

//----------------------------------------------------------
int UkEngine::processMapChar(UkKeyEvent & ev)
{
    int capsLockOn = 0;
    int shiftPressed = 0;
    if (m_keyCheckFunc)
        m_keyCheckFunc(&shiftPressed, &capsLockOn);

    if (capsLockOn)
        ev.vnSym = changeCase(ev.vnSym);

    int ret = processAppend(ev);
    if (!m_pCtrl->vietKey)
        return ret;

    if (m_current >= 0 && m_buffer[m_current].form != vnw_empty &&
        m_buffer[m_current].form != vnw_nonVn) {
        return 1;
    }

    if (m_current < 0)
        return 0;

    // mapChar doesn't apply
    m_current--;
    WordInfo & entry = m_buffer[m_current];

    bool undo = false;
    // test if undo is needed
    if (entry.form != vnw_empty && entry.form != vnw_nonVn) {
        VnLexiName prevSym = entry.vnSym;
        if (entry.caps) {
            prevSym = (VnLexiName)(prevSym - 1);
        }
        if (prevSym == ev.vnSym) {
            if (entry.form != vnw_c) {
                int vStart, vEnd, curTonePos, newTonePos, tone;
                VowelSeq vs, newVs;

                vEnd = m_current - entry.vOffset;
                vs = m_buffer[vEnd].vseq;
                vStart = vEnd - VSeqList[vs].len +1;
                curTonePos = vStart + getTonePosition(vs, vEnd == m_current);
                tone = m_buffer[curTonePos].tone;
                markChange(m_current);
                m_current--;

                //check if tone position is needed
                if (tone != 0 && m_current >= 0 &&
                    (m_buffer[m_current].form == vnw_v || m_buffer[m_current].form == vnw_cv)) {
                    newVs = m_buffer[m_current].vseq;
                    newTonePos = vStart + getTonePosition(newVs, true);
                    if (newTonePos != curTonePos) {
                        markChange(newTonePos);
                        m_buffer[newTonePos].tone = tone;
                        markChange(curTonePos);
                        m_buffer[curTonePos].tone = 0;
                    }
                }
            }
            else {
                markChange(m_current);
                m_current--;
            }
            undo = true;
        }
    }

    ev.evType = vneNormal;
    ev.chType = m_pCtrl->input.getCharType(ev.keyCode);
    ev.vnSym = IsoToVnLexi(ev.keyCode);
    ret = processAppend(ev);
    if (undo) {
        m_singleMode = false;
        m_reverted = true;
        return 1;
    }
    return ret;
}

//----------------------------------------------------------
int UkEngine::processTelexW(UkKeyEvent & ev)
{
    if (!m_pCtrl->vietKey)
        return processAppend(ev);

    int ret;
    static bool usedAsMapChar = false;
    int capsLockOn = 0;
    int shiftPressed = 0;
    if (m_keyCheckFunc)
        m_keyCheckFunc(&shiftPressed, &capsLockOn);

    if (usedAsMapChar) {
        ev.evType = vneMapChar;
        ev.vnSym = isupper(ev.keyCode)? vnl_Uh : vnl_uh;
        if (capsLockOn)
            ev.vnSym = changeCase(ev.vnSym);
        ev.chType = ukcVn;
        ret = processMapChar(ev);
        if (ret == 0) {
            if (m_current >= 0)
                m_current--;
            usedAsMapChar = false;
            ev.evType = vneHookAll;
            return processHook(ev);
        }
        return ret;
    }

    ev.evType = vneHookAll;
    usedAsMapChar = false;
    ret = processHook(ev);
    if (ret == 0) {
        if (m_current >= 0)
            m_current--;
        ev.evType = vneMapChar;
        ev.vnSym = isupper(ev.keyCode)? vnl_Uh : vnl_uh;
        if (capsLockOn)
            ev.vnSym = changeCase(ev.vnSym);
        ev.chType = ukcVn;
        usedAsMapChar = true;
        return processMapChar(ev);
    }
    return ret;
}

//----------------------------------------------------------
int UkEngine::checkEscapeVIQR(UkKeyEvent & ev)
{
    if (m_current < 0)
        return 0;
    WordInfo & entry = m_buffer[m_current];
    int escape = 0;
    if (entry.form == vnw_v || entry.form == vnw_cv) {
        switch (ev.keyCode) {
        case '^':
            escape = (entry.vnSym == vnl_a || entry.vnSym == vnl_o || entry.vnSym == vnl_e);
            break;
        case '(':
            escape = (entry.vnSym == vnl_a);
            break;
        case '+':
            escape = (entry.vnSym == vnl_o || entry.vnSym == vnl_u);
            break;
        case '\'':
        case '`':
        case '?':
        case '~':
        case '.':
            escape = (entry.tone == 0);
            break;
        }
    }
    else if (entry.form == vnw_nonVn) {
        unsigned char ch = toupper(entry.keyCode);
        switch (ev.keyCode) {
        case '^':
            escape = (ch == 'A' || ch == 'O' || ch == 'E');
            break;
        case '(':
            escape = (ch == 'A');
            break;
        case '+':
            escape = (ch == 'O' || ch == 'U');
            break;
        case '\'':
        case '`':
        case '?':
        case '~':
        case '.':
            escape = (ch == 'A' || ch == 'E' || ch == 'I' ||
                      ch == 'O' || ch == 'U' || ch == 'Y');
            break;
        }
    }
  
    if (escape) {
        m_current++;
        WordInfo *p = &m_buffer[m_current];
        p->form = (ev.chType == ukcWordBreak) ? vnw_empty : vnw_nonVn;
        p->c1Offset = p->c2Offset = p->vOffset = -1;
        p->keyCode = '?';
        p->vnSym = vnl_nonVnChar;

        m_current++;
        p++;
        p->form = (ev.chType == ukcWordBreak) ? vnw_empty : vnw_nonVn;
        p->c1Offset = p->c2Offset = p->vOffset = -1;
        p->keyCode = ev.keyCode;
        p->vnSym = vnl_nonVnChar;

        //write output
        m_pOutBuf[0] = '\\';
        m_pOutBuf[1] = ev.keyCode;
        *m_pOutSize = 2;
        m_outputWritten = true;
    }
    return escape;
}

//----------------------------------------------------------
int UkEngine::processAppend(UkKeyEvent & ev)
{
    int ret = 0;
    switch (ev.chType) {
    case ukcReset:
#if defined(_WIN32)
        if (ev.keyCode == ENTER_CHAR) {
            if (m_pCtrl->options.macroEnabled && macroMatch(ev))
                return 1;
        }
#endif
        reset();
        return 0;
    case ukcWordBreak:
        m_singleMode = false;
        return processWordEnd(ev);
    case ukcNonVn:
        {
            if (m_pCtrl->vietKey && m_pCtrl->charsetId == CONV_CHARSET_VIQR && checkEscapeVIQR(ev))
                return 1;

            m_current++;
            WordInfo & entry = m_buffer[m_current];
            entry.form = (ev.chType == ukcWordBreak) ? vnw_empty : vnw_nonVn;
            entry.c1Offset = entry.c2Offset = entry.vOffset = -1;
            entry.keyCode = ev.keyCode;
            entry.vnSym = vnToLower(ev.vnSym);
            entry.tone = 0;
            entry.caps = (entry.vnSym != ev.vnSym);
            if (!m_pCtrl->vietKey || m_pCtrl->charsetId != CONV_CHARSET_UNI_CSTRING)
                return 0;
            markChange(m_current);
            return 1;
        }
    case ukcVn:
        {
            if (IsVnVowel[ev.vnSym]) {
                VnLexiName v = (VnLexiName)StdVnNoTone[vnToLower(ev.vnSym)];
                if (m_current >= 0 && m_buffer[m_current].form == vnw_c &&
                      ((m_buffer[m_current].cseq == cs_q && v == vnl_u) ||
                      (m_buffer[m_current].cseq == cs_g && v == vnl_i))) {
                    return appendConsonnant(ev); //process u after q, i after g as consonnants
                }
                return appendVowel(ev);
            }
            return appendConsonnant(ev);
        }
        break;
    }

    return ret;
}

//----------------------------------------------------------
int UkEngine::appendVowel(UkKeyEvent & ev)
{
    bool autoCompleted = false;

    m_current++;
    WordInfo & entry = m_buffer[m_current];

    VnLexiName lowerSym = vnToLower(ev.vnSym);
    VnLexiName canSym = (VnLexiName)StdVnNoTone[lowerSym];

    entry.vnSym = canSym;
    entry.caps = (lowerSym != ev.vnSym);
    entry.tone = (lowerSym - canSym)/2;
    entry.keyCode = ev.keyCode;

    if (m_current == 0 || !m_pCtrl->vietKey) {
        entry.form = vnw_v;
        entry.c1Offset = entry.c2Offset = -1;
        entry.vOffset = 0;
        entry.vseq = lookupVSeq(canSym);

        if (!m_pCtrl->vietKey || 
            ((m_pCtrl->charsetId != CONV_CHARSET_UNI_CSTRING) && isalpha(entry.keyCode)) ) {
            return 0;
        }
        markChange(m_current);
        return 1;
    }
  
    WordInfo & prev = m_buffer[m_current-1];
    VowelSeq vs, newVs;
    ConSeq cs;
    int prevTonePos;
    int tone, newTone, tonePos, newTonePos;

    switch (prev.form) {

    case vnw_empty:
        entry.form = vnw_v;
        entry.c1Offset = entry.c2Offset = -1;
        entry.vOffset = 0;
        entry.vseq = newVs = lookupVSeq(canSym);
        break;

    case vnw_nonVn:
    case vnw_cvc:
    case vnw_vc:
        entry.form = vnw_nonVn;
        entry.c1Offset = entry.c2Offset = entry.vOffset = -1;
        break;

    case vnw_v:
    case vnw_cv:
        vs = prev.vseq;
        prevTonePos = (m_current - 1) - (VSeqList[vs].len - 1) + getTonePosition(vs, true);
        tone = m_buffer[prevTonePos].tone;
    
        if (lowerSym != canSym && tone != 0) //new sym has a tone, but there's is already a preceeding tone
            newVs = vs_nil;
        else {
            if (VSeqList[vs].len == 3)
                newVs = vs_nil;
            else if (VSeqList[vs].len == 2)
                newVs = lookupVSeq(VSeqList[vs].v[0], VSeqList[vs].v[1], canSym);
            else
                newVs = lookupVSeq(VSeqList[vs].v[0], canSym);
        }

        if (newVs != vs_nil && prev.form == vnw_cv) {
            cs = m_buffer[m_current - 1 - prev.c1Offset].cseq;
            if (!isValidCV(cs, newVs))
                newVs = vs_nil;
        }

        if (newVs == vs_nil) {
            entry.form = vnw_nonVn;
            entry.c1Offset = entry.c2Offset = entry.vOffset = -1;
            break;
        }
    
        entry.form = prev.form;
        if (prev.form == vnw_cv)
            entry.c1Offset = prev.c1Offset + 1;
        else
            entry.c1Offset = -1;
        entry.c2Offset = -1;
        entry.vOffset = 0;
        entry.vseq = newVs;
        entry.tone = 0;
        
        newTone = (lowerSym - canSym)/2;
        if (tone == 0) {
            if (newTone != 0) {
                tone = newTone;
                tonePos = getTonePosition(newVs, true) + ((m_current - 1) - VSeqList[vs].len + 1);
                markChange(tonePos);
                m_buffer[tonePos].tone = tone;
                return 1;
            }
        }
        else {
            newTonePos = getTonePosition(newVs, true) + ((m_current - 1) - VSeqList[vs].len + 1);
            if (newTonePos != prevTonePos) {
                markChange(prevTonePos);
                m_buffer[prevTonePos].tone = 0;
                markChange(newTonePos);
                if (newTone != 0)
                    tone = newTone;
                m_buffer[newTonePos].tone = tone;
                return 1;
            }
            if (newTone != 0 && newTone != tone) {
                tone = newTone;
                markChange(prevTonePos);
                m_buffer[prevTonePos].tone = tone;
                return 1;
            }

        }

        break;
    case vnw_c:
        newVs = lookupVSeq(canSym);
        cs = prev.cseq;
        if (!isValidCV(cs, newVs)) {
            entry.form = vnw_nonVn;
            entry.c1Offset = entry.c2Offset = entry.vOffset = -1;
            break;
        }

        entry.form = vnw_cv;
        entry.c1Offset = 1;
        entry.c2Offset = -1;
        entry.vOffset = 0;
        entry.vseq = newVs;

        if (cs == cs_gi && prev.tone != 0) {
            if (entry.tone == 0)
                entry.tone = prev.tone;
            markChange(m_current - 1);
            prev.tone = 0;
            return 1;
        }
    
        break;
  }

    if (!autoCompleted &&
        (m_pCtrl->charsetId != CONV_CHARSET_UNI_CSTRING) && 
        isalpha(entry.keyCode)) {
        return 0;
    }

    markChange(m_current);
    return 1;
}

//----------------------------------------------------------
int UkEngine::appendConsonnant(UkKeyEvent & ev)
{
    bool complexEvent = false;
    m_current++;
    WordInfo & entry = m_buffer[m_current];

    VnLexiName lowerSym = vnToLower(ev.vnSym);

    entry.vnSym = lowerSym;
    entry.caps = (lowerSym != ev.vnSym);
    entry.keyCode = ev.keyCode;
    entry.tone = 0;

    if (m_current == 0 || !m_pCtrl->vietKey) {
        entry.form = vnw_c;
        entry.c1Offset = 0;
        entry.c2Offset = -1;
        entry.vOffset = -1;
        entry.cseq = lookupCSeq(lowerSym);
        if (!m_pCtrl->vietKey || m_pCtrl->charsetId != CONV_CHARSET_UNI_CSTRING)
            return 0;
        markChange(m_current);
        return 1;
    }

    ConSeq cs, newCs, c1;
    VowelSeq vs, newVs;
    bool isValid;

    WordInfo & prev = m_buffer[m_current-1];

    switch (prev.form) {
    case vnw_nonVn:
        entry.form = vnw_nonVn;
        entry.c1Offset = entry.c2Offset = entry.vOffset = -1;
        if (m_pCtrl->charsetId != CONV_CHARSET_UNI_CSTRING)
            return 0;
        markChange(m_current);
        return 1;
    case vnw_empty:
        entry.form = vnw_c;
        entry.c1Offset = 0;
        entry.c2Offset = -1;
        entry.vOffset = -1;
        entry.cseq = lookupCSeq(lowerSym);
        if (m_pCtrl->charsetId != CONV_CHARSET_UNI_CSTRING)
            return 0;
        markChange(m_current);
        return 1;
    case vnw_v:
    case vnw_cv:
        vs = prev.vseq;
        newVs = vs;
        if (vs == vs_uoh || vs == vs_uho) {
            newVs = vs_uhoh;
        }

        c1 = cs_nil;
        if (prev.c1Offset != -1)
            c1 = m_buffer[m_current-1-prev.c1Offset].cseq;

        newCs = lookupCSeq(lowerSym);
        isValid = isValidCVC(c1, newVs, newCs);

        if (isValid) {
            //check u+o -> u+o+
            if (vs == vs_uho) {
                markChange(m_current-1);
                prev.vnSym = vnl_oh;
                prev.vseq = vs_uhoh;
                complexEvent = true;
            }
            else if (vs == vs_uoh) {
                markChange(m_current-2);
                m_buffer[m_current-2].vnSym = vnl_uh;
                m_buffer[m_current-2].vseq = vs_uh;
                prev.vseq = vs_uhoh;
                complexEvent = true;
            }

            if (prev.form == vnw_v) {
                entry.form = vnw_vc;
                entry.c1Offset = -1;
                entry.c2Offset = 0;
                entry.vOffset = 1;
            }
            else { //prev == vnw_cv
                entry.form = vnw_cvc;
                entry.c1Offset = prev.c1Offset + 1;
                entry.c2Offset = 0;
                entry.vOffset = 1;
            }
            entry.cseq = newCs;

            //reposition tone if needed
            int oldIdx = (m_current-1) - (VSeqList[vs].len - 1) + getTonePosition(vs, true);
            if (m_buffer[oldIdx].tone != 0) {
                int newIdx = (m_current-1) - (VSeqList[newVs].len - 1) + getTonePosition(newVs, false);
                if (newIdx != oldIdx) {
                    markChange(newIdx);
                    m_buffer[newIdx].tone = m_buffer[oldIdx].tone;
                    markChange(oldIdx);
                    m_buffer[oldIdx].tone = 0;
                    return 1;
                }
            }
        }
        else {
            entry.form = vnw_nonVn;
            entry.c1Offset = entry.c2Offset = entry.vOffset = -1;
        }

        if (complexEvent) {
            return 1;
        }

        if (m_pCtrl->charsetId != CONV_CHARSET_UNI_CSTRING)
            return 0;
        markChange(m_current);
        return 1;
    case vnw_c:
    case vnw_vc:
    case vnw_cvc:
        cs = prev.cseq;
        if (CSeqList[cs].len == 3)
            newCs = cs_nil;
        else if (CSeqList[cs].len == 2)
            newCs = lookupCSeq(CSeqList[cs].c[0], CSeqList[cs].c[1], lowerSym);
        else
            newCs = lookupCSeq(CSeqList[cs].c[0], lowerSym);
        
        if (newCs != cs_nil && (prev.form == vnw_vc || prev.form == vnw_cvc)) {
            // Check CVC combination
            c1 = cs_nil;
            if (prev.c1Offset != -1)
                c1 = m_buffer[m_current-1-prev.c1Offset].cseq;

            int vIdx = (m_current - 1) - prev.vOffset;
            vs = m_buffer[vIdx].vseq;
            isValid = isValidCVC(c1, vs, newCs);

            if (!isValid)
                newCs = cs_nil;
        }

        if (newCs == cs_nil) {
            entry.form = vnw_nonVn;
            entry.c1Offset = entry.c2Offset = entry.vOffset = -1;
        }
        else {
            if (prev.form == vnw_c) {
                entry.form = vnw_c;
                entry.c1Offset = 0;
                entry.c2Offset = -1;
                entry.vOffset = -1;
            }
            else if (prev.form == vnw_vc) {
                entry.form = vnw_vc;
                entry.c1Offset = -1;
                entry.c2Offset = 0;
                entry.vOffset = prev.vOffset + 1;
            }
            else { //vnw_cvc
                entry.form = vnw_cvc;
                entry.c1Offset = prev.c1Offset + 1;
                entry.c2Offset = 0;
                entry.vOffset = prev.vOffset + 1;
            }
            entry.cseq = newCs;
        }
        if (m_pCtrl->charsetId != CONV_CHARSET_UNI_CSTRING)
            return 0;
        markChange(m_current);
        return 1;
    }

    if (m_pCtrl->charsetId != CONV_CHARSET_UNI_CSTRING)
        return 0;
    markChange(m_current);
    return 1;
}

//----------------------------------------------------------
int UkEngine::processEscChar(UkKeyEvent & ev)
{
    if (m_pCtrl->vietKey && 
        m_current >=0 && m_buffer[m_current].form != vnw_empty && m_buffer[m_current].form != vnw_nonVn) {
        m_toEscape = true;
    }
    return processAppend(ev);
}

//----------------------------------------------------------
void UkEngine::pass(int keyCode)
{
    UkKeyEvent ev;
    m_pCtrl->input.keyCodeToEvent(keyCode, ev);
    processAppend(ev);
}

//---------------------------------------------
// This can be called only after other processing have been done.
// The new event is supposed to be put into m_buffer already
//---------------------------------------------
int UkEngine::processNoSpellCheck(UkKeyEvent & ev)
{
    WordInfo & entry = m_buffer[m_current];
    if (IsVnVowel[entry.vnSym]) {
        entry.form = vnw_v;
        entry.vOffset = 0;
        entry.vseq = lookupVSeq(entry.vnSym);
        entry.c1Offset = entry.c2Offset = -1;
    }
    else {
        entry.form = vnw_c;
        entry.c1Offset = 0;
        entry.c2Offset = -1;
        entry.vOffset = -1;
        entry.cseq = lookupCSeq(entry.vnSym);
    }

    if (ev.evType == vneNormal &&
        ((entry.keyCode >= 'a' && entry.keyCode <= 'z') || 
         (entry.keyCode >= 'A' && entry.keyCode <= 'Z') ) )
        return 0;
    markChange(m_current);
    return 1;
}
//----------------------------------------------------------
int UkEngine::process(unsigned int keyCode, int & backs, unsigned char *outBuf, int & outSize, UkOutputType & outType)
{
    UkKeyEvent ev;
    prepareBuffer();
    m_backs = 0;
    m_changePos = m_current+1;
    m_pOutBuf = outBuf;
    m_pOutSize = &outSize;
    m_outputWritten = false;
    m_reverted = false;
    m_keyRestored = false;
    m_keyRestoring = false;
    m_outType = UkCharOutput;

    m_pCtrl->input.keyCodeToEvent(keyCode, ev);

    int ret;
    if (!m_toEscape) {
        ret = (this->*UkKeyProcList[ev.evType])(ev);
    }
    else {
        m_toEscape = false;
        if (m_current < 0 || ev.evType == vneNormal || ev.evType == vneEscChar) {
            ret = processAppend(ev);
        }
        else {
            m_current--;
            processAppend(ev);
            markChange(m_current); //this will assign m_backs to 1 and mark the character for output
            ret = 1;
        }
    }

    if ( m_pCtrl->vietKey &&
         m_current >= 0 && m_buffer[m_current].form == vnw_nonVn &&
         ev.chType == ukcVn &&
         (!m_pCtrl->options.spellCheckEnabled || m_singleMode) )
    {

        //The spell check has failed, but because we are in non-spellcheck mode,
        //we consider the new character as the beginning of a new word
        ret = processNoSpellCheck(ev);
        /*
        if ((!m_pCtrl->options.spellCheckEnabled || m_singleMode) || 
            ( !m_reverted && 
              (m_current < 1 || m_buffer[m_current-1].form != vnw_nonVn)) ) {

            ret = processNoSpellCheck(ev);
        }
        */
    }

    //we add key to key buffer only if that key has not caused a reset
    if (m_current >= 0) {
        ev.chType = m_pCtrl->input.getCharType(ev.keyCode);
        m_keyCurrent++;
        m_keyStrokes[m_keyCurrent].ev = ev;
        m_keyStrokes[m_keyCurrent].converted = (ret && !m_keyRestored);
    }

    if (ret == 0) {
        backs = 0;
        outSize = 0;
        outType = m_outType;
        return 0;
    }

    backs = m_backs;
    if (!m_outputWritten) {
        writeOutput(outBuf, outSize);
    }
    outType = m_outType;

    return ret;
}


//----------------------------------------------------------
// Returns 0 on success
//         error code otherwise
//  outBuf: buffer to write
//  outSize: [in] size of buffer in bytes
//           [out] bytes written to buffer
//----------------------------------------------------------
int UkEngine::writeOutput(unsigned char *outBuf, int & outSize)
{
    StdVnChar stdChar;
    int i, bytesWritten;
    int ret = 1;
    StringBOStream os(outBuf, outSize);
    VnCharset *pCharset = VnCharsetLibObj.getVnCharset(m_pCtrl->charsetId);
    pCharset->startOutput();

    for (i = m_changePos; i <= m_current; i++) {
        if (m_buffer[i].vnSym != vnl_nonVnChar) {
            //process vn symbol
            stdChar = m_buffer[i].vnSym + VnStdCharOffset;
            if (m_buffer[i].caps)
                stdChar--;
            if (m_buffer[i].tone != 0)
                stdChar += m_buffer[i].tone * 2;
        }
        else {
            stdChar = IsoToStdVnChar(m_buffer[i].keyCode);
        }
    
        if (stdChar != INVALID_STD_CHAR)
            ret = pCharset->putChar(os, stdChar, bytesWritten);
    }

    outSize = os.getOutBytes();
    return (ret? 0 : VNCONV_OUT_OF_MEMORY);
}

//---------------------------------------------
// Returns the number of backspaces needed to
// go back from last to first
//---------------------------------------------
int UkEngine::getSeqSteps(int first, int last)
{
    StdVnChar stdChar;

    if (last < first)
        return 0;

    if (m_pCtrl->charsetId == CONV_CHARSET_XUTF8 || 
        m_pCtrl->charsetId == CONV_CHARSET_UNICODE)
        return (last - first +  1);

    StringBOStream os(0, 0);
    int i, bytesWritten;

    VnCharset *pCharset = VnCharsetLibObj.getVnCharset(m_pCtrl->charsetId);
    pCharset->startOutput();

    for (i = first; i <= last; i++) {
        if (m_buffer[i].vnSym != vnl_nonVnChar) {
            //process vn symbol
            stdChar = m_buffer[i].vnSym + VnStdCharOffset;
            if (m_buffer[i].caps)
                stdChar--;
            if (m_buffer[i].tone != 0)
                stdChar += m_buffer[i].tone*2;
        }
        else {
            stdChar = m_buffer[i].keyCode;
        }
    
        if (stdChar != INVALID_STD_CHAR)
            pCharset->putChar(os, stdChar, bytesWritten);
    }
  
    int len = os.getOutBytes();
    if (m_pCtrl->charsetId == CONV_CHARSET_UNIDECOMPOSED)
        len = len / 2;
    return len;
}

//---------------------------------------------
void UkEngine::markChange(int pos)
{
    if (pos < m_changePos) {
        m_backs += getSeqSteps(pos, m_changePos-1);
        m_changePos = pos;
    }
}

//----------------------------------------------------------------
// Called from processBackspace to keep
// character buffer (m_buffer) and key stroke buffer in synch
//----------------------------------------------------------------
void UkEngine::synchKeyStrokeBuffer()
{
    //synchronize with key-stroke buffer
    if (m_keyCurrent >= 0)
        m_keyCurrent--;
    if (m_current >= 0 && m_buffer[m_current].form == vnw_empty) {
        //in character buffer, we have reached a word break,
        //so we also need to move key stroke pointer backward to corresponding word break
        while (m_keyCurrent >= 0 && m_keyStrokes[m_keyCurrent].ev.chType != ukcWordBreak)
        {
            m_keyCurrent--;
        }
    }
}

//---------------------------------------------
int UkEngine::processBackspace(int & backs, unsigned char *outBuf, int & outSize, UkOutputType & outType)
{
    outType = UkCharOutput;
    if (!m_pCtrl->vietKey || m_current < 0) {
        backs = 0;
        outSize = 0;
        return 0;
    }

    m_backs = 0;
    m_changePos = m_current + 1;
    markChange(m_current);

    if (m_current == 0 || 
        m_buffer[m_current].form == vnw_empty ||
        m_buffer[m_current].form == vnw_nonVn ||
        m_buffer[m_current].form == vnw_c ||
        m_buffer[m_current-1].form == vnw_c ||
        m_buffer[m_current-1].form == vnw_cvc ||
        m_buffer[m_current-1].form == vnw_vc) {

        m_current--;
        backs = m_backs;
        outSize = 0;
        synchKeyStrokeBuffer();
        return (backs > 1);
    }
  
    VowelSeq vs, newVs;
    int curTonePos, newTonePos, tone, vStart, vEnd;

    vEnd = m_current - m_buffer[m_current].vOffset;
    vs = m_buffer[vEnd].vseq;
    vStart = vEnd - VSeqList[vs].len + 1;
    newVs = m_buffer[m_current-1].vseq;
    curTonePos = vStart +  getTonePosition(vs, vEnd == m_current);
    newTonePos = vStart + getTonePosition(newVs, true);
    tone = m_buffer[curTonePos].tone;

    if (tone == 0 || curTonePos == newTonePos || 
        (curTonePos == m_current && m_buffer[m_current].tone != 0)) {
        m_current--;
        backs = m_backs;
        outSize = 0;
        synchKeyStrokeBuffer();
        return (backs > 1);
    }

    markChange(newTonePos);
    m_buffer[newTonePos].tone = tone;
    markChange(curTonePos);
    m_buffer[curTonePos].tone = 0;
    m_current--;
    synchKeyStrokeBuffer();
    backs = m_backs;
    writeOutput(outBuf, outSize);
    return 1;
}

//------------------------------------------------
void UkEngine::reset()
{
    m_current = -1;
    m_keyCurrent = -1;
    m_singleMode = false;
    m_toEscape = false;
}

//------------------------------------------------
void UkEngine::resetKeyBuf()
{
    m_keyCurrent = -1;
}

//------------------------------------------------
UkEngine::UkEngine()
{
    if (!m_classInit) {
        engineClassInit();
        m_classInit = true;
    }
    m_pCtrl = 0;
    m_bufSize = MAX_UK_ENGINE;
    m_keyBufSize = MAX_UK_ENGINE;
    m_current = -1;
    m_keyCurrent = -1;
    m_singleMode = false;
    m_keyCheckFunc = 0;
    m_reverted = false;
    m_toEscape = false;
    m_keyRestored = false;
}

//----------------------------------------------------
// make sure there are at least 10 entries available
//----------------------------------------------------
void UkEngine::prepareBuffer()
{
    int rid;
    //prepare symbol buffer
    if (m_current >= 0 && m_current + 10 >= m_bufSize) {
        // Get rid of at least half of the current entries
        // don't get rid from the middle of a word.
        for (rid = m_current/2; m_buffer[rid].form != vnw_empty && rid < m_current; rid++);
        if (rid == m_current) {
            m_current = -1;
        }
        else {
            rid++;
            memmove(m_buffer, m_buffer+rid, (m_current-rid+1)*sizeof(WordInfo));
            m_current -= rid;
        }
    }

    //prepare key stroke buffer
    if (m_keyCurrent > 0 && m_keyCurrent + 1 >= m_keyBufSize) {
        // Get rid of at least half of the current entries
        rid = m_keyCurrent/2;
        memmove(m_keyStrokes, m_keyStrokes + rid, (m_keyCurrent-rid+1)*sizeof(m_keyStrokes[0]));
        m_keyCurrent -= rid;
    }

}

#define ENTER_CHAR 13
enum VnCaseType {VnCaseNoChange, VnCaseAllCapital, VnCaseAllSmall};

//----------------------------------------------------
int UkEngine::macroMatch(UkKeyEvent & ev)
{
    int capsLockOn = 0;
    int shiftPressed = 0;
    if (m_keyCheckFunc)
        m_keyCheckFunc(&shiftPressed, &capsLockOn);

    if (shiftPressed && (ev.keyCode ==' ' || ev.keyCode == ENTER_CHAR))
        return 0;

    const StdVnChar *pMacText = NULL;
    StdVnChar key[MAX_MACRO_KEY_LEN+1];
    StdVnChar *pKeyStart;

    // Use static macro text so we can gain a bit of performance
    // by avoiding memory allocation each time this function is called
    static StdVnChar macroText[MAX_MACRO_TEXT_LEN+1];

    int i, j;

    i = m_current;
    while (i >= 0 && (m_current-i + 1) < MAX_MACRO_KEY_LEN) {
        while (i>=0 && m_buffer[i].form != vnw_empty && (m_current-i + 1) < MAX_MACRO_KEY_LEN)
            i--;
        if (i>=0 && m_buffer[i].form != vnw_empty)
            return 0;

        if (i>=0) {
            if (m_buffer[i].vnSym != vnl_nonVnChar) {
                key[0] = m_buffer[i].vnSym + VnStdCharOffset;
                if (m_buffer[i].caps)
                    key[0]--;
                key[0] += m_buffer[i].tone*2;
            }
            else
                key[0] = m_buffer[i].keyCode;
        }

        for (j=i+1; j<=m_current; j++) {
            if (m_buffer[j].vnSym != vnl_nonVnChar) {
                key[j-i] = m_buffer[j].vnSym + VnStdCharOffset;
                if (m_buffer[j].caps)
                    key[j-i]--;
                key[j-i] += m_buffer[j].tone*2;
            }
            else
                key[j-i] = m_buffer[j].keyCode;
        }
        key[m_current-i+1] = 0;
        //search macro table
        pMacText = m_pCtrl->macStore.lookup(key+1);
        if (pMacText) {
            i++; //mark the position where change is needed
            pKeyStart = key + 1;
            break;
        }
        if (i>=0) {
            pMacText = m_pCtrl->macStore.lookup(key);
            if (pMacText) {
                pKeyStart = key;
                break;
            }
        }
        i--;
    }

    if (!pMacText) {
        return 0;
    }

    markChange(i);

    // determine the form of macro replacements: ALL CAPITALS, First Character Capital, or no change
    VnCaseType macroCase;
    if (IS_STD_VN_LOWER(*pKeyStart)) {
        macroCase = VnCaseAllSmall;
    }
    else if (IS_STD_VN_UPPER(*pKeyStart)) {
        macroCase = VnCaseAllCapital;
        for (i=1; pKeyStart[i]; i++) {
            if (IS_STD_VN_LOWER(pKeyStart[i])) {
                macroCase = VnCaseNoChange;
            }
        }
    }
    else macroCase = VnCaseNoChange;

    // Convert case of macro text according to macroCase
    int charCount = 0;
    while (pMacText[charCount] != 0)
        charCount++;

    for (i = 0; i < charCount; i++)
    {
        if (macroCase == VnCaseAllCapital)
            macroText[i] = StdVnToUpper(pMacText[i]);
        else if (macroCase == VnCaseAllSmall)
            macroText[i] = StdVnToLower(pMacText[i]);
        else
            macroText[i] = pMacText[i];
    }

    // Convert to target output charset
    int outSize;
    int maxOutSize = *m_pOutSize;
    int inLen = charCount * sizeof(StdVnChar);
    VnConvert(CONV_CHARSET_VNSTANDARD, m_pCtrl->charsetId,
              (UKBYTE *) macroText, (UKBYTE *)m_pOutBuf,
              &inLen, &maxOutSize);
    outSize = maxOutSize;

    //write the last input character
    StdVnChar vnChar;
    if (outSize < *m_pOutSize) {
        maxOutSize = *m_pOutSize - outSize;
        if (ev.vnSym != vnl_nonVnChar)
            vnChar = ev.vnSym + VnStdCharOffset;
        else
            vnChar = ev.keyCode;
        inLen = sizeof(StdVnChar);
        VnConvert(CONV_CHARSET_VNSTANDARD, m_pCtrl->charsetId,
                  (UKBYTE *) &vnChar, ((UKBYTE *)m_pOutBuf) + outSize,
                  &inLen, &maxOutSize);
        outSize += maxOutSize;
    }
    int backs = m_backs; //store m_backs before calling reset
    reset();
    m_outputWritten = true;
    m_backs = backs;
    *m_pOutSize = outSize;
    return 1;
}

//----------------------------------------------------
int UkEngine::restoreKeyStrokes(int & backs, unsigned char *outBuf, int & outSize, UkOutputType & outType)
{
    outType = UkKeyOutput;
    if (!lastWordHasVnMark()) {
        backs = 0;
        outSize = 0;
        return 0;
    }

    m_backs = 0;
    m_changePos = m_current+1;

    int keyStart;
    bool converted = false;
    for (keyStart = m_keyCurrent; keyStart >= 0 && m_keyStrokes[keyStart].ev.chType != ukcWordBreak; keyStart--) {
        if (m_keyStrokes[keyStart].converted) {
            converted = true;
        }
    }
    keyStart++;

    if (!converted) {
        //no key stroke has been converted, so it doesn't make sense to restore key strokes
        backs = 0;
        outSize = 0;
        return 0;
    }

    //int i = m_current;
    while (m_current >=0 && m_buffer[m_current].form != vnw_empty)
        m_current--;
    markChange(m_current+1);
    backs = m_backs;

    int count;
    int i;
    UkKeyEvent ev;
    m_keyRestoring = true;
    for (i=keyStart, count = 0; i <= m_keyCurrent; i++) {
        if (count<outSize) {
            outBuf[count++] = (unsigned char)m_keyStrokes[i].ev.keyCode;
        }
        m_pCtrl->input.keyCodeToSymbol(m_keyStrokes[i].ev.keyCode, ev);
        m_keyStrokes[i].converted = false;
        processAppend(ev);
    }
    outSize = count;
    m_keyRestoring = false;

    return 1;
}

//--------------------------------------------------
void UkEngine::setSingleMode()
{
    m_singleMode = true;
}

//--------------------------------------------------
void SetupUnikeyEngine()
{
    SetupInputClassifierTable();
    int i;
    VnLexiName lexi;

    //Calculate IsoStdVnCharMap
    for (i=0; i < 256; i++) {
        IsoStdVnCharMap[i] = i;
    }

    for (i=0; SpecialWesternChars[i]; i++) {
        IsoStdVnCharMap[SpecialWesternChars[i]] = (vnl_lastChar + i) + VnStdCharOffset;
    }

    for (i=0; i < 256; i++) {
        if ((lexi = IsoToVnLexi(i)) != vnl_nonVnChar) {
            IsoStdVnCharMap[i] = lexi + VnStdCharOffset;
        }
    }
}

//--------------------------------------------------
bool UkEngine::atWordBeginning()
{
    return (m_current < 0 || m_buffer[m_current].form == vnw_empty);
}

//--------------------------------------------------
// Check for macro first, if there's a match, expand macro. If not:
// Spell-check, if is valid Vietnamese, return normally, if not:
// restore key strokes if auto-restore is enabled
//--------------------------------------------------
int UkEngine::processWordEnd(UkKeyEvent & ev)
{
    if (m_pCtrl->options.macroEnabled && macroMatch(ev))
        return 1;

    if (!m_pCtrl->options.spellCheckEnabled || m_singleMode || m_current < 0 || m_keyRestoring) {
        m_current++;
        WordInfo & entry = m_buffer[m_current];
        entry.form = vnw_empty;
        entry.c1Offset = entry.c2Offset = entry.vOffset = -1;
        entry.keyCode = ev.keyCode;
        entry.vnSym = vnToLower(ev.vnSym);
        entry.caps = (entry.vnSym != ev.vnSym);
        return 0;
    }

    int outSize = 0;
    if (m_pCtrl->options.autoNonVnRestore && lastWordIsNonVn()) {
        outSize = *m_pOutSize;
        if (restoreKeyStrokes(m_backs, m_pOutBuf, outSize, m_outType)) {
            m_keyRestored = true;
            m_outputWritten = true;
        }
    }

    m_current++;
    WordInfo & entry = m_buffer[m_current];
    entry.form = vnw_empty;
    entry.c1Offset = entry.c2Offset = entry.vOffset = -1;
    entry.keyCode = ev.keyCode;
    entry.vnSym = vnToLower(ev.vnSym);
    entry.caps = (entry.vnSym != ev.vnSym);
    
    if (m_keyRestored && outSize < *m_pOutSize) {
        m_pOutBuf[outSize] = ev.keyCode;
        outSize++;
        *m_pOutSize = outSize;
        return 1;
    }
 
    return 0;
}

//---------------------------------------------------------------------------
// Test if last word is a non-Vietnamese word, so that
// the engine can restore key strokes if it is indeed not a Vietnamese word
//---------------------------------------------------------------------------
bool UkEngine::lastWordIsNonVn()
{
    if (m_current < 0)
        return false;

    switch (m_buffer[m_current].form) {
        case vnw_nonVn:
            return true;
        case vnw_empty:
        case vnw_c:
            return false;
        case vnw_v:
        case vnw_cv:
            return !VSeqList[m_buffer[m_current].vseq].complete;
        case vnw_vc:
        case vnw_cvc: {
            int vIndex = m_current - m_buffer[m_current].vOffset;
            VowelSeq vs = m_buffer[vIndex].vseq;
            if (!VSeqList[vs].complete)
                return true;
            ConSeq cs = m_buffer[m_current].cseq;
            ConSeq c1 = cs_nil;
            if (m_buffer[m_current].c1Offset != -1)
                c1 = m_buffer[m_current-m_buffer[m_current].c1Offset].cseq;

            if (!isValidCVC(c1, vs, cs)) {
                return true;
            }

            int tonePos = (vIndex - VSeqList[vs].len + 1) + getTonePosition(vs, false);
            int tone = m_buffer[tonePos].tone;
            if ((cs == cs_c || cs == cs_ch || cs == cs_p || cs == cs_t) &&
                (tone == 2 || tone == 3 || tone == 4))
            {
                return true;
            }
        }
    }
    return false;
}

//---------------------------------------------------------------------------
// Test if last word has a Vietnamese mark, that is tones, decorators
//---------------------------------------------------------------------------
bool UkEngine::lastWordHasVnMark()
{
    for (int i=m_current; i>=0 && m_buffer[i].form != vnw_empty; i--) {
        VnLexiName sym = m_buffer[i].vnSym;
        if (sym != vnl_nonVnChar) {
            if (IsVnVowel[sym]) {
                if (m_buffer[i].tone)
                    return true;
            }
            if (sym != StdVnRootChar[sym] )
                return true;
        }
    }
    return false;
}

Generated by  Doxygen 1.6.0   Back to index