1 /* Text widget implementation. */
13 #include "bfu/dialog.h"
15 #include "config/kbdbind.h"
16 #include "intl/gettext/libintl.h"
17 #include "terminal/draw.h"
18 #include "terminal/mouse.h"
19 #include "terminal/terminal.h"
20 #include "util/color.h"
22 #define is_unsplitable(pos) (*(pos) && *(pos) != '\n' && !isspace(*(pos)))
25 add_dlg_text(struct dialog
*dlg
, unsigned char *text
,
26 enum format_align align
, int bottom_pad
)
28 struct widget
*widget
= &dlg
->widgets
[dlg
->number_of_widgets
++];
30 widget
->type
= WIDGET_TEXT
;
33 widget
->info
.text
.align
= align
;
34 widget
->info
.text
.is_label
= !!bottom_pad
;
37 /* Returns length of substring (from start of @text) before a split. */
39 split_line(unsigned char *text
, int max_width
)
41 unsigned char *split
= text
;
43 if (max_width
<= 0) return 0;
45 while (*split
&& *split
!= '\n') {
46 unsigned char *next_split
= split
+ 1;
48 while (is_unsplitable(next_split
))
51 if (next_split
- text
> max_width
) {
52 /* Force a split if no position was found yet,
53 * meaning there's no splittable substring under
56 split
= &text
[max_width
];
58 /* Give preference to split on a punctuation
59 * if any. Note that most of the time
60 * punctuation char is followed by a space so
61 * this rule will not match often.
62 * We match dash and quotes too. */
63 while (--split
!= text
) {
64 if (!ispunct(*split
)) continue;
69 /* If no way to do a clean split, just return
70 * requested maximal width. */
85 #define LINES_GRANULARITY 0x7
86 #define realloc_lines(x, o, n) mem_align_alloc(x, o, n, unsigned char *, LINES_GRANULARITY)
88 /* Find the start of each line with the current max width */
89 static unsigned char **
90 split_lines(struct widget_data
*widget_data
, int max_width
)
92 unsigned char *text
= widget_data
->widget
->text
;
93 unsigned char **lines
= (unsigned char **) widget_data
->cdata
;
96 if (widget_data
->info
.text
.max_width
== max_width
) return lines
;
98 /* We want to recalculate the max line width */
99 widget_data
->box
.width
= 0;
104 /* Skip first leading \n or space. */
105 if (isspace(*text
)) text
++;
108 width
= split_line(text
, max_width
);
110 /* split_line() may return 0. */
112 width
= 1; /* Infinite loop prevention. */
115 int_lower_bound(&widget_data
->box
.width
, width
);
117 if (!realloc_lines(&lines
, line
, line
+ 1))
120 lines
[line
++] = text
;
124 /* Yes it might be a bit ugly on the other hand it will be autofreed
126 widget_data
->cdata
= (unsigned char *) lines
;
127 widget_data
->info
.text
.lines
= line
;
128 widget_data
->info
.text
.max_width
= max_width
;
133 /* Format text according to dialog box and alignment. */
135 dlg_format_text_do(struct terminal
*term
, unsigned char *text
,
136 int x
, int *y
, int width
, int *real_width
,
137 struct color_pair
*color
, enum format_align align
)
142 for (; *text
; text
+= line_width
, (*y
)++) {
145 /* Skip first leading \n or space. */
146 if (!firstline
&& isspace(*text
))
152 line_width
= split_line(text
, width
);
154 /* split_line() may return 0. */
155 if (line_width
< 1) {
156 line_width
= 1; /* Infinite loop prevention. */
160 if (real_width
) int_lower_bound(real_width
, line_width
);
161 if (!term
|| !line_width
) continue;
163 /* Calculate the number of chars to indent */
164 if (align
== ALIGN_CENTER
)
165 shift
= (width
- line_width
) / 2;
166 else if (align
== ALIGN_RIGHT
)
167 shift
= width
- line_width
;
171 assert(line_width
<= width
&& shift
< width
);
173 draw_text(term
, x
+ shift
, *y
, text
, line_width
, 0, color
);
178 dlg_format_text(struct terminal
*term
, struct widget_data
*widget_data
,
179 int x
, int *y
, int width
, int *real_width
, int max_height
)
181 unsigned char *text
= widget_data
->widget
->text
;
182 unsigned char saved
= 0;
183 unsigned char *saved_pos
= NULL
;
186 height
= int_max(0, max_height
- 3);
188 /* If we are drawing set up the box before setting up the
190 set_box(&widget_data
->box
, x
, *y
,
191 widget_data
->box
.width
, height
);
192 if (height
== 0) return;
194 /* Can we scroll and do we even have to? */
195 if (widget_data
->widget
->info
.text
.is_scrollable
196 && (widget_data
->info
.text
.max_width
!= width
197 || height
< widget_data
->info
.text
.lines
))
199 unsigned char **lines
;
203 /* Ensure that the current split is valid but don't
204 * split if we don't have to */
205 if (widget_data
->box
.width
!= width
206 && !split_lines(widget_data
, width
))
209 lines
= (unsigned char **) widget_data
->cdata
;
211 /* Make maximum number of lines available */
212 visible
= int_max(widget_data
->info
.text
.lines
- height
,
215 int_bounds(&widget_data
->info
.text
.current
, 0, visible
);
216 current
= widget_data
->info
.text
.current
;
218 /* Set the current position */
219 text
= lines
[current
];
221 /* Do we have to force a text end ? */
222 visible
= widget_data
->info
.text
.lines
- current
;
223 if (visible
> height
) {
224 int lines_pos
= current
+ height
;
226 saved_pos
= lines
[lines_pos
];
228 /* We save the start of lines so backtrack to see
229 * if the previous line has a line end that should
230 * also be trimmed. */
231 if (lines_pos
> 0 && saved_pos
[-1] == '\n')
238 /* Force dialog to be the width of the longest line */
239 if (real_width
) int_lower_bound(real_width
, widget_data
->box
.width
);
242 /* Always reset @current if we do not need to scroll */
243 widget_data
->info
.text
.current
= 0;
246 dlg_format_text_do(term
, text
,
247 x
, y
, width
, real_width
,
248 get_bfu_color(term
, "dialog.text"),
249 widget_data
->widget
->info
.text
.align
);
251 if (widget_data
->widget
->info
.text
.is_label
) (*y
)--;
253 /* If we scrolled and something was trimmed restore it */
254 if (saved
&& saved_pos
) *saved_pos
= saved
;
257 static widget_handler_status_T
258 display_text(struct dialog_data
*dlg_data
, struct widget_data
*widget_data
)
260 struct window
*win
= dlg_data
->win
;
262 int scale
, current
, step
;
263 int lines
= widget_data
->info
.text
.lines
;
266 dlg_data
->box
.x
+ dlg_data
->box
.width
- DIALOG_LEFT_BORDER
- 1,
269 widget_data
->box
.height
);
271 if (!text_is_scrollable(widget_data
) || box
.height
<= 0)
272 return EVENT_PROCESSED
;
274 draw_box(win
->term
, &box
, ' ', 0,
275 get_bfu_color(win
->term
, "dialog.scrollbar"));
277 current
= widget_data
->info
.text
.current
;
278 scale
= (box
.height
+ 1) * 100 / lines
;
280 /* Scale the offset of @current */
281 step
= (current
+ 1) * scale
/ 100;
282 int_bounds(&step
, 0, widget_data
->box
.height
- 1);
284 /* Scale the number of visible lines */
285 box
.height
= (box
.height
+ 1) * scale
/ 100;
286 int_bounds(&box
.height
, 1, int_max(widget_data
->box
.height
- step
, 1));
288 /* Ensure we always step to the last position too */
289 if (lines
- widget_data
->box
.height
== current
) {
290 step
= widget_data
->box
.height
- box
.height
;
295 /* Save infos about selected scrollbar position and size.
296 * We'll use it when handling mouse. */
297 widget_data
->info
.text
.scroller_height
= box
.height
;
298 widget_data
->info
.text
.scroller_y
= box
.y
;
301 draw_box(win
->term
, &box
, ' ', 0,
302 get_bfu_color(win
->term
, "dialog.scrollbar-selected"));
304 /* Hope this is at least a bit reasonable. Set cursor
305 * and window pointer to start of the first text line. */
306 set_cursor(win
->term
, widget_data
->box
.x
, widget_data
->box
.y
, 1);
307 set_window_ptr(win
, widget_data
->box
.x
, widget_data
->box
.y
);
309 return EVENT_PROCESSED
;
313 format_and_display_text(struct widget_data
*widget_data
,
314 struct dialog_data
*dlg_data
,
317 struct terminal
*term
= dlg_data
->win
->term
;
318 int y
= widget_data
->box
.y
;
319 int height
= dialog_max_height(term
);
320 int lines
= widget_data
->info
.text
.lines
;
323 assert(widget_data
->box
.height
>= 0);
325 int_bounds(¤t
, 0, lines
- widget_data
->box
.height
);
327 if (widget_data
->info
.text
.current
== current
) return;
329 widget_data
->info
.text
.current
= current
;
331 draw_box(term
, &widget_data
->box
, ' ', 0,
332 get_bfu_color(term
, "dialog.generic"));
334 dlg_format_text(term
, widget_data
,
335 widget_data
->box
.x
, &y
, widget_data
->box
.width
, NULL
,
338 display_text(dlg_data
, widget_data
);
339 redraw_from_window(dlg_data
->win
);
342 static widget_handler_status_T
343 kbd_text(struct dialog_data
*dlg_data
, struct widget_data
*widget_data
)
345 int current
= widget_data
->info
.text
.current
;
346 struct term_event
*ev
= dlg_data
->term_event
;
348 switch (kbd_action(KEYMAP_MENU
, ev
, NULL
)) {
357 case ACT_MENU_PAGE_UP
:
358 current
-= widget_data
->box
.height
;
361 case ACT_MENU_PAGE_DOWN
:
362 current
+= widget_data
->box
.height
;
370 current
= widget_data
->info
.text
.lines
;
374 return EVENT_NOT_PROCESSED
;
377 format_and_display_text(widget_data
, dlg_data
, current
);
379 return EVENT_PROCESSED
;
382 static widget_handler_status_T
383 mouse_text(struct dialog_data
*dlg_data
, struct widget_data
*widget_data
)
386 int border
= DIALOG_LEFT_BORDER
+ DIALOG_LEFT_INNER_BORDER
;
387 int current
= widget_data
->info
.text
.current
;
388 int scroller_y
= widget_data
->info
.text
.scroller_y
;
389 int scroller_height
= widget_data
->info
.text
.scroller_height
;
390 int scroller_middle
= scroller_y
+ scroller_height
/2
391 - widget_data
->info
.text
.scroller_last_dir
;
392 struct box scroller_box
;
393 struct term_event
*ev
= dlg_data
->term_event
;
395 set_box(&scroller_box
,
396 dlg_data
->box
.x
+ dlg_data
->box
.width
- 1 - border
,
398 DIALOG_LEFT_INNER_BORDER
* 2 + 1,
399 widget_data
->box
.height
);
401 /* One can scroll by clicking or rolling the wheel on the scrollbar
402 * or one or two cells to its left or its right. */
403 if (!check_mouse_position(ev
, &scroller_box
))
404 return EVENT_NOT_PROCESSED
;
406 switch (get_mouse_button(ev
)) {
408 /* Left click scrolls up or down by one step. */
409 if (ev
->info
.mouse
.y
<= scroller_middle
)
416 /* Right click scrolls up or down by more than one step.
418 if (ev
->info
.mouse
.y
<= scroller_middle
)
425 /* Mouse wheel up scrolls up. */
430 /* Mouse wheel up scrolls down. */
435 return EVENT_NOT_PROCESSED
;
438 /* Save last direction used. */
439 if (widget_data
->info
.text
.current
< current
)
440 widget_data
->info
.text
.scroller_last_dir
= 1;
442 widget_data
->info
.text
.scroller_last_dir
= -1;
444 format_and_display_text(widget_data
, dlg_data
, current
);
446 #endif /* CONFIG_MOUSE */
448 return EVENT_PROCESSED
;
452 struct widget_ops text_ops
= {