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