all repos — fluxbox @ cdd6861aff5cd06f84c9d23456b5a92b4202e423

custom fork of the fluxbox windowmanager

Support editing utf-8 text in TextBox

aka "Flüxbøx"

βµγ, pardon,
BUG: 720
Thomas Lübking thomas.luebking@gmail.com
commit

cdd6861aff5cd06f84c9d23456b5a92b4202e423

parent

2047b1a2ba9ea5d3df77c8de9b4e2b3fd3a40a6d

5 files changed, 113 insertions(+), 23 deletions(-)

jump to
M src/FbTk/App.ccsrc/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 @@ }

} FbStringUtil::init(); + + m_xim = 0; + if (setmodifiers && FbStringUtil::haveUTF8()) { + m_xim = XOpenIM(m_display, NULL, NULL, NULL); + } } App::~App() {
M src/FbTk/App.hhsrc/FbTk/App.hh

@@ -53,6 +53,7 @@ virtual void eventLoop();

/// 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
M src/FbTk/EventManager.ccsrc/FbTk/EventManager.cc

@@ -161,7 +161,8 @@ return;

switch (ev.type) { case KeyPress: - evhand->keyPressEvent(ev.xkey); + if (!XFilterEvent(&ev, win)) + evhand->keyPressEvent(ev.xkey); break; case KeyRelease: evhand->keyReleaseEvent(ev.xkey);
M src/FbTk/TextBox.ccsrc/FbTk/TextBox.cc

@@ -43,18 +43,22 @@ #endif

#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 @@ 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); }

@@ -111,36 +116,43 @@ m_cursor_pos = m_end_pos - m_start_pos;

} 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::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 @@ t.erase(pos, length);

m_text.setLogical(t); adjustEndPos(); } - if (length > 1) + if (selected && length > 1) adjustStartPos(); }

@@ -168,7 +185,8 @@ if (hasSelection())

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::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 @@ keychar[0] = ',';

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 @@ m_select_pos = std::string::npos;

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())

@@ -525,6 +576,29 @@ }

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
M src/FbTk/TextBox.hhsrc/FbTk/TextBox.hh

@@ -58,6 +58,7 @@

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 @@ void adjustStartPos();

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