作者:庄晓立(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 夜,于大连。