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