diff options
Diffstat (limited to 'macosx/ExtInput.cxx')
| -rw-r--r-- | macosx/ExtInput.cxx | 608 | 
1 files changed, 608 insertions, 0 deletions
| diff --git a/macosx/ExtInput.cxx b/macosx/ExtInput.cxx new file mode 100644 index 000000000..677a82c3f --- /dev/null +++ b/macosx/ExtInput.cxx @@ -0,0 +1,608 @@ +/******************************************************************************* + +Copyright (c) 2007 Adobe Systems Incorporated + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +********************************************************************************/ + +#include "ScintillaMacOSX.h" +#include "ExtInput.h" + +using namespace Scintilla; + +// uncomment this for a log to /dev/console +// #define LOG_TSM 1 + +#if LOG_TSM +FILE* logFile = NULL; +#endif + +static EventHandlerUPP tsmHandler; + +static EventTypeSpec	tsmSpecs[] = { +	{ kEventClassTextInput, kEventTextInputUpdateActiveInputArea }, +//	{ kEventClassTextInput, kEventTextInputUnicodeForKeyEvent }, +	{ kEventClassTextInput, kEventTextInputOffsetToPos }, +	{ kEventClassTextInput, kEventTextInputPosToOffset }, +	{ kEventClassTextInput, kEventTextInputGetSelectedText } +}; + +#define kScintillaTSM	'ScTs' + +// The following structure is attached to the HIViewRef as property kScintillaTSM + +struct TSMData +{ +	HIViewRef			view;				// this view +	TSMDocumentID		docid;				// the TSM document ID +	EventHandlerRef		handler;			// the event handler +	ScintillaMacOSX*	scintilla;			// the Scintilla pointer +	int					styleMask;			// the document style mask +	int					indicStyle [3];		// indicator styles save +	int					indicColor [3];		// indicator colors save +	int					selStart;			// starting position of selection (Scintilla offset) +	int					selLength;			// UTF-8 number of characters +	int					selCur;				// current position (Scintilla offset) +	int					inhibitRecursion;	// true to stop recursion +	bool				active;				// true if this is active +}; + +static const int numSpecs = 5; + + +// Fetch a range of text as UTF-16; delete the buffer after use + +static char* getTextPortion (TSMData* data, UInt32 start, UInt32 size) +{ +	Scintilla::TextRange range; +	range.chrg.cpMin = start; +	range.chrg.cpMax = start + size; +	range.lpstrText = new char [size + 1]; +	range.lpstrText [size] = 0; +	data->scintilla->WndProc (SCI_GETTEXTRANGE, 0, (uptr_t) &range); +	return range.lpstrText; +} + +static pascal OSStatus doHandleTSM (EventHandlerCallRef, EventRef inEvent, void* userData); + +void ExtInput::attach (HIViewRef viewRef) +{ +	if (NULL == tsmHandler) +		tsmHandler = NewEventHandlerUPP (doHandleTSM); +	::UseInputWindow (NULL, FALSE); + +#ifdef LOG_TSM +	if (NULL == logFile) +		logFile = fopen ("/dev/console", "a"); +#endif + +	// create and attach the TSM data +	TSMData* data = new TSMData; + +	data->view				= viewRef; +	data->active			= false; +	data->inhibitRecursion	= 0; +	 +	::GetControlProperty (viewRef, scintillaMacOSType, 0, sizeof( data->scintilla ), NULL, &data->scintilla); +	 +	if (NULL != data->scintilla) +	{ +		// create the TSM document ref +		InterfaceTypeList interfaceTypes; +		interfaceTypes[0] = kUnicodeDocumentInterfaceType; +		::NewTSMDocument (1, interfaceTypes, &data->docid, (long) viewRef); +		// install my event handler +		::InstallControlEventHandler (viewRef, tsmHandler, numSpecs, tsmSpecs, data, &data->handler); + +		::SetControlProperty (viewRef, kScintillaTSM, 0, sizeof (data), &data); +	} +	else +		delete data; +} + +static TSMData* getTSMData (HIViewRef viewRef) +{ +	TSMData* data = NULL; +	UInt32 n; +	::GetControlProperty (viewRef, kScintillaTSM, 0, sizeof (data), &n, (UInt32*) &data); +	return data; +} + +void ExtInput::detach (HIViewRef viewRef) +{ +	TSMData* data = getTSMData (viewRef); +	if (NULL != data) +	{ +		::DeleteTSMDocument (data->docid); +		::RemoveEventHandler (data->handler); +		delete data; +	} +} + +void ExtInput::activate (HIViewRef viewRef, bool on) +{ +	TSMData* data = getTSMData (viewRef); +	if (NULL == data) +		return; + +	if (on) +	{ +		::ActivateTSMDocument (data->docid); +		HIRect bounds; +		::HIViewGetBounds (viewRef, &bounds); +		::HIViewConvertRect (&bounds, viewRef, NULL); +		RgnHandle hRgn = ::NewRgn(); +		::SetRectRgn (hRgn, (short) bounds.origin.x, (short) bounds.origin.y,  +							(short) (bounds.origin.x + bounds.size.width),  +							(short) (bounds.origin.y + bounds.size.height)); +#if LOG_TSM +		fprintf (logFile, "TSMSetInlineInputRegion (%08lX, %ld:%ld-%ld:%ld)\n",  +						 (long) data->docid, (long) bounds.origin.x, (long) bounds.origin.y, +						 (long) (bounds.origin.x + bounds.size.width), (long) (bounds.origin.y + bounds.size.height)); +		fflush (logFile); +#endif +		::TSMSetInlineInputRegion (data->docid, HIViewGetWindow (data->view), hRgn); +		::DisposeRgn (hRgn); +		::UseInputWindow (NULL, FALSE); +	} +	else +	{ +#if LOG_TSM +		fprintf (logFile, "DeactivateTSMDocument (%08lX)\n", (long) data->docid); +		fflush (logFile); +#endif +		::DeactivateTSMDocument (data->docid); +	} +} + +static void startInput (TSMData* data, bool delSelection = true) +{ +	if (!data->active && 0 == data->inhibitRecursion) +	{ +		data->active = true; +		 +		// Delete any selection +		if( delSelection ) +			data->scintilla->WndProc (SCI_REPLACESEL, 0, reinterpret_cast<sptr_t>("")); +		  +		// need all style bits because we do indicators +		data->styleMask = data->scintilla->WndProc (SCI_GETSTYLEBITS, 0, 0); +		data->scintilla->WndProc (SCI_SETSTYLEBITS, 5, 0); +		 +		// Set the target range for successive replacements +		data->selStart	= +		data->selCur	= data->scintilla->WndProc (SCI_GETCURRENTPOS, 0, 0); +		data->selLength	= 0; +		 +		// save needed styles +		for (int i = 0; i < 2; i++) +		{ +			data->indicStyle [i] = data->scintilla->WndProc (SCI_INDICGETSTYLE, i, 0); +			data->indicColor [i] = data->scintilla->WndProc (SCI_INDICGETFORE, i, 0); +		} +		// set styles and colors +		data->scintilla->WndProc (SCI_INDICSETSTYLE, 0, INDIC_SQUIGGLE); +		data->scintilla->WndProc (SCI_INDICSETFORE,  0, 0x808080); +		data->scintilla->WndProc (SCI_INDICSETSTYLE, 1, INDIC_PLAIN);	// selected converted +		data->scintilla->WndProc (SCI_INDICSETFORE,  1, 0x808080); +		data->scintilla->WndProc (SCI_INDICSETSTYLE, 2, INDIC_PLAIN);	// selected raw +		data->scintilla->WndProc (SCI_INDICSETFORE,  2, 0x0000FF); +		// stop Undo +		data->scintilla->WndProc (SCI_BEGINUNDOACTION, 0, 0); +	} +} + +static void stopInput (TSMData* data, int pos) +{ +	if (data->active && 0 == data->inhibitRecursion) +	{ +		// First fix the doc - this may cause more messages +		// but do not fall into recursion +		data->inhibitRecursion++; +		::FixTSMDocument (data->docid); +		data->inhibitRecursion--; +		data->active = false; +		 +		// Remove indicator styles +		data->scintilla->WndProc (SCI_STARTSTYLING, data->selStart, INDICS_MASK); +		data->scintilla->WndProc (SCI_SETSTYLING, pos - data->selStart, 0); +		// Restore old indicator styles and colors +		data->scintilla->WndProc (SCI_SETSTYLEBITS, data->styleMask, 0); +		for (int i = 0; i < 2; i++) +		{ +			data->scintilla->WndProc (SCI_INDICSETSTYLE, i, data->indicStyle [i]); +			data->scintilla->WndProc (SCI_INDICSETFORE, i, data->indicColor [i]); +		} + +		// remove selection and re-allow selections to display +		data->scintilla->WndProc (SCI_SETSEL, pos, pos); +		data->scintilla->WndProc (SCI_TARGETFROMSELECTION, 0, 0); +		data->scintilla->WndProc (SCI_HIDESELECTION, 0, 0); + +		// move the caret behind the current area +		data->scintilla->WndProc (SCI_SETCURRENTPOS, pos, 0); +		// re-enable Undo +		data->scintilla->WndProc (SCI_ENDUNDOACTION, 0, 0); +		// re-colorize +		int32_t startLine = data->scintilla->WndProc (SCI_LINEFROMPOSITION, data->selStart, 0); +		int32_t startPos  = data->scintilla->WndProc (SCI_POSITIONFROMLINE, startLine, 0); +		int32_t endLine   = data->scintilla->WndProc (SCI_LINEFROMPOSITION, pos, 0); +		if (endLine == startLine) +			endLine++; +		int32_t endPos    = data->scintilla->WndProc (SCI_POSITIONFROMLINE, endLine, 0); +		 +		data->scintilla->WndProc (SCI_COLOURISE, startPos, endPos); +	} +} + +void ExtInput::stop (HIViewRef viewRef) +{ +	TSMData* data = getTSMData (viewRef); +	if (NULL != data) +		stopInput (data, data->selStart + data->selLength); +} + +static char* UTF16toUTF8 (const UniChar* buf, int len, int& utf8len) +{ +	CFStringRef str = CFStringCreateWithCharactersNoCopy (NULL, buf, (UInt32) len, kCFAllocatorNull); +	CFRange range = { 0, len }; +	CFIndex bufLen; +	CFStringGetBytes (str, range, kCFStringEncodingUTF8, '?', false, NULL, 0, &bufLen); +	UInt8* utf8buf = new UInt8 [bufLen+1]; +	CFStringGetBytes (str, range, kCFStringEncodingUTF8, '?', false, utf8buf, bufLen, NULL); +	utf8buf [bufLen] = 0; +	CFRelease (str); +	utf8len = (int) bufLen; +	return (char*) utf8buf; +} + +static int UCS2Length (const char* buf, int len) +{ +	int n = 0; +	while (len > 0) +	{ +		int bytes = 0; +		char ch = *buf; +		while (ch & 0x80) +			bytes++, ch <<= 1; +		len -= bytes; +		n += bytes; +	} +	return n; +} + +static int UTF8Length (const UniChar* buf, int len) +{ +	int n = 0; +	while (len > 0) +	{ +		UInt32 uch = *buf++; +		len--; +		if (uch >= 0xD800 && uch <= 0xDBFF && len > 0) +		{ +			UInt32 uch2 = *buf; +			if (uch2 >= 0xDC00 && uch2 <= 0xDFFF) +			{ +				buf++; +				len--; +				uch = ((uch & 0x3FF) << 10) + (uch2 & 0x3FF); +			} +		} +		n++; +		if (uch > 0x7F) +			n++; +		if (uch > 0x7FF) +			n++; +		if (uch > 0xFFFF) +			n++; +		if (uch > 0x1FFFFF) +			n++; +		if (uch > 0x3FFFFFF) +			n++; +	} +	return n; +} + +static OSStatus handleTSMUpdateActiveInputArea (TSMData* data, EventRef inEvent) +{ +	UInt32				fixLength; +	int					caretPos = -1; +	UInt32				actualSize; +	::TextRangeArray*	hiliteRanges = NULL; +	char*				hiliteBuffer = NULL; +	bool				done; +	 +	// extract the text +	UniChar* buffer = NULL; +	UniChar temp [128]; +	UniChar* text = temp; +	 +	// get the fix length (in bytes) +	OSStatus err = ::GetEventParameter (inEvent, kEventParamTextInputSendFixLen, +								   typeLongInteger, NULL, sizeof (long), NULL, &fixLength); +	// need the size (in bytes) +	if (noErr == err) +		err = ::GetEventParameter (inEvent, kEventParamTextInputSendText, +										typeUnicodeText, NULL, 256, &actualSize, temp); +										 +	// then allocate and fetch if necessary +	UInt32 textLength = actualSize / sizeof (UniChar); +	fixLength /= sizeof (UniChar); + +	if (noErr == err) +	{ +		// this indicates that we are completely done +		done = (fixLength == textLength || fixLength < 0); +		if (textLength >= 128) +		{ +			buffer = text = new UniChar [textLength]; +			err = ::GetEventParameter (inEvent, kEventParamTextInputSendText, +									   typeUnicodeText, NULL, actualSize, NULL, (void*) text); +		} +		 +		// set the text now, but convert it to UTF-8 first +		int utf8len; +		char* utf8 = UTF16toUTF8 (text, textLength, utf8len); +		data->scintilla->WndProc (SCI_SETTARGETSTART, data->selStart, 0); +		data->scintilla->WndProc (SCI_SETTARGETEND, data->selStart + data->selLength, 0); +		data->scintilla->WndProc (SCI_HIDESELECTION, 1, 0); +		data->scintilla->WndProc (SCI_REPLACETARGET, utf8len, (sptr_t) utf8); +		data->selLength = utf8len; +		delete [] utf8; +	} +	 +	// attempt to extract the array of hilite ranges +	if (noErr == err) +	{ +		::TextRangeArray tempTextRangeArray; +		OSStatus tempErr = ::GetEventParameter (inEvent, kEventParamTextInputSendHiliteRng, +											 typeTextRangeArray, NULL, sizeof (::TextRangeArray), &actualSize, &tempTextRangeArray); +		if (noErr == tempErr) +		{ +			// allocate memory and get the stuff! +			hiliteBuffer = new char [actualSize]; +			hiliteRanges = (::TextRangeArray*) hiliteBuffer; +			err = ::GetEventParameter (inEvent, kEventParamTextInputSendHiliteRng, +									   typeTextRangeArray, NULL, actualSize, NULL, hiliteRanges); +			if (noErr != err) +			{ +				delete [] hiliteBuffer; +				hiliteBuffer = NULL; +				hiliteRanges = NULL; +			} +		} +	} +#if LOG_TSM +	fprintf (logFile, "kEventTextInputUpdateActiveInputArea:\n" +					 "  TextLength = %ld\n" +					 "  FixLength = %ld\n", +					 (long) textLength, (long) fixLength); +	fflush (logFile); +#endif + +	if (NULL != hiliteRanges) +	{ +		for (int i = 0; i < hiliteRanges->fNumOfRanges; i++) +		{ +#if LOG_TSM +			fprintf (logFile, "  Range #%d: %ld-%ld (%d)\n", +								i+1, +								hiliteRanges->fRange[i].fStart, +								hiliteRanges->fRange[i].fEnd, +								hiliteRanges->fRange[i].fHiliteStyle); +			fflush (logFile); +#endif +			// start and end of range, zero based +			long bgn = long (hiliteRanges->fRange[i].fStart) / sizeof (UniChar); +			long end = long (hiliteRanges->fRange[i].fEnd) / sizeof (UniChar); +			if (bgn >= 0 && end >= 0) +			{ +				// move the caret if this is requested +				if (hiliteRanges->fRange[i].fHiliteStyle == kTSMHiliteCaretPosition) +					caretPos = bgn; +				else +				{ +					// determine which style to use +					int style; +					switch (hiliteRanges->fRange[i].fHiliteStyle) +					{ +						case kTSMHiliteRawText:					style = INDIC0_MASK; break; +						case kTSMHiliteSelectedRawText:			style = INDIC0_MASK; break; +						case kTSMHiliteConvertedText:			style = INDIC1_MASK; break; +						case kTSMHiliteSelectedConvertedText:	style = INDIC2_MASK; break; +						default:								style = INDIC0_MASK; +					} +					// bgn and end are Unicode offsets from the starting pos +					// use the text buffer to determine the UTF-8 offsets +					long utf8bgn  = data->selStart + UTF8Length (text, bgn); +					long utf8size = UTF8Length (text + bgn, end - bgn); +					// set indicators +					int oldEnd = data->scintilla->WndProc (SCI_GETENDSTYLED, 0, 0); +					data->scintilla->WndProc (SCI_STARTSTYLING, utf8bgn, INDICS_MASK); +					data->scintilla->WndProc (SCI_SETSTYLING, utf8size, style & ~1); +					data->scintilla->WndProc (SCI_STARTSTYLING, oldEnd, 31); +				} +			} +		} +	} +	if (noErr == err) +	{ +		// if the fixed length is == to the new text, we are done +		if (done) +			stopInput (data, data->selStart + UTF8Length (text, textLength)); +		else if (caretPos >= 0) +		{ +			data->selCur = data->selStart + UTF8Length (text, caretPos); +			data->scintilla->WndProc (SCI_SETCURRENTPOS, data->selCur, 0); +		} +	} +	 +	delete [] hiliteBuffer; +	delete [] buffer; +	return err; +} + +struct MacPoint { +  short               v; +  short               h; +}; + +static OSErr handleTSMOffset2Pos (TSMData* data, EventRef inEvent) +{ +	long offset; +	 +	// get the offfset to convert +	OSStatus err = ::GetEventParameter (inEvent, kEventParamTextInputSendTextOffset, +										typeLongInteger, NULL, sizeof (long), NULL, &offset); +	if (noErr == err) +	{ +		// where is the caret now? +		HIPoint where; + +		int line = (int) data->scintilla->WndProc (SCI_LINEFROMPOSITION, data->selCur, 0); +		where.x = data->scintilla->WndProc (SCI_POINTXFROMPOSITION, 0, data->selCur); +		where.y = data->scintilla->WndProc (SCI_POINTYFROMPOSITION, 0, data->selCur) +				+ data->scintilla->WndProc (SCI_TEXTHEIGHT, line, 0); +		// convert to window coords +		::HIViewConvertPoint (&where, data->view, NULL); +		// convert to screen coords +		Rect global; +		GetWindowBounds (HIViewGetWindow (data->view), kWindowStructureRgn, &global); +		MacPoint pt; +		pt.h = (short) where.x + global.left; +		pt.v = (short) where.y + global.top; + +		// set the result +		err = ::SetEventParameter (inEvent, kEventParamTextInputReplyPoint, typeQDPoint, sizeof (MacPoint), &pt); +#if LOG_TSM +		fprintf (logFile, "kEventTextInputOffsetToPos:\n" +						 "  Offset: %ld\n" +						 "  Pos: %ld:%ld (orig = %ld:%ld)\n", offset,  +						 (long) pt.h, (long) pt.v, +						 (long) where.x, (long) where.y); +		fflush (logFile); +#endif +	} +	return err; +} + +static OSErr handleTSMPos2Offset (TSMData* data, EventRef inEvent) +{ +	MacPoint		qdPosition; +	long			offset; +	short			regionClass; +	 +	// retrieve the global point to convert +	OSStatus err = ::GetEventParameter (inEvent, kEventParamTextInputSendCurrentPoint, +										typeQDPoint, NULL, sizeof (MacPoint), NULL, &qdPosition); +	if (noErr == err) +	{ +#if LOG_TSM +		fprintf (logFile, "kEventTextInputPosToOffset:\n" +						 "  Pos: %ld:%ld\n", (long) qdPosition.v, (long) qdPosition.h); +		fflush (logFile); +#endif +		// convert to local coordinates +		HIRect rect; +		rect.origin.x = qdPosition.h; +		rect.origin.y = qdPosition.v; +		rect.size.width = +		rect.size.height = 1; +		::HIViewConvertRect (&rect, NULL, data->view); + +		// we always report the position to be within the composition; +		// coords inside the same pane are clipped to the composition, +		// and if the position is outside, then we deactivate this instance +		// this leaves the edit open and active so we can edit multiple panes +		regionClass = kTSMInsideOfActiveInputArea; +		 +		// compute the offset (relative value) +		offset = data->scintilla->WndProc (SCI_POSITIONFROMPOINTCLOSE, (uptr_t) rect.origin.x, (sptr_t) rect.origin.y); +		if (offset >= 0) +		{ +			// convert to a UTF-16 offset (Brute Force) +			char* buf = getTextPortion (data, 0, offset); +			offset = UCS2Length (buf, offset); +			delete [] buf; +			 +#if LOG_TSM +			fprintf (logFile, "  Offset: %ld (class %ld)\n", offset, (long) regionClass); +			fflush (logFile); +#endif +			// store the offset +			err = ::SetEventParameter (inEvent, kEventParamTextInputReplyTextOffset, typeLongInteger, sizeof (long), &offset); +			if (noErr == err) +				err = ::SetEventParameter (inEvent, kEventParamTextInputReplyRegionClass, typeShortInteger, sizeof (short), ®ionClass); +		} +		else +		{ +			// not this pane! +			err = eventNotHandledErr; +			ExtInput::activate (data->view, false); +		} +			 +	} +	return err;	 +} + +static OSErr handleTSMGetText (TSMData* data, EventRef inEvent) +{ +	char* buf = getTextPortion (data, data->selStart, data->selLength); +		 +#if LOG_TSM +		fprintf (logFile, "kEventTextInputGetSelectedText:\n" +						  "  Text: \"%s\"\n", buf); +		fflush (logFile); +#endif +	OSStatus status = ::SetEventParameter (inEvent, kEventParamTextInputReplyText, typeUTF8Text, data->selLength, buf); +	delete [] buf; +	return status; +}		 + +static pascal OSStatus doHandleTSM (EventHandlerCallRef, EventRef inEvent, void* userData) +{ +	TSMData* data = (TSMData*) userData; +	 +	OSStatus err = eventNotHandledErr; + +	switch (::GetEventKind (inEvent)) +	{ +		case kEventTextInputUpdateActiveInputArea: +			// Make sure that input has been started +			startInput (data); +			err = handleTSMUpdateActiveInputArea (data, inEvent); +			break; +//		case kEventTextInputUnicodeForKeyEvent: +//			err = handleTSMUnicodeInput (inEvent); +//			break; +		case kEventTextInputOffsetToPos: +			err = handleTSMOffset2Pos (data, inEvent); +			break; +		case kEventTextInputPosToOffset: +			err = handleTSMPos2Offset (data, inEvent); +			break; +		case kEventTextInputGetSelectedText: +			// Make sure that input has been started +			startInput (data, false); +			err = handleTSMGetText (data, inEvent); +			break; +	} +	return err; +} + | 
