]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - contrib/bsddialog/lib/calendarbox.c
OpenSSL: update to 3.0.10
[FreeBSD/FreeBSD.git] / contrib / bsddialog / lib / calendarbox.c
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause
3  *
4  * Copyright (c) 2022 Alfonso Sabato Siciliano
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer.
11  * 2. Redistributions in binary form must reproduce the above copyright
12  *    notice, this list of conditions and the following disclaimer in the
13  *    documentation and/or other materials provided with the distribution.
14  *
15  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25  * SUCH DAMAGE.
26  */
27
28 #include <sys/param.h>
29
30 #include <curses.h>
31 #include <stdlib.h>
32 #include <string.h>
33
34 #include "bsddialog.h"
35 #include "bsddialog_theme.h"
36 #include "lib_util.h"
37
38 #define MINHCAL   13
39 #define MINWCAL   36 /* 34 calendar, 1 + 1 margins */
40 #define MINYEAR   1900
41 #define MAXYEAR   999999999
42
43 static int month_days(int yy, int mm)
44 {
45         int days;
46
47         if (mm == 2)
48                 days = ISLEAP(yy) ? 29 : 28;
49         else if (mm == 4 || mm == 6 || mm == 9 || mm == 11)
50                 days = 30;
51         else
52                 days = 31;
53
54         return (days);
55 }
56
57 enum operation {
58         UP_DAY,
59         DOWN_DAY,
60         LEFT_DAY,
61         RIGHT_DAY,
62         UP_MONTH,
63         DOWN_MONTH,
64         UP_YEAR,
65         DOWN_YEAR
66 };
67
68 static void datectl(enum operation op, int *yy, int *mm, int *dd)
69 {
70         int ndays;
71
72         ndays = month_days(*yy, *mm);
73
74         switch (op) {
75         case UP_DAY:
76                 if (*dd > 7)
77                         *dd -= 7;
78                 else {
79                         if (*mm == 1) {
80                                 *yy -= 1;
81                                 *mm = 12;
82                         } else
83                                 *mm -= 1;
84                         ndays = month_days(*yy, *mm);
85                         *dd = ndays - abs(7 - *dd);
86                 }
87                 break;
88         case DOWN_DAY:
89                 if (*dd + 7 < ndays)
90                         *dd += 7;
91                 else {
92                         if (*mm == 12) {
93                                 *yy += 1;
94                                 *mm = 1;
95                         } else
96                                 *mm += 1;
97                         *dd = *dd + 7 - ndays;
98                 }
99                 break;
100         case LEFT_DAY:
101                 if (*dd > 1)
102                         *dd -= 1;
103                 else {
104                         if (*mm == 1) {
105                                 *yy -= 1;
106                                 *mm = 12;
107                         } else
108                                 *mm -= 1;
109                         *dd = month_days(*yy, *mm);
110                 }
111                 break;
112         case RIGHT_DAY:
113                 if (*dd < ndays)
114                         *dd += 1;
115                 else {
116                         if (*mm == 12) {
117                                 *yy += 1;
118                                 *mm = 1;
119                         } else
120                                 *mm += 1;
121                         *dd = 1;
122                 }
123                 break;
124         case UP_MONTH:
125                 if (*mm == 1) {
126                         *mm = 12;
127                         *yy -= 1;
128                 } else
129                         *mm -= 1;
130                 ndays = month_days(*yy, *mm);
131                 if (*dd > ndays)
132                         *dd = ndays;
133                 break;
134         case DOWN_MONTH:
135                 if (*mm == 12) {
136                         *mm = 1;
137                         *yy += 1;
138                 } else
139                         *mm += 1;
140                 ndays = month_days(*yy, *mm);
141                 if (*dd > ndays)
142                         *dd = ndays;
143                 break;
144         case UP_YEAR:
145                 *yy -= 1;
146                 ndays = month_days(*yy, *mm);
147                 if (*dd > ndays)
148                         *dd = ndays;
149                 break;
150         case DOWN_YEAR:
151                 *yy += 1;
152                 ndays = month_days(*yy, *mm);
153                 if (*dd > ndays)
154                         *dd = ndays;
155                 break;
156         }
157
158         if (*yy < MINYEAR) {
159                 *yy = MINYEAR;
160                 *mm = 1;
161                 *dd = 1;
162         }
163         if (*yy > MAXYEAR) {
164                 *yy = MAXYEAR;
165                 *mm = 12;
166                 *dd = 31;
167         }
168 }
169
170 static int week_day(int yy, int mm, int dd)
171 {
172         int wd;
173
174         dd += mm < 3 ? yy-- : yy - 2;
175         wd = 23*mm/9 + dd + 4 + yy/4 - yy/100 + yy/400;
176         wd %= 7;
177
178         return (wd);
179 }
180
181 static void
182 print_calendar(struct bsddialog_conf *conf, WINDOW *win, int yy, int mm, int dd,
183     bool active)
184 {
185         int ndays, i, y, x, wd, h, w;
186
187         getmaxyx(win, h, w);
188         wclear(win);
189         draw_borders(conf, win, h, w, RAISED);
190         if (active) {
191                 wattron(win, t.dialog.arrowcolor);
192                 mvwhline(win, 0, 15, conf->ascii_lines ? '^' : ACS_UARROW, 4);
193                 mvwhline(win, h-1, 15, conf->ascii_lines ? 'v' : ACS_DARROW, 4);
194                 mvwvline(win, 3, 0, conf->ascii_lines ? '<' : ACS_LARROW, 3);
195                 mvwvline(win, 3, w-1, conf->ascii_lines ? '>' : ACS_RARROW, 3);
196                 wattroff(win, t.dialog.arrowcolor);
197         }
198
199         mvwaddstr(win, 1, 5, "Sun Mon Tue Wed Thu Fri Sat");
200         ndays = month_days(yy, mm);
201         y = 2;
202         wd = week_day(yy, mm, 1);
203         for (i = 1; i <= ndays; i++) {
204                 x = 5 + (4 * wd); /* x has to be 6 with week number */
205                 wmove(win, y, x);
206                 mvwprintw(win, y, x, "%2d", i);
207                 if (i == dd) {
208                         wattron(win, t.menu.f_namecolor);
209                         mvwprintw(win, y, x, "%2d", i);
210                         wattroff(win, t.menu.f_namecolor);
211                 }
212                 wd++;
213                 if (wd > 6) {
214                         wd = 0;
215                         y++;
216                 }
217         }
218
219         wrefresh(win);
220 }
221
222 static void
223 drawsquare(struct bsddialog_conf *conf, WINDOW *win, const char *fmt,
224     const void *value, bool focus)
225 {
226         int h, w;
227
228         getmaxyx(win, h, w);
229         draw_borders(conf, win, h, w, RAISED);
230         if (focus) {
231                 wattron(win, t.dialog.arrowcolor);
232                 mvwhline(win, 0, 7, conf->ascii_lines ? '^' : ACS_UARROW, 3);
233                 mvwhline(win, 2, 7, conf->ascii_lines ? 'v' : ACS_DARROW, 3);
234                 wattroff(win, t.dialog.arrowcolor);
235         }
236
237         if (focus)
238                 wattron(win, t.menu.f_namecolor);
239         if (strchr(fmt, 's') != NULL)
240                 mvwprintw(win, 1, 1, fmt, (const char*)value);
241         else
242                 mvwprintw(win, 1, 1, fmt, *((const int*)value));
243         if (focus)
244                 wattroff(win, t.menu.f_namecolor);
245
246         wrefresh(win);
247 }
248
249 static int
250 calendar_autosize(struct bsddialog_conf *conf, int rows, int cols, int *h,
251     int *w, const char *text, struct buttons bs)
252 {
253         int htext, wtext;
254
255         if (cols == BSDDIALOG_AUTOSIZE || rows == BSDDIALOG_AUTOSIZE) {
256                 if (text_size(conf, rows, cols, text, &bs, MINHCAL, MINWCAL,
257                     &htext, &wtext) != 0)
258                         return (BSDDIALOG_ERROR);
259         }
260
261         if (cols == BSDDIALOG_AUTOSIZE)
262                 *w = widget_min_width(conf, wtext, MINWCAL, &bs);
263
264         if (rows == BSDDIALOG_AUTOSIZE)
265                 *h = widget_min_height(conf, htext, MINHCAL, true);
266
267         return (0);
268 }
269
270 static int calendar_checksize(int rows, int cols, struct buttons bs)
271 {
272         int mincols;
273
274         mincols = MAX(MINWCAL, buttons_min_width(bs));
275         mincols += VBORDERS;
276
277         if (cols < mincols)
278                 RETURN_ERROR("Few cols for this calendar (at least 38)");
279
280         if (rows < MINHCAL + 2 + 2) /* 2 buttons + 2 borders */
281                 RETURN_ERROR("Few rows for calendar (at least 17)");
282
283         return (0);
284 }
285
286 int
287 bsddialog_calendar(struct bsddialog_conf *conf, const char *text, int rows,
288     int cols, unsigned int *yy, unsigned int *mm, unsigned int *dd)
289 {
290         bool loop, focusbuttons;
291         int retval, y, x, h, w, sel, ycal, xcal, year, month, day;
292         wint_t input;
293         WINDOW *widget, *textpad, *shadow, *yearwin, *monthwin, *daywin;
294         struct buttons bs;
295         const char *m[12] = {
296                 "January", "February", "March", "April", "May", "June", "July",
297                 "August", "September", "October", "November", "December"
298         };
299
300         if (yy == NULL || mm == NULL || dd == NULL)
301                 RETURN_ERROR("yy / mm / dd cannot be NULL");
302
303         year = *yy > MAXYEAR ? MAXYEAR : *yy;
304         if (year < MINYEAR)
305                 year = MINYEAR;
306         month = *mm > 12 ? 12 : *mm;
307         if (month == 0)
308                 month = 1;
309         day = *dd == 0 ? 1 : *dd;
310         if(day > month_days(year, month))
311                 day = month_days(year, month);
312
313         get_buttons(conf, &bs, BUTTON_OK_LABEL, BUTTON_CANCEL_LABEL);
314
315         if (set_widget_size(conf, rows, cols, &h, &w) != 0)
316                 return (BSDDIALOG_ERROR);
317         if (calendar_autosize(conf, rows, cols, &h, &w, text, bs) != 0)
318                 return (BSDDIALOG_ERROR);
319         if (calendar_checksize(h, w, bs) != 0)
320                 return (BSDDIALOG_ERROR);
321         if (set_widget_position(conf, &y, &x, h, w) != 0)
322                 return (BSDDIALOG_ERROR);
323
324         if (new_dialog(conf, &shadow, &widget, y, x, h, w, &textpad, text, &bs,
325             true) != 0)
326                 return (BSDDIALOG_ERROR);
327
328         pnoutrefresh(textpad, 0, 0, y+1, x+2, y+h-17, x+w-2);
329         doupdate();
330
331         ycal = y + h - 15;
332         xcal = x + w/2 - 17;
333         mvwaddstr(widget, h - 16, w/2 - 17, "Month");
334         monthwin = new_boxed_window(conf, ycal, xcal, 3, 17, RAISED);
335         mvwaddstr(widget, h - 16, w/2, "Year");
336         yearwin = new_boxed_window(conf, ycal, xcal + 17, 3, 17, RAISED);
337         daywin = new_boxed_window(conf, ycal + 3, xcal, 9, 34, RAISED);
338
339         wrefresh(widget);
340
341         sel = -1;
342         loop = focusbuttons = true;
343         while (loop) {
344                 drawsquare(conf, monthwin, "%15s", m[month - 1], sel == 0);
345                 drawsquare(conf, yearwin, "%15d", &year, sel == 1);
346                 print_calendar(conf, daywin, year, month, day, sel == 2);
347
348                 if (get_wch(&input) == ERR)
349                         continue;
350                 switch(input) {
351                 case KEY_ENTER:
352                 case 10: /* Enter */
353                         if (focusbuttons || conf->button.always_active) {
354                                 retval = bs.value[bs.curr];
355                                 loop = false;
356                         }
357                         break;
358                 case 27: /* Esc */
359                         if (conf->key.enable_esc) {
360                                 retval = BSDDIALOG_ESC;
361                                 loop = false;
362                         }
363                         break;
364                 case '\t': /* TAB */
365                         if (focusbuttons) {
366                                 bs.curr++;
367                                 if (bs.curr >= (int)bs.nbuttons) {
368                                         focusbuttons = false;
369                                         sel = 0;
370                                         bs.curr = conf->button.always_active ?
371                                             0 : -1;
372                                 }
373                         } else {
374                                 sel++;
375                                 if (sel > 2) {
376                                         focusbuttons = true;
377                                         sel = -1;
378                                         bs.curr = 0;
379                                 }
380                         }
381                         draw_buttons(widget, bs, true);
382                         wrefresh(widget);
383                         break;
384                 case KEY_RIGHT:
385                         if (focusbuttons) {
386                                 bs.curr++;
387                                 if (bs.curr >= (int)bs.nbuttons) {
388                                         focusbuttons = false;
389                                         sel = 0;
390                                         bs.curr = conf->button.always_active ?
391                                             0 : -1;
392                                 }
393                         } else if (sel == 2) {
394                                 datectl(RIGHT_DAY, &year, &month, &day);
395                         } else { /* Month or Year*/
396                                 sel++;
397                         }
398                         draw_buttons(widget, bs, true);
399                         wrefresh(widget);
400                         break;
401                 case KEY_LEFT:
402                         if (focusbuttons) {
403                                 bs.curr--;
404                                 if (bs.curr < 0) {
405                                         focusbuttons = false;
406                                         sel = 2;
407                                         bs.curr = conf->button.always_active ?
408                                             0 : -1;
409                                 }
410                         } else if (sel == 2) {
411                                 datectl(LEFT_DAY, &year, &month, &day);
412                         } else if (sel == 1) {
413                                 sel = 0;
414                         } else { /* sel = 0, Month */
415                                 focusbuttons = true;
416                                 sel = -1;
417                                 bs.curr = 0;
418                         }
419                         draw_buttons(widget, bs, true);
420                         wrefresh(widget);
421                         break;
422                 case KEY_UP:
423                         if (focusbuttons) {
424                                 sel = 2;
425                                 focusbuttons = false;
426                                 bs.curr = conf->button.always_active ? 0 : -1;
427                                 draw_buttons(widget, bs, true);
428                                 wrefresh(widget);
429                         } else if (sel == 0) {
430                                 datectl(UP_MONTH, &year, &month, &day);
431                         } else if (sel == 1) {
432                                 datectl(UP_YEAR, &year, &month, &day);
433                         } else { /* sel = 2 */
434                                 datectl(UP_DAY, &year, &month, &day);
435                         }
436                         break;
437                 case KEY_DOWN:
438                         if (focusbuttons) {
439                                 break;
440                         } else if (sel == 0) {
441                                 datectl(DOWN_MONTH, &year, &month, &day);
442                         } else if (sel == 1) {
443                                 datectl(DOWN_YEAR, &year, &month, &day);
444                         } else { /* sel = 2 */
445                                 datectl(DOWN_DAY, &year, &month, &day);
446                         }
447                         break;
448                 case KEY_HOME:
449                         datectl(UP_MONTH, &year, &month, &day);
450                         break;
451                 case KEY_END:
452                         datectl(DOWN_MONTH, &year, &month, &day);
453                         break;
454                 case KEY_PPAGE:
455                         datectl(UP_YEAR, &year, &month, &day);
456                         break;
457                 case KEY_NPAGE:
458                         datectl(DOWN_YEAR, &year, &month, &day);
459                         break;
460                 case KEY_F(1):
461                         if (conf->key.f1_file == NULL &&
462                             conf->key.f1_message == NULL)
463                                 break;
464                         if (f1help(conf) != 0)
465                                 return (BSDDIALOG_ERROR);
466                         /* No break, screen size can change */
467                 case KEY_RESIZE:
468                         /* Important for decreasing screen */
469                         hide_widget(y, x, h, w, conf->shadow);
470                         refresh();
471
472                         if (set_widget_size(conf, rows, cols, &h, &w) != 0)
473                                 return (BSDDIALOG_ERROR);
474                         if (calendar_autosize(conf, rows, cols, &h, &w, text,
475                             bs) != 0)
476                                 return (BSDDIALOG_ERROR);
477                         if (calendar_checksize(h, w, bs) != 0)
478                                 return (BSDDIALOG_ERROR);
479                         if (set_widget_position(conf, &y, &x, h, w) != 0)
480                                 return (BSDDIALOG_ERROR);
481
482                         if (update_dialog(conf, shadow, widget, y, x, h, w,
483                             textpad, text, &bs, true) != 0)
484                                 return (BSDDIALOG_ERROR);
485                         pnoutrefresh(textpad, 0, 0, y+1, x+2, y+h-17, x+w-2);
486                         doupdate();
487
488                         ycal = y + h - 15;
489                         xcal = x + w/2 - 17;
490                         mvwaddstr(widget, h - 16, w/2 - 17, "Month");
491                         mvwin(monthwin, ycal, xcal);
492                         mvwaddstr(widget, h - 16, w/2, "Year");
493                         mvwin(yearwin, ycal, xcal + 17);
494                         mvwin(daywin, ycal + 3, xcal);
495                         wrefresh(widget);
496
497                         /* Important to avoid grey lines expanding screen */
498                         refresh();
499                         break;
500                 default:
501                         if (shortcut_buttons(input, &bs)) {
502                                 retval = bs.value[bs.curr];
503                                 loop = false;
504                         }
505                 }
506         }
507
508         if (retval == BSDDIALOG_OK) {
509                 *yy = year;
510                 *mm = month;
511                 *dd = day;
512         }
513
514         delwin(yearwin);
515         delwin(monthwin);
516         delwin(daywin);
517         end_dialog(conf, shadow, widget, textpad);
518
519         return (retval);
520 }