1 // OVKPPhraseTools.cpp: Phrase management tool 2 // 3 // Copyright (c) 2004-2006 The OpenVanilla Project (http://openvanilla.org) 4 // All rights reserved. 5 // 6 // Redistribution and use in source and binary forms, with or without 7 // modification, are permitted provided that the following conditions 8 // are met: 9 // 10 // 1. Redistributions of source code must retain the above copyright 11 // notice, this list of conditions and the following disclaimer. 12 // 2. Redistributions in binary form must reproduce the above copyright 13 // notice, this list of conditions and the following disclaimer in the 14 // documentation and/or other materials provided with the distribution. 15 // 3. Neither the name of OpenVanilla nor the names of its contributors 16 // may be used to endorse or promote products derived from this software 17 // without specific prior written permission. 18 // 19 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 22 // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 23 // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 24 // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 25 // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 26 // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 27 // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 28 // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 29 // POSSIBILITY OF SUCH DAMAGE. 30 31 #include "OVKPPhraseTools.h" 32 #include <time.h> 33 34 const char *PTMsg(OVService *s, const char *en, const char *tw=NULL, 35 const char *cn=NULL); 36 const char *PTMsg(const char *lc, const char *en, const char *tw=NULL, 37 const char *cn=NULL); 38 bool PTDBErrMsg(const char *m, SQLite3 *db, bool returnValue=false); 39 bool PTDBErrMsg(OVService *s, const char *m, SQLite3 *db, bool 40 returnValue=false); 41 void PTRecordingMessage(const string& rstr, OVService *s); 42 void PTModeOnMsg(OVService *s); 43 string PTTime(); 44 45 OVKPPhraseToolsContext::OVKPPhraseToolsContext(OVKPPhraseTools *p) { 46 parent=p; 47 clear(); 48 } 49 50 void OVKPPhraseToolsContext::start(OVBuffer*, OVCandidate*, OVService*) { 51 } 52 53 void OVKPPhraseToolsContext::clear() { 54 state=PTS_WAIT; 55 parent->recordingMode=false; 56 parent->recSeq.clear(); 57 58 wildcard=false; 59 valuelist.clear(); 60 candilist.clear(); 61 keyseq.clear(); 62 } 63 64 void OVKPPhraseToolsContext::end() { 65 clear(); 66 } 67 68 int OVKPPhraseToolsContext::keyEvent(OVKeyCode *pk, OVBuffer *pb, 69 OVCandidate *pc, OVService *ps) 70 { 71 k=pk; 72 b=pb; 73 c=pc; 74 s=ps; 75 76 switch(state) { 77 case PTS_MODE_ACTIVATED: 78 return state_activated(); 79 case PTS_MODE_COMMAND: 80 return state_command(); 81 case PTS_MODE_CANDIDATE: 82 return state_candidate(); 83 } 84 85 if (b->isEmpty() && (k->code()==ovkBackspace || k->code()==ovkDelete) && 86 !k->isFunctionKey()) { 87 if (parent->recordingMode) { 88 parent->recSeq.pop(); 89 if (!parent->recSeq.count()) { 90 parent->recordingMode=false; 91 return(cancelmsg(PTS_WAIT, false)); 92 } 93 else { 94 PTRecordingMessage(parent->recSeq.compose(), s); 95 } 96 } 97 return false; 98 } 99 100 if (b->isEmpty() && k->code()==parent->actkey && !k->isFunctionKey()) { 101 state=PTS_MODE_ACTIVATED; 102 PTModeOnMsg(s); 103 return true; 104 } 105 106 return false; 107 } 108 109 // bool OVKPPhraseToolsContext::state_ 110 111 bool OVKPPhraseToolsContext::state_activated() { 112 char foo[8]; 113 114 // buffer is still empty at this stage, 115 // all function key combinations will end this state 116 if (k->isFunctionKey()) { 117 return cancelmsg(PTS_WAIT, false); 118 } 119 120 // if it's once again the activation key, send the same key code back 121 if (k->code()==parent->actkey) { 122 sprintf(foo, "%c", parent->actkey); 123 b->clear()->append(foo)->send(); 124 return cancelmsg(PTS_WAIT, true); 125 } 126 127 if (k->code()==parent->catchkey) { 128 s->notify(PTMsg(s, "Start text recording", "進入記錄模式", "进入记录模式")); 129 parent->recordingMode=true; 130 state=PTS_WAIT; 131 return true; 132 } 133 134 if (isprint(k->code())) { 135 state=PTS_MODE_COMMAND; 136 return state_command(); 137 } 138 139 if (k->code()==ovkEsc) { 140 clear(); 141 return cancelmsg(PTS_WAIT, true); 142 } 143 144 return cancelmsg(PTS_WAIT, false); 145 } 146 147 bool OVKPPhraseToolsContext::state_command() { 148 // now we have something in the buffer, ban all function keys 149 if (k->isFunctionKey()) { 150 b->update(); 151 s->beep(); 152 PTModeOnMsg(s); 153 return true; 154 } 155 156 if (k->code()==ovkDelete || k->code()==ovkBackspace) { 157 keyseq.pop(); 158 if (!keyseq.count()) { // keyseq now empty 159 clearBufCandi(); 160 return cancelmsg(PTS_WAIT, true); 161 } 162 PTModeOnMsg(s); 163 return composeAndUpdateBuffer(); 164 } 165 166 if (k->code()==ovkReturn) { // process the command! 167 return parse_command(); 168 } 169 170 if (k->code()==ovkEsc) { // escape cancels altogether 171 clearBufCandi(); 172 return cancelmsg(PTS_WAIT, true); 173 } 174 175 if (k->code()==ovkTab) { // wildcard search 176 return command_search(keyseq.compose(), true); 177 } 178 179 if (isprint(k->code())) { 180 keyseq.add(string(1, k->code())); 181 PTModeOnMsg(s); 182 return composeAndUpdateBuffer(); 183 } 184 185 s->beep(); // non-print char 186 return true; 187 } 188 189 bool OVKPPhraseToolsContext::state_candidate() { 190 if (k->code()==ovkEsc || k->isFunctionKey()) { 191 s->beep(); 192 clearBufCandi(); 193 return cancelmsg(PTS_WAIT, true); 194 } 195 196 switch(k->code()) { 197 case ovkSpace: 198 case ovkDown: 199 case ovkPageDown: 200 case ovkLeft: 201 candictrl.pageDown(); 202 candictrl.update(c); 203 return true; 204 case ovkUp: 205 case ovkPageUp: 206 case ovkRight: 207 candictrl.pageUp(); 208 candictrl.update(c); 209 return true; 210 } 211 212 int selected; 213 selected=(k->code()==ovkReturn) ? candictrl.select(parent->selkey[0]) : 214 candictrl.select(k->code()); 215 216 if (selected == -1) { 217 s->beep(); 218 return true; 219 } 220 string sel=valuelist[selected]; 221 b->clear()->append(sel.c_str())->send(); 222 clearBufCandi(); 223 224 string msg(PTMsg(s, "Sent phrase: ", "送出詞彙:", "送出词汇:")); 225 msg += sel; 226 s->notify(msg.c_str()); 227 228 return true; 229 } 230 231 232 bool OVKPPhraseToolsContext::parse_command() { 233 string cps=keyseq.compose(); 234 235 // if it's help 236 if (cps=="h" || cps=="H") { 237 s->notify(PTMsg(s, 238 "commands:\na key -- associate the recorded phrase with key", 239 "指令:\na key ─ 將記錄下的詞彙以key值存入資料庫", 240 "指令:\na key ─ 将记录下的词汇以key值存入数据库")); 241 return clearBufCandi(); 242 } 243 244 // if it's a command 245 if (cps.length() > 2 && cps[1]==' ') { 246 string param=cps.substr(2, cps.length()-2); 247 switch(tolower(cps[0])) { 248 case 'a': 249 return command_add(param); 250 } 251 252 s->notify(PTMsg( 253 "command not recognied", "錯誤的指令", "错误的指令")); 254 return clearBufCandi(); 255 } 256 257 // search and candidate (if any) or commit 258 return command_search(cps, false); 259 } 260 261 bool OVKPPhraseToolsContext::command_add(const string& k) { 262 string v=parent->recSeq.compose(); 263 if (!v.length()) { 264 s->notify(PTMsg(s, "No recored phrase to add", 265 "沒有任何記錄到的詞彙可存入數據數", 266 "没有任何记录到的词汇可存入数据库")); 267 return clearBufCandi(); 268 } 269 270 if (SQLITE_OK==parent->db->execute( 271 "insert into phrase values('%q', '%q', '', '%q', '0');", 272 k.c_str(), v.c_str(), PTTime().c_str())) 273 { 274 string m = k + " => " + v; 275 s->notify(m.c_str()); 276 } 277 else { 278 PTDBErrMsg(s, PTMsg(s, "Database error", "資料庫錯誤", "数据库错误"), 279 parent->db); 280 } 281 282 return clearBufCandi(); 283 } 284 285 bool OVKPPhraseToolsContext::command_search(const string& k, bool wcard) { 286 try { 287 SQLite3Statement* st; 288 289 wildcard=wcard; 290 291 st=parent->db->prepare(wildcard ? 292 "select key, value, context from phrase where key like '%q%%' order by freq desc, time desc;" : 293 "select key, value, context from phrase where lower(key)=lower('%q') order by freq desc, time desc;", k.c_str()); 294 295 if (!st) throw "database error"; 296 297 candilist.clear(); 298 valuelist.clear(); 299 while (st->step()==SQLITE_ROW) { 300 string k(st->column_text(0)); 301 string v(st->column_text(1)); 302 string x(st->column_text(2)); // context information 303 304 valuelist.push_back(v); 305 306 string d; 307 if (wildcard) d=k + string(" => "); 308 d+=v; 309 310 if (x.length()) { 311 d+=string(" ("); 312 d+=x; 313 d+=string(")"); 314 } 315 316 candilist.push_back(d); 317 } 318 319 delete st; 320 321 // if there is nothing 322 if (!valuelist.size()) { 323 s->beep(); 324 s->notify(PTMsg(s, "Not found", 325 "找不到具有這個鍵碼的詞", "找不到具有这个键码的词")); 326 327 if (wildcard) return true; // keep editing 328 return clearBufCandi(); // clean everything 329 } 330 331 // if there is only one candidate and non-wildcard, just commit it 332 if (valuelist.size()==1 && !wildcard) { 333 b->clear()->append(valuelist[0].c_str())->send(); 334 return clearBufCandi(); 335 } 336 337 candictrl.prepare(&candilist, (char*)parent->selkey.c_str(), c); 338 state=PTS_MODE_CANDIDATE; 339 return true; 340 } 341 catch (const char*) { 342 PTDBErrMsg(s, PTMsg(s, "Database error", "資料庫錯誤", "数据库错误"), 343 parent->db); 344 } 345 return clearBufCandi(); 346 } 347 348 bool OVKPPhraseToolsContext::composeAndUpdateBuffer() { 349 b->clear()->append(keyseq.compose().c_str())->update(); 350 return true; 351 } 352 353 bool OVKPPhraseToolsContext::clearBufCandi() { 354 if (!b->isEmpty()) b->clear()->update(); 355 if (c->onScreen()) c->hide()->clear()->update(); 356 clear(); 357 return true; 358 } 359 360 bool OVKPPhraseToolsContext::cancelmsg(int newstate, bool returnvalue) { 361 state=newstate; 362 return cancelmsg(returnvalue); 363 } 364 365 bool OVKPPhraseToolsContext::cancelmsg(bool returnvalue) { 366 s->notify(PTMsg(s, "Phrase management mode OFF", "跳出詞彙管理工具", "跳出词汇管理工具")); 367 clearBufCandi(); 368 return returnvalue; 369 } 370 371 OVKPPhraseTools::OVKPPhraseTools(bool &cflag, PTKeySequence &wseq, 372 SQLite3 *pdb) : recordingMode(cflag), recSeq(wseq) 373 { 374 db=pdb; 375 } 376 377 const char *OVKPPhraseTools::localizedName(const char *lc) { 378 return PTMsg(lc, "Phrase Management", "詞彙管理工具", 379 "词汇管理工具"); 380 } 381 382 int OVKPPhraseTools::initialize(OVDictionary *c, OVService* s, const char*) { 383 update(c, s); 384 return true; 385 } 386 387 void OVKPPhraseTools::update(OVDictionary *cfg, OVService*) { 388 // this overwrites and fixes a bug 389 const char *ak=cfg->getStringWithDefault("activationKey", PT_DEFKEY); 390 const char *ck=cfg->getStringWithDefault("textRecordingKey", PT_CATCHKEY); 391 392 if (strlen(ak) > 1) { // wrong default 393 cfg->setString("activationKey", PT_DEFKEY); 394 } 395 if (strlen(ck) > 1) { 396 cfg->setString("textRecordingKey", PT_CATCHKEY); 397 } 398 399 actkey=*(cfg->getStringWithDefault("activationKey", PT_DEFKEY)); 400 catchkey=*(cfg->getStringWithDefault("textRecordingKey", PT_CATCHKEY)); 401 selkey=cfg->getStringWithDefault("selectionKey", "1234567890"); 402 } 403 404 OVInputMethodContext *OVKPPhraseTools::newContext() { 405 return new OVKPPhraseToolsContext(this); 406 } 407 408 OVOFTextRecorder::OVOFTextRecorder(bool &cflag, PTKeySequence &wseq) 409 : recordingMode(cflag), recSeq(wseq) 410 { 411 } 412 413 const char *OVOFTextRecorder::localizedName(const char *lc) { 414 return PTMsg(lc, "Phrase Tools: Text Recorder", "詞彙管理─文字輸出記錄器", 415 "词汇管理─文字输出记录器"); 416 } 417 418 const char *OVOFTextRecorder::process(const char* i, OVService* s) { 419 if (!recordingMode) return i; 420 421 recSeq.add(string(i)); 422 PTRecordingMessage(recSeq.compose(), s); 423 424 return i; 425 } 426 427 bool PTDBErrMsg(const char *m, SQLite3 *db, bool returnValue) { 428 murmur("OVKPPhraseTools: %s (db errcode=%d, errmsg=%s)", m, db->errcode(), db->errmsg()); 429 return returnValue; 430 } 431 432 bool PTDBErrMsg(OVService *s, const char *m, SQLite3 *db, bool returnValue) { 433 s->notify(m); 434 murmur("OVKPPhraseTools: %s (db errcode=%d, errmsg=%s)", m, db->errcode(), db->errmsg()); 435 return returnValue; 436 437 } 438 439 const char *PTMsg(const char *lc, const char *en, const char *tw, 440 const char *cn) 441 { 442 if (!strcasecmp(lc, "zh_TW")) return tw ? tw : en; 443 if (!strcasecmp(lc, "zh_CN")) return cn ? cn : en; 444 return en; 445 } 446 447 const char *PTMsg(OVService *s, const char *en, const char *tw, const char *cn) 448 { 449 return PTMsg(s->locale(), en, tw, cn); 450 } 451 452 void PTModeOnMsg(OVService *s) { 453 s->notify(PTMsg(s, "Phrase management mode", "詞彙管理工具", "词汇管理工具")); 454 } 455 456 void PTRecordingMessage(const string& rstr, OVService *s) { 457 string msgString = PTMsg(s, "Recording: ", "記錄中:", "记录中:"); 458 msgString += rstr; 459 s->notify(msgString.c_str()); 460 } 461 462 string PTTime(){ 463 time_t tt; 464 time(&tt); 465 466 struct tm *tp; 467 tp=gmtime(&tt); 468 469 char buf[256]; 470 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); 471 return string(buf); 472 } 473 474 475 // Because the current architecture of OV loader does not allow 476 // attached module instance creation (e.g. getting an instance of 477 // OVInputMethod also gets you an OVOutputFilter that comes with it), 478 // we'll have to make do by putting control flag, working string etc. 479 // into the shared heap of the module library (the .dylib/.dll) instead 480 // of putting those information in class instances. It's ok if each 481 // GUI application loads an instance of OV loader, hence also an instance 482 // of the .dylib/.dll. It'll be more interesting if there is only 483 // one instance of loader (ie. loader server)... we have to be careful. 484 485 bool ptRecordingMode=false; 486 PTKeySequence ptRecSeq; 487 SQLite3 *ptDB=NULL; 488 489 extern "C" int OVInitializeLibrary(OVService *s, const char *mp) { 490 const char *ps=s->pathSeparator(); 491 const char *up=s->userSpacePath("OVKPPhraseTools"); 492 493 char *pn=sqlite3_mprintf("%s%s%s", up, ps, PT_DBNAME); 494 string dn(pn); 495 free(pn); 496 497 murmur("OVKPPhraseTools: opening database %s", dn.c_str()); 498 499 try { 500 ptDB=new SQLite3; 501 if (!ptDB) { 502 murmur("memory allocation error"); 503 return false; 504 } 505 506 if (ptDB->open(dn.c_str()) != SQLITE_OK) throw "error opening database"; 507 508 ptDB->execute(PT_DBTBLCREATE); 509 ptDB->execute(PT_DBIDXCREATE); 510 } 511 catch (const char *m) { 512 if (ptDB) { 513 PTDBErrMsg(m, ptDB); 514 delete ptDB; 515 } 516 ptDB=NULL; 517 return false; 518 } 519 return true; 520 } 521 522 extern "C" OVModule *OVGetModuleFromLibrary(int idx) { 523 switch(idx) { 524 case 0: 525 return new OVKPPhraseTools(ptRecordingMode, ptRecSeq, 526 ptDB); 527 case 1: 528 return new OVOFTextRecorder(ptRecordingMode, ptRecSeq); 529 } 530 531 return NULL; 532 } 533 534 extern "C" unsigned int OVGetLibraryVersion() { 535 return OV_VERSION; 536 } 537 538