当前位置: 首页 > news >正文

十一、xlib绘制编辑框-续

系列文章目录

本系列文章记录在Linux操作系统下,如何在不依赖QT、GTK等开源GUI库的情况下,基于x11窗口系统(xlib)图形界面应用程序开发。之所以使用x11进行窗口开发,是在开发一个基于duilib跨平台的界面库项目,使用gtk时,发现基于gtk的开发,依赖的动态库太多,发布时太麻烦,gtk不支持静态库编译发布。所以我们决定使用更底层的xlib接口。在此记录下linux系统下基于xlib接口的界面开发

一、xlib创建窗口
二、xlib事件
三、xlib窗口图元
四、xlib区域
五、xlib绘制按钮控件
六、绘制图片
七、xlib窗口渲染
八、实现编辑框控件
九、异形窗口
十、基于xlib实现定时器
十一、xlib绘制编辑框-续


文章目录

  • 系列文章目录
    • 1.实现插入光标眨眼
    • 2.键盘控制插入点
    • 3.鼠标操作


前面的示例我们记录了如何利用xlib提供的接口实现一个编辑框,接受中文字符输入。在这篇文章中我们继续完善编辑框功能。这篇文章准备实现以下功能:插入光标眨眼、支持键盘左键和右键实现实现光标移动,支持鼠标选择显示插入光标点,支持从插入光标处实现插入文字,删除文字(Backspace、Del)。

1.实现插入光标眨眼

结合前一节中我们所实现的定时器功能,我们可以实现一个插入光标眨眼的功能。实现这个功能我们可以把插入光标的显示分为两部分,一部分为显示插入光标。另一部不显示插入光标,显示和不显示各占500ms。这样我们就可以实现一个插入光标的眨眼功能。首先我们需要继承上个示例中的Timeout基类,实现一个定时器事件基类,在Timeout定时器处理事件中我们调用编辑框DoPaint绘制函数。在绘制函数中,我们可以使用一个变量来记录光标显示和不显示的时间,根据这个时间来决定要不要进行插入光标绘制。

处理编辑框的定时器实现类如下:

class EditTimeout : public Timeout {
public:EditTimeout(Display *display, Window window,UIEdit &editControl):m_editControl{editControl},m_display{display},m_window{window}{}void OnTimeout() override {DoPaint(m_display,m_window,m_editControl);}
private:UIEdit &m_editControl;Display *m_display;Window m_window;
};

DoPaint会调用ShowCaret决定要不要进行插入光标绘制。ShowCaret代码实现逻辑修改如下

