Generating Candidates from an Application

Kirby left a comment to my post on candidates asking when a text service should create an ITfCandidateList.

The answer is that when the text service wants to show candidates (via a preserved key or other mechanism), it should show its modal UI, and quite possibly may wish to push a new context so that the modal UI can be keyboard enabled (the sample does this). 

The thing to note here is that ITfCandidateList is not involved here. 

So when does the text service use ITfCandidateList?  When the application wants to show candidates.

How does this work?  Well, it's pretty complicated, but I'll try to outline the steps here.  (Get ready, it's going to get a little wild...)

The big blocks

1)  Application gets ITfFnReconversion interface.

2)  Application gets target range (ITextStoreACPServices::CreateRange comes in very handy here).

3)  Application calls ITfFnReconversion::QueryRange to transform the target range.

4)  Application calls ITfFnReconversion::GetReconversion to get the list of candidates.

5)  Application displays candidate list (e.g., in context menu, dialog, what have you).

6)  Application calls ITfCandidateList::SetResult() when the selection is selected.

7)  Application releases candidate list, reconversion interface, and does other cleanup.

Alternatively, if the application wants to let the text service manage the UI, it can call ITfFnReconversion::Reconvert at step 4 and step out of the way.   (Note that not all text services implement this, as this function must not wait for the UI to be dismissed before returning.)

An excellent way to test this is with Microsoft Word.  Word supports reconversion, and will show the candidates in the context menu when you right-click on a word.

Getting ITfFnReconversion

This is a two-step process.  First, you get the system function provider, and then you get the reconversion pointer (error checking is omitted):

ITfFunctionProvider* ipProvider;

hr = ITfThreadMgr::GetFunctionProvider(GUID_SYSTEM_FUNCTIONPROVIDER, &ipProvider);

IUnknown* ipConv;

hr = ipProvider->GetFunction(GUID_NULL, IID_ITfFnReconversion, &ipConv);

ITfFnReconversion *ipConversion;

hr = ipConv->QueryInterface(IID_ITfFnReconversion, &ipConversion);

What should QueryRange do?

ITfFnReconversion::QueryRange needs to modify the range to cover the entire range of text contained by the conversion token.  An example is useful here, as that last sentence didn't even make sense to me.  The Speech text service does correction by words, and all words must be part of a single recognition. 

Therefore, the Speech text services's implementation of QueryRange alters the range of text to contain entire words that are part of a single recognition. 

If the range starts in the middle of a word with recognition data, QueryRange shifts the start of the range to the start of a word.  If the range starts in text that doesn't have recognition data associated with it, it moves the start point to a point where it does have recognition data, or to the end of the initial range.  If it runs off the end, it sets pfConvertable to FALSE, and returns NULL.

If it does find some recognition data for the start of the range, it adjusts the end of the range in the same manner.

But wait, you say.  How can QueryRange do all that when it doesn't have an edit cookie?  Well, it gets one in the usual manner - it (say it with me) schedules an edit session to do all that work.  OK, you say, how can I get my data back from an asynchronous edit session?  Well, you're in luck - you're guaranteed to be able to schedule a synchronous edit session from ITfFnReconversion::QueryRange

What should GetReconversion do?

ITfFnReconversion::GetReconversion needs to compute the candidates from a target range (that should be aligned with a conversion token).  Like QueryRange, most of the work will need to be done from an edit session, and also like QueryRange, you are guaranteed to be able to schedule a synchronous edit session from within GetReconversion.  How you go about computing the candidates is discussed in my next post.