aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorThomas Lübking <thomas.luebking@gmail.com>2016-09-03 22:08:04 (GMT)
committerMathias Gumz <akira@fluxbox.org>2016-09-11 07:56:33 (GMT)
commitcdd6861aff5cd06f84c9d23456b5a92b4202e423 (patch)
treea517c8a2638fe146c2db4af075dbf05f7db1e917
parent2047b1a2ba9ea5d3df77c8de9b4e2b3fd3a40a6d (diff)
downloadfluxbox-cdd6861aff5cd06f84c9d23456b5a92b4202e423.zip
fluxbox-cdd6861aff5cd06f84c9d23456b5a92b4202e423.tar.bz2
Support editing utf-8 text in TextBox
aka "Flüxbøx" βµγ, pardon, BUG: 720
-rw-r--r--src/FbTk/App.cc8
-rw-r--r--src/FbTk/App.hh2
-rw-r--r--src/FbTk/EventManager.cc3
-rw-r--r--src/FbTk/TextBox.cc118
-rw-r--r--src/FbTk/TextBox.hh5
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
64App::~App() { 72App::~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
46namespace FbTk { 48namespace FbTk {
47 49
50
48TextBox::TextBox(int screen_num, 51TextBox::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
113void TextBox::cursorForward() { 118void 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
123void TextBox::cursorBackward() { 130void 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
132void TextBox::backspace() { 143void 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() {
148void TextBox::deleteForward() { 160void 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
269void TextBox::keyPressEvent(XKeyEvent &event) { 287void 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
449void 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
415void TextBox::setCursorPosition(int pos) { 466void 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
581TextBox::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