 |
|
Chinput的光标跟随模式
作 者: 于明俭
Root风格的基本特点是输入窗口固定在屏幕位置, 对汉字输入
来说, 它一般包含了输入区和输入选择区. 其缺点是切换输入
窗口时, 常常需要把输入窗口拖到合适的位置再开始输入.
正因为如此, 人们使用根窗口风格的输入条时常常怀念Windows
下的光标跟随模式.
在XIM中定义了四种风格(Styles), 包括RootWindow, OverTheSpot,
OnTheSpot, OffTheSpot. 其中根窗口模式是位置固定在屏幕上的
独立的窗口输入模式, 窗口内一般包括输入区和选择区; OverTheSpot
模式是覆盖在当前输入位置的独立小窗口, 由于窗口较小, 通常只能
容纳输入区, 选择区须有另外的窗口; OnTheSpot模式没有自己的
选择窗口, 是由应用软件来处理汉字的编辑(输入)和选择的, 它适合
于无选择或选择条目较少的情况; OffTheSpot模式是在应用软件的
窗口下方开辟的条内显示输入区和选择区的(如XEmacs下的输入条).
Chinput 的光标跟随模式就是对OverTheSpot的扩展, 它的输入区
不是覆盖在当前的输入位置, 而是有适当的偏移, 使输入时便于观察
光标附近的输入文本.
- 光标跟随窗口的实现
Chinput 的对Client提供的风格是许多的, 主要是为了迎合
多种Client, 实际上, 它所起作用的仍然是
XIMPreeditPosition | XIMStatusNothing
- 窗口布局
为了支持光标跟随模式, Chinput重新设计了输入布局. 新的
布局分为两个部分, 输入条和选择条. 输入条是跟随光标移动
的, 选择条是附属于输入条的. 如图,
- 输入条位置的确定:
输入条位置的确定是在 StoreIC (IMdkit) 中
for (i = 0; i < (int)call_data->preedit_attr_num; i++, pre_attr++) {
if (Is (XNSpotLocation, pre_attr)){
rec->pre_attr.spot_location = *(XPoint*)pre_attr->value;
if(rec == current_focus_ic) HZprocLocation(rec);
}
....
}
设置位置.
if(ic->focus_win) {
//try focus win first
XTranslateCoordinates(display, ic->focus_win,
DefaultRootWindow(display),
ic->pre_attr.spot_location.x + HZSERVER_POS_XOFFSET,
ic->pre_attr.spot_location.y + HZSERVER_POS_YOFFSET,
&x0, &y0, &child);
} else if(ic->client_win) {
XTranslateCoordinates(display, ic->client_win,
DefaultRootWindow(display),
ic->pre_attr.spot_location.x + HZSERVER_POS_XOFFSET,
ic->pre_attr.spot_location.y + HZSERVER_POS_YOFFSET,
&x0, &y0, &child);
} else {
return;
}
//调整输入条位置使不超出屏幕.
...
XMoveWindow(display, window, curx1, cury1);
XRaiseWindow(display, window);
- 选择条的状态和属性:
选择条附属于输入条, 只有有选择项时才弹出, 保持了屏幕的整洁.
它的位置是根据输入条位置确定的, 并且受屏幕限制.
- 状态显示和控制
汉字输入中需要全角/半角切换和中/英文标点符号切换. Chinput 采用了
独立的软件来控制它们, 并且显示当前状态. 控制条和输入条之间的通讯
使用了ClientMessage.
- 在Server中捕获消息:
while (1) {
XNextEvent(display, &report);
if (XFilterEvent(&report, None) == True) {
//fprintf(stderr, "window %ld\n",report.xany.window);
continue;
}
switch (report.type) {
case ClientMessage:
//处理ClientMessage
break;
...
}
}
- 在控制条中捕获消息:
XSetSelectionOwner(gdk_display, hz_cprotocol_atom,
GDK_WINDOW_XWINDOW(root_window->window) , CurrentTime);
//process config event
gdk_add_client_message_filter(hz_config_atom, process_client, NULL);
gtk_main();
- 与旧模式兼容问题
Chinput 仍然保留了旧的显示模式, 并且可以热键切换. 当采用旧的模式
时, 位置设置不再起作用.
Chinput 不但保持了模式的兼容, 而且保持了协议的兼容. Chinput原来使用的
是自定义协议, 数据传输和配置采用ClientMessage. 在新版本中, 仍然可以同时
使用两种协议, 并且XIM协议优先.
- GB/Big5兼容性问题
Chinput 同时包容了GB和Big5两种输入, 即同时支持两种locale, 在输入
过程中动态切换. 所以Chinput可以在zh_CN或zh_TW两种locale下启动,
起动后同时支持两种编码.
xims = IMOpenIM(display,
IMModifiers, "Xi18n", //X11R6 protocol
IMServerWindow, window, //input window
IMServerName, imname, //XIM server name
IMLocale, "zh_CN,zh_TW", //XIM server locale
IMServerTransport, transport, //Comm. protocol
IMInputStyles, input_styles, //faked styles
NULL);
|---GB 输入方法 -------------------- GB Client
| \_____________g2b__________ /
Chinput--+ X
| /-------------b2g---------/ \
|---Big5 输入方法 ------------------- Big5 Client
考虑到大陆和台湾输入方法的习惯不同, Chinput在GB locale下启动时
只载入大陆的输入方法, 这时如果需要输入Big5汉字, 需要在Big5 locale
下启动应用软件, 输入的汉字自动转变成Big5编码(基于hc3的对照表).
这种自动识别的模式的缺点是对GB模式启动的应用程序不能输入Big5,
反之亦然. 比如启动了简体中文的netscape, 当浏览Big5编码的页面时
想输入繁体中文, 自动识别方式是不行的. 所以新版的Chinput增加了
输出编码的锁定功能, 一旦锁定了某种输出编码, 则无论输入服务器
当前的编码是GB还是BIG5, 都输出成锁定的编码.
flag_encoding = FindEnc(call_data);//client encoding
if((flag_encoding == BIG5 &&
server_encoding == GB &&
flag_lock == NONE) ||
(flag_encoding == BIG5 &&
server_encoding == BIG5 &&
flag_lock == GB)){
setlocale(LC_CTYPE, "zh_TW.Big5");
// GB->Big5 转码
} else
if((flag_encoding == GB &&
server_encoding == BIG5 &&
flag_lock == NONE) ||
(flag_encoding == GB &&
server_encoding == GB &&
flag_lock == BIG5)){
// Big5->GB 转码
setlocale(LC_CTYPE, "zh_CN.GBK");
}
XmbTextListToTextProperty(dpy, clist, 1, XCompoundTextStyle, &tp);
- 主动输入问题
多谢 T.H.Hsieh 的帮助, 现在 Chinput 也可以采用XIM下的浏览输入. Chinput
可以在浏览所有汉字的同时, 主动输入到聚焦窗口中.
void ForwardString(char *text)
{
IMCommitStruct cms;
XTextProperty tp;
Display *display = this_xims->core.display;
char **list_return;
int count_return;
flag_encoding = FindEncByID(last_icid);
text[2] = '\0';
if(flag_encoding == GB)
setlocale(LC_CTYPE, "zh_CN.GBK");
else
setlocale(LC_CTYPE, "zh_TW.Big5");
XmbTextListToTextProperty(display, (char **)&text, 1,
XCompoundTextStyle, &tp);
memset(&cms, 0, sizeof(cms));
cms.major_code = XIM_COMMIT;
cms.icid = last_icid;
cms.connect_id = last_connectid;
cms.flag = XimLookupChars;
cms.commit_string = (char *)tp.value;
IMCommitString(this_xims, (XPointer)&cms);
XFree(tp.value);
}
主动输入可以用于手写输入和语音输入.
- 无边界窗口的移动
由于无边界窗口不受窗口管理器的管理, 所以窗口的移动必须由
应用软件解决. 移动的方法是在程序的事件循环中截取鼠标事件,
作相应的处理.
//劫获鼠标事件后
...
//用XQueryPointer获得鼠标指针的位置.
XQueryPointer(display, parent,
&r, &w, &press_x, &press_y,
&win_x, &win_y, &mask);
XChangeActivePointerGrab(display,
PointerMotionMask | ButtonMotionMask | ButtonReleaseMask |
OwnerGrabButtonMask, hand_cursor, CurrentTime);
XGrabServer(display);
//进入子循环
while(1){
XNextEvent( display, &report);
switch(report.type) {
case ButtonRelease:
if(report.xbutton.button == Button1){
//真正移动窗口
...
//释放display
XUngrabServer(display);
return;
}
break;
case MotionNotify:
//画出虚拟窗口
...
break;
default:
break;
}
}
|
|
 |
|
|