static uint64_t lastTimeout = 0;static void ShowCaret(Display *display,Window window, GC gc,UIEdit &editControl) {struct timeval tv = {0};gettimeofday(&tv,nullptr);uint64_t currentTime = tv.tv_sec*1000 + tv.tv_usec/1000;if(currentTime-lastTimeout>500 && currentTime-lastTimeout<1000){//500~1000毫秒之间不显示插入光标return;}if(currentTime-lastTimeout>=1000){lastTimeout = currentTime;}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);
}

当定时器事件处在0-500毫秒之间时,我们显示插入光标,当定时器事件处在500-1000毫秒之间我们不显示插入光标。大于1000时,定时器记录归“零"。还有一个地方需要,那就是在DoPaint函数的开始处,我们需要调用XClearArea清空整个编辑框的位置,因为不清除该区域,当我们不需要显示插入光标时,由于之前绘制的光标可能没有从窗口中清除。DoPaint稍加改动。

void DoPaint(Display *display, Window window,UIEdit &editControl) {XClearArea(display,window,editControl.x,editControl.y,editControl.width,editControl.height,False);...
}

在main函数中生成EditTimeout的实例,并且设置每隔100毫秒执行一次定时器事件。将EditTimeout实例添加到TimeoutContext管理的定时器实现中。调用事件循环和非阻塞接口实现事件处理和定时器超时处理。

    EditTimeout *editTimeout = new EditTimeout(display,window,editControl);editTimeout->SetInterval(100);//TimeoutContext::GetInstance().Add(editTimeout);struct pollfd fd = {.fd = ConnectionNumber(display),.events =  POLLIN};while (1) {bool ret = XPending(display)> 0 || poll(&fd,1,TimeoutContext::GetInstance().GetMinimumTimeout())>0;if (!ret) {//timeoutTimeoutContext::GetInstance().ProcessTimeoutEvents();continue;}TimeoutContext::GetInstance().ProcessTimeoutEvents();....}

完整代码如下:

#include <clocale>
#include <cstring>
#include <X11/Xlib.h>
#include <stdio.h>
#include <stdlib.h>
#include <string>
#include <sys/poll.h>
#include <X11/Xft/Xft.h>
#include <sys/time.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() {}
};class Timeout
{
public:virtual ~Timeout() = default;uint32_t GetTimerId(){return m_timerId;}void    SetTimerId(uint32_t id){m_timerId = id;}void SetInterval(uint32_t interval){m_interval = interval;}uint32_t GetInterval(){return m_interval;}uint64_t GetTimeoutMilliseconds(){return m_timeoutMilliseconds;}void    SetTimeoutMilliseconds(uint64_t timeoutMilliseconds){m_timeoutMilliseconds = timeoutMilliseconds;}virtual void    OnTimeout() = 0;private:uint32_t  m_timerId;uint32_t  m_interval;uint64_t  m_timeoutMilliseconds;
};const uint32_t MAX_TIMEOUT_EVENTS = 512;class TimeoutContext
{
public:static TimeoutContext &GetInstance();void            Add(Timeout *timeout);void            Remove(Timeout *timeout);uint64_t        GetMinimumTimeout();void            ProcessTimeoutEvents();private:void    ShiftDown(int currentIndex);void    ShiftUp(int currentIndex);
private:TimeoutContext():m_elements{0}{memset(m_timeoutEvents,0,sizeof(m_timeoutEvents));}
private:Timeout *m_timeoutEvents[MAX_TIMEOUT_EVENTS];int     m_elements;
};TimeoutContext &TimeoutContext::GetInstance() {static TimeoutContext timeoutContext;return timeoutContext;
}void TimeoutContext::Add(Timeout *timeout) {struct timeval now = {0};gettimeofday(&now,nullptr);uint64_t milliseconds = now.tv_sec*1000 + now.tv_usec/1000;timeout->SetTimeoutMilliseconds(milliseconds + timeout->GetInterval());m_timeoutEvents[m_elements] = timeout;this->ShiftUp(m_elements++);
}void TimeoutContext::Remove(Timeout *timeout) {if(timeout == nullptr){m_timeoutEvents[0] = m_timeoutEvents[--m_elements];this->ShiftDown(0);return;}for(uint32_t i=0;i<m_elements;i++){if(m_timeoutEvents[i] == timeout){m_timeoutEvents[i] = m_timeoutEvents[m_elements-1];m_elements--;this->ShiftDown(i);return;}}
}void TimeoutContext::ShiftDown(int currentIndex) {int childIndex = currentIndex*2+1;while(childIndex < m_elements){if(childIndex+1 < m_elements && m_timeoutEvents[childIndex]->GetInterval() > m_timeoutEvents[childIndex+1]->GetTimeoutMilliseconds()){//找到两个子节点中,过期时间较小的那个childIndex = childIndex + 1;}if(m_timeoutEvents[currentIndex]->GetTimeoutMilliseconds() <= m_timeoutEvents[childIndex]->GetTimeoutMilliseconds()){break;}Timeout *tmpValue = m_timeoutEvents[currentIndex];m_timeoutEvents[currentIndex] = m_timeoutEvents[childIndex];m_timeoutEvents[childIndex] = tmpValue;currentIndex = childIndex;childIndex = currentIndex*2+1;}
}void TimeoutContext::ShiftUp(int currentIndex) {int parentIndex = (currentIndex-1)/2;while(currentIndex != 0){if(m_timeoutEvents[parentIndex]->GetTimeoutMilliseconds()  <= m_timeoutEvents[currentIndex]->GetTimeoutMilliseconds()){break;}Timeout *tmpValue = m_timeoutEvents[parentIndex];m_timeoutEvents[parentIndex] = m_timeoutEvents[currentIndex];m_timeoutEvents[currentIndex] = tmpValue;currentIndex = parentIndex;parentIndex = (currentIndex-1)/2;}
}uint64_t TimeoutContext::GetMinimumTimeout() {if (m_elements == 0) {return 0xFFFFFFFF;}Timeout *timeout = m_timeoutEvents[0];struct timeval  now{0};gettimeofday(&now, nullptr);uint64_t milliseconds = now.tv_sec * 1000 + now.tv_usec/1000;return timeout->GetTimeoutMilliseconds() > milliseconds ? timeout->GetTimeoutMilliseconds()-milliseconds :0;
}void TimeoutContext::ProcessTimeoutEvents() {uint32_t processCount = 0;//每次循环最多只处理100个超时事件,以防止程序进入死循环状态。struct timeval now {0};gettimeofday(&now,nullptr);uint64_t milliseconds = now.tv_sec*1000 + now.tv_usec/1000;while (m_elements>0 && m_timeoutEvents[0]->GetTimeoutMilliseconds()<=milliseconds && processCount++<100) {auto *timeout = m_timeoutEvents[0];m_timeoutEvents[0] = m_timeoutEvents[--m_elements];ShiftDown(0);long lRes = 0;timeout->OnTimeout();this->Add(timeout);}
}static uint64_t lastTimeout = 0;static void ShowCaret(Display *display,Window window, GC gc,UIEdit &editControl) {struct timeval tv = {0};gettimeofday(&tv,nullptr);uint64_t currentTime = tv.tv_sec*1000 + tv.tv_usec/1000;if(currentTime-lastTimeout>500 && currentTime-lastTimeout<1000){//500~1000毫秒之间不显示插入光标return;}if(currentTime-lastTimeout>=1000){lastTimeout = currentTime;}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) {XClearArea(display,window,editControl.x,editControl.y,editControl.width,editControl.height,False);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}
}class EditTimeout : public Timeout {
public:EditTimeout(Display *display, Window window,UIEdit &editControl):m_editControl{editControl},m_display{display},m_window{window}{}void OnTimeout() override {DoPaint(m_display,m_window,m_editControl);}
private:UIEdit &m_editControl;Display *m_display;Window m_window;
};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) ) ;EditTimeout *editTimeout = new EditTimeout(display,window,editControl);editTimeout->SetInterval(100);//TimeoutContext::GetInstance().Add(editTimeout);struct pollfd fd = {.fd = ConnectionNumber(display),.events =  POLLIN};while (1) {bool ret = XPending(display)> 0 || poll(&fd,1,TimeoutContext::GetInstance().GetMinimumTimeout())>0;if (!ret) {//timeoutTimeoutContext::GetInstance().ProcessTimeoutEvents();continue;}TimeoutContext::GetInstance().ProcessTimeoutEvents();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);}}TimeoutContext::GetInstance().Remove(editTimeout);delete editTimeout;XftFontClose(display,editControl.font);XDestroyWindow(display, window);XCloseDisplay(display);return 0;
}

编译代码运行结果如下。这里并不是最终的代码,随着功能的添加,以上部分函数代码可能已经无法满足我们的需求,还需要进行重构。以上给我们的中间临时代码,这也是我们在开发过程中经常遇到的需要对代码进行重构、修改演进。代码很多,实际开发中我们会把以上代码进行重构,根据职责分在不同的头文件和源文件中。

在这里插入图片描述

2.键盘控制插入点

之前所有的示例中。我们的光标插入点都是在编辑框整段文本的最后,比如我们输入20个字符,发现第10个字符出现了错误。按照目前的实现我们需要把第10个字符之后所有输入的字符都删掉。这个显然用户交互体验极差。我们通过键盘中的左键、右键来移动插入光标位置,删除待修改的字符,重新输入。

接下来需要解决的问题一是我们需要修改UIEdit结体体,添加一个能够记录当前光标所在位置变量,这个变量存储的是以“字符”为单位的下标值(变量名为m_caretIndex),当这个变量取值为0时表示我需要在第一个字符之前输入或修改数据,为1时表示在第1个字符之后做插入、修改操作;由于汉字通常占用3个字节、英文占用1个字节;以字符为单位进行操作时,当按下键盘的左、右键、backspace或是del键时,我们只需要光标左移+1、-1、删除前一个字符或删除后一个字符即可。

