八、实现编辑框控件

在GUI应用程序中,需要在界面中放入一个编辑框来接收用户键盘的输入。通过前面自绘Button控件,我们知道xlib接口,没有按钮、编辑框这样的概念。我们需要组合xlib提供的接口,处理鼠标、键盘事件来实现高级控件。与QT、GTK这样高级UI库相比,利用QT、GTK进行开发,可以比喻成购买一个成品玩具;而使用xlib进行开发,我们需要利用“七巧板”的每一块,自己创造、组合玩具模型。我们准备利用两篇文章记录在xlib下如何实现编辑框控件。在这篇文章中,我们将记录如何实现编辑框,接收输入法的输入(包含英语、中文以及混合输入的情况),显示输入光标、响应键盘Backspace。在后一篇关于实现编辑框控件文章中我们将介绍如何实现根据鼠标点击位置显示插入光标、实现编辑框文本插入以及插入光标“眨眼”功能。

1.准备

编辑框的绘制相比于Button按钮的绘制要复杂许多。按钮我们只需要响应鼠标事件,而编辑框不仅需要响应鼠标事件,还需要响应键盘事件,键盘事件中还需要区分是向编辑框中输入文本、还是通过控制键(Del、Backspace、Left)实现文本的删除、输入位置的调整。在接下来的示例中我们需要用到的几个重要函数如下:

函数名作用
XOpenIM是xlib库的一个函数,用于打开一个输入法实例,输入法实例用于处理复杂文本输入。它允许应用程序与用户首选的输入法进行交互来接受这些语言的输入。
XSetLocaleModifiers是xlib中的一个函数,用于设置或查询与当前语言环境相关的修饰符。这些修饰符可以影响输入法的行为以及文本的渲染方式。
XCreateICxlib库中的一个函数,用于创建输入上下文,这是与特定输入法实例进行交互的关键步骤,它允许用户通过输入法向应用程序输入文本,尤其处理复杂脚本或非拉丁字符集时很重要。它可以设置输入法的风格,关联客户端窗口,在进行中文输入时,可以设置候选文本显示位置。
setlocale用于设置或查询程序当前的语言环境。
Xutf8LookupStringxlib提供的接口,用于处理键盘事件,并尝试将键盘事件转为UTF-编码的字符串;处理来自非英文键盘的输入,支持多语言文本输入。
XCreatePixmapFromBitmapData从位图中创建一个pixmap。这里创建位图,当某个位被标置为1时,则使用前景色创建一个像素点。

此外在事件循环中,还需要添加以下代码XFilterEvent(&event, None)。添加这段代码的作用是在进行中文输入时,每当我们按下键盘下的一个键时,都会产生一个事件;而在进行中文输入时,一般一个字符需要几个按键才能输入完成。XFilterEvent的作用是如果判断当前事件是输入法的事件,那么这个事件不应该交给窗口处理。不加上这段代码,我们在编辑框中进行输入时,只能输入英文字符。

此外就是XSetLocaleModifiers和setlocale这两个函数。经过测试如果在GUI程序中没有调用XSetLocaleModifiers,那么即使我们把输入法切换到中文状态,得到的键盘事件还是英文字符状态,无法输入中文。GUI程序如果没有调用setlocale这个函数,我们可以打开中文输入法,但输入法得到的中文字符无法产生窗口事件,中文字符串无法提交到窗口中进行处理。

2.编辑框绘制

在进行编辑框绘制之前,我们先定义一个结构体,用于存储编辑框控件的基本信息如坐标、大小、文本、输入法实例、字体等。

struct UIEdit {
    int x;   //控件离窗口左上角水平方向偏移值
    int y;   //控件离窗口左上角垂直方向偏移值
    int width; //控件宽度
    int height; //控件高度
    string text; //控件文本
    XftFont *font; //文本的字体,在前面我们记录过使用XDrawString会有中文乱码,这里使用XftDrawStringUtf8
    XIM     m_xim; //输入法实例
    XIC     m_xic; //输入法上下文
    UIEdit():x{0},
            y{0},
            width{0},
            height{0},
            font{nullptr},
            m_xim{nullptr},
            m_xic{nullptr}
    {

    }
    ~UIEdit() {

    }
};

