rofi  1.7.3
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-2021 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 
44 #define DOT_OFFSET 15
45 
46 static void textbox_draw(widget *, cairo_t *);
47 static void textbox_free(widget *);
48 static int textbox_get_width(widget *);
49 static int _textbox_get_height(widget *);
50 static void __textbox_update_pango_text(textbox *tb);
51 
53 static PangoContext *p_context = NULL;
55 static PangoFontMetrics *p_metrics = NULL;
56 
58 static TBFontConfig *tbfc_default = NULL;
59 
61 static GHashTable *tbfc_cache = NULL;
62 
63 static gboolean textbox_blink(gpointer data) {
64  textbox *tb = (textbox *)data;
65  if (tb->blink < 2) {
66  tb->blink = !tb->blink;
69  } else {
70  tb->blink--;
71  }
72  return TRUE;
73 }
74 
75 static void textbox_resize(widget *wid, short w, short h) {
76  textbox *tb = (textbox *)wid;
77  textbox_moveresize(tb, tb->widget.x, tb->widget.y, w, h);
78 }
79 static int textbox_get_desired_height(widget *wid, const int width) {
80  textbox *tb = (textbox *)wid;
81  unsigned int offset = ((tb->flags & TB_INDICATOR) ? DOT_OFFSET : 0);
82  if ((tb->flags & TB_AUTOHEIGHT) == 0) {
83  return tb->widget.h;
84  }
85  if (tb->changed) {
87  }
88  int old_width = pango_layout_get_width(tb->layout);
89  pango_layout_set_width(
90  tb->layout,
91  PANGO_SCALE *
92  (width - widget_padding_get_padding_width(WIDGET(tb)) - offset));
93 
94  int height =
95  textbox_get_estimated_height(tb, pango_layout_get_line_count(tb->layout));
96  pango_layout_set_width(tb->layout, old_width);
97  return height;
98 }
99 
102  MouseBindingMouseDefaultAction action, gint x,
103  gint y, G_GNUC_UNUSED void *user_data) {
104  textbox *tb = (textbox *)wid;
105  switch (action) {
106  case MOUSE_CLICK_DOWN: {
107  gint i;
108  // subtract padding on left.
109  x -= widget_padding_get_left(wid);
110  gint max = textbox_get_font_width(tb);
111  // Right of text, move to end.
112  if (x >= max) {
113  textbox_cursor_end(tb);
114  } else if (x > 0) {
115  // If in range, get index.
116  pango_layout_xy_to_index(tb->layout, x * PANGO_SCALE, y * PANGO_SCALE, &i,
117  NULL);
118  textbox_cursor(tb, i);
119  }
121  }
122  case MOUSE_CLICK_UP:
123  case MOUSE_DCLICK_DOWN:
124  case MOUSE_DCLICK_UP:
125  break;
126  }
128 }
129 
131  tb->tbfc = tbfc_default;
132  const char *font = rofi_theme_get_string(WIDGET(tb), "font", NULL);
133  if (font) {
134  TBFontConfig *tbfc = g_hash_table_lookup(tbfc_cache, font);
135  if (tbfc == NULL) {
136  tbfc = g_malloc0(sizeof(TBFontConfig));
137  tbfc->pfd = pango_font_description_from_string(font);
138  if (helper_validate_font(tbfc->pfd, font)) {
139  tbfc->metrics = pango_context_get_metrics(p_context, tbfc->pfd, NULL);
140 
141  PangoLayout *layout = pango_layout_new(p_context);
142  pango_layout_set_font_description(layout, tbfc->pfd);
143  pango_layout_set_text(layout, "aAjb", -1);
144  PangoRectangle rect;
145  pango_layout_get_pixel_extents(layout, NULL, &rect);
146  tbfc->height = rect.y + rect.height;
147  g_object_unref(layout);
148 
149  // Cast away consts. (*yuck*) because table_insert does not know it is
150  // const.
151  g_hash_table_insert(tbfc_cache, (char *)font, tbfc);
152  } else {
153  pango_font_description_free(tbfc->pfd);
154  g_free(tbfc);
155  tbfc = NULL;
156  }
157  }
158  if (tbfc) {
159  // Update for used font.
160  pango_layout_set_font_description(tb->layout, tbfc->pfd);
161  tb->tbfc = tbfc;
162  }
163  }
164 }
165 
166 static void textbox_tab_stops(textbox *tb) {
167  GList *dists = rofi_theme_get_list_distance(WIDGET(tb), "tab-stops");
168 
169  if (dists != NULL) {
170  PangoTabArray *tabs = pango_tab_array_new(g_list_length(dists), TRUE);
171 
172  int i = 0, ppx = 0;
173  for (const GList *iter = g_list_first(dists); iter != NULL;
174  iter = g_list_next(iter), i++) {
175  const RofiDistance *dist = iter->data;
176 
178  if (px <= ppx) {
179  continue;
180  }
181  pango_tab_array_set_tab(tabs, i, PANGO_TAB_LEFT, px);
182  ppx = px;
183  }
184  pango_layout_set_tabs(tb->layout, tabs);
185 
186  pango_tab_array_free(tabs);
187  g_list_free_full(dists, g_free);
188  }
189 }
190 
191 textbox *textbox_create(widget *parent, WidgetType type, const char *name,
193  const char *text, double xalign, double yalign) {
194  textbox *tb = g_slice_new0(textbox);
195 
196  widget_init(WIDGET(tb), parent, type, name);
197 
198  tb->widget.draw = textbox_draw;
199  tb->widget.free = textbox_free;
205  tb->flags = flags;
206  tb->emode = PANGO_ELLIPSIZE_END;
207 
208  tb->changed = FALSE;
209 
210  tb->layout = pango_layout_new(p_context);
211  textbox_font(tb, tbft);
212 
214  textbox_tab_stops(tb);
215 
216  if ((tb->flags & TB_WRAP) == TB_WRAP) {
217  pango_layout_set_wrap(tb->layout, PANGO_WRAP_WORD_CHAR);
218  }
219 
220  // Allow overriding of markup.
221  if (rofi_theme_get_boolean(WIDGET(tb), "markup",
222  (tb->flags & TB_MARKUP) == TB_MARKUP)) {
223  tb->flags |= TB_MARKUP;
224  } else {
225  tb->flags &= (~TB_MARKUP);
226  }
227 
228  const char *txt = rofi_theme_get_string(WIDGET(tb), "str", text);
229  if (txt == NULL || (*txt) == '\0') {
230  txt = rofi_theme_get_string(WIDGET(tb), "content", text);
231  }
232  const char *placeholder =
233  rofi_theme_get_string(WIDGET(tb), "placeholder", NULL);
234  if (placeholder) {
235  tb->placeholder = placeholder;
236  }
237  textbox_text(tb, txt ? txt : "");
238  textbox_cursor_end(tb);
239 
240  // auto height/width modes get handled here
241  textbox_moveresize(tb, tb->widget.x, tb->widget.y, tb->widget.w,
242  tb->widget.h);
243 
244  tb->blink_timeout = 0;
245  tb->blink = 1;
246  if ((tb->flags & TB_EDITABLE) == TB_EDITABLE) {
247  if (rofi_theme_get_boolean(WIDGET(tb), "blink", TRUE)) {
248  tb->blink_timeout = g_timeout_add(1200, textbox_blink, tb);
249  }
251  }
252 
253  tb->yalign = rofi_theme_get_double(WIDGET(tb), "vertical-align", yalign);
254  tb->yalign = MAX(0, MIN(1.0, tb->yalign));
255  tb->xalign = rofi_theme_get_double(WIDGET(tb), "horizontal-align", xalign);
256  tb->xalign = MAX(0, MIN(1.0, tb->xalign));
257 
258  return tb;
259 }
260 
264 const char *const theme_prop_names[][3] = {
266  {"normal.normal", "selected.normal", "alternate.normal"},
268  {"normal.urgent", "selected.urgent", "alternate.urgent"},
270  {"normal.active", "selected.active", "alternate.active"},
271 };
272 
274  TextBoxFontType t = tbft & STATE_MASK;
275  if (tb == NULL) {
276  return;
277  }
278  // ACTIVE has priority over URGENT if both set.
279  if (t == (URGENT | ACTIVE)) {
280  t = ACTIVE;
281  }
282  switch ((tbft & FMOD_MASK)) {
283  case HIGHLIGHT:
285  break;
286  case ALT:
288  break;
289  default:
291  break;
292  }
293  if (tb->tbft != tbft || tb->widget.state == NULL) {
295  }
296  tb->tbft = tbft;
297 }
298 
306  pango_layout_set_attributes(tb->layout, NULL);
307  if (tb->placeholder && (tb->text == NULL || tb->text[0] == 0)) {
308  tb->show_placeholder = TRUE;
309  pango_layout_set_text(tb->layout, tb->placeholder, -1);
310  return;
311  }
312  tb->show_placeholder = FALSE;
313  if ((tb->flags & TB_PASSWORD) == TB_PASSWORD) {
314  size_t l = g_utf8_strlen(tb->text, -1);
315  char string[l + 1];
316  memset(string, '*', l);
317  string[l] = '\0';
318  pango_layout_set_text(tb->layout, string, l);
319  } else if (tb->flags & TB_MARKUP || tb->tbft & MARKUP) {
320  pango_layout_set_markup(tb->layout, tb->text, -1);
321  } else {
322  pango_layout_set_text(tb->layout, tb->text, -1);
323  }
324 }
325 const char *textbox_get_visible_text(const textbox *tb) {
326  if (tb == NULL) {
327  return NULL;
328  }
329  return pango_layout_get_text(tb->layout);
330 }
332  if (tb == NULL) {
333  return NULL;
334  }
335  return pango_layout_get_attributes(tb->layout);
336 }
337 void textbox_set_pango_attributes(textbox *tb, PangoAttrList *list) {
338  if (tb == NULL) {
339  return;
340  }
341  pango_layout_set_attributes(tb->layout, list);
342 }
343 
344 // set the default text to display
345 void textbox_text(textbox *tb, const char *text) {
346  if (tb == NULL) {
347  return;
348  }
349  g_free(tb->text);
350  const gchar *last_pointer = NULL;
351 
352  if (text == NULL) {
353  tb->text = g_strdup("Invalid string.");
354  } else {
355  if (g_utf8_validate(text, -1, &last_pointer)) {
356  tb->text = g_strdup(text);
357  } else {
358  if (last_pointer != NULL) {
359  // Copy string up to invalid character.
360  tb->text = g_strndup(text, (last_pointer - text));
361  } else {
362  tb->text = g_strdup("Invalid UTF-8 string.");
363  }
364  }
365  }
367  if (tb->flags & TB_AUTOWIDTH) {
368  textbox_moveresize(tb, tb->widget.x, tb->widget.y, tb->widget.w,
369  tb->widget.h);
370  if (WIDGET(tb)->parent) {
371  widget_update(WIDGET(tb)->parent);
372  }
373  }
374 
375  tb->cursor = MAX(0, MIN((int)g_utf8_strlen(tb->text, -1), tb->cursor));
377 }
378 
379 // within the parent handled auto width/height modes
380 void textbox_moveresize(textbox *tb, int x, int y, int w, int h) {
381  unsigned int offset = ((tb->flags & TB_INDICATOR) ? DOT_OFFSET : 0);
382  if (tb->flags & TB_AUTOWIDTH) {
383  pango_layout_set_width(tb->layout, -1);
384  w = textbox_get_font_width(tb) +
386  } else {
387  // set ellipsize
388  if ((tb->flags & TB_EDITABLE) == TB_EDITABLE) {
389  pango_layout_set_ellipsize(tb->layout, PANGO_ELLIPSIZE_MIDDLE);
390  } else if ((tb->flags & TB_WRAP) != TB_WRAP) {
391  pango_layout_set_ellipsize(tb->layout, tb->emode);
392  } else {
393  pango_layout_set_ellipsize(tb->layout, PANGO_ELLIPSIZE_NONE);
394  }
395  }
396 
397  if (tb->flags & TB_AUTOHEIGHT) {
398  // Width determines height!
399  int tw = MAX(1, w);
400  pango_layout_set_width(
401  tb->layout,
402  PANGO_SCALE *
403  (tw - widget_padding_get_padding_width(WIDGET(tb)) - offset));
404  int hd = textbox_get_height(tb);
405  h = MAX(hd, h);
406  }
407 
408  if (x != tb->widget.x || y != tb->widget.y || w != tb->widget.w ||
409  h != tb->widget.h) {
410  tb->widget.x = x;
411  tb->widget.y = y;
412  tb->widget.h = MAX(1, h);
413  tb->widget.w = MAX(1, w);
414  }
415 
416  // We always want to update this
417  pango_layout_set_width(
418  tb->layout,
419  PANGO_SCALE * (tb->widget.w -
422 }
423 
424 // will also unmap the window if still displayed
425 static void textbox_free(widget *wid) {
426  if (wid == NULL) {
427  return;
428  }
429  textbox *tb = (textbox *)wid;
430  if (tb->blink_timeout > 0) {
431  g_source_remove(tb->blink_timeout);
432  tb->blink_timeout = 0;
433  }
434  g_free(tb->text);
435 
436  if (tb->layout != NULL) {
437  g_object_unref(tb->layout);
438  }
439 
440  g_slice_free(textbox, tb);
441 }
442 
443 static void textbox_draw(widget *wid, cairo_t *draw) {
444  if (wid == NULL) {
445  return;
446  }
447  textbox *tb = (textbox *)wid;
448  unsigned int dot_offset = ((tb->flags & TB_INDICATOR) ? DOT_OFFSET : 0);
449 
450  if (tb->changed) {
452  }
453 
454  // Skip the side MARGIN on the X axis.
455  int x = widget_padding_get_left(WIDGET(tb));
456  int top = widget_padding_get_top(WIDGET(tb));
457  int y = (pango_font_metrics_get_ascent(tb->tbfc->metrics) -
458  pango_layout_get_baseline(tb->layout)) /
459  PANGO_SCALE;
460  int line_width = 0, line_height = 0;
461  // Get actual width.
462  pango_layout_get_pixel_size(tb->layout, &line_width, &line_height);
463 
464  if (tb->yalign > 0.001) {
465  int bottom = widget_padding_get_bottom(WIDGET(tb));
466  top = (tb->widget.h - bottom - line_height - top) * tb->yalign + top;
467  }
468  y += top;
469 
470  x += dot_offset;
471 
472  if (tb->xalign > 0.001) {
473  int rem =
474  MAX(0, tb->widget.w - widget_padding_get_padding_width(WIDGET(tb)) -
475  line_width);
476  x = tb->xalign * rem + widget_padding_get_left(WIDGET(tb));
477  }
478  // TODO check if this is still needed after flatning.
479  cairo_set_operator(draw, CAIRO_OPERATOR_OVER);
480  cairo_set_source_rgb(draw, 0.0, 0.0, 0.0);
481  rofi_theme_get_color(WIDGET(tb), "text-color", draw);
482 
483  if (tb->show_placeholder) {
484  rofi_theme_get_color(WIDGET(tb), "placeholder-color", draw);
485  }
486  // Set ARGB
487  // We need to set over, otherwise subpixel hinting wont work.
488  cairo_move_to(draw, x, top);
489  cairo_save(draw);
490  cairo_reset_clip(draw);
491  pango_cairo_show_layout(draw, tb->layout);
492  cairo_restore(draw);
493 
494  // draw the cursor
495  rofi_theme_get_color(WIDGET(tb), "text-color", draw);
496  if (tb->flags & TB_EDITABLE && tb->blink) {
497  // We want to place the cursor based on the text shown.
498  const char *text = pango_layout_get_text(tb->layout);
499  // Clamp the position, should not be needed, but we are paranoid.
500  int cursor_offset = MIN(tb->cursor, g_utf8_strlen(text, -1));
501  PangoRectangle pos;
502  // convert to byte location.
503  char *offset = g_utf8_offset_to_pointer(text, cursor_offset);
504  pango_layout_get_cursor_pos(tb->layout, offset - text, &pos, NULL);
505  int cursor_x = pos.x / PANGO_SCALE;
506  int cursor_y = pos.y / PANGO_SCALE;
507  int cursor_height = pos.height / PANGO_SCALE;
508  int cursor_width = 2;
509  cairo_rectangle(draw, x + cursor_x, y + cursor_y, cursor_width,
510  cursor_height);
511  cairo_fill(draw);
512  }
513 
514  if ((tb->flags & TB_INDICATOR) == TB_INDICATOR && (tb->tbft & (SELECTED))) {
515  cairo_arc(draw, DOT_OFFSET / 2.0, tb->widget.h / 2.0, 2.0, 0, 2.0 * M_PI);
516  cairo_fill(draw);
517  }
518 }
519 
520 // cursor handling for edit mode
521 void textbox_cursor(textbox *tb, int pos) {
522  if (tb == NULL) {
523  return;
524  }
525  int length = (tb->text == NULL) ? 0 : g_utf8_strlen(tb->text, -1);
526  tb->cursor = MAX(0, MIN(length, pos));
527  // Stop blink!
528  tb->blink = 3;
530 }
531 
539 static int textbox_cursor_inc(textbox *tb) {
540  int old = tb->cursor;
541  textbox_cursor(tb, tb->cursor + 1);
542  return old != tb->cursor;
543 }
544 
552 static int textbox_cursor_dec(textbox *tb) {
553  int old = tb->cursor;
554  textbox_cursor(tb, tb->cursor - 1);
555  return old != tb->cursor;
556 }
557 
558 // Move word right
560  if (tb->text == NULL) {
561  return;
562  }
563  // Find word boundaries, with pango_Break?
564  gchar *c = g_utf8_offset_to_pointer(tb->text, tb->cursor);
565  while ((c = g_utf8_next_char(c))) {
566  gunichar uc = g_utf8_get_char(c);
567  GUnicodeBreakType bt = g_unichar_break_type(uc);
568  if ((bt == G_UNICODE_BREAK_ALPHABETIC ||
569  bt == G_UNICODE_BREAK_HEBREW_LETTER || bt == G_UNICODE_BREAK_NUMERIC ||
570  bt == G_UNICODE_BREAK_QUOTATION)) {
571  break;
572  }
573  }
574  if (c == NULL || *c == '\0') {
575  return;
576  }
577  while ((c = g_utf8_next_char(c))) {
578  gunichar uc = g_utf8_get_char(c);
579  GUnicodeBreakType bt = g_unichar_break_type(uc);
580  if (!(bt == G_UNICODE_BREAK_ALPHABETIC ||
581  bt == G_UNICODE_BREAK_HEBREW_LETTER ||
582  bt == G_UNICODE_BREAK_NUMERIC || bt == G_UNICODE_BREAK_QUOTATION)) {
583  break;
584  }
585  }
586  int index = g_utf8_pointer_to_offset(tb->text, c);
587  textbox_cursor(tb, index);
588 }
589 // move word left
591  // Find word boundaries, with pango_Break?
592  gchar *n;
593  gchar *c = g_utf8_offset_to_pointer(tb->text, tb->cursor);
594  while ((c = g_utf8_prev_char(c)) && c != tb->text) {
595  gunichar uc = g_utf8_get_char(c);
596  GUnicodeBreakType bt = g_unichar_break_type(uc);
597  if ((bt == G_UNICODE_BREAK_ALPHABETIC ||
598  bt == G_UNICODE_BREAK_HEBREW_LETTER || bt == G_UNICODE_BREAK_NUMERIC ||
599  bt == G_UNICODE_BREAK_QUOTATION)) {
600  break;
601  }
602  }
603  if (c != tb->text) {
604  while ((n = g_utf8_prev_char(c))) {
605  gunichar uc = g_utf8_get_char(n);
606  GUnicodeBreakType bt = g_unichar_break_type(uc);
607  if (!(bt == G_UNICODE_BREAK_ALPHABETIC ||
608  bt == G_UNICODE_BREAK_HEBREW_LETTER ||
609  bt == G_UNICODE_BREAK_NUMERIC || bt == G_UNICODE_BREAK_QUOTATION)) {
610  break;
611  }
612  c = n;
613  if (n == tb->text) {
614  break;
615  }
616  }
617  }
618  int index = g_utf8_pointer_to_offset(tb->text, c);
619  textbox_cursor(tb, index);
620 }
621 
622 // end of line
624  if (tb->text == NULL) {
625  tb->cursor = 0;
627  return;
628  }
629  tb->cursor = (int)g_utf8_strlen(tb->text, -1);
631  // Stop blink!
632  tb->blink = 2;
633 }
634 
635 // insert text
636 void textbox_insert(textbox *tb, const int char_pos, const char *str,
637  const int slen) {
638  if (tb == NULL) {
639  return;
640  }
641  char *c = g_utf8_offset_to_pointer(tb->text, char_pos);
642  int pos = c - tb->text;
643  int len = (int)strlen(tb->text);
644  pos = MAX(0, MIN(len, pos));
645  // expand buffer
646  tb->text = g_realloc(tb->text, len + slen + 1);
647  // move everything after cursor upward
648  char *at = tb->text + pos;
649  memmove(at + slen, at, len - pos + 1);
650  // insert new str
651  memmove(at, str, slen);
652 
653  // Set modified, lay out need te be redrawn
654  // Stop blink!
655  tb->blink = 2;
656  tb->changed = TRUE;
657 }
658 
659 // remove text
660 void textbox_delete(textbox *tb, int pos, int dlen) {
661  if (tb == NULL) {
662  return;
663  }
664  int len = g_utf8_strlen(tb->text, -1);
665  if (len == pos) {
666  return;
667  }
668  pos = MAX(0, MIN(len, pos));
669  if ((pos + dlen) > len) {
670  dlen = len - dlen;
671  }
672  // move everything after pos+dlen down
673  char *start = g_utf8_offset_to_pointer(tb->text, pos);
674  char *end = g_utf8_offset_to_pointer(tb->text, pos + dlen);
675  // Move remainder + closing \0
676  memmove(start, end, (tb->text + strlen(tb->text)) - end + 1);
677  if (tb->cursor >= pos && tb->cursor < (pos + dlen)) {
678  tb->cursor = pos;
679  } else if (tb->cursor >= (pos + dlen)) {
680  tb->cursor -= dlen;
681  }
682  // Set modified, lay out need te be redrawn
683  // Stop blink!
684  tb->blink = 2;
685  tb->changed = TRUE;
686 }
687 
693 static void textbox_cursor_del(textbox *tb) {
694  if (tb == NULL || tb->text == NULL) {
695  return;
696  }
697  textbox_delete(tb, tb->cursor, 1);
698 }
699 
705 static void textbox_cursor_bkspc(textbox *tb) {
706  if (tb && tb->cursor > 0) {
707  textbox_cursor_dec(tb);
708  textbox_cursor_del(tb);
709  }
710 }
712  if (tb && tb->cursor > 0) {
713  int cursor = tb->cursor;
715  if (cursor > tb->cursor) {
716  textbox_delete(tb, tb->cursor, cursor - tb->cursor);
717  }
718  }
719 }
720 static void textbox_cursor_del_eol(textbox *tb) {
721  if (tb && tb->cursor >= 0) {
722  int length = g_utf8_strlen(tb->text, -1) - tb->cursor;
723  if (length >= 0) {
724  textbox_delete(tb, tb->cursor, length);
725  }
726  }
727 }
728 static void textbox_cursor_del_sol(textbox *tb) {
729  if (tb && tb->cursor >= 0) {
730  int length = tb->cursor;
731  if (length >= 0) {
732  textbox_delete(tb, 0, length);
733  }
734  }
735 }
737  if (tb && tb->cursor >= 0) {
738  int cursor = tb->cursor;
740  if (cursor < tb->cursor) {
741  textbox_delete(tb, cursor, tb->cursor - cursor);
742  }
743  }
744 }
745 
746 // handle a keypress in edit mode
747 // 2 = nav
748 // 0 = unhandled
749 // 1 = handled
750 // -1 = handled and return pressed (finished)
752  if (tb == NULL) {
753  return 0;
754  }
755  if (!(tb->flags & TB_EDITABLE)) {
756  return 0;
757  }
758 
759  switch (action) {
760  // Left or Ctrl-b
761  case MOVE_CHAR_BACK:
762  return (textbox_cursor_dec(tb) == TRUE) ? 2 : 0;
763  // Right or Ctrl-F
764  case MOVE_CHAR_FORWARD:
765  return (textbox_cursor_inc(tb) == TRUE) ? 2 : 0;
766  // Ctrl-U: Kill from the beginning to the end of the line.
767  case CLEAR_LINE:
768  textbox_text(tb, "");
769  return 1;
770  // Ctrl-A
771  case MOVE_FRONT:
772  textbox_cursor(tb, 0);
773  return 2;
774  // Ctrl-E
775  case MOVE_END:
776  textbox_cursor_end(tb);
777  return 2;
778  // Ctrl-Alt-h
779  case REMOVE_WORD_BACK:
781  return 1;
782  // Ctrl-Alt-d
783  case REMOVE_WORD_FORWARD:
785  return 1;
786  case REMOVE_TO_EOL:
788  return 1;
789  case REMOVE_TO_SOL:
791  return 1;
792  // Delete or Ctrl-D
793  case REMOVE_CHAR_FORWARD:
794  textbox_cursor_del(tb);
795  return 1;
796  // Alt-B, Ctrl-Left
797  case MOVE_WORD_BACK:
799  return 2;
800  // Alt-F, Ctrl-Right
801  case MOVE_WORD_FORWARD:
803  return 2;
804  // BackSpace, Shift-BackSpace, Ctrl-h
805  case REMOVE_CHAR_BACK:
807  return 1;
808  default:
809  g_return_val_if_reached(0);
810  }
811 }
812 
813 gboolean textbox_append_text(textbox *tb, const char *pad, const int pad_len) {
814  if (tb == NULL) {
815  return FALSE;
816  }
817  if (!(tb->flags & TB_EDITABLE)) {
818  return FALSE;
819  }
820 
821  // Filter When alt/ctrl is pressed do not accept the character.
822 
823  gboolean used_something = FALSE;
824  const gchar *w, *n, *e;
825  for (w = pad, n = g_utf8_next_char(w), e = w + pad_len; w < e;
826  w = n, n = g_utf8_next_char(n)) {
827  if (g_unichar_iscntrl(g_utf8_get_char(w))) {
828  continue;
829  }
830  textbox_insert(tb, tb->cursor, w, n - w);
831  textbox_cursor(tb, tb->cursor + 1);
832  used_something = TRUE;
833  }
834  return used_something;
835 }
836 
837 static void tbfc_entry_free(TBFontConfig *tbfc) {
838  pango_font_metrics_unref(tbfc->metrics);
839  if (tbfc->pfd) {
840  pango_font_description_free(tbfc->pfd);
841  }
842  g_free(tbfc);
843 }
844 void textbox_setup(void) {
845  tbfc_cache = g_hash_table_new_full(g_str_hash, g_str_equal, NULL,
846  (GDestroyNotify)tbfc_entry_free);
847 }
848 
850 const char *default_font_name = "default";
851 void textbox_set_pango_context(const char *font, PangoContext *p) {
852  g_assert(p_metrics == NULL);
853  p_context = g_object_ref(p);
854  p_metrics = pango_context_get_metrics(p_context, NULL, NULL);
855  TBFontConfig *tbfc = g_malloc0(sizeof(TBFontConfig));
856  tbfc->metrics = p_metrics;
857 
858  PangoLayout *layout = pango_layout_new(p_context);
859  pango_layout_set_text(layout, "aAjb", -1);
860  PangoRectangle rect;
861  pango_layout_get_pixel_extents(layout, NULL, &rect);
862  tbfc->height = rect.y + rect.height;
863  g_object_unref(layout);
864  tbfc_default = tbfc;
865 
866  g_hash_table_insert(tbfc_cache, (gpointer *)(font ? font : default_font_name),
867  tbfc);
868 }
869 
870 void textbox_cleanup(void) {
871  g_hash_table_destroy(tbfc_cache);
872  if (p_context) {
873  g_object_unref(p_context);
874  p_context = NULL;
875  }
876 }
877 
879  textbox *tb = (textbox *)wid;
880  if (tb->flags & TB_AUTOWIDTH) {
881  unsigned int offset = (tb->flags & TB_INDICATOR) ? DOT_OFFSET : 0;
883  offset;
884  }
885  return tb->widget.w;
886 }
887 
889  textbox *tb = (textbox *)wid;
890  if (tb->flags & TB_AUTOHEIGHT) {
892  tb, pango_layout_get_line_count(tb->layout));
893  }
894  return tb->widget.h;
895 }
896 int textbox_get_height(const textbox *tb) {
897  return textbox_get_font_height(tb) +
899 }
900 
902  PangoRectangle rect;
903  pango_layout_get_pixel_extents(tb->layout, NULL, &rect);
904  return rect.height + rect.y;
905 }
906 
908  PangoRectangle rect;
909  pango_layout_get_pixel_extents(tb->layout, NULL, &rect);
910  return rect.width + rect.x;
911 }
912 
915 
917 static double char_width = -1;
919  if (char_width < 0) {
920  int width = pango_font_metrics_get_approximate_char_width(p_metrics);
921  char_width = (width) / (double)PANGO_SCALE;
922  }
923  return char_width;
924 }
925 
927 static double ch_width = -1;
929  if (ch_width < 0) {
930  int width = pango_font_metrics_get_approximate_digit_width(p_metrics);
931  ch_width = (width) / (double)PANGO_SCALE;
932  }
933  return ch_width;
934 }
935 
936 int textbox_get_estimated_height(const textbox *tb, int eh) {
937  int height = tb->tbfc->height;
938  return (eh * height) + widget_padding_get_padding_height(WIDGET(tb));
939 }
940 int textbox_get_desired_width(widget *wid, G_GNUC_UNUSED const int height) {
941  if (wid == NULL) {
942  return 0;
943  }
944  textbox *tb = (textbox *)wid;
945  unsigned int offset = ((tb->flags & TB_INDICATOR) ? DOT_OFFSET : 0);
946  if (wid->expand && tb->flags & TB_AUTOWIDTH) {
948  offset;
949  }
950  RofiDistance w = rofi_theme_get_distance(WIDGET(tb), "width", 0);
952  if (wi > 0) {
953  return wi;
954  }
955  int padding = widget_padding_get_left(WIDGET(tb));
956  padding += widget_padding_get_right(WIDGET(tb));
957  int old_width = pango_layout_get_width(tb->layout);
958  pango_layout_set_width(tb->layout, -1);
959  int width = textbox_get_font_width(tb);
960  // Restore.
961  pango_layout_set_width(tb->layout, old_width);
962  return width + padding + offset;
963 }
964 
965 void textbox_set_ellipsize(textbox *tb, PangoEllipsizeMode mode) {
966  if (tb) {
967  tb->emode = mode;
968  if ((tb->flags & TB_WRAP) != TB_WRAP) {
969  // Store the mode.
970  pango_layout_set_ellipsize(tb->layout, tb->emode);
972  }
973  }
974 }
gboolean helper_validate_font(PangoFontDescription *pfd, const char *font)
Definition: helper.c:605
KeyBindingAction
Definition: keyb.h:58
MouseBindingMouseDefaultAction
Definition: keyb.h:166
@ 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:167
@ MOUSE_DCLICK_UP
Definition: keyb.h:170
@ MOUSE_CLICK_UP
Definition: keyb.h:168
@ MOUSE_DCLICK_DOWN
Definition: keyb.h:169
int textbox_get_height(const textbox *tb)
Definition: textbox.c:896
void textbox_insert(textbox *tb, const int char_pos, const char *str, const int slen)
Definition: textbox.c:636
void textbox_font(textbox *tb, TextBoxFontType tbft)
Definition: textbox.c:273
TextboxFlags
Definition: textbox.h:89
void textbox_delete(textbox *tb, int pos, int dlen)
Definition: textbox.c:660
int textbox_keybinding(textbox *tb, KeyBindingAction action)
Definition: textbox.c:751
TextBoxFontType
Definition: textbox.h:101
void textbox_cleanup(void)
Definition: textbox.c:870
double textbox_get_estimated_char_width(void)
Definition: textbox.c:918
int textbox_get_font_height(const textbox *tb)
Definition: textbox.c:901
void textbox_set_pango_attributes(textbox *tb, PangoAttrList *list)
Definition: textbox.c:337
void textbox_set_ellipsize(textbox *tb, PangoEllipsizeMode mode)
Definition: textbox.c:965
void textbox_setup(void)
Definition: textbox.c:844
const char * textbox_get_visible_text(const textbox *tb)
Definition: textbox.c:325
double textbox_get_estimated_char_height(void)
Definition: textbox.c:914
PangoAttrList * textbox_get_pango_attributes(textbox *tb)
Definition: textbox.c:331
textbox * textbox_create(widget *parent, WidgetType type, const char *name, TextboxFlags flags, TextBoxFontType tbft, const char *text, double xalign, double yalign)
Definition: textbox.c:191
int textbox_get_estimated_height(const textbox *tb, int eh)
Definition: textbox.c:936
void textbox_cursor(textbox *tb, int pos)
Definition: textbox.c:521
void textbox_set_pango_context(const char *font, PangoContext *p)
Definition: textbox.c:851
int textbox_get_font_width(const textbox *tb)
Definition: textbox.c:907
void textbox_cursor_end(textbox *tb)
Definition: textbox.c:623
gboolean textbox_append_text(textbox *tb, const char *pad, const int pad_len)
Definition: textbox.c:813
void textbox_moveresize(textbox *tb, int x, int y, int w, int h)
Definition: textbox.c:380
void textbox_text(textbox *tb, const char *text)
Definition: textbox.c:345
double textbox_get_estimated_ch(void)
Definition: textbox.c:928
@ TB_INDICATOR
Definition: textbox.h:96
@ 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
@ SELECTED
Definition: textbox.h:109
@ URGENT
Definition: textbox.h:105
@ ACTIVE
Definition: textbox.h:107
@ HIGHLIGHT
Definition: textbox.h:116
@ STATE_MASK
Definition: textbox.h:120
@ ALT
Definition: textbox.h:114
@ FMOD_MASK
Definition: textbox.h:118
@ MARKUP
Definition: textbox.h:111
void rofi_view_queue_redraw(void)
Definition: view.c:508
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:139
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:58
static PangoContext * p_context
Definition: textbox.c:53
static int textbox_get_width(widget *)
Definition: textbox.c:878
static void textbox_cursor_dec_word(textbox *tb)
Definition: textbox.c:590
static void textbox_cursor_inc_word(textbox *tb)
Definition: textbox.c:559
static gboolean textbox_blink(gpointer data)
Definition: textbox.c:63
static WidgetTriggerActionResult textbox_editable_trigger_action(widget *wid, MouseBindingMouseDefaultAction action, gint x, gint y, G_GNUC_UNUSED void *user_data)
Definition: textbox.c:101
const char *const theme_prop_names[][3]
Definition: textbox.c:264
const char * default_font_name
Definition: textbox.c:850
#define DOT_OFFSET
Definition: textbox.c:44
static double ch_width
Definition: textbox.c:927
static int textbox_get_desired_height(widget *wid, const int width)
Definition: textbox.c:79
static int textbox_cursor_inc(textbox *tb)
Definition: textbox.c:539
static void textbox_free(widget *)
Definition: textbox.c:425
int textbox_get_desired_width(widget *wid, G_GNUC_UNUSED const int height)
Definition: textbox.c:940
static void textbox_initialize_font(textbox *tb)
Definition: textbox.c:130
static void textbox_resize(widget *wid, short w, short h)
Definition: textbox.c:75
static void textbox_cursor_del_sol(textbox *tb)
Definition: textbox.c:728
static void textbox_tab_stops(textbox *tb)
Definition: textbox.c:166
static void textbox_cursor_bkspc(textbox *tb)
Definition: textbox.c:705
static void textbox_cursor_bkspc_word(textbox *tb)
Definition: textbox.c:711
static void textbox_draw(widget *, cairo_t *)
Definition: textbox.c:443
static void textbox_cursor_del_word(textbox *tb)
Definition: textbox.c:736
static PangoFontMetrics * p_metrics
Definition: textbox.c:55
static void textbox_cursor_del(textbox *tb)
Definition: textbox.c:693
static double char_width
Definition: textbox.c:917
static void __textbox_update_pango_text(textbox *tb)
Definition: textbox.c:305
static int _textbox_get_height(widget *)
Definition: textbox.c:888
static void textbox_cursor_del_eol(textbox *tb)
Definition: textbox.c:720
static int textbox_cursor_dec(textbox *tb)
Definition: textbox.c:552
static void tbfc_entry_free(TBFontConfig *tbfc)
Definition: textbox.c:837
static GHashTable * tbfc_cache
Definition: textbox.c:61
GList * rofi_theme_get_list_distance(const widget *widget, const char *property)
Definition: theme.c:1227
RofiDistance rofi_theme_get_distance(const widget *widget, const char *property, int def)
Definition: theme.c:865
int rofi_theme_get_boolean(const widget *widget, const char *property, int def)
Definition: theme.c:891
int distance_get_pixel(RofiDistance d, RofiOrientation ori)
Definition: theme.c:1403
void rofi_theme_get_color(const widget *widget, const char *property, cairo_t *d)
Definition: theme.c:1055
double rofi_theme_get_double(const widget *widget, const char *property, double def)
Definition: theme.c:1028
const char * rofi_theme_get_string(const widget *widget, const char *property, const char *def)
Definition: theme.c:978
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:635
int widget_padding_get_left(const widget *wid)
Definition: widget.c:574
int widget_padding_get_right(const widget *wid)
Definition: widget.c:584
int widget_padding_get_padding_height(const widget *wid)
Definition: widget.c:629
int widget_padding_get_top(const widget *wid)
Definition: widget.c:596
int widget_padding_get_bottom(const widget *wid)
Definition: widget.c:606