第二个问题,当我们使用键盘左、右键移动光标后。我们的光标位置发生了改变,这里我们需要重新计算出新的光标点的位置。这里有两种实现方式,一种是在初始化编辑框时我们计算每个字符的宽度,并将所有字符的宽度存储到缓存变量中,当文本发生变化时维护缓冲变量字符宽度时,这种实现方式节省性能,不用每次都计算所有字符宽度,缺点较为复杂,需要维护文本变换时,每个字符对应宽度信息。另一种是当光标信息发生变化时我们就重新计算光标应该显示的位置,我们使用这种方式。

第三个问题,我们想要实现从当光标处向前删除一个字符,或是向后删除一个字符。我们

需要知道m_caretIndex个字符在utf8缓冲区的偏移值 。m_caretIndex对应的utf8字节缓冲区偏移可以将前m_caretIndex每个字符所占的字节相加。

p o s = ∑ i = 0 m _ c a r e t I n d e x − 1 B y t e s O f C h a r A t i pos = \sum\limits_{i=0}^{m\_caretIndex-1}Bytes\ Of\ Char\ At\ i pos=i=0m_caretIndex1Bytes Of Char At i

上式中的totalBytes即为第m_caretPos个字符在utf8缓冲区中的偏移值。使用代码实现如下:

static int Utf8PosByteCharIndex(int charIndex,UIEdit &editControl) {if (charIndex > GetNumberOfCharacters(editControl.text)) {charIndex = GetNumberOfCharacters(editControl.text);}int pos = 0;const char *start = editControl.text.c_str();const char *end = start + editControl.text.length();const char *next = CharNext(start);while(charIndex-- && start<end){pos += (next - start);start = next;next = CharNext(start);}return pos;
}

得到了字符所有utf8缓冲区的偏移后,我们也就可以计算出该光标绘到屏幕后应该显示的位置了

static int CalculateCaretPos(Display *display,UIEdit &editControl) {int pos = Utf8PosByteCharIndex(editControl);if (pos<0) {return 0;}XGlyphInfo  glyphInfo{0};::XftTextExtentsUtf8(display, editControl.font, reinterpret_cast<const FcChar8 *>(editControl.text.c_str()), pos, &glyphInfo);return glyphInfo.width;
}

响应键盘事件,键盘处理事件较为复杂,包括输入文本、删除文字、左键/右键移动光标

输入文本

