* The instruction to remove the double quotes from the processing of
[alpine.git] / alpine / busy.c
blob22f34701debb516c52c04217d72af923b5d61717
1 #if !defined(lint) && !defined(DOS)
2 static char rcsid[] = "$Id: signal.c 138 2006-09-22 22:12:03Z mikes@u.washington.edu $";
3 #endif
5 /* ========================================================================
6 * Copyright 2006-2007 University of Washington
7 * Copyright 2013-2020 Eduardo Chappa
9 * Licensed under the Apache License, Version 2.0 (the "License");
10 * you may not use this file except in compliance with the License.
11 * You may obtain a copy of the License at
13 * http://www.apache.org/licenses/LICENSE-2.0
15 * ========================================================================
18 /*======================================================================
19 Implement busy_cue spinner
20 ====*/
23 #include <system.h>
24 #include <general.h>
26 #include "../c-client/c-client.h"
28 #include "../pith/conf.h"
29 #include "../pith/state.h"
30 #include "../pith/status.h"
31 #include "../pith/busy.h"
32 #include "../pith/debug.h"
33 #include "../pith/help.h"
35 #include "../pith/charconv/utf8.h"
37 #ifdef _WINDOWS
38 #include "../pico/osdep/mswin.h"
39 #endif
41 #include "status.h"
42 #include "after.h"
43 #include "busy.h"
46 static int dotcount;
47 static char busy_message[MAX_BM + 1];
48 static int busy_cue_pause;
49 static int busy_width;
50 static int final_message;
51 static int final_message_pri;
53 static percent_done_t percent_done_ptr;
55 #define MAX_SPINNER_WIDTH 32
56 #define MAX_SPINNER_ELEMENTS 64
58 static int spinner = 0;
59 static struct _spinner {
60 int width,
61 elements;
62 char *bars[MAX_SPINNER_ELEMENTS];
63 unsigned used_this_round:1;
64 } spinners[] = {
65 {4, 4, {"<|> ", "</> ", "<-> ", "<\\> "}},
66 {8, 34, {"~~~~~~~~", "~~~~~~~~", "/~~~~~~~", "/~~~~~~~", "_/~~~~~~",
67 "_/~~~~~~", "__/~~~~~", "__/~~~~~", "___/~~~~", "___/~~~~",
68 "\\___/~~~", "\\___/~~~", "~\\___/~~", "~\\___/~~", "~~\\___/~",
69 "~~\\___/~", "~~~\\___/", "~~~\\___/", "^~~~\\___", "^~~~\\___",
70 "~^~~~\\__", "~^~~~\\__", "~~^~~~\\_", "~~^~~~\\_", "~~~^~~~\\",
71 "~~~^~~~\\", "~~~~^~~~", "~~~~^~~~", "~~~~~^~~", "~~~~~^~~",
72 "~~~~~~^~", "~~~~~~^~", "~~~~~~~^", "~~~~~~~^"}},
73 {9, 14 , {"| WAIT |", "| WAIT |", "-|WAIT|-", "-|WAIT|-",
74 "--|AI|--", "--|AI|--", "---||---", "---||---",
75 "--|AI|--", "--|AI|--", "-|WAIT|-", "-|WAIT|-",
76 "| WAIT |", "| WAIT |"}},
77 {9, 24 , {"o | ", "o | ", " o | ", " o | ",
78 " o | ", " o | ", " o| ", " o| ",
79 " \\ ", " \\ ", " -o ", " -o ",
80 " / o ", " / o ", " | o ", " | o ",
81 " \\ o", " \\ o", " - ", " - ",
82 " / ", " / ", " | ", " | "}},
83 {8, 38, {"~~~~~~~~", "~~~~~~~~", "/~~~~~~~", "/~~~~~~~", "_/~~~~~~",
84 "_/~~~~~~", "__/~~~~~", "__/~~~~~", "___/~~~~", "___/~~~~",
85 "\\___/~~~", "\\___/~~~", "~\\___/~~", "~\\___/~~", "~~\\___/~",
86 "~~\\___/~", "~~~\\___/", "~~~\\___/", "~~~~\\___", "~~~~\\___",
87 "\\~~~~\\__", "\\~~~~\\__", "/\\~~~~\\_", "/\\~~~~\\_", "~/\\~~~~\\",
88 "~/\\~~~~\\", "~~/\\~~~~", "~~/\\~~~~", "~~~/\\~~~", "~~~/\\~~~",
89 "~~~~/\\~~", "~~~~/\\~~", "~~~~~/\\~", "~~~~~/\\~", "~~~~~~/\\",
90 "~~~~~~/\\", "~~~~~~~/", "~~~~~~~/"}},
91 {6, 10, {"> ", "<> ", "><> ", " ><> ", " ><> ",
92 " ><> ", " ><> ", " ><>", " ><", " >"}},
93 {6, 10, {" <", " <>", " <><", " <>< ", " <>< ",
94 " <>< ", " <>< ", "<>< ", ">< ", "< "}},
95 {6, 10, {"> <", "<> <>", "><> <><", " ><><>< ", " ><>< ",
96 " <><> ", " <><><> ", "<>< ><>", ">< ><", "< >"}},
97 {11, 4, {"--|-(o)-|--", "--/-(o)-\\--", "----(o)----", "--\\-(o)-/--"}},
98 {6, 7, {"\\____/", "_\\__/_", "__\\/__", "__/\\__",
99 "_/__\\_", "/____\\", "|____|"}},
100 {4, 4, {"<|> ", "<\\> ", "<-> ", "</> "}},
101 {4, 10,{"| ", " / ", " _ ", " \\ ", " | ", " | ", " \\ ",
102 " _ ", " / ", "| "}},
103 {4, 8, {"_ _ ", "\\ \\ ", " | |", " / /", " _ _", " / /", " | |", "\\ \\ "}},
104 {4, 8, {"_ ", "\\ ", " | ", " / ", " _ ", " \\ ", " | ", "/ "}},
105 {4, 8, {"_ ", "\\ ", " | ", " / ", " _ ", " / ", " | ", "\\ "}},
106 {4, 4, {" . ", " o ", " O ", " o "}},
107 {4, 5, {"....", " ...", ". ..", ".. .", "... "}},
108 {4, 5, {" ", ". ", " . ", " . ", " ."}},
109 {4, 4, {".oOo", "oOo.", "Oo.o", "o.oO"}},
110 {4, 11,{"____", "\\___", "|\\__", "||\\_", "|||\\", "||||", "/|||",
111 "_/||", "__/|", "___/", "____"}},
112 {7, 9, {". .", " . . ", " . . ",
113 " . ", " + ", " * ", " X ",
114 " # ", " "}},
115 {4, 4, {". O ", "o o ", "O . ", "o o "}},
116 {4, 26,{"| ", "/ ", "_ ", "\\ ", " | ", " / ", " _ ", " \\ ",
117 " | ", " / ", " _ ", " \\ ", " |", " |", " \\ ", " _ ", " / ",
118 " | ", " \\ ", " _ ", " / ", " | ", "\\ ", "_ ", "/ ", "| "}},
119 {4, 8, {"* ", "-* ", "--* ", " --*", " --", " -", " ", " "}},
120 {4, 2, {"\\/\\/", "/\\/\\"}},
121 {4, 4, {"\\|/|", "|\\|/", "/|\\|", "|/|\\"}}
127 * various pauses in 100th's of second
129 #define BUSY_PERIOD_PERCENT 25 /* percent done update */
130 #define BUSY_MSG_DONE 0 /* no more updates */
131 #define BUSY_MSG_RETRY 25 /* message line conflict */
132 #define BUSY_DELAY_PERCENT 33 /* pause before showing % */
133 #define BUSY_DELAY_SPINNER 100 /* second pause before spinner */
136 /* internal prototypes */
137 int do_busy_cue(void *);
138 void done_busy_cue(void *);
142 * Turn on a busy cue.
144 * msg -- the busy message to print in status line
145 * pc_f -- if non-null, call this function to get the percent done,
146 * (an integer between 0 and 100). If null, append dots.
147 * delay -- seconds to delay output of delay notification
149 * Returns: 0 If busy cue was already set up before we got here
150 * 1 If busy cue was not already set up.
152 * NOTE: busy_cue and cancel_busy_cue MUST be called symmetrically in the
153 * same lexical scope.
156 busy_cue(char *msg, percent_done_t pc_f, int delay)
158 AFTER_S *a = NULL, **ap;
160 dprint((9, "busy_cue(%s, %p, %d)\n", msg ? msg : "Busy", pc_f, delay));
162 if(!(ps_global && ps_global->ttyo)){
163 dprint((9, "busy_cue returns No (ttyo)"));
164 return(0);
168 * If we're already busy'ing, but a cue is invoked that
169 * supplies more useful info, use it...
171 if(after_active){
172 if(msg || pc_f)
173 stop_after(1); /* uninstall old handler */
174 else
175 return(0); /* nothing to add, return */
178 /* get ready to set up list of AFTER_S's */
179 ap = &a;
181 dotcount = 0;
182 percent_done_ptr = pc_f;
184 if(msg){
185 strncpy(busy_message, msg, sizeof(busy_message));
186 final_message = 1;
188 else{
189 strncpy(busy_message, "Busy", sizeof(busy_message));
190 final_message = 0;
193 busy_message[sizeof(busy_message)-1] = '\0';
194 busy_width = utf8_width(busy_message);
196 if(!delay){
197 char progress[MAX_SCREEN_COLS+1];
198 int space_left, slots_used;
200 final_message = 1;
201 space_left = (ps_global->ttyo ? ps_global->ttyo->screen_cols
202 : 80) - busy_width - 2; /* 2 is for [] */
203 slots_used = MAX(0, MIN(space_left-3, 10));
205 if(percent_done_ptr && slots_used >= 4){
206 snprintf(progress, sizeof(progress), "%s |%*s|", busy_message, slots_used, "");
207 progress[sizeof(progress)-1] = '\0';
209 else{
210 dotcount++;
211 snprintf(progress, sizeof(progress), "%s%*s", busy_message,
212 spinners[spinner].width + 1, "");
213 progress[sizeof(progress)-1] = '\0';
217 if(status_message_remaining()){
218 char buf[sizeof(progress) + 30];
219 char *append = " [not actually shown]";
221 strncpy(buf, progress, sizeof(buf)-1);
222 buf[sizeof(buf)-1] = '\0';
224 strncat(buf, append, sizeof(buf) - strlen(buf) - 1);
225 buf[sizeof(buf)-1] = '\0';
227 add_review_message(buf, -1);
229 else{
230 q_status_message(SM_ORDER, 0, 1, progress);
233 * We use display_message so that the initial message will
234 * be forced out only if there is not a previous message
235 * currently being displayed that hasn't been displayed for
236 * its min display time yet. In that case, we don't want
237 * to force out the initial message.
239 display_message('x');
242 fflush(stdout);
246 * Randomly select one of the animations, even taking care
247 * to run through all of them before starting over!
248 * The user won't actually see them all because most of them
249 * will never show up before they are canceled, but it
250 * should still help.
252 spinner = -1;
253 if(F_OFF(F_USE_BORING_SPINNER,ps_global) && ps_global->active_status_interval > 0){
254 int arrsize, eligible, pick_this_one, i, j;
256 arrsize = sizeof(spinners)/sizeof(struct _spinner);
258 /* how many of them are eligible to be used this round? */
259 for(eligible = i = 0; i < arrsize; i++)
260 if(!spinners[i].used_this_round)
261 eligible++;
263 if(eligible == 0) /* reset */
264 for(eligible = i = 0; i < arrsize; i++){
265 spinners[i].used_this_round = 0;
266 eligible++;
269 if(eligible > 0){ /* it will be */
270 pick_this_one = random() % eligible;
271 for(j = i = 0; i < arrsize && spinner < 0; i++)
272 if(!spinners[i].used_this_round){
273 if(j == pick_this_one)
274 spinner = i;
275 else
276 j++;
281 if(spinner < 0 || spinner > sizeof(spinners)/sizeof(struct _spinner) -1)
282 spinner = 0;
284 *ap = new_afterstruct();
285 (*ap)->delay = (pc_f) ? BUSY_DELAY_PERCENT : BUSY_DELAY_SPINNER;
286 (*ap)->f = do_busy_cue;
287 (*ap)->cf = done_busy_cue;
288 ap = &(*ap)->next;
290 start_after(a); /* launch cue handler */
292 #ifdef _WINDOWS
293 mswin_setcursor(MSWIN_CURSOR_BUSY);
294 #endif
296 return(1);
301 * If final_message was set when busy_cue was called:
302 * and message_pri = -1 -- no final message queued
303 * else final message queued with min equal to message_pri
305 void
306 cancel_busy_cue(int message_pri)
308 dprint((9, "cancel_busy_cue(%d)\n", message_pri));
310 final_message_pri = message_pri;
312 stop_after(0);
316 * suspend_busy_cue - continue previously installed busy_cue.
318 void
319 suspend_busy_cue(void)
321 dprint((9, "suspend_busy_cue\n"));
323 if(after_active)
324 busy_cue_pause = 1;
329 * resume_busy_cue - continue previously installed busy_cue.
331 void
332 resume_busy_cue(unsigned int pause)
334 dprint((9, "resume_busy_cue\n"));
336 if(after_active)
337 busy_cue_pause = 0;
342 * do_busy_cue - paint the busy cue and return how long caller
343 * should pause before calling us again
346 do_busy_cue(void *data)
348 int space_left, slots_used, period;
349 char dbuf[MAX_SCREEN_COLS+1];
351 /* Don't wipe out any displayed status message prematurely */
352 if(status_message_remaining() || busy_cue_pause)
353 return(BUSY_MSG_RETRY);
355 space_left = (ps_global->ttyo ? ps_global->ttyo->screen_cols : 80) -
356 busy_width - 2; /* 2 is for [] */
357 slots_used = MAX(0, MIN(space_left-3, 10));
359 if(percent_done_ptr && slots_used >= 4){
360 int completed, pd;
361 char *s;
363 pd = (*percent_done_ptr)();
364 pd = MIN(MAX(0, pd), 100);
366 completed = (pd * slots_used) / 100;
367 snprintf(dbuf, sizeof(dbuf), "%s |%s%s%*s|", busy_message,
368 completed > 1 ? repeat_char(completed-1, pd==100 ? ' ' : '-') : "",
369 (completed > 0 && pd != 100) ? ">" : "",
370 slots_used - completed, "");
371 dbuf[sizeof(dbuf)-1] = '\0';
373 if(slots_used == 10){
374 s = dbuf + strlen(dbuf) - 8;
375 if(pd < 10){
376 s++; s++;
377 *s++ = '0' + pd;
379 else if(pd < 100){
380 s++;
381 *s++ = '0' + pd / 10;
382 *s++ = '0' + pd % 10;
384 else{
385 *s++ = '1';
386 *s++ = '0';
387 *s++ = '0';
390 *s = '%';
393 period = BUSY_PERIOD_PERCENT;
395 else{
396 char b[MAX_SPINNER_WIDTH + 2];
397 int ind;
399 ind = (dotcount % spinners[spinner].elements);
401 spinners[spinner].used_this_round = 1;
402 if(space_left >= spinners[spinner].width + 1){
403 b[0] = SPACE;
404 strncpy(b+1,
405 (ps_global->active_status_interval > 0)
406 ? spinners[spinner].bars[ind] : "... ", sizeof(b)-1);
407 b[sizeof(b)-1] = '\0';
409 else if(space_left >= 2 && space_left < sizeof(b)){
410 b[0] = '.';
411 b[1] = '.';
412 b[2] = '.';
413 b[space_left] = '\0';
415 else
416 b[0] = '\0';
418 snprintf(dbuf, sizeof(dbuf), "%s%s", busy_message, b);
419 dbuf[sizeof(dbuf)-1] = '\0';
421 /* convert interval to delay in 100ths of second */
422 period = (ps_global->active_status_interval > 0)
423 ? (100 / MIN(10, ps_global->active_status_interval)) : BUSY_MSG_DONE;
426 status_message_write(dbuf, 1);
427 dotcount++;
428 fflush(stdout);
430 return(period);
434 void
435 done_busy_cue(void *data)
437 int space_left, slots_used;
439 if(final_message && final_message_pri >= 0){
440 char progress[MAX_SCREEN_COLS+1];
442 /* 2 is for [] */
443 space_left = (ps_global->ttyo ? ps_global->ttyo->screen_cols : 80) - busy_width - 2;
444 slots_used = MAX(0, MIN(space_left-3, 10));
446 if(percent_done_ptr && slots_used >= 4){
447 int left, right;
449 right = (slots_used - 4)/2;
450 left = slots_used - 4 - right;
451 snprintf(progress, sizeof(progress), "%s |%*s100%%%*s|",
452 busy_message, left, "", right, "");
453 progress[sizeof(progress)-1] = '\0';
454 q_status_message(SM_ORDER,
455 final_message_pri>=2 ? MAX(final_message_pri,3) : 0,
456 final_message_pri+2, progress);
458 else{
459 int padding;
461 padding = MAX(0, MIN(space_left-5, spinners[spinner].width-4));
463 snprintf(progress, sizeof(progress), "%s %*sDONE", busy_message,
464 padding, "");
465 progress[sizeof(progress)-1] = '\0';
466 q_status_message(SM_ORDER,
467 final_message_pri>=2 ? MAX(final_message_pri,3) : 0,
468 final_message_pri+2, progress);
472 mark_status_dirty();