1 /* 2 * $Id: calendar.c,v 1.110 2022/04/03 22:38:16 tom Exp $ 3 * 4 * calendar.c -- implements the calendar box 5 * 6 * Copyright 2001-2021,2022 Thomas E. Dickey 7 * 8 * This program is free software; you can redistribute it and/or modify 9 * it under the terms of the GNU Lesser General Public License, version 2.1 10 * as published by the Free Software Foundation. 11 * 12 * This program is distributed in the hope that it will be useful, but 13 * WITHOUT ANY WARRANTY; without even the implied warranty of 14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 * Lesser General Public License for more details. 16 * 17 * You should have received a copy of the GNU Lesser General Public 18 * License along with this program; if not, write to 19 * Free Software Foundation, Inc. 20 * 51 Franklin St., Fifth Floor 21 * Boston, MA 02110, USA. 22 */ 23 24 #include <dlg_internals.h> 25 #include <dlg_keys.h> 26 27 #define ONE_DAY (60 * 60 * 24) 28 29 #define MON_WIDE 4 /* width of a month-name */ 30 #define DAY_HIGH 6 /* maximum lines in day-grid */ 31 #define DAY_WIDE (8 * MON_WIDE) /* width of the day-grid */ 32 #define HDR_HIGH 1 /* height of cells with month/year */ 33 #define BTN_HIGH 1 /* height of button-row excluding margin */ 34 35 /* two more lines: titles for day-of-week and month/year boxes */ 36 #define MIN_HIGH (DAY_HIGH + 2 + HDR_HIGH + BTN_HIGH + (MAX_DAYS * MARGIN)) 37 #define MIN_WIDE (DAY_WIDE + (4 * MARGIN)) 38 39 typedef enum { 40 sMONTH = -3 41 ,sYEAR = -2 42 ,sDAY = -1 43 } STATES; 44 45 struct _box; 46 47 typedef int (*BOX_DRAW) (struct _box *, struct tm *); 48 49 typedef struct _box { 50 WINDOW *parent; 51 WINDOW *window; 52 int x; 53 int y; 54 int width; 55 int height; 56 BOX_DRAW box_draw; 57 int week_start; 58 } BOX; 59 60 #define MAX_DAYS 7 61 #define MAX_MONTHS 12 62 63 static char *cached_days[MAX_DAYS]; 64 static char *cached_months[MAX_MONTHS]; 65 66 static const char * 67 nameOfDayOfWeek(int n) 68 { 69 static bool shown[MAX_DAYS]; 70 71 while (n < 0) { 72 n += MAX_DAYS; 73 } 74 n %= MAX_DAYS; 75 #ifdef ENABLE_NLS 76 if (cached_days[n] == 0) { 77 const nl_item items[MAX_DAYS] = 78 { 79 ABDAY_1, ABDAY_2, ABDAY_3, ABDAY_4, ABDAY_5, ABDAY_6, ABDAY_7 80 }; 81 cached_days[n] = dlg_strclone(nl_langinfo(items[n])); 82 memset(shown, 0, sizeof(shown)); 83 } 84 #endif 85 if (cached_days[n] == 0) { 86 static const char *posix_days[MAX_DAYS] = 87 { 88 "Sunday", 89 "Monday", 90 "Tuesday", 91 "Wednesday", 92 "Thursday", 93 "Friday", 94 "Saturday" 95 }; 96 size_t limit = MON_WIDE - 1; 97 char *value = dlg_strclone(posix_days[n]); 98 99 /* 100 * POSIX does not actually say what the length of an abbreviated name 101 * is. Typically it is 2, which will fit into our layout. That also 102 * happens to work with CJK entries as seen in glibc, which are a 103 * double-width cell. For now (2016/01/26), handle too-long names only 104 * for POSIX values. 105 */ 106 if (strlen(value) > limit) 107 value[limit] = '\0'; 108 cached_days[n] = value; 109 } 110 if (!shown[n]) { 111 DLG_TRACE(("# DAY(%d) = '%s'\n", n, cached_days[n])); 112 shown[n] = TRUE; 113 } 114 return cached_days[n]; 115 } 116 117 static const char * 118 nameOfMonth(int n) 119 { 120 static bool shown[MAX_MONTHS]; 121 122 while (n < 0) { 123 n += MAX_MONTHS; 124 } 125 n %= MAX_MONTHS; 126 #ifdef ENABLE_NLS 127 if (cached_months[n] == 0) { 128 const nl_item items[MAX_MONTHS] = 129 { 130 MON_1, MON_2, MON_3, MON_4, MON_5, MON_6, 131 MON_7, MON_8, MON_9, MON_10, MON_11, MON_12 132 }; 133 cached_months[n] = dlg_strclone(nl_langinfo(items[n])); 134 memset(shown, 0, sizeof(shown)); 135 } 136 #endif 137 if (cached_months[n] == 0) { 138 static const char *posix_mons[MAX_MONTHS] = 139 { 140 "January", 141 "February", 142 "March", 143 "April", 144 "May", 145 "June", 146 "July", 147 "August", 148 "September", 149 "October", 150 "November", 151 "December" 152 }; 153 cached_months[n] = dlg_strclone(posix_mons[n]); 154 } 155 if (!shown[n]) { 156 DLG_TRACE(("# MON(%d) = '%s'\n", n, cached_months[n])); 157 shown[n] = TRUE; 158 } 159 return cached_months[n]; 160 } 161 162 /* 163 * Algorithm for Gregorian calendar. 164 */ 165 static int 166 isleap(int y) 167 { 168 return ((y % 4 == 0) && 169 ((y % 100 != 0) || 170 (y % 400 == 0))) ? 1 : 0; 171 } 172 173 static void 174 adjust_year_month(int *year, int *month) 175 { 176 while (*month < 0) { 177 *month += MAX_MONTHS; 178 *year -= 1; 179 } 180 while (*month >= MAX_MONTHS) { 181 *month -= MAX_MONTHS; 182 *year += 1; 183 } 184 } 185 186 static int 187 days_per_month(int year, int month) 188 { 189 static const int nominal[] = 190 { 191 31, 28, 31, 30, 31, 30, 192 31, 31, 30, 31, 30, 31 193 }; 194 int result; 195 196 adjust_year_month(&year, &month); 197 result = nominal[month]; 198 if (month == 1) 199 result += isleap(year); 200 return result; 201 } 202 203 static int 204 days_in_month(struct tm *current, int offset /* -1, 0, 1 */ ) 205 { 206 int year = current->tm_year + 1900; 207 int month = current->tm_mon + offset; 208 209 adjust_year_month(&year, &month); 210 return days_per_month(year, month); 211 } 212 213 static int 214 days_per_year(int year) 215 { 216 return (isleap(year) ? 366 : 365); 217 } 218 219 static int 220 days_in_year(struct tm *current, int offset /* -1, 0, 1 */ ) 221 { 222 return days_per_year(current->tm_year + 1900 + offset); 223 } 224 225 /* 226 * Adapted from C FAQ 227 * "17.28: How can I find the day of the week given the date?" 228 * implementation by Tomohiko Sakamoto. 229 * 230 * d = day (0 to whatever) 231 * m = month (1 through 12) 232 * y = year (1752 and later, for Gregorian calendar) 233 */ 234 static int 235 day_of_week(int y, int m, int d) 236 { 237 static int t[] = 238 { 239 0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4 240 }; 241 y -= (m < 3); 242 return (6 + (y + (y / 4) - (y / 100) + (y / 400) + t[m - 1] + d)) % MAX_DAYS; 243 } 244 245 static int 246 day_in_year(int year, int month, int day) 247 { 248 int result = day; 249 while (--month >= 1) 250 result += days_per_month(year, month); 251 return result; 252 } 253 254 static int 255 iso_week(int year, int month, int day) 256 { 257 int week = 1; 258 int dow; 259 int new_year_dow; 260 int diy; 261 int new_years_eve_dow; 262 static const int thursday = 3; 263 264 /* add the number weeks *between* date and newyear */ 265 diy = day_in_year(year, month, day); 266 week += (diy - 1) / MAX_DAYS; 267 268 /* 0 = Monday */ 269 dow = day_of_week(year, month, day); 270 new_year_dow = day_of_week(year, 1, 1); 271 272 /* 273 * If New Year falls on Friday, Saturday or Sunday, then New Years's week 274 * is the last week of the preceding year. In that case subtract one week. 275 */ 276 if (new_year_dow > thursday) 277 --week; 278 279 /* Add one week if there is a Sunday to Monday transition. */ 280 if (dow - new_year_dow < 0) 281 ++week; 282 283 /* Check if we are in the last week of the preceding year. */ 284 if (week < 1) { 285 week = iso_week(--year, 12, 31); 286 } 287 288 /* 289 * If we are in the same week as New Year's eve, check if New Year's eve is 290 * in the first week of the next year. 291 */ 292 new_years_eve_dow = (new_year_dow + 364 + isleap(year)) % MAX_DAYS; 293 if (365 + isleap(year) - diy < MAX_DAYS 294 && new_years_eve_dow >= dow 295 && new_years_eve_dow < thursday) { 296 week = 1; 297 } 298 return week; 299 } 300 301 static int * 302 getisoweeks(int year, int month) 303 { 304 static int result[10]; 305 int windx = 0; 306 int day; 307 int dpm = days_per_month(year, month); 308 309 for (day = 1; day <= dpm; day += MAX_DAYS) 310 result[windx++] = iso_week(year, month, day); 311 /* 312 * Ensure that there is a week number associated with the last day of the 313 * month, e.g., in case the last day of the month falls before Thursday, 314 * so that we have to show the week-number for the beginning of the 315 * following month. 316 */ 317 result[windx] = iso_week(year, month, dpm); 318 return result; 319 } 320 321 static int 322 day_cell_number(struct tm *current) 323 { 324 int cell; 325 cell = current->tm_mday - ((6 + current->tm_mday - current->tm_wday) % MAX_DAYS); 326 if ((current->tm_mday - 1) % MAX_DAYS != current->tm_wday) 327 cell += 6; 328 else 329 cell--; 330 return cell; 331 } 332 333 static int 334 next_or_previous(int key, int two_d) 335 { 336 int result = 0; 337 338 switch (key) { 339 case DLGK_GRID_UP: 340 result = two_d ? -MAX_DAYS : -1; 341 break; 342 case DLGK_GRID_LEFT: 343 result = -1; 344 break; 345 case DLGK_GRID_DOWN: 346 result = two_d ? MAX_DAYS : 1; 347 break; 348 case DLGK_GRID_RIGHT: 349 result = 1; 350 break; 351 default: 352 beep(); 353 break; 354 } 355 return result; 356 } 357 358 /* 359 * Draw the day-of-month selection box 360 */ 361 static int 362 draw_day(BOX * data, struct tm *current) 363 { 364 int cell_wide = MON_WIDE; 365 int y, x, this_x; 366 int save_y = 0, save_x = 0; 367 int day = current->tm_mday; 368 int mday; 369 int week = 0; 370 int windx = 0; 371 int *weeks = 0; 372 int last = days_in_month(current, 0); 373 int prev = days_in_month(current, -1); 374 375 werase(data->window); 376 dlg_draw_box2(data->parent, 377 data->y - MARGIN, data->x - MARGIN, 378 data->height + (2 * MARGIN), data->width + (2 * MARGIN), 379 menubox_attr, 380 menubox_border_attr, 381 menubox_border2_attr); 382 383 dlg_attrset(data->window, menubox_attr); /* daynames headline */ 384 for (x = 0; x < MAX_DAYS; x++) { 385 mvwprintw(data->window, 386 0, (x + 1) * cell_wide, "%*.*s ", 387 cell_wide - 1, 388 cell_wide - 1, 389 nameOfDayOfWeek(x + data->week_start)); 390 } 391 392 mday = ((6 + current->tm_mday - 393 current->tm_wday + 394 data->week_start) % MAX_DAYS) - MAX_DAYS; 395 if (mday <= -MAX_DAYS) 396 mday += MAX_DAYS; 397 398 if (dialog_vars.iso_week) { 399 weeks = getisoweeks(current->tm_year + 1900, current->tm_mon + 1); 400 } else { 401 /* mday is now in the range -6 to 0. */ 402 week = (current->tm_yday + 6 + mday - current->tm_mday) / MAX_DAYS; 403 } 404 405 for (y = 1; mday < last; y++) { 406 dlg_attrset(data->window, menubox_attr); /* weeknumbers headline */ 407 mvwprintw(data->window, 408 y, 0, 409 "%*d ", 410 cell_wide - 1, 411 weeks ? weeks[windx++] : ++week); 412 for (x = 0; x < MAX_DAYS; x++) { 413 this_x = 1 + (x + 1) * cell_wide; 414 ++mday; 415 if (wmove(data->window, y, this_x) == ERR) 416 continue; 417 dlg_attrset(data->window, item_attr); /* not selected days */ 418 if (mday == day) { 419 dlg_attrset(data->window, item_selected_attr); /* selected day */ 420 save_y = y; 421 save_x = this_x; 422 } 423 if (mday > 0) { 424 if (mday <= last) { 425 wprintw(data->window, "%*d", cell_wide - 2, mday); 426 } else if (mday == day) { 427 wprintw(data->window, "%*d", cell_wide - 2, mday - last); 428 } 429 } else if (mday == day) { 430 wprintw(data->window, "%*d", cell_wide - 2, mday + prev); 431 } 432 } 433 wmove(data->window, save_y, save_x); 434 } 435 /* just draw arrows - scrollbar is unsuitable here */ 436 dlg_draw_arrows(data->parent, TRUE, TRUE, 437 data->x + ARROWS_COL, 438 data->y - 1, 439 data->y + data->height); 440 441 return 0; 442 } 443 444 /* 445 * Draw the month-of-year selection box 446 */ 447 static int 448 draw_month(BOX * data, struct tm *current) 449 { 450 int month; 451 452 month = current->tm_mon + 1; 453 454 dlg_attrset(data->parent, dialog_attr); /* Headline "Month" */ 455 (void) mvwprintw(data->parent, data->y - 2, data->x - 1, _("Month")); 456 dlg_draw_box2(data->parent, 457 data->y - 1, data->x - 1, 458 data->height + 2, data->width + 2, 459 menubox_attr, 460 menubox_border_attr, 461 menubox_border2_attr); 462 dlg_attrset(data->window, item_attr); /* color the month selection */ 463 mvwprintw(data->window, 0, 0, "%s", nameOfMonth(month - 1)); 464 wmove(data->window, 0, 0); 465 return 0; 466 } 467 468 /* 469 * Draw the year selection box 470 */ 471 static int 472 draw_year(BOX * data, struct tm *current) 473 { 474 int year = current->tm_year + 1900; 475 476 dlg_attrset(data->parent, dialog_attr); /* Headline "Year" */ 477 (void) mvwprintw(data->parent, data->y - 2, data->x - 1, _("Year")); 478 dlg_draw_box2(data->parent, 479 data->y - 1, data->x - 1, 480 data->height + 2, data->width + 2, 481 menubox_attr, 482 menubox_border_attr, 483 menubox_border2_attr); 484 dlg_attrset(data->window, item_attr); /* color the year selection */ 485 mvwprintw(data->window, 0, 0, "%4d", year); 486 wmove(data->window, 0, 0); 487 return 0; 488 } 489 490 static int 491 init_object(BOX * data, 492 WINDOW *parent, 493 int x, int y, 494 int width, int height, 495 BOX_DRAW box_draw, 496 int key_offset, 497 int code) 498 { 499 data->parent = parent; 500 data->x = x; 501 data->y = y; 502 data->width = width; 503 data->height = height; 504 data->box_draw = box_draw; 505 data->week_start = key_offset; 506 507 data->window = dlg_der_window(data->parent, 508 data->height, data->width, 509 data->y, data->x); 510 if (data->window == 0) 511 return -1; 512 513 dlg_mouse_setbase(getbegx(parent), getbegy(parent)); 514 if (code == 'D') { 515 dlg_mouse_mkbigregion(y + 1, x + MON_WIDE, height - 1, width - MON_WIDE, 516 KEY_MAX + key_offset, 1, MON_WIDE, 3); 517 } else { 518 dlg_mouse_mkregion(y, x, height, width, code); 519 } 520 521 return 0; 522 } 523 524 #if defined(ENABLE_NLS) && defined(HAVE_NL_LANGINFO_1STDAY) 525 #elif defined(HAVE_DLG_GAUGE) 526 static int 527 read_locale_setting(const char *name, int which) 528 { 529 FILE *fp; 530 char command[80]; 531 int result = -1; 532 533 sprintf(command, "locale %s", name); 534 if ((fp = dlg_popen(command, "r")) != 0) { 535 int count = 0; 536 char buf[80]; 537 538 while (fgets(buf, (int) sizeof(buf) - 1, fp) != 0) { 539 if (++count > which) { 540 char *next = 0; 541 long check = strtol(buf, &next, 0); 542 if (next != 0 && 543 next != buf && 544 *next == '\n') { 545 result = (int) check; 546 } 547 break; 548 } 549 } 550 pclose(fp); 551 } 552 return result; 553 } 554 #endif 555 556 static int 557 WeekStart(void) 558 { 559 int result = 0; 560 char *option = dialog_vars.week_start; 561 if (option != 0) { 562 if (option[0]) { 563 char *next = 0; 564 long check = strtol(option, &next, 0); 565 if (next == 0 || 566 next == option || 567 *next != '\0') { 568 if (!strcmp(option, "locale")) { 569 #if defined(ENABLE_NLS) && defined(HAVE_NL_LANGINFO_1STDAY) 570 /* 571 * glibc-specific. 572 */ 573 int first_day = nl_langinfo(_NL_TIME_FIRST_WEEKDAY)[0]; 574 char *basis_ptr = nl_langinfo(_NL_TIME_WEEK_1STDAY); 575 int basis_day = (int) (intptr_t) basis_ptr; 576 #elif defined(HAVE_DLG_GAUGE) 577 /* 578 * probably Linux-specific, but harmless otherwise. When 579 * available, the locale program will return a single 580 * integer on one line. 581 */ 582 int first_day = read_locale_setting("first_weekday", 0); 583 int basis_day = read_locale_setting("week-1stday", 0); 584 #endif 585 #if (defined(ENABLE_NLS) && defined(HAVE_NL_LANGINFO_1STDAY)) || defined(HAVE_DLG_GAUGE) 586 int week_1stday = -1; 587 if (basis_day == 19971130) 588 week_1stday = 0; /* Sun */ 589 else if (basis_day == 19971201) 590 week_1stday = 1; /* Mon */ 591 if (week_1stday >= 0) { 592 result = first_day - week_1stday - 1; 593 } 594 #else 595 result = 0; /* Sun */ 596 #endif 597 } else { 598 int day; 599 size_t eql = strlen(option); 600 for (day = 0; day < MAX_DAYS; ++day) { 601 if (!strncmp(nameOfDayOfWeek(day), option, eql)) { 602 result = day; 603 break; 604 } 605 } 606 } 607 } else if (check < 0) { 608 result = -1; 609 } else { 610 result = (int) (check % MAX_DAYS); 611 } 612 } 613 } 614 return result; 615 } 616 617 static int 618 CleanupResult(int code, WINDOW *dialog, char *prompt, DIALOG_VARS * save_vars) 619 { 620 int n; 621 622 if (dialog != 0) 623 dlg_del_window(dialog); 624 dlg_mouse_free_regions(); 625 if (prompt != 0) 626 free(prompt); 627 dlg_restore_vars(save_vars); 628 629 for (n = 0; n < MAX_DAYS; ++n) { 630 free(cached_days[n]); 631 cached_days[n] = 0; 632 } 633 for (n = 0; n < MAX_MONTHS; ++n) { 634 free(cached_months[n]); 635 cached_months[n] = 0; 636 } 637 638 return code; 639 } 640 641 static void 642 trace_date(struct tm *current, struct tm *old) 643 { 644 bool changed = (old == 0 || 645 current->tm_mday != old->tm_mday || 646 current->tm_mon != old->tm_mon || 647 current->tm_year != old->tm_year); 648 if (changed) { 649 DLG_TRACE(("# current %04d/%02d/%02d\n", 650 current->tm_year + 1900, 651 current->tm_mon + 1, 652 current->tm_mday)); 653 } else { 654 DLG_TRACE(("# current (unchanged)\n")); 655 } 656 } 657 658 #define DrawObject(data) (data)->box_draw(data, ¤t) 659 660 /* 661 * Display a dialog box for entering a date 662 */ 663 int 664 dialog_calendar(const char *title, 665 const char *subtitle, 666 int height, 667 int width, 668 int day, 669 int month, 670 int year) 671 { 672 /* *INDENT-OFF* */ 673 static DLG_KEYS_BINDING binding[] = { 674 HELPKEY_BINDINGS, 675 ENTERKEY_BINDINGS, 676 TOGGLEKEY_BINDINGS, 677 DLG_KEYS_DATA( DLGK_FIELD_NEXT, TAB ), 678 DLG_KEYS_DATA( DLGK_FIELD_PREV, KEY_BTAB ), 679 DLG_KEYS_DATA( DLGK_GRID_DOWN, 'j' ), 680 DLG_KEYS_DATA( DLGK_GRID_DOWN, DLGK_MOUSE(KEY_NPAGE) ), 681 DLG_KEYS_DATA( DLGK_GRID_DOWN, KEY_DOWN ), 682 DLG_KEYS_DATA( DLGK_GRID_DOWN, KEY_NPAGE ), 683 DLG_KEYS_DATA( DLGK_GRID_LEFT, '-' ), 684 DLG_KEYS_DATA( DLGK_GRID_LEFT, 'h' ), 685 DLG_KEYS_DATA( DLGK_GRID_LEFT, CHR_BACKSPACE ), 686 DLG_KEYS_DATA( DLGK_GRID_LEFT, CHR_PREVIOUS ), 687 DLG_KEYS_DATA( DLGK_GRID_LEFT, KEY_LEFT ), 688 DLG_KEYS_DATA( DLGK_GRID_RIGHT, '+' ), 689 DLG_KEYS_DATA( DLGK_GRID_RIGHT, 'l' ), 690 DLG_KEYS_DATA( DLGK_GRID_RIGHT, CHR_NEXT ), 691 DLG_KEYS_DATA( DLGK_GRID_RIGHT, KEY_NEXT ), 692 DLG_KEYS_DATA( DLGK_GRID_RIGHT, KEY_RIGHT ), 693 DLG_KEYS_DATA( DLGK_GRID_UP, 'k' ), 694 DLG_KEYS_DATA( DLGK_GRID_UP, KEY_PPAGE ), 695 DLG_KEYS_DATA( DLGK_GRID_UP, KEY_PREVIOUS ), 696 DLG_KEYS_DATA( DLGK_GRID_UP, KEY_UP ), 697 DLG_KEYS_DATA( DLGK_GRID_UP, DLGK_MOUSE(KEY_PPAGE) ), 698 END_KEYS_BINDING 699 }; 700 /* *INDENT-ON* */ 701 702 #ifdef KEY_RESIZE 703 int old_height = height; 704 int old_width = width; 705 #endif 706 BOX dy_box, mn_box, yr_box; 707 int fkey; 708 int key; 709 int step; 710 int button; 711 int result = DLG_EXIT_UNKNOWN; 712 int week_start; 713 WINDOW *dialog; 714 time_t now_time; 715 struct tm current; 716 int state = dlg_default_button(); 717 const char **buttons = dlg_ok_labels(); 718 char *prompt; 719 int mincols = MIN_WIDE; 720 char buffer[MAX_LEN]; 721 DIALOG_VARS save_vars; 722 723 DLG_TRACE(("# calendar args:\n")); 724 DLG_TRACE2S("title", title); 725 DLG_TRACE2S("message", subtitle); 726 DLG_TRACE2N("height", height); 727 DLG_TRACE2N("width", width); 728 DLG_TRACE2N("day", day); 729 DLG_TRACE2N("month", month); 730 DLG_TRACE2N("year", year); 731 732 dlg_save_vars(&save_vars); 733 dialog_vars.separate_output = TRUE; 734 735 dlg_does_output(); 736 737 /* 738 * Unless overridden, the current time/date is our starting point. 739 */ 740 now_time = time((time_t *) 0); 741 current = *localtime(&now_time); 742 743 #ifdef HAVE_MKTIME 744 current.tm_isdst = -1; 745 if (year >= 1900) { 746 current.tm_year = year - 1900; 747 } 748 if (month >= 1) { 749 current.tm_mon = month - 1; 750 } 751 if (day > 0 && day <= days_per_month(current.tm_year + 1900, 752 current.tm_mon)) { 753 current.tm_mday = day; 754 } 755 now_time = mktime(¤t); 756 #else 757 if (day < 0) 758 day = current.tm_mday; 759 if (month < 0) 760 month = current.tm_mon + 1; 761 if (year < 0) 762 year = current.tm_year + 1900; 763 764 /* compute a struct tm that matches the day/month/year parameters */ 765 if (((year -= 1900) > 0) && (year < 200)) { 766 /* ugly, but I'd like to run this on older machines w/o mktime -TD */ 767 for (;;) { 768 if (year > current.tm_year) { 769 now_time += ONE_DAY * days_in_year(¤t, 0); 770 } else if (year < current.tm_year) { 771 now_time -= ONE_DAY * days_in_year(¤t, -1); 772 } else if (month > current.tm_mon + 1) { 773 now_time += ONE_DAY * days_in_month(¤t, 0); 774 } else if (month < current.tm_mon + 1) { 775 now_time -= ONE_DAY * days_in_month(¤t, -1); 776 } else if (day > current.tm_mday) { 777 now_time += ONE_DAY; 778 } else if (day < current.tm_mday) { 779 now_time -= ONE_DAY; 780 } else { 781 break; 782 } 783 current = *localtime(&now_time); 784 } 785 } 786 #endif 787 788 dlg_button_layout(buttons, &mincols); 789 790 #ifdef KEY_RESIZE 791 retry: 792 #endif 793 794 prompt = dlg_strclone(subtitle); 795 dlg_auto_size(title, prompt, &height, &width, 0, mincols); 796 797 height += MIN_HIGH - 1; 798 dlg_print_size(height, width); 799 dlg_ctl_size(height, width); 800 801 dialog = dlg_new_window(height, width, 802 dlg_box_y_ordinate(height), 803 dlg_box_x_ordinate(width)); 804 dlg_register_window(dialog, "calendar", binding); 805 dlg_register_buttons(dialog, "calendar", buttons); 806 807 /* mainbox */ 808 dlg_draw_box2(dialog, 0, 0, height, width, dialog_attr, border_attr, border2_attr); 809 dlg_draw_bottom_box2(dialog, border_attr, border2_attr, dialog_attr); 810 dlg_draw_title(dialog, title); 811 812 dlg_attrset(dialog, dialog_attr); /* text mainbox */ 813 dlg_print_autowrap(dialog, prompt, height, width); 814 815 /* compute positions of day, month and year boxes */ 816 memset(&dy_box, 0, sizeof(dy_box)); 817 memset(&mn_box, 0, sizeof(mn_box)); 818 memset(&yr_box, 0, sizeof(yr_box)); 819 820 if ((week_start = WeekStart()) < 0 || 821 init_object(&dy_box, 822 dialog, 823 (width - DAY_WIDE) / 2, 824 1 + (height - (DAY_HIGH + BTN_HIGH + (5 * MARGIN))), 825 DAY_WIDE, 826 DAY_HIGH + 1, 827 draw_day, 828 week_start, 829 'D') < 0 || 830 ((dy_box.week_start = WeekStart()) < 0) || 831 DrawObject(&dy_box) < 0) { 832 return CleanupResult(DLG_EXIT_ERROR, dialog, prompt, &save_vars); 833 } 834 835 if (init_object(&mn_box, 836 dialog, 837 dy_box.x, 838 dy_box.y - (HDR_HIGH + 2 * MARGIN), 839 (DAY_WIDE / 2) - MARGIN, 840 HDR_HIGH, 841 draw_month, 842 0, 843 'M') < 0 844 || DrawObject(&mn_box) < 0) { 845 return CleanupResult(DLG_EXIT_ERROR, dialog, prompt, &save_vars); 846 } 847 848 if (init_object(&yr_box, 849 dialog, 850 dy_box.x + mn_box.width + 2, 851 mn_box.y, 852 mn_box.width, 853 mn_box.height, 854 draw_year, 855 0, 856 'Y') < 0 857 || DrawObject(&yr_box) < 0) { 858 return CleanupResult(DLG_EXIT_ERROR, dialog, prompt, &save_vars); 859 } 860 861 dlg_trace_win(dialog); 862 while (result == DLG_EXIT_UNKNOWN) { 863 int key2; 864 BOX *obj = (state == sDAY ? &dy_box 865 : (state == sMONTH ? &mn_box : 866 (state == sYEAR ? &yr_box : 0))); 867 868 button = (state < 0) ? 0 : state; 869 dlg_draw_buttons(dialog, height - 2, 0, buttons, button, FALSE, width); 870 if (obj != 0) 871 dlg_set_focus(dialog, obj->window); 872 873 key = dlg_mouse_wgetch(dialog, &fkey); 874 if (dlg_result_key(key, fkey, &result)) { 875 if (!dlg_button_key(result, &button, &key, &fkey)) 876 break; 877 } 878 #define Mouse2Key(key) (key - M_EVENT) 879 if (fkey && (key >= DLGK_MOUSE(KEY_MIN) && key <= DLGK_MOUSE(KEY_MAX))) { 880 key = dlg_lookup_key(dialog, Mouse2Key(key), &fkey); 881 } 882 883 if ((key2 = dlg_char_to_button(key, buttons)) >= 0) { 884 result = key2; 885 } else if (fkey) { 886 /* handle function-keys */ 887 switch (key) { 888 case DLGK_MOUSE('D'): 889 state = sDAY; 890 break; 891 case DLGK_MOUSE('M'): 892 state = sMONTH; 893 break; 894 case DLGK_MOUSE('Y'): 895 state = sYEAR; 896 break; 897 case DLGK_TOGGLE: 898 case DLGK_ENTER: 899 result = dlg_enter_buttoncode(button); 900 break; 901 case DLGK_LEAVE: 902 result = dlg_ok_buttoncode(button); 903 break; 904 case DLGK_FIELD_PREV: 905 state = dlg_prev_ok_buttonindex(state, sMONTH); 906 break; 907 case DLGK_FIELD_NEXT: 908 state = dlg_next_ok_buttonindex(state, sMONTH); 909 break; 910 #ifdef KEY_RESIZE 911 case KEY_RESIZE: 912 dlg_will_resize(dialog); 913 /* reset data */ 914 height = old_height; 915 width = old_width; 916 free(prompt); 917 _dlg_resize_cleanup(dialog); 918 /* repaint */ 919 goto retry; 920 #endif 921 default: 922 step = 0; 923 key2 = -1; 924 if (is_DLGK_MOUSE(key)) { 925 if ((key2 = dlg_ok_buttoncode(Mouse2Key(key))) >= 0) { 926 result = key2; 927 break; 928 } else if (key >= DLGK_MOUSE(KEY_MAX)) { 929 state = sDAY; 930 obj = &dy_box; 931 key2 = 1; 932 step = (key 933 - DLGK_MOUSE(KEY_MAX) 934 - day_cell_number(¤t)); 935 DLG_TRACE(("# mouseclick decoded %d\n", step)); 936 } 937 } 938 if (obj != 0) { 939 if (key2 < 0) { 940 step = next_or_previous(key, (obj == &dy_box)); 941 } 942 if (step != 0) { 943 struct tm old = current; 944 945 /* see comment regarding mktime -TD */ 946 if (obj == &dy_box) { 947 now_time += ONE_DAY * step; 948 } else if (obj == &mn_box) { 949 if (step > 0) 950 now_time += ONE_DAY * 951 days_in_month(¤t, 0); 952 else 953 now_time -= ONE_DAY * 954 days_in_month(¤t, -1); 955 } else if (obj == &yr_box) { 956 if (step > 0) 957 now_time += (ONE_DAY 958 * days_in_year(¤t, 0)); 959 else 960 now_time -= (ONE_DAY 961 * days_in_year(¤t, -1)); 962 } 963 964 current = *localtime(&now_time); 965 966 trace_date(¤t, &old); 967 if (obj != &dy_box 968 && (current.tm_mday != old.tm_mday 969 || current.tm_mon != old.tm_mon 970 || current.tm_year != old.tm_year)) 971 DrawObject(&dy_box); 972 if (obj != &mn_box && current.tm_mon != old.tm_mon) 973 DrawObject(&mn_box); 974 if (obj != &yr_box && current.tm_year != old.tm_year) 975 DrawObject(&yr_box); 976 (void) DrawObject(obj); 977 } 978 } else if (state >= 0) { 979 if (next_or_previous(key, FALSE) < 0) 980 state = dlg_prev_ok_buttonindex(state, sMONTH); 981 else if (next_or_previous(key, FALSE) > 0) 982 state = dlg_next_ok_buttonindex(state, sMONTH); 983 } 984 break; 985 } 986 } 987 } 988 989 #define DefaultFormat(dst, src) \ 990 sprintf(dst, "%02d/%02d/%0d", \ 991 src.tm_mday, src.tm_mon + 1, src.tm_year + 1900) 992 #ifdef HAVE_STRFTIME 993 if (dialog_vars.date_format != 0) { 994 size_t used = strftime(buffer, 995 sizeof(buffer) - 1, 996 dialog_vars.date_format, 997 ¤t); 998 if (used == 0 || *buffer == '\0') 999 DefaultFormat(buffer, current); 1000 } else 1001 #endif 1002 DefaultFormat(buffer, current); 1003 1004 dlg_add_result(buffer); 1005 AddLastKey(); 1006 1007 return CleanupResult(result, dialog, prompt, &save_vars); 1008 } 1009