rofi 1.7.5
textbox.c
Go to the documentation of this file.
1/*
2 * rofi
3 *
4 * MIT/X11 License
5 * Copyright © 2012 Sean Pringle <sean.pringle@gmail.com>
6 * Copyright © 2013-2022 Qball Cow <qball@gmpclient.org>
7 *
8 * Permission is hereby granted, free of charge, to any person obtaining
9 * a copy of this software and associated documentation files (the
10 * "Software"), to deal in the Software without restriction, including
11 * without limitation the rights to use, copy, modify, merge, publish,
12 * distribute, sublicense, and/or sell copies of the Software, and to
13 * permit persons to whom the Software is furnished to do so, subject to
14 * the following conditions:
15 *
16 * The above copyright notice and this permission notice shall be
17 * included in all copies or substantial portions of the Software.
18 *
19 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
20 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
21 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
22 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
23 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
24 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
25 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
26 *
27 */
28
29#include "widgets/textbox.h"
30#include "helper-theme.h"
31#include "helper.h"
32#include "keyb.h"
33#include "mode.h"
34#include "view.h"
35#include <ctype.h>
36#include <glib.h>
37#include <math.h>
38#include <string.h>
39#include <xcb/xcb.h>
40
41#include "theme.h"
42
43static void textbox_draw(widget *, cairo_t *);
44static void textbox_free(widget *);
45static int textbox_get_width(widget *);
46static int _textbox_get_height(widget *);
48
50static PangoContext *p_context = NULL;
52static PangoFontMetrics *p_metrics = NULL;
53
56
58static GHashTable *tbfc_cache = NULL;
59
60static gboolean textbox_blink(gpointer data) {
61 textbox *tb = (textbox *)data;
62 if (tb->blink < 2) {
63 tb->blink = !tb->blink;
66 } else {
67 tb->blink--;
68 }
69 return TRUE;
70}
71
72static void textbox_resize(widget *wid, short w, short h) {
73 textbox *tb = (textbox *)wid;
74 textbox_moveresize(tb, tb->widget.x, tb->widget.y, w, h);
75}
76static int textbox_get_desired_height(widget *wid, const int width) {
77 textbox *tb = (textbox *)wid;
78 if ((tb->flags & TB_AUTOHEIGHT) == 0) {
79 return tb->widget.h;
80 }
81 if (tb->changed) {
83 }
84 int old_width = pango_layout_get_width(tb->layout);
85 pango_layout_set_width(
86 tb->layout,
87 PANGO_SCALE * (width - widget_padding_get_padding_width(WIDGET(tb))));
88
89 int height =
90 textbox_get_estimated_height(tb, pango_layout_get_line_count(tb->layout));
91 pango_layout_set_width(tb->layout, old_width);
92 return height;
93}
94
97 MouseBindingMouseDefaultAction action, gint x,
98 gint y, G_GNUC_UNUSED void *user_data) {
99 textbox *tb = (textbox *)wid;
100 switch (action) {
101 case MOUSE_CLICK_DOWN: {
102 gint i;
103 // subtract padding on left.
104 x -= widget_padding_get_left(wid);
105 gint max = textbox_get_font_width(tb);
106 // Right of text, move to end.
107 if (x >= max) {
109 } else if (x > 0) {
110 // If in range, get index.
111 pango_layout_xy_to_index(tb->layout, x * PANGO_SCALE, y * PANGO_SCALE, &i,
112 NULL);
113 textbox_cursor(tb, i);
114 }
116 }
117 case MOUSE_CLICK_UP:
119 case MOUSE_DCLICK_UP:
120 break;
121 }
123}
124
126 tb->tbfc = tbfc_default;
127 const char *font = rofi_theme_get_string(WIDGET(tb), "font", NULL);
128 if (font) {
129 TBFontConfig *tbfc = g_hash_table_lookup(tbfc_cache, font);
130 if (tbfc == NULL) {
131 tbfc = g_malloc0(sizeof(TBFontConfig));
132 tbfc->pfd = pango_font_description_from_string(font);
133 if (helper_validate_font(tbfc->pfd, font)) {
134 tbfc->metrics = pango_context_get_metrics(p_context, tbfc->pfd, NULL);
135
136 PangoLayout *layout = pango_layout_new(p_context);
137 pango_layout_set_font_description(layout, tbfc->pfd);
138 pango_layout_set_text(layout, "aAjb", -1);
139 PangoRectangle rect;
140 pango_layout_get_pixel_extents(layout, NULL, &rect);
141 tbfc->height = rect.y + rect.height;
142 g_object_unref(layout);
143
144 // Cast away consts. (*yuck*) because table_insert does not know it is
145 // const.
146 g_hash_table_insert(tbfc_cache, (char *)font, tbfc);
147 } else {
148 pango_font_description_free(tbfc->pfd);
149 g_free(tbfc);
150 tbfc = NULL;
151 }
152 }
153 if (tbfc) {
154 // Update for used font.
155 pango_layout_set_font_description(tb->layout, tbfc->pfd);
156 tb->tbfc = tbfc;
157 }
158 }
159}
160
161static void textbox_tab_stops(textbox *tb) {
162 GList *dists = rofi_theme_get_list_distance(WIDGET(tb), "tab-stops");
163
164 if (dists != NULL) {
165 PangoTabArray *tabs = pango_tab_array_new(g_list_length(dists), TRUE);
166
167 int i = 0, ppx = 0;
168 for (const GList *iter = g_list_first(dists); iter != NULL;
169 iter = g_list_next(iter), i++) {
170 const RofiDistance *dist = iter->data;
171
173 if (px <= ppx) {
174 continue;
175 }
176 pango_tab_array_set_tab(tabs, i, PANGO_TAB_LEFT, px);
177 ppx = px;
178 }
179 pango_layout_set_tabs(tb->layout, tabs);
180
181 pango_tab_array_free(tabs);
182 g_list_free_full(dists, g_free);
183 }
184}
185
186textbox *textbox_create(widget *parent, WidgetType type, const char *name,
188 const char *text, double xalign, double yalign) {
189 textbox *tb = g_slice_new0(textbox);
190
191 widget_init(WIDGET(tb), parent, type, name);
192
200 tb->flags = flags;
201 tb->emode = PANGO_ELLIPSIZE_END;
202
203 tb->changed = FALSE;
204
205 tb->layout = pango_layout_new(p_context);
206 textbox_font(tb, tbft);
207
210
211 if ((tb->flags & TB_WRAP) == TB_WRAP) {
212 pango_layout_set_wrap(tb->layout, PANGO_WRAP_WORD_CHAR);
213 }
214
215 // Allow overriding of markup.
216 if (rofi_theme_get_boolean(WIDGET(tb), "markup",
217 (tb->flags & TB_MARKUP) == TB_MARKUP)) {
218 tb->flags |= TB_MARKUP;
219 } else {
220 tb->flags &= (~TB_MARKUP);
221 }
222
223 const char *txt = rofi_theme_get_string(WIDGET(tb), "str", text);
224 if (txt == NULL || (*txt) == '\0') {
225 txt = rofi_theme_get_string(WIDGET(tb), "content", text);
226 }
227 const char *placeholder =
228 rofi_theme_get_string(WIDGET(tb), "placeholder", NULL);
229 if (placeholder) {
230 tb->placeholder = placeholder;
231 }
232 textbox_text(tb, txt ? txt : "");
234
235 tb->blink_timeout = 0;
236 tb->blink = 1;
237 if ((tb->flags & TB_EDITABLE) == TB_EDITABLE) {
238 if (rofi_theme_get_boolean(WIDGET(tb), "blink", TRUE)) {
239 tb->blink_timeout = g_timeout_add(1200, textbox_blink, tb);
240 }
242 }
243
244 tb->yalign = rofi_theme_get_double(WIDGET(tb), "vertical-align", yalign);
245 tb->yalign = MAX(0, MIN(1.0, tb->yalign));
246 tb->xalign = rofi_theme_get_double(WIDGET(tb), "horizontal-align", xalign);
247 tb->xalign = MAX(0, MIN(1.0, tb->xalign));
248
249 if (tb->xalign < 0.2) {
250 pango_layout_set_alignment(tb->layout, PANGO_ALIGN_LEFT);
251 } else if (tb->xalign < 0.8) {
252 pango_layout_set_alignment(tb->layout, PANGO_ALIGN_CENTER);
253 } else {
254 pango_layout_set_alignment(tb->layout, PANGO_ALIGN_RIGHT);
255 }
256 // auto height/width modes get handled here
257 // UPDATE: don't autoheight here, as there is no width set.
258 // so no height can be determined and might result into crash.
259 // textbox_moveresize(tb, tb->widget.x, tb->widget.y, tb->widget.w,
260 // tb->widget.h);
261
262 return tb;
263}
264
268const char *const theme_prop_names[][3] = {
270 {"normal.normal", "selected.normal", "alternate.normal"},
272 {"normal.urgent", "selected.urgent", "alternate.urgent"},
274 {"normal.active", "selected.active", "alternate.active"},
275};
276
278 TextBoxFontType t = tbft & STATE_MASK;
279 if (tb == NULL) {
280 return;
281 }
282 // ACTIVE has priority over URGENT if both set.
283 if (t == (URGENT | ACTIVE)) {
284 t = ACTIVE;
285 }
286 switch ((tbft & FMOD_MASK)) {
287 case HIGHLIGHT:
289 break;
290 case ALT:
292 break;
293 default:
295 break;
296 }
297 if (tb->tbft != tbft || tb->widget.state == NULL) {
299 }
300 tb->tbft = tbft;
301}
302
310 pango_layout_set_attributes(tb->layout, NULL);
311 if (tb->placeholder && (tb->text == NULL || tb->text[0] == 0)) {
312 tb->show_placeholder = TRUE;
313 pango_layout_set_text(tb->layout, tb->placeholder, -1);
314 return;
315 }
316 tb->show_placeholder = FALSE;
317 if ((tb->flags & TB_PASSWORD) == TB_PASSWORD) {
318 size_t l = g_utf8_strlen(tb->text, -1);
319 char string[l + 1];
320 memset(string, '*', l);
321 string[l] = '\0';
322 pango_layout_set_text(tb->layout, string, l);
323 } else if (tb->flags & TB_MARKUP || tb->tbft & MARKUP) {
324 pango_layout_set_markup(tb->layout, tb->text, -1);
325 } else {
326 pango_layout_set_text(tb->layout, tb->text, -1);
327 }
328 if (tb->text) {
329 RofiHighlightColorStyle th = {0, {0.0, 0.0, 0.0, 0.0}};
330 th = rofi_theme_get_highlight(WIDGET(tb), "text-transform", th);
331 if (th.style != 0) {
332 PangoAttrList *list = pango_attr_list_new();
333 helper_token_match_set_pango_attr_on_style(list, 0, G_MAXUINT, th);
334 pango_layout_set_attributes(tb->layout, list);
335 }
336 }
337}
338const char *textbox_get_visible_text(const textbox *tb) {
339 if (tb == NULL) {
340 return NULL;
341 }
342 return pango_layout_get_text(tb->layout);
343}
345 if (tb == NULL) {
346 return NULL;
347 }
348 return pango_layout_get_attributes(tb->layout);
349}
350void textbox_set_pango_attributes(textbox *tb, PangoAttrList *list) {
351 if (tb == NULL) {
352 return;
353 }
354 pango_layout_set_attributes(tb->layout, list);
355}
356
357// set the default text to display
358void textbox_text(textbox *tb, const char *text) {
359 if (tb == NULL) {
360 return;
361 }
362 g_free(tb->text);
363 const gchar *last_pointer = NULL;
364
365 if (text == NULL) {
366 tb->text = g_strdup("Invalid string.");
367 } else {
368 if (g_utf8_validate(text, -1, &last_pointer)) {
369 tb->text = g_strdup(text);
370 } else {
371 if (last_pointer != NULL) {
372 // Copy string up to invalid character.
373 tb->text = g_strndup(text, (last_pointer - text));
374 } else {
375 tb->text = g_strdup("Invalid UTF-8 string.");
376 }
377 }
378 }
380 if (tb->flags & TB_AUTOWIDTH) {
381 textbox_moveresize(tb, tb->widget.x, tb->widget.y, tb->widget.w,
382 tb->widget.h);
383 if (WIDGET(tb)->parent) {
384 widget_update(WIDGET(tb)->parent);
385 }
386 }
387
388 tb->cursor = MAX(0, MIN((int)g_utf8_strlen(tb->text, -1), tb->cursor));
390}
391
392// within the parent handled auto width/height modes
393void textbox_moveresize(textbox *tb, int x, int y, int w, int h) {
394 if (tb->flags & TB_AUTOWIDTH) {
395 pango_layout_set_width(tb->layout, -1);
396 w = textbox_get_font_width(tb) +
398 } else {
399 // set ellipsize
400 if ((tb->flags & TB_EDITABLE) == TB_EDITABLE) {
401 pango_layout_set_ellipsize(tb->layout, PANGO_ELLIPSIZE_MIDDLE);
402 } else if ((tb->flags & TB_WRAP) != TB_WRAP) {
403 pango_layout_set_ellipsize(tb->layout, tb->emode);
404 } else {
405 pango_layout_set_ellipsize(tb->layout, PANGO_ELLIPSIZE_NONE);
406 }
407 }
408
409 if (tb->flags & TB_AUTOHEIGHT) {
410 // Width determines height!
411 int padding = widget_padding_get_padding_width(WIDGET(tb));
412 int tw = MAX(1 + padding, w);
413 pango_layout_set_width(tb->layout, PANGO_SCALE * (tw - padding));
414 int hd = textbox_get_height(tb);
415 h = MAX(hd, h);
416 }
417
418 if (x != tb->widget.x || y != tb->widget.y || w != tb->widget.w ||
419 h != tb->widget.h) {
420 tb->widget.x = x;
421 tb->widget.y = y;
422 tb->widget.h = MAX(1, h);
423 tb->widget.w = MAX(1, w);
424 }
425
426 // We always want to update this
427 pango_layout_set_width(
428 tb->layout, PANGO_SCALE * (tb->widget.w -
431}
432
433// will also unmap the window if still displayed
434static void textbox_free(widget *wid) {
435 if (wid == NULL) {
436 return;
437 }
438 textbox *tb = (textbox *)wid;
439 if (tb->blink_timeout > 0) {
440 g_source_remove(tb->blink_timeout);
441 tb->blink_timeout = 0;
442 }
443 g_free(tb->text);
444
445 if (tb->layout != NULL) {
446 g_object_unref(tb->layout);
447 }
448
449 g_slice_free(textbox, tb);
450}
451
452static void textbox_draw(widget *wid, cairo_t *draw) {
453 if (wid == NULL) {
454 return;
455 }
456 textbox *tb = (textbox *)wid;
457 int dot_offset = 0;
458
459 if (tb->changed) {
461 }
462
463 // Skip the side MARGIN on the X axis.
464 int x;
465 int top = widget_padding_get_top(WIDGET(tb));
466 int y = (pango_font_metrics_get_ascent(tb->tbfc->metrics) -
467 pango_layout_get_baseline(tb->layout)) /
468 PANGO_SCALE;
469 int line_width = 0, line_height = 0;
470 // Get actual width.
471 pango_layout_get_pixel_size(tb->layout, &line_width, &line_height);
472
473 if (tb->yalign > 0.001) {
474 int bottom = widget_padding_get_bottom(WIDGET(tb));
475 top = (tb->widget.h - bottom - line_height - top) * tb->yalign + top;
476 }
477 y += top;
478
479 // TODO check if this is still needed after flatning.
480 cairo_set_operator(draw, CAIRO_OPERATOR_OVER);
481 cairo_set_source_rgb(draw, 0.0, 0.0, 0.0);
482 rofi_theme_get_color(WIDGET(tb), "text-color", draw);
483
484 if (tb->show_placeholder) {
485 rofi_theme_get_color(WIDGET(tb), "placeholder-color", draw);
486 }
487 // Set ARGB
488 // We need to set over, otherwise subpixel hinting wont work.
489 switch (pango_layout_get_alignment(tb->layout)) {
490 case PANGO_ALIGN_CENTER: {
491 int rem =
493 line_width - dot_offset);
494 x = (tb->xalign - 0.5) * rem + widget_padding_get_left(WIDGET(tb));
495 cairo_move_to(draw, x, top);
496 break;
497 }
498 case PANGO_ALIGN_RIGHT: {
499 int rem =
501 line_width - dot_offset);
502 x = -(1.0 - tb->xalign) * rem + widget_padding_get_left(WIDGET(tb));
503 cairo_move_to(draw, x, top);
504 break;
505 }
506 default: {
507 int rem =
509 line_width - dot_offset);
510 x = tb->xalign * rem + widget_padding_get_left(WIDGET(tb));
511 x += dot_offset;
512 cairo_move_to(draw, x, top);
513 break;
514 }
515 }
516 cairo_save(draw);
517 cairo_reset_clip(draw);
518 pango_cairo_show_layout(draw, tb->layout);
519 cairo_restore(draw);
520
521 // draw the cursor
522 rofi_theme_get_color(WIDGET(tb), "text-color", draw);
523 if (tb->flags & TB_EDITABLE && tb->blink) {
524 // We want to place the cursor based on the text shown.
525 const char *text = pango_layout_get_text(tb->layout);
526 // Clamp the position, should not be needed, but we are paranoid.
527 int cursor_offset = MIN(tb->cursor, g_utf8_strlen(text, -1));
528 PangoRectangle pos;
529 // convert to byte location.
530 char *offset = g_utf8_offset_to_pointer(text, cursor_offset);
531 pango_layout_get_cursor_pos(tb->layout, offset - text, &pos, NULL);
532 int cursor_x = pos.x / PANGO_SCALE;
533 int cursor_y = pos.y / PANGO_SCALE;
534 int cursor_height = pos.height / PANGO_SCALE;
535 int cursor_width = 2;
536 cairo_rectangle(draw, x + cursor_x, y + cursor_y, cursor_width,
537 cursor_height);
538 cairo_fill(draw);
539 }
540}
541
542// cursor handling for edit mode
543void textbox_cursor(textbox *tb, int pos) {
544 if (tb == NULL) {
545 return;
546 }
547 int length = (tb->text == NULL) ? 0 : g_utf8_strlen(tb->text, -1);
548 tb->cursor = MAX(0, MIN(length, pos));
549 // Stop blink!
550 tb->blink = 3;
552}
553
562 int old = tb->cursor;
563 textbox_cursor(tb, tb->cursor + 1);
564 return old != tb->cursor;
565}
566
575 int old = tb->cursor;
576 textbox_cursor(tb, tb->cursor - 1);
577 return old != tb->cursor;
578}
579
580// Move word right
582 if (tb->text == NULL) {
583 return;
584 }
585 // Find word boundaries, with pango_Break?
586 gchar *c = g_utf8_offset_to_pointer(tb->text, tb->cursor);
587 while ((c = g_utf8_next_char(c))) {
588 gunichar uc = g_utf8_get_char(c);
589 GUnicodeBreakType bt = g_unichar_break_type(uc);
590 if ((bt == G_UNICODE_BREAK_ALPHABETIC ||
591 bt == G_UNICODE_BREAK_HEBREW_LETTER || bt == G_UNICODE_BREAK_NUMERIC ||
592 bt == G_UNICODE_BREAK_QUOTATION)) {
593 break;
594 }
595 }
596 if (c == NULL || *c == '\0') {
597 return;
598 }
599 while ((c = g_utf8_next_char(c))) {
600 gunichar uc = g_utf8_get_char(c);
601 GUnicodeBreakType bt = g_unichar_break_type(uc);
602 if (!(bt == G_UNICODE_BREAK_ALPHABETIC ||
603 bt == G_UNICODE_BREAK_HEBREW_LETTER ||
604 bt == G_UNICODE_BREAK_NUMERIC || bt == G_UNICODE_BREAK_QUOTATION)) {
605 break;
606 }
607 }
608 int index = g_utf8_pointer_to_offset(tb->text, c);
609 textbox_cursor(tb, index);
610}
611// move word left
613 // Find word boundaries, with pango_Break?
614 gchar *n;
615 gchar *c = g_utf8_offset_to_pointer(tb->text, tb->cursor);
616 while ((c = g_utf8_prev_char(c)) && c != tb->text) {
617 gunichar uc = g_utf8_get_char(c);
618 GUnicodeBreakType bt = g_unichar_break_type(uc);
619 if ((bt == G_UNICODE_BREAK_ALPHABETIC ||
620 bt == G_UNICODE_BREAK_HEBREW_LETTER || bt == G_UNICODE_BREAK_NUMERIC ||
621 bt == G_UNICODE_BREAK_QUOTATION)) {
622 break;
623 }
624 }
625 if (c != tb->text) {
626 while ((n = g_utf8_prev_char(c))) {
627 gunichar uc = g_utf8_get_char(n);
628 GUnicodeBreakType bt = g_unichar_break_type(uc);
629 if (!(bt == G_UNICODE_BREAK_ALPHABETIC ||
630 bt == G_UNICODE_BREAK_HEBREW_LETTER ||
631 bt == G_UNICODE_BREAK_NUMERIC || bt == G_UNICODE_BREAK_QUOTATION)) {
632 break;
633 }
634 c = n;
635 if (n == tb->text) {
636 break;
637 }
638 }
639 }
640 int index = g_utf8_pointer_to_offset(tb->text, c);
641 textbox_cursor(tb, index);
642}
643
644// end of line
646 if (tb->text == NULL) {
647 tb->cursor = 0;
649 return;
650 }
651 tb->cursor = (int)g_utf8_strlen(tb->text, -1);
653 // Stop blink!
654 tb->blink = 2;
655}
656
657// insert text
658void textbox_insert(textbox *tb, const int char_pos, const char *str,
659 const int slen) {
660 if (tb == NULL) {
661 return;
662 }
663 char *c = g_utf8_offset_to_pointer(tb->text, char_pos);
664 int pos = c - tb->text;
665 int len = (int)strlen(tb->text);
666 pos = MAX(0, MIN(len, pos));
667 // expand buffer
668 tb->text = g_realloc(tb->text, len + slen + 1);
669 // move everything after cursor upward
670 char *at = tb->text + pos;
671 memmove(at + slen, at, len - pos + 1);
672 // insert new str
673 memmove(at, str, slen);
674
675 // Set modified, lay out need te be redrawn
676 // Stop blink!
677 tb->blink = 2;
678 tb->changed = TRUE;
679}
680
681// remove text
682void textbox_delete(textbox *tb, int pos, int dlen) {
683 if (tb == NULL) {
684 return;
685 }
686 int len = g_utf8_strlen(tb->text, -1);
687 if (len == pos) {
688 return;
689 }
690 pos = MAX(0, MIN(len, pos));
691 if ((pos + dlen) > len) {
692 dlen = len - dlen;
693 }
694 // move everything after pos+dlen down
695 char *start = g_utf8_offset_to_pointer(tb->text, pos);
696 char *end = g_utf8_offset_to_pointer(tb->text, pos + dlen);
697 // Move remainder + closing \0
698 memmove(start, end, (tb->text + strlen(tb->text)) - end + 1);
699 if (tb->cursor >= pos && tb->cursor < (pos + dlen)) {
700 tb->cursor = pos;
701 } else if (tb->cursor >= (pos + dlen)) {
702 tb->cursor -= dlen;
703 }
704 // Set modified, lay out need te be redrawn
705 // Stop blink!
706 tb->blink = 2;
707 tb->changed = TRUE;
708}
709
715static void textbox_cursor_del(textbox *tb) {
716 if (tb == NULL || tb->text == NULL) {
717 return;
718 }
719 textbox_delete(tb, tb->cursor, 1);
720}
721
728 if (tb && tb->cursor > 0) {
731 }
732}
734 if (tb && tb->cursor > 0) {
735 int cursor = tb->cursor;
737 if (cursor > tb->cursor) {
738 textbox_delete(tb, tb->cursor, cursor - tb->cursor);
739 }
740 }
741}
743 if (tb && tb->cursor >= 0) {
744 int length = g_utf8_strlen(tb->text, -1) - tb->cursor;
745 if (length >= 0) {
746 textbox_delete(tb, tb->cursor, length);
747 }
748 }
749}
751 if (tb && tb->cursor >= 0) {
752 int length = tb->cursor;
753 textbox_delete(tb, 0, length);
754 }
755}
757 if (tb && tb->cursor >= 0) {
758 int cursor = tb->cursor;
760 if (cursor < tb->cursor) {
761 textbox_delete(tb, cursor, tb->cursor - cursor);
762 }
763 }
764}
765
766// handle a keypress in edit mode
767// 2 = nav
768// 0 = unhandled
769// 1 = handled
770// -1 = handled and return pressed (finished)
772 if (tb == NULL) {
773 return 0;
774 }
775 if (!(tb->flags & TB_EDITABLE)) {
776 return 0;
777 }
778
779 switch (action) {
780 // Left or Ctrl-b
781 case MOVE_CHAR_BACK:
782 return (textbox_cursor_dec(tb) == TRUE) ? 2 : 0;
783 // Right or Ctrl-F
785 return (textbox_cursor_inc(tb) == TRUE) ? 2 : 0;
786 // Ctrl-U: Kill from the beginning to the end of the line.
787 case CLEAR_LINE:
788 textbox_text(tb, "");
789 return 1;
790 // Ctrl-A
791 case MOVE_FRONT:
792 textbox_cursor(tb, 0);
793 return 2;
794 // Ctrl-E
795 case MOVE_END:
797 return 2;
798 // Ctrl-Alt-h
799 case REMOVE_WORD_BACK:
801 return 1;
802 // Ctrl-Alt-d
805 return 1;
806 case REMOVE_TO_EOL:
808 return 1;
809 case REMOVE_TO_SOL:
811 return 1;
812 // Delete or Ctrl-D
815 return 1;
816 // Alt-B, Ctrl-Left
817 case MOVE_WORD_BACK:
819 return 2;
820 // Alt-F, Ctrl-Right
823 return 2;
824 // BackSpace, Shift-BackSpace, Ctrl-h
825 case REMOVE_CHAR_BACK:
827 return 1;
828 default:
829 g_return_val_if_reached(0);
830 }
831}
832
833gboolean textbox_append_text(textbox *tb, const char *pad, const int pad_len) {
834 if (tb == NULL) {
835 return FALSE;
836 }
837 if (!(tb->flags & TB_EDITABLE)) {
838 return FALSE;
839 }
840
841 // Filter When alt/ctrl is pressed do not accept the character.
842
843 gboolean used_something = FALSE;
844 const gchar *w, *n, *e;
845 for (w = pad, n = g_utf8_next_char(w), e = w + pad_len; w < e;
846 w = n, n = g_utf8_next_char(n)) {
847 if (g_unichar_iscntrl(g_utf8_get_char(w))) {
848 continue;
849 }
850 textbox_insert(tb, tb->cursor, w, n - w);
851 textbox_cursor(tb, tb->cursor + 1);
852 used_something = TRUE;
853 }
854 return used_something;
855}
856
857static void tbfc_entry_free(TBFontConfig *tbfc) {
858 pango_font_metrics_unref(tbfc->metrics);
859 if (tbfc->pfd) {
860 pango_font_description_free(tbfc->pfd);
861 }
862 g_free(tbfc);
863}
864void textbox_setup(void) {
865 tbfc_cache = g_hash_table_new_full(g_str_hash, g_str_equal, NULL,
866 (GDestroyNotify)tbfc_entry_free);
867}
868
870const char *default_font_name = "default";
871void textbox_set_pango_context(const char *font, PangoContext *p) {
872 g_assert(p_metrics == NULL);
873 p_context = g_object_ref(p);
874 p_metrics = pango_context_get_metrics(p_context, NULL, NULL);
875 TBFontConfig *tbfc = g_malloc0(sizeof(TBFontConfig));
876 tbfc->metrics = p_metrics;
877
878 PangoLayout *layout = pango_layout_new(p_context);
879 pango_layout_set_text(layout, "aAjb", -1);
880 PangoRectangle rect;
881 pango_layout_get_pixel_extents(layout, NULL, &rect);
882 tbfc->height = rect.y + rect.height;
883 g_object_unref(layout);
884 tbfc_default = tbfc;
885
886 g_hash_table_insert(tbfc_cache, (gpointer *)(font ? font : default_font_name),
887 tbfc);
888}
889
890void textbox_cleanup(void) {
891 g_hash_table_destroy(tbfc_cache);
892 if (p_context) {
893 g_object_unref(p_context);
894 p_context = NULL;
895 }
896}
897
899 textbox *tb = (textbox *)wid;
900 if (tb->flags & TB_AUTOWIDTH) {
902 }
903 return tb->widget.w;
904}
905
907 textbox *tb = (textbox *)wid;
908 if (tb->flags & TB_AUTOHEIGHT) {
910 tb, pango_layout_get_line_count(tb->layout));
911 }
912 return tb->widget.h;
913}
918
920 PangoRectangle rect;
921 pango_layout_get_pixel_extents(tb->layout, NULL, &rect);
922 return rect.height + rect.y;
923}
924
926 PangoRectangle rect;
927 pango_layout_get_pixel_extents(tb->layout, NULL, &rect);
928 return rect.width + rect.x;
929}
930
933
935static double char_width = -1;
937 if (char_width < 0) {
938 int width = pango_font_metrics_get_approximate_char_width(p_metrics);
939 char_width = (width) / (double)PANGO_SCALE;
940 }
941 return char_width;
942}
943
945static double ch_width = -1;
947 if (ch_width < 0) {
948 int width = pango_font_metrics_get_approximate_digit_width(p_metrics);
949 ch_width = (width) / (double)PANGO_SCALE;
950 }
951 return ch_width;
952}
953
954int textbox_get_estimated_height(const textbox *tb, int eh) {
955 int height = tb->tbfc->height;
956 return (eh * height) + widget_padding_get_padding_height(WIDGET(tb));
957}
958int textbox_get_desired_width(widget *wid, G_GNUC_UNUSED const int height) {
959 if (wid == NULL) {
960 return 0;
961 }
962 textbox *tb = (textbox *)wid;
963 if (wid->expand && tb->flags & TB_AUTOWIDTH) {
965 }
966 RofiDistance w = rofi_theme_get_distance(WIDGET(tb), "width", 0);
968 if (wi > 0) {
969 return wi;
970 }
971 int padding = widget_padding_get_left(WIDGET(tb));
972 padding += widget_padding_get_right(WIDGET(tb));
973 int old_width = pango_layout_get_width(tb->layout);
974 pango_layout_set_width(tb->layout, -1);
975 int width = textbox_get_font_width(tb);
976 // Restore.
977 pango_layout_set_width(tb->layout, old_width);
978 return width + padding;
979}
980
981void textbox_set_ellipsize(textbox *tb, PangoEllipsizeMode mode) {
982 if (tb) {
983 tb->emode = mode;
984 if ((tb->flags & TB_WRAP) != TB_WRAP) {
985 // Store the mode.
986 pango_layout_set_ellipsize(tb->layout, tb->emode);
988 }
989 }
990}
void helper_token_match_set_pango_attr_on_style(PangoAttrList *retv, int start, int end, RofiHighlightColorStyle th)
Definition helper.c:418
gboolean helper_validate_font(PangoFontDescription *pfd, const char *font)
Definition helper.c:628
KeyBindingAction
Definition keyb.h:58
MouseBindingMouseDefaultAction
Definition keyb.h:168
@ REMOVE_TO_SOL
Definition keyb.h:88
@ MOVE_FRONT
Definition keyb.h:66
@ REMOVE_WORD_FORWARD
Definition keyb.h:80
@ REMOVE_WORD_BACK
Definition keyb.h:78
@ MOVE_CHAR_FORWARD
Definition keyb.h:76
@ MOVE_WORD_FORWARD
Definition keyb.h:72
@ REMOVE_TO_EOL
Definition keyb.h:86
@ MOVE_WORD_BACK
Definition keyb.h:70
@ MOVE_END
Definition keyb.h:68
@ REMOVE_CHAR_BACK
Definition keyb.h:84
@ CLEAR_LINE
Definition keyb.h:64
@ MOVE_CHAR_BACK
Definition keyb.h:74
@ REMOVE_CHAR_FORWARD
Definition keyb.h:82
@ MOUSE_CLICK_DOWN
Definition keyb.h:169
@ MOUSE_DCLICK_UP
Definition keyb.h:172
@ MOUSE_CLICK_UP
Definition keyb.h:170
@ MOUSE_DCLICK_DOWN
Definition keyb.h:171
int textbox_get_height(const textbox *tb)
Definition textbox.c:914
void textbox_insert(textbox *tb, const int char_pos, const char *str, const int slen)
Definition textbox.c:658
void textbox_font(textbox *tb, TextBoxFontType tbft)
Definition textbox.c:277
TextboxFlags
Definition textbox.h:89
void textbox_delete(textbox *tb, int pos, int dlen)
Definition textbox.c:682
int textbox_keybinding(textbox *tb, KeyBindingAction action)
Definition textbox.c:771
TextBoxFontType
Definition textbox.h:100
void textbox_cleanup(void)
Definition textbox.c:890
double textbox_get_estimated_char_width(void)
Definition textbox.c:936
int textbox_get_font_height(const textbox *tb)
Definition textbox.c:919
void textbox_set_pango_attributes(textbox *tb, PangoAttrList *list)
Definition textbox.c:350
void textbox_set_ellipsize(textbox *tb, PangoEllipsizeMode mode)
Definition textbox.c:981
void textbox_setup(void)
Definition textbox.c:864
double textbox_get_estimated_char_height(void)
Definition textbox.c:932
const char * textbox_get_visible_text(const textbox *tb)
Definition textbox.c:338
int textbox_get_estimated_height(const textbox *tb, int eh)
Definition textbox.c:954
void textbox_cursor(textbox *tb, int pos)
Definition textbox.c:543
void textbox_set_pango_context(const char *font, PangoContext *p)
Definition textbox.c:871
textbox * textbox_create(widget *parent, WidgetType type, const char *name, TextboxFlags flags, TextBoxFontType tbft, const char *text, double xalign, double yalign)
Definition textbox.c:186
int textbox_get_font_width(const textbox *tb)
Definition textbox.c:925
void textbox_cursor_end(textbox *tb)
Definition textbox.c:645
gboolean textbox_append_text(textbox *tb, const char *pad, const int pad_len)
Definition textbox.c:833
void textbox_moveresize(textbox *tb, int x, int y, int w, int h)
Definition textbox.c:393
PangoAttrList * textbox_get_pango_attributes(textbox *tb)
Definition textbox.c:344
void textbox_text(textbox *tb, const char *text)
Definition textbox.c:358
double textbox_get_estimated_ch(void)
Definition textbox.c:946
@ TB_AUTOHEIGHT
Definition textbox.h:90
@ TB_PASSWORD
Definition textbox.h:95
@ TB_MARKUP
Definition textbox.h:93
@ TB_WRAP
Definition textbox.h:94
@ TB_EDITABLE
Definition textbox.h:92
@ TB_AUTOWIDTH
Definition textbox.h:91
@ URGENT
Definition textbox.h:104
@ ACTIVE
Definition textbox.h:106
@ HIGHLIGHT
Definition textbox.h:115
@ STATE_MASK
Definition textbox.h:119
@ ALT
Definition textbox.h:113
@ FMOD_MASK
Definition textbox.h:117
@ MARKUP
Definition textbox.h:110
void rofi_view_queue_redraw(void)
Definition view.c:535
void widget_queue_redraw(widget *wid)
Definition widget.c:487
WidgetType
Definition widget.h:56
void widget_update(widget *widget)
Definition widget.c:477
#define WIDGET(a)
Definition widget.h:119
WidgetTriggerActionResult
Definition widget.h:76
@ WIDGET_TRIGGER_ACTION_RESULT_HANDLED
Definition widget.h:80
@ WIDGET_TRIGGER_ACTION_RESULT_IGNORED
Definition widget.h:78
@ ROFI_ORIENTATION_HORIZONTAL
Definition rofi-types.h:143
RofiHighlightStyle style
Definition rofi-types.h:221
double height
Definition textbox.h:55
PangoFontMetrics * metrics
Definition textbox.h:53
PangoFontDescription * pfd
Definition textbox.h:51
void(* free)(struct _widget *widget)
const char * state
widget_trigger_action_cb trigger_action
int(* get_desired_width)(struct _widget *, const int height)
int(* get_width)(struct _widget *)
int(* get_height)(struct _widget *)
int(* get_desired_height)(struct _widget *, const int width)
gboolean expand
void(* draw)(struct _widget *widget, cairo_t *draw)
void(* resize)(struct _widget *, short, short)
int blink
Definition textbox.h:73
const char * placeholder
Definition textbox.h:66
char * text
Definition textbox.h:65
short cursor
Definition textbox.h:64
PangoEllipsizeMode emode
Definition textbox.h:81
double yalign
Definition textbox.h:76
widget widget
Definition textbox.h:62
int tbft
Definition textbox.h:69
double xalign
Definition textbox.h:77
guint blink_timeout
Definition textbox.h:74
int show_placeholder
Definition textbox.h:67
PangoLayout * layout
Definition textbox.h:68
TBFontConfig * tbfc
Definition textbox.h:79
unsigned long flags
Definition textbox.h:63
int changed
Definition textbox.h:71
static TBFontConfig * tbfc_default
Definition textbox.c:55
static PangoContext * p_context
Definition textbox.c:50
static int textbox_get_width(widget *)
Definition textbox.c:898
static void textbox_cursor_dec_word(textbox *tb)
Definition textbox.c:612
static void textbox_cursor_inc_word(textbox *tb)
Definition textbox.c:581
static gboolean textbox_blink(gpointer data)
Definition textbox.c:60
static WidgetTriggerActionResult textbox_editable_trigger_action(widget *wid, MouseBindingMouseDefaultAction action, gint x, gint y, G_GNUC_UNUSED void *user_data)
Definition textbox.c:96
const char *const theme_prop_names[][3]
Definition textbox.c:268
const char * default_font_name
Definition textbox.c:870
static double ch_width
Definition textbox.c:945
static int textbox_get_desired_height(widget *wid, const int width)
Definition textbox.c:76
static int textbox_cursor_inc(textbox *tb)
Definition textbox.c:561
static void textbox_free(widget *)
Definition textbox.c:434
int textbox_get_desired_width(widget *wid, G_GNUC_UNUSED const int height)
Definition textbox.c:958
static void textbox_initialize_font(textbox *tb)
Definition textbox.c:125
static void textbox_resize(widget *wid, short w, short h)
Definition textbox.c:72
static void textbox_cursor_del_sol(textbox *tb)
Definition textbox.c:750
static void textbox_tab_stops(textbox *tb)
Definition textbox.c:161
static void textbox_cursor_bkspc(textbox *tb)
Definition textbox.c:727
static void textbox_cursor_bkspc_word(textbox *tb)
Definition textbox.c:733
static void textbox_draw(widget *, cairo_t *)
Definition textbox.c:452
static void textbox_cursor_del_word(textbox *tb)
Definition textbox.c:756
static PangoFontMetrics * p_metrics
Definition textbox.c:52
static void textbox_cursor_del(textbox *tb)
Definition textbox.c:715
static double char_width
Definition textbox.c:935
static void __textbox_update_pango_text(textbox *tb)
Definition textbox.c:309
static int _textbox_get_height(widget *)
Definition textbox.c:906
static void textbox_cursor_del_eol(textbox *tb)
Definition textbox.c:742
static int textbox_cursor_dec(textbox *tb)
Definition textbox.c:574
static void tbfc_entry_free(TBFontConfig *tbfc)
Definition textbox.c:857
static GHashTable * tbfc_cache
Definition textbox.c:58
const char * rofi_theme_get_string(const widget *widget, const char *property, const char *def)
Definition theme.c:990
RofiDistance rofi_theme_get_distance(const widget *widget, const char *property, int def)
Definition theme.c:877
int rofi_theme_get_boolean(const widget *widget, const char *property, int def)
Definition theme.c:903
int distance_get_pixel(RofiDistance d, RofiOrientation ori)
Definition theme.c:1415
void rofi_theme_get_color(const widget *widget, const char *property, cairo_t *d)
Definition theme.c:1067
RofiHighlightColorStyle rofi_theme_get_highlight(widget *widget, const char *property, RofiHighlightColorStyle th)
Definition theme.c:1320
double rofi_theme_get_double(const widget *widget, const char *property, double def)
Definition theme.c:1040
GList * rofi_theme_get_list_distance(const widget *widget, const char *property)
Definition theme.c:1239
MenuFlags flags
Definition view.c:107
void widget_init(widget *wid, widget *parent, WidgetType type, const char *name)
Definition widget.c:34
void widget_set_state(widget *widget, const char *state)
Definition widget.c:57
int widget_padding_get_padding_width(const widget *wid)
Definition widget.c:637
int widget_padding_get_left(const widget *wid)
Definition widget.c:576
int widget_padding_get_right(const widget *wid)
Definition widget.c:586
int widget_padding_get_padding_height(const widget *wid)
Definition widget.c:631
int widget_padding_get_top(const widget *wid)
Definition widget.c:598
int widget_padding_get_bottom(const widget *wid)
Definition widget.c:608