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