作者:庄晓立(liigo)
日期:2011年7月20日
原创链接:http://blog.csdn.net/liigo/article/details/6621104
转载请注明出处:http://blog.csdn.net/liigo
最新0.7.5版本的CEGUI是直接支持中文输入的,只要正常设置中文字体就行了。不过也仅限于“支持”、“够用”而已,有不足之处:在窗口模式(非全屏)下,汉字输入提示框显示在窗口左下角,不支持光标跟随功能,使用不方便;在全屏模式下,更是连汉字输入提示框都不显示了,只能盲打输入汉字。本文主要解决CEGUI中文汉字输入法“光标跟随”功能中最核心的地方,获取CEGUI编辑框(Editbox, MultiLineEditbox)中当前光标(Caret)的屏幕坐标。
目前CEGUI0.7.5自身并不支持返回光标位置。两年前有人在CEGUI官方论坛提问如何得到编辑框光标位置,CEGUI老大CrazyEddie亲自回复给出了详细的办法,不过他的办法只是针对“多行编辑框MultiLineEditbox”(对单行编辑框Editbox无效),而且没有考虑多行编辑框有纵向滚动条的情况。
我(liigo)的解决方案是在研究和修改CEGUI源代码之后得到的。
先说多行编辑框MultiLineEditbox的情况吧,相对简单点。思路就是CrazyEddie老大的思路:通过光标序号索引(MultiLineEditbox::getCaratIndex())得到光标所在行号(MultiLineEditbox::getLineNumberFromIndex),然后想办法得到最顶行的行号,两行号之差乘以行高(getFont()->getLineSpacing()),得纵向高度值Y;根据各文本行信息(MultiLineEditbox::getFormattedLines()),得到光标所在行中光标之前的文本,计算其横向宽度值X;多行编辑框左上角的屏幕坐标很容易取得,加上前面的X、Y值,就得到了光标的屏幕坐标。其中,“取最顶行行号”的办法是我(liigo)从CEGUI源码里翻出来的代码:
//取第一个可见行索引, 代码来自 FalagardMultiLineEditbox::cacheTextLines() int topLineNo = static_cast<size_t>(pMultiLine->getVertScrollbar()->getScrollPosition() / pMultiLine->getFont()->getLineSpacing());下面是多行编辑框(CEGUI::MultiLineEditbox)取光标位置的核心代码: if(activeWindow->testClassName("MultiLineEditbox")) { CEGUI::MultiLineEditbox* pMultiLine = static_cast<CEGUI::MultiLineEditbox*>(activeWindow); CEGUI::Rect textRenderArea = CEGUI::CoordConverter::windowToScreen(*pMultiLine, pMultiLine->getTextRenderArea()); x = textRenderArea.d_left; y = textRenderArea.d_top; int lineNo = pMultiLine->getLineNumberFromIndex(pMultiLine->getCaratIndex()); int topLineNo = static_cast<size_t>(pMultiLine->getVertScrollbar()->getScrollPosition() / pMultiLine->getFont()->getLineSpacing()); //取第一个可见行索引, 代码来自 FalagardMultiLineEditbox::cacheTextLines() y += (lineNo - topLineNo) * (pMultiLine->getFont()->getLineSpacing()); const CEGUI::MultiLineEditbox::LineList& lineList = pMultiLine->getFormattedLines(); CEGUI::String lineBeforeCaret = pMultiLine->getTextVisual().substr(lineList[lineNo].d_startIdx, pMultiLine->getCaratIndex() - lineList[lineNo].d_startIdx); x += pMultiLine->getFont()->getTextExtent(lineBeforeCaret); }
由于多行编辑框的滚动条是以像素为单位滚动,而不是以行为单位滚动,所以上面的代码计算出的纵坐标y会有一些偏差,但最多偏差一行文本的高度,问题应该不大。如果要想完美解决,恐怕还得修改CEGUI源码才行。
下面是单行编辑框(CEGUI::Editbox)取光标位置的核心代码:
if(activeWindow->testClassName("Editbox")) { CEGUI::Editbox * pEditbox = static_cast<CEGUI::Editbox*>(activeWindow); CEGUI::String textBeforeCaret; if(pEditbox->isTextMasked()) textBeforeCaret = CEGUI::String(pEditbox->getCaratIndex(), pEditbox->getMaskCodePoint()); else textBeforeCaret = pEditbox->getTextVisual().substr(0, pEditbox->getCaratIndex()); CEGUI::Rect screenArea = CEGUI::CoordConverter::windowToScreen(*pEditbox->getParent(), pEditbox->getArea()); //下面这个判断用于临时绕过CoordConverter::windowToScreen的一个BUG //详见: http://www.cegui.org.uk/phpBB2/viewtopic.php?f=10&t=5728 if(pEditbox->getParent()->testClassName("FrameWindow")) { CEGUI::FrameWindow* pFrame = (CEGUI::FrameWindow*) pEditbox->getParent(); screenArea.offset(CEGUI::Point(8, 35)); //横向加上FrameWindow边框宽度,纵向加上FrameWindow标题栏的高度 } x = screenArea.d_left; x += pEditbox->getFont()->getTextExtent(textBeforeCaret); x += pEditbox->getTextOffset(); //需修改CEGUI源码 y = screenArea.d_top; y += (screenArea.getHeight() - pEditbox->getFont()->getFontHeight()) / 2; //文字垂直居中显示 }需要修改CEGUI源码之处,说明如下:
/* //Need modify CEGUI's source code: //CEGUIEditbox.h, class EditboxWindowRenderer, add a virtua method, //and overrides it in FalEditbox.h, class FalagardEditbox: // float getTextOffset() const { return d_lastTextOffset; } //and add the same name method to class CEGUI::Editbox, just like Editbox::getTextIndexFromPosition() 需修改CEGUI源码 文件CEGUIEditbox.h里面: class EditboxWindowRenderer加一个虚函数: virtual float getTextOffset() const = 0; class Editbox加一个成员函数定义: float getTextOffset() const; 文件FalEditbox.h里面: class FalagardEditbox加一个函数: virtual float getTextOffset() const override { return d_lastTextOffset; } 文件CEGUIEditbox.cpp: float Editbox::getTextOffset() const { if (d_windowRenderer != 0) { EditboxWindowRenderer* wr = (EditboxWindowRenderer*)d_windowRenderer; return wr->getTextOffset(); } else { CEGUI_THROW(InvalidRequestException("Editbox::getTextOffset: " "This function must be implemented by the window renderer")); } } */前面代码中出现的变量 activeWindow,是getGUISheet()->getActiveChild()返回的当前激活的控件。但是还要考虑特殊情况,在鼠标点击多行编辑框(MultiEditbox)纵向滚动条的情况下,当前拥有焦点(focus)的仍是多行编辑框,光标(caret)也仍在多行编辑框内,可是,但是,可但是,activeWindow已经不是多行编辑框了,它成了滚动条控件或滚动条的子控件(视鼠标点击位置而定)。所以我们的代码要处理这种情况: CEGUI::Window* activeWindow = CEGUI::System::getSingleton().getGUISheet()->getActiveChild(); if(activeWindow == NULL) return FALSE; //处理activeWindow是多行编辑框的滚动条控件,或滚动条子控件的情况 if(activeWindow->testClassName("Scrollbar")) { activeWindow = activeWindow->getParent(); }else { //Thumb or PushButton of Scrollbar CEGUI::Window* parentWindow = activeWindow->getParent(); if(parentWindow && parentWindow->testClassName("Scrollbar")) activeWindow = parentWindow->getParent(); } if(activeWindow == NULL) return FALSE;
好了,有了光标的屏幕坐标,再把编辑框内文字的高度(getFont()->getFontHeight())考虑进去,定位输入法提示框是没什么难度了。至于输入法提示框内要显示的内容(输入码、候选字词),则超出了本文范畴,那属于IME编程领域,网上有很多公开的资料可供参考。
还有其它一些额外内容,我(liigo)也简单提一提,只盼对读者有用。
//注册"窗口创建"事件处理函数 CEGUI::WindowManager::getSingleton().subscribeEvent(CEGUI::WindowManager::EventWindowCreated, _internal_OnCreateWindow); //处理编辑框创建事件, 注册其"焦点得失"事件处理函数 static bool _internal_OnCreateWindow(const CEGUI::EventArgs& args) { const CEGUI::WindowEventArgs& winEventArgs = static_cast<const CEGUI::WindowEventArgs&>(args); //对于编辑框(Editbox,MultiLineEditbox),注册焦点得失事件处理函数,以便关联输入法 if(winEventArgs.window->testClassName("Editbox") || winEventArgs.window->testClassName("MultiLineEditbox")) { winEventArgs.window->subscribeEvent(CEGUI::Window::EventActivated, _internal_OnEditboxSetFocus); winEventArgs.window->subscribeEvent(CEGUI::Window::EventDeactivated, _internal_OnEditboxKillFocus); } return true; }全文完。谢谢。liigo 2011/7/20 夜,于大连。