绘制编辑框实现代码如下

#include <clocale>
#include <cstring>
#include <X11/Xlib.h>
#include <stdio.h>
#include <stdlib.h>
#include <string>
#include <X11/Xft/Xft.h>

using namespace std;

struct UIEdit {
    int x;
    int y;
    int width;
    int height;
    string text;
    XftFont *font;
    XIM     m_xim;
    XIC     m_xic;
    UIEdit():x{0},
            y{0},
            width{0},
            height{0},
            font{nullptr},
            m_xim{nullptr},
            m_xic{nullptr}
    {

    }
    ~UIEdit() {

    }
};

void DoPaint(Display *display, Window window,UIEdit &editControl) {
    int screen = DefaultScreen(display);
    GC gc = XCreateGC(display,window,0,nullptr);
    XSetForeground(display,gc,0x666666);
    XDrawRectangle(display,window,gc,editControl.x,editControl.y,editControl.width,editControl.height);
    XSetForeground(display,gc, 0xfcfcfc);
    XFillRectangle(display,window,gc,editControl.x+1,editControl.y+1,editControl.width-2,editControl.height-2);
    if (!editControl.text.empty()) {
        XftDraw *xftDraw = XftDrawCreate(display,window,DefaultVisual(display,screen),DefaultColormap(display,screen));
        XftColor    textColor;
        textColor.color.alpha = 0xffff;
        textColor.color.red = 0;
        textColor.color.green = 0;
        textColor.color.blue = 0;
        XftDrawStringUtf8(xftDraw,&textColor,editControl.font,
            editControl.x+10,editControl.y + editControl.font->ascent + 3,
            reinterpret_cast<const FcChar8*>(editControl.text.c_str()),editControl.text.length());
        XftDrawDestroy(xftDraw);
    }
    XFreeGC(display,gc);
}

int main() {
    Display *display;
    Window window;
    int screen;
    XEvent event;

    display = XOpenDisplay(NULL);
    if (display == NULL) {
        fprintf(stderr, "无法打开X显示器\n");
        exit(1);
    }
    setlocale(LC_ALL, "");
    XSetLocaleModifiers("");

    screen = DefaultScreen(display);
    window = XCreateSimpleWindow(display, RootWindow(display, screen), 10, 10, 400, 300, 1,
                                 BlackPixel(display, screen), WhitePixel(display, screen));

    /* 选择要接收的事件类型 */
    XSelectInput(display, window, ExposureMask|KeyPressMask);
    XMapWindow(display, window);

    UIEdit  editControl;
    editControl.x = 30;
    editControl.y = 30;
    editControl.width = 200;
    editControl.height = 40;
    editControl.font = XftFontOpenName(display, screen, "文泉驿微米黑-14");


    while (1) {
        XNextEvent(display, &event);
        if(XFilterEvent(&event,None)){
            continue;
        }
        if (event.type == Expose) {
            DoPaint(display,window,editControl);
        }
    }

    XftFontClose(display,editControl.font);
    XDestroyWindow(display, window);
    XCloseDisplay(display);

    return 0;
}

编辑以上程序,运行结果如下:

在这里插入图片描述

3.添加输入文本功能

在前面的示例中,我们在界面中绘制了一个文本框。但目前为止,这个编辑框并没有任何功能,没有显示文字、不能进行编辑,也不能输入文本。下面我们先为编辑框添加文本输入功能。在前面文章中我们记录了xlib中的事件类型,想要实现文本输入功能,我们需要处理xlib的KeyPress事件,然后调用Xutf8LookupString查看键盘行为,是输入单个英文字符、输入中文字符、还是做一些控制操作(Del、Backspace)。Xutf8LookupString函数原型如下:

