diff options
author | Thomas Lübking <thomas.luebking@gmail.com> | 2016-04-24 05:57:08 (GMT) |
---|---|---|
committer | Mathias Gumz <akira@fluxbox.org> | 2016-04-25 17:04:13 (GMT) |
commit | 58b50fb786a15fb1740a3d900e271d0baa6b5482 (patch) | |
tree | 74641aec921d24ccc47f32b268dec1f8e61261f6 | |
parent | 2e8766174eaa11e7f370ba5927ed38e18efced11 (diff) | |
download | fluxbox-58b50fb786a15fb1740a3d900e271d0baa6b5482.zip fluxbox-58b50fb786a15fb1740a3d900e271d0baa6b5482.tar.bz2 |
Allow text selection
FbTk::TextBox now acts like any contemporary input field ;-)
-rw-r--r-- | src/FbTk/TextBox.cc | 207 | ||||
-rw-r--r-- | src/FbTk/TextBox.hh | 6 |
2 files changed, 151 insertions, 62 deletions
diff --git a/src/FbTk/TextBox.cc b/src/FbTk/TextBox.cc index 74771a6..43b8573 100644 --- a/src/FbTk/TextBox.cc +++ b/src/FbTk/TextBox.cc | |||
@@ -53,7 +53,8 @@ TextBox::TextBox(int screen_num, | |||
53 | m_gc(0), | 53 | m_gc(0), |
54 | m_cursor_pos(0), | 54 | m_cursor_pos(0), |
55 | m_start_pos(0), | 55 | m_start_pos(0), |
56 | m_end_pos(0) { | 56 | m_end_pos(0), |
57 | m_select_pos(-1) { | ||
57 | 58 | ||
58 | FbTk::EventManager::instance()->add(*this, *this); | 59 | FbTk::EventManager::instance()->add(*this, *this); |
59 | } | 60 | } |
@@ -128,6 +129,9 @@ void TextBox::cursorBackward() { | |||
128 | } | 129 | } |
129 | 130 | ||
130 | void TextBox::backspace() { | 131 | void TextBox::backspace() { |
132 | if (hasSelection()) | ||
133 | return deleteForward(); | ||
134 | |||
131 | if (m_start_pos || cursorPosition()) { | 135 | if (m_start_pos || cursorPosition()) { |
132 | FbString t = text(); | 136 | FbString t = text(); |
133 | t.erase(m_start_pos + cursorPosition() - 1, 1); | 137 | t.erase(m_start_pos + cursorPosition() - 1, 1); |
@@ -141,15 +145,27 @@ void TextBox::backspace() { | |||
141 | } | 145 | } |
142 | 146 | ||
143 | void TextBox::deleteForward() { | 147 | void TextBox::deleteForward() { |
144 | if (m_start_pos + m_cursor_pos < m_end_pos) { | 148 | std::string::size_type pos = m_start_pos + m_cursor_pos; |
149 | int length = 1; | ||
150 | if (hasSelection()) { | ||
151 | pos = std::min(m_start_pos + m_cursor_pos, m_select_pos); | ||
152 | length = std::max(m_start_pos + m_cursor_pos, m_select_pos) - pos; | ||
153 | m_cursor_pos = pos - m_start_pos; | ||
154 | } | ||
155 | if (pos < m_end_pos) { | ||
145 | FbString t = text(); | 156 | FbString t = text(); |
146 | t.erase(m_start_pos + m_cursor_pos, 1); | 157 | t.erase(pos, length); |
147 | m_text.setLogical(t); | 158 | m_text.setLogical(t); |
148 | adjustEndPos(); | 159 | adjustEndPos(); |
149 | } | 160 | } |
161 | if (length > 1) | ||
162 | adjustStartPos(); | ||
150 | } | 163 | } |
151 | 164 | ||
152 | void TextBox::insertText(const std::string &val) { | 165 | void TextBox::insertText(const std::string &val) { |
166 | if (hasSelection()) | ||
167 | deleteForward(); | ||
168 | |||
153 | FbString t = text(); | 169 | FbString t = text(); |
154 | t.insert(m_start_pos + cursorPosition(), val); | 170 | t.insert(m_start_pos + cursorPosition(), val); |
155 | m_text.setLogical(t); | 171 | m_text.setLogical(t); |
@@ -168,20 +184,48 @@ void TextBox::killToEnd() { | |||
168 | } | 184 | } |
169 | 185 | ||
170 | void TextBox::clear() { | 186 | void TextBox::clear() { |
187 | Display *dpy = FbTk::App::instance()->display(); | ||
171 | FbWindow::clear(); | 188 | FbWindow::clear(); |
172 | // center text by default | 189 | // center text by default |
173 | int center_pos = (height() + font().ascent())/2; | 190 | int center_pos = (height() + font().ascent())/2; |
174 | if (gc() == 0) | 191 | if (gc() == 0) |
175 | setGC(DefaultGC(FbTk::App::instance()->display(), screenNumber())); | 192 | setGC(DefaultGC(dpy, screenNumber())); |
193 | |||
176 | 194 | ||
177 | font().drawText(*this, screenNumber(), | 195 | int cursor_pos = font().textWidth(m_text.visual().c_str() + m_start_pos, m_cursor_pos); |
178 | gc(), | 196 | |
197 | font().drawText(*this, screenNumber(), gc(), | ||
179 | m_text.visual().c_str() + m_start_pos, | 198 | m_text.visual().c_str() + m_start_pos, |
180 | m_end_pos - m_start_pos, | 199 | m_end_pos - m_start_pos, -1, center_pos); // pos |
181 | 0, center_pos); // pos | 200 | |
201 | if (hasSelection()) { | ||
202 | int select_pos = m_select_pos <= m_start_pos ? 0 : | ||
203 | font().textWidth(m_text.visual().c_str() + m_start_pos, | ||
204 | m_select_pos - m_start_pos); | ||
205 | int start = std::max(m_start_pos, std::min(m_start_pos + m_cursor_pos, m_select_pos)); | ||
206 | int length = std::max(m_start_pos + m_cursor_pos, m_select_pos) - start; | ||
207 | int x = std::min(select_pos, cursor_pos); | ||
208 | int width = std::abs(select_pos - cursor_pos); | ||
209 | |||
210 | XGCValues backup; | ||
211 | XGetGCValues(dpy, gc(), GCForeground|GCBackground, &backup); | ||
212 | XSetForeground(dpy, gc(), backup.foreground); | ||
213 | |||
214 | fillRectangle(gc(), x, 0, width, height()); | ||
215 | |||
216 | XColor c; | ||
217 | c.pixel = backup.foreground; | ||
218 | XQueryColor(dpy, DefaultColormap(dpy, screenNumber()), &c); | ||
219 | XSetForeground(dpy, gc(), c.red + c.green + c.blue > 0x17ffe ? | ||
220 | BlackPixel(dpy, screenNumber()) : | ||
221 | WhitePixel(dpy, screenNumber())); | ||
222 | font().drawText(*this, screenNumber(), gc(), | ||
223 | m_text.visual().c_str() + start, length, x, center_pos); // pos | ||
224 | XSetForeground(dpy, gc(), backup.foreground); | ||
225 | } | ||
226 | |||
182 | 227 | ||
183 | // draw cursor position | 228 | // draw cursor position |
184 | int cursor_pos = font().textWidth(m_text.visual().c_str() + m_start_pos, m_cursor_pos) + 1; | ||
185 | drawLine(gc(), cursor_pos, center_pos, cursor_pos, center_pos - font().height()); | 229 | drawLine(gc(), cursor_pos, center_pos, cursor_pos, center_pos - font().height()); |
186 | } | 230 | } |
187 | 231 | ||
@@ -231,12 +275,52 @@ void TextBox::keyPressEvent(XKeyEvent &event) { | |||
231 | // a modifier key by itself doesn't do anything | 275 | // a modifier key by itself doesn't do anything |
232 | if (IsModifierKey(ks)) return; | 276 | if (IsModifierKey(ks)) return; |
233 | 277 | ||
234 | if (FbTk::KeyUtil::instance().isolateModifierMask(event.state)) { // handle keybindings with state | ||
235 | if ((event.state & ControlMask) == ControlMask) { | ||
236 | 278 | ||
237 | switch (ks) { | 279 | if (m_select_pos == -1 && (event.state & ShiftMask) == ShiftMask) { |
238 | case XK_Left: { | 280 | m_select_pos = m_cursor_pos + m_start_pos; |
281 | } | ||
282 | |||
283 | if ((event.state & ControlMask) == ControlMask) { | ||
284 | |||
285 | switch (ks) { | ||
286 | case XK_Left: { | ||
287 | unsigned int pos = findEmptySpaceLeft(); | ||
288 | if (pos < m_start_pos){ | ||
289 | m_start_pos = pos; | ||
290 | m_cursor_pos = 0; | ||
291 | } else if (m_start_pos > 0) { | ||
292 | m_cursor_pos = pos - m_start_pos; | ||
293 | } else { | ||
294 | m_cursor_pos = pos; | ||
295 | } | ||
296 | adjustPos(); | ||
297 | } | ||
298 | break; | ||
299 | case XK_Right: | ||
300 | if (!m_text.logical().empty() && m_cursor_pos < m_text.logical().size()){ | ||
301 | unsigned int pos = findEmptySpaceRight(); | ||
302 | if (pos > m_start_pos) | ||
303 | pos -= m_start_pos; | ||
304 | else | ||
305 | pos = 0; | ||
306 | if (m_start_pos + pos <= m_end_pos) | ||
307 | m_cursor_pos = pos; | ||
308 | else if (m_end_pos < text().size()) { | ||
309 | m_cursor_pos = pos; | ||
310 | m_end_pos = pos; | ||
311 | } | ||
312 | |||
313 | adjustPos(); | ||
314 | |||
315 | } | ||
316 | break; | ||
317 | |||
318 | case XK_BackSpace: { | ||
239 | unsigned int pos = findEmptySpaceLeft(); | 319 | unsigned int pos = findEmptySpaceLeft(); |
320 | FbString t = text(); | ||
321 | t.erase(pos, m_cursor_pos - pos + m_start_pos); | ||
322 | m_text.setLogical(t); | ||
323 | |||
240 | if (pos < m_start_pos){ | 324 | if (pos < m_start_pos){ |
241 | m_start_pos = pos; | 325 | m_start_pos = pos; |
242 | m_cursor_pos = 0; | 326 | m_cursor_pos = 0; |
@@ -247,54 +331,17 @@ void TextBox::keyPressEvent(XKeyEvent &event) { | |||
247 | } | 331 | } |
248 | adjustPos(); | 332 | adjustPos(); |
249 | } | 333 | } |
250 | break; | 334 | break; |
251 | case XK_Right: | 335 | case XK_Delete: { |
252 | if (!m_text.logical().empty() && m_cursor_pos < m_text.logical().size()){ | 336 | if (text().empty() || m_cursor_pos >= text().size()) |
253 | unsigned int pos = findEmptySpaceRight(); | 337 | break; |
254 | if (pos > m_start_pos) | 338 | unsigned int pos = findEmptySpaceRight(); |
255 | pos -= m_start_pos; | 339 | FbString t = text(); |
256 | else | 340 | t.erase(m_cursor_pos + m_start_pos, pos - (m_cursor_pos + m_start_pos)); |
257 | pos = 0; | 341 | m_text.setLogical(t); |
258 | if (m_start_pos + pos <= m_end_pos) | 342 | adjustPos(); |
259 | m_cursor_pos = pos; | ||
260 | else if (m_end_pos < text().size()) { | ||
261 | m_cursor_pos = pos; | ||
262 | m_end_pos = pos; | ||
263 | } | ||
264 | |||
265 | adjustPos(); | ||
266 | |||
267 | } | ||
268 | break; | ||
269 | |||
270 | case XK_BackSpace: { | ||
271 | unsigned int pos = findEmptySpaceLeft(); | ||
272 | FbString t = text(); | ||
273 | t.erase(pos, m_cursor_pos - pos + m_start_pos); | ||
274 | m_text.setLogical(t); | ||
275 | |||
276 | if (pos < m_start_pos){ | ||
277 | m_start_pos = pos; | ||
278 | m_cursor_pos = 0; | ||
279 | } else if (m_start_pos > 0) { | ||
280 | m_cursor_pos = pos - m_start_pos; | ||
281 | } else { | ||
282 | m_cursor_pos = pos; | ||
283 | } | ||
284 | adjustPos(); | ||
285 | } | ||
286 | break; | ||
287 | case XK_Delete: { | ||
288 | if (text().empty() || m_cursor_pos >= text().size()) | ||
289 | break; | ||
290 | unsigned int pos = findEmptySpaceRight(); | ||
291 | FbString t = text(); | ||
292 | t.erase(m_cursor_pos + m_start_pos, pos - (m_cursor_pos + m_start_pos)); | ||
293 | m_text.setLogical(t); | ||
294 | adjustPos(); | ||
295 | } | ||
296 | break; | ||
297 | } | 343 | } |
344 | break; | ||
298 | } | 345 | } |
299 | 346 | ||
300 | } else { // no state | 347 | } else { // no state |
@@ -358,6 +405,8 @@ void TextBox::keyPressEvent(XKeyEvent &event) { | |||
358 | val += keychar[0]; | 405 | val += keychar[0]; |
359 | insertText(val); | 406 | insertText(val); |
360 | } | 407 | } |
408 | if ((event.state & ShiftMask) != ShiftMask) | ||
409 | m_select_pos = -1; | ||
361 | clear(); | 410 | clear(); |
362 | } | 411 | } |
363 | 412 | ||
@@ -369,6 +418,7 @@ void TextBox::setCursorPosition(int pos) { | |||
369 | 418 | ||
370 | void TextBox::adjustEndPos() { | 419 | void TextBox::adjustEndPos() { |
371 | m_end_pos = text().size(); | 420 | m_end_pos = text().size(); |
421 | m_start_pos = std::min(m_start_pos, m_end_pos); | ||
372 | int text_width = font().textWidth(text().c_str() + m_start_pos, m_end_pos - m_start_pos); | 422 | int text_width = font().textWidth(text().c_str() + m_start_pos, m_end_pos - m_start_pos); |
373 | while (text_width > static_cast<signed>(width())) { | 423 | while (text_width > static_cast<signed>(width())) { |
374 | m_end_pos--; | 424 | m_end_pos--; |
@@ -381,7 +431,7 @@ void TextBox::adjustStartPos() { | |||
381 | const char* visual = m_text.visual().c_str(); | 431 | const char* visual = m_text.visual().c_str(); |
382 | 432 | ||
383 | int text_width = font().textWidth(visual, m_end_pos); | 433 | int text_width = font().textWidth(visual, m_end_pos); |
384 | if (text_width < static_cast<signed>(width())) | 434 | if (m_cursor_pos > -1 && text_width < static_cast<signed>(width())) |
385 | return; | 435 | return; |
386 | 436 | ||
387 | int start_pos = 0; | 437 | int start_pos = 0; |
@@ -440,4 +490,39 @@ void TextBox::adjustPos(){ | |||
440 | adjustStartPos(); | 490 | adjustStartPos(); |
441 | 491 | ||
442 | } | 492 | } |
493 | |||
494 | |||
495 | void TextBox::select(std::string::size_type pos, int length) | ||
496 | { | ||
497 | if (length < 0) { | ||
498 | length = -length; | ||
499 | pos = pos >= length ? pos - length : 0; | ||
500 | } | ||
501 | |||
502 | if (length > 0 && pos < text().size()) { | ||
503 | m_select_pos = pos; | ||
504 | pos = std::min(text().size(), pos + length); | ||
505 | |||
506 | if (pos > m_start_pos) | ||
507 | pos -= m_start_pos; | ||
508 | else | ||
509 | pos = 0; | ||
510 | if (m_start_pos + pos <= m_end_pos) | ||
511 | m_cursor_pos = pos; | ||
512 | else if (m_end_pos < text().size()) { | ||
513 | m_cursor_pos = pos; | ||
514 | m_end_pos = pos; | ||
515 | } | ||
516 | |||
517 | adjustPos(); | ||
518 | } else { | ||
519 | m_select_pos = -1; | ||
520 | } | ||
521 | clear(); | ||
522 | } | ||
523 | |||
524 | void TextBox::selectAll() { | ||
525 | select(0, m_text.visual().size()); | ||
526 | } | ||
527 | |||
443 | } // end namespace FbTk | 528 | } // end namespace FbTk |
diff --git a/src/FbTk/TextBox.hh b/src/FbTk/TextBox.hh index 40a7c3c..2c1ef9d 100644 --- a/src/FbTk/TextBox.hh +++ b/src/FbTk/TextBox.hh | |||
@@ -65,6 +65,10 @@ public: | |||
65 | int cursorPosition() const { return m_cursor_pos; } | 65 | int cursorPosition() const { return m_cursor_pos; } |
66 | int textStartPos() const { return m_start_pos; } | 66 | int textStartPos() const { return m_start_pos; } |
67 | 67 | ||
68 | bool hasSelection() const { return m_select_pos != -1 && m_select_pos != m_cursor_pos + m_start_pos; } | ||
69 | void select(std::string::size_type pos, int length); | ||
70 | void selectAll(); | ||
71 | |||
68 | unsigned int findEmptySpaceLeft(); | 72 | unsigned int findEmptySpaceLeft(); |
69 | unsigned int findEmptySpaceRight(); | 73 | unsigned int findEmptySpaceRight(); |
70 | 74 | ||
@@ -77,7 +81,7 @@ private: | |||
77 | const FbTk::Font *m_font; | 81 | const FbTk::Font *m_font; |
78 | BiDiString m_text; | 82 | BiDiString m_text; |
79 | GC m_gc; | 83 | GC m_gc; |
80 | std::string::size_type m_cursor_pos, m_start_pos, m_end_pos; | 84 | std::string::size_type m_cursor_pos, m_start_pos, m_end_pos, m_select_pos; |
81 | }; | 85 | }; |
82 | 86 | ||
83 | } // end namespace FbTk | 87 | } // end namespace FbTk |