rofi 1.7.9
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
255 const char *password_mask_char =
256 rofi_theme_get_string(WIDGET(tb), "password-mask", NULL);
257 if (password_mask_char == NULL || (*password_mask_char) == '\0') {
258 tb->password_mask_char = "*";
259 } else {
260 tb->password_mask_char = password_mask_char;
261 }
262
263 textbox_text(tb, txt ? txt : "");
265
266 tb->blink_timeout = 0;
267 tb->blink = 1;
268 if ((tb->flags & TB_EDITABLE) == TB_EDITABLE) {
269 if (rofi_theme_get_boolean(WIDGET(tb), "blink", TRUE)) {
270 tb->blink_timeout = g_timeout_add(1200, textbox_blink, tb);
271 }
273 }
274
275 tb->yalign = rofi_theme_get_double(WIDGET(tb), "vertical-align", yalign);
276 tb->yalign = MAX(0, MIN(1.0, tb->yalign));
277 tb->xalign = rofi_theme_get_double(WIDGET(tb), "horizontal-align", xalign);
278 tb->xalign = MAX(0, MIN(1.0, tb->xalign));
279
280 if (tb->xalign < 0.2) {
281 pango_layout_set_alignment(tb->layout, PANGO_ALIGN_LEFT);
282 } else if (tb->xalign < 0.8) {
283 pango_layout_set_alignment(tb->layout, PANGO_ALIGN_CENTER);
284 } else {
285 pango_layout_set_alignment(tb->layout, PANGO_ALIGN_RIGHT);
286 }
287 // auto height/width modes get handled here
288 // UPDATE: don't autoheight here, as there is no width set.
289 // so no height can be determined and might result into crash.
290 // textbox_moveresize(tb, tb->widget.x, tb->widget.y, tb->widget.w,
291 // tb->widget.h);
292
293 return tb;
294}
295
299const char *const theme_prop_names[][3] = {
301 {"normal.normal", "selected.normal", "alternate.normal"},
303 {"normal.urgent", "selected.urgent", "alternate.urgent"},
305 {"normal.active", "selected.active", "alternate.active"},
306};
307
309 TextBoxFontType t = tbft & STATE_MASK;
310 if (tb == NULL) {
311 return;
312 }
313 // ACTIVE has priority over URGENT if both set.
314 if (t == (URGENT | ACTIVE)) {
315 t = ACTIVE;
316 }
317 switch ((tbft & FMOD_MASK)) {
318 case HIGHLIGHT:
320 break;
321 case ALT:
323 break;
324 default:
326 break;
327 }
328 if (tb->tbft != tbft || tb->widget.state == NULL) {
330 }
331 tb->tbft = tbft;
332}
333
341 pango_layout_set_attributes(tb->layout, NULL);
342 if (tb->placeholder && (tb->text == NULL || tb->text[0] == 0)) {
343 tb->show_placeholder = TRUE;
344 pango_layout_set_markup(tb->layout, tb->placeholder, -1);
345 return;
346 }
347 tb->show_placeholder = FALSE;
348 if ((tb->flags & TB_PASSWORD) == TB_PASSWORD) {
349 size_t text_len = g_utf8_strlen(tb->text, -1);
350 size_t mask_len = strlen(tb->password_mask_char);
351 char string[text_len * mask_len + 1];
352 for (size_t offset = 0; offset < text_len * mask_len; offset += mask_len) {
353 memcpy(string + offset, tb->password_mask_char, mask_len);
354 }
355 string[text_len * mask_len] = '\0';
356 pango_layout_set_text(tb->layout, string, -1);
357 } else if (tb->flags & TB_MARKUP || tb->tbft & MARKUP) {
358 pango_layout_set_markup(tb->layout, tb->text, -1);
359 } else {
360 pango_layout_set_text(tb->layout, tb->text, -1);
361 }
362 if (tb->text) {
363 RofiHighlightColorStyle th = {0, {0.0, 0.0, 0.0, 0.0}};
364 th = rofi_theme_get_highlight(WIDGET(tb), "text-transform", th);
365 if (th.style != 0) {
366 PangoAttrList *list = pango_attr_list_new();
367 helper_token_match_set_pango_attr_on_style(list, 0, G_MAXUINT, th);
368 pango_layout_set_attributes(tb->layout, list);
369 }
370 }
371}
372const char *textbox_get_visible_text(const textbox *tb) {
373 if (tb == NULL) {
374 return NULL;
375 }
376 return pango_layout_get_text(tb->layout);
377}
379 if (tb == NULL) {
380 return NULL;
381 }
382 return pango_layout_get_attributes(tb->layout);
383}
384void textbox_set_pango_attributes(textbox *tb, PangoAttrList *list) {
385 if (tb == NULL) {
386 return;
387 }
388 pango_layout_set_attributes(tb->layout, list);
389}
390
391char *textbox_get_text(const textbox *tb) {
392 if (tb->text == NULL) {
393 return g_strdup("");
394 }
395 return g_strdup(tb->text);
396}
398 if (tb) {
399 return tb->cursor;
400 }
401 return 0;
402}
403// set the default text to display
404void textbox_text(textbox *tb, const char *text) {
405 if (tb == NULL) {
406 return;
407 }
408 g_free(tb->text);
409 const gchar *last_pointer = NULL;
410
411 if (text == NULL) {
412 tb->text = g_strdup("Invalid string.");
413 } else {
414 if (g_utf8_validate(text, -1, &last_pointer)) {
415 tb->text = g_strdup(text);
416 } else {
417 if (last_pointer != NULL) {
418 // Copy string up to invalid character.
419 tb->text = g_strndup(text, (last_pointer - text));
420 } else {
421 tb->text = g_strdup("Invalid UTF-8 string.");
422 }
423 }
424 }
426 if (tb->flags & TB_AUTOWIDTH) {
427 textbox_moveresize(tb, tb->widget.x, tb->widget.y, tb->widget.w,
428 tb->widget.h);
429 if (WIDGET(tb)->parent) {
430 widget_update(WIDGET(tb)->parent);
431 }
432 }
433
434 tb->cursor = MAX(0, MIN((int)g_utf8_strlen(tb->text, -1), tb->cursor));
436}
437
438// within the parent handled auto width/height modes
439void textbox_moveresize(textbox *tb, int x, int y, int w, int h) {
440 if (tb->flags & TB_AUTOWIDTH) {
441 pango_layout_set_width(tb->layout, -1);
442 w = textbox_get_font_width(tb) +
444 } else {
445 // set ellipsize
446 if ((tb->flags & TB_EDITABLE) == TB_EDITABLE) {
447 pango_layout_set_ellipsize(tb->layout, PANGO_ELLIPSIZE_MIDDLE);
448 } else if ((tb->flags & TB_WRAP) != TB_WRAP) {
449 pango_layout_set_ellipsize(tb->layout, tb->emode);
450 } else {
451 pango_layout_set_ellipsize(tb->layout, PANGO_ELLIPSIZE_NONE);
452 }
453 }
454
455 if (tb->flags & TB_AUTOHEIGHT) {
456 // Width determines height!
457 int padding = widget_padding_get_padding_width(WIDGET(tb));
458 int tw = MAX(1 + padding, w);
459 pango_layout_set_width(tb->layout, PANGO_SCALE * (tw - padding));
460 int hd = textbox_get_height(tb);
461 h = MAX(hd, h);
462 }
463
464 if (x != tb->widget.x || y != tb->widget.y || w != tb->widget.w ||
465 h != tb->widget.h) {
466 tb->widget.x = x;
467 tb->widget.y = y;
468 tb->widget.h = MAX(1, h);
469 tb->widget.w = MAX(1, w);
470 }
471
472 // We always want to update this
473 pango_layout_set_width(
474 tb->layout, PANGO_SCALE * (tb->widget.w -
477}
478
479// will also unmap the window if still displayed
480static void textbox_free(widget *wid) {
481 if (wid == NULL) {
482 return;
483 }
484 textbox *tb = (textbox *)wid;
485 if (tb->blink_timeout > 0) {
486 g_source_remove(tb->blink_timeout);
487 tb->blink_timeout = 0;
488 }
489 g_free(tb->text);
490
491 g_free(tb->placeholder);
492 if (tb->layout != NULL) {
493 g_object_unref(tb->layout);
494 }
495
496 g_slice_free(textbox, tb);
497}
498
499static void textbox_draw(widget *wid, cairo_t *draw) {
500 if (wid == NULL) {
501 return;
502 }
503 textbox *tb = (textbox *)wid;
504
505 if (tb->changed) {
507 }
508
509 // Skip the side MARGIN on the X axis.
510 int x;
511 int top = widget_padding_get_top(WIDGET(tb));
512 int y = (pango_font_metrics_get_ascent(tb->tbfc->metrics) -
513 pango_layout_get_baseline(tb->layout)) /
514 PANGO_SCALE;
515 int line_width = 0, line_height = 0;
516 // Get actual width.
517 pango_layout_get_pixel_size(tb->layout, &line_width, &line_height);
518
519 if (tb->yalign > 0.001) {
520 int bottom = widget_padding_get_bottom(WIDGET(tb));
521 top = (tb->widget.h - bottom - line_height - top) * tb->yalign + top;
522 }
523 y += top;
524
525 // Set ARGB
526 // NOTE: cairo operator must be OVER at this moment,
527 // to not break subpixel text rendering.
528
529 cairo_set_source_rgb(draw, 0.0, 0.0, 0.0);
530 // use text color as fallback for themes that don't specify the cursor color
531 rofi_theme_get_color(WIDGET(tb), "text-color", draw);
532
533 {
534 int rem =
536 line_width);
537 switch (pango_layout_get_alignment(tb->layout)) {
538 case PANGO_ALIGN_CENTER:
539 x = rem * (tb->xalign - 0.5);
540 break;
541 case PANGO_ALIGN_RIGHT:
542 x = rem * (tb->xalign - 1.0);
543 break;
544 default:
545 x = rem * tb->xalign;
546 break;
547 }
549 }
550
551 // draw the cursor
552 if (tb->flags & TB_EDITABLE) {
553 // We want to place the cursor based on the text shown.
554 const char *text = pango_layout_get_text(tb->layout);
555 // hide the cursor, if no text is entered and hide-empty-cursor is set to true
556 if (!(tb->text[0] == '\0' && rofi_theme_get_boolean(WIDGET(tb), "hide-cursor-on-empty", FALSE) == TRUE)){
557 // Clamp the position, should not be needed, but we are paranoid.
558 size_t cursor_offset;
559
560 if ((tb->flags & TB_PASSWORD) == TB_PASSWORD) {
561 // Calculate cursor position based on mask length
562 size_t mask_len = strlen(tb->password_mask_char);
563 cursor_offset = MIN(tb->cursor * mask_len, strlen(text));
564 } else {
565 cursor_offset = MIN(tb->cursor, g_utf8_strlen(text, -1));
566 // convert to byte location.
567 char *offset = g_utf8_offset_to_pointer(text, cursor_offset);
568 cursor_offset = offset - text;
569 }
570 PangoRectangle pos;
571 pango_layout_get_cursor_pos(tb->layout, cursor_offset, &pos, NULL);
572 int cursor_x = pos.x / PANGO_SCALE;
573 int cursor_y = pos.y / PANGO_SCALE;
574 int cursor_height = pos.height / PANGO_SCALE;
575 RofiDistance cursor_width =
576 rofi_theme_get_distance(WIDGET(tb), "cursor-width", 2);
577 int cursor_pixel_width =
579 if ((x + cursor_x) != tb->cursor_x_pos) {
580 tb->cursor_x_pos = x + cursor_x;
581 }
582 if (tb->blink) {
583 // This save/restore state is necessary to render the text in the
584 // correct color when `cursor-color` is set
585 cairo_save(draw);
586 // use text color as fallback for themes that don't specify the cursor
587 // color
588 rofi_theme_get_color(WIDGET(tb), "cursor-color", draw);
589 cairo_rectangle(draw, x + cursor_x, y + cursor_y, cursor_pixel_width,
590 cursor_height);
591 if (rofi_theme_get_boolean(WIDGET(tb), "cursor-outline", FALSE)) {
592 cairo_fill_preserve(draw);
593 rofi_theme_get_color(WIDGET(tb), "cursor-outline-color", draw);
594 double width =
595 rofi_theme_get_double(WIDGET(tb), "cursor-outline-width", 0.5);
596 cairo_set_line_width(draw, width);
597 cairo_stroke(draw);
598 } else {
599 cairo_fill(draw);
600 }
601 cairo_restore(draw);
602 }
603 }
604 }
605
606 // draw the text
607 cairo_save(draw);
608 double x1, y1, x2, y2;
609 cairo_clip_extents(draw, &x1, &y1, &x2, &y2);
610 cairo_reset_clip(draw);
611 cairo_rectangle(draw, x1, y1, x2 - x1, y2 - y1);
612 cairo_clip(draw);
613
614 gboolean show_outline;
615 if (tb->show_placeholder) {
616 rofi_theme_get_color(WIDGET(tb), "placeholder-color", draw);
617 show_outline = FALSE;
618 } else {
619 show_outline = rofi_theme_get_boolean(WIDGET(tb), "text-outline", FALSE);
620 }
621 cairo_move_to(draw, x, top);
622 pango_cairo_show_layout(draw, tb->layout);
623
624 if (show_outline) {
625 rofi_theme_get_color(WIDGET(tb), "text-outline-color", draw);
626 double width = rofi_theme_get_double(WIDGET(tb), "text-outline-width", 0.5);
627 cairo_move_to(draw, x, top);
628 pango_cairo_layout_path(draw, tb->layout);
629 cairo_set_line_width(draw, width);
630 cairo_stroke(draw);
631 }
632
633 cairo_restore(draw);
634}
635
636// cursor handling for edit mode
637void textbox_cursor(textbox *tb, int pos) {
638 if (tb == NULL) {
639 return;
640 }
641 int length = (tb->text == NULL) ? 0 : g_utf8_strlen(tb->text, -1);
642 tb->cursor = MAX(0, MIN(length, pos));
643 // Stop blink!
644 tb->blink = 3;
646}
647
656 int old = tb->cursor;
657 textbox_cursor(tb, tb->cursor + 1);
658 return old != tb->cursor;
659}
660
669 int old = tb->cursor;
670 textbox_cursor(tb, tb->cursor - 1);
671 return old != tb->cursor;
672}
673
674// Move word right
676 if (tb->text == NULL) {
677 return;
678 }
679 // Find word boundaries, with pango_Break?
680 gchar *c = g_utf8_offset_to_pointer(tb->text, tb->cursor);
681 while ((c = g_utf8_next_char(c))) {
682 gunichar uc = g_utf8_get_char(c);
683 GUnicodeBreakType bt = g_unichar_break_type(uc);
684 if ((bt == G_UNICODE_BREAK_ALPHABETIC ||
685 bt == G_UNICODE_BREAK_HEBREW_LETTER || bt == G_UNICODE_BREAK_NUMERIC ||
686 bt == G_UNICODE_BREAK_QUOTATION)) {
687 break;
688 }
689 }
690 if (c == NULL || *c == '\0') {
691 return;
692 }
693 while ((c = g_utf8_next_char(c))) {
694 gunichar uc = g_utf8_get_char(c);
695 GUnicodeBreakType bt = g_unichar_break_type(uc);
696 if (!(bt == G_UNICODE_BREAK_ALPHABETIC ||
697 bt == G_UNICODE_BREAK_HEBREW_LETTER ||
698 bt == G_UNICODE_BREAK_NUMERIC || bt == G_UNICODE_BREAK_QUOTATION)) {
699 break;
700 }
701 }
702 int index = g_utf8_pointer_to_offset(tb->text, c);
703 textbox_cursor(tb, index);
704}
705// move word left
707 // Find word boundaries, with pango_Break?
708 gchar *n;
709 gchar *c = g_utf8_offset_to_pointer(tb->text, tb->cursor);
710 while ((c = g_utf8_prev_char(c)) && c != tb->text) {
711 gunichar uc = g_utf8_get_char(c);
712 GUnicodeBreakType bt = g_unichar_break_type(uc);
713 if ((bt == G_UNICODE_BREAK_ALPHABETIC ||
714 bt == G_UNICODE_BREAK_HEBREW_LETTER || bt == G_UNICODE_BREAK_NUMERIC ||
715 bt == G_UNICODE_BREAK_QUOTATION)) {
716 break;
717 }
718 }
719 if (c != tb->text) {
720 while ((n = g_utf8_prev_char(c))) {
721 gunichar uc = g_utf8_get_char(n);
722 GUnicodeBreakType bt = g_unichar_break_type(uc);
723 if (!(bt == G_UNICODE_BREAK_ALPHABETIC ||
724 bt == G_UNICODE_BREAK_HEBREW_LETTER ||
725 bt == G_UNICODE_BREAK_NUMERIC || bt == G_UNICODE_BREAK_QUOTATION)) {
726 break;
727 }
728 c = n;
729 if (n == tb->text) {
730 break;
731 }
732 }
733 }
734 int index = g_utf8_pointer_to_offset(tb->text, c);
735 textbox_cursor(tb, index);
736}
737
738// end of line
740 if (tb->text == NULL) {
741 tb->cursor = 0;
743 return;
744 }
745 tb->cursor = (int)g_utf8_strlen(tb->text, -1);
747 // Stop blink!
748 tb->blink = 2;
749}
750
751// insert text
752void textbox_insert(textbox *tb, const int char_pos, const char *str,
753 const int slen) {
754 if (tb == NULL) {
755 return;
756 }
757 char *c = g_utf8_offset_to_pointer(tb->text, char_pos);
758 int pos = c - tb->text;
759 int len = (int)strlen(tb->text);
760 pos = MAX(0, MIN(len, pos));
761 // expand buffer
762 tb->text = g_realloc(tb->text, len + slen + 1);
763 // move everything after cursor upward
764 char *at = tb->text + pos;
765 memmove(at + slen, at, len - pos + 1);
766 // insert new str
767 memmove(at, str, slen);
768
769 // Set modified, lay out need te be redrawn
770 // Stop blink!
771 tb->blink = 2;
772 tb->changed = TRUE;
773}
774
775// remove text
776void textbox_delete(textbox *tb, int pos, int dlen) {
777 if (tb == NULL) {
778 return;
779 }
780 int len = g_utf8_strlen(tb->text, -1);
781 if (len == pos) {
782 return;
783 }
784 pos = MAX(0, MIN(len, pos));
785 if ((pos + dlen) > len) {
786 dlen = len - dlen;
787 }
788 // move everything after pos+dlen down
789 char *start = g_utf8_offset_to_pointer(tb->text, pos);
790 char *end = g_utf8_offset_to_pointer(tb->text, pos + dlen);
791 // Move remainder + closing \0
792 memmove(start, end, (tb->text + strlen(tb->text)) - end + 1);
793 if (tb->cursor >= pos && tb->cursor < (pos + dlen)) {
794 tb->cursor = pos;
795 } else if (tb->cursor >= (pos + dlen)) {
796 tb->cursor -= dlen;
797 }
798 // Set modified, lay out need te be redrawn
799 // Stop blink!
800 tb->blink = 2;
801 tb->changed = TRUE;
802}
803
809static void textbox_cursor_del(textbox *tb) {
810 if (tb == NULL || tb->text == NULL) {
811 return;
812 }
813 textbox_delete(tb, tb->cursor, 1);
814}
815
822 if (tb && tb->cursor > 0) {
825 }
826}
828 if (tb && tb->cursor > 0) {
829 int cursor = tb->cursor;
831 if (cursor > tb->cursor) {
832 textbox_delete(tb, tb->cursor, cursor - tb->cursor);
833 }
834 }
835}
837 if (tb && tb->cursor >= 0) {
838 int length = g_utf8_strlen(tb->text, -1) - tb->cursor;
839 if (length >= 0) {
840 textbox_delete(tb, tb->cursor, length);
841 }
842 }
843}
845 if (tb && tb->cursor >= 0) {
846 int length = tb->cursor;
847 textbox_delete(tb, 0, length);
848 }
849}
851 if (tb && tb->cursor >= 0) {
852 int cursor = tb->cursor;
854 if (cursor < tb->cursor) {
855 textbox_delete(tb, cursor, tb->cursor - cursor);
856 }
857 }
858}
859
860// handle a keypress in edit mode
861// 2 = nav
862// 0 = unhandled
863// 1 = handled
864// -1 = handled and return pressed (finished)
866 if (tb == NULL) {
867 return 0;
868 }
869 if (!(tb->flags & TB_EDITABLE)) {
870 return 0;
871 }
872
873 switch (action) {
874 // Left or Ctrl-b
875 case MOVE_CHAR_BACK:
876 return (textbox_cursor_dec(tb) == TRUE) ? 2 : 0;
877 // Right or Ctrl-F
879 return (textbox_cursor_inc(tb) == TRUE) ? 2 : 0;
880 // Ctrl-U: Kill from the beginning to the end of the line.
881 case CLEAR_LINE:
882 textbox_text(tb, "");
883 return 1;
884 // Ctrl-A
885 case MOVE_FRONT:
886 textbox_cursor(tb, 0);
887 return 2;
888 // Ctrl-E
889 case MOVE_END:
891 return 2;
892 // Ctrl-Alt-h
893 case REMOVE_WORD_BACK:
895 return 1;
896 // Ctrl-Alt-d
899 return 1;
900 case REMOVE_TO_EOL:
902 return 1;
903 case REMOVE_TO_SOL:
905 return 1;
906 // Delete or Ctrl-D
909 return 1;
910 // Alt-B, Ctrl-Left
911 case MOVE_WORD_BACK:
913 return 2;
914 // Alt-F, Ctrl-Right
917 return 2;
918 // BackSpace, Shift-BackSpace, Ctrl-h
919 case REMOVE_CHAR_BACK:
921 return 1;
922 default:
923 g_return_val_if_reached(0);
924 }
925}
926
927gboolean textbox_append_text(textbox *tb, const char *pad, const int pad_len) {
928 if (tb == NULL) {
929 return FALSE;
930 }
931 if (!(tb->flags & TB_EDITABLE)) {
932 return FALSE;
933 }
934
935 // Filter When alt/ctrl is pressed do not accept the character.
936
937 gboolean used_something = FALSE;
938 const gchar *w, *n, *e;
939 for (w = pad, n = g_utf8_next_char(w), e = w + pad_len; w < e;
940 w = n, n = g_utf8_next_char(n)) {
941 gunichar c = g_utf8_get_char(w);
942 if (g_unichar_isspace(c)) {
944 textbox_insert(tb, tb->cursor, " ", 1);
945 textbox_cursor(tb, tb->cursor + 1);
946 used_something = TRUE;
947 } else if (g_unichar_iscntrl(c)) {
948 /* skip control characters. */
949 g_info("Got an invalid character: %08X", c);
950 } else {
952 textbox_insert(tb, tb->cursor, w, n - w);
953 textbox_cursor(tb, tb->cursor + 1);
954 used_something = TRUE;
955 }
956 }
957 return used_something;
958}
959
960static void tbfc_entry_free(TBFontConfig *tbfc) {
961 pango_font_metrics_unref(tbfc->metrics);
962 if (tbfc->pfd) {
963 pango_font_description_free(tbfc->pfd);
964 }
965 g_free(tbfc);
966}
967void textbox_setup(void) {
968 tbfc_cache = g_hash_table_new_full(g_str_hash, g_str_equal, NULL,
969 (GDestroyNotify)tbfc_entry_free);
970}
971
973const char *default_font_name = "default";
974void textbox_set_pango_context(const char *font, PangoContext *p) {
975 g_assert(p_metrics == NULL);
976 p_context = g_object_ref(p);
977 p_metrics = pango_context_get_metrics(p_context, NULL, NULL);
978 TBFontConfig *tbfc = g_malloc0(sizeof(TBFontConfig));
979 tbfc->metrics = p_metrics;
980
981 PangoLayout *layout = pango_layout_new(p_context);
982 pango_layout_set_text(layout, "aAjb", -1);
983 PangoRectangle rect;
984 pango_layout_get_pixel_extents(layout, NULL, &rect);
985 tbfc->height = rect.y + rect.height;
986 if (tbfc->metrics) {
987 int h = pango_font_metrics_get_height(tbfc->metrics) / PANGO_SCALE;
988 if (h > 0) {
989 tbfc->height = h;
990 }
991 }
992 g_object_unref(layout);
993 tbfc_default = tbfc;
994
995 g_hash_table_insert(tbfc_cache, (gpointer *)(font ? font : default_font_name),
996 tbfc);
997}
998
999void textbox_cleanup(void) {
1000 g_hash_table_destroy(tbfc_cache);
1001 if (p_context) {
1002 g_object_unref(p_context);
1003 p_context = NULL;
1004 }
1005}
1006
1008 textbox *tb = (textbox *)wid;
1009 if (tb->flags & TB_AUTOWIDTH) {
1011 }
1012 return tb->widget.w;
1013}
1014
1016 textbox *tb = (textbox *)wid;
1017 if (tb->flags & TB_AUTOHEIGHT) {
1019 tb, pango_layout_get_line_count(tb->layout));
1020 }
1021 return tb->widget.h;
1022}
1027
1029 PangoRectangle rect;
1030 pango_layout_get_pixel_extents(tb->layout, NULL, &rect);
1031 return rect.height + rect.y;
1032}
1033
1035 PangoRectangle rect;
1036 pango_layout_get_pixel_extents(tb->layout, NULL, &rect);
1037 return rect.width + rect.x;
1038}
1039
1041double textbox_get_estimated_char_height(void) { return tbfc_default->height; }
1042
1044static double char_width = -1;
1046 if (char_width < 0) {
1047 int width = pango_font_metrics_get_approximate_char_width(p_metrics);
1048 char_width = (width) / (double)PANGO_SCALE;
1049 }
1050 return char_width;
1051}
1052
1054static double ch_width = -1;
1056 if (ch_width < 0) {
1057 int width = pango_font_metrics_get_approximate_digit_width(p_metrics);
1058 ch_width = (width) / (double)PANGO_SCALE;
1059 }
1060 return ch_width;
1061}
1062
1064 int height = tb->tbfc->height;
1065 return (eh * height) + widget_padding_get_padding_height(WIDGET(tb));
1066}
1067int textbox_get_desired_width(widget *wid, G_GNUC_UNUSED const int height) {
1068 if (wid == NULL) {
1069 return 0;
1070 }
1071 textbox *tb = (textbox *)wid;
1072 if (wid->expand && tb->flags & TB_AUTOWIDTH) {
1074 }
1075 RofiDistance w = rofi_theme_get_distance(WIDGET(tb), "width", 0);
1077 if (wi > 0) {
1078 return wi;
1079 }
1080 int padding = widget_padding_get_left(WIDGET(tb));
1081 padding += widget_padding_get_right(WIDGET(tb));
1082 int old_width = pango_layout_get_width(tb->layout);
1083 pango_layout_set_width(tb->layout, -1);
1084 int width = textbox_get_font_width(tb);
1085 // Restore.
1086 pango_layout_set_width(tb->layout, old_width);
1087 return width + padding;
1088}
1089
1090void textbox_set_ellipsize(textbox *tb, PangoEllipsizeMode mode) {
1091 if (tb) {
1092 tb->emode = mode;
1093 if ((tb->flags & TB_WRAP) != TB_WRAP) {
1094 // Store the mode.
1095 pango_layout_set_ellipsize(tb->layout, tb->emode);
1097 }
1098 }
1099}
1101 if (tb == NULL) {
1102 return 0;
1103 }
1104 return tb->cursor_x_pos;
1105}
void helper_token_match_set_pango_attr_on_style(PangoAttrList *retv, int start, int end, RofiHighlightColorStyle th)
Definition helper.c:442
gboolean helper_validate_font(PangoFontDescription *pfd, const char *font)
Definition helper.c:653
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:1023
void textbox_insert(textbox *tb, const int char_pos, const char *str, const int slen)
Definition textbox.c:752
void textbox_font(textbox *tb, TextBoxFontType tbft)
Definition textbox.c:308
TextboxFlags
Definition textbox.h:93
void textbox_delete(textbox *tb, int pos, int dlen)
Definition textbox.c:776
int textbox_keybinding(textbox *tb, KeyBindingAction action)
Definition textbox.c:865
TextBoxFontType
Definition textbox.h:104
void textbox_cleanup(void)
Definition textbox.c:999
double textbox_get_estimated_char_width(void)
Definition textbox.c:1045
int textbox_get_font_height(const textbox *tb)
Definition textbox.c:1028
void textbox_set_pango_attributes(textbox *tb, PangoAttrList *list)
Definition textbox.c:384
void textbox_set_ellipsize(textbox *tb, PangoEllipsizeMode mode)
Definition textbox.c:1090
int textbox_get_desired_width(widget *wid, G_GNUC_UNUSED const int height)
Definition textbox.c:1067
void textbox_setup(void)
Definition textbox.c:967
double textbox_get_estimated_char_height(void)
Definition textbox.c:1041
const char * textbox_get_visible_text(const textbox *tb)
Definition textbox.c:372
int textbox_get_cursor(const textbox *tb)
Definition textbox.c:397
int textbox_get_estimated_height(const textbox *tb, int eh)
Definition textbox.c:1063
void textbox_cursor(textbox *tb, int pos)
Definition textbox.c:637
void textbox_set_pango_context(const char *font, PangoContext *p)
Definition textbox.c:974
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:1034
void textbox_cursor_end(textbox *tb)
Definition textbox.c:739
gboolean textbox_append_text(textbox *tb, const char *pad, const int pad_len)
Definition textbox.c:927
void textbox_moveresize(textbox *tb, int x, int y, int w, int h)
Definition textbox.c:439
PangoAttrList * textbox_get_pango_attributes(textbox *tb)
Definition textbox.c:378
void textbox_text(textbox *tb, const char *text)
Definition textbox.c:404
double textbox_get_estimated_ch(void)
Definition textbox.c:1055
int textbox_get_cursor_x_pos(const textbox *tb)
Definition textbox.c:1100
char * textbox_get_text(const textbox *tb)
Definition textbox.c:391
@ TB_AUTOHEIGHT
Definition textbox.h:94
@ TB_PASSWORD
Definition textbox.h:99
@ TB_MARKUP
Definition textbox.h:97
@ TB_WRAP
Definition textbox.h:98
@ TB_EDITABLE
Definition textbox.h:96
@ TB_AUTOWIDTH
Definition textbox.h:95
@ URGENT
Definition textbox.h:108
@ ACTIVE
Definition textbox.h:110
@ HIGHLIGHT
Definition textbox.h:119
@ STATE_MASK
Definition textbox.h:123
@ ALT
Definition textbox.h:117
@ FMOD_MASK
Definition textbox.h:121
@ MARKUP
Definition textbox.h:114
void rofi_view_queue_redraw(void)
Definition view.c:609
void widget_queue_redraw(widget *wid)
Definition widget.c:487
struct _widget widget
Definition widget.h:51
void widget_update(widget *wid)
Definition widget.c:477
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
const char * password_mask_char
Definition textbox.h:85
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:1007
static void textbox_cursor_dec_word(textbox *tb)
Definition textbox.c:706
static void textbox_cursor_inc_word(textbox *tb)
Definition textbox.c:675
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:299
const char * default_font_name
Definition textbox.c:973
static double ch_width
Definition textbox.c:1054
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:655
static void textbox_free(widget *)
Definition textbox.c:480
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:844
static void textbox_tab_stops(textbox *tb)
Definition textbox.c:179
static void textbox_cursor_bkspc(textbox *tb)
Definition textbox.c:821
static void textbox_cursor_bkspc_word(textbox *tb)
Definition textbox.c:827
static void textbox_draw(widget *, cairo_t *)
Definition textbox.c:499
static void textbox_cursor_del_word(textbox *tb)
Definition textbox.c:850
static PangoFontMetrics * p_metrics
Definition textbox.c:54
static void textbox_cursor_del(textbox *tb)
Definition textbox.c:809
static double char_width
Definition textbox.c:1044
static void __textbox_update_pango_text(textbox *tb)
Definition textbox.c:340
static int _textbox_get_height(widget *)
Definition textbox.c:1015
static void textbox_cursor_del_eol(textbox *tb)
Definition textbox.c:836
static int textbox_cursor_dec(textbox *tb)
Definition textbox.c:668
static void tbfc_entry_free(TBFontConfig *tbfc)
Definition textbox.c:960
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: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