当我们在显示光标插入点通过键盘输入向文本框中输入文字时,键盘一次性可能输入多个字符,这时m_caretIndex需要加上新输入的字符数。同时我们还需要更新utf8缓冲区,在m_carentIndex所对应的utf8偏移处插入文本。实现逻辑如下:

    if(status == XLookupChars){int utf8Pos = Utf8PosByteCharIndex(editControl);editControl.text.insert(utf8Pos,text);editControl.m_caretIndex += GetNumberOfCharacters(text);DoPaint(display,window,editControl);}if(status == XLookupBoth){if( (!(keyEvent.state & ControlMask)) && IsPrintableChar(keysym)){int utf8Pos = Utf8PosByteCharIndex(editControl);editControl.text.insert(utf8Pos,text);editControl.m_caretIndex += GetNumberOfCharacters(text);DoPaint(display,window,editControl);//其它代省略}

之前我们直接调用editControl.text.append向文本的尾部追文本。现在我们需要从插入点处插入文本。

删除字符

我们可以使用Backspace从当前插入光标处删除前一个字符,Del删除光标处后面一个字符。下面是我们处理键盘的Backspace和Del事件

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

光标移动

使用left、right、Home、End键实现插入光标位置的移动。

实现逻辑如下

if(status == XLookupKeySym){//a key without text on itif (keysym == XK_Left) {if (editControl.m_caretIndex == 0) {return;}editControl.m_caretIndex--;DoPaint(display,window,editControl);}if (keysym == XK_Right) {if (editControl.m_caretIndex == editControl.text.length()) {return;}editControl.m_caretIndex++;DoPaint(display,window,editControl);}if (keysym == XK_Home) {editControl.m_caretIndex = 0;DoPaint(display,window,editControl);}if (keysym == XK_End) {editControl.m_caretIndex = editControl.text.length();DoPaint(display,window,editControl);}}

一个完整的可运行代码如下

#include <clocale>
#include <cstring>
#include <X11/Xlib.h>
#include <stdio.h>
#include <stdlib.h>
#include <string>
#include <sys/poll.h>
#include <X11/Xft/Xft.h>
#include <sys/time.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;int     m_caretIndex;UIEdit():x{0},y{0},width{0},height{0},font{nullptr},m_xim{nullptr},m_xic{nullptr},m_caretIndex{0}{}~UIEdit() {}
};class Timeout
{
public:virtual ~Timeout() = default;uint32_t GetTimerId(){return m_timerId;}void    SetTimerId(uint32_t id){m_timerId = id;}void SetInterval(uint32_t interval){m_interval = interval;}uint32_t GetInterval(){return m_interval;}uint64_t GetTimeoutMilliseconds(){return m_timeoutMilliseconds;}void    SetTimeoutMilliseconds(uint64_t timeoutMilliseconds){m_timeoutMilliseconds = timeoutMilliseconds;}virtual void    OnTimeout() = 0;private:uint32_t  m_timerId;uint32_t  m_interval;uint64_t  m_timeoutMilliseconds;
};const uint32_t MAX_TIMEOUT_EVENTS = 512;class TimeoutContext
{
public:static TimeoutContext &GetInstance();void            Add(Timeout *timeout);void            Remove(Timeout *timeout);uint64_t        GetMinimumTimeout();void            ProcessTimeoutEvents();private:void    ShiftDown(int currentIndex);void    ShiftUp(int currentIndex);
private:TimeoutContext():m_elements{0}{memset(m_timeoutEvents,0,sizeof(m_timeoutEvents));}
private:Timeout *m_timeoutEvents[MAX_TIMEOUT_EVENTS];int     m_elements;
};TimeoutContext &TimeoutContext::GetInstance() {static TimeoutContext timeoutContext;return timeoutContext;
}void TimeoutContext::Add(Timeout *timeout) {struct timeval now = {0};gettimeofday(&now,nullptr);uint64_t milliseconds = now.tv_sec*1000 + now.tv_usec/1000;timeout->SetTimeoutMilliseconds(milliseconds + timeout->GetInterval());m_timeoutEvents[m_elements] = timeout;this->ShiftUp(m_elements++);
}void TimeoutContext::Remove(Timeout *timeout) {if(timeout == nullptr){m_timeoutEvents[0] = m_timeoutEvents[--m_elements];this->ShiftDown(0);return;}for(uint32_t i=0;i<m_elements;i++){if(m_timeoutEvents[i] == timeout){m_timeoutEvents[i] = m_timeoutEvents[m_elements-1];m_elements--;this->ShiftDown(i);return;}}
}void TimeoutContext::ShiftDown(int currentIndex) {int childIndex = currentIndex*2+1;while(childIndex < m_elements){if(childIndex+1 < m_elements && m_timeoutEvents[childIndex]->GetInterval() > m_timeoutEvents[childIndex+1]->GetTimeoutMilliseconds()){//找到两个子节点中,过期时间较小的那个childIndex = childIndex + 1;}if(m_timeoutEvents[currentIndex]->GetTimeoutMilliseconds() <= m_timeoutEvents[childIndex]->GetTimeoutMilliseconds()){break;}Timeout *tmpValue = m_timeoutEvents[currentIndex];m_timeoutEvents[currentIndex] = m_timeoutEvents[childIndex];m_timeoutEvents[childIndex] = tmpValue;currentIndex = childIndex;childIndex = currentIndex*2+1;}
}void TimeoutContext::ShiftUp(int currentIndex) {int parentIndex = (currentIndex-1)/2;while(currentIndex != 0){if(m_timeoutEvents[parentIndex]->GetTimeoutMilliseconds()  <= m_timeoutEvents[currentIndex]->GetTimeoutMilliseconds()){break;}Timeout *tmpValue = m_timeoutEvents[parentIndex];m_timeoutEvents[parentIndex] = m_timeoutEvents[currentIndex];m_timeoutEvents[currentIndex] = tmpValue;currentIndex = parentIndex;parentIndex = (currentIndex-1)/2;}
}uint64_t TimeoutContext::GetMinimumTimeout() {if (m_elements == 0) {return 0xFFFFFFFF;}Timeout *timeout = m_timeoutEvents[0];struct timeval  now{0};gettimeofday(&now, nullptr);uint64_t milliseconds = now.tv_sec * 1000 + now.tv_usec/1000;return timeout->GetTimeoutMilliseconds() > milliseconds ? timeout->GetTimeoutMilliseconds()-milliseconds :0;
}void TimeoutContext::ProcessTimeoutEvents() {uint32_t processCount = 0;//每次循环最多只处理100个超时事件,以防止程序进入死循环状态。struct timeval now {0};gettimeofday(&now,nullptr);uint64_t milliseconds = now.tv_sec*1000 + now.tv_usec/1000;while (m_elements>0 && m_timeoutEvents[0]->GetTimeoutMilliseconds()<=milliseconds && processCount++<100) {auto *timeout = m_timeoutEvents[0];m_timeoutEvents[0] = m_timeoutEvents[--m_elements];ShiftDown(0);long lRes = 0;timeout->OnTimeout();this->Add(timeout);}
}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;
}static uint32_t GetNumberOfCharacters(const string &str)
{const char *start = str.c_str();const char *end = str.c_str() + str.length();uint32_t result = 0;while(start < end){start = CharNext(start);result++;}return result;
}static int Utf8PosByteCharIndex(UIEdit &editControl) {int charIndex = editControl.m_caretIndex;if (charIndex > GetNumberOfCharacters(editControl.text)) {charIndex = GetNumberOfCharacters(editControl.text);}int pos = 0;const char *start = editControl.text.c_str();const char *end = start + editControl.text.length();const char *next = CharNext(start);while(charIndex-- && start<end){pos += (next - start);start = next;next = CharNext(start);}return pos;
}static int CalculateCaretPos(Display *display,UIEdit &editControl) {int pos = Utf8PosByteCharIndex(editControl);if (pos<0) {return 0;}XGlyphInfo  glyphInfo{0};::XftTextExtentsUtf8(display, editControl.font, reinterpret_cast<const FcChar8 *>(editControl.text.c_str()), pos, &glyphInfo);return glyphInfo.width;
}static uint64_t lastTimeout = 0;static void ShowCaret(Display *display,Window window, GC gc,UIEdit &editControl) {struct timeval tv = {0};gettimeofday(&tv,nullptr);uint64_t currentTime = tv.tv_sec*1000 + tv.tv_usec/1000;if(currentTime-lastTimeout>500 && currentTime-lastTimeout<1000){//500~1000毫秒之间不显示插入光标return;}if(currentTime-lastTimeout>=1000){lastTimeout = currentTime;}int textWidth = CalculateCaretPos(display,editControl);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) {XClearArea(display,window,editControl.x,editControl.y,editControl.width,editControl.height,False);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){int utf8Pos = Utf8PosByteCharIndex(editControl);editControl.text.insert(utf8Pos,text);editControl.m_caretIndex += GetNumberOfCharacters(text);DoPaint(display,window,editControl);}if(status == XLookupBoth){if( (!(keyEvent.state & ControlMask)) && IsPrintableChar(keysym)){int utf8Pos = Utf8PosByteCharIndex(editControl);editControl.text.insert(utf8Pos,text);editControl.m_caretIndex += GetNumberOfCharacters(text);DoPaint(display,window,editControl);}if(keysym == XK_BackSpace){if(editControl.text.length()==0){return;}//const char *p = editControl.text.c_str() + Utf8PosByteCharIndex(editControl);//找到以字节为单位的最后一个字符的开始字节处const char *charStart = CharPrev(editControl.text.c_str(),p);//移除最后一个Unicode字符。editControl.text.erase(charStart - editControl.text.c_str(), p-charStart);editControl.m_caretIndex--;DoPaint(display,window,editControl);}if (keysym == XK_Delete) {const char *p = editControl.text.c_str() + Utf8PosByteCharIndex(editControl);const char *nextChar = CharNext(p);editControl.text.erase(Utf8PosByteCharIndex(editControl),nextChar-p);DoPaint(display,window,editControl);}}if(status == XLookupKeySym){//a key without text on itif (keysym == XK_Left) {if (editControl.m_caretIndex == 0) {return;}editControl.m_caretIndex--;DoPaint(display,window,editControl);}if (keysym == XK_Right) {if (editControl.m_caretIndex == editControl.text.length()) {return;}editControl.m_caretIndex++;DoPaint(display,window,editControl);}if (keysym == XK_Home) {editControl.m_caretIndex = 0;DoPaint(display,window,editControl);}if (keysym == XK_End) {editControl.m_caretIndex = editControl.text.length();DoPaint(display,window,editControl);}}
}class EditTimeout : public Timeout {
public:EditTimeout(Display *display, Window window,UIEdit &editControl):m_editControl{editControl},m_display{display},m_window{window}{}void OnTimeout() override {DoPaint(m_display,m_window,m_editControl);}
private:UIEdit &m_editControl;Display *m_display;Window m_window;
};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) ) ;EditTimeout *editTimeout = new EditTimeout(display,window,editControl);editTimeout->SetInterval(100);//TimeoutContext::GetInstance().Add(editTimeout);struct pollfd fd = {.fd = ConnectionNumber(display),.events =  POLLIN};while (1) {bool ret = XPending(display)> 0 || poll(&fd,1,TimeoutContext::GetInstance().GetMinimumTimeout())>0;if (!ret) {//timeoutTimeoutContext::GetInstance().ProcessTimeoutEvents();continue;}TimeoutContext::GetInstance().ProcessTimeoutEvents();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);}}TimeoutContext::GetInstance().Remove(editTimeout);delete editTimeout;XftFontClose(display,editControl.font);XDestroyWindow(display, window);XCloseDisplay(display);return 0;
}

编译以上程序,运行效果如下:

在这里插入图片描述

3.鼠标操作

当然我们也可以通过鼠标点击来确定插入光标的位置。当我们按下鼠标按钮后,需要对编辑框中的文字一个一个进行宽度计算,以确定哪个字符位置与鼠标按下时的位置最近;以此确定插入光标的位置。实现逻辑如下:

if (event.type == ButtonPress) {if (event.xbutton.button == Button1) {if (event.xbutton.x>=editControl.x && event.xbutton.x<=editControl.x+editControl.width&& event.xbutton.y>=editControl.y && event.xbutton.y<=editControl.y+editControl.height) {editControl.m_caretIndex = 0;//鼠标x坐标减去文本开始绘制的位置int textStartX = editControl.x+3;int tmpWidth = abs(event.xbutton.x - textStartX);int pos = -1;int numberOfChar = GetNumberOfCharacters(editControl.text);int currentTextWidth = 0;const char *p = editControl.text.c_str();for (int i=0;i<numberOfChar;i++) {const char *nextChar = CharNext(p);XGlyphInfo  glyphInfo{0};::XftTextExtentsUtf8(display, editControl.font, reinterpret_cast<const FcChar8*>(p), nextChar-p, &glyphInfo);currentTextWidth += glyphInfo.width;if (abs(event.xbutton.x - (textStartX+currentTextWidth))<tmpWidth) {tmpWidth = abs(event.xbutton.x - (textStartX+currentTextWidth));pos = i;}p=nextChar;//int utf8Pos = Utf8PosByteCharIndex()}if (pos != -1) {editControl.m_caretIndex = pos+1;}DoPaint(display,window,editControl);}}}

编译运行带有鼠标点击功能编辑框。运行结果如下

在这里插入图片描述

以上我们实现了一个复杂的编辑框功能。支持键盘输入、删除、鼠标操作、插入光标显示。当然如果我们仔细查看,上面显示的插入光标会遮盖会一部分文本数据。这时因为插入光标本身是使用位图实现的,占用了6个像素的宽度。所以我们还可以对于编辑框控件进行改进。将文本的显示分成插入光标之前和之后,对于插入光标之后的文本,我们可以在其原本显示位置基础上在x轴方向增加一些像素。

相关文章:

十一、xlib绘制编辑框-续

系列文章目录 本系列文章记录在Linux操作系统下&#xff0c;如何在不依赖QT、GTK等开源GUI库的情况下&#xff0c;基于x11窗口系统&#xff08;xlib&#xff09;图形界面应用程序开发。之所以使用x11进行窗口开发&#xff0c;是在开发一个基于duilib跨平台的界面库项目&#x…...

PyTorch进阶实战指南:02分布式训练深度优化

PyTorch进阶实战指南&#xff1a;02分布式训练深度优化 前言 在大模型时代&#xff0c;分布式训练已成为突破单机算力瓶颈的核心技术。本文深入解析PyTorch分布式训练的技术实现&#xff0c;从单机多卡并行到万卡集群协同&#xff0c;系统揭示现代深度学习规模化训练的核心机制…...

使用Vite创建一个动态网页的前端项目

1. 引言 虽然现在的前端更新换代的速度很快&#xff0c;IDE和工具一批批的换&#xff0c;但是我们始终要理解一点基本的程序构建的思维&#xff0c;这些环境和工具都是为了帮助我们更快的发布程序。笔者还记得以前写前端代码的时候&#xff0c;只使用文本编辑器&#xff0c;然…...

常见的LLM

常见的 LLM&#xff08;大语言模型&#xff0c;Large Language Models&#xff09;可以按照开源/闭源、机构/公司、用途等维度分类。以下是一些主流和常见的 LLM 及其简介&#xff1a; 一、开源 LLM Meta&#xff08;Facebook&#xff09; 名称参数量特点LLaMA 1 / 2 / 37B /…...

助力 FPGA 国产化,ALINX 携多款方案亮相深圳、广州“紫光同创 FPGA 技术研讨会”

5 月中旬&#xff0c;一年一度的紫光同创技术研讨会系列活动正式拉开帷幕&#xff0c;相继在深圳、广州带来 FPGA 技术交流盛宴。 ALINX 作为紫光同创官方合作伙伴&#xff0c;长期助力推动 FPGA 国产化应用发展&#xff0c;此次携多款基于 Kosmo-2 系列产品开发的方案 demo 亮…...

深入浅出IIC协议 - 从总线原理到FPGA实战开发 --第四篇:I2C工业级优化实践

第四篇&#xff1a;I2C工业级优化实践 副标题 &#xff1a;从实验室到产线——I2C控制器的高可靠设计秘籍 1. 时序收敛技巧 1.1 关键路径识别与优化 Vivado时序报告解析 &#xff1a; Slack (MET): 0.152ns (要求≥0) Data Path Delay: 3.821ns (逻辑布线) Cell Delay: i…...

【leetcode】70. 爬楼梯

文章目录 1. 数组2. 优化空间 假设你正在爬楼梯。需要 n 阶你才能到达楼顶。 每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢&#xff1f; 示例 1&#xff1a; 输入&#xff1a;n 2 输出&#xff1a;2 解释&#xff1a;有两种方法可以爬到楼顶。 1 阶 1…...

【web全栈】若依框架B站学习视频

文章目录 基础篇-01 AI若依导学视频基础篇02 若依搭建基础篇03 入门案例基础篇04 功能详解-权限控制 基础篇-01 AI若依导学视频 基础篇02 若依搭建 基础篇03 入门案例 基础篇04 功能详解-权限控制...

MFC 捕捉桌面存成jpg案例代码

下面是关于截屏并保存成jpg文件的代码。由主函数OnCapScreenJpg()、DDBToDIB()、JpegFromDib()、DibToSamps()以及QuadFromWord()函数组成。这些函数的功能包括截取屏幕、将截取的屏幕转成设备无关bmp、再进一步压缩成jpeg格式。这些代码是从网上得到的&#xff0c;得到的代码没…...

2.4.4-死锁的处理策略-检测和解除

知识总览 死锁的检测 用资源分配图这种数据结构来检测是否产生了死锁&#xff0c;资源分配图上有2种节点&#xff0c;进程节点用圆圈表示&#xff0c;一个圆圈代表一个进程&#xff0c;还有资源节点&#xff0c;一个矩形代表一类资源&#xff0c;用矩形中的圆圈表示当前类型的…...

豪越智能仓储:为消防应急物资管理“上锁”

在城市的繁华街角&#xff0c;一场突如其来的大火无情地肆虐着一栋商业大楼。火焰在楼内疯狂蔓延&#xff0c;滚滚浓烟迅速弥漫&#xff0c;人们的生命财产安全受到了严重威胁。消防警报声骤然响起&#xff0c;消防队员们迅速出动&#xff0c;争分夺秒赶赴火灾现场。然而&#…...

(06)数字化转型之质量管理:遵循PDCA规范的全流程避险指南

在全球化竞争和消费升级的双重驱动下&#xff0c;质量管理已从单纯的产品检验演变为企业核心竞争力的重要组成部分。一个完善的质量管理体系不仅能降低质量成本、提升客户满意度&#xff0c;更能成为品牌差异化的战略武器。本文将系统性地介绍现代企业质量管理的完整框架&#…...

图论算法精解(Java 实现):从基础到高频面试题

一、图的基础表示方法 1.1 邻接矩阵&#xff08;Adjacency Matrix&#xff09; 邻接矩阵是表示图的一种直观方式&#xff0c;它使用一个二维数组来存储节点之间的连接关系。对于一个有 n 个节点的图&#xff0c;邻接矩阵是一个 nn 的矩阵&#xff0c;其中 matrix [i][j] 表示…...

[Linux] Linux信号量深度解析与实践(代码示例)

Linux信号量深度解析与实践 文章目录 Linux信号量深度解析与实践一、什么是信号量1. 信号量的核心概念2. 信号量的分类3. 信号量的操作机制 二、怎么用信号量1. 信号量API的深度解析&#xff08;1&#xff09;无名信号量API&#xff08;2&#xff09;有名信号量API&#xff08;…...

Switch最新 模拟器 Eden(伊甸)正式发布 替代Yuzu模拟器

Switch最新 模拟器 Eden&#xff08;伊甸&#xff09;正式发布 替代Yuzu模拟器 100 帧跑满《塞尔达传说&#xff1a;旷野之息》 这款模拟器基于 Yuzu 框架开发&#xff0c;但团队强调它并非…...

[cg] [ds]深度缓冲z与线性z推导

4. GLSL 代码实现 在着色器中&#xff0c;将深度缓冲值转换为线性深度&#xff1a; float LinearizeDepth(float depth, float near, float far) {// OpenGL 的 NDC 深度范围是 [-1, 1]&#xff0c;需转换float z_ndc 2.0 * depth - 1.0;// 计算线性深度return (2.0 * near …...

clock的时钟频率check代码

在芯片验证中&#xff0c;经常遇到需要check时钟频率的场景&#xff0c;由于时钟数量有很多&#xff0c;手动写代码得到后年马月&#xff0c;所以我这边写了一个宏define&#xff0c;可以通过输入参数的形式验证需要check的时钟频率&#xff0c;大大提升了验证效率和准确率&…...

企业数字化转型是否已由信息化+自动化向智能化迈进?

DeepSeek引发的AI热潮迅速蔓延到了各个行业&#xff0c;目前接入DeepSeek的企业&#xff0c;涵盖了科技互联网、云服务、电信、金融、能源、汽车、手机等热门领域&#xff0c;甚至全国各地政府机构也纷纷引入。 在 DeepSeek 等国产 AI 技术的推动下&#xff0c;众多企业已经敏锐…...

PT5F2307触摸A/D型8-Bit MCU

1. 产品概述 ● PT5F2307是一款51内核的触控A/D型8位MCU&#xff0c;内置16K*8bit FLASH、内部256*8bit SRAM、外部512*8bit SRAM、触控检测、12位高精度ADC、RTC、PWM等功能&#xff0c;抗干扰能力强&#xff0c;适用于滑条遥控器、智能门锁、消费类电子产品等电子应用领域。 …...

嵌入式STM32学习——串口USART 2.0(printf重定义及串口发送)

printf重定义&#xff1a; C语言里面的printf函数默认输出设备是显示器&#xff0c;如果要实现printf函数输出正在串口或者LCD显示屏上&#xff0c;必须要重定义标准库函数里调用的与输出设备相关的函数&#xff0c;比如printf输出到串口&#xff0c;需要将fputc里面的输出指向…...

进程信号(上)【Linux操作系统】

文章目录 进程信号信号引入进程要如何识别信号&#xff1f;进程接收到信号的时候&#xff0c;不一定马上处理信号进程处理信号的情况 信号相关概念信号产生键盘产生通过指令向进程发送信号系统调用向进程发送信号软件条件异常错误 操作系统如何知道进程出现了异常错误&#xff…...

全方位详解微服务架构中的Service Mesh(服务网格)

一、引言 随着微服务架构的广泛应用&#xff0c;微服务之间的通信管理、流量控制、安全保障等问题变得日益复杂。服务网格&#xff08;Service Mesh&#xff09;作为一种新兴的技术&#xff0c;为解决这些问题提供了有效的方案。它将服务间通信的管理从微服务代码中分离出来&a…...

bi工具是什么意思?bi工具的主要功能有哪些?

目录 一、BI 工具是什么意思&#xff1f; 1. 基本概念 2. 发展历程 ​编辑二、BI 工具的主要功能 1. 数据连接与整合 2. 数据存储与管理 3. 数据分析与挖掘 4. 可视化呈现 5. 报表生成与分享 6. 实时监控与预警 三、BI 工具的应用场景 1. 销售与营销 2. 财务与会计…...

cocos creator使用jenkins打包微信小游戏,自动上传资源到cdn,windows版运行jenkins

cocos 版本2.4.11 在windows上jenkins的具体配置和部署&#xff0c;可参考上一篇文章cocos creator使用jenkins打包流程&#xff0c;打包webmobile_jenkins打包,发布,部署cocoscreator-CSDN博客 特别注意&#xff0c;windows上运行jenkins需要关闭windows自己的jenkins服务&a…...

PaddleOCR的Pytorch推理模块

概述 在项目中&#xff0c;遇到文字识别OCR的使用场景。 然而&#xff0c;目前效果最好的PaddleOCR只能用百度的PaddlePaddle框架运行。 常见项目中&#xff0c;往往使用更普遍的Pytorch框架&#xff0c;单独安装PaddlePaddle不仅会让项目过于臃肿&#xff0c;而且可能存在冲…...

操作系统期末复习(一)

一、选择 1.从用户的观点看&#xff0c;操作系统是&#xff08;&#xff09; A.用户与计算机之间的接口 B.控制和管理计算机资源的软件 C.合理地组织计算机工作流程的软件 由若干层次的程序按一定的结构组成的有机体 答案&#xff1a;A 2.操作系统在计算机系统中位于&#x…...

今日行情明日机会——20250521

上证指数缩量收阳线&#xff0c;个股跌多涨少&#xff0c;整体处于日线上涨末端&#xff0c;注意风险。 深证指数&#xff0c;出现60分钟的顶分型&#xff0c;需要观察方向的选择。 2025年5月21日涨停股主要行业方向分析 并购重组 涨停家数&#xff1a;9家。 代表标的&am…...

传统Spring MVC + RESTful 与 Vue3 结合 JWT Token 验证的示例

以下是针对非Spring Boot项目&#xff08;传统Spring MVC&#xff09;的示例 一、项目结构 src/ ├── main/ │ ├── java/ │ │ └── com/ │ │ └── example/ │ │ ├── config/ # 配置类目录 │ │ │ ├─…...

使用Redis的Bitmap实现了签到功能

思路分析 我们可以把 年和月 作为BitMap的key&#xff0c;然后保存到一个BitMap中&#xff0c;每次签到就到对应的位上把数字从0 变为1&#xff0c;只要是1&#xff0c;就代表是这一天签到了&#xff0c;反之咋没有签到。 关键问题 问题一&#xff1a; 什么叫做连续签到天数…...

Unity-编辑器扩展-其二

今天我们来基于之前提到的编辑器扩展的内容来做一些有实际用处的内容&#xff1a; 检查丢失的组件 首先是一个比较实际的内容&#xff1a;当我们在做项目时&#xff0c;经常会涉及到预设体在不同项目或者不同文件路径下的转移&#xff0c;这个时候很容易在某个具体的prefab对…...

项目中Warmup耗时高该如何操作处理

1&#xff09;项目中Warmup耗时高该如何操作处理 2&#xff09;如何在卸载资源后Untracked和Other的内存都回收 3&#xff09;总Triangles的值是否包含了通过GPU Instancing画的三角形 4&#xff09;有没有用Lua来修复虚幻引擎中对C代码进行插桩Hook的方案 这是第432篇UWA技术知…...

php://filter的trick

php://filter流最常见的用法就是文件包含读取文件&#xff0c;但是它不止可以用来读取文件&#xff0c;还可以和RCE&#xff0c;XXE&#xff0c;反序列化等进行组合利用 filter协议介绍 php://filter是php独有的一种协议&#xff0c;它是一种过滤器&#xff0c;可以作为一个中…...

STM32 I2C硬件读写

一、I2C外设简介 STM32内部集成了硬件I2C收发电路&#xff0c;可以由硬件自动执行时钟生成、起始终止条件生成、应答位收发、数据收发等功能&#xff0c;减轻CPU的负担支持多主机模型&#xff08;固定多主机、可变多主机&#xff09;支持7位/10位地址模式支持不同的通讯速度&a…...

Qt+线段拖曳示例代码

Qt线段拖曳示例代码&#xff0c;功能见下图。 代码如下&#xff1a; canvaswidget.h #ifndef CANVASWIDGET_H #define CANVASWIDGET_H#include <QWidget> #include <QPainter> #include <QMouseEvent> #include <QVector>class CanvasWidget : publi…...

计算机网络相关面试题

一、HTTP1.1和HTTP2的区别 HTTP/1&#xff08;主要指 HTTP/1.1&#xff09;和 HTTP/2 是 Web 协议发展中的两个重要版本&#xff0c;二者在性能、协议机制和功能特性上有显著差异。以下从多个维度对比分析&#xff0c;并结合具体案例说明&#xff1a; 一、连接与请求处理方式 1…...

docker中部署Universal Media Server (UMS)

Universal Media Server (UMS) 本身主要是作为桌面服务程序开发的&#xff08;主要面向 Java GUI DLNA 播放&#xff09;&#xff0c;但确实可以通过 Docker 进行部署。虽然官方没有提供 Docker 镜像&#xff0c;但社区有一些可用的方式可以在 Docker 中运行它。 下面是一个可…...

WordPress Elementor零基础教程

一、WordPress Elementor 是什么&#xff1f;—— 可视化网站搭建 “积木工具箱” 基础定义 Elementor 是 WordPress 的一款可视化页面构建插件&#xff0c;就如同网站搭建领域的 “PPT 编辑器”。它能让你无需编写代码&#xff0c;仅通过拖放模块&#xff08;像图片、文本、…...

鸿蒙UI开发——实现一个上拉抽屉效果

1、概 述 在项目开发中&#xff0c;我们可能会遇到临时交互的场景&#xff08;即&#xff1a;弹出一个临时交互框&#xff0c;交互完毕后继续用户的主流程&#xff09;&#xff0c;效果如下&#xff1a; 在ArkUI中&#xff0c;此类弹出窗被称为“半模态页面”&#xff0c;ArkU…...

详细介绍Qwen3技术报告中提到的模型架构技术

详细介绍Qwen3技术报告中提到的一些主流模型架构技术&#xff0c;并为核心流程配上相关的LaTeX公式。 这些技术都是当前大型语言模型&#xff08;LLM&#xff09;领域为了提升模型性能、训练效率、推理速度或稳定性而采用的关键组件。 1. Grouped Query Attention (GQA) - 分组…...

docker面试题(3)

如何临时退出一个正在交互的容器的终端&#xff0c;而不终止它 按ctrlp&#xff0c;后按ctrlq &#xff0c;如果按ctrlc会使容器内的应用进程终止&#xff0c;进而会使容器终止 很多应用容器都默认是后台运行的&#xff0c;怎么查看它们输出的日志信息 使用docker logs &#…...

2025年二级等保实施全攻略:传统架构与云等保方案深度解析

2025年&#xff0c;随着《网络安全法》的深化落实和等保2.0标准的全面推行&#xff0c;二级等保已成为中小企业及非核心业务系统的合规基线。如何在高效满足监管要求的同时&#xff0c;兼顾成本与安全效能&#xff1f;本文将结合最新政策与实战经验&#xff0c;从传统架构到云等…...

技术点对比

数据库 数据库程序在线访问与ORM访问的对比 数据库程序在线ORM访问优点性能好性能差可以处理复杂sql缺点 性能&#xff1a; 复杂sql支持&#xff1a; 开发成本&#xff1a; 架构风格 管道-过滤器风格与数据仓库风格对比 管道-过滤器风格数据仓储风格备注交互方式顺序结构…...

自监督学习与监督学习

&#x1f50d; 一、监督学习 vs 自监督学习&#xff1a;核心区别 维度监督学习&#xff08;Supervised Learning&#xff09;自监督学习&#xff08;Self-Supervised Learning&#xff09;是否需要人工标注的标签✅ 需要&#xff0c;如分类标签、边界框等❌ 不需要&#xff0c…...

Java操作数据库,JDBC

package myjdbc; import com.mysql.jdbc.Driver; import java.sql.Connection; import java.sql.SQLException; import java.sql.Statement; import java.util.Properties; /*** 练习JDBC&#xff0c;完成一些简单的操作。*/ public class jdbc01 {public static void main(Str…...

UML 活动图 (Activity Diagram) 使用案例

UML 活动图使用案例 UML 活动图 (Activity Diagram) 使用案例活动图的主要元素典型使用案例1. 用户登录流程2. 在线购物流程3. 订单处理系统4. 文件审批流程 活动图的优势何时使用活动图 UML 活动图 (Activity Diagram) 使用案例 活动图是UML中用于描述业务流程或系统工作流程…...

回溯法求解N皇后问题

目录 前言 一、回溯法是什么&#xff1f; 二、N皇后问题描述 分析解题思路 三、算法设计 1、递归法 2、非递归法 总结 前言 本文将从递归形式和非递归形式两种方法来介绍求解N皇后问题的回溯法&#xff0c;后续也会更新更多有关算法分析这方面的问题欢迎大家关注~&#x1f929…...

网络流量分析工具ntopng的安装与基本使用

网络流量分析工具ntopng的安装与基本使用 一、ntopng基本介绍1.1 ntopng简介1.2 主要特点1.3 使用场景 二、本地环境介绍2.1 本地环境规划2.2 本次实践介绍 三、安装ntopng工具3.1 官网地址3.2 配置软件源3.3 添加软件源3.4 安装ntopng 四、ntopng的基本配置4.1 修改配置文件4.…...

新导游入行规范与职业发展指导

随着旅游行业的蓬勃发展&#xff0c;导游作为旅游服务的重要环节&#xff0c;其职业素养和专业能力备受关注。对于新入行的导游而言&#xff0c;了解行业规范&#xff0c;明确职业发展方向&#xff0c;是开启职业生涯的重要一步。​ 一、严格遵守行业规范​ 持证上岗&#xf…...

数据结构与算法——堆

堆 树树的概念与结构树的相关术语树的表示树形结构实际运用场景 二叉树概念与结构特殊的二叉树满二叉树完全二叉树 二叉树存储结构顺序结构链式结构 实现顺序结构二叉树堆的概念与结构堆的实现向上调整算法&#xff08;插入数据&#xff09;向下调整算法 堆的应用堆排序(建堆)向…...

【写在创作纪念日】基于SpringBoot和PostGIS的各省东西南北四至极点区县可视化

目录 前言 一、空间检索简介 1、空间表结构 2、四至空间检索 二、前后端实现 1、后端实现 2、前端集成 三、成果展示 1、东部省份 2、西部省份 3、南部省份 4、北部省份 5、中部省份 四、总结 前言 在当今数字化时代&#xff0c;地理信息数据的分析与可视化对于众…...