From cdd6861aff5cd06f84c9d23456b5a92b4202e423 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20L=C3=BCbking?= Date: Sun, 4 Sep 2016 00:08:04 +0200 Subject: Support editing utf-8 text in TextBox MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit aka "Flüxbøx" βµγ, pardon, BUG: 720 --- src/FbTk/App.cc | 8 ++++ src/FbTk/App.hh | 2 + src/FbTk/EventManager.cc | 3 +- src/FbTk/TextBox.cc | 118 ++++++++++++++++++++++++++++++++++++++--------- src/FbTk/TextBox.hh | 5 ++ 5 files changed, 113 insertions(+), 23 deletions(-) diff --git a/src/FbTk/App.cc b/src/FbTk/App.cc index a3e806a..c90f7c8 100644 --- a/src/FbTk/App.cc +++ b/src/FbTk/App.cc @@ -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() { diff --git a/src/FbTk/App.hh b/src/FbTk/App.hh index b83dd15..a31ed06 100644 --- a/src/FbTk/App.hh +++ b/src/FbTk/App.hh @@ -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 diff --git a/src/FbTk/EventManager.cc b/src/FbTk/EventManager.cc index b9d5e98..3df56de 100644 --- a/src/FbTk/EventManager.cc +++ b/src/FbTk/EventManager.cc @@ -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); diff --git a/src/FbTk/TextBox.cc b/src/FbTk/TextBox.cc index 8c0af87..bf46ae3 100644 --- a/src/FbTk/TextBox.cc +++ b/src/FbTk/TextBox.cc @@ -43,18 +43,22 @@ #include #include +#include + 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 diff --git a/src/FbTk/TextBox.hh b/src/FbTk/TextBox.hh index a3b857f..20f3610 100644 --- a/src/FbTk/TextBox.hh +++ b/src/FbTk/TextBox.hh @@ -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 -- cgit v0.11.2