1// TSComposingBuffer.cpp
2
3#include <Carbon/Carbon.h>
4#include "ATSComponent.h"
5#include "TSComposingBuffer.h"
6
7// we have to define these consts, because they're gone in Tiger... :(
8#define tsbkConvertedText   4
9#define tsbkCaretPosition   1
10#define tsbkSelectedConvertedText 5
11
12TSComposingBuffer::TSComposingBuffer(ComponentInstance i)
13{
14    inst=i;
15    lastupdate=0;
16    str=[NSMutableString new];
17}
18
19TSComposingBuffer::~TSComposingBuffer()
20{
21    [str release];
22}
23
24TSComposingBuffer* TSComposingBuffer::clear()
25{
26    NSRange r;
27    r.length=[str length];
28    r.location=0;
29    [str deleteCharactersInRange: r];
30
31    // lastupdate=0;
32    return this;
33}
34
35TSComposingBuffer* TSComposingBuffer::send()
36{
37    if (!isEmpty())
38    {
39        update(FALSE);
40        lastupdate=[str length];
41        update(TRUE);
42        lastupdate=0;
43
44
45        [str setString:@""];
46        // update(FALSE);
47    }
48    return clear();
49}
50
51Boolean TSComposingBuffer::isEmpty()
52{
53    return [str length] ? FALSE : TRUE;
54}
55
56NSMutableString* TSComposingBuffer::getContent()
57{
58    return str;
59}
60
61TSComposingBuffer* TSComposingBuffer::append(NSString *s)
62{
63    if (s) [str appendString: s];
64    return this;
65}
66
67int TSComposingBuffer::realPos(int p)
68{
69    int rp=0;
70    unsigned int i;
71
72    for (i=0; i<[str length]; i++)
73    {
74        if (rp==p) break;
75
76        // see if encounters a surrogate pair
77        UniChar c=[str characterAtIndex: i];
78        if (c >= 0xd800 && c < 0xdc00)
79        {
80            c=[str characterAtIndex: ++i];
81            if (i<[str length] && c >= 0xdc00 && c < 0xe000) rp++;
82        }
83        else rp++;
84    }
85
86    return i;
87}
88
89Point TSComposingBuffer::getAppCursorPosition()
90{
91    Point pnt;
92	long refcon=0, offset=0;
93	Boolean edge=0;
94	EventRef event=NULL;
95	OSStatus status;
96
97	// define an ASSERT macro here for getAppCursorPosition (GACP)
98	#define GACPASSERT(x)  if ((status=x) != noErr) return pnt;
99
100	GACPASSERT(CreateEvent(NULL, kEventClassTextInput,
101	   kEventTextInputOffsetToPos, GetCurrentEventTime(),
102	   kEventAttributeUserEvent, &event));
103
104	ScriptLanguageRecord record;
105	bzero(&record, sizeof(record));
106	pnt.h = pnt.v = 0;
107
108	#define EVENTPARAM(a, b, c, d) \
109	   GACPASSERT(SetEventParameter(event, a, b, sizeof(c), d));
110
111	EVENTPARAM(kEventParamTextInputSendComponentInstance,
112		typeComponentInstance, ComponentInstance, &inst);
113	EVENTPARAM(kEventParamTextInputSendRefCon, typeLongInteger, long, &refcon);
114	EVENTPARAM(kEventParamTextInputSendTextOffset, typeLongInteger, long, &offset);
115	EVENTPARAM(kEventParamTextInputSendSLRec, typeIntlWritingCode,
116		ScriptLanguageRecord, &record);
117	EVENTPARAM(kEventParamTextInputSendLeadingEdge, typeBoolean, Boolean, &edge);
118    GACPASSERT(SendTextInputEvent(event));
119    GACPASSERT(GetEventParameter(event,
120        kEventParamTextInputReplyPoint, typeQDPoint, NULL, sizeof(Point),
121		NULL, &pnt));
122
123    if (event) ReleaseEvent(event);
124    return pnt;
125
126    #undef EVENTPARAM
127    #undef GACPASSERT
128}
129
130// Apple's terminolgy calls it "session fix"
131TSComposingBuffer* TSComposingBuffer::update(Boolean send, int cursor,
132    int markFrom, int markTo)
133{
134    OSErr error=noErr;
135    UniChar *buf=NULL;
136    long reallen=[str length]*sizeof(UniChar);
137    long fixlen=0;
138    EventRef event=NULL;
139    ScriptLanguageRecord script;
140    TextRange pinrange;
141    TextRangeArrayPtr markrange=nil, updaterange=nil;
142
143    if (send) fixlen=reallen;
144
145    // define an ASSERT macro here (U=update)
146    #define UASSERT(x)  if ((error=x) != noErr) { clear(); \
147        if (buf) { delete[] buf; } \
148        return this; }
149
150    UASSERT(CreateEvent(NULL, kEventClassTextInput,
151        kEventTextInputUpdateActiveInputArea,
152        GetCurrentEventTime(), kEventAttributeUserEvent, &event));
153
154    // first parameter: reference to our text service component's instance
155    UASSERT(SetEventParameter(event, kEventParamTextInputSendComponentInstance,
156        typeComponentInstance, sizeof(ComponentInstance),
157        &inst));
158
159    // second parameter: script information
160    script.fScript=ATSCSCRIPT;
161    script.fLanguage=ATSCLANGUAGE;
162
163    UASSERT(SetEventParameter(event, kEventParamTextInputSendSLRec,
164        typeIntlWritingCode, sizeof(ScriptLanguageRecord), &script));
165
166    buf=new UniChar[[str length]+1];
167    NSRange bufrange;
168    bufrange.location=0;
169    bufrange.length=[str length];
170    if (!buf) { clear(); return this; }
171    [str getCharacters:buf range: bufrange];
172    // 3rd parameter: what we have in the buffer
173    UASSERT(SetEventParameter(event, kEventParamTextInputSendText,
174        typeUnicodeText, reallen, buf));
175
176    // 4th paramter: "fix" length, >0 if we're dumping ("sending") the buffer
177    UASSERT(SetEventParameter(event, kEventParamTextInputSendFixLen,
178        typeLongInteger, sizeof(long), &fixlen));
179
180    // set update region
181    updaterange=(TextRangeArrayPtr)NewPtrClear(sizeof(short)+
182        sizeof(TextRange)*2);
183
184    if (!updaterange) return this;   // memory full (unlikely)
185
186    updaterange->fNumOfRanges=2;
187    updaterange->fRange[0].fStart=0;
188    updaterange->fRange[0].fEnd=lastupdate*sizeof(UniChar);
189    updaterange->fRange[0].fHiliteStyle=0;
190    updaterange->fRange[1].fStart=0;
191    updaterange->fRange[1].fEnd=reallen;
192    updaterange->fRange[1].fHiliteStyle=0;
193
194    lastupdate=[str length];
195    UASSERT(SetEventParameter(event, kEventParamTextInputSendUpdateRng,
196        typeTextRangeArray, sizeof(short)+sizeof(TextRange)*2, updaterange));
197
198    markrange=(TextRangeArrayPtr)NewPtrClear(sizeof(short)+
199        sizeof(TextRange)*3);
200
201    // among the four TSM update messages: k(Selected)RawText and
202    // k(Selected)ConvertedText, it seems only the latter pair works,
203    // i.e both kSelectedRawText and kRawText seem to be defunct
204
205    #define SETRANGE(r, f, e, s) markrange->fRange[r].fStart=f; \
206        markrange->fRange[r].fEnd=e; \
207        markrange->fRange[r].fHiliteStyle=s
208
209    if(!markrange) return this;     // memory full (unlikely)
210
211    markrange->fNumOfRanges=2;
212    int realcur=reallen;
213
214    // if cursor is set (!= -1), we set its position accordingly
215    if (cursor>=0 && realPos(cursor)<=(int)[str length])
216        realcur=realPos(cursor)*sizeof(UniChar);
217
218    // requests app to draw a light gray underline to our text area
219    SETRANGE(0, 0, reallen, tsbkConvertedText);
220    SETRANGE(1, realcur, realcur, tsbkCaretPosition);
221
222    // if markFrom & markTo are set, draw a darker line underneath the marked area
223    if ((markFrom>=0 && realPos(markFrom)<=(int)[str length]) &&
224        (markTo>markFrom && realPos(markTo)<=(int)[str length]))
225    {
226        markrange->fNumOfRanges=3;		// send one more range block
227        SETRANGE(2, realPos(markFrom)*sizeof(UniChar),
228            realPos(markTo)*sizeof(UniChar), tsbkSelectedConvertedText);
229    }
230
231    if (!send) {
232        UASSERT(SetEventParameter(event, kEventParamTextInputSendHiliteRng,
233            typeTextRangeArray,
234            sizeof(short)+sizeof(TextRange)*markrange->fNumOfRanges, markrange));
235    }
236
237    // we don't any "clause" information
238    pinrange.fStart=0;
239    pinrange.fEnd=reallen;
240    UASSERT(SetEventParameter(event,kEventParamTextInputSendPinRng,
241        typeTextRange, sizeof(TextRange), &pinrange));
242
243    UASSERT(SendTextInputEvent(event));      // send event, whew!
244
245    // delete the allocated range objects
246    DisposePtr((Ptr)markrange);
247    DisposePtr((Ptr)updaterange);
248    delete[] buf;
249    return this;
250}
251