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