I recently upgraded to OS X Lion, so I really wanted to get some use out of Apple’s new InputMethodKit functions and abilities, like submenus, and things like that. Right off the bat I ran into a couple of problems.
Firstly, there is very little actual documentation from Apple, especially for the new features. In fact, most of the new methods aren’t even mentioned in the Framework Reference. They do have a couple of lines of documentation each in their respective header files, (some of them have none), but that’s it. There isn’t even any documentation on the Apple documentation website. Furthermore, there isn’t much discussion going on on the internets as to how to use IMKit.
With that in mind I’m going to go over what I’ve accomplished so far in the hopes that it will help out others who find themselves in this situation. Most specifically, this post is about attempting to attach an IMKCandidates subList that displays the same window type as a regular candidate window (kIMKSingleColumnScrollingCandidatePanel).
I’m going to start off with a list of things that did not work as (I) expected, and finally I’ll go over details, and I’ll post a link to a GitHub repo with a basic skeleton in case anyone else wants to use it. I’d appreciate feedback on ways to improve it as well, since I’m kind of new to Objective-C programming, and have a lot to learn.
Most things did not work as expected (maybe my fault), but I ended up explicitly implementing almost all behavior. To save others trouble I have created a skeleton project that people can use as a starting point. If you see any problems with it, let me know.
What Didn’t Work
I wanted to attach an IMKCandidates child to an existing IMKCandidates object, and I wanted it to show and hide automatically. The documentation for IMKCandidates’ hideChild method says:
@abstract If the current selection has a child IMKCandidates that is being shown hide it.
@discussion Typically a client will not need to call this as IMKCandidates automatically hides and shows children.
That in hand, I assumed it would be easy. It was not. I tried a bunch of approaches that I had thought should work relatively automatically, and didn’t work.
- Attaching an IMKCandidates object with no server
- Attaching one with the same server
- Attaching one with another server to give it an inputController
- Using the [candidates showSublist:subListArray withDelegate:nil] *
* This one mostly worked, but for some reason it would only show the sublist automatically, and not hide it, and left/right keys were not captured, and it doesn’t have numbers to quickly select an option Because of these problems, I was reduced to recreating the wheel and manually implementing showing/hiding of the sublist, capturing arrow key presses, number entry, list movement, and so forth.
The Long of It
What I did was create two IMKCandidate instances. The first was the main instance, initialized with a server, the second with a nil server. Then you attach the child when necessary [candidates attachChild:...], and display it when necessary [candidates showChild]. The first thing you need to do is make sure you IMKInputController gets all the key events first, so that you can properly control events:
[newAttributes setValue:@"YES" forKey:@"IMKCandidatesSendServerKeyEventFirst"];
If you don’t do this, some selectors may get pre-empted by the IMKCandidates object, which is not what you want, since you have to take complete control. The next thing is positioning. When you use [candidates showChild], it will display the candidates window at [0, 0] on your screen.
To make it display next to your existing candidates window you need to use the [candidates candidateFrame] method which gives you the rectangle box frame for the currently selected candidate. Then just calculate and set the information. You want to calculate this right before showing it, in case positioning has changed due to menu movement or something.
NSRect currentFrame = [candidates candidateFrame];
NSPoint windowInsertionPoint = NSMakePoint(NSMaxX(currentFrame), NSMaxY(currentFrame));
The insertion point is an NSPoint, and we make it from the top right corner of the NSRect by getting its maximum X and Y values.