1 /* 2 * $Id: calendar.c,v 1.97 2018/06/19 22:57:01 tom Exp $ 3 * 4 * calendar.c -- implements the calendar box 5 * 6 * Copyright 2001-2017,2018 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 <dialog.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 static const char *posix_days[MAX_DAYS] = 79 { 80 "Sunday", 81 "Monday", 82 "Tuesday", 83 "Wednesday", 84 "Thursday", 85 "Friday", 86 "Saturday" 87 }; 88 89 while (n < 0) { 90 n += MAX_DAYS; 91 } 92 n %= MAX_DAYS; 93 #ifdef ENABLE_NLS 94 if (cached_days[n] == 0) { 95 const nl_item items[MAX_DAYS] = 96 { 97 ABDAY_1, ABDAY_2, ABDAY_3, ABDAY_4, ABDAY_5, ABDAY_6, ABDAY_7 98 }; 99 cached_days[n] = dlg_strclone(nl_langinfo(items[n])); 100 memset(shown, 0, sizeof(shown)); 101 } 102 #endif 103 if (cached_days[n] == 0) { 104 size_t len, 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 ((len = 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 static const char *posix_mons[MAX_MONTHS] = 130 { 131 "January", 132 "February", 133 "March", 134 "April", 135 "May", 136 "June", 137 "July", 138 "August", 139 "September", 140 "October", 141 "November", 142 "December" 143 }; 144 145 while (n < 0) { 146 n += MAX_MONTHS; 147 } 148 n %= MAX_MONTHS; 149 #ifdef ENABLE_NLS 150 if (cached_months[n] == 0) { 151 const nl_item items[MAX_MONTHS] = 152 { 153 MON_1, MON_2, MON_3, MON_4, MON_5, MON_6, 154 MON_7, MON_8, MON_9, MON_10, MON_11, MON_12 155 }; 156 cached_months[n] = dlg_strclone(nl_langinfo(items[n])); 157 memset(shown, 0, sizeof(shown)); 158 } 159 #endif 160 if (cached_months[n] == 0) { 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 ++year; 305 week = 1; 306 } 307 return week; 308 } 309 310 static int * 311 getisoweeks(int year, int month) 312 { 313 static int result[10]; 314 int windx = 0; 315 int day; 316 int dpm = days_per_month(year, month); 317 318 for (day = 1; day <= dpm; day += MAX_DAYS) 319 result[windx++] = iso_week(year, month, day); 320 /* 321 * Ensure that there is a week number associated with the last day of the 322 * month, e.g., in case the last day of the month falls before Thursday, 323 * so that we have to show the week-number for the beginning of the 324 * following month. 325 */ 326 result[windx] = iso_week(year, month, dpm); 327 return result; 328 } 329 330 static int 331 day_cell_number(struct tm *current) 332 { 333 int cell; 334 cell = current->tm_mday - ((6 + current->tm_mday - current->tm_wday) % MAX_DAYS); 335 if ((current->tm_mday - 1) % MAX_DAYS != current->tm_wday) 336 cell += 6; 337 else 338 cell--; 339 return cell; 340 } 341 342 static int 343 next_or_previous(int key, int two_d) 344 { 345 int result = 0; 346 347 switch (key) { 348 case DLGK_GRID_UP: 349 result = two_d ? -MAX_DAYS : -1; 350 break; 351 case DLGK_GRID_LEFT: 352 result = -1; 353 break; 354 case DLGK_GRID_DOWN: 355 result = two_d ? MAX_DAYS : 1; 356 break; 357 case DLGK_GRID_RIGHT: 358 result = 1; 359 break; 360 default: 361 beep(); 362 break; 363 } 364 return result; 365 } 366 367 /* 368 * Draw the day-of-month selection box 369 */ 370 static int 371 draw_day(BOX * data, struct tm *current) 372 { 373 int cell_wide = MON_WIDE; 374 int y, x, this_x = 0; 375 int save_y = 0, save_x = 0; 376 int day = current->tm_mday; 377 int mday; 378 int week = 0; 379 int windx = 0; 380 int *weeks = 0; 381 int last = days_in_month(current, 0); 382 int prev = days_in_month(current, -1); 383 384 werase(data->window); 385 dlg_draw_box2(data->parent, 386 data->y - MARGIN, data->x - MARGIN, 387 data->height + (2 * MARGIN), data->width + (2 * MARGIN), 388 menubox_attr, 389 menubox_border_attr, 390 menubox_border2_attr); 391 392 dlg_attrset(data->window, menubox_attr); /* daynames headline */ 393 for (x = 0; x < MAX_DAYS; x++) { 394 mvwprintw(data->window, 395 0, (x + 1) * cell_wide, "%*.*s ", 396 cell_wide - 1, 397 cell_wide - 1, 398 nameOfDayOfWeek(x + data->week_start)); 399 } 400 401 mday = ((6 + current->tm_mday - 402 current->tm_wday + 403 data->week_start) % MAX_DAYS) - MAX_DAYS; 404 if (mday <= -MAX_DAYS) 405 mday += MAX_DAYS; 406 407 if (dialog_vars.iso_week) { 408 weeks = getisoweeks(current->tm_year + 1900, current->tm_mon + 1); 409 } else { 410 /* mday is now in the range -6 to 0. */ 411 week = (current->tm_yday + 6 + mday - current->tm_mday) / MAX_DAYS; 412 } 413 414 for (y = 1; mday < last; y++) { 415 dlg_attrset(data->window, menubox_attr); /* weeknumbers headline */ 416 mvwprintw(data->window, 417 y, 0, 418 "%*d ", 419 cell_wide - 1, 420 weeks ? weeks[windx++] : ++week); 421 for (x = 0; x < MAX_DAYS; x++) { 422 this_x = 1 + (x + 1) * cell_wide; 423 ++mday; 424 if (wmove(data->window, y, this_x) == ERR) 425 continue; 426 dlg_attrset(data->window, item_attr); /* not selected days */ 427 if (mday == day) { 428 dlg_attrset(data->window, item_selected_attr); /* selected day */ 429 save_y = y; 430 save_x = this_x; 431 } 432 if (mday > 0) { 433 if (mday <= last) { 434 wprintw(data->window, "%*d", cell_wide - 2, mday); 435 } else if (mday == day) { 436 wprintw(data->window, "%*d", cell_wide - 2, mday - last); 437 } 438 } else if (mday == day) { 439 wprintw(data->window, "%*d", cell_wide - 2, mday + prev); 440 } 441 } 442 wmove(data->window, save_y, save_x); 443 } 444 /* just draw arrows - scrollbar is unsuitable here */ 445 dlg_draw_arrows(data->parent, TRUE, TRUE, 446 data->x + ARROWS_COL, 447 data->y - 1, 448 data->y + data->height); 449 450 return 0; 451 } 452 453 /* 454 * Draw the month-of-year selection box 455 */ 456 static int 457 draw_month(BOX * data, struct tm *current) 458 { 459 int month; 460 461 month = current->tm_mon + 1; 462 463 dlg_attrset(data->parent, dialog_attr); /* Headline "Month" */ 464 (void) mvwprintw(data->parent, data->y - 2, data->x - 1, _("Month")); 465 dlg_draw_box2(data->parent, 466 data->y - 1, data->x - 1, 467 data->height + 2, data->width + 2, 468 menubox_attr, 469 menubox_border_attr, 470 menubox_border2_attr); 471 dlg_attrset(data->window, item_attr); /* color the month selection */ 472 mvwprintw(data->window, 0, 0, "%s", nameOfMonth(month - 1)); 473 wmove(data->window, 0, 0); 474 return 0; 475 } 476 477 /* 478 * Draw the year selection box 479 */ 480 static int 481 draw_year(BOX * data, struct tm *current) 482 { 483 int year = current->tm_year + 1900; 484 485 dlg_attrset(data->parent, dialog_attr); /* Headline "Year" */ 486 (void) mvwprintw(data->parent, data->y - 2, data->x - 1, _("Year")); 487 dlg_draw_box2(data->parent, 488 data->y - 1, data->x - 1, 489 data->height + 2, data->width + 2, 490 menubox_attr, 491 menubox_border_attr, 492 menubox_border2_attr); 493 dlg_attrset(data->window, item_attr); /* color the year selection */ 494 mvwprintw(data->window, 0, 0, "%4d", year); 495 wmove(data->window, 0, 0); 496 return 0; 497 } 498 499 static int 500 init_object(BOX * data, 501 WINDOW *parent, 502 int x, int y, 503 int width, int height, 504 BOX_DRAW box_draw, 505 int key_offset, 506 int code) 507 { 508 data->parent = parent; 509 data->x = x; 510 data->y = y; 511 data->width = width; 512 data->height = height; 513 data->box_draw = box_draw; 514 data->week_start = key_offset; 515 516 data->window = derwin(data->parent, 517 data->height, data->width, 518 data->y, data->x); 519 if (data->window == 0) 520 return -1; 521 (void) keypad(data->window, TRUE); 522 523 dlg_mouse_setbase(getbegx(parent), getbegy(parent)); 524 if (code == 'D') { 525 dlg_mouse_mkbigregion(y + 1, x + MON_WIDE, height - 1, width - MON_WIDE, 526 KEY_MAX + key_offset, 1, MON_WIDE, 3); 527 } else { 528 dlg_mouse_mkregion(y, x, height, width, code); 529 } 530 531 return 0; 532 } 533 534 #if defined(ENABLE_NLS) && defined(HAVE_NL_LANGINFO_1STDAY) 535 #elif defined(HAVE_DLG_GAUGE) 536 static int 537 read_locale_setting(const char *name, int which) 538 { 539 FILE *fp; 540 char command[80]; 541 int result = -1; 542 543 sprintf(command, "locale %s", name); 544 if ((fp = dlg_popen(command, "r")) != 0) { 545 int count = 0; 546 char buf[80]; 547 548 while (fgets(buf, (int) sizeof(buf) - 1, fp) != 0) { 549 if (++count > which) { 550 char *next = 0; 551 long check = strtol(buf, &next, 0); 552 if (next != 0 && 553 next != buf && 554 *next == '\n') { 555 result = (int) check; 556 } 557 break; 558 } 559 } 560 pclose(fp); 561 } 562 return result; 563 } 564 #endif 565 566 static int 567 WeekStart(void) 568 { 569 int result = 0; 570 char *option = dialog_vars.week_start; 571 if (option != 0) { 572 if (option[0]) { 573 char *next = 0; 574 long check = strtol(option, &next, 0); 575 if (next == 0 || 576 next == option || 577 *next != '\0') { 578 if (!strcmp(option, "locale")) { 579 #if defined(ENABLE_NLS) && defined(HAVE_NL_LANGINFO_1STDAY) 580 /* 581 * glibc-specific. 582 */ 583 int first_day = nl_langinfo(_NL_TIME_FIRST_WEEKDAY)[0]; 584 char *basis_ptr = nl_langinfo(_NL_TIME_WEEK_1STDAY); 585 int basis_day = (int) (intptr_t) basis_ptr; 586 #elif defined(HAVE_DLG_GAUGE) 587 /* 588 * probably Linux-specific, but harmless otherwise. When 589 * available, the locale program will return a single 590 * integer on one line. 591 */ 592 int first_day = read_locale_setting("first_weekday", 0); 593 int basis_day = read_locale_setting("week-1stday", 0); 594 #endif 595 #if (defined(ENABLE_NLS) && defined(HAVE_NL_LANGINFO_1STDAY)) || defined(HAVE_DLG_GAUGE) 596 int week_1stday = -1; 597 if (basis_day == 19971130) 598 week_1stday = 0; /* Sun */ 599 else if (basis_day == 19971201) 600 week_1stday = 1; /* Mon */ 601 if (week_1stday >= 0) { 602 result = first_day - week_1stday - 1; 603 } 604 #else 605 result = 0; /* Sun */ 606 #endif 607 } else { 608 int day; 609 size_t eql = strlen(option); 610 for (day = 0; day < MAX_DAYS; ++day) { 611 if (!strncmp(nameOfDayOfWeek(day), option, eql)) { 612 result = day; 613 break; 614 } 615 } 616 } 617 } else if (check < 0) { 618 result = -1; 619 } else { 620 result = (int) (check % MAX_DAYS); 621 } 622 } 623 } 624 return result; 625 } 626 627 static int 628 CleanupResult(int code, WINDOW *dialog, char *prompt, DIALOG_VARS * save_vars) 629 { 630 int n; 631 632 if (dialog != 0) 633 dlg_del_window(dialog); 634 dlg_mouse_free_regions(); 635 if (prompt != 0) 636 free(prompt); 637 dlg_restore_vars(save_vars); 638 639 for (n = 0; n < MAX_DAYS; ++n) { 640 free(cached_days[n]); 641 cached_days[n] = 0; 642 } 643 for (n = 0; n < MAX_MONTHS; ++n) { 644 free(cached_months[n]); 645 cached_months[n] = 0; 646 } 647 648 return code; 649 } 650 651 static void 652 trace_date(struct tm *current, struct tm *old) 653 { 654 bool changed = (old == 0 || 655 current->tm_mday != old->tm_mday || 656 current->tm_mon != old->tm_mon || 657 current->tm_year != old->tm_year); 658 if (changed) { 659 DLG_TRACE(("# current %04d/%02d/%02d\n", 660 current->tm_year + 1900, 661 current->tm_mon + 1, 662 current->tm_mday)); 663 } else { 664 DLG_TRACE(("# current (unchanged)\n")); 665 } 666 } 667 668 #define DrawObject(data) (data)->box_draw(data, ¤t) 669 670 /* 671 * Display a dialog box for entering a date 672 */ 673 int 674 dialog_calendar(const char *title, 675 const char *subtitle, 676 int height, 677 int width, 678 int day, 679 int month, 680 int year) 681 { 682 /* *INDENT-OFF* */ 683 static DLG_KEYS_BINDING binding[] = { 684 HELPKEY_BINDINGS, 685 ENTERKEY_BINDINGS, 686 TOGGLEKEY_BINDINGS, 687 DLG_KEYS_DATA( DLGK_FIELD_NEXT, TAB ), 688 DLG_KEYS_DATA( DLGK_FIELD_PREV, KEY_BTAB ), 689 DLG_KEYS_DATA( DLGK_GRID_DOWN, 'j' ), 690 DLG_KEYS_DATA( DLGK_GRID_DOWN, DLGK_MOUSE(KEY_NPAGE) ), 691 DLG_KEYS_DATA( DLGK_GRID_DOWN, KEY_DOWN ), 692 DLG_KEYS_DATA( DLGK_GRID_DOWN, KEY_NPAGE ), 693 DLG_KEYS_DATA( DLGK_GRID_LEFT, '-' ), 694 DLG_KEYS_DATA( DLGK_GRID_LEFT, 'h' ), 695 DLG_KEYS_DATA( DLGK_GRID_LEFT, CHR_BACKSPACE ), 696 DLG_KEYS_DATA( DLGK_GRID_LEFT, CHR_PREVIOUS ), 697 DLG_KEYS_DATA( DLGK_GRID_LEFT, KEY_LEFT ), 698 DLG_KEYS_DATA( DLGK_GRID_RIGHT, '+' ), 699 DLG_KEYS_DATA( DLGK_GRID_RIGHT, 'l' ), 700 DLG_KEYS_DATA( DLGK_GRID_RIGHT, CHR_NEXT ), 701 DLG_KEYS_DATA( DLGK_GRID_RIGHT, KEY_NEXT ), 702 DLG_KEYS_DATA( DLGK_GRID_RIGHT, KEY_RIGHT ), 703 DLG_KEYS_DATA( DLGK_GRID_UP, 'k' ), 704 DLG_KEYS_DATA( DLGK_GRID_UP, KEY_PPAGE ), 705 DLG_KEYS_DATA( DLGK_GRID_UP, KEY_PREVIOUS ), 706 DLG_KEYS_DATA( DLGK_GRID_UP, KEY_UP ), 707 DLG_KEYS_DATA( DLGK_GRID_UP, DLGK_MOUSE(KEY_PPAGE) ), 708 END_KEYS_BINDING 709 }; 710 /* *INDENT-ON* */ 711 712 #ifdef KEY_RESIZE 713 int old_height = height; 714 int old_width = width; 715 #endif 716 BOX dy_box, mn_box, yr_box; 717 int fkey; 718 int key = 0; 719 int key2; 720 int step; 721 int button; 722 int result = DLG_EXIT_UNKNOWN; 723 int week_start; 724 WINDOW *dialog; 725 time_t now_time = time((time_t *) 0); 726 struct tm current; 727 int state = dlg_default_button(); 728 const char **buttons = dlg_ok_labels(); 729 char *prompt; 730 int mincols = MIN_WIDE; 731 char buffer[MAX_LEN]; 732 DIALOG_VARS save_vars; 733 734 DLG_TRACE(("# calendar args:\n")); 735 DLG_TRACE2S("title", title); 736 DLG_TRACE2S("message", subtitle); 737 DLG_TRACE2N("height", height); 738 DLG_TRACE2N("width", width); 739 DLG_TRACE2N("day", day); 740 DLG_TRACE2N("month", month); 741 DLG_TRACE2N("year", year); 742 743 dlg_save_vars(&save_vars); 744 dialog_vars.separate_output = TRUE; 745 746 dlg_does_output(); 747 748 /* 749 * Unless overrridden, the current time/date is our starting point. 750 */ 751 now_time = time((time_t *) 0); 752 current = *localtime(&now_time); 753 754 #if HAVE_MKTIME 755 current.tm_isdst = -1; 756 if (year >= 1900) { 757 current.tm_year = year - 1900; 758 } 759 if (month >= 1) { 760 current.tm_mon = month - 1; 761 } 762 if (day > 0 && day <= days_per_month(current.tm_year + 1900, 763 current.tm_mon + 1)) { 764 current.tm_mday = day; 765 } 766 now_time = mktime(¤t); 767 #else 768 if (day < 0) 769 day = current.tm_mday; 770 if (month < 0) 771 month = current.tm_mon + 1; 772 if (year < 0) 773 year = current.tm_year + 1900; 774 775 /* compute a struct tm that matches the day/month/year parameters */ 776 if (((year -= 1900) > 0) && (year < 200)) { 777 /* ugly, but I'd like to run this on older machines w/o mktime -TD */ 778 for (;;) { 779 if (year > current.tm_year) { 780 now_time += ONE_DAY * days_in_year(¤t, 0); 781 } else if (year < current.tm_year) { 782 now_time -= ONE_DAY * days_in_year(¤t, -1); 783 } else if (month > current.tm_mon + 1) { 784 now_time += ONE_DAY * days_in_month(¤t, 0); 785 } else if (month < current.tm_mon + 1) { 786 now_time -= ONE_DAY * days_in_month(¤t, -1); 787 } else if (day > current.tm_mday) { 788 now_time += ONE_DAY; 789 } else if (day < current.tm_mday) { 790 now_time -= ONE_DAY; 791 } else { 792 break; 793 } 794 current = *localtime(&now_time); 795 } 796 } 797 #endif 798 799 dlg_button_layout(buttons, &mincols); 800 801 #ifdef KEY_RESIZE 802 retry: 803 #endif 804 805 prompt = dlg_strclone(subtitle); 806 dlg_auto_size(title, prompt, &height, &width, 0, mincols); 807 808 height += MIN_HIGH - 1; 809 dlg_print_size(height, width); 810 dlg_ctl_size(height, width); 811 812 dialog = dlg_new_window(height, width, 813 dlg_box_y_ordinate(height), 814 dlg_box_x_ordinate(width)); 815 dlg_register_window(dialog, "calendar", binding); 816 dlg_register_buttons(dialog, "calendar", buttons); 817 818 /* mainbox */ 819 dlg_draw_box2(dialog, 0, 0, height, width, dialog_attr, border_attr, border2_attr); 820 dlg_draw_bottom_box2(dialog, border_attr, border2_attr, dialog_attr); 821 dlg_draw_title(dialog, title); 822 823 dlg_attrset(dialog, dialog_attr); /* text mainbox */ 824 dlg_print_autowrap(dialog, prompt, height, width); 825 826 /* compute positions of day, month and year boxes */ 827 memset(&dy_box, 0, sizeof(dy_box)); 828 memset(&mn_box, 0, sizeof(mn_box)); 829 memset(&yr_box, 0, sizeof(yr_box)); 830 831 if ((week_start = WeekStart()) < 0 || 832 init_object(&dy_box, 833 dialog, 834 (width - DAY_WIDE) / 2, 835 1 + (height - (DAY_HIGH + BTN_HIGH + (5 * MARGIN))), 836 DAY_WIDE, 837 DAY_HIGH + 1, 838 draw_day, 839 week_start, 840 'D') < 0 || 841 ((dy_box.week_start = WeekStart()) < 0) || 842 DrawObject(&dy_box) < 0) { 843 return CleanupResult(DLG_EXIT_ERROR, dialog, prompt, &save_vars); 844 } 845 846 if (init_object(&mn_box, 847 dialog, 848 dy_box.x, 849 dy_box.y - (HDR_HIGH + 2 * MARGIN), 850 (DAY_WIDE / 2) - MARGIN, 851 HDR_HIGH, 852 draw_month, 853 0, 854 'M') < 0 855 || DrawObject(&mn_box) < 0) { 856 return CleanupResult(DLG_EXIT_ERROR, dialog, prompt, &save_vars); 857 } 858 859 if (init_object(&yr_box, 860 dialog, 861 dy_box.x + mn_box.width + 2, 862 mn_box.y, 863 mn_box.width, 864 mn_box.height, 865 draw_year, 866 0, 867 'Y') < 0 868 || DrawObject(&yr_box) < 0) { 869 return CleanupResult(DLG_EXIT_ERROR, dialog, prompt, &save_vars); 870 } 871 872 dlg_trace_win(dialog); 873 while (result == DLG_EXIT_UNKNOWN) { 874 BOX *obj = (state == sDAY ? &dy_box 875 : (state == sMONTH ? &mn_box : 876 (state == sYEAR ? &yr_box : 0))); 877 878 button = (state < 0) ? 0 : state; 879 dlg_draw_buttons(dialog, height - 2, 0, buttons, button, FALSE, width); 880 if (obj != 0) 881 dlg_set_focus(dialog, obj->window); 882 883 key = dlg_mouse_wgetch(dialog, &fkey); 884 if (dlg_result_key(key, fkey, &result)) 885 break; 886 887 #define Mouse2Key(key) (key - M_EVENT) 888 if (fkey && (key >= DLGK_MOUSE(KEY_MIN) && key <= DLGK_MOUSE(KEY_MAX))) { 889 key = dlg_lookup_key(dialog, Mouse2Key(key), &fkey); 890 } 891 892 if ((key2 = dlg_char_to_button(key, buttons)) >= 0) { 893 result = key2; 894 } else if (fkey) { 895 /* handle function-keys */ 896 switch (key) { 897 case DLGK_MOUSE('D'): 898 state = sDAY; 899 break; 900 case DLGK_MOUSE('M'): 901 state = sMONTH; 902 break; 903 case DLGK_MOUSE('Y'): 904 state = sYEAR; 905 break; 906 case DLGK_TOGGLE: 907 case DLGK_ENTER: 908 result = dlg_enter_buttoncode(button); 909 break; 910 case DLGK_FIELD_PREV: 911 state = dlg_prev_ok_buttonindex(state, sMONTH); 912 break; 913 case DLGK_FIELD_NEXT: 914 state = dlg_next_ok_buttonindex(state, sMONTH); 915 break; 916 #ifdef KEY_RESIZE 917 case KEY_RESIZE: 918 dlg_will_resize(dialog); 919 /* reset data */ 920 height = old_height; 921 width = old_width; 922 free(prompt); 923 dlg_clear(); 924 dlg_del_window(dialog); 925 dlg_mouse_free_regions(); 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 dlg_add_separator(); 1014 dlg_add_last_key(-1); 1015 1016 return CleanupResult(result, dialog, prompt, &save_vars); 1017 } 1018