diff options
author | Thomas Lübking <thomas.luebking@gmail.com> | 2016-09-03 22:08:04 (GMT) |
---|---|---|
committer | Mathias Gumz <akira@fluxbox.org> | 2016-09-11 07:56:33 (GMT) |
commit | cdd6861aff5cd06f84c9d23456b5a92b4202e423 (patch) | |
tree | a517c8a2638fe146c2db4af075dbf05f7db1e917 /src | |
parent | 2047b1a2ba9ea5d3df77c8de9b4e2b3fd3a40a6d (diff) | |
download | fluxbox-cdd6861aff5cd06f84c9d23456b5a92b4202e423.zip fluxbox-cdd6861aff5cd06f84c9d23456b5a92b4202e423.tar.bz2 |
Support editing utf-8 text in TextBox
aka "Flüxbøx"
βµγ, pardon,
BUG: 720
Diffstat (limited to 'src')
-rw-r--r-- | src/FbTk/App.cc | 8 | ||||
-rw-r--r-- | src/FbTk/App.hh | 2 | ||||
-rw-r--r-- | src/FbTk/EventManager.cc | 3 | ||||
-rw-r--r-- | src/FbTk/TextBox.cc | 118 | ||||
-rw-r--r-- | 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) { | |||
45 | if (s_app != 0) | 45 | if (s_app != 0) |
46 | throw std::string("Can't create more than one instance of FbTk::App"); | 46 | throw std::string("Can't create more than one instance of FbTk::App"); |
47 | s_app = this; | 47 | s_app = this; |
48 | // with recent versions of X11 one needs to specify XSetLocaleModifiers, | ||
49 | // otherwise no extra events won't be generated. | ||
50 | const bool setmodifiers = XSetLocaleModifiers("@im=none"); | ||
48 | // this allows the use of std::string.c_str(), which returns | 51 | // this allows the use of std::string.c_str(), which returns |
49 | // a blank string, rather than a null string, so we make them equivalent | 52 | // a blank string, rather than a null string, so we make them equivalent |
50 | if (displayname != 0 && displayname[0] == '\0') | 53 | if (displayname != 0 && displayname[0] == '\0') |
@@ -59,6 +62,11 @@ App::App(const char *displayname):m_done(false), m_display(0) { | |||
59 | } | 62 | } |
60 | 63 | ||
61 | FbStringUtil::init(); | 64 | FbStringUtil::init(); |
65 | |||
66 | m_xim = 0; | ||
67 | if (setmodifiers && FbStringUtil::haveUTF8()) { | ||
68 | m_xim = XOpenIM(m_display, NULL, NULL, NULL); | ||
69 | } | ||
62 | } | 70 | } |
63 | 71 | ||
64 | App::~App() { | 72 | 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: | |||
53 | /// forces an end to event loop | 53 | /// forces an end to event loop |
54 | void end(); | 54 | void end(); |
55 | bool done() const { return m_done; } | 55 | bool done() const { return m_done; } |
56 | const XIM &inputModule() const { return m_xim; } | ||
56 | 57 | ||
57 | // the setenv()-routine is not everywhere available and | 58 | // the setenv()-routine is not everywhere available and |
58 | // putenv() doesnt manage the strings in the environment | 59 | // putenv() doesnt manage the strings in the environment |
@@ -62,6 +63,7 @@ private: | |||
62 | static App *s_app; | 63 | static App *s_app; |
63 | bool m_done; | 64 | bool m_done; |
64 | Display *m_display; | 65 | Display *m_display; |
66 | XIM m_xim; | ||
65 | }; | 67 | }; |
66 | 68 | ||
67 | } // end namespace FbTk | 69 | } // 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) { | |||
161 | 161 | ||
162 | switch (ev.type) { | 162 | switch (ev.type) { |
163 | case KeyPress: | 163 | case KeyPress: |
164 | evhand->keyPressEvent(ev.xkey); | 164 | if (!XFilterEvent(&ev, win)) |
165 | evhand->keyPressEvent(ev.xkey); | ||
165 | break; | 166 | break; |
166 | case KeyRelease: | 167 | case KeyRelease: |
167 | evhand->keyReleaseEvent(ev.xkey); | 168 | 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 @@ | |||
43 | #include <X11/keysym.h> | 43 | #include <X11/keysym.h> |
44 | #include <X11/Xutil.h> | 44 | #include <X11/Xutil.h> |
45 | 45 | ||
46 | #include <iostream> | ||
47 | |||
46 | namespace FbTk { | 48 | namespace FbTk { |
47 | 49 | ||
50 | |||
48 | TextBox::TextBox(int screen_num, | 51 | TextBox::TextBox(int screen_num, |
49 | const Font &font, const std::string &text): | 52 | const Font &font, const std::string &text): |
50 | FbWindow(screen_num, 0, 0, 1, 1, ExposureMask | KeyPressMask | ButtonPressMask), | 53 | FbWindow(screen_num, 0, 0, 1, 1, ExposureMask | KeyPressMask | ButtonPressMask | FocusChangeMask | KeymapStateMask), |
51 | m_font(&font), | 54 | m_font(&font), |
52 | m_text(text), | 55 | m_text(text), |
53 | m_gc(0), | 56 | m_gc(0), |
54 | m_cursor_pos(0), | 57 | m_cursor_pos(0), |
55 | m_start_pos(0), | 58 | m_start_pos(0), |
56 | m_end_pos(0), | 59 | m_end_pos(0), |
57 | m_select_pos(std::string::npos) { | 60 | m_select_pos(std::string::npos), |
61 | m_xic(0) { | ||
58 | 62 | ||
59 | FbTk::EventManager::instance()->add(*this, *this); | 63 | FbTk::EventManager::instance()->add(*this, *this); |
60 | } | 64 | } |
@@ -68,7 +72,8 @@ TextBox::TextBox(const FbWindow &parent, | |||
68 | m_cursor_pos(0), | 72 | m_cursor_pos(0), |
69 | m_start_pos(0), | 73 | m_start_pos(0), |
70 | m_end_pos(0), | 74 | m_end_pos(0), |
71 | m_select_pos(std::string::npos) { | 75 | m_select_pos(std::string::npos), |
76 | m_xic(0) { | ||
72 | 77 | ||
73 | FbTk::EventManager::instance()->add(*this, *this); | 78 | FbTk::EventManager::instance()->add(*this, *this); |
74 | } | 79 | } |
@@ -111,36 +116,43 @@ void TextBox::cursorEnd() { | |||
111 | } | 116 | } |
112 | 117 | ||
113 | void TextBox::cursorForward() { | 118 | void TextBox::cursorForward() { |
114 | if (m_start_pos + cursorPosition() < m_end_pos) | 119 | StringRange r = charRange(m_start_pos + cursorPosition()); |
115 | m_cursor_pos++; | 120 | const std::string::size_type s = r.end - r.begin + 1; |
121 | if (r.end < m_end_pos) | ||
122 | m_cursor_pos = r.end + 1 - m_start_pos; | ||
116 | else if (m_end_pos < text().size()) { | 123 | else if (m_end_pos < text().size()) { |
117 | m_cursor_pos++; | 124 | m_cursor_pos = r.end + 1 - m_start_pos; |
118 | m_end_pos++; | 125 | m_end_pos += s; |
119 | adjustStartPos(); | 126 | adjustStartPos(); |
120 | } | 127 | } |
121 | } | 128 | } |
122 | 129 | ||
123 | void TextBox::cursorBackward() { | 130 | void TextBox::cursorBackward() { |
124 | if (cursorPosition()) | 131 | if (m_start_pos || cursorPosition()) { |
125 | m_cursor_pos--; | 132 | StringRange r = charRange(m_start_pos + cursorPosition() - 1); |
126 | else if (m_start_pos) { | 133 | const std::string::size_type s = r.end - r.begin + 1; |
127 | m_start_pos--; | 134 | if (cursorPosition()) |
128 | adjustEndPos(); | 135 | m_cursor_pos = r.begin - m_start_pos; |
136 | else if (m_start_pos) { | ||
137 | m_start_pos -= s; | ||
138 | adjustEndPos(); | ||
139 | } | ||
129 | } | 140 | } |
130 | } | 141 | } |
131 | 142 | ||
132 | void TextBox::backspace() { | 143 | void TextBox::backspace() { |
133 | if (hasSelection()) | 144 | if (hasSelection()) |
134 | return deleteForward(); | 145 | return deleteForward(); |
135 | |||
136 | if (m_start_pos || cursorPosition()) { | 146 | if (m_start_pos || cursorPosition()) { |
137 | FbString t = text(); | 147 | FbString t = text(); |
138 | t.erase(m_start_pos + cursorPosition() - 1, 1); | 148 | StringRange r = charRange(m_start_pos + cursorPosition() - 1); |
149 | const std::string::size_type s = r.end - r.begin + 1; | ||
150 | t.erase(r.begin, s); | ||
139 | m_text.setLogical(t); | 151 | m_text.setLogical(t); |
140 | if (cursorPosition()) | 152 | if (cursorPosition()) |
141 | setCursorPosition(cursorPosition() - 1); | 153 | setCursorPosition(r.begin - m_start_pos); |
142 | else | 154 | else |
143 | m_start_pos--; | 155 | m_start_pos -= s; |
144 | adjustEndPos(); | 156 | adjustEndPos(); |
145 | } | 157 | } |
146 | } | 158 | } |
@@ -148,10 +160,15 @@ void TextBox::backspace() { | |||
148 | void TextBox::deleteForward() { | 160 | void TextBox::deleteForward() { |
149 | std::string::size_type pos = m_start_pos + m_cursor_pos; | 161 | std::string::size_type pos = m_start_pos + m_cursor_pos; |
150 | int length = 1; | 162 | int length = 1; |
151 | if (hasSelection()) { | 163 | bool selected = false; |
164 | if (selected = hasSelection()) { | ||
152 | pos = std::min(m_start_pos + m_cursor_pos, m_select_pos); | 165 | pos = std::min(m_start_pos + m_cursor_pos, m_select_pos); |
153 | length = std::max(m_start_pos + m_cursor_pos, m_select_pos) - pos; | 166 | length = std::max(m_start_pos + m_cursor_pos, m_select_pos) - pos; |
154 | m_cursor_pos = pos - m_start_pos; | 167 | m_cursor_pos = pos - m_start_pos; |
168 | } else { | ||
169 | StringRange r = charRange(pos); | ||
170 | pos = r.begin; | ||
171 | length = r.end - r.begin + 1; | ||
155 | } | 172 | } |
156 | if (pos < m_end_pos) { | 173 | if (pos < m_end_pos) { |
157 | FbString t = text(); | 174 | FbString t = text(); |
@@ -159,7 +176,7 @@ void TextBox::deleteForward() { | |||
159 | m_text.setLogical(t); | 176 | m_text.setLogical(t); |
160 | adjustEndPos(); | 177 | adjustEndPos(); |
161 | } | 178 | } |
162 | if (length > 1) | 179 | if (selected && length > 1) |
163 | adjustStartPos(); | 180 | adjustStartPos(); |
164 | } | 181 | } |
165 | 182 | ||
@@ -168,7 +185,8 @@ void TextBox::insertText(const std::string &val) { | |||
168 | deleteForward(); | 185 | deleteForward(); |
169 | 186 | ||
170 | FbString t = text(); | 187 | FbString t = text(); |
171 | t.insert(m_start_pos + cursorPosition(), val); | 188 | std::string::size_type pos = m_start_pos + m_cursor_pos; |
189 | t.insert(pos ? charRange(pos - 1).end + 1 : pos, val); | ||
172 | m_text.setLogical(t); | 190 | m_text.setLogical(t); |
173 | m_cursor_pos += val.size(); | 191 | m_cursor_pos += val.size(); |
174 | m_end_pos += val.size(); | 192 | m_end_pos += val.size(); |
@@ -267,12 +285,17 @@ void TextBox::buttonPressEvent(XButtonEvent &event) { | |||
267 | } | 285 | } |
268 | 286 | ||
269 | void TextBox::keyPressEvent(XKeyEvent &event) { | 287 | void TextBox::keyPressEvent(XKeyEvent &event) { |
270 | 288 | ||
271 | event.state = KeyUtil::instance().cleanMods(event.state); | 289 | event.state = KeyUtil::instance().cleanMods(event.state); |
272 | 290 | ||
273 | KeySym ks; | 291 | KeySym ks; |
274 | char keychar[1]; | 292 | char keychar[20]; |
275 | XLookupString(&event, keychar, 1, &ks, 0); | 293 | int count = 1; |
294 | if (m_xic) | ||
295 | count = Xutf8LookupString(m_xic, &event, keychar, 20, &ks, 0); | ||
296 | else | ||
297 | XLookupString(&event, keychar, 1, &ks, 0); | ||
298 | |||
276 | // a modifier key by itself doesn't do anything | 299 | // a modifier key by itself doesn't do anything |
277 | if (IsModifierKey(ks)) return; | 300 | if (IsModifierKey(ks)) return; |
278 | 301 | ||
@@ -401,6 +424,17 @@ void TextBox::keyPressEvent(XKeyEvent &event) { | |||
401 | break; | 424 | break; |
402 | } | 425 | } |
403 | } | 426 | } |
427 | if (count > 1 && count < 20) { | ||
428 | wchar_t wc; | ||
429 | count = mbrtowc(&wc, keychar, count, 0); | ||
430 | if (count > 0 && iswprint(wc)) { | ||
431 | keychar[count] = '\0'; | ||
432 | std::string val; | ||
433 | val += (char*)keychar; | ||
434 | insertText(val); | ||
435 | m_select_pos = std::string::npos; | ||
436 | } | ||
437 | } | ||
404 | if (isprint(keychar[0])) { | 438 | if (isprint(keychar[0])) { |
405 | std::string val; | 439 | std::string val; |
406 | val += keychar[0]; | 440 | val += keychar[0]; |
@@ -412,6 +446,23 @@ void TextBox::keyPressEvent(XKeyEvent &event) { | |||
412 | clear(); | 446 | clear(); |
413 | } | 447 | } |
414 | 448 | ||
449 | void TextBox::handleEvent(XEvent &event) { | ||
450 | if (event.type == KeymapNotify) { | ||
451 | XRefreshKeyboardMapping(&event.xmapping); | ||
452 | } else if (event.type == FocusIn) { | ||
453 | if (!m_xic && App::instance()->inputModule()) | ||
454 | m_xic = XCreateIC(App::instance()->inputModule(), XNInputStyle, XIMPreeditNothing | XIMStatusNothing, XNClientWindow, window(), NULL); | ||
455 | if (m_xic) | ||
456 | XSetICFocus(m_xic); | ||
457 | } else if (event.type == FocusIn) { | ||
458 | if (m_xic) { | ||
459 | XUnsetICFocus(m_xic); | ||
460 | XFree(m_xic); | ||
461 | m_xic = 0; | ||
462 | } | ||
463 | } | ||
464 | } | ||
465 | |||
415 | void TextBox::setCursorPosition(int pos) { | 466 | void TextBox::setCursorPosition(int pos) { |
416 | m_cursor_pos = pos < 0 ? 0 : pos; | 467 | m_cursor_pos = pos < 0 ? 0 : pos; |
417 | if (m_cursor_pos > text().size()) | 468 | if (m_cursor_pos > text().size()) |
@@ -527,4 +578,27 @@ void TextBox::selectAll() { | |||
527 | select(0, m_text.visual().size()); | 578 | select(0, m_text.visual().size()); |
528 | } | 579 | } |
529 | 580 | ||
581 | TextBox::StringRange TextBox::charRange(std::string::size_type pos) const { | ||
582 | auto isUtf8Head = [](char c) { | ||
583 | const char indicator = (1<<7)|(1<<6); // first byte starts 11, following 10 | ||
584 | return (c & indicator) == indicator; | ||
585 | }; | ||
586 | |||
587 | StringRange range = {pos, pos}; | ||
588 | FbString t = text(); | ||
589 | |||
590 | if (pos < 0 || pos >= t.size() || t.at(pos) > 0) // invalid pos or ASCII | ||
591 | return range; | ||
592 | |||
593 | while (range.begin > 0 && t.at(range.begin) < 0 && !isUtf8Head(t.at(range.begin))) | ||
594 | --range.begin; | ||
595 | |||
596 | if (isUtf8Head(t.at(range.end))) // if pos is a utf-8 head, move into the range | ||
597 | ++range.end; | ||
598 | while (range.end < t.size() - 1 && t.at(range.end + 1) < 0 && !isUtf8Head(t.at(range.end + 1))) | ||
599 | ++range.end; | ||
600 | |||
601 | return range; | ||
602 | } | ||
603 | |||
530 | } // end namespace FbTk | 604 | } // 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: | |||
58 | void exposeEvent(XExposeEvent &event); | 58 | void exposeEvent(XExposeEvent &event); |
59 | void buttonPressEvent(XButtonEvent &event); | 59 | void buttonPressEvent(XButtonEvent &event); |
60 | void keyPressEvent(XKeyEvent &event); | 60 | void keyPressEvent(XKeyEvent &event); |
61 | void handleEvent(XEvent &event); | ||
61 | 62 | ||
62 | const FbString &text() const { return m_text.logical(); } | 63 | const FbString &text() const { return m_text.logical(); } |
63 | const Font &font() const { return *m_font; } | 64 | const Font &font() const { return *m_font; } |
@@ -79,10 +80,14 @@ private: | |||
79 | 80 | ||
80 | void adjustPos(); | 81 | void adjustPos(); |
81 | 82 | ||
83 | typedef struct { std::string::size_type begin, end; } StringRange; | ||
84 | StringRange charRange(std::string::size_type pos) const; | ||
85 | |||
82 | const FbTk::Font *m_font; | 86 | const FbTk::Font *m_font; |
83 | BiDiString m_text; | 87 | BiDiString m_text; |
84 | GC m_gc; | 88 | GC m_gc; |
85 | std::string::size_type m_cursor_pos, m_start_pos, m_end_pos, m_select_pos; | 89 | std::string::size_type m_cursor_pos, m_start_pos, m_end_pos, m_select_pos; |
90 | XIC m_xic; | ||
86 | }; | 91 | }; |
87 | 92 | ||
88 | } // end namespace FbTk | 93 | } // end namespace FbTk |