/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- * * The contents of this file are subject to the Netscape Public License * Version 1.0 (the "NPL"); you may not use this file except in * compliance with the NPL. You may obtain a copy of the NPL at * http://www.mozilla.org/NPL/ * * Software distributed under the NPL is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the NPL * for the specific language governing rights and limitations under the * NPL. * * The Initial Developer of this code under the NPL is Netscape * Communications Corporation. Portions created by Netscape are * Copyright (C) 1998 Netscape Communications Corporation. All Rights * Reserved. */ #include "TSMProxy.h" #include "proto.h" #include "edt.h" #include "uintl.h" #include "intl_csi.h" #include "xp_trace.h" HoldUpdatesProxy::HoldUpdatesProxy(CEditView &inTextView) : mTextView(inTextView) { mTextView.SetHoldUpdates(this); mStartY = 0; mHeight = 0; } HoldUpdatesProxy::~HoldUpdatesProxy() { mTextView.SetHoldUpdates(nil); mTextView.DocumentChanged(mStartY, mHeight); } void HoldUpdatesProxy::DocumentChanged( int32 iStartY, int32 iHeight ) { if (mHeight == 0) { // there is no range already // just set to the new range mStartY = iStartY; mHeight = iHeight; } else if (mHeight == -1) { // the current range already extends to the bottom // should the top be moved up? if (iStartY < mStartY) mStartY = iStartY; } else if (iHeight == -1) { // the new range extendes all the way to the bottom // should the top be moved up? mHeight = iHeight; if (iStartY < mStartY) mStartY = iStartY; } else { if (iStartY < mStartY) { // use the new top if (iStartY + iHeight > mStartY + mHeight) { // and the new height mStartY = iStartY; mHeight = iHeight; } else { // but the old height mHeight += mStartY - iStartY; mStartY = iStartY; } } else { // use the old top if (iStartY + iHeight > mStartY + mHeight) { // but use the new height mHeight = iStartY + iHeight - mStartY; } } } } AEEventHandlerUPP HTMLInlineTSMProxy::sAEHandler = NewAEEventHandlerProc( AEHandlerTSM ); // HTMLInlineTSMProxy *HTMLInlineTSMProxy::sCurrentProxy = NULL; #if _HAVE_FIXES_FOR_REPLACING_AEGIZMOS_ void HTMLInlineTSMProxy::PasteFromPtr(const Ptr thedata, int32 len, short hiliteStyle, INTL_Encoding_ID datacsid) { if (len < 1) return; EDT_CharacterData *pData = EDT_NewCharacterData(); if (pData) { pData->mask = TF_INLINEINPUT | TF_INLINEINPUTTHICK | TF_INLINEINPUTDOTTED; switch (hiliteStyle) { case kCaretPosition: pData->values = TF_INLINEINPUT | TF_INLINEINPUTTHICK; // this is just a guess actually: FIX ME!! break; case kRawText: pData->values = 0; break; case kSelectedRawText: pData->values = TF_INLINEINPUT | TF_INLINEINPUTTHICK | TF_INLINEINPUTDOTTED; break; default: XP_ASSERT(false); case kConvertedText: pData->values = TF_INLINEINPUT; break; case kSelectedConvertedText: pData->values = TF_INLINEINPUT | TF_INLINEINPUTTHICK; break; } EDT_SetCharacterData( mContext ,pData ); EDT_FreeCharacterData(pData); } // HACK HACK HACK // ok, so everyone has been really helpful and all but I'm going to put this in as a hack // rather than try to do it "right": unicodeString will either be "thedata" or the result // if we need to do unicode conversion. We'll free this below if the pointer address has changed char *unicodeString = thedata; INTL_CharSetInfo csi = LO_GetDocumentCharacterSetInfo(mContext); int16 win_csid = INTL_GetCSIWinCSID(csi); XP_ASSERT( CS_UTF7 != win_csid ); if(( (win_csid == CS_UTF8) ) && ( win_csid != datacsid )) { unicodeString = (char *)INTL_ConvertLineWithoutAutoDetect( datacsid, win_csid, (unsigned char *)thedata, len ); len = strlen(unicodeString); } if (len < 16) { // can we use a small static buffer? char smallbuffer[16]; XP_MEMCPY(smallbuffer, unicodeString, len); smallbuffer[len] = '\0'; EDT_InsertText(mContext, smallbuffer); } else { char *verytemp = (char *) XP_ALLOC(len + 1); if (verytemp) { XP_MEMCPY(verytemp, unicodeString, len); verytemp[len] = '\0'; EDT_InsertText(mContext, verytemp); XP_FREE(verytemp); } } // see hack alert above if ( unicodeString != thedata ) XP_FREEIF(unicodeString); } #endif _HAVE_FIXES_FOR_REPLACING_AEGIZMOS_ HTMLInlineTSMProxy::HTMLInlineTSMProxy( CEditView &inTextView ) : mTextView( inTextView ) { mTSMDocID = 0; OSType supportedType = kTextService; OSErr err = ::NewTSMDocument( 1, &supportedType, &mTSMDocID, (long)(void *)this ); ThrowIfOSErr_(err); mInputHoleActive = false; mDocActive = false; } HTMLInlineTSMProxy::~HTMLInlineTSMProxy() { if ( mDocActive ) Deactivate(); // for a bug in TSM. See TE27 OSErr err = noErr; if ( mTSMDocID ) err = ::DeleteTSMDocument(mTSMDocID); mTSMDocID = 0; // Assert_(err == noErr); } void HTMLInlineTSMProxy::Activate( void ) { OSErr err = noErr; Assert_( mDocActive == false ); InstallTSMHandlers(); // sCurrentProxy = this; #ifdef Debug_Signal // check to see if a bug in TSM will be encountered ProcessSerialNumber psn, csn; err = GetCurrentProcess(&psn); // ThrowIfOSErr_(err); err = GetFrontProcess(&csn); // ThrowIfOSErr_(err); Assert_((psn.highLongOfPSN == csn.highLongOfPSN) && (psn.lowLongOfPSN == csn.lowLongOfPSN)); #endif if ( mTSMDocID ) err = ::ActivateTSMDocument( mTSMDocID ); else err = ::UseInputWindow(NULL, true); // ThrowIfOSErr_(err); if ( err == noErr ) mDocActive = true; } void HTMLInlineTSMProxy::Deactivate( void ) { OSErr err = noErr; Assert_( mDocActive ); RemoveTSMHandlers(); // sCurrentProxy = NULL; err = ::DeactivateTSMDocument( mTSMDocID ); if (err != tsmDocNotActiveErr) // this just seems to happen too much -- it is okay if it happens { Assert_( err == noErr ); } mDocActive = false; } void HTMLInlineTSMProxy::FlushInput( void ) { OSErr err = noErr; Assert_( mTSMDocID != 0 ); if ( mTSMDocID != 0 ) { err = ::FixTSMDocument( mTSMDocID ); } } void HTMLInlineTSMProxy::InstallTSMHandlers( void ) { OSErr err = noErr; err = ::AEInstallEventHandler(kTextServiceClass, kUpdateActiveInputArea, sAEHandler, kUpdateActiveInputArea, false); ThrowIfOSErr_(err); err = ::AEInstallEventHandler(kTextServiceClass, kPos2Offset, sAEHandler, kPos2Offset, false); ThrowIfOSErr_(err); err = ::AEInstallEventHandler(kTextServiceClass, kOffset2Pos, sAEHandler, kOffset2Pos, false); ThrowIfOSErr_(err); } void HTMLInlineTSMProxy::RemoveTSMHandlers( void ) { OSErr err = noErr; err = ::AERemoveEventHandler(kTextServiceClass, kUpdateActiveInputArea, sAEHandler, false); ThrowIfOSErr_(err); err = ::AERemoveEventHandler(kTextServiceClass, kPos2Offset, sAEHandler, false); ThrowIfOSErr_(err); err = ::AERemoveEventHandler(kTextServiceClass, kOffset2Pos, sAEHandler, false); ThrowIfOSErr_(err); } pascal OSErr HTMLInlineTSMProxy::AEHandlerTSM( const AppleEvent *inAppleEvent, AppleEvent *outReply, Int32 inRefCon ) { // XP_Trace("begin HTMLInlineTSMProxy::AEHandlerTSM\n"); OSErr err = noErr; THz oldZone = ::LMGetTheZone(), // Apple bug #115424? appZone = ::LMGetApplZone(); ::LMSetTheZone(appZone); #if _HAVE_FIXES_FOR_REPLACING_AEGIZMOS_ try { Assert_( sCurrentProxy != NULL ); StHandleLocker lock(inAppleEvent->dataHandle); AESubDesc appleEvent; AEDescToSubDesc(inAppleEvent, &appleEvent); AESubDesc keySubDesc; AEGetKeySubDesc( &appleEvent, keyAETSMDocumentRefcon, &keySubDesc ); long len; Int32 *tsmdocrefcon = (Int32*)AEGetSubDescData( &keySubDesc, &len ); ThrowIf_(NULL == tsmdocrefcon); // XP_Trace("try to get keyAETSMDocumentRefcon\n"); HTMLInlineTSMProxy *proxy = (HTMLInlineTSMProxy *)(*tsmdocrefcon); AEStream replyStream; err = AEStream_Open( &replyStream); err = AEStream_OpenRecord( &replyStream, typeAERecord ); if ( proxy != NULL ) { switch( inRefCon ) { case kUpdateActiveInputArea: // XP_Trace("kUpdateActiveInputArea\n"); proxy->AEUpdate(appleEvent); break; case kPos2Offset: XP_Trace("kPos2Offset\n"); proxy->AEPos2Offset(appleEvent, replyStream); break; case kOffset2Pos: XP_Trace("kOffset2Pos\n"); proxy->AEOffset2Pos(appleEvent, replyStream); break; default: XP_Trace("AppleEvent %c%c%c%c \n", (char)(inRefCon >> 24), (char)((inRefCon >> 16) & 0xff), (char)((inRefCon >> 8) & 0xff), (char)(inRefCon & 0xff) ); Assert_(0); break; } } // XP_Trace("AEStream_CloseRecord\n"); err = AEStream_CloseRecord( &replyStream ); // Transfer reply parameters to the real reply (hopefully MacOS 8 will have a way around this) // ie, can simply say: // // replyStream.Close(outReply); // StAEDescriptor reply; // XP_Trace("AEStream_Close\n"); err = AEStream_Close( &replyStream, reply ); AESubDesc replySD; AEDescToSubDesc(reply, &replySD); AEKeyword key; int32 upperBound = AECountSubDescItems( &replySD ); for (long i = 1; i <= upperBound; i++) { StAEDescriptor parm; AESubDesc nthSubDesc; err = AEGetNthSubDesc( &replySD, i, &key, &nthSubDesc ); err = AESubDescToDesc( &nthSubDesc, typeWildCard, &parm.mDesc ); // replySD.NthItem(i, &key).ToDesc(&parm.mDesc); err = ::AEPutParamDesc(outReply, key, &parm.mDesc); ThrowIfOSErr_(err); } } catch ( ExceptionCode inErr ) { err = inErr; } catch ( ... ) { err = paramErr; } #endif _HAVE_FIXES_FOR_REPLACING_AEGIZMOS_ ::LMSetTheZone(oldZone); // Apple bug #115424? // XP_Trace ("end HTMLInlineTSMProxy::AEHandlerTSM\n"); return err; } #if _HAVE_FIXES_FOR_REPLACING_AEGIZMOS_ void HTMLInlineTSMProxy::AEUpdate( const AESubDesc &inAppleEvent ) { OSErr err; CEditView::OutOfFocus(&mTextView); HoldUpdatesProxy stopUpdatesProxy(mTextView); // if we don't already have an input hole, remember where we are if (!mInputHoleActive) { mInputHoleActive = true; mInputHoleStart = EDT_GetInsertPointOffset(mContext); mInputHoleLen = 0; } AESubDesc keySubDesc; long textlen; long dummylen; // Get keyAETheData err = AEGetKeySubDesc( &inAppleEvent, keyAETheData, &keySubDesc ); ThrowIfOSErr_(err); Ptr thedata = (char *) AEGetSubDescData( &keySubDesc, &textlen ); // Get keyAEFixLength err = AEGetKeySubDesc( &inAppleEvent, keyAEFixLength, &keySubDesc ); ThrowIfOSErr_(err); Int32 fixLength = *(Int32 *) AEGetSubDescData( &keySubDesc, &dummylen ); if (fixLength < 0) // special signal to fix it all!! fixLength = textlen; // Get keyAEScriptTag err = AEGetKeySubDesc( &inAppleEvent, keyAETSMScriptTag, &keySubDesc ); ThrowIfOSErr_(err); ScriptLanguageRecord *sl = (ScriptLanguageRecord *) AEGetSubDescData( &keySubDesc, &dummylen ); INTL_Encoding_ID datacsid = ScriptToEncoding( sl->fScript ); // Currently do not depend on it. // Get [optional] keyAEUpdateRange // Currently do not depend on it. // Get [optional] keyAEPinRange // Currently do not depend on it. // Get [optional] keyAEClauseOffsets // Currently do not depend on it. mTextView.EraseCaret(); mTextView.HideCaret(true); // if we do already have an input hole, select all the text and delete so that we start fresh if (mInputHoleLen) { EDT_CharacterData *temp = EDT_GetCharacterData( mContext ); EDT_SetInsertPointToOffset(mContext, mInputHoleStart, mInputHoleLen); EDT_DeletePreviousChar(mContext); if (temp) { if (textlen) // if len == 0, then don't bother setting the character data because there is nothing left! EDT_SetCharacterData( mContext, temp ); EDT_FreeCharacterData( temp ); } } // we will handle this special case because it makes the algorithm easier to understand. // the input hole is going away because we are going to fix everything... if (fixLength == textlen) { PasteFromPtr(thedata, fixLength, kRawText, datacsid); mInputHoleActive = false; CEditView::OutOfFocus(&mTextView); mTextView.HideCaret(false); return; } // we have already selected the old data, now paste in anything that needs to be fixed if (fixLength) { PasteFromPtr(thedata, fixLength, kRawText, datacsid); mInputHoleStart = EDT_GetInsertPointOffset(mContext); // a new starting point for our input hole } // Get [optional] keyAEHiliteRange err = AEGetKeySubDesc( &inAppleEvent, keyAEHiliteRange, &keySubDesc ); XP_ASSERT( keySubDesc != NULL && err == noErr ); if ( err == noErr) { // if (inAppleEvent.KeyExists(keyAEHiliteRange)) { // AESubDesc hiliteSD( hiliteRangeSubDesc, typeTextRangeArray ); // TextRangeArrayPtr p = (TextRangeArrayPtr)hiliteSD.GetDataPtr(); TextRangeArrayPtr p = (TextRangeArrayPtr)AEGetSubDescData( &keySubDesc, &dummylen ); for (Int32 i = 0; i < p->fNumOfRanges; i++) { TextRange record; // we don't care about any extra information which is supposed to be encoded in the sign of any of these numbers record.fStart = abs(p->fRange[i].fStart); record.fEnd = abs(p->fRange[i].fEnd); record.fHiliteStyle = abs(p->fRange[i].fHiliteStyle); PasteFromPtr(thedata + fixLength + record.fStart, record.fEnd - record.fStart, record.fHiliteStyle, datacsid); } } mInputHoleLen = EDT_GetInsertPointOffset(mContext) - mInputHoleStart; // a new length for our input hole // output mTextView.HideCaret(false); CEditView::OutOfFocus(&mTextView); } // so which is it? #define keyAELeadingEdge keyAELeftSide void HTMLInlineTSMProxy::AEPos2Offset( const AESubDesc &inAppleEvent, AEStream &inStream) const { // input Point* pWhere; Point where; Boolean dragging; long len; OSErr err; AESubDesc subdesc; err = AEGetKeySubDesc( &inAppleEvent, keyAECurrentPoint, &subdesc ); pWhere = (Point *)AEGetSubDescData( &subdesc, &len ); where.h = pWhere->h; where.v = pWhere->v; // inAppleEvent.KeyedItem(keyAECurrentPoint).ToPtr(typeQDPoint, &where, sizeof(where)); err = AEGetKeySubDesc( &inAppleEvent, keyAEDragging, &subdesc ); if ( AEGetSubDescType( &subdesc ) != typeNull ) dragging = *(Boolean *)AEGetSubDescData( &subdesc, &len ); else dragging = false; // AESubDesc sd = inAppleEvent.KeyedItem(keyAEDragging); // if (sd.GetType() != typeNull) // keyAEdragging is optional // dragging = sd.ToBoolean(); // process CEditView::OutOfFocus(&mTextView); mTextView.FocusDraw(); // for GlobalToLocal ::GlobalToLocal(&where); CEditView::OutOfFocus(&mTextView); SPoint32 where32; mTextView.LocalToImagePoint(where, where32); LO_HitResult result; LO_Hit(mContext, where32.h, where32.v, false, &result, nil); if (result.type != LO_HIT_ELEMENT || // result.lo_hitElement.region != LO_HIT_ELEMENT_REGION_MIDDLE || result.lo_hitElement.position.element->type != LO_TEXT) { err = AEStream_WriteKey( &inStream, keyAEOffset ); // inStream.WriteKey(keyAEOffset); Int32 offset = -1; err = AEStream_WriteDesc( &inStream, typeLongInteger, &offset, sizeof(offset) ); // inStream.WriteDesc(typeLongInteger, &offset, sizeof(offset)); err = AEStream_WriteKey( &inStream, keyAERegionClass ); // inStream.WriteKey(keyAERegionClass); short aShort = kTSMOutsideOfBody; err = AEStream_WriteDesc( &inStream, typeShortInteger, &aShort, sizeof(aShort) ); // inStream.WriteDesc(typeShortInteger, &aShort, sizeof(aShort)); return; } ED_BufferOffset newPosition = EDT_LayoutElementToOffset( mContext, result.lo_hitElement.position.element, result.lo_hitElement.position.position); /* ED_BufferOffset saveSelStart, saveSelEnd; EDT_GetSelectionOffsets(mContext, &saveSelStart, &saveSelEnd); // remember position EDT_PositionCaret(mContext, where32.h, where32.v ); ED_BufferOffset newPosition = EDT_GetInsertPointOffset(mContext); EDT_SetInsertPointToOffset(mContext, saveSelStart, saveSelEnd - saveSelStart); // restore position */ // restrict to the active range if you are dragging if (dragging) { if (newPosition < mInputHoleStart) newPosition = mInputHoleStart; if (newPosition > mInputHoleStart + mInputHoleLen) newPosition = mInputHoleStart + mInputHoleLen; } // output err = AEStream_WriteKey( &inStream, keyAEOffset ); // inStream.WriteKey(keyAEOffset); Int32 offset = newPosition; offset -= mInputHoleStart; err = AEStream_WriteDesc( &inStream, typeLongInteger, &offset, sizeof(offset) ); // inStream.WriteDesc(typeLongInteger, &offset, sizeof(offset)); err = AEStream_WriteKey( &inStream, keyAERegionClass ); // inStream.WriteKey(keyAERegionClass); short aShort = kTSMOutsideOfBody; SDimension32 sizeImage; SDimension16 sizeFrame; mTextView.GetImageSize(sizeImage); mTextView.GetFrameSize(sizeFrame); if ((0 <= where32.h) && (where32.h < sizeFrame.width) && (0 <= where32.v) && (where.v < sizeImage.height)) { if (offset >= 0 && offset <= mInputHoleLen) aShort = kTSMInsideOfActiveInputArea; else aShort = kTSMInsideOfBody; } err = AEStream_WriteDesc( &inStream, typeShortInteger, &aShort, sizeof(aShort) ); // inStream.WriteDesc(typeShortInteger, &aShort, sizeof(aShort)); } void HTMLInlineTSMProxy::AEOffset2Pos( const AESubDesc &inAppleEvent, AEStream &inStream) const { OSErr err; // input AESubDesc subdesc; err = AEGetKeySubDesc( &inAppleEvent, keyAEOffset, &subdesc ); long len; Int32 offset = *(Int32 *)AEGetSubDescData( &subdesc, &len ); offset += mInputHoleStart; LO_Element * element; int32 caretPos; EDT_OffsetToLayoutElement(mContext, offset, &element, &caretPos); SPoint32 where32; int32 veryTemp; GetCaretPosition( mContext, element, caretPos, &where32.h, &veryTemp, &where32.v ); Point where; mTextView.ImageToLocalPoint(where32, where); CEditView::OutOfFocus(&mTextView); mTextView.FocusDraw(); // for LocalToGlobal ::LocalToGlobal(&where); // output err = AEStream_WriteKey( &inStream, keyAEPoint ); err = AEStream_WriteDesc( &inStream, typeQDPoint, &where, sizeof(where) ); // inStream.WriteKey(keyAEPoint); // inStream.WriteDesc(typeQDPoint, &where, sizeof(where)); } #endif _HAVE_FIXES_FOR_REPLACING_AEGIZMOS_