rofi 1.7.7
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-2023 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#include "config.h"
29
30#include "helper-theme.h"
31#include "helper.h"
32#include "keyb.h"
33#include "mode.h"
34#include "timings.h"
35#include "view.h"
36#include "widgets/textbox.h"
37#include <ctype.h>
38#include <glib.h>
39#include <math.h>
40#include <string.h>
41#include <xcb/xcb.h>
42
43#include "theme.h"
44
45static void textbox_draw(widget *, cairo_t *);
46static void textbox_free(widget *);
47static int textbox_get_width(widget *);
48static int _textbox_get_height(widget *);
50
52static PangoContext *p_context = NULL;
54static PangoFontMetrics *p_metrics = NULL;
55
58
60static GHashTable *tbfc_cache = NULL;
61
62static gboolean textbox_blink(gpointer data) {
63 textbox *tb = (textbox *)data;
64 if (tb->blink < 2) {
65 tb->blink = !tb->blink;
68 } else {
69 tb->blink--;
70 }
71 return TRUE;
72}
73
74static void textbox_resize(widget *wid, short w, short h) {
75 textbox *tb = (textbox *)wid;
76 textbox_moveresize(tb, tb->widget.x, tb->widget.y, w, h);
77}
78static int textbox_get_desired_height(widget *wid, const int width) {
79 textbox *tb = (textbox *)wid;
80 if ((tb->flags & TB_AUTOHEIGHT) == 0) {
81 return tb->widget.h;
82 }
83 if (tb->changed) {
85 }
86 int old_width = pango_layout_get_width(tb->layout);
87 pango_layout_set_width(
88 tb->layout,
89 PANGO_SCALE * (width - widget_padding_get_padding_width(WIDGET(tb))));
90
91 int height =
92 textbox_get_estimated_height(tb, pango_layout_get_line_count(tb->layout));
93 pango_layout_set_width(tb->layout, old_width);
94 return height;
95}
96
99 MouseBindingMouseDefaultAction action, gint x,
100 gint y, G_GNUC_UNUSED void *user_data) {
101 textbox *tb = (textbox *)wid;
102 switch (action) {
103 case MOUSE_CLICK_DOWN: {
104 gint i;
105 // subtract padding on left.
106 x -= widget_padding_get_left(wid);
107 gint max = textbox_get_font_width(tb);
108 // Right of text, move to end.
109 if (x >= max) {
111 } else if (x > 0) {
112 // If in range, get index.
113 pango_layout_xy_to_index(tb->layout, x * PANGO_SCALE, y * PANGO_SCALE, &i,
114 NULL);
115 textbox_cursor(tb, i);
116 }
118 }
119 case MOUSE_CLICK_UP:
121 case MOUSE_DCLICK_UP:
122 break;
123 }
125}
126
128 tb->tbfc = tbfc_default;
129 const char *font = rofi_theme_get_string(WIDGET(tb), "font", NULL);
130 if (font) {
131 TBFontConfig *tbfc = g_hash_table_lookup(tbfc_cache, font);
132 if (tbfc == NULL) {
133 tbfc = g_malloc0(sizeof(TBFontConfig));
134 tbfc->pfd = pango_font_description_from_string(font);
135 if (helper_validate_font(tbfc->pfd, font)) {
136 tbfc->metrics = pango_context_get_metrics(p_context, tbfc->pfd, NULL);
137
138 PangoLayout *layout = pango_layout_new(p_context);
139 pango_layout_set_font_description(layout, tbfc->pfd);
140 pango_layout_set_text(layout, "aAjb", -1);
141 PangoRectangle rect;
142 pango_layout_get_pixel_extents(layout, NULL, &rect);
143 tbfc->height = rect.y + rect.height;
144
145 // Try to find height from font. Might be slow?
146 TICK_N("Get font height");
147 PangoFont *context_font = pango_context_load_font(p_context, tbfc->pfd);
148 if (context_font) {
149 PangoFontMetrics *fm = pango_font_get_metrics(context_font, NULL);
150 if (fm) {
151 int h = pango_font_metrics_get_height(fm) / PANGO_SCALE;
152 if (h > 0) {
153 tbfc->height = h;
154 }
155 pango_font_metrics_unref(fm);
156 }
157 g_object_unref(context_font);
158 }
159 TICK_N("Get font height");
160 g_object_unref(layout);
161
162 // Cast away consts. (*yuck*) because table_insert does not know it is
163 // const.
164 g_hash_table_insert(tbfc_cache, (char *)font, tbfc);
165 } else {
166 pango_font_description_free(tbfc->pfd);
167 g_free(tbfc);
168 tbfc = NULL;
169 }
170 }
171 if (tbfc) {
172 // Update for used font.
173 pango_layout_set_font_description(tb->layout, tbfc->pfd);
174 tb->tbfc = tbfc;
175 }
176 }
177}
178
179static void textbox_tab_stops(textbox *tb) {
180 GList *dists = rofi_theme_get_list_distance(WIDGET(tb), "tab-stops");
181
182 if (dists != NULL) {
183 PangoTabArray *tabs = pango_tab_array_new(g_list_length(dists), TRUE);
184
185 int i = 0, ppx = 0;
186 for (const GList *iter = g_list_first(dists); iter != NULL;
187 iter = g_list_next(iter), i++) {
188 const RofiDistance *dist = iter->data;
189
191 if (px <= ppx) {
192 continue;
193 }
194 pango_tab_array_set_tab(tabs, i, PANGO_TAB_LEFT, px);
195 ppx = px;
196 }
197 pango_layout_set_tabs(tb->layout, tabs);
198
199 pango_tab_array_free(tabs);
200 g_list_free_full(dists, g_free);
201 }
202}
203
204textbox *textbox_create(widget *parent, WidgetType type, const char *name,
206 const char *text, double xalign, double yalign) {
207 textbox *tb = g_slice_new0(textbox);
208
209 widget_init(WIDGET(tb), parent, type, name);
210
218 tb->flags = flags;
219 tb->emode = PANGO_ELLIPSIZE_END;
220
221 tb->changed = FALSE;
222
223 tb->layout = pango_layout_new(p_context);
224 textbox_font(tb, tbft);
225
228
229 if ((tb->flags & TB_WRAP) == TB_WRAP) {
230 pango_layout_set_wrap(tb->layout, PANGO_WRAP_WORD_CHAR);
231 }
232
233 // Allow overriding of markup.
234 if (rofi_theme_get_boolean(WIDGET(tb), "markup",
235 (tb->flags & TB_MARKUP) == TB_MARKUP)) {
236 tb->flags |= TB_MARKUP;
237 } else {
238 tb->flags &= (~TB_MARKUP);
239 }
240
241 const char *txt = rofi_theme_get_string(WIDGET(tb), "str", text);
242 if (txt == NULL || (*txt) == '\0') {
243 txt = rofi_theme_get_string(WIDGET(tb), "content", text);
244 }
245 const char *placeholder =
246 rofi_theme_get_string(WIDGET(tb), "placeholder", NULL);
247 if (placeholder) {
248 if (rofi_theme_get_boolean(WIDGET(tb), "placeholder-markup", FALSE)) {
249 tb->placeholder = g_strdup(placeholder);
250 } else {
251 tb->placeholder = g_markup_escape_text(placeholder, -1);
252 }
253 }
254 textbox_text(tb, txt ? txt : "");
256
257 tb->blink_timeout = 0;
258 tb->blink = 1;
259 if ((tb->flags & TB_EDITABLE) == TB_EDITABLE) {
260 if (rofi_theme_get_boolean(WIDGET(tb), "blink", TRUE)) {
261 tb->blink_timeout = g_timeout_add(1200, textbox_blink, tb);
262 }
264 }
265
266 tb->yalign = rofi_theme_get_double(WIDGET(tb), "vertical-align", yalign);
267 tb->yalign = MAX(0, MIN(1.0, tb->yalign));
268 tb->xalign = rofi_theme_get_double(WIDGET(tb), "horizontal-align", xalign);
269 tb->xalign = MAX(0, MIN(1.0, tb->xalign));
270
271 if (tb->xalign < 0.2) {
272 pango_layout_set_alignment(tb->layout, PANGO_ALIGN_LEFT);
273 } else if (tb->xalign < 0.8) {
274 pango_layout_set_alignment(tb->layout, PANGO_ALIGN_CENTER);
275 } else {
276 pango_layout_set_alignment(tb->layout, PANGO_ALIGN_RIGHT);
277 }
278 // auto height/width modes get handled here
279 // UPDATE: don't autoheight here, as there is no width set.
280 // so no height can be determined and might result into crash.
281 // textbox_moveresize(tb, tb->widget.x, tb->widget.y, tb->widget.w,
282 // tb->widget.h);
283
284 return tb;
285}
286
290const char *const theme_prop_names[][3] = {
292 {"normal.normal", "selected.normal", "alternate.normal"},
294 {"normal.urgent", "selected.urgent", "alternate.urgent"},
296 {"normal.active", "selected.active", "alternate.active"},
297};
298
300 TextBoxFontType t = tbft & STATE_MASK;
301 if (tb == NULL) {
302 return;
303 }
304 // ACTIVE has priority over URGENT if both set.
305 if (t == (URGENT | ACTIVE)) {
306 t = ACTIVE;
307 }
308 switch ((tbft & FMOD_MASK)) {
309 case HIGHLIGHT:
311 break;
312 case ALT:
314 break;
315 default:
317 break;
318 }
319 if (tb->tbft != tbft || tb->widget.state == NULL) {
321 }
322 tb->tbft = tbft;
323}
324
332 pango_layout_set_attributes(tb->layout, NULL);
333 if (tb->placeholder && (tb->text == NULL || tb->text[0] == 0)) {
334 tb->show_placeholder = TRUE;
335 pango_layout_set_markup(tb->layout, tb->placeholder, -1);
336 return;
337 }
338 tb->show_placeholder = FALSE;
339 if ((tb->flags & TB_PASSWORD) == TB_PASSWORD) {
340 size_t l = g_utf8_strlen(tb->text, -1);
341 char string[l + 1];
342 memset(string, '*', l);
343 string[l] = '\0';
344 pango_layout_set_text(tb->layout, string, l);
345 } else if (tb->flags & TB_MARKUP || tb->tbft & MARKUP) {
346 pango_layout_set_markup(tb->layout, tb->text, -1);
347 } else {
348 pango_layout_set_text(tb->layout, tb->text, -1);
349 }
350 if (tb->text) {
351 RofiHighlightColorStyle th = {0, {0.0, 0.0, 0.0, 0.0}};
352 th = rofi_theme_get_highlight(WIDGET(tb), "text-transform", th);
353 if (th.style != 0) {
354 PangoAttrList *list = pango_attr_list_new();
355 helper_token_match_set_pango_attr_on_style(list, 0, G_MAXUINT, th);
356 pango_layout_set_attributes(tb->layout, list);
357 }
358 }
359}
360const char *textbox_get_visible_text(const textbox *tb) {
361 if (tb == NULL) {
362 return NULL;
363 }
364 return pango_layout_get_text(tb->layout);
365}
367 if (tb == NULL) {
368 return NULL;
369 }
370 return pango_layout_get_attributes(tb->layout);
371}
372void textbox_set_pango_attributes(textbox *tb, PangoAttrList *list) {
373 if (tb == NULL) {
374 return;
375 }
376 pango_layout_set_attributes(tb->layout, list);
377}
378
379char *textbox_get_text(const textbox *tb) {
380 if (tb->text == NULL) {
381 return g_strdup("");
382 }
383 return g_strdup(tb->text);
384}
386 if (tb) {
387 return tb->cursor;
388 }
389 return 0;
390}
391// set the default text to display
392void textbox_text(textbox *tb, const char *text) {
393 if (tb == NULL) {
394 return;
395 }
396 g_free(tb->text);
397 const gchar *last_pointer = NULL;
398
399 if (text == NULL) {
400 tb->text = g_strdup("Invalid string.");
401 } else {
402 if (g_utf8_validate(text, -1, &last_pointer)) {
403 tb->text = g_strdup(text);
404 } else {
405 if (last_pointer != NULL) {
406 // Copy string up to invalid character.
407 tb->text = g_strndup(text, (last_pointer - text));
408 } else {
409 tb->text = g_strdup("Invalid UTF-8 string.");
410 }
411 }
412 }
414 if (tb->flags & TB_AUTOWIDTH) {
415 textbox_moveresize(tb, tb->widget.x, tb->widget.y, tb->widget.w,
416 tb->widget.h);
417 if (WIDGET(tb)->parent) {
418 widget_update(WIDGET(tb)->parent);
419 }
420 }
421
422 tb->cursor = MAX(0, MIN((int)g_utf8_strlen(tb->text, -1), tb->cursor));
424}
425
426// within the parent handled auto width/height modes
427void textbox_moveresize(textbox *tb, int x, int y, int w, int h) {
428 if (tb->flags & TB_AUTOWIDTH) {
429 pango_layout_set_width(tb->layout, -1);
430 w = textbox_get_font_width(tb) +
432 } else {
433 // set ellipsize
434 if ((tb->flags & TB_EDITABLE) == TB_EDITABLE) {
435 pango_layout_set_ellipsize(tb->layout, PANGO_ELLIPSIZE_MIDDLE);
436 } else if ((tb->flags & TB_WRAP) != TB_WRAP) {
437 pango_layout_set_ellipsize(tb->layout, tb->emode);
438 } else {
439 pango_layout_set_ellipsize(tb->layout, PANGO_ELLIPSIZE_NONE);
440 }
441 }
442
443 if (tb->flags & TB_AUTOHEIGHT) {
444 // Width determines height!
445 int padding = widget_padding_get_padding_width(WIDGET(tb));
446 int tw = MAX(1 + padding, w);
447 pango_layout_set_width(tb->layout, PANGO_SCALE * (tw - padding));
448 int hd = textbox_get_height(tb);
449 h = MAX(hd, h);
450 }
451
452 if (x != tb->widget.x || y != tb->widget.y || w != tb->widget.w ||
453 h != tb->widget.h) {
454 tb->widget.x = x;
455 tb->widget.y = y;
456 tb->widget.h = MAX(1, h);
457 tb->widget.w = MAX(1, w);
458 }
459
460 // We always want to update this
461 pango_layout_set_width(
462 tb->layout, PANGO_SCALE * (tb->widget.w -
465}
466
467// will also unmap the window if still displayed
468static void textbox_free(widget *wid) {
469 if (wid == NULL) {
470 return;
471 }
472 textbox *tb = (textbox *)wid;
473 if (tb->blink_timeout > 0) {
474 g_source_remove(tb->blink_timeout);
475 tb->blink_timeout = 0;
476 }
477 g_free(tb->text);
478
479 g_free(tb->placeholder);
480 if (tb->layout != NULL) {
481 g_object_unref(tb->layout);
482 }
483
484 g_slice_free(textbox, tb);
485}
486
487static void textbox_draw(widget *wid, cairo_t *draw) {
488 if (wid == NULL) {
489 return;
490 }
491 textbox *tb = (textbox *)wid;
492 int dot_offset = 0;
493
494 if (tb->changed) {
496 }
497
498 // Skip the side MARGIN on the X axis.
499 int x;
500 int top = widget_padding_get_top(WIDGET(tb));
501 int y = (pango_font_metrics_get_ascent(tb->tbfc->metrics) -
502 pango_layout_get_baseline(tb->layout)) /
503 PANGO_SCALE;
504 int line_width = 0, line_height = 0;
505 // Get actual width.
506 pango_layout_get_pixel_size(tb->layout, &line_width, &line_height);
507
508 if (tb->yalign > 0.001) {
509 int bottom = widget_padding_get_bottom(WIDGET(tb));
510 top = (tb->widget.h - bottom - line_height - top) * tb->yalign + top;
511 }
512 y += top;
513
514 // Set ARGB
515 // NOTE: cairo operator must be OVER at this moment,
516 // to not break subpixel text rendering.
517
518 cairo_set_source_rgb(draw, 0.0, 0.0, 0.0);
519 // use text color as fallback for themes that don't specify the cursor color
520 rofi_theme_get_color(WIDGET(tb), "text-color", draw);
521
522 {
523 int rem =
525 line_width - dot_offset);
526 switch (pango_layout_get_alignment(tb->layout)) {
527 case PANGO_ALIGN_CENTER:
528 x = rem * (tb->xalign - 0.5);
529 break;
530 case PANGO_ALIGN_RIGHT:
531 x = rem * (tb->xalign - 1.0);
532 break;
533 default:
534 x = rem * tb->xalign + dot_offset;
535 break;
536 }
538 }
539
540 // draw the cursor
541 if (tb->flags & TB_EDITABLE) {
542 // We want to place the cursor based on the text shown.
543 const char *text = pango_layout_get_text(tb->layout);
544 // Clamp the position, should not be needed, but we are paranoid.
545 int cursor_offset = MIN(tb->cursor, g_utf8_strlen(text, -1));
546 PangoRectangle pos;
547 // convert to byte location.
548 char *offset = g_utf8_offset_to_pointer(text, cursor_offset);
549 pango_layout_get_cursor_pos(tb->layout, offset - text, &pos, NULL);
550 int cursor_x = pos.x / PANGO_SCALE;
551 int cursor_y = pos.y / PANGO_SCALE;
552 int cursor_height = pos.height / PANGO_SCALE;
553 RofiDistance cursor_width =
554 rofi_theme_get_distance(WIDGET(tb), "cursor-width", 2);
555 int cursor_pixel_width =
557 if ((x + cursor_x) != tb->cursor_x_pos) {
558 tb->cursor_x_pos = x + cursor_x;
559 }
560 if (tb->blink) {
561 // This save/restore state is necessary to render the text in the
562 // correct color when `cursor-color` is set
563 cairo_save(draw);
564 // use text color as fallback for themes that don't specify the cursor
565 // color
566 rofi_theme_get_color(WIDGET(tb), "cursor-color", draw);
567 cairo_rectangle(draw, x + cursor_x, y + cursor_y, cursor_pixel_width,
568 cursor_height);
569 if (rofi_theme_get_boolean(WIDGET(tb), "cursor-outline", FALSE)) {
570 cairo_fill_preserve(draw);
571 rofi_theme_get_color(WIDGET(tb), "cursor-outline-color", draw);
572 double width =
573 rofi_theme_get_double(WIDGET(tb), "cursor-outline-width", 0.5);
574 cairo_set_line_width(draw, width);
575 cairo_stroke(draw);
576 } else {
577 cairo_fill(draw);
578 }
579 cairo_restore(draw);
580 }
581 }
582
583 // draw the text
584 cairo_save(draw);
585 double x1, y1, x2, y2;
586 cairo_clip_extents(draw, &x1, &y1, &x2, &y2);
587 cairo_reset_clip(draw);
588 cairo_rectangle(draw, x1, y1, x2 - x1, y2 - y1);
589 cairo_clip(draw);
590
591 gboolean show_outline;
592 if (tb->show_placeholder) {
593 rofi_theme_get_color(WIDGET(tb), "placeholder-color", draw);
594 show_outline = FALSE;
595 } else {
596 show_outline = rofi_theme_get_boolean(WIDGET(tb), "text-outline", FALSE);
597 }
598 cairo_move_to(draw, x, top);
599 pango_cairo_show_layout(draw, tb->layout);
600
601 if (show_outline) {
602 rofi_theme_get_color(WIDGET(tb), "text-outline-color", draw);
603 double width = rofi_theme_get_double(WIDGET(tb), "text-outline-width", 0.5);
604 cairo_move_to(draw, x, top);
605 pango_cairo_layout_path(draw, tb->layout);
606 cairo_set_line_width(draw, width);
607 cairo_stroke(draw);
608 }
609
610 cairo_restore(draw);
611}
612
613// cursor handling for edit mode
614void textbox_cursor(textbox *tb, int pos) {
615 if (tb == NULL) {
616 return;
617 }
618 int length = (tb->text == NULL) ? 0 : g_utf8_strlen(tb->text, -1);
619 tb->cursor = MAX(0, MIN(length, pos));
620 // Stop blink!
621 tb->blink = 3;
623}
624
633 int old = tb->cursor;
634 textbox_cursor(tb, tb->cursor + 1);
635 return old != tb->cursor;
636}
637
646 int old = tb->cursor;
647 textbox_cursor(tb, tb->cursor - 1);
648 return old != tb->cursor;
649}
650
651// Move word right
653 if (tb->text == NULL) {
654 return;
655 }
656 // Find word boundaries, with pango_Break?
657 gchar *c = g_utf8_offset_to_pointer(tb->text, tb->cursor);
658 while ((c = g_utf8_next_char(c))) {
659 gunichar uc = g_utf8_get_char(c);
660 GUnicodeBreakType bt = g_unichar_break_type(uc);
661 if ((bt == G_UNICODE_BREAK_ALPHABETIC ||
662 bt == G_UNICODE_BREAK_HEBREW_LETTER || bt == G_UNICODE_BREAK_NUMERIC ||
663 bt == G_UNICODE_BREAK_QUOTATION)) {
664 break;
665 }
666 }
667 if (c == NULL || *c == '\0') {
668 return;
669 }
670 while ((c = g_utf8_next_char(c))) {
671 gunichar uc = g_utf8_get_char(c);
672 GUnicodeBreakType bt = g_unichar_break_type(uc);
673 if (!(bt == G_UNICODE_BREAK_ALPHABETIC ||
674 bt == G_UNICODE_BREAK_HEBREW_LETTER ||
675 bt == G_UNICODE_BREAK_NUMERIC || bt == G_UNICODE_BREAK_QUOTATION)) {
676 break;
677 }
678 }
679 int index = g_utf8_pointer_to_offset(tb->text, c);
680 textbox_cursor(tb, index);
681}
682// move word left
684 // Find word boundaries, with pango_Break?
685 gchar *n;
686 gchar *c = g_utf8_offset_to_pointer(tb->text, tb->cursor);
687 while ((c = g_utf8_prev_char(c)) && c != tb->text) {
688 gunichar uc = g_utf8_get_char(c);
689 GUnicodeBreakType bt = g_unichar_break_type(uc);
690 if ((bt == G_UNICODE_BREAK_ALPHABETIC ||
691 bt == G_UNICODE_BREAK_HEBREW_LETTER || bt == G_UNICODE_BREAK_NUMERIC ||
692 bt == G_UNICODE_BREAK_QUOTATION)) {
693 break;
694 }
695 }
696 if (c != tb->text) {
697 while ((n = g_utf8_prev_char(c))) {
698 gunichar uc = g_utf8_get_char(n);
699 GUnicodeBreakType bt = g_unichar_break_type(uc);
700 if (!(bt == G_UNICODE_BREAK_ALPHABETIC ||
701 bt == G_UNICODE_BREAK_HEBREW_LETTER ||
702 bt == G_UNICODE_BREAK_NUMERIC || bt == G_UNICODE_BREAK_QUOTATION)) {
703 break;
704 }
705 c = n;
706 if (n == tb->text) {
707 break;
708 }
709 }
710 }
711 int index = g_utf8_pointer_to_offset(tb->text, c);
712 textbox_cursor(tb, index);
713}
714
715// end of line
717 if (tb->text == NULL) {
718 tb->cursor = 0;
720 return;
721 }
722 tb->cursor = (int)g_utf8_strlen(tb->text, -1);
724 // Stop blink!
725 tb->blink = 2;
726}
727
728// insert text
729void textbox_insert(textbox *tb, const int char_pos, const char *str,
730 const int slen) {
731 if (tb == NULL) {
732 return;
733 }
734 char *c = g_utf8_offset_to_pointer(tb->text, char_pos);
735 int pos = c - tb->text;
736 int len = (int)strlen(tb->text);
737 pos = MAX(0, MIN(len, pos));
738 // expand buffer
739 tb->text = g_realloc(tb->text, len + slen + 1);
740 // move everything after cursor upward
741 char *at = tb->text + pos;
742 memmove(at + slen, at, len - pos + 1);
743 // insert new str
744 memmove(at, str, slen);
745
746 // Set modified, lay out need te be redrawn
747 // Stop blink!
748 tb->blink = 2;
749 tb->changed = TRUE;
750}
751
752// remove text
753void textbox_delete(textbox *tb, int pos, int dlen) {
754 if (tb == NULL) {
755 return;
756 }
757 int len = g_utf8_strlen(tb->text, -1);
758 if (len == pos) {
759 return;
760 }
761 pos = MAX(0, MIN(len, pos));
762 if ((pos + dlen) > len) {
763 dlen = len - dlen;
764 }
765 // move everything after pos+dlen down
766 char *start = g_utf8_offset_to_pointer(tb->text, pos);
767 char *end = g_utf8_offset_to_pointer(tb->text, pos + dlen);
768 // Move remainder + closing \0
769 memmove(start, end, (tb->text + strlen(tb->text)) - end + 1);
770 if (tb->cursor >= pos && tb->cursor < (pos + dlen)) {
771 tb->cursor = pos;
772 } else if (tb->cursor >= (pos + dlen)) {
773 tb->cursor -= dlen;
774 }
775 // Set modified, lay out need te be redrawn
776 // Stop blink!
777 tb->blink = 2;
778 tb->changed = TRUE;
779}
780
786static void textbox_cursor_del(textbox *tb) {
787 if (tb == NULL || tb->text == NULL) {
788 return;
789 }
790 textbox_delete(tb, tb->cursor, 1);
791}
792
799 if (tb && tb->cursor > 0) {
802 }
803}
805 if (tb && tb->cursor > 0) {
806 int cursor = tb->cursor;
808 if (cursor > tb->cursor) {
809 textbox_delete(tb, tb->cursor, cursor - tb->cursor);
810 }
811 }
812}
814 if (tb && tb->cursor >= 0) {
815 int length = g_utf8_strlen(tb->text, -1) - tb->cursor;
816 if (length >= 0) {
817 textbox_delete(tb, tb->cursor, length);
818 }
819 }
820}
822 if (tb && tb->cursor >= 0) {
823 int length = tb->cursor;
824 textbox_delete(tb, 0, length);
825 }
826}
828 if (tb && tb->cursor >= 0) {
829 int cursor = tb->cursor;
831 if (cursor < tb->cursor) {
832 textbox_delete(tb, cursor, tb->cursor - cursor);
833 }
834 }
835}
836
837// handle a keypress in edit mode
838// 2 = nav
839// 0 = unhandled
840// 1 = handled
841// -1 = handled and return pressed (finished)
843 if (tb == NULL) {
844 return 0;
845 }
846 if (!(tb->flags & TB_EDITABLE)) {
847 return 0;
848 }
849
850 switch (action) {
851 // Left or Ctrl-b
852 case MOVE_CHAR_BACK:
853 return (textbox_cursor_dec(tb) == TRUE) ? 2 : 0;
854 // Right or Ctrl-F
856 return (textbox_cursor_inc(tb) == TRUE) ? 2 : 0;
857 // Ctrl-U: Kill from the beginning to the end of the line.
858 case CLEAR_LINE:
859 textbox_text(tb, "");
860 return 1;
861 // Ctrl-A
862 case MOVE_FRONT:
863 textbox_cursor(tb, 0);
864 return 2;
865 // Ctrl-E
866 case MOVE_END:
868 return 2;
869 // Ctrl-Alt-h
870 case REMOVE_WORD_BACK:
872 return 1;
873 // Ctrl-Alt-d
876 return 1;
877 case REMOVE_TO_EOL:
879 return 1;
880 case REMOVE_TO_SOL:
882 return 1;
883 // Delete or Ctrl-D
886 return 1;
887 // Alt-B, Ctrl-Left
888 case MOVE_WORD_BACK:
890 return 2;
891 // Alt-F, Ctrl-Right
894 return 2;
895 // BackSpace, Shift-BackSpace, Ctrl-h
896 case REMOVE_CHAR_BACK:
898 return 1;
899 default:
900 g_return_val_if_reached(0);
901 }
902}
903
904gboolean textbox_append_text(textbox *tb, const char *pad, const int pad_len) {
905 if (tb == NULL) {
906 return FALSE;
907 }
908 if (!(tb->flags & TB_EDITABLE)) {
909 return FALSE;
910 }
911
912 // Filter When alt/ctrl is pressed do not accept the character.
913
914 gboolean used_something = FALSE;
915 const gchar *w, *n, *e;
916 for (w = pad, n = g_utf8_next_char(w), e = w + pad_len; w < e;
917 w = n, n = g_utf8_next_char(n)) {
918 gunichar c = g_utf8_get_char(w);
919 if (g_unichar_isspace(c)) {
921 textbox_insert(tb, tb->cursor, " ", 1);
922 textbox_cursor(tb, tb->cursor + 1);
923 used_something = TRUE;
924 } else if (g_unichar_iscntrl(c)) {
925 /* skip control characters. */
926 g_info("Got an invalid character: %08X", c);
927 } else {
929 textbox_insert(tb, tb->cursor, w, n - w);
930 textbox_cursor(tb, tb->cursor + 1);
931 used_something = TRUE;
932 }
933 }
934 return used_something;
935}
936
937static void tbfc_entry_free(TBFontConfig *tbfc) {
938 pango_font_metrics_unref(tbfc->metrics);
939 if (tbfc->pfd) {
940 pango_font_description_free(tbfc->pfd);
941 }
942 g_free(tbfc);
943}
944void textbox_setup(void) {
945 tbfc_cache = g_hash_table_new_full(g_str_hash, g_str_equal, NULL,
946 (GDestroyNotify)tbfc_entry_free);
947}
948
950const char *default_font_name = "default";
951void textbox_set_pango_context(const char *font, PangoContext *p) {
952 g_assert(p_metrics == NULL);
953 p_context = g_object_ref(p);
954 p_metrics = pango_context_get_metrics(p_context, NULL, NULL);
955 TBFontConfig *tbfc = g_malloc0(sizeof(TBFontConfig));
956 tbfc->metrics = p_metrics;
957
958 PangoLayout *layout = pango_layout_new(p_context);
959 pango_layout_set_text(layout, "aAjb", -1);
960 PangoRectangle rect;
961 pango_layout_get_pixel_extents(layout, NULL, &rect);
962 tbfc->height = rect.y + rect.height;
963 if (tbfc->metrics) {
964 int h = pango_font_metrics_get_height(tbfc->metrics) / PANGO_SCALE;
965 if (h > 0) {
966 tbfc->height = h;
967 }
968 }
969 g_object_unref(layout);
970 tbfc_default = tbfc;
971
972 g_hash_table_insert(tbfc_cache, (gpointer *)(font ? font : default_font_name),
973 tbfc);
974}
975
976void textbox_cleanup(void) {
977 g_hash_table_destroy(tbfc_cache);
978 if (p_context) {
979 g_object_unref(p_context);
980 p_context = NULL;
981 }
982}
983
985 textbox *tb = (textbox *)wid;
986 if (tb->flags & TB_AUTOWIDTH) {
988 }
989 return tb->widget.w;
990}
991
993 textbox *tb = (textbox *)wid;
994 if (tb->flags & TB_AUTOHEIGHT) {
996 tb, pango_layout_get_line_count(tb->layout));
997 }
998 return tb->widget.h;
999}
1004
1006 PangoRectangle rect;
1007 pango_layout_get_pixel_extents(tb->layout, NULL, &rect);
1008 return rect.height + rect.y;
1009}
1010
1012 PangoRectangle rect;
1013 pango_layout_get_pixel_extents(tb->layout, NULL, &rect);
1014 return rect.width + rect.x;
1015}
1016
1018double textbox_get_estimated_char_height(void) { return tbfc_default->height; }
1019
1021static double char_width = -1;
1023 if (char_width < 0) {
1024 int width = pango_font_metrics_get_approximate_char_width(p_metrics);
1025 char_width = (width) / (double)PANGO_SCALE;
1026 }
1027 return char_width;
1028}
1029
1031static double ch_width = -1;
1033 if (ch_width < 0) {
1034 int width = pango_font_metrics_get_approximate_digit_width(p_metrics);
1035 ch_width = (width) / (double)PANGO_SCALE;
1036 }
1037 return ch_width;
1038}
1039
1041 int height = tb->tbfc->height;
1042 return (eh * height) + widget_padding_get_padding_height(WIDGET(tb));
1043}
1044int textbox_get_desired_width(widget *wid, G_GNUC_UNUSED const int height) {
1045 if (wid == NULL) {
1046 return 0;
1047 }
1048 textbox *tb = (textbox *)wid;
1049 if (wid->expand && tb->flags & TB_AUTOWIDTH) {
1051 }
1052 RofiDistance w = rofi_theme_get_distance(WIDGET(tb), "width", 0);
1054 if (wi > 0) {
1055 return wi;
1056 }
1057 int padding = widget_padding_get_left(WIDGET(tb));
1058 padding += widget_padding_get_right(WIDGET(tb));
1059 int old_width = pango_layout_get_width(tb->layout);
1060 pango_layout_set_width(tb->layout, -1);
1061 int width = textbox_get_font_width(tb);
1062 // Restore.
1063 pango_layout_set_width(tb->layout, old_width);
1064 return width + padding;
1065}
1066
1067void textbox_set_ellipsize(textbox *tb, PangoEllipsizeMode mode) {
1068 if (tb) {
1069 tb->emode = mode;
1070 if ((tb->flags & TB_WRAP) != TB_WRAP) {
1071 // Store the mode.
1072 pango_layout_set_ellipsize(tb->layout, tb->emode);
1074 }
1075 }
1076}
1078 if (tb == NULL) {
1079 return 0;
1080 }
1081 return tb->cursor_x_pos;
1082}
void helper_token_match_set_pango_attr_on_style(PangoAttrList *retv, int start, int end, RofiHighlightColorStyle th)
Definition helper.c:415
gboolean helper_validate_font(PangoFontDescription *pfd, const char *font)
Definition helper.c:626
KeyBindingAction
Definition keyb.h:58
MouseBindingMouseDefaultAction
Definition keyb.h:172
@ REMOVE_TO_SOL
Definition keyb.h:90
@ MOVE_FRONT
Definition keyb.h:68
@ REMOVE_WORD_FORWARD
Definition keyb.h:82
@ REMOVE_WORD_BACK
Definition keyb.h:80
@ MOVE_CHAR_FORWARD
Definition keyb.h:78
@ MOVE_WORD_FORWARD
Definition keyb.h:74
@ REMOVE_TO_EOL
Definition keyb.h:88
@ MOVE_WORD_BACK
Definition keyb.h:72
@ MOVE_END
Definition keyb.h:70
@ REMOVE_CHAR_BACK
Definition keyb.h:86
@ CLEAR_LINE
Definition keyb.h:66
@ MOVE_CHAR_BACK
Definition keyb.h:76
@ REMOVE_CHAR_FORWARD
Definition keyb.h:84
@ MOUSE_CLICK_DOWN
Definition keyb.h:173
@ MOUSE_DCLICK_UP
Definition keyb.h:176
@ MOUSE_CLICK_UP
Definition keyb.h:174
@ MOUSE_DCLICK_DOWN
Definition keyb.h:175
#define TICK_N(a)
Definition timings.h:69
int textbox_get_height(const textbox *tb)
Definition textbox.c:1000
void textbox_insert(textbox *tb, const int char_pos, const char *str, const int slen)
Definition textbox.c:729
void textbox_font(textbox *tb, TextBoxFontType tbft)
Definition textbox.c:299
TextboxFlags
Definition textbox.h:91
void textbox_delete(textbox *tb, int pos, int dlen)
Definition textbox.c:753
int textbox_keybinding(textbox *tb, KeyBindingAction action)
Definition textbox.c:842
TextBoxFontType
Definition textbox.h:102
void textbox_cleanup(void)
Definition textbox.c:976
double textbox_get_estimated_char_width(void)
Definition textbox.c:1022
int textbox_get_font_height(const textbox *tb)
Definition textbox.c:1005
void textbox_set_pango_attributes(textbox *tb, PangoAttrList *list)
Definition textbox.c:372
void textbox_set_ellipsize(textbox *tb, PangoEllipsizeMode mode)
Definition textbox.c:1067
int textbox_get_desired_width(widget *wid, G_GNUC_UNUSED const int height)
Definition textbox.c:1044
void textbox_setup(void)
Definition textbox.c:944
double textbox_get_estimated_char_height(void)
Definition textbox.c:1018
const char * textbox_get_visible_text(const textbox *tb)
Definition textbox.c:360
int textbox_get_cursor(const textbox *tb)
Definition textbox.c:385
int textbox_get_estimated_height(const textbox *tb, int eh)
Definition textbox.c:1040
void textbox_cursor(textbox *tb, int pos)
Definition textbox.c:614
void textbox_set_pango_context(const char *font, PangoContext *p)
Definition textbox.c:951
textbox * textbox_create(widget *parent, WidgetType type, const char *name, TextboxFlags flags, TextBoxFontType tbft, const char *text, double xalign, double yalign)
Definition textbox.c:204
int textbox_get_font_width(const textbox *tb)
Definition textbox.c:1011
void textbox_cursor_end(textbox *tb)
Definition textbox.c:716
gboolean textbox_append_text(textbox *tb, const char *pad, const int pad_len)
Definition textbox.c:904
void textbox_moveresize(textbox *tb, int x, int y, int w, int h)
Definition textbox.c:427
PangoAttrList * textbox_get_pango_attributes(textbox *tb)
Definition textbox.c:366
void textbox_text(textbox *tb, const char *text)
Definition textbox.c:392
double textbox_get_estimated_ch(void)
Definition textbox.c:1032
int textbox_get_cursor_x_pos(const textbox *tb)
Definition textbox.c:1077
char * textbox_get_text(const textbox *tb)
Definition textbox.c:379
@ TB_AUTOHEIGHT
Definition textbox.h:92
@ TB_PASSWORD
Definition textbox.h:97
@ TB_MARKUP
Definition textbox.h:95
@ TB_WRAP
Definition textbox.h:96
@ TB_EDITABLE
Definition textbox.h:94
@ TB_AUTOWIDTH
Definition textbox.h:93
@ URGENT
Definition textbox.h:106
@ ACTIVE
Definition textbox.h:108
@ HIGHLIGHT
Definition textbox.h:117
@ STATE_MASK
Definition textbox.h:121
@ ALT
Definition textbox.h:115
@ FMOD_MASK
Definition textbox.h:119
@ MARKUP
Definition textbox.h:112
void rofi_view_queue_redraw(void)
Definition view.c:593
void widget_queue_redraw(widget *wid)
Definition widget.c:477
struct _widget widget
Definition widget.h:51
void widget_update(widget *wid)
Definition widget.c:467
WidgetType
Definition widget.h:56
#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:141
RofiHighlightStyle style
Definition rofi-types.h:219
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
int cursor_x_pos
Definition textbox.h:79
char * text
Definition textbox.h:65
short cursor
Definition textbox.h:64
PangoEllipsizeMode emode
Definition textbox.h:83
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:81
unsigned long flags
Definition textbox.h:63
char * placeholder
Definition textbox.h:66
int changed
Definition textbox.h:71
static TBFontConfig * tbfc_default
Definition textbox.c:57
static PangoContext * p_context
Definition textbox.c:52
static int textbox_get_width(widget *)
Definition textbox.c:984
static void textbox_cursor_dec_word(textbox *tb)
Definition textbox.c:683
static void textbox_cursor_inc_word(textbox *tb)
Definition textbox.c:652
static gboolean textbox_blink(gpointer data)
Definition textbox.c:62
static WidgetTriggerActionResult textbox_editable_trigger_action(widget *wid, MouseBindingMouseDefaultAction action, gint x, gint y, G_GNUC_UNUSED void *user_data)
Definition textbox.c:98
const char *const theme_prop_names[][3]
Definition textbox.c:290
const char * default_font_name
Definition textbox.c:950
static double ch_width
Definition textbox.c:1031
static int textbox_get_desired_height(widget *wid, const int width)
Definition textbox.c:78
static int textbox_cursor_inc(textbox *tb)
Definition textbox.c:632
static void textbox_free(widget *)
Definition textbox.c:468
static void textbox_initialize_font(textbox *tb)
Definition textbox.c:127
static void textbox_resize(widget *wid, short w, short h)
Definition textbox.c:74
static void textbox_cursor_del_sol(textbox *tb)
Definition textbox.c:821
static void textbox_tab_stops(textbox *tb)
Definition textbox.c:179
static void textbox_cursor_bkspc(textbox *tb)
Definition textbox.c:798
static void textbox_cursor_bkspc_word(textbox *tb)
Definition textbox.c:804
static void textbox_draw(widget *, cairo_t *)
Definition textbox.c:487
static void textbox_cursor_del_word(textbox *tb)
Definition textbox.c:827
static PangoFontMetrics * p_metrics
Definition textbox.c:54
static void textbox_cursor_del(textbox *tb)
Definition textbox.c:786
static double char_width
Definition textbox.c:1021
static void __textbox_update_pango_text(textbox *tb)
Definition textbox.c:331
static int _textbox_get_height(widget *)
Definition textbox.c:992
static void textbox_cursor_del_eol(textbox *tb)
Definition textbox.c:813
static int textbox_cursor_dec(textbox *tb)
Definition textbox.c:645
static void tbfc_entry_free(TBFontConfig *tbfc)
Definition textbox.c:937
static GHashTable * tbfc_cache
Definition textbox.c:60
RofiHighlightColorStyle rofi_theme_get_highlight(widget *wid, const char *property, RofiHighlightColorStyle th)
Definition theme.c:1309
int distance_get_pixel(RofiDistance d, RofiOrientation ori)
Definition theme.c:1405
double rofi_theme_get_double(const widget *wid, const char *property, double def)
Definition theme.c:1038
int rofi_theme_get_boolean(const widget *wid, const char *property, int def)
Definition theme.c:901
GList * rofi_theme_get_list_distance(const widget *wid, const char *property)
Definition theme.c:1233
RofiDistance rofi_theme_get_distance(const widget *wid, const char *property, int def)
Definition theme.c:875
void rofi_theme_get_color(const widget *wid, const char *property, cairo_t *d)
Definition theme.c:1065
const char * rofi_theme_get_string(const widget *wid, const char *property, const char *def)
Definition theme.c:987
MenuFlags flags
Definition view.c:127
void widget_set_state(widget *wid, const char *state)
Definition widget.c:58
void widget_init(widget *wid, widget *parent, WidgetType type, const char *name)
Definition widget.c:35
int widget_padding_get_padding_width(const widget *wid)
Definition widget.c:627
int widget_padding_get_left(const widget *wid)
Definition widget.c:566
int widget_padding_get_right(const widget *wid)
Definition widget.c:576
int widget_padding_get_padding_height(const widget *wid)
Definition widget.c:621
int widget_padding_get_top(const widget *wid)
Definition widget.c:588
int widget_padding_get_bottom(const widget *wid)
Definition widget.c:598