extern int Xutf8LookupString(
    XIC            /* ic */,
    XKeyPressedEvent*    /* event */,
    char*        /* buffer_return */,
    int            /* bytes_buffer */,
    KeySym*        /* keysym_return */,
    Status*        /* status_return */
);
  • ic使用XCreateIC创建的输入法上下文

  • event XKeyEvent类型结构体指针XKeyPressedEvent和XKeyEvent是同一结构体,使用typedef进行了类型定义。

  • buffer_return 存储转换后的utf-8字符串

  • bytes_buffer 存储的utf8字符串大小(以字节为单位)

  • keysym_return 接收与按键对应的符号信息

  • status_return Status类型变量指针,利用该返回值,我们可以获取键盘输入的文本信息,控制信息。status_return可取的值如下

    • XBufferOverflow 缓冲区太小,无法容纳完整的结果

    • XLookupNone 没有找到对应的字符映射

    • XLookupChars 找到了一些字符,但不是完整的组合键序列

    • XLookupKeySym 找到了一个键符号,但没有对应的字符

    • XLookupBoth 找到了键符号和字符。

接下来我们把窗口按键事件、输入法上下文以及Xutf8LookupString进行组合,实现编辑框文本输入。可编译运行代码如下

#include <clocale>
#include <cstring>
#include <X11/Xlib.h>
#include <stdio.h>
#include <stdlib.h>
#include <string>
#include <X11/Xft/Xft.h>

using namespace std;

struct UIEdit {
    int x;
    int y;
    int width;
    int height;
    string text;
    XftFont *font;
    XIM     m_xim;
    XIC     m_xic;
    UIEdit():x{0},
            y{0},
            width{0},
            height{0},
            font{nullptr},
            m_xim{nullptr},
            m_xic{nullptr}
    {

    }
    ~UIEdit() {

    }
};

void DoPaint(Display *display, Window window,UIEdit &editControl) {
    int screen = DefaultScreen(display);
    GC gc = XCreateGC(display,window,0,nullptr);
    XSetForeground(display,gc,0x666666);
    XDrawRectangle(display,window,gc,editControl.x,editControl.y,editControl.width,editControl.height);
    XSetForeground(display,gc, 0xfcfcfc);
    XFillRectangle(display,window,gc,editControl.x+1,editControl.y+1,editControl.width-2,editControl.height-2);
    if (!editControl.text.empty()) {
        XftDraw *xftDraw = XftDrawCreate(display,window,DefaultVisual(display,screen),DefaultColormap(display,screen));
        XftColor    textColor;
        textColor.color.alpha = 0xffff;
        textColor.color.red = 0;
        textColor.color.green = 0;
        textColor.color.blue = 0;
        XftDrawStringUtf8(xftDraw,&textColor,editControl.font,
            editControl.x+10,editControl.y + editControl.font->ascent + 3,
            reinterpret_cast<const FcChar8*>(editControl.text.c_str()),editControl.text.length());
        XftDrawDestroy(xftDraw);
    }
    XFreeGC(display,gc);
}

static bool IsPrintableChar(KeySym keysym)
{
    return (keysym>=0x20 && keysym<127) || (keysym>=XK_KP_Multiply && keysym<=XK_KP_9);
}

void DoKeyPress(Display *display, Window window, UIEdit &editControl,XKeyEvent &keyEvent) {
    KeySym keysym = NoSymbol;
    char text[32] = {};
    Status status;
    Xutf8LookupString(editControl.m_xic,&keyEvent,text,sizeof(text)-1,&keysym,&status);
    if(status == XBufferOverflow){
        //an IME was probably used,and wants to commit more than 32 chars.
        //ignore this fairly unlikely case for now
    }
    if(status == XLookupChars){
        editControl.text.append(text);
        DoPaint(display,window,editControl);
    }
    if(status == XLookupBoth){
        if( (!(keyEvent.state & ControlMask)) && IsPrintableChar(keysym))
        {
            editControl.text.append(text);
            DoPaint(display,window,editControl);
        }
    }
    if(status == XLookupKeySym){
        //a key without text on it
    }
}

