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