1// OVIMUIM.mm: UIM-Anthy Wrapper 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// #define OV_DEBUG 32#include <OpenVanilla/OpenVanilla.h> 33#include <OpenVanilla/OVLibrary.h> 34#include <OpenVanilla/OVUtility.h> 35#include <ctype.h> 36#include <stdio.h> 37#include <string.h> 38#include <Cocoa/Cocoa.h> 39#include <UIM/uim.h> 40#include <string> 41#include <vector> 42 43using namespace std; 44 45class OUCandidate { 46public: 47 OUCandidate() { 48 clear(); perpage=10; 49 } 50 51 void clear() { list.clear(); count=pos=pagefrom=0; } 52 53 void add(const char *s) { 54 list.push_back(string(s)); 55 count=list.size(); 56 } 57 void setPerPageItems(int l) { perpage=l; } 58 59 void pageUp() { 60 pagefrom-=perpage; 61 checkBound(); 62 } 63 64 void pageDown() { 65 pagefrom+=perpage; 66 checkBound(); 67 } 68 69 bool canPageDown() { 70 if (pagefrom+perpage >= count) return false; 71 return true; 72 } 73 74 void checkBound() { 75 if (pagefrom >= count) pagefrom=count-perpage; 76 if (pagefrom < 0) pagefrom=0; 77 } 78 79 void set(int p) { 80 int pageto=pagefrom+perpage; // pageto = lowerbound +1 81 if (pageto >= count) pageto=count; 82 83 if (p >= pageto) { // next page 84 pagefrom=p; 85 checkBound(); 86 } 87 if (p < pagefrom) { // previous page 88 pagefrom=p-perpage+1; 89 checkBound(); 90 } 91 if (!pos && p==count-1) { // back from 0, special case 92 pagefrom=p-perpage+1; 93 checkBound(); 94 } 95 96 pos=p; 97 } 98 99 int digits(int x) { 100 int d=0; 101 do { 102 x=x/10; 103 d++; 104 } while(x); 105 106 return d; 107 } 108 109 void update(OVCandidate *c) { 110 int pageto=pagefrom+perpage; // pageto = lowerbound +1 111 if (pageto >= count) pageto=count; 112 113 int pagecount=(pageto-pagefrom); 114 115 c->clear(); 116 117 // get the formatting string right 118 int ptd=digits(pageto-1); 119 char formatstr[256]; 120 // char ds[256]; 121 sprintf(formatstr, "%%0%dd.\t%%s", ptd); 122 123/* sprintf(ds, "%d", ptd); 124 strcpy(formatstr, "%0"); 125 strcat(formatstr, ds); 126 strcat(formatstr, "d. \t%s"); */ 127 128 // test run, get the longest line 129 const char *d; 130 char buf[256]; 131 size_t lgst=0; 132 for (int i=pagefrom; i < pageto; i++) { 133 d=list[i].c_str(); 134 sprintf(buf, formatstr, i, d); 135 if (strlen(buf) > lgst) lgst=strlen(buf); 136 } 137 138 139 for (int i=pagefrom; i < pageto; i++) { 140 d=list[i].c_str(); 141 sprintf(buf, formatstr, i, d); 142 while(strlen(buf) < lgst) strcat(buf, " "); 143 144 if (i==pos) strcat (buf, " «« "); 145 if (i!=pageto-1 && pagecount>1) strcat(buf, "\n"); 146 c->append(buf); 147 } 148 c->update(); 149 } 150 151 152protected: 153 int count, pos, perpage; 154 int pagefrom; 155 156 vector<string> list; 157}; 158 159class OUEncodingConvertor { 160public: 161 OUEncodingConvertor(NSStringEncoding e) { tocode=e; } 162 char *convert(const char *fromstr) { 163 NSString *s=[NSString stringWithCString:fromstr encoding:tocode]; 164 const char *u8=[s UTF8String]; 165 char *rs=(char*)calloc(1, strlen(u8)+1); 166 strcpy(rs, u8); 167 return rs; 168 } 169protected: 170 NSStringEncoding tocode; 171}; 172 173int OUEncodingIsConvertible(const char *to, const char *from) { 174 return 1; 175} 176 177void *OUEncodingCreate(const char *to, const char *from) { 178 return new OUEncodingConvertor(CFStringConvertEncodingToNSStringEncoding(kCFStringEncodingEUC_JP)); 179} 180 181char *OUEncodingConvert(void *obj, const char *str) { 182 return ((OUEncodingConvertor*)obj)->convert(str); 183} 184 185void OUEncodingRelease(void *obj) { 186 delete (OUEncodingConvertor*)obj; 187} 188 189 190 191class OVIMUIMContext : public OVInputMethodContext 192{ 193public: 194 OVIMUIMContext() { 195 ovbuf=NULL; 196 ovcandi=NULL; 197 ovsrv=NULL; 198 } 199 200 void setUIMContext(uim_context c) { 201 uc = c; 202 } 203 virtual int keyEvent(OVKeyCode* k, OVBuffer* b, OVCandidate* c, OVService* srv) { 204 // retain objects for the uim callbacks 205 ovbuf=b; 206 ovcandi=c; 207 ovsrv=srv; 208 209 int keycode=k->code(); 210 if (k->isFunctionKey()) return 0; 211 212 // convert keycode to UIM keycode 213 switch (k->code()) { 214 case ovkHome: keycode=UKey_Home; break; 215 case ovkEnd: keycode=UKey_End; break; 216 case ovkPageUp: keycode=UKey_Prior; break; 217 case ovkPageDown: keycode=UKey_Next; break; 218 case ovkLeft: keycode=UKey_Left; break; 219 case ovkRight: keycode=UKey_Right; break; 220 case ovkUp: keycode=UKey_Up; break; 221 case ovkDown: keycode=UKey_Down; break; 222 case ovkReturn: keycode=UKey_Return; break; 223 case ovkEsc: keycode=UKey_Escape; break; 224 case ovkDelete: keycode=UKey_Delete; break; 225 case ovkBackspace: keycode=UKey_Backspace; break; 226 } 227 228 int r=uim_press_key(uc, keycode, 0); 229 uim_release_key(uc, keycode, 0); 230 231 if (!r) return 1; 232 return 0; 233 } 234 235// UIM callbacks 236public: 237 void uimClear() { 238 if (!ovbuf) return; 239 ovbuf->clear()->update(); 240 upos=uhighlightstart=uhighlightend=0; 241 ucountpos=1; 242 } 243 244 void uimPush(int state, const char *s) { 245 // UIM states 246 // UPreeditAttr_UnderLine = 1, // draw underline (NORMAL MODE) 247 // UPreeditAttr_Reverse = 2, // draw "candidate line" (HIGHLIGHT) 248 // UPreeditAttr_Cursor = 4, // cursor stops here 249 // binary OR op: (2 | 4) = 6 // candidate lne & cursor stops here 250 251 murmur("uimPush, state=%d, string=%d, strlen=%s", state, s ? strlen(s): 0, s ? s :0); 252 253 int charcount=0; 254 if (s) { 255 NSString *ns=[NSString stringWithUTF8String:s]; 256 charcount=[ns length]; 257 } 258 if (state & UPreeditAttr_Reverse) { 259 // if (!ovcandi->onScreen()) ovsrv->notify("漢字変換モード, ←/→ to move, ↑↓ for candidates"); 260 uhighlightstart=upos; 261 uhighlightend=upos+charcount; 262 } 263 if (ucountpos) upos+=charcount; 264 if (state & UPreeditAttr_Cursor) ucountpos=0; // stop counting for cursor pos 265 266 murmur("last segment length=%d, new cursor position=%d, cursor counting=%s", 267 charcount, upos, ucountpos ? "CONTINUING" : "STOPPED"); 268 269 if (s) ovbuf->append(s); 270 } 271 272 void uimUpdate() { 273 murmur("uimUpdate: updating composing (preedit buffer)"); 274 if (ovbuf) ovbuf->update(upos, uhighlightstart, uhighlightend); 275 upos=uhighlightstart=uhighlightend=0; 276 ucountpos=1; 277 } 278 279 void uimCommit(const char *s) { 280 murmur("uimCommit, commit string=%s", s); 281 if (ovbuf) ovbuf->clear()->append(s)->send(); 282 } 283 284 void uimCandidateActivate(int nr, int display_limit) { 285 murmur("candidate activate, nr=%d, limit=%d", nr, display_limit); 286 ccount=0; 287 cindex=0; 288 cperpage=display_limit; 289 ccount=nr; 290 291 ovcandi->clear(); 292 cdi.clear(); 293 cdi.setPerPageItems(display_limit); 294 295 for (int i=0; i<nr; i++) { 296 uim_candidate c; 297 c=uim_get_candidate(uc, i, 0); // the "acclerator hint" (0) doesn't seem to work 298 const char *str=uim_candidate_get_cand_str(c); 299 300 cdi.add(str); 301 302 /* char buf[256]; 303 sprintf(buf, "%d.\t%s", i, str); 304 ovcandi->append(buf); 305 if (i != nr-1) ovcandi->append("\n"); */ 306 307 uim_candidate_free(c); 308 } 309 310 cdi.update(ovcandi); 311 ovcandi->show(); 312 313 // ovcandi->update()->show(); 314 // ovsrv->notify("candidate mode, ↑↓ to move or ESC"); 315 } 316 317 void uimCandidateSelect(int index) { 318 cindex=index; 319 320 murmur("candidate select! index=%d", index); 321 322 uim_candidate c; 323 c=uim_get_candidate(uc, index, 0); // try replace 0 324 const char *str=uim_candidate_get_cand_str(c); 325 326 char buf[256]; 327 sprintf(buf, "↑↓ candidate = %s (%d)", str, index); 328 // ovsrv->notify(buf); 329 330 cdi.set(index); 331 cdi.update(ovcandi); 332 333 uim_candidate_free(c); 334 } 335 336 void uimCandidateShiftPage(int direction) { 337 // this is called when PgUp/PgDown is entered, 338 // we have to do our own page flipping though 339 murmur("uim candidate shift page! direction=%d", direction); 340 char buf[256]; 341 sprintf(buf, "page shift, direction=%d", direction); 342 // ovsrv->notify(buf); 343 344 murmur ("set index"); 345 if (direction) { 346 if (!cdi.canPageDown()) { 347 ovsrv->beep(); 348 } 349 else { 350 cdi.pageDown(); 351 cindex += cperpage; 352 if (cindex >= ccount) cindex=ccount-1; 353 } 354 } 355 else { 356 cindex-=cperpage; 357 if (cindex < 0) { 358 cindex=0; 359 ovsrv->beep(); 360 } 361 cdi.pageUp(); 362 } 363 uim_set_candidate_index(uc, cindex); 364 cdi.set(cindex); 365 cdi.update(ovcandi); 366 367 // uimCandidateSelect will be called, so we do nothing 368 } 369 370 void uimCandidateDeactivate() { 371 murmur("uim candidate list deactivated"); 372 ovcandi->hide()->clear()->update(); 373 } 374 375protected: 376 OVBuffer *ovbuf; 377 OVCandidate *ovcandi; 378 OVService *ovsrv; 379 uim_context uc; 380 381 OUCandidate cdi; 382 383 int upos; 384 int ucountpos; 385 int uhighlightstart; 386 int uhighlightend; 387 388 int ccount; 389 int cindex; 390 int cperpage; 391}; 392 393// UIM callback functions 394 395void OUPreeditClear(void *ptr) { if (ptr) ((OVIMUIMContext*)ptr)->uimClear(); } 396void OUPreeditPush(void *ptr, int state, const char *s) { if (ptr) ((OVIMUIMContext*)ptr)->uimPush(state, s); } 397void OUPreeditUpdate(void *ptr) { if (ptr) ((OVIMUIMContext*)ptr)->uimUpdate(); } 398void OUPreeditCommit(void *ptr, const char *s) { if (ptr) ((OVIMUIMContext*)ptr)->uimCommit(s); } 399void OUCandidateActivate(void *ptr, int nr, int display_limit) { if (ptr) ((OVIMUIMContext*)ptr)->uimCandidateActivate(nr, display_limit); } 400void OUCandidateSelect(void *ptr, int index) { if (ptr) ((OVIMUIMContext*)ptr)->uimCandidateSelect(index); } 401void OUCandidateShiftPage(void *ptr, int direction) { if (ptr) ((OVIMUIMContext*)ptr)->uimCandidateShiftPage(direction); } 402void OUCandidateDeactivate(void *ptr) { if (ptr) ((OVIMUIMContext*)ptr)->uimCandidateDeactivate(); } 403 404class OVIMUIM : public OVInputMethod 405{ 406public: 407 virtual const char* identifier() { return "OVIMUIM"; } 408 virtual OVInputMethodContext *newContext() { 409 OVIMUIMContext *ovc=new OVIMUIMContext; 410 uim_context uc; 411 412 // create UIM-anthy 413 murmur("uim context create"); 414 uc=uim_create_context(ovc, "UTF-8", "ja", "anthy", &enccvtr, OUPreeditCommit); 415 if (uc) { 416 ovc->setUIMContext(uc); 417 } 418 else return NULL; 419 420 uim_set_preedit_cb(uc, OUPreeditClear, OUPreeditPush, OUPreeditUpdate); 421 uim_set_candidate_selector_cb(uc, OUCandidateActivate, OUCandidateSelect, OUCandidateShiftPage, OUCandidateDeactivate); 422 // mode 1 is anthy's Hiragana mode 423 uim_set_mode(uc, 1); 424 425 return ovc; 426 } 427 virtual int initialize(OVDictionary *, OVService*, const char *mp) { 428 // THIS IS ACTUALLY NOT RIGHT, BUT WE PRESUME OVIMUIM::INIT WILL ONLY 429 // BE CALLED ONCE 430 431 murmur("OVUIM init"); 432 // setup UIM code convertor (currently only EUC_JP -> UTF8) 433 enccvtr.is_convertible=OUEncodingIsConvertible; 434 enccvtr.create=OUEncodingCreate; 435 enccvtr.convert=OUEncodingConvert; 436 enccvtr.release=OUEncodingRelease; 437 438 // setup UIM 439 uim_init(); 440 return 1; 441 } 442 virtual const char* localizedName(const char *locale) { 443 if (!strcasecmp(locale, "zh_TW")) return "日文輸入法 (UIM-anthy)"; 444 return "UIM (anthy) - Hiragana"; 445 } 446 447protected: 448 struct uim_code_converter enccvtr; 449}; 450 451OV_SINGLE_MODULE_WRAPPER(OVIMUIM); 452 453