int main() {
    Display *display;
    Window window;
    int screen;
    XEvent event;

    display = XOpenDisplay(NULL);
    if (display == NULL) {
        fprintf(stderr, "无法打开X显示器\n");
        exit(1);
    }
    setlocale(LC_ALL, "");
    XSetLocaleModifiers("");

    screen = DefaultScreen(display);
    window = XCreateSimpleWindow(display, RootWindow(display, screen), 10, 10, 400, 300, 1,
                                 BlackPixel(display, screen), WhitePixel(display, screen));

    /* 选择要接收的事件类型 */
    XSelectInput(display, window, ExposureMask|KeyPressMask);
    XMapWindow(display, window);

    UIEdit  editControl;
    editControl.x = 30;
    editControl.y = 30;
    editControl.width = 200;
    editControl.height = 40;
    editControl.font = XftFontOpenName(display, screen, "文泉驿微米黑-14");
    editControl.m_xim = XOpenIM(display,0,0,0);
    editControl.m_xic = XCreateIC(editControl.m_xim,XNInputStyle,XIMPreeditNothing|XIMStatusNothing,
                                XNClientWindow,window,
                                XNFocusWindow,window,nullptr);

    while (1) {
        XNextEvent(display, &event);
        if(XFilterEvent(&event,None)){
            continue;
        }
        if (event.type == Expose) {
            DoPaint(display,window,editControl);
        }
        if (event.type == KeyPress) {
            DoKeyPress(display,window,editControl,event.xkey);
        }
    }

    XftFontClose(display,editControl.font);
    XDestroyWindow(display, window);
    XCloseDisplay(display);

    return 0;
}

编译以上代码。程序运行后我们可以输入一些文本。效果如下

在这里插入图片描述

4.添加插入光标

在上面的示例中,我们实现了一个接收键盘输入,可以输入中英文的编辑框。如果在输入过程中显示一个文字输入光标则能够展现出更强的交互、提升用户体验。

我们利用XCreatePixmapFromBitmapData来创建一个文字插入光标Pixmap。光标的bitmap数据如下

const int CURSOR_WIDTH = 6;
const int CURSOR_HEIGHT=24;

static unsigned char cursoricon_bits [ ] = {
        0x3f , 0x03f , 0x0c , 0x0c , 0x0c , 0x0c , 0x0c , 0x0c , 0x0c , 0x0c , 0x0c , 0x0c ,
        0x0c , 0x0c , 0x0c , 0x0c , 0x0c , 0x0c , 0x0c , 0x0c , 0x0c , 0x0c , 0x03f , 0x03f } ;

利用以上位图数据我们创建一个黑色的Pixmap实现代码如下

XCreatePixmapFromBitmapData( display , window ,
                reinterpret_cast<char *>(cursoricon_bits), CURSOR_WIDTH , CURSOR_HEIGHT ,
                0xff000000 , WhitePixel ( display , screen ) ,
                DefaultDepth(display,screen) ) ;

由于这里需要保存插入光标的Pixmap,我们为UIEdit结构体,添加一个用于保存插入光标的Pixmap对象

struct UIEdit {
    Pixmap  m_cursorPixmap;
};

将光标显示到编辑框控件中。我们可以使用XCopyArea把UIEdit结体中保存的光标位置拷贝到编辑框位置显示。以下是带有插入光标的编辑框效果

在这里插入图片描述

还有一个问题需要解决,每当我们向编辑框中输入文本时,我们的插入光标需要能够根据输入字符的宽度显示的文本的最后。Xft库给我们提供了一个计算文本宽度的方法,使用该方法,我们不用考虑每个字符占用宽度、英文字符和汉字所占用的像素宽度可能不同。计算文本宽度函数为XftTextExtentsUtf8,该函数原型如下

void
XftTextExtentsUtf8 (Display	    *dpy,
		    XftFont	    *pub,
		    _Xconst FcChar8 *string,
		    int		    len,
		    XGlyphInfo	    *extents);

实现显示插入光标完整代码如下:

#include <clocale>
#include <cstring>
#include <X11/Xlib.h>
#include <stdio.h>
#include <stdlib.h>
#include <string>
#include <X11/Xft/Xft.h>

using namespace std;

const int CURSOR_WIDTH = 6;
const int CURSOR_HEIGHT=24;

static unsigned char cursoricon_bits [ ] = {
    0x3f , 0x03f , 0x0c , 0x0c , 0x0c , 0x0c , 0x0c , 0x0c , 0x0c , 0x0c , 0x0c , 0x0c ,
    0x0c , 0x0c , 0x0c , 0x0c , 0x0c , 0x0c , 0x0c , 0x0c , 0x0c , 0x0c , 0x03f , 0x03f } ;

