// OVKPPhraseTools.cpp: Phrase management tool // // 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. #include "OVKPPhraseTools.h" #include const char *PTMsg(OVService *s, const char *en, const char *tw=NULL, const char *cn=NULL); const char *PTMsg(const char *lc, const char *en, const char *tw=NULL, const char *cn=NULL); bool PTDBErrMsg(const char *m, SQLite3 *db, bool returnValue=false); bool PTDBErrMsg(OVService *s, const char *m, SQLite3 *db, bool returnValue=false); void PTRecordingMessage(const string& rstr, OVService *s); void PTModeOnMsg(OVService *s); string PTTime(); OVKPPhraseToolsContext::OVKPPhraseToolsContext(OVKPPhraseTools *p) { parent=p; clear(); } void OVKPPhraseToolsContext::start(OVBuffer*, OVCandidate*, OVService*) { } void OVKPPhraseToolsContext::clear() { state=PTS_WAIT; parent->recordingMode=false; parent->recSeq.clear(); wildcard=false; valuelist.clear(); candilist.clear(); keyseq.clear(); } void OVKPPhraseToolsContext::end() { clear(); } int OVKPPhraseToolsContext::keyEvent(OVKeyCode *pk, OVBuffer *pb, OVCandidate *pc, OVService *ps) { k=pk; b=pb; c=pc; s=ps; switch(state) { case PTS_MODE_ACTIVATED: return state_activated(); case PTS_MODE_COMMAND: return state_command(); case PTS_MODE_CANDIDATE: return state_candidate(); } if (b->isEmpty() && (k->code()==ovkBackspace || k->code()==ovkDelete) && !k->isFunctionKey()) { if (parent->recordingMode) { parent->recSeq.pop(); if (!parent->recSeq.count()) { parent->recordingMode=false; return(cancelmsg(PTS_WAIT, false)); } else { PTRecordingMessage(parent->recSeq.compose(), s); } } return false; } if (b->isEmpty() && k->code()==parent->actkey && !k->isFunctionKey()) { state=PTS_MODE_ACTIVATED; PTModeOnMsg(s); return true; } return false; } // bool OVKPPhraseToolsContext::state_ bool OVKPPhraseToolsContext::state_activated() { char foo[8]; // buffer is still empty at this stage, // all function key combinations will end this state if (k->isFunctionKey()) { return cancelmsg(PTS_WAIT, false); } // if it's once again the activation key, send the same key code back if (k->code()==parent->actkey) { sprintf(foo, "%c", parent->actkey); b->clear()->append(foo)->send(); return cancelmsg(PTS_WAIT, true); } if (k->code()==parent->catchkey) { s->notify(PTMsg(s, "Start text recording", "進入記錄模式", "进入记录模式")); parent->recordingMode=true; state=PTS_WAIT; return true; } if (isprint(k->code())) { state=PTS_MODE_COMMAND; return state_command(); } if (k->code()==ovkEsc) { clear(); return cancelmsg(PTS_WAIT, true); } return cancelmsg(PTS_WAIT, false); } bool OVKPPhraseToolsContext::state_command() { // now we have something in the buffer, ban all function keys if (k->isFunctionKey()) { b->update(); s->beep(); PTModeOnMsg(s); return true; } if (k->code()==ovkDelete || k->code()==ovkBackspace) { keyseq.pop(); if (!keyseq.count()) { // keyseq now empty clearBufCandi(); return cancelmsg(PTS_WAIT, true); } PTModeOnMsg(s); return composeAndUpdateBuffer(); } if (k->code()==ovkReturn) { // process the command! return parse_command(); } if (k->code()==ovkEsc) { // escape cancels altogether clearBufCandi(); return cancelmsg(PTS_WAIT, true); } if (k->code()==ovkTab) { // wildcard search return command_search(keyseq.compose(), true); } if (isprint(k->code())) { keyseq.add(string(1, k->code())); PTModeOnMsg(s); return composeAndUpdateBuffer(); } s->beep(); // non-print char return true; } bool OVKPPhraseToolsContext::state_candidate() { if (k->code()==ovkEsc || k->isFunctionKey()) { s->beep(); clearBufCandi(); return cancelmsg(PTS_WAIT, true); } switch(k->code()) { case ovkSpace: case ovkDown: case ovkPageDown: case ovkLeft: candictrl.pageDown(); candictrl.update(c); return true; case ovkUp: case ovkPageUp: case ovkRight: candictrl.pageUp(); candictrl.update(c); return true; } int selected; selected=(k->code()==ovkReturn) ? candictrl.select(parent->selkey[0]) : candictrl.select(k->code()); if (selected == -1) { s->beep(); return true; } string sel=valuelist[selected]; b->clear()->append(sel.c_str())->send(); clearBufCandi(); string msg(PTMsg(s, "Sent phrase: ", "送出詞彙:", "送出词汇:")); msg += sel; s->notify(msg.c_str()); return true; } bool OVKPPhraseToolsContext::parse_command() { string cps=keyseq.compose(); // if it's help if (cps=="h" || cps=="H") { s->notify(PTMsg(s, "commands:\na key -- associate the recorded phrase with key", "指令:\na key ─ 將記錄下的詞彙以key值存入資料庫", "指令:\na key ─ 将记录下的词汇以key值存入数据库")); return clearBufCandi(); } // if it's a command if (cps.length() > 2 && cps[1]==' ') { string param=cps.substr(2, cps.length()-2); switch(tolower(cps[0])) { case 'a': return command_add(param); } s->notify(PTMsg( "command not recognied", "錯誤的指令", "错误的指令")); return clearBufCandi(); } // search and candidate (if any) or commit return command_search(cps, false); } bool OVKPPhraseToolsContext::command_add(const string& k) { string v=parent->recSeq.compose(); if (!v.length()) { s->notify(PTMsg(s, "No recored phrase to add", "沒有任何記錄到的詞彙可存入數據數", "没有任何记录到的词汇可存入数据库")); return clearBufCandi(); } if (SQLITE_OK==parent->db->execute( "insert into phrase values('%q', '%q', '', '%q', '0');", k.c_str(), v.c_str(), PTTime().c_str())) { string m = k + " => " + v; s->notify(m.c_str()); } else { PTDBErrMsg(s, PTMsg(s, "Database error", "資料庫錯誤", "数据库错误"), parent->db); } return clearBufCandi(); } bool OVKPPhraseToolsContext::command_search(const string& k, bool wcard) { try { SQLite3Statement* st; wildcard=wcard; st=parent->db->prepare(wildcard ? "select key, value, context from phrase where key like '%q%%' order by freq desc, time desc;" : "select key, value, context from phrase where lower(key)=lower('%q') order by freq desc, time desc;", k.c_str()); if (!st) throw "database error"; candilist.clear(); valuelist.clear(); while (st->step()==SQLITE_ROW) { string k(st->column_text(0)); string v(st->column_text(1)); string x(st->column_text(2)); // context information valuelist.push_back(v); string d; if (wildcard) d=k + string(" => "); d+=v; if (x.length()) { d+=string(" ("); d+=x; d+=string(")"); } candilist.push_back(d); } delete st; // if there is nothing if (!valuelist.size()) { s->beep(); s->notify(PTMsg(s, "Not found", "找不到具有這個鍵碼的詞", "找不到具有这个键码的词")); if (wildcard) return true; // keep editing return clearBufCandi(); // clean everything } // if there is only one candidate and non-wildcard, just commit it if (valuelist.size()==1 && !wildcard) { b->clear()->append(valuelist[0].c_str())->send(); return clearBufCandi(); } candictrl.prepare(&candilist, (char*)parent->selkey.c_str(), c); state=PTS_MODE_CANDIDATE; return true; } catch (const char*) { PTDBErrMsg(s, PTMsg(s, "Database error", "資料庫錯誤", "数据库错误"), parent->db); } return clearBufCandi(); } bool OVKPPhraseToolsContext::composeAndUpdateBuffer() { b->clear()->append(keyseq.compose().c_str())->update(); return true; } bool OVKPPhraseToolsContext::clearBufCandi() { if (!b->isEmpty()) b->clear()->update(); if (c->onScreen()) c->hide()->clear()->update(); clear(); return true; } bool OVKPPhraseToolsContext::cancelmsg(int newstate, bool returnvalue) { state=newstate; return cancelmsg(returnvalue); } bool OVKPPhraseToolsContext::cancelmsg(bool returnvalue) { s->notify(PTMsg(s, "Phrase management mode OFF", "跳出詞彙管理工具", "跳出词汇管理工具")); clearBufCandi(); return returnvalue; } OVKPPhraseTools::OVKPPhraseTools(bool &cflag, PTKeySequence &wseq, SQLite3 *pdb) : recordingMode(cflag), recSeq(wseq) { db=pdb; } const char *OVKPPhraseTools::localizedName(const char *lc) { return PTMsg(lc, "Phrase Management", "詞彙管理工具", "词汇管理工具"); } int OVKPPhraseTools::initialize(OVDictionary *c, OVService* s, const char*) { update(c, s); return true; } void OVKPPhraseTools::update(OVDictionary *cfg, OVService*) { // this overwrites and fixes a bug const char *ak=cfg->getStringWithDefault("activationKey", PT_DEFKEY); const char *ck=cfg->getStringWithDefault("textRecordingKey", PT_CATCHKEY); if (strlen(ak) > 1) { // wrong default cfg->setString("activationKey", PT_DEFKEY); } if (strlen(ck) > 1) { cfg->setString("textRecordingKey", PT_CATCHKEY); } actkey=*(cfg->getStringWithDefault("activationKey", PT_DEFKEY)); catchkey=*(cfg->getStringWithDefault("textRecordingKey", PT_CATCHKEY)); selkey=cfg->getStringWithDefault("selectionKey", "1234567890"); } OVInputMethodContext *OVKPPhraseTools::newContext() { return new OVKPPhraseToolsContext(this); } OVOFTextRecorder::OVOFTextRecorder(bool &cflag, PTKeySequence &wseq) : recordingMode(cflag), recSeq(wseq) { } const char *OVOFTextRecorder::localizedName(const char *lc) { return PTMsg(lc, "Phrase Tools: Text Recorder", "詞彙管理─文字輸出記錄器", "词汇管理─文字输出记录器"); } const char *OVOFTextRecorder::process(const char* i, OVService* s) { if (!recordingMode) return i; recSeq.add(string(i)); PTRecordingMessage(recSeq.compose(), s); return i; } bool PTDBErrMsg(const char *m, SQLite3 *db, bool returnValue) { murmur("OVKPPhraseTools: %s (db errcode=%d, errmsg=%s)", m, db->errcode(), db->errmsg()); return returnValue; } bool PTDBErrMsg(OVService *s, const char *m, SQLite3 *db, bool returnValue) { s->notify(m); murmur("OVKPPhraseTools: %s (db errcode=%d, errmsg=%s)", m, db->errcode(), db->errmsg()); return returnValue; } const char *PTMsg(const char *lc, const char *en, const char *tw, const char *cn) { if (!strcasecmp(lc, "zh_TW")) return tw ? tw : en; if (!strcasecmp(lc, "zh_CN")) return cn ? cn : en; return en; } const char *PTMsg(OVService *s, const char *en, const char *tw, const char *cn) { return PTMsg(s->locale(), en, tw, cn); } void PTModeOnMsg(OVService *s) { s->notify(PTMsg(s, "Phrase management mode", "詞彙管理工具", "词汇管理工具")); } void PTRecordingMessage(const string& rstr, OVService *s) { string msgString = PTMsg(s, "Recording: ", "記錄中:", "记录中:"); msgString += rstr; s->notify(msgString.c_str()); } string PTTime(){ time_t tt; time(&tt); struct tm *tp; tp=gmtime(&tt); char buf[256]; sprintf(buf, "%04d-%02d-%02d %02d:%02d:%02d", tp->tm_year+1900, tp->tm_mon+1, tp->tm_mday, tp->tm_hour, tp->tm_min, tp->tm_sec); return string(buf); } // Because the current architecture of OV loader does not allow // attached module instance creation (e.g. getting an instance of // OVInputMethod also gets you an OVOutputFilter that comes with it), // we'll have to make do by putting control flag, working string etc. // into the shared heap of the module library (the .dylib/.dll) instead // of putting those information in class instances. It's ok if each // GUI application loads an instance of OV loader, hence also an instance // of the .dylib/.dll. It'll be more interesting if there is only // one instance of loader (ie. loader server)... we have to be careful. bool ptRecordingMode=false; PTKeySequence ptRecSeq; SQLite3 *ptDB=NULL; extern "C" int OVInitializeLibrary(OVService *s, const char *mp) { const char *ps=s->pathSeparator(); const char *up=s->userSpacePath("OVKPPhraseTools"); char *pn=sqlite3_mprintf("%s%s%s", up, ps, PT_DBNAME); string dn(pn); free(pn); murmur("OVKPPhraseTools: opening database %s", dn.c_str()); try { ptDB=new SQLite3; if (!ptDB) { murmur("memory allocation error"); return false; } if (ptDB->open(dn.c_str()) != SQLITE_OK) throw "error opening database"; ptDB->execute(PT_DBTBLCREATE); ptDB->execute(PT_DBIDXCREATE); } catch (const char *m) { if (ptDB) { PTDBErrMsg(m, ptDB); delete ptDB; } ptDB=NULL; return false; } return true; } extern "C" OVModule *OVGetModuleFromLibrary(int idx) { switch(idx) { case 0: return new OVKPPhraseTools(ptRecordingMode, ptRecSeq, ptDB); case 1: return new OVOFTextRecorder(ptRecordingMode, ptRecSeq); } return NULL; } extern "C" unsigned int OVGetLibraryVersion() { return OV_VERSION; }