自由软件 BlueGroup
 站内搜索:  
您的位置:首页> 中文系统

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;
                    }
    	}
    




Copyright © 2000 LSLNET.COM Website. All rights reserved. 蓝森林网站版权所有。 E-mail : contact@lslnet.com