Support editing utf-8 text in TextBox
aka "Flüxbøx" βµγ, pardon, BUG: 720
This commit is contained in:
parent
2047b1a2ba
commit
cdd6861aff
5 changed files with 113 additions and 23 deletions
|
@ -45,6 +45,9 @@ App::App(const char *displayname):m_done(false), m_display(0) {
|
|||
if (s_app != 0)
|
||||
throw std::string("Can't create more than one instance of FbTk::App");
|
||||
s_app = this;
|
||||
// with recent versions of X11 one needs to specify XSetLocaleModifiers,
|
||||
// otherwise no extra events won't be generated.
|
||||
const bool setmodifiers = XSetLocaleModifiers("@im=none");
|
||||
// this allows the use of std::string.c_str(), which returns
|
||||
// a blank string, rather than a null string, so we make them equivalent
|
||||
if (displayname != 0 && displayname[0] == '\0')
|
||||
|
@ -59,6 +62,11 @@ App::App(const char *displayname):m_done(false), m_display(0) {
|
|||
}
|
||||
|
||||
FbStringUtil::init();
|
||||
|
||||
m_xim = 0;
|
||||
if (setmodifiers && FbStringUtil::haveUTF8()) {
|
||||
m_xim = XOpenIM(m_display, NULL, NULL, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
App::~App() {
|
||||
|
|
|
@ -53,6 +53,7 @@ public:
|
|||
/// forces an end to event loop
|
||||
void end();
|
||||
bool done() const { return m_done; }
|
||||
const XIM &inputModule() const { return m_xim; }
|
||||
|
||||
// the setenv()-routine is not everywhere available and
|
||||
// putenv() doesnt manage the strings in the environment
|
||||
|
@ -62,6 +63,7 @@ private:
|
|||
static App *s_app;
|
||||
bool m_done;
|
||||
Display *m_display;
|
||||
XIM m_xim;
|
||||
};
|
||||
|
||||
} // end namespace FbTk
|
||||
|
|
|
@ -161,7 +161,8 @@ void EventManager::dispatch(Window win, XEvent &ev, bool parent) {
|
|||
|
||||
switch (ev.type) {
|
||||
case KeyPress:
|
||||
evhand->keyPressEvent(ev.xkey);
|
||||
if (!XFilterEvent(&ev, win))
|
||||
evhand->keyPressEvent(ev.xkey);
|
||||
break;
|
||||
case KeyRelease:
|
||||
evhand->keyReleaseEvent(ev.xkey);
|
||||
|
|
|
@ -43,18 +43,22 @@
|
|||
#include <X11/keysym.h>
|
||||
#include <X11/Xutil.h>
|
||||
|
||||
#include <iostream>
|
||||
|
||||
namespace FbTk {
|
||||
|
||||
|
||||
TextBox::TextBox(int screen_num,
|
||||
const Font &font, const std::string &text):
|
||||
FbWindow(screen_num, 0, 0, 1, 1, ExposureMask | KeyPressMask | ButtonPressMask),
|
||||
FbWindow(screen_num, 0, 0, 1, 1, ExposureMask | KeyPressMask | ButtonPressMask | FocusChangeMask | KeymapStateMask),
|
||||
m_font(&font),
|
||||
m_text(text),
|
||||
m_gc(0),
|
||||
m_cursor_pos(0),
|
||||
m_start_pos(0),
|
||||
m_end_pos(0),
|
||||
m_select_pos(std::string::npos) {
|
||||
m_select_pos(std::string::npos),
|
||||
m_xic(0) {
|
||||
|
||||
FbTk::EventManager::instance()->add(*this, *this);
|
||||
}
|
||||
|
@ -68,7 +72,8 @@ TextBox::TextBox(const FbWindow &parent,
|
|||
m_cursor_pos(0),
|
||||
m_start_pos(0),
|
||||
m_end_pos(0),
|
||||
m_select_pos(std::string::npos) {
|
||||
m_select_pos(std::string::npos),
|
||||
m_xic(0) {
|
||||
|
||||
FbTk::EventManager::instance()->add(*this, *this);
|
||||
}
|
||||
|
@ -111,36 +116,43 @@ void TextBox::cursorEnd() {
|
|||
}
|
||||
|
||||
void TextBox::cursorForward() {
|
||||
if (m_start_pos + cursorPosition() < m_end_pos)
|
||||
m_cursor_pos++;
|
||||
StringRange r = charRange(m_start_pos + cursorPosition());
|
||||
const std::string::size_type s = r.end - r.begin + 1;
|
||||
if (r.end < m_end_pos)
|
||||
m_cursor_pos = r.end + 1 - m_start_pos;
|
||||
else if (m_end_pos < text().size()) {
|
||||
m_cursor_pos++;
|
||||
m_end_pos++;
|
||||
m_cursor_pos = r.end + 1 - m_start_pos;
|
||||
m_end_pos += s;
|
||||
adjustStartPos();
|
||||
}
|
||||
}
|
||||
|
||||
void TextBox::cursorBackward() {
|
||||
if (cursorPosition())
|
||||
m_cursor_pos--;
|
||||
else if (m_start_pos) {
|
||||
m_start_pos--;
|
||||
adjustEndPos();
|
||||
if (m_start_pos || cursorPosition()) {
|
||||
StringRange r = charRange(m_start_pos + cursorPosition() - 1);
|
||||
const std::string::size_type s = r.end - r.begin + 1;
|
||||
if (cursorPosition())
|
||||
m_cursor_pos = r.begin - m_start_pos;
|
||||
else if (m_start_pos) {
|
||||
m_start_pos -= s;
|
||||
adjustEndPos();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TextBox::backspace() {
|
||||
if (hasSelection())
|
||||
return deleteForward();
|
||||
|
||||
if (m_start_pos || cursorPosition()) {
|
||||
FbString t = text();
|
||||
t.erase(m_start_pos + cursorPosition() - 1, 1);
|
||||
StringRange r = charRange(m_start_pos + cursorPosition() - 1);
|
||||
const std::string::size_type s = r.end - r.begin + 1;
|
||||
t.erase(r.begin, s);
|
||||
m_text.setLogical(t);
|
||||
if (cursorPosition())
|
||||
setCursorPosition(cursorPosition() - 1);
|
||||
setCursorPosition(r.begin - m_start_pos);
|
||||
else
|
||||
m_start_pos--;
|
||||
m_start_pos -= s;
|
||||
adjustEndPos();
|
||||
}
|
||||
}
|
||||
|
@ -148,10 +160,15 @@ void TextBox::backspace() {
|
|||
void TextBox::deleteForward() {
|
||||
std::string::size_type pos = m_start_pos + m_cursor_pos;
|
||||
int length = 1;
|
||||
if (hasSelection()) {
|
||||
bool selected = false;
|
||||
if (selected = hasSelection()) {
|
||||
pos = std::min(m_start_pos + m_cursor_pos, m_select_pos);
|
||||
length = std::max(m_start_pos + m_cursor_pos, m_select_pos) - pos;
|
||||
m_cursor_pos = pos - m_start_pos;
|
||||
} else {
|
||||
StringRange r = charRange(pos);
|
||||
pos = r.begin;
|
||||
length = r.end - r.begin + 1;
|
||||
}
|
||||
if (pos < m_end_pos) {
|
||||
FbString t = text();
|
||||
|
@ -159,7 +176,7 @@ void TextBox::deleteForward() {
|
|||
m_text.setLogical(t);
|
||||
adjustEndPos();
|
||||
}
|
||||
if (length > 1)
|
||||
if (selected && length > 1)
|
||||
adjustStartPos();
|
||||
}
|
||||
|
||||
|
@ -168,7 +185,8 @@ void TextBox::insertText(const std::string &val) {
|
|||
deleteForward();
|
||||
|
||||
FbString t = text();
|
||||
t.insert(m_start_pos + cursorPosition(), val);
|
||||
std::string::size_type pos = m_start_pos + m_cursor_pos;
|
||||
t.insert(pos ? charRange(pos - 1).end + 1 : pos, val);
|
||||
m_text.setLogical(t);
|
||||
m_cursor_pos += val.size();
|
||||
m_end_pos += val.size();
|
||||
|
@ -267,12 +285,17 @@ void TextBox::buttonPressEvent(XButtonEvent &event) {
|
|||
}
|
||||
|
||||
void TextBox::keyPressEvent(XKeyEvent &event) {
|
||||
|
||||
|
||||
event.state = KeyUtil::instance().cleanMods(event.state);
|
||||
|
||||
KeySym ks;
|
||||
char keychar[1];
|
||||
XLookupString(&event, keychar, 1, &ks, 0);
|
||||
char keychar[20];
|
||||
int count = 1;
|
||||
if (m_xic)
|
||||
count = Xutf8LookupString(m_xic, &event, keychar, 20, &ks, 0);
|
||||
else
|
||||
XLookupString(&event, keychar, 1, &ks, 0);
|
||||
|
||||
// a modifier key by itself doesn't do anything
|
||||
if (IsModifierKey(ks)) return;
|
||||
|
||||
|
@ -401,6 +424,17 @@ void TextBox::keyPressEvent(XKeyEvent &event) {
|
|||
break;
|
||||
}
|
||||
}
|
||||
if (count > 1 && count < 20) {
|
||||
wchar_t wc;
|
||||
count = mbrtowc(&wc, keychar, count, 0);
|
||||
if (count > 0 && iswprint(wc)) {
|
||||
keychar[count] = '\0';
|
||||
std::string val;
|
||||
val += (char*)keychar;
|
||||
insertText(val);
|
||||
m_select_pos = std::string::npos;
|
||||
}
|
||||
}
|
||||
if (isprint(keychar[0])) {
|
||||
std::string val;
|
||||
val += keychar[0];
|
||||
|
@ -412,6 +446,23 @@ void TextBox::keyPressEvent(XKeyEvent &event) {
|
|||
clear();
|
||||
}
|
||||
|
||||
void TextBox::handleEvent(XEvent &event) {
|
||||
if (event.type == KeymapNotify) {
|
||||
XRefreshKeyboardMapping(&event.xmapping);
|
||||
} else if (event.type == FocusIn) {
|
||||
if (!m_xic && App::instance()->inputModule())
|
||||
m_xic = XCreateIC(App::instance()->inputModule(), XNInputStyle, XIMPreeditNothing | XIMStatusNothing, XNClientWindow, window(), NULL);
|
||||
if (m_xic)
|
||||
XSetICFocus(m_xic);
|
||||
} else if (event.type == FocusIn) {
|
||||
if (m_xic) {
|
||||
XUnsetICFocus(m_xic);
|
||||
XFree(m_xic);
|
||||
m_xic = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TextBox::setCursorPosition(int pos) {
|
||||
m_cursor_pos = pos < 0 ? 0 : pos;
|
||||
if (m_cursor_pos > text().size())
|
||||
|
@ -527,4 +578,27 @@ void TextBox::selectAll() {
|
|||
select(0, m_text.visual().size());
|
||||
}
|
||||
|
||||
TextBox::StringRange TextBox::charRange(std::string::size_type pos) const {
|
||||
auto isUtf8Head = [](char c) {
|
||||
const char indicator = (1<<7)|(1<<6); // first byte starts 11, following 10
|
||||
return (c & indicator) == indicator;
|
||||
};
|
||||
|
||||
StringRange range = {pos, pos};
|
||||
FbString t = text();
|
||||
|
||||
if (pos < 0 || pos >= t.size() || t.at(pos) > 0) // invalid pos or ASCII
|
||||
return range;
|
||||
|
||||
while (range.begin > 0 && t.at(range.begin) < 0 && !isUtf8Head(t.at(range.begin)))
|
||||
--range.begin;
|
||||
|
||||
if (isUtf8Head(t.at(range.end))) // if pos is a utf-8 head, move into the range
|
||||
++range.end;
|
||||
while (range.end < t.size() - 1 && t.at(range.end + 1) < 0 && !isUtf8Head(t.at(range.end + 1)))
|
||||
++range.end;
|
||||
|
||||
return range;
|
||||
}
|
||||
|
||||
} // end namespace FbTk
|
||||
|
|
|
@ -58,6 +58,7 @@ public:
|
|||
void exposeEvent(XExposeEvent &event);
|
||||
void buttonPressEvent(XButtonEvent &event);
|
||||
void keyPressEvent(XKeyEvent &event);
|
||||
void handleEvent(XEvent &event);
|
||||
|
||||
const FbString &text() const { return m_text.logical(); }
|
||||
const Font &font() const { return *m_font; }
|
||||
|
@ -79,10 +80,14 @@ private:
|
|||
|
||||
void adjustPos();
|
||||
|
||||
typedef struct { std::string::size_type begin, end; } StringRange;
|
||||
StringRange charRange(std::string::size_type pos) const;
|
||||
|
||||
const FbTk::Font *m_font;
|
||||
BiDiString m_text;
|
||||
GC m_gc;
|
||||
std::string::size_type m_cursor_pos, m_start_pos, m_end_pos, m_select_pos;
|
||||
XIC m_xic;
|
||||
};
|
||||
|
||||
} // end namespace FbTk
|
||||
|
|
Loading…
Reference in a new issue