struct UIEdit {
    int x;
    int y;
    int width;
    int height;
    string text;
    XftFont *font;
    XIM     m_xim;
    XIC     m_xic;
    Pixmap  m_cursorPixmap;
    UIEdit():x{0},
            y{0},
            width{0},
            height{0},
            font{nullptr},
            m_xim{nullptr},
            m_xic{nullptr}
    {

    }
    ~UIEdit() {

    }
};

static void ShowCaret(Display *display,Window window, GC gc,UIEdit &editControl) {
    int textWidth = 0;
    if (editControl.text.length()>0) {
        XGlyphInfo  glyphInfo{0};
        ::XftTextExtentsUtf8(display, editControl.font, reinterpret_cast<const FcChar8 *>(editControl.text.c_str()), editControl.text.length(), &glyphInfo);
        textWidth = glyphInfo.width;
    }
    XCopyArea(display,editControl.m_cursorPixmap,window,gc,0,0,CURSOR_WIDTH,CURSOR_HEIGHT,editControl.x + textWidth + 5,editControl.y+3);
}

void DoPaint(Display *display, Window window,UIEdit &editControl) {
    int screen = DefaultScreen(display);
    GC gc = XCreateGC(display,window,0,nullptr);
    XSetForeground(display,gc,0x666666);
    XDrawRectangle(display,window,gc,editControl.x,editControl.y,editControl.width,editControl.height);
    XSetForeground(display,gc, 0xfcfcfc);
    XFillRectangle(display,window,gc,editControl.x+1,editControl.y+1,editControl.width-2,editControl.height-2);
    if (!editControl.text.empty()) {
        XftDraw *xftDraw = XftDrawCreate(display,window,DefaultVisual(display,screen),DefaultColormap(display,screen));
        XftColor    textColor;
        textColor.color.alpha = 0xffff;
        textColor.color.red = 0;
        textColor.color.green = 0;
        textColor.color.blue = 0;
        XftDrawStringUtf8(xftDraw,&textColor,editControl.font,
            editControl.x+3,editControl.y + editControl.font->ascent + 3,
            reinterpret_cast<const FcChar8*>(editControl.text.c_str()),editControl.text.length());
        XftDrawDestroy(xftDraw);
    }
    ShowCaret(display,window,gc,editControl);
    XFreeGC(display,gc);
}

static bool IsPrintableChar(KeySym keysym)
{
    return (keysym>=0x20 && keysym<127) || (keysym>=XK_KP_Multiply && keysym<=XK_KP_9);
}

void DoKeyPress(Display *display, Window window, UIEdit &editControl,XKeyEvent &keyEvent) {
    KeySym keysym = NoSymbol;
    char text[32] = {};
    Status status;
    Xutf8LookupString(editControl.m_xic,&keyEvent,text,sizeof(text)-1,&keysym,&status);
    if(status == XBufferOverflow){
        //an IME was probably used,and wants to commit more than 32 chars.
        //ignore this fairly unlikely case for now
    }
    if(status == XLookupChars){
        editControl.text.append(text);
        DoPaint(display,window,editControl);
    }
    if(status == XLookupBoth){
        if( (!(keyEvent.state & ControlMask)) && IsPrintableChar(keysym))
        {
            editControl.text.append(text);
            DoPaint(display,window,editControl);
        }
    }
    if(status == XLookupKeySym){
        //a key without text on it
    }
}

