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