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