int main() {
    Display *display;
    Window window;
    int screen;
    XEvent event;

    display = XOpenDisplay(NULL);
    if (display == NULL) {
        fprintf(stderr, "无法打开X显示器\n");
        exit(1);
    }
    setlocale(LC_ALL, "");
    XSetLocaleModifiers("");

    screen = DefaultScreen(display);
    window = XCreateSimpleWindow(display, RootWindow(display, screen), 10, 10, 400, 300, 1,
                                 BlackPixel(display, screen), WhitePixel(display, screen));

    /* 选择要接收的事件类型 */
    XSelectInput(display, window, ExposureMask|KeyPressMask);
    XMapWindow(display, window);

    UIEdit  editControl;
    editControl.x = 30;
    editControl.y = 30;
    editControl.width = 200;
    editControl.height = 40;
    editControl.font = XftFontOpenName(display, screen, "文泉驿微米黑-14");
    editControl.m_xim = XOpenIM(display,0,0,0);
    editControl.m_xic = XCreateIC(editControl.m_xim,XNInputStyle,XIMPreeditNothing|XIMStatusNothing,
                                XNClientWindow,window,
                                XNFocusWindow,window,nullptr);
    editControl.m_cursorPixmap = XCreatePixmapFromBitmapData( display , window ,
                reinterpret_cast<char *>(cursoricon_bits), CURSOR_WIDTH , CURSOR_HEIGHT ,
                0xff000000 , WhitePixel ( display , screen ) ,
                DefaultDepth(display,screen) ) ;

    while (1) {
        XNextEvent(display, &event);
        if(XFilterEvent(&event,None)){
            continue;
        }
        if (event.type == Expose) {
            DoPaint(display,window,editControl);
        }
        if (event.type == KeyPress) {
            DoKeyPress(display,window,editControl,event.xkey);
        }
    }

    XftFontClose(display,editControl.font);
    XDestroyWindow(display, window);
    XCloseDisplay(display);

    return 0;
}

编译以上代码,运行结果示例如下:

在这里插入图片描述

5.响应Backspace

对于编辑框,在输入过程中,如果我发现出现了错误。这时希望能够按Backspace键删除错误的文本。实现这个功能,我们需要为文本添加Backspace按键响应,每当按下一次Backspace我们就从编辑框控件中删除最后一次输入的一个“字符”,在Linux操作系统下所有的字符串默认都是utf8编码,每个汉字占用约3个字节,每个英文字符占用一个字符。当我们从编辑框中删除一个“字符”时,需要把这个字符所占用字节数据删除掉。先来看下utf8对于Unicode字符的编码

Unicode                     UTF8
00000000----0000007F        0xxxxxxx  1字节
00000080----000007FF        110xxxxx 10xxxxxx 2字节
00000800----0000FFFF        1110xxxx 10xxxxxx 10xxxxxx 3字节
00010000----001FFFFF        11110xxx 10xxxxxx 10xxxxxx 10xxxxxx 4字节
00020000----03FFFFFF        111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 5字节
04000000----7FFFFFFF        1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 6字节

从上表中可以看出对于一个Unicode字符,可以采用1-6个字节表示。对于一个以字节为存储单位的字符串,我们可以遍历整个字节区域,当每个字节以0、110、1110、11110、111110、1111110开始时,我们可以认为这是一个Unicode字符的开始,并且该字符占用的字节数分别为1,2,3,4,5,6。当我们遇到以10开始的字节时,则认为这是一个字符串的中间位置。有了以上关于utf8对于Unicode字符编码的理解,我们可以在以字节为存储单位的缓冲区中实现两个功能,从当前字符字节偏移处找到该Unicode字符结束处字节偏移;知道当前字符结尾处所在字节偏移找到该字符开始处字节偏移。这两个功能分别定义CharNext和CharPrev。实现如下

inline const char* CharNext(const char *p){
    u_char  character = *p;
    if(*p == 0){
        return p;
    }
    if( (character & 0x80) == 0){
        return (p+1);
    }else if( (character >> 5) == 0B110){
        return (p+2);
    }else if( (character>>4) == 0B1110){
        return (p+3);
    }else if( (character>>3) == 0B11110){
        return (p+4);
    }else if( (character>>2) == 0B111110){
        return (p+5);
    }else if( (character>>1) == 0B1111110){
        return (p+6);
    }
    return p+1;
}  

inline const char* CharPrev(const char *start, const char *current)
{
    if(start == current){
        return start;
    }
    const char *result = current - 1;
    while(result != start){
        u_char character = *result;
        if( (character>>6) == 0B10){
            result = result - 1;
        }else{
            break;
        }
    }
    return result;
}

这两个函数在编辑框中实现Backspace、del功能时很有作用。

