1 /* ========================================================================
2 * Copyright 2006-2007 University of Washington
3 * Copyright 2013-2022 Eduardo Chappa
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
9 * http://www.apache.org/licenses/LICENSE-2.0
11 * ========================================================================
14 /*======================================================================
15 Implement busy_cue spinner
22 #include "../c-client/c-client.h"
24 #include "../pith/conf.h"
25 #include "../pith/state.h"
26 #include "../pith/status.h"
27 #include "../pith/busy.h"
28 #include "../pith/debug.h"
29 #include "../pith/help.h"
31 #include "../pith/charconv/utf8.h"
34 #include "../pico/osdep/mswin.h"
43 static char busy_message
[MAX_BM
+ 1];
44 static int busy_cue_pause
;
45 static int busy_width
;
46 static int final_message
;
47 static int final_message_pri
;
49 static percent_done_t percent_done_ptr
;
51 #define MAX_SPINNER_WIDTH 32
52 #define MAX_SPINNER_ELEMENTS 64
54 static int spinner
= 0;
55 static struct _spinner
{
58 char *bars
[MAX_SPINNER_ELEMENTS
];
59 unsigned used_this_round
:1;
61 {4, 4, {"<|> ", "</> ", "<-> ", "<\\> "}},
62 {8, 34, {"~~~~~~~~", "~~~~~~~~", "/~~~~~~~", "/~~~~~~~", "_/~~~~~~",
63 "_/~~~~~~", "__/~~~~~", "__/~~~~~", "___/~~~~", "___/~~~~",
64 "\\___/~~~", "\\___/~~~", "~\\___/~~", "~\\___/~~", "~~\\___/~",
65 "~~\\___/~", "~~~\\___/", "~~~\\___/", "^~~~\\___", "^~~~\\___",
66 "~^~~~\\__", "~^~~~\\__", "~~^~~~\\_", "~~^~~~\\_", "~~~^~~~\\",
67 "~~~^~~~\\", "~~~~^~~~", "~~~~^~~~", "~~~~~^~~", "~~~~~^~~",
68 "~~~~~~^~", "~~~~~~^~", "~~~~~~~^", "~~~~~~~^"}},
69 {9, 14 , {"| WAIT |", "| WAIT |", "-|WAIT|-", "-|WAIT|-",
70 "--|AI|--", "--|AI|--", "---||---", "---||---",
71 "--|AI|--", "--|AI|--", "-|WAIT|-", "-|WAIT|-",
72 "| WAIT |", "| WAIT |"}},
73 {9, 24 , {"o | ", "o | ", " o | ", " o | ",
74 " o | ", " o | ", " o| ", " o| ",
75 " \\ ", " \\ ", " -o ", " -o ",
76 " / o ", " / o ", " | o ", " | o ",
77 " \\ o", " \\ o", " - ", " - ",
78 " / ", " / ", " | ", " | "}},
79 {8, 38, {"~~~~~~~~", "~~~~~~~~", "/~~~~~~~", "/~~~~~~~", "_/~~~~~~",
80 "_/~~~~~~", "__/~~~~~", "__/~~~~~", "___/~~~~", "___/~~~~",
81 "\\___/~~~", "\\___/~~~", "~\\___/~~", "~\\___/~~", "~~\\___/~",
82 "~~\\___/~", "~~~\\___/", "~~~\\___/", "~~~~\\___", "~~~~\\___",
83 "\\~~~~\\__", "\\~~~~\\__", "/\\~~~~\\_", "/\\~~~~\\_", "~/\\~~~~\\",
84 "~/\\~~~~\\", "~~/\\~~~~", "~~/\\~~~~", "~~~/\\~~~", "~~~/\\~~~",
85 "~~~~/\\~~", "~~~~/\\~~", "~~~~~/\\~", "~~~~~/\\~", "~~~~~~/\\",
86 "~~~~~~/\\", "~~~~~~~/", "~~~~~~~/"}},
87 {6, 10, {"> ", "<> ", "><> ", " ><> ", " ><> ",
88 " ><> ", " ><> ", " ><>", " ><", " >"}},
89 {6, 10, {" <", " <>", " <><", " <>< ", " <>< ",
90 " <>< ", " <>< ", "<>< ", ">< ", "< "}},
91 {6, 10, {"> <", "<> <>", "><> <><", " ><><>< ", " ><>< ",
92 " <><> ", " <><><> ", "<>< ><>", ">< ><", "< >"}},
93 {11, 4, {"--|-(o)-|--", "--/-(o)-\\--", "----(o)----", "--\\-(o)-/--"}},
94 {6, 7, {"\\____/", "_\\__/_", "__\\/__", "__/\\__",
95 "_/__\\_", "/____\\", "|____|"}},
96 {4, 4, {"<|> ", "<\\> ", "<-> ", "</> "}},
97 {4, 10,{"| ", " / ", " _ ", " \\ ", " | ", " | ", " \\ ",
99 {4, 8, {"_ _ ", "\\ \\ ", " | |", " / /", " _ _", " / /", " | |", "\\ \\ "}},
100 {4, 8, {"_ ", "\\ ", " | ", " / ", " _ ", " \\ ", " | ", "/ "}},
101 {4, 8, {"_ ", "\\ ", " | ", " / ", " _ ", " / ", " | ", "\\ "}},
102 {4, 4, {" . ", " o ", " O ", " o "}},
103 {4, 5, {"....", " ...", ". ..", ".. .", "... "}},
104 {4, 5, {" ", ". ", " . ", " . ", " ."}},
105 {4, 4, {".oOo", "oOo.", "Oo.o", "o.oO"}},
106 {4, 11,{"____", "\\___", "|\\__", "||\\_", "|||\\", "||||", "/|||",
107 "_/||", "__/|", "___/", "____"}},
108 {7, 9, {". .", " . . ", " . . ",
109 " . ", " + ", " * ", " X ",
111 {4, 4, {". O ", "o o ", "O . ", "o o "}},
112 {4, 26,{"| ", "/ ", "_ ", "\\ ", " | ", " / ", " _ ", " \\ ",
113 " | ", " / ", " _ ", " \\ ", " |", " |", " \\ ", " _ ", " / ",
114 " | ", " \\ ", " _ ", " / ", " | ", "\\ ", "_ ", "/ ", "| "}},
115 {4, 8, {"* ", "-* ", "--* ", " --*", " --", " -", " ", " "}},
116 {4, 2, {"\\/\\/", "/\\/\\"}},
117 {4, 4, {"\\|/|", "|\\|/", "/|\\|", "|/|\\"}}
123 * various pauses in 100th's of second
125 #define BUSY_PERIOD_PERCENT 25 /* percent done update */
126 #define BUSY_MSG_DONE 0 /* no more updates */
127 #define BUSY_MSG_RETRY 25 /* message line conflict */
128 #define BUSY_DELAY_PERCENT 33 /* pause before showing % */
129 #define BUSY_DELAY_SPINNER 100 /* second pause before spinner */
132 /* internal prototypes */
133 int do_busy_cue(void *);
134 void done_busy_cue(void *);
138 * Turn on a busy cue.
140 * msg -- the busy message to print in status line
141 * pc_f -- if non-null, call this function to get the percent done,
142 * (an integer between 0 and 100). If null, append dots.
143 * delay -- seconds to delay output of delay notification
145 * Returns: 0 If busy cue was already set up before we got here
146 * 1 If busy cue was not already set up.
148 * NOTE: busy_cue and cancel_busy_cue MUST be called symmetrically in the
149 * same lexical scope.
152 busy_cue(char *msg
, percent_done_t pc_f
, int delay
)
154 AFTER_S
*a
= NULL
, **ap
;
156 dprint((9, "busy_cue(%s, %p, %d)\n", msg
? msg
: "Busy", pc_f
, delay
));
158 if(!(ps_global
&& ps_global
->ttyo
)){
159 dprint((9, "busy_cue returns No (ttyo)"));
164 * If we're already busy'ing, but a cue is invoked that
165 * supplies more useful info, use it...
169 stop_after(1); /* uninstall old handler */
171 return(0); /* nothing to add, return */
174 /* get ready to set up list of AFTER_S's */
178 percent_done_ptr
= pc_f
;
181 strncpy(busy_message
, msg
, sizeof(busy_message
));
185 strncpy(busy_message
, "Busy", sizeof(busy_message
));
189 busy_message
[sizeof(busy_message
)-1] = '\0';
190 busy_width
= utf8_width(busy_message
);
193 char progress
[MAX_SCREEN_COLS
+1];
194 int space_left
, slots_used
;
197 space_left
= (ps_global
->ttyo
? ps_global
->ttyo
->screen_cols
198 : 80) - busy_width
- 2; /* 2 is for [] */
199 slots_used
= MAX(0, MIN(space_left
-3, 10));
201 if(percent_done_ptr
&& slots_used
>= 4){
202 snprintf(progress
, sizeof(progress
), "%s |%*s|", busy_message
, slots_used
, "");
203 progress
[sizeof(progress
)-1] = '\0';
207 snprintf(progress
, sizeof(progress
), "%s%*s", busy_message
,
208 spinners
[spinner
].width
+ 1, "");
209 progress
[sizeof(progress
)-1] = '\0';
213 if(status_message_remaining()){
214 char buf
[sizeof(progress
) + 30];
215 char *append
= " [not actually shown]";
217 strncpy(buf
, progress
, sizeof(buf
)-1);
218 buf
[sizeof(buf
)-1] = '\0';
220 strncat(buf
, append
, sizeof(buf
) - strlen(buf
) - 1);
221 buf
[sizeof(buf
)-1] = '\0';
223 add_review_message(buf
, -1);
226 q_status_message(SM_ORDER
, 0, 1, progress
);
229 * We use display_message so that the initial message will
230 * be forced out only if there is not a previous message
231 * currently being displayed that hasn't been displayed for
232 * its min display time yet. In that case, we don't want
233 * to force out the initial message.
235 display_message('x');
242 * Randomly select one of the animations, even taking care
243 * to run through all of them before starting over!
244 * The user won't actually see them all because most of them
245 * will never show up before they are canceled, but it
249 if(F_OFF(F_USE_BORING_SPINNER
,ps_global
) && ps_global
->active_status_interval
> 0){
250 int arrsize
, eligible
, pick_this_one
, i
, j
;
252 arrsize
= sizeof(spinners
)/sizeof(struct _spinner
);
254 /* how many of them are eligible to be used this round? */
255 for(eligible
= i
= 0; i
< arrsize
; i
++)
256 if(!spinners
[i
].used_this_round
)
259 if(eligible
== 0) /* reset */
260 for(eligible
= i
= 0; i
< arrsize
; i
++){
261 spinners
[i
].used_this_round
= 0;
265 if(eligible
> 0){ /* it will be */
266 pick_this_one
= random() % eligible
;
267 for(j
= i
= 0; i
< arrsize
&& spinner
< 0; i
++)
268 if(!spinners
[i
].used_this_round
){
269 if(j
== pick_this_one
)
277 if(spinner
< 0 || spinner
> sizeof(spinners
)/sizeof(struct _spinner
) -1)
280 *ap
= new_afterstruct();
281 (*ap
)->delay
= (pc_f
) ? BUSY_DELAY_PERCENT
: BUSY_DELAY_SPINNER
;
282 (*ap
)->f
= do_busy_cue
;
283 (*ap
)->cf
= done_busy_cue
;
286 start_after(a
); /* launch cue handler */
289 mswin_setcursor(MSWIN_CURSOR_BUSY
);
297 * If final_message was set when busy_cue was called:
298 * and message_pri = -1 -- no final message queued
299 * else final message queued with min equal to message_pri
302 cancel_busy_cue(int message_pri
)
304 dprint((9, "cancel_busy_cue(%d)\n", message_pri
));
306 final_message_pri
= message_pri
;
312 * suspend_busy_cue - continue previously installed busy_cue.
315 suspend_busy_cue(void)
317 dprint((9, "suspend_busy_cue\n"));
325 * resume_busy_cue - continue previously installed busy_cue.
328 resume_busy_cue(unsigned int pause
)
330 dprint((9, "resume_busy_cue\n"));
338 * do_busy_cue - paint the busy cue and return how long caller
339 * should pause before calling us again
342 do_busy_cue(void *data
)
344 int space_left
, slots_used
, period
;
345 char dbuf
[MAX_SCREEN_COLS
+1];
347 /* Don't wipe out any displayed status message prematurely */
348 if(status_message_remaining() || busy_cue_pause
)
349 return(BUSY_MSG_RETRY
);
351 space_left
= (ps_global
->ttyo
? ps_global
->ttyo
->screen_cols
: 80) -
352 busy_width
- 2; /* 2 is for [] */
353 slots_used
= MAX(0, MIN(space_left
-3, 10));
355 if(percent_done_ptr
&& slots_used
>= 4){
359 pd
= (*percent_done_ptr
)();
360 pd
= MIN(MAX(0, pd
), 100);
362 completed
= (pd
* slots_used
) / 100;
363 snprintf(dbuf
, sizeof(dbuf
), "%s |%s%s%*s|", busy_message
,
364 completed
> 1 ? repeat_char(completed
-1, pd
==100 ? ' ' : '-') : "",
365 (completed
> 0 && pd
!= 100) ? ">" : "",
366 slots_used
- completed
, "");
367 dbuf
[sizeof(dbuf
)-1] = '\0';
369 if(slots_used
== 10){
370 s
= dbuf
+ strlen(dbuf
) - 8;
377 *s
++ = '0' + pd
/ 10;
378 *s
++ = '0' + pd
% 10;
389 period
= BUSY_PERIOD_PERCENT
;
392 char b
[MAX_SPINNER_WIDTH
+ 2];
395 ind
= (dotcount
% spinners
[spinner
].elements
);
397 spinners
[spinner
].used_this_round
= 1;
398 if(space_left
>= spinners
[spinner
].width
+ 1){
401 (ps_global
->active_status_interval
> 0)
402 ? spinners
[spinner
].bars
[ind
] : "... ", sizeof(b
)-1);
403 b
[sizeof(b
)-1] = '\0';
405 else if(space_left
>= 2 && space_left
< sizeof(b
)){
409 b
[space_left
] = '\0';
414 snprintf(dbuf
, sizeof(dbuf
), "%s%s", busy_message
, b
);
415 dbuf
[sizeof(dbuf
)-1] = '\0';
417 /* convert interval to delay in 100ths of second */
418 period
= (ps_global
->active_status_interval
> 0)
419 ? (100 / MIN(10, ps_global
->active_status_interval
)) : BUSY_MSG_DONE
;
422 status_message_write(dbuf
, 1);
431 done_busy_cue(void *data
)
433 int space_left
, slots_used
;
435 if(final_message
&& final_message_pri
>= 0){
436 char progress
[MAX_SCREEN_COLS
+1];
439 space_left
= (ps_global
->ttyo
? ps_global
->ttyo
->screen_cols
: 80) - busy_width
- 2;
440 slots_used
= MAX(0, MIN(space_left
-3, 10));
442 if(percent_done_ptr
&& slots_used
>= 4){
445 right
= (slots_used
- 4)/2;
446 left
= slots_used
- 4 - right
;
447 snprintf(progress
, sizeof(progress
), "%s |%*s100%%%*s|",
448 busy_message
, left
, "", right
, "");
449 progress
[sizeof(progress
)-1] = '\0';
450 q_status_message(SM_ORDER
,
451 final_message_pri
>=2 ? MAX(final_message_pri
,3) : 0,
452 final_message_pri
+2, progress
);
457 padding
= MAX(0, MIN(space_left
-5, spinners
[spinner
].width
-4));
459 snprintf(progress
, sizeof(progress
), "%s %*sDONE", busy_message
,
461 progress
[sizeof(progress
)-1] = '\0';
462 q_status_message(SM_ORDER
,
463 final_message_pri
>=2 ? MAX(final_message_pri
,3) : 0,
464 final_message_pri
+2, progress
);