rofi 2.1.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 <stdint.h>
41#include <string.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,
205 TextboxFlags flags, TextBoxFontType tbft,
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 if (text_len > ((SIZE_MAX - 1) / mask_len)) {
352 text_len = ((SIZE_MAX - 1) / mask_len);
353 }
354
355 char *string = g_malloc(text_len * mask_len + 1);
356 if (string) {
357 for (size_t offset = 0; offset < text_len * mask_len;
358 offset += mask_len) {
359 memcpy(string + offset, tb->password_mask_char, mask_len);
360 }
361 string[text_len * mask_len] = '\0';
362 pango_layout_set_text(tb->layout, string, -1);
363 g_free(string);
364 }
365 } else if (tb->flags & TB_MARKUP || tb->tbft & MARKUP) {
366 pango_layout_set_markup(tb->layout, tb->text, -1);
367 } else {
368 pango_layout_set_text(tb->layout, tb->text, -1);
369 }
370 if (tb->text) {
371 RofiHighlightColorStyle th = {0, {0.0, 0.0, 0.0, 0.0}};
372 th = rofi_theme_get_highlight(WIDGET(tb), "text-transform", th);
373 if (th.style != 0) {
374 PangoAttrList *list = pango_attr_list_new();
375 helper_token_match_set_pango_attr_on_style(list, 0, G_MAXUINT, th);
376 pango_layout_set_attributes(tb->layout, list);
377 }
378 }
379}
380const char *textbox_get_visible_text(const textbox *tb) {
381 if (tb == NULL) {
382 return NULL;
383 }
384 return pango_layout_get_text(tb->layout);
385}
387 if (tb == NULL) {
388 return NULL;
389 }
390 return pango_layout_get_attributes(tb->layout);
391}
392void textbox_set_pango_attributes(textbox *tb, PangoAttrList *list) {
393 if (tb == NULL) {
394 return;
395 }
396 pango_layout_set_attributes(tb->layout, list);
397}
398
399char *textbox_get_text(const textbox *tb) {
400 if (tb->text == NULL) {
401 return g_strdup("");
402 }
403 return g_strdup(tb->text);
404}
406 if (tb) {
407 return tb->cursor;
408 }
409 return 0;
410}
411// set the default text to display
412void textbox_text(textbox *tb, const char *text) {
413 if (tb == NULL) {
414 return;
415 }
416 g_free(tb->text);
417 const gchar *last_pointer = NULL;
418
419 if (text == NULL) {
420 tb->text = g_strdup("Invalid string.");
421 } else {
422 if (g_utf8_validate(text, -1, &last_pointer)) {
423 tb->text = g_strdup(text);
424 } else {
425 if (last_pointer != NULL) {
426 // Copy string up to invalid character.
427 tb->text = g_strndup(text, (last_pointer - text));
428 } else {
429 tb->text = g_strdup("Invalid UTF-8 string.");
430 }
431 }
432 }
434 if (tb->flags & TB_AUTOWIDTH) {
435 textbox_moveresize(tb, tb->widget.x, tb->widget.y, tb->widget.w,
436 tb->widget.h);
437 if (WIDGET(tb)->parent) {
438 widget_update(WIDGET(tb)->parent);
439 }
440 }
441
442 tb->cursor = MAX(0, MIN((int)g_utf8_strlen(tb->text, -1), tb->cursor));
444}
445
446// within the parent handled auto width/height modes
447void textbox_moveresize(textbox *tb, int x, int y, int w, int h) {
448 if (tb->flags & TB_AUTOWIDTH) {
449 pango_layout_set_width(tb->layout, -1);
450 w = textbox_get_font_width(tb) +
452 } else {
453 // set ellipsize
454 if ((tb->flags & TB_EDITABLE) == TB_EDITABLE) {
455 pango_layout_set_ellipsize(tb->layout, PANGO_ELLIPSIZE_MIDDLE);
456 } else if ((tb->flags & TB_WRAP) != TB_WRAP) {
457 pango_layout_set_ellipsize(tb->layout, tb->emode);
458 } else {
459 pango_layout_set_ellipsize(tb->layout, PANGO_ELLIPSIZE_NONE);
460 }
461 }
462
463 if (tb->flags & TB_AUTOHEIGHT) {
464 // Width determines height!
465 int padding = widget_padding_get_padding_width(WIDGET(tb));
466 int tw = MAX(1 + padding, w);
467 pango_layout_set_width(tb->layout, PANGO_SCALE * (tw - padding));
468 int hd = textbox_get_height(tb);
469 h = MAX(hd, h);
470 }
471
472 if (x != tb->widget.x || y != tb->widget.y || w != tb->widget.w ||
473 h != tb->widget.h) {
474 tb->widget.x = x;
475 tb->widget.y = y;
476 tb->widget.h = MAX(1, h);
477 tb->widget.w = MAX(1, w);
478 }
479
480 // We always want to update this
481 pango_layout_set_width(
482 tb->layout, PANGO_SCALE * (tb->widget.w -
485}
486
487// will also unmap the window if still displayed
488static void textbox_free(widget *wid) {
489 if (wid == NULL) {
490 return;
491 }
492 textbox *tb = (textbox *)wid;
493 if (tb->blink_timeout > 0) {
494 g_source_remove(tb->blink_timeout);
495 tb->blink_timeout = 0;
496 }
497 g_free(tb->text);
498
499 g_free(tb->placeholder);
500 if (tb->layout != NULL) {
501 g_object_unref(tb->layout);
502 }
503
504 g_slice_free(textbox, tb);
505}
506
507static void textbox_draw(widget *wid, cairo_t *draw) {
508 if (wid == NULL) {
509 return;
510 }
511 textbox *tb = (textbox *)wid;
512
513 if (tb->changed) {
515 }
516
517 // Skip the side MARGIN on the X axis.
518 int x;
519 int top = widget_padding_get_top(WIDGET(tb));
520 int y = (pango_font_metrics_get_ascent(tb->tbfc->metrics) -
521 pango_layout_get_baseline(tb->layout)) /
522 PANGO_SCALE;
523 int line_width = 0, line_height = 0;
524 // Get actual width.
525 pango_layout_get_pixel_size(tb->layout, &line_width, &line_height);
526
527 if (tb->yalign > 0.001) {
528 int bottom = widget_padding_get_bottom(WIDGET(tb));
529 top = (tb->widget.h - bottom - line_height - top) * tb->yalign + top;
530 }
531 y += top;
532
533 // Set ARGB
534 // NOTE: cairo operator must be OVER at this moment,
535 // to not break subpixel text rendering.
536
537 cairo_set_source_rgb(draw, 0.0, 0.0, 0.0);
538 // use text color as fallback for themes that don't specify the cursor color
539 rofi_theme_get_color(WIDGET(tb), "text-color", draw);
540
541 {
542 int rem =
544 line_width);
545 switch (pango_layout_get_alignment(tb->layout)) {
546 case PANGO_ALIGN_CENTER:
547 x = rem * (tb->xalign - 0.5);
548 break;
549 case PANGO_ALIGN_RIGHT:
550 x = rem * (tb->xalign - 1.0);
551 break;
552 default:
553 x = rem * tb->xalign;
554 break;
555 }
557 }
558
559 // draw the cursor
560 if (tb->flags & TB_EDITABLE) {
561 // We want to place the cursor based on the text shown.
562 const char *text = pango_layout_get_text(tb->layout);
563 // hide the cursor, if no text is entered and hide-empty-cursor is set to
564 // true
565 if (!(tb->text[0] == '\0' &&
566 rofi_theme_get_boolean(WIDGET(tb), "hide-cursor-on-empty", FALSE) ==
567 TRUE)) {
568 // Clamp the position, should not be needed, but we are paranoid.
569 size_t cursor_offset;
570
571 if ((tb->flags & TB_PASSWORD) == TB_PASSWORD) {
572 // Calculate cursor position based on mask length
573 size_t mask_len = strlen(tb->password_mask_char);
574 cursor_offset = MIN(tb->cursor * mask_len, strlen(text));
575 } else {
576 cursor_offset = MIN(tb->cursor, g_utf8_strlen(text, -1));
577 // convert to byte location.
578 char *offset = g_utf8_offset_to_pointer(text, cursor_offset);
579 cursor_offset = offset - text;
580 }
581 PangoRectangle pos;
582 pango_layout_get_cursor_pos(tb->layout, cursor_offset, &pos, NULL);
583 int cursor_x = pos.x / PANGO_SCALE;
584 int cursor_y = pos.y / PANGO_SCALE;
585 int cursor_height = pos.height / PANGO_SCALE;
586 RofiDistance cursor_width =
587 rofi_theme_get_distance(WIDGET(tb), "cursor-width", 2);
588 int cursor_pixel_width =
590 if ((x + cursor_x) != tb->cursor_x_pos) {
591 tb->cursor_x_pos = x + cursor_x;
592 }
593 if (tb->blink) {
594 // This save/restore state is necessary to render the text in the
595 // correct color when `cursor-color` is set
596 cairo_save(draw);
597 // use text color as fallback for themes that don't specify the cursor
598 // color
599 rofi_theme_get_color(WIDGET(tb), "cursor-color", draw);
600 cairo_rectangle(draw, x + cursor_x, y + cursor_y, cursor_pixel_width,
601 cursor_height);
602 if (rofi_theme_get_boolean(WIDGET(tb), "cursor-outline", FALSE)) {
603 cairo_fill_preserve(draw);
604 rofi_theme_get_color(WIDGET(tb), "cursor-outline-color", draw);
605 double width =
606 rofi_theme_get_double(WIDGET(tb), "cursor-outline-width", 0.5);
607 cairo_set_line_width(draw, width);
608 cairo_stroke(draw);
609 } else {
610 cairo_fill(draw);
611 }
612 cairo_restore(draw);
613 }
614 }
615 }
616
617 // draw the text
618 cairo_save(draw);
619 double x1, y1, x2, y2;
620 cairo_clip_extents(draw, &x1, &y1, &x2, &y2);
621 cairo_reset_clip(draw);
622 cairo_rectangle(draw, x1, y1, x2 - x1, y2 - y1);
623 cairo_clip(draw);
624
625 gboolean show_outline;
626 if (tb->show_placeholder) {
627 rofi_theme_get_color(WIDGET(tb), "placeholder-color", draw);
628 show_outline = FALSE;
629 } else {
630 show_outline = rofi_theme_get_boolean(WIDGET(tb), "text-outline", FALSE);
631 }
632 cairo_move_to(draw, x, top);
633 pango_cairo_show_layout(draw, tb->layout);
634
635 if (show_outline) {
636 rofi_theme_get_color(WIDGET(tb), "text-outline-color", draw);
637 double width = rofi_theme_get_double(WIDGET(tb), "text-outline-width", 0.5);
638 cairo_move_to(draw, x, top);
639 pango_cairo_layout_path(draw, tb->layout);
640 cairo_set_line_width(draw, width);
641 cairo_stroke(draw);
642 }
643
644 cairo_restore(draw);
645}
646
647// cursor handling for edit mode
648void textbox_cursor(textbox *tb, int pos) {
649 if (tb == NULL) {
650 return;
651 }
652 int length = (tb->text == NULL) ? 0 : g_utf8_strlen(tb->text, -1);
653 tb->cursor = MAX(0, MIN(length, pos));
654 // Stop blink!
655 tb->blink = 3;
657}
658
667 int old = tb->cursor;
668 textbox_cursor(tb, tb->cursor + 1);
669 return old != tb->cursor;
670}
671
680 int old = tb->cursor;
681 textbox_cursor(tb, tb->cursor - 1);
682 return old != tb->cursor;
683}
684
685// Move word right
687 if (tb->text == NULL) {
688 return;
689 }
690 // Find word boundaries, with pango_Break?
691 gchar *c = g_utf8_offset_to_pointer(tb->text, tb->cursor);
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 || bt == G_UNICODE_BREAK_NUMERIC ||
697 bt == G_UNICODE_BREAK_QUOTATION)) {
698 break;
699 }
700 }
701 if (c == NULL || *c == '\0') {
702 return;
703 }
704 while ((c = g_utf8_next_char(c))) {
705 gunichar uc = g_utf8_get_char(c);
706 GUnicodeBreakType bt = g_unichar_break_type(uc);
707 if (!(bt == G_UNICODE_BREAK_ALPHABETIC ||
708 bt == G_UNICODE_BREAK_HEBREW_LETTER ||
709 bt == G_UNICODE_BREAK_NUMERIC || bt == G_UNICODE_BREAK_QUOTATION)) {
710 break;
711 }
712 }
713 int index = g_utf8_pointer_to_offset(tb->text, c);
714 textbox_cursor(tb, index);
715}
716// move word left
718 // Find word boundaries, with pango_Break?
719 gchar *n;
720 gchar *c = g_utf8_offset_to_pointer(tb->text, tb->cursor);
721 while ((c = g_utf8_prev_char(c)) && c != tb->text) {
722 gunichar uc = g_utf8_get_char(c);
723 GUnicodeBreakType bt = g_unichar_break_type(uc);
724 if ((bt == G_UNICODE_BREAK_ALPHABETIC ||
725 bt == G_UNICODE_BREAK_HEBREW_LETTER || bt == G_UNICODE_BREAK_NUMERIC ||
726 bt == G_UNICODE_BREAK_QUOTATION)) {
727 break;
728 }
729 }
730 if (c != tb->text) {
731 while ((n = g_utf8_prev_char(c))) {
732 gunichar uc = g_utf8_get_char(n);
733 GUnicodeBreakType bt = g_unichar_break_type(uc);
734 if (!(bt == G_UNICODE_BREAK_ALPHABETIC ||
735 bt == G_UNICODE_BREAK_HEBREW_LETTER ||
736 bt == G_UNICODE_BREAK_NUMERIC || bt == G_UNICODE_BREAK_QUOTATION)) {
737 break;
738 }
739 c = n;
740 if (n == tb->text) {
741 break;
742 }
743 }
744 }
745 int index = g_utf8_pointer_to_offset(tb->text, c);
746 textbox_cursor(tb, index);
747}
748
749// end of line
751 if (tb->text == NULL) {
752 tb->cursor = 0;
754 return;
755 }
756 tb->cursor = (int)g_utf8_strlen(tb->text, -1);
758 // Stop blink!
759 tb->blink = 2;
760}
761
762// insert text
763void textbox_insert(textbox *tb, const int char_pos, const char *str,
764 const int slen) {
765 if (tb == NULL) {
766 return;
767 }
768 char *c = g_utf8_offset_to_pointer(tb->text, char_pos);
769 int pos = c - tb->text;
770 int len = (int)strlen(tb->text);
771 pos = MAX(0, MIN(len, pos));
772 // expand buffer
773 tb->text = g_realloc(tb->text, len + slen + 1);
774 // move everything after cursor upward
775 char *at = tb->text + pos;
776 memmove(at + slen, at, len - pos + 1);
777 // insert new str
778 memmove(at, str, slen);
779
780 // Set modified, lay out need te be redrawn
781 // Stop blink!
782 tb->blink = 2;
783 tb->changed = TRUE;
784}
785
786// remove text
787void textbox_delete(textbox *tb, int pos, int dlen) {
788 if (tb == NULL) {
789 return;
790 }
791 int len = g_utf8_strlen(tb->text, -1);
792 if (len == pos) {
793 return;
794 }
795 pos = MAX(0, MIN(len, pos));
796 if ((pos + dlen) > len) {
797 dlen = len - pos;
798 }
799 // move everything after pos+dlen down
800 char *start = g_utf8_offset_to_pointer(tb->text, pos);
801 char *end = g_utf8_offset_to_pointer(tb->text, pos + dlen);
802 // Move remainder + closing \0
803 memmove(start, end, (tb->text + strlen(tb->text)) - end + 1);
804 if (tb->cursor >= pos && tb->cursor < (pos + dlen)) {
805 tb->cursor = pos;
806 } else if (tb->cursor >= (pos + dlen)) {
807 tb->cursor -= dlen;
808 }
809 // Set modified, lay out need te be redrawn
810 // Stop blink!
811 tb->blink = 2;
812 tb->changed = TRUE;
813}
814
820static void textbox_cursor_del(textbox *tb) {
821 if (tb == NULL || tb->text == NULL) {
822 return;
823 }
824 textbox_delete(tb, tb->cursor, 1);
825}
826
833 if (tb && tb->cursor > 0) {
836 }
837}
838
845 // Need to have more then 2 characters in front of the cursor.
846 if (tb == NULL || tb->cursor < 2) {
847 return;
848 }
849 // Find pointer to cursor.
850 gchar *cursor_ptr = g_utf8_offset_to_pointer(tb->text, tb->cursor);
851 if (cursor_ptr == NULL) {
852 // We should never reach this.
853 g_warning("Invalid cursor index detected.");
854 return;
855 }
856 // Find previous character pointer. (1)
857 gchar *second_char_ptr = g_utf8_find_prev_char(tb->text, cursor_ptr);
858 if (second_char_ptr == NULL) {
859 // Failed to move cursor one back, so we cannot swap.
860 return;
861 }
862 // Find previous previous character pointer. (2)
863 gchar *first_char_ptr = g_utf8_find_prev_char(tb->text, second_char_ptr);
864 if (first_char_ptr == NULL) {
865 // Failed to move cursor back 2 characters, so we cannot swap.
866 return;
867 }
868 // Calculate size of each character
869 size_t first_char_l = second_char_ptr - first_char_ptr;
870 size_t second_char_l = cursor_ptr - second_char_ptr;
871 // Create a temp buffer so we can swap.
872 gchar temp[second_char_l + first_char_l];
873 // Copy char 2 into first place.
874 memcpy(temp, second_char_ptr, second_char_l);
875 // Copy char 1 into 2nd place.
876 memcpy(temp + second_char_l, first_char_ptr, first_char_l);
877 // Copy new order back into original string.
878 memcpy(first_char_ptr, temp, second_char_l + first_char_l);
879 // Set modified, lay out need te be redrawn
880 // Stop blink!
881 tb->blink = 2;
882 tb->changed = TRUE;
883}
885 if (tb && tb->cursor > 0) {
886 int cursor = tb->cursor;
888 if (cursor > tb->cursor) {
889 textbox_delete(tb, tb->cursor, cursor - tb->cursor);
890 }
891 }
892}
894 if (tb && tb->cursor >= 0) {
895 int length = g_utf8_strlen(tb->text, -1) - tb->cursor;
896 if (length >= 0) {
897 textbox_delete(tb, tb->cursor, length);
898 }
899 }
900}
902 if (tb && tb->cursor >= 0) {
903 int length = tb->cursor;
904 textbox_delete(tb, 0, length);
905 }
906}
908 if (tb && tb->cursor >= 0) {
909 int cursor = tb->cursor;
911 if (cursor < tb->cursor) {
912 textbox_delete(tb, cursor, tb->cursor - cursor);
913 }
914 }
915}
916
917// handle a keypress in edit mode
918// 2 = nav
919// 0 = unhandled
920// 1 = handled
921// -1 = handled and return pressed (finished)
923 if (tb == NULL) {
924 return 0;
925 }
926 if (!(tb->flags & TB_EDITABLE)) {
927 return 0;
928 }
929
930 switch (action) {
931 // Left or Ctrl-b
932 case MOVE_CHAR_BACK:
933 return (textbox_cursor_dec(tb) == TRUE) ? 2 : 0;
934 // Right or Ctrl-F
936 return (textbox_cursor_inc(tb) == TRUE) ? 2 : 0;
937 // Ctrl-U: Kill from the beginning to the end of the line.
938 case CLEAR_LINE:
939 textbox_text(tb, "");
940 return 1;
941 // Ctrl-A
942 case MOVE_FRONT:
943 textbox_cursor(tb, 0);
944 return 2;
945 // Ctrl-E
946 case MOVE_END:
948 return 2;
949 // Ctrl-Alt-h
950 case REMOVE_WORD_BACK:
952 return 1;
953 // Ctrl-Alt-d
956 return 1;
957 case REMOVE_TO_EOL:
959 return 1;
960 case REMOVE_TO_SOL:
962 return 1;
963 // Delete or Ctrl-D
966 return 1;
967 // Alt-B, Ctrl-Left
968 case MOVE_WORD_BACK:
970 return 2;
971 // Alt-F, Ctrl-Right
974 return 2;
975 // BackSpace, Shift-BackSpace, Ctrl-h
976 case REMOVE_CHAR_BACK:
978 return 1;
979 case TRANSPOSE_CHARS:
981 return 1;
982 default:
983 g_return_val_if_reached(0);
984 }
985}
986
987gboolean textbox_append_text(textbox *tb, const char *pad, const int pad_len) {
988 if (tb == NULL) {
989 return FALSE;
990 }
991 if (!(tb->flags & TB_EDITABLE)) {
992 return FALSE;
993 }
994
995 // Filter When alt/ctrl is pressed do not accept the character.
996
997 gboolean used_something = FALSE;
998 const gchar *w, *n, *e;
999 for (w = pad, n = g_utf8_next_char(w), e = w + pad_len; w < e;
1000 w = n, n = g_utf8_next_char(n)) {
1001 gunichar c = g_utf8_get_char(w);
1002 if (g_unichar_isspace(c)) {
1004 textbox_insert(tb, tb->cursor, " ", 1);
1005 textbox_cursor(tb, tb->cursor + 1);
1006 used_something = TRUE;
1007 } else if (g_unichar_iscntrl(c)) {
1008 /* skip control characters. */
1009 g_info("Got an invalid character: %08X", c);
1010 } else {
1012 textbox_insert(tb, tb->cursor, w, n - w);
1013 textbox_cursor(tb, tb->cursor + 1);
1014 used_something = TRUE;
1015 }
1016 }
1017 return used_something;
1018}
1019
1020static void tbfc_entry_free(TBFontConfig *tbfc) {
1021 pango_font_metrics_unref(tbfc->metrics);
1022 if (tbfc->pfd) {
1023 pango_font_description_free(tbfc->pfd);
1024 }
1025 g_free(tbfc);
1026}
1027void textbox_setup(void) {
1028 tbfc_cache = g_hash_table_new_full(g_str_hash, g_str_equal, NULL,
1029 (GDestroyNotify)tbfc_entry_free);
1030}
1031
1033const char *default_font_name = "default";
1034void textbox_set_pango_context(const char *font, PangoContext *p) {
1035 g_assert(p_metrics == NULL);
1036 p_context = g_object_ref(p);
1037 p_metrics = pango_context_get_metrics(p_context, NULL, NULL);
1038 TBFontConfig *tbfc = g_malloc0(sizeof(TBFontConfig));
1039 tbfc->metrics = p_metrics;
1040
1041 PangoLayout *layout = pango_layout_new(p_context);
1042 pango_layout_set_text(layout, "aAjb", -1);
1043 PangoRectangle rect;
1044 pango_layout_get_pixel_extents(layout, NULL, &rect);
1045 tbfc->height = rect.y + rect.height;
1046 if (tbfc->metrics) {
1047 int h = pango_font_metrics_get_height(tbfc->metrics) / PANGO_SCALE;
1048 if (h > 0) {
1049 tbfc->height = h;
1050 }
1051 }
1052 g_object_unref(layout);
1053 tbfc_default = tbfc;
1054
1055 g_hash_table_insert(tbfc_cache, (gpointer *)(font ? font : default_font_name),
1056 tbfc);
1057}
1058
1060 g_hash_table_destroy(tbfc_cache);
1061 if (p_context) {
1062 g_object_unref(p_context);
1063 p_context = NULL;
1064 }
1065}
1066
1068 textbox *tb = (textbox *)wid;
1069 if (tb->flags & TB_AUTOWIDTH) {
1071 }
1072 return tb->widget.w;
1073}
1074
1076 textbox *tb = (textbox *)wid;
1077 if (tb->flags & TB_AUTOHEIGHT) {
1079 tb, pango_layout_get_line_count(tb->layout));
1080 }
1081 return tb->widget.h;
1082}
1087
1089 PangoRectangle rect;
1090 pango_layout_get_pixel_extents(tb->layout, NULL, &rect);
1091 return rect.height + rect.y;
1092}
1093
1095 PangoRectangle rect;
1096 pango_layout_get_pixel_extents(tb->layout, NULL, &rect);
1097 return rect.width + rect.x;
1098}
1099
1102
1104static double char_width = -1;
1106 if (char_width < 0) {
1107 int width = pango_font_metrics_get_approximate_char_width(p_metrics);
1108 char_width = (width) / (double)PANGO_SCALE;
1109 }
1110 return char_width;
1111}
1112
1114static double ch_width = -1;
1116 if (ch_width < 0) {
1117 int width = pango_font_metrics_get_approximate_digit_width(p_metrics);
1118 ch_width = (width) / (double)PANGO_SCALE;
1119 }
1120 return ch_width;
1121}
1122
1124 int height = tb->tbfc->height;
1125 return (eh * height) + widget_padding_get_padding_height(WIDGET(tb));
1126}
1127int textbox_get_desired_width(widget *wid, G_GNUC_UNUSED const int height) {
1128 if (wid == NULL) {
1129 return 0;
1130 }
1131 textbox *tb = (textbox *)wid;
1132 if (wid->expand && tb->flags & TB_AUTOWIDTH) {
1134 }
1135 RofiDistance w = rofi_theme_get_distance(WIDGET(tb), "width", 0);
1137 if (wi > 0) {
1138 return wi;
1139 }
1140 int padding = widget_padding_get_left(WIDGET(tb));
1141 padding += widget_padding_get_right(WIDGET(tb));
1142 int old_width = pango_layout_get_width(tb->layout);
1143 pango_layout_set_width(tb->layout, -1);
1144 int width = textbox_get_font_width(tb);
1145 // Restore.
1146 pango_layout_set_width(tb->layout, old_width);
1147 return width + padding;
1148}
1149
1150void textbox_set_ellipsize(textbox *tb, PangoEllipsizeMode mode) {
1151 if (tb) {
1152 tb->emode = mode;
1153 if ((tb->flags & TB_WRAP) != TB_WRAP) {
1154 // Store the mode.
1155 pango_layout_set_ellipsize(tb->layout, tb->emode);
1157 }
1158 }
1159}
1161 if (tb == NULL) {
1162 return 0;
1163 }
1164 return tb->cursor_x_pos;
1165}
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:176
@ 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
@ TRANSPOSE_CHARS
Definition keyb.h:92
@ 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:177
@ MOUSE_DCLICK_UP
Definition keyb.h:180
@ MOUSE_CLICK_UP
Definition keyb.h:178
@ MOUSE_DCLICK_DOWN
Definition keyb.h:179
#define TICK_N(a)
Definition timings.h:69
int textbox_get_height(const textbox *tb)
Definition textbox.c:1083
void textbox_insert(textbox *tb, const int char_pos, const char *str, const int slen)
Definition textbox.c:763
void textbox_font(textbox *tb, TextBoxFontType tbft)
Definition textbox.c:308
TextboxFlags
Definition textbox.h:92
void textbox_delete(textbox *tb, int pos, int dlen)
Definition textbox.c:787
int textbox_keybinding(textbox *tb, KeyBindingAction action)
Definition textbox.c:922
TextBoxFontType
Definition textbox.h:103
void textbox_cleanup(void)
Definition textbox.c:1059
double textbox_get_estimated_char_width(void)
Definition textbox.c:1105
int textbox_get_font_height(const textbox *tb)
Definition textbox.c:1088
void textbox_set_pango_attributes(textbox *tb, PangoAttrList *list)
Definition textbox.c:392
void textbox_set_ellipsize(textbox *tb, PangoEllipsizeMode mode)
Definition textbox.c:1150
int textbox_get_desired_width(widget *wid, G_GNUC_UNUSED const int height)
Definition textbox.c:1127
void textbox_setup(void)
Definition textbox.c:1027
double textbox_get_estimated_char_height(void)
Definition textbox.c:1101
const char * textbox_get_visible_text(const textbox *tb)
Definition textbox.c:380
int textbox_get_cursor(const textbox *tb)
Definition textbox.c:405
int textbox_get_estimated_height(const textbox *tb, int eh)
Definition textbox.c:1123
void textbox_cursor(textbox *tb, int pos)
Definition textbox.c:648
void textbox_set_pango_context(const char *font, PangoContext *p)
Definition textbox.c:1034
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:1094
void textbox_cursor_end(textbox *tb)
Definition textbox.c:750
gboolean textbox_append_text(textbox *tb, const char *pad, const int pad_len)
Definition textbox.c:987
void textbox_moveresize(textbox *tb, int x, int y, int w, int h)
Definition textbox.c:447
PangoAttrList * textbox_get_pango_attributes(textbox *tb)
Definition textbox.c:386
void textbox_text(textbox *tb, const char *text)
Definition textbox.c:412
double textbox_get_estimated_ch(void)
Definition textbox.c:1115
int textbox_get_cursor_x_pos(const textbox *tb)
Definition textbox.c:1160
char * textbox_get_text(const textbox *tb)
Definition textbox.c:399
@ 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:2193
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
const char * state
int(*) get_desired_height(struct _widget *, const int width)
int(*) get_height(struct _widget *)
void(*) draw(struct _widget *widget, cairo_t *draw)
widget_trigger_action_cb trigger_action
void(*) free(struct _widget *widget)
int(*) get_desired_width(struct _widget *, const int height)
int(*) get_width(struct _widget *)
void(*) resize(struct _widget *, short, short)
gboolean expand
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:57
static PangoContext * p_context
Definition textbox.c:52
static int textbox_get_width(widget *)
Definition textbox.c:1067
static void textbox_cursor_dec_word(textbox *tb)
Definition textbox.c:717
static void textbox_cursor_inc_word(textbox *tb)
Definition textbox.c:686
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:1033
static double ch_width
Definition textbox.c:1114
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:666
static void textbox_free(widget *)
Definition textbox.c:488
static void textbox_initialize_font(textbox *tb)
Definition textbox.c:127
static void textbox_transpose_chars(textbox *tb)
Definition textbox.c:844
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:901
static void textbox_tab_stops(textbox *tb)
Definition textbox.c:179
static void textbox_cursor_bkspc(textbox *tb)
Definition textbox.c:832
static void textbox_cursor_bkspc_word(textbox *tb)
Definition textbox.c:884
static void textbox_draw(widget *, cairo_t *)
Definition textbox.c:507
static void textbox_cursor_del_word(textbox *tb)
Definition textbox.c:907
static PangoFontMetrics * p_metrics
Definition textbox.c:54
static void textbox_cursor_del(textbox *tb)
Definition textbox.c:820
static double char_width
Definition textbox.c:1104
static void __textbox_update_pango_text(textbox *tb)
Definition textbox.c:340
static int _textbox_get_height(widget *)
Definition textbox.c:1075
static void textbox_cursor_del_eol(textbox *tb)
Definition textbox.c:893
static int textbox_cursor_dec(textbox *tb)
Definition textbox.c:679
static void tbfc_entry_free(TBFontConfig *tbfc)
Definition textbox.c:1020
static GHashTable * tbfc_cache
Definition textbox.c:60
RofiHighlightColorStyle rofi_theme_get_highlight(widget *wid, const char *property, RofiHighlightColorStyle th)
Definition theme.c:1308
int distance_get_pixel(RofiDistance d, RofiOrientation ori)
Definition theme.c:1404
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
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