在DoKeyPress函数中添加Backspace按键的响应

    if(status == XLookupBoth){
        if( (!(keyEvent.state & ControlMask)) && IsPrintableChar(keysym))
        {
            editControl.text.append(text);
            DoPaint(display,window,editControl);
        }
        if(keysym == XK_BackSpace){
            if(editControl.text.length()==0){
                return;
            }
            const char *p = editControl.text.c_str() + editControl.text.length();
            //找到以字节为单位的最后一个字符的开始字节处
            const char *charStart = CharPrev(editControl.text.c_str(),p);
            //移除最后一个Unicode字符。
            editControl.text.erase(charStart - editControl.text.c_str(), p-charStart);
            DoPaint(display,window,editControl);
        }
    }

编辑框带有插入光标,可以响应Backspace完整实现代码如下

#include <clocale>
#include <cstring>
#include <X11/Xlib.h>
#include <stdio.h>
#include <stdlib.h>
#include <string>
#include <X11/Xft/Xft.h>

using namespace std;

const int CURSOR_WIDTH = 6;
const int CURSOR_HEIGHT=24;

static unsigned char cursoricon_bits [ ] = {
    0x3f , 0x03f , 0x0c , 0x0c , 0x0c , 0x0c , 0x0c , 0x0c , 0x0c , 0x0c , 0x0c , 0x0c ,
    0x0c , 0x0c , 0x0c , 0x0c , 0x0c , 0x0c , 0x0c , 0x0c , 0x0c , 0x0c , 0x03f , 0x03f } ;

struct UIEdit {
    int x;
    int y;
    int width;
    int height;
    string text;
    XftFont *font;
    XIM     m_xim;
    XIC     m_xic;
    Pixmap  m_cursorPixmap;
    UIEdit():x{0},
            y{0},
            width{0},
            height{0},
            font{nullptr},
            m_xim{nullptr},
            m_xic{nullptr}
    {

    }
    ~UIEdit() {

    }
};

static void ShowCaret(Display *display,Window window, GC gc,UIEdit &editControl) {
    int textWidth = 0;
    if (editControl.text.length()>0) {
        XGlyphInfo  glyphInfo{0};
        ::XftTextExtentsUtf8(display, editControl.font, reinterpret_cast<const FcChar8 *>(editControl.text.c_str()), editControl.text.length(), &glyphInfo);
        textWidth = glyphInfo.width;
    }
    XCopyArea(display,editControl.m_cursorPixmap,window,gc,0,0,CURSOR_WIDTH,CURSOR_HEIGHT,editControl.x + textWidth + 5,editControl.y+3);
}

inline const char* CharNext(const char *p){
    u_char  character = *p;
    if(*p == 0){
        return p;
    }
    if( (character & 0x80) == 0){
        return (p+1);
    }else if( (character >> 5) == 0B110){
        return (p+2);
    }else if( (character>>4) == 0B1110){
        return (p+3);
    }else if( (character>>3) == 0B11110){
        return (p+4);
    }else if( (character>>2) == 0B111110){
        return (p+5);
    }else if( (character>>1) == 0B1111110){
        return (p+6);
    }
    return p+1;
}

inline const char* CharPrev(const char *start, const char *current)
{
    if(start == current){
        return start;
    }
    const char *result = current - 1;
    while(result != start){
        u_char character = *result;
        if( (character>>6) == 0B10){
            result = result - 1;
        }else{
            break;
        }
    }
    return result;
}

void DoPaint(Display *display, Window window,UIEdit &editControl) {
    int screen = DefaultScreen(display);
    GC gc = XCreateGC(display,window,0,nullptr);
    XSetForeground(display,gc,0x666666);
    XDrawRectangle(display,window,gc,editControl.x,editControl.y,editControl.width,editControl.height);
    XSetForeground(display,gc, 0xfcfcfc);
    XFillRectangle(display,window,gc,editControl.x+1,editControl.y+1,editControl.width-2,editControl.height-2);
    if (!editControl.text.empty()) {
        XftDraw *xftDraw = XftDrawCreate(display,window,DefaultVisual(display,screen),DefaultColormap(display,screen));
        XftColor    textColor;
        textColor.color.alpha = 0xffff;
        textColor.color.red = 0;
        textColor.color.green = 0;
        textColor.color.blue = 0;
        XftDrawStringUtf8(xftDraw,&textColor,editControl.font,
            editControl.x+3,editControl.y + editControl.font->ascent + 3,
            reinterpret_cast<const FcChar8*>(editControl.text.c_str()),editControl.text.length());
        XftDrawDestroy(xftDraw);
    }
    ShowCaret(display,window,gc,editControl);
    XFreeGC(display,gc);
}

static bool IsPrintableChar(KeySym keysym)
{
    return (keysym>=0x20 && keysym<127) || (keysym>=XK_KP_Multiply && keysym<=XK_KP_9);
}

void DoKeyPress(Display *display, Window window, UIEdit &editControl,XKeyEvent &keyEvent) {
    KeySym keysym = NoSymbol;
    char text[32] = {};
    Status status;
    Xutf8LookupString(editControl.m_xic,&keyEvent,text,sizeof(text)-1,&keysym,&status);
    if(status == XBufferOverflow){
        //an IME was probably used,and wants to commit more than 32 chars.
        //ignore this fairly unlikely case for now
    }
    if(status == XLookupChars){
        editControl.text.append(text);
        DoPaint(display,window,editControl);
    }
    if(status == XLookupBoth){
        if( (!(keyEvent.state & ControlMask)) && IsPrintableChar(keysym))
        {
            editControl.text.append(text);
            DoPaint(display,window,editControl);
        }
        if(keysym == XK_BackSpace){
            if(editControl.text.length()==0){
                return;
            }
            const char *p = editControl.text.c_str() + editControl.text.length();
            //找到以字节为单位的最后一个字符的开始字节处
            const char *charStart = CharPrev(editControl.text.c_str(),p);
            //移除最后一个Unicode字符。
            editControl.text.erase(charStart - editControl.text.c_str(), p-charStart);
            DoPaint(display,window,editControl);
        }
    }
    if(status == XLookupKeySym){
        //a key without text on it
    }
}

int main() {
    Display *display;
    Window window;
    int screen;
    XEvent event;

    display = XOpenDisplay(NULL);
    if (display == NULL) {
        fprintf(stderr, "无法打开X显示器\n");
        exit(1);
    }
    setlocale(LC_ALL, "");
    XSetLocaleModifiers("");

    screen = DefaultScreen(display);
    window = XCreateSimpleWindow(display, RootWindow(display, screen), 10, 10, 400, 300, 1,
                                 BlackPixel(display, screen), WhitePixel(display, screen));

    /* 选择要接收的事件类型 */
    XSelectInput(display, window, ExposureMask|KeyPressMask);
    XMapWindow(display, window);

    UIEdit  editControl;
    editControl.x = 30;
    editControl.y = 30;
    editControl.width = 200;
    editControl.height = 40;
    editControl.font = XftFontOpenName(display, screen, "文泉驿微米黑-14");
    editControl.m_xim = XOpenIM(display,0,0,0);
    editControl.m_xic = XCreateIC(editControl.m_xim,XNInputStyle,XIMPreeditNothing|XIMStatusNothing,
                                XNClientWindow,window,
                                XNFocusWindow,window,nullptr);
    editControl.m_cursorPixmap = XCreatePixmapFromBitmapData( display , window ,
                reinterpret_cast<char *>(cursoricon_bits), CURSOR_WIDTH , CURSOR_HEIGHT ,
                0xff000000 , WhitePixel ( display , screen ) ,
                DefaultDepth(display,screen) ) ;

    while (1) {
        XNextEvent(display, &event);
        if(XFilterEvent(&event,None)){
            continue;
        }
        if (event.type == Expose) {
            DoPaint(display,window,editControl);
        }
        if (event.type == KeyPress) {
            DoKeyPress(display,window,editControl,event.xkey);
        }
    }

    XftFontClose(display,editControl.font);
    XDestroyWindow(display, window);
    XCloseDisplay(display);

    return 0;
}

最后使用gif展示编辑框实现效果。

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值