// OVIMArray.h: The Array Input Method // // Copyright (c) 2004-2006 The OpenVanilla Project (http://openvanilla.org) // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions // are met: // // 1. Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // 2. Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // 3. Neither the name of OpenVanilla nor the names of its contributors // may be used to endorse or promote products derived from this software // without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. // #define OV_DEBUG #include "ctype.h" #include "OVIMArray.h" #include #include #include using namespace std; using namespace OV_Array; void OVIMArrayContext::clear(){ keyseq.clear(); changeState(STATE_WAIT_KEY1); } void OVIMArrayContext::updateDisplay(OVBuffer* buf){ buf->clear(); if (keyseq.length()){ string str; keyseq.compose(str); buf->append(str.c_str()); } buf->update(); } int OVIMArrayContext::updateCandidate(OVCIN *tab,OVBuffer *buf, OVCandidate *candibar){ tab->getWordVectorByChar(keyseq.getSeq(), candidateStringVector); string currentSelKey = tab->getSelKey(); if( candidateStringVector.size() == 0 ) clearCandidate(candibar); else candi.prepare(&candidateStringVector, const_cast(currentSelKey.c_str()), candibar); return 1; } int OVIMArrayContext::WaitKey1(OVKeyCode* key, OVBuffer* buf, OVCandidate* candibar, OVService* srv){ if(keyseq.length() != 1) return 0; updateCandidate(tabs[SHORT_TAB], buf, candibar); char keycode = keyseq.getSeq()[0]; if( keycode == 't' ) buf->clear()->append((char*)"的")->update(); if( isprint(keycode) && keyseq.valid(keycode) ) changeState(STATE_WAIT_KEY2); return 1; } int OVIMArrayContext::WaitKey2(OVKeyCode* key, OVBuffer* buf, OVCandidate* candibar, OVService* srv){ if( keyseq.length()!=2 ) return 0; char keycode = keyseq.getSeq()[1]; if( isWSeq(keyseq.getSeq()[0], keyseq.getSeq()[1]) ){ updateCandidate(tabs[MAIN_TAB], buf, candibar); buf->clear()->append(candidateStringVector[0].c_str())->update(); changeState(STATE_WAIT_CANDIDATE); } else{ updateCandidate(tabs[SHORT_TAB], buf, candibar); if( isprint(keycode) && keyseq.valid(keycode) ) changeState(STATE_WAIT_KEY3); } return 1; } int OVIMArrayContext::WaitKey3(OVKeyCode* key, OVBuffer* buf, OVCandidate* candibar, OVService* srv){ if( keyseq.length() >= 3){ updateCandidate(tabs[MAIN_TAB], buf, candibar); } return 1; } int OVIMArrayContext::WaitCandidate(OVKeyCode* key, OVBuffer* buf, OVCandidate* candibar, OVService* srv){ const char keycode = key->code(); if (keycode == ovkEsc || keycode == ovkBackspace){ clearAll(buf, candibar); changeState(STATE_WAIT_KEY1); return RET_DONE; } if (keycode == ovkDown || keycode == ovkRight || (!candi.onePage() && keycode==ovkSpace) ){ candi.pageDown()->update(candibar); return RET_DONE; } if (keycode == ovkUp || keycode == ovkLeft){ candi.pageUp()->update(candibar); return RET_DONE; } // enter == first candidate // space (when candidate list has only one page) == first candidate char c=key->code(); bool notSelkey = tabs[MAIN_TAB]->getSelKey().find(key->code()) == string::npos; bool defaultSelKey = c == ovkReturn || (candi.onePage() && c==ovkSpace); murmur("notSelkey: %d", notSelkey); if ( defaultSelKey || notSelkey) c=candi.getSelKey()[0]; string output; if (candi.select(c, output)) { sendAndReset(output.c_str(), buf, candibar, srv); if( notSelkey && !defaultSelKey ) return RET_CONTINUE; return RET_DONE; } return RET_PASS; } void OVIMArrayContext::queryKeyName(const char *keys, std::string& outKeyNames){ int len = strlen(keys); for(int i=0;i keyName; if( tabs[MAIN_TAB]->getCharVectorByKey(inKey, keyName) > 0) outKeyNames.append(keyName[0]); } } void OVIMArrayContext::sendAndReset(const char *ch, OVBuffer* buf, OVCandidate* candibar, OVService* srv){ bool notifySP = false; // lookup special code if((parent->isAutoSP() || parent->isForceSP()) && tabs[SPECIAL_TAB]->getWordVectorByChar(ch, specialCodeVector)>0) { int splen = specialCodeVector[0].length(); const char *spcode = specialCodeVector[0].c_str(); if( !(splen == keyseq.length() && equal(spcode, spcode+splen, keyseq.getSeq())) ){ char buf[16]; string keynames; queryKeyName(specialCodeVector[0].c_str(), keynames); sprintf(buf, "%s: %s", ch, keynames.c_str()); srv->notify(buf); notifySP = true; } } if( isForceSPSeq() ) parent->setForceSP( !parent->isForceSP() ); else{ if( !(parent->isForceSP() && notifySP) ) buf->clear()->append(ch)->send(); else srv->beep(); } clearAll(buf, candibar); changeState(STATE_WAIT_KEY1); } void OVIMArrayContext::clearAll(OVBuffer* buf, OVCandidate* candi_bar){ clearCandidate(candi_bar); buf->clear()->update(); keyseq.clear(); } void OVIMArrayContext::clearCandidate(OVCandidate *candi_bar){ candi.cancel(); candi_bar->hide()->clear(); } int OVIMArrayContext::selectCandidate(int num, string& out){ char c=candi.getSelKey()[num]; return candi.select(c, out); } int OVIMArrayContext::keyEvent(OVKeyCode* key, OVBuffer* buf, OVCandidate* candi_bar, OVService* srv) { int ret = 0; const char keycode = key->code(); const bool validkey = keyseq.valid(keycode) || ( keyseq.getSeq()[0]=='w' && isdigit(keycode) ); murmur("OVIMArray state: %d", state); if (!keyseq.length() && !isprint(keycode)) return 0; if (!keyseq.length() && key->isFunctionKey()) return 0; if (isprint(key->code()) && key->isCapslock() && keyseq.length()==0) { char cbuf[2]; if (key->isShift()) sprintf(cbuf, "%c", toupper(key->code())); else sprintf(cbuf, "%c", tolower(key->code())); buf->append(cbuf)->send(); return 1; } if( key->isShift() && isprint(keycode) && keyseq.length()==0 ){ char k[2] = { tolower(keycode), 0 }; buf->append(k)->send(); return 1; } if (!keyseq.length() && !validkey) { char k[2] = { tolower(keycode), 0 }; buf->append(k)->send(); return 1; } if(keycode==ovkEsc){ clearAll(buf, candi_bar); changeState(STATE_WAIT_KEY1); return 1; } if( state == STATE_WAIT_CANDIDATE ){ int r = WaitCandidate(key, buf, candi_bar, srv); if( r != RET_CONTINUE ) return r; } if( candi.onDuty() && isdigit(keycode) && !(keyseq.length() == 1 && isWSeq(keyseq.getSeq()[0],keycode)) ){ string c; if( candi.select(keycode, c) ){ if( c != "⎔" ){ sendAndReset(c.c_str(), buf, candi_bar, srv); } else{ srv->beep(); if( state <= STATE_WAIT_KEY3 ) //dirty hack to set duty=1 updateCandidate(tabs[SHORT_TAB], buf, candi_bar); } } return 1; } if (keyseq.length() && keycode == ovkSpace){ tabs[MAIN_TAB]->getWordVectorByChar(keyseq.getSeq(), candidateStringVector); string c; if(candidateStringVector.size() == 1){ if(selectCandidate(0, c)) sendAndReset(c.c_str(), buf, candi_bar, srv); } else if(candidateStringVector.size() > 1){ updateCandidate(tabs[MAIN_TAB], buf, candi_bar); if(selectCandidate(0, c)) buf->clear()->append(c.c_str())->update(); changeState(STATE_WAIT_CANDIDATE); } return 1; } if( isprint(keycode) && validkey ){ if( keyseq.length() >= 5 || (keyseq.length() == 4 && keycode != 'i') ) return 1; murmur("keyseq add [%c]", keycode); keyseq.add(keycode); updateDisplay(buf); ret = 1; } else if (key->code() == ovkDelete || key->code() == ovkBackspace){ keyseq.remove(); updateDisplay(buf); if(keyseq.length() == 0 && candi.onDuty()) clearCandidate(candi_bar); changeBackState(state); ret = 1; } dispatchStateHandler(key, buf, candi_bar, srv); return ret; } void OVIMArrayContext::dispatchStateHandler(OVKeyCode* key, OVBuffer* buf, OVCandidate* candi_bar, OVService* srv) { switch(state){ case STATE_WAIT_KEY1: WaitKey1(key, buf, candi_bar, srv); break; case STATE_WAIT_KEY2: WaitKey2(key, buf, candi_bar, srv); break; case STATE_WAIT_KEY3: WaitKey3(key, buf, candi_bar, srv); break; default: break; } } void OVIMArrayContext::changeBackState(STATE s){ switch(s){ case STATE_WAIT_CANDIDATE: case STATE_WAIT_KEY2: changeState(STATE_WAIT_KEY1); break; case STATE_WAIT_KEY3: if( keyseq.length() == 2) changeState(STATE_WAIT_KEY2); else if( keyseq.length() == 1) changeState(STATE_WAIT_KEY1); break; default: break; } } void OVIMArrayContext::changeState(STATE s){ murmur("OVIMArray change state: %d -> %d", state, s); state = s; } int OVIMArray::initialize(OVDictionary *conf, OVService* s, const char *path) { char buf[PATH_MAX]; char *cinfiles[] = { "%sOVIMArray%sarray30.cin", "%sOVIMArray%sarray-shortcode.cin", "%sOVIMArray%sarray-special.cin" }; for(int i=0;i<3;i++){ sprintf(buf, cinfiles[i], path, s->pathSeparator()); murmur("OVIMArray: open cin %s", buf); tabs[i] = new OVCIN(buf); } updateConfig(conf); return 1; } int OVIMArray::updateConfig(OVDictionary *conf){ const char *AutoSP = "特別碼提示"; const char *ForceSP = "快打模式"; if( !conf->keyExist(AutoSP) ) conf->setInteger(AutoSP, 1); if( !conf->keyExist(ForceSP) ) conf->setInteger(ForceSP, 0); cfgAutoSP = conf->getInteger(AutoSP); cfgForceSP = conf->getInteger(ForceSP); return 1; } OV_SINGLE_MODULE_WRAPPER(OVIMArray);