Developing IME Aware Applications
Maybe you don't care about the development of an input method, but you probably need to know a bit when implementing BView-derived classes. Your own class needs to deal with internationalized text, such as a word editor or a web page etc. Although the BTextView class would do most of the work for you, BTextView is a huge class, sometime you need to write a custom and flexible class for other purposes.
We'll explain that in two parts. Part 1 will show you how to write an input aware application, then we'll see how to write an input method at part 2.
Part 1: Input method aware derived class from BView
The tutorial is based on a class I wrote a long time ago, the contents of its header file is shown below:
#ifndef __SINPUT_AWARE_STRING_VIEW_H__ #define __SINPUT_AWARE_STRING_VIEW_H__ #include <SupportDefs.h> #include <Messenger.h> #include <Input.h> #include <besavager/StringView.h> class SInputAwareStringView : public SStringView { public: SInputAwareStringView(BRect frame, const char *name, uint32 resizeMask = B_FOLLOW_LEFT | B_FOLLOW_TOP, uint32 flags = B_WILL_DRAW | B_FRAME_EVENTS); virtual void MessageReceived(BMessage *message); private: BMessenger msgr_input; bool im_started; void IMStarted(BMessage *message); void IMStopped(); void IMChanged(BMessage *message); void IMLocationRequest(BMessage *message); }; #endif /* __SINPUT_AWARE_STRING_VIEW_H__ */
Section 1.1 The B_INPUT_METHOD_AWARE flag
If BView's flags contains B_INPUT_METHOD_AWARE, it means the B_INPUT_METHOD_EVENT message generated by the active input method will be processed by the MessageReceived() method, otherwise the input server will popup a window to deal with the message then send a message (B_KEY_DOWN) to the application later (This can cause strange issues in newer versions of BeOS, like Dano/Zeta etc.).
So, the derived class should do something like below in its constructor:
SInputAwareStringView::SInputAwareStringView(BRect frame, const char *name, uint32 resizeMask, uint32 flags) : SStringView(frame, name, NULL, NULL, 0, NULL, resizeMask, flags | B_INPUT_METHOD_AWARE), im_started(false) { }
Section 1.2 The B_KEY_DOWN message
The B_KEY_DOWN message is the most important keyboard event. The input server sends this message to the active view of the running application when its flags don't contain B_INPUT_METHOD_AWARE.
The message contains the fields shown below (from the Be Book):
Field | Type code | Description |
"when" | B_INT64_TYPE | Event time, in microseconds since 01/01/70 |
"key" | B_INT32_TYPE | The code for the physical key that was pressed, you can find out how to get the key by the functions by "key" shown below |
"be:key_repeat" | B_INT32_TYPE | The "iteration number" of this key down. |
"modifiers" | B_INT32_TYPE | The modifier keys that were in effect at the time of the event. |
"states" | B_UINT8_TYPE | The state of all keys at the time of the event. |
"byte"[3] | B_INT8_TYPE | The UTF-8 data that's generated, it contains three bytes. |
"bytes" | B_STRING_TYPE | The UTF-8 string that's generated. (The string usually contains a single character, and usually contains words when it was sent by input server as the B_INPUT_METHOD_EVENT handled.) |
"raw_char" | B_INT32_TYPE | Modifier-independent ASCII code for the character. |
A perfect derived class should pay attention to the contents of the field named "bytes", then the field named "byte"[3] if "bytes" doesn't exist, then lastly "raw_char".
Section 1.3 The B_INPUT_METHOD_EVENT message
The input server sends this message to the view when the input method did some work. The message will be ignored if the view's flags doesn't contain B_INPUT_METHOD_AWARE. The paragraphs shown below are quoted from the Be Book.
Each B_INPUT_METHOD_EVENT message contains a be:opcode field (an int32 value) indicating the kind of event:
Value | Description |
B_INPUT_METHOD_STARTED | Indicates that a new input transaction has begun. |
B_INPUT_METHOD_STOPPED | Indicates that the transaction is over. |
B_INPUT_METHOD_CHANGED | Indicates that the state of transaction is changed. |
B_INPUT_METHOD_LOCATION_REQUEST | Indicates that the input method asking for the on-screen location of each character. |
In addition, except B_INPUT_METHOD_STOPPED, the special fields contained in each other kind.
The B_INPUT_METHOD_STARTED contains fields shown below:
Field | Type code | Description |
"be:reply_to" | B_MESSENGER_TYPE | The messenger to communicate with you during the transaction |
The messenger pointed by "be:reply_to" usually for replying the location of characters to input method, you can also use it to send a B_INPUT_METHOD_STOPPED kind message or others to stop the transaction.
The B_INPUT_METHOD_CHANGED contains fields shown below:
Field | Type code | Description |
"be:string" | B_STRING_TYPE | The text the user is currently entering; the receiver will display it at the current insertion point. BTextView also highlights the text in blue to show that it's part of a transitory transaction. |
"be:selection" | B_INT32_TYPE | A pair of B_INT32_TYPE offsets in bytes into the be:string if part of be:string is current selected. BTextView highlights this selection in red instead of drawing it in blue. |
"be:clause_start" | B_INT32_TYPE | Zero or more offsets into the be:string for handling languages (such as Japanese) that separate a sentence or phrase into numerous clauses. An equal number of be:clause_start and be:clause_end pairs delimit these clauses; BTextView separates the blue/red highlighting wherever there is a clause boundary. |
"be:clause_end" | B_INT32_TYPE | Zero or more offsets into be:string; there must be as many be:clause_end entries as there are be:clause_start. |
"be:confirmed" | B_BOOL_TYPE | True when the user has entered and "confirmed" the current string and wishes to end the transaction. BTextView unhighlights the blue/red text and waits for a B_INPUT_METHOD_STOPPED (to close the transaction) or another B_INPUT_METHOD_CHANGED (to start a new transaction immediately). |
When the kind of B_INPUT_METHOD_EVENT is B_INPUT_METHOD_LOCATION_REQUEST, the derived class should reply the message contains the fields shown below to the messenger that pointed by "be:reply_to" in the B_INPUT_METHOD_STARTED kind:
Field | Type code | Description |
"be:opcode" | B_INT32_TYPE | Must set to B_INPUT_METHOD_LOCATION_REQUEST. |
"be:location_reply" | B_POINT_TYPE | The coordinates of each UTF-8 character (there should be one be:location_reply for every character in be:string, and the character maybe contains more than one byte) relative to the display (not your view or your window). |
"be:height_reply" | B_FLOAT_TYPE | The height of each character(maybe contains more than on byte) in be:string. |
Section 1.4 Example
Now, we will start our practice if you are ready for coding.
void SInputAwareStringView::MessageReceived(BMessage *message) { switch(message->what) { case B_INPUT_METHOD_EVENT: // input method event received { int32 op_code; // the kind of message if(message->FindInt32("be:opcode", &op_code) != B_OK) break; switch(op_code) { case B_INPUT_METHOD_STARTED: // prepare for input transaction IMStarted(message); break; case B_INPUT_METHOD_STOPPED: // stop the transaction and clear something IMStopped(); break; case B_INPUT_METHOD_CHANGED: // displaying characters when the string entering changed IMChanged(message); break; case B_INPUT_METHOD_LOCATION_REQUEST: // reply the lcoation of characters IMLocationRequest(message); break; default: // call member function of base class SStringView::MessageReceived(message); } } break; default: // call member function of base class SStringView::MessageReceived(message); } } void SInputAwareStringView::IMStarted(BMessage *message) { // here we store the messenger that we would communicate with the input method during the transaction im_started = (message->FindMessenger("be:reply_to", &msgr_input) == B_OK); } void SInputAwareStringView::IMStopped() { msgr_input = BMessenger(); SetText(NULL); // clear the text had shown im_started = false; } void SInputAwareStringView::IMChanged(BMessage *message) { if(!im_started) return; const char *im_string = NULL; message->FindString("be:string", &im_string); if(im_string == NULL) im_string = ""; int32 start = 0, end = 0; BList color_list; for(int32 i = 0; message->FindInt32("be:clause_start", i, &start) == B_OK && message->FindInt32("be:clause_end", i, &end) == B_OK; i++) { // handle clauses if(end > start) // visible { // set the background of clauses to be blue, the offsets are in bytes. // for example, the "[/]" means the start/end offset of that. // string: T h i s i s a [w o r d] . // offset: 0 1 2 3 4 5 6 7 8 9 10 11 // "be:clause_start" = 7 // "be:clause_end" = 11 s_string_view_color *color = new s_string_view_color; s_rgb_color_setto(&color->color, 0, 0, 0); s_rgb_color_setto(&color->background, 152, 203, 255); color->draw_background = true; color->start_offset = start; color->end_offset = end - 1; color_list.AddItem((void*)color); } } for(int32 i = 0; message->FindInt32("be:selection", i * 2, &start) == B_OK && message->FindInt32("be:selection", i * 2 + 1, &end) == B_OK; i++) { // handle selection if(end > start) // visible { // set the background of clauses to be red, the offsets are in bytes. // for example, the "[/]" means the start/end offset of that. // string: T h i s i s a [w o r d] . // offset: 0 1 2 3 4 5 6 7 8 9 10 11 // "be:selection"[0] = 7 // "be:selection"[1] = 11 s_string_view_color *color = new s_string_view_color; s_rgb_color_setto(&color->color, 0, 0, 0); s_rgb_color_setto(&color->background, 255, 152, 152); color->draw_background = true; color->start_offset = start; color->end_offset = end - 1; color_list.AddItem((void*)color); } } if(!color_list.IsEmpty()) // you don't need to know what it is below { int32 n = color_list.CountItems(); s_string_view_color *colors = new s_string_view_color[n]; for(int32 i = 0; i < n; i++) { s_string_view_color *color = (s_string_view_color*)color_list.ItemAt(i); if(color) { if(colors) { s_rgb_color_setto(&colors[i].color, color->color); s_rgb_color_setto(&colors[i].background, color->background); colors[i].draw_background = color->draw_background; colors[i].start_offset = color->start_offset; colors[i].end_offset = color->end_offset; } delete color; } } color_list.MakeEmpty(); SetText(im_string, colors, n); if(colors) delete[] colors; } else { SetText(im_string); } } void SInputAwareStringView::IMLocationRequest(BMessage *message) { if(!im_started) return; const char *im_string = Text(); if(im_string == NULL || *im_string == 0) return; BMessage reply(B_INPUT_METHOD_EVENT); // the message for reply reply.AddInt32("be:opcode", B_INPUT_METHOD_LOCATION_REQUEST); // must set this BPoint left_top = ConvertToScreen(BPoint(0, 0)); // first we convert the (0, 0) to the coordinate relative to the display int32 offset = 0; uint32 index = 0; while(true) { if(__utf8_char_at(im_string, index, &offset)) // the (index + 1)th UTF-8 character { BRect rect = TextRegion(offset, offset + 1).Frame(); // get the region of text relative to the view if(!rect.IsValid()) break; // convert to the coordinate relative to the display BPoint pt = left_top; pt += rect.LeftTop(); reply.AddPoint("be:location_reply", pt); // the left-top point of each character reply.AddFloat("be:height_reply", rect.Height()); // the height of each character } else { break; } index++; } // send the message to the input method within input server msgr_input.SendMessage(&reply); }