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