1 /* 2 * $Id: calendar.c,v 1.104 2020/03/27 20:20:18 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 = derwin(data->parent, 516 data->height, data->width, 517 data->y, data->x); 518 if (data->window == 0) 519 return -1; 520 (void) keypad(data->window, TRUE); 521 522 dlg_mouse_setbase(getbegx(parent), getbegy(parent)); 523 if (code == 'D') { 524 dlg_mouse_mkbigregion(y + 1, x + MON_WIDE, height - 1, width - MON_WIDE, 525 KEY_MAX + key_offset, 1, MON_WIDE, 3); 526 } else { 527 dlg_mouse_mkregion(y, x, height, width, code); 528 } 529 530 return 0; 531 } 532 533 #if defined(ENABLE_NLS) && defined(HAVE_NL_LANGINFO_1STDAY) 534 #elif defined(HAVE_DLG_GAUGE) 535 static int 536 read_locale_setting(const char *name, int which) 537 { 538 FILE *fp; 539 char command[80]; 540 int result = -1; 541 542 sprintf(command, "locale %s", name); 543 if ((fp = dlg_popen(command, "r")) != 0) { 544 int count = 0; 545 char buf[80]; 546 547 while (fgets(buf, (int) sizeof(buf) - 1, fp) != 0) { 548 if (++count > which) { 549 char *next = 0; 550 long check = strtol(buf, &next, 0); 551 if (next != 0 && 552 next != buf && 553 *next == '\n') { 554 result = (int) check; 555 } 556 break; 557 } 558 } 559 pclose(fp); 560 } 561 return result; 562 } 563 #endif 564 565 static int 566 WeekStart(void) 567 { 568 int result = 0; 569 char *option = dialog_vars.week_start; 570 if (option != 0) { 571 if (option[0]) { 572 char *next = 0; 573 long check = strtol(option, &next, 0); 574 if (next == 0 || 575 next == option || 576 *next != '\0') { 577 if (!strcmp(option, "locale")) { 578 #if defined(ENABLE_NLS) && defined(HAVE_NL_LANGINFO_1STDAY) 579 /* 580 * glibc-specific. 581 */ 582 int first_day = nl_langinfo(_NL_TIME_FIRST_WEEKDAY)[0]; 583 char *basis_ptr = nl_langinfo(_NL_TIME_WEEK_1STDAY); 584 int basis_day = (int) (intptr_t) basis_ptr; 585 #elif defined(HAVE_DLG_GAUGE) 586 /* 587 * probably Linux-specific, but harmless otherwise. When 588 * available, the locale program will return a single 589 * integer on one line. 590 */ 591 int first_day = read_locale_setting("first_weekday", 0); 592 int basis_day = read_locale_setting("week-1stday", 0); 593 #endif 594 #if (defined(ENABLE_NLS) && defined(HAVE_NL_LANGINFO_1STDAY)) || defined(HAVE_DLG_GAUGE) 595 int week_1stday = -1; 596 if (basis_day == 19971130) 597 week_1stday = 0; /* Sun */ 598 else if (basis_day == 19971201) 599 week_1stday = 1; /* Mon */ 600 if (week_1stday >= 0) { 601 result = first_day - week_1stday - 1; 602 } 603 #else 604 result = 0; /* Sun */ 605 #endif 606 } else { 607 int day; 608 size_t eql = strlen(option); 609 for (day = 0; day < MAX_DAYS; ++day) { 610 if (!strncmp(nameOfDayOfWeek(day), option, eql)) { 611 result = day; 612 break; 613 } 614 } 615 } 616 } else if (check < 0) { 617 result = -1; 618 } else { 619 result = (int) (check % MAX_DAYS); 620 } 621 } 622 } 623 return result; 624 } 625 626 static int 627 CleanupResult(int code, WINDOW *dialog, char *prompt, DIALOG_VARS * save_vars) 628 { 629 int n; 630 631 if (dialog != 0) 632 dlg_del_window(dialog); 633 dlg_mouse_free_regions(); 634 if (prompt != 0) 635 free(prompt); 636 dlg_restore_vars(save_vars); 637 638 for (n = 0; n < MAX_DAYS; ++n) { 639 free(cached_days[n]); 640 cached_days[n] = 0; 641 } 642 for (n = 0; n < MAX_MONTHS; ++n) { 643 free(cached_months[n]); 644 cached_months[n] = 0; 645 } 646 647 return code; 648 } 649 650 static void 651 trace_date(struct tm *current, struct tm *old) 652 { 653 bool changed = (old == 0 || 654 current->tm_mday != old->tm_mday || 655 current->tm_mon != old->tm_mon || 656 current->tm_year != old->tm_year); 657 if (changed) { 658 DLG_TRACE(("# current %04d/%02d/%02d\n", 659 current->tm_year + 1900, 660 current->tm_mon + 1, 661 current->tm_mday)); 662 } else { 663 DLG_TRACE(("# current (unchanged)\n")); 664 } 665 } 666 667 #define DrawObject(data) (data)->box_draw(data, ¤t) 668 669 /* 670 * Display a dialog box for entering a date 671 */ 672 int 673 dialog_calendar(const char *title, 674 const char *subtitle, 675 int height, 676 int width, 677 int day, 678 int month, 679 int year) 680 { 681 /* *INDENT-OFF* */ 682 static DLG_KEYS_BINDING binding[] = { 683 HELPKEY_BINDINGS, 684 ENTERKEY_BINDINGS, 685 TOGGLEKEY_BINDINGS, 686 DLG_KEYS_DATA( DLGK_FIELD_NEXT, TAB ), 687 DLG_KEYS_DATA( DLGK_FIELD_PREV, KEY_BTAB ), 688 DLG_KEYS_DATA( DLGK_GRID_DOWN, 'j' ), 689 DLG_KEYS_DATA( DLGK_GRID_DOWN, DLGK_MOUSE(KEY_NPAGE) ), 690 DLG_KEYS_DATA( DLGK_GRID_DOWN, KEY_DOWN ), 691 DLG_KEYS_DATA( DLGK_GRID_DOWN, KEY_NPAGE ), 692 DLG_KEYS_DATA( DLGK_GRID_LEFT, '-' ), 693 DLG_KEYS_DATA( DLGK_GRID_LEFT, 'h' ), 694 DLG_KEYS_DATA( DLGK_GRID_LEFT, CHR_BACKSPACE ), 695 DLG_KEYS_DATA( DLGK_GRID_LEFT, CHR_PREVIOUS ), 696 DLG_KEYS_DATA( DLGK_GRID_LEFT, KEY_LEFT ), 697 DLG_KEYS_DATA( DLGK_GRID_RIGHT, '+' ), 698 DLG_KEYS_DATA( DLGK_GRID_RIGHT, 'l' ), 699 DLG_KEYS_DATA( DLGK_GRID_RIGHT, CHR_NEXT ), 700 DLG_KEYS_DATA( DLGK_GRID_RIGHT, KEY_NEXT ), 701 DLG_KEYS_DATA( DLGK_GRID_RIGHT, KEY_RIGHT ), 702 DLG_KEYS_DATA( DLGK_GRID_UP, 'k' ), 703 DLG_KEYS_DATA( DLGK_GRID_UP, KEY_PPAGE ), 704 DLG_KEYS_DATA( DLGK_GRID_UP, KEY_PREVIOUS ), 705 DLG_KEYS_DATA( DLGK_GRID_UP, KEY_UP ), 706 DLG_KEYS_DATA( DLGK_GRID_UP, DLGK_MOUSE(KEY_PPAGE) ), 707 END_KEYS_BINDING 708 }; 709 /* *INDENT-ON* */ 710 711 #ifdef KEY_RESIZE 712 int old_height = height; 713 int old_width = width; 714 #endif 715 BOX dy_box, mn_box, yr_box; 716 int fkey; 717 int key; 718 int step; 719 int button; 720 int result = DLG_EXIT_UNKNOWN; 721 int week_start; 722 WINDOW *dialog; 723 time_t now_time; 724 struct tm current; 725 int state = dlg_default_button(); 726 const char **buttons = dlg_ok_labels(); 727 char *prompt; 728 int mincols = MIN_WIDE; 729 char buffer[MAX_LEN]; 730 DIALOG_VARS save_vars; 731 732 DLG_TRACE(("# calendar args:\n")); 733 DLG_TRACE2S("title", title); 734 DLG_TRACE2S("message", subtitle); 735 DLG_TRACE2N("height", height); 736 DLG_TRACE2N("width", width); 737 DLG_TRACE2N("day", day); 738 DLG_TRACE2N("month", month); 739 DLG_TRACE2N("year", year); 740 741 dlg_save_vars(&save_vars); 742 dialog_vars.separate_output = TRUE; 743 744 dlg_does_output(); 745 746 /* 747 * Unless overridden, the current time/date is our starting point. 748 */ 749 now_time = time((time_t *) 0); 750 current = *localtime(&now_time); 751 752 #if HAVE_MKTIME 753 current.tm_isdst = -1; 754 if (year >= 1900) { 755 current.tm_year = year - 1900; 756 } 757 if (month >= 1) { 758 current.tm_mon = month - 1; 759 } 760 if (day > 0 && day <= days_per_month(current.tm_year + 1900, 761 current.tm_mon + 1)) { 762 current.tm_mday = day; 763 } 764 now_time = mktime(¤t); 765 #else 766 if (day < 0) 767 day = current.tm_mday; 768 if (month < 0) 769 month = current.tm_mon + 1; 770 if (year < 0) 771 year = current.tm_year + 1900; 772 773 /* compute a struct tm that matches the day/month/year parameters */ 774 if (((year -= 1900) > 0) && (year < 200)) { 775 /* ugly, but I'd like to run this on older machines w/o mktime -TD */ 776 for (;;) { 777 if (year > current.tm_year) { 778 now_time += ONE_DAY * days_in_year(¤t, 0); 779 } else if (year < current.tm_year) { 780 now_time -= ONE_DAY * days_in_year(¤t, -1); 781 } else if (month > current.tm_mon + 1) { 782 now_time += ONE_DAY * days_in_month(¤t, 0); 783 } else if (month < current.tm_mon + 1) { 784 now_time -= ONE_DAY * days_in_month(¤t, -1); 785 } else if (day > current.tm_mday) { 786 now_time += ONE_DAY; 787 } else if (day < current.tm_mday) { 788 now_time -= ONE_DAY; 789 } else { 790 break; 791 } 792 current = *localtime(&now_time); 793 } 794 } 795 #endif 796 797 dlg_button_layout(buttons, &mincols); 798 799 #ifdef KEY_RESIZE 800 retry: 801 #endif 802 803 prompt = dlg_strclone(subtitle); 804 dlg_auto_size(title, prompt, &height, &width, 0, mincols); 805 806 height += MIN_HIGH - 1; 807 dlg_print_size(height, width); 808 dlg_ctl_size(height, width); 809 810 dialog = dlg_new_window(height, width, 811 dlg_box_y_ordinate(height), 812 dlg_box_x_ordinate(width)); 813 dlg_register_window(dialog, "calendar", binding); 814 dlg_register_buttons(dialog, "calendar", buttons); 815 816 /* mainbox */ 817 dlg_draw_box2(dialog, 0, 0, height, width, dialog_attr, border_attr, border2_attr); 818 dlg_draw_bottom_box2(dialog, border_attr, border2_attr, dialog_attr); 819 dlg_draw_title(dialog, title); 820 821 dlg_attrset(dialog, dialog_attr); /* text mainbox */ 822 dlg_print_autowrap(dialog, prompt, height, width); 823 824 /* compute positions of day, month and year boxes */ 825 memset(&dy_box, 0, sizeof(dy_box)); 826 memset(&mn_box, 0, sizeof(mn_box)); 827 memset(&yr_box, 0, sizeof(yr_box)); 828 829 if ((week_start = WeekStart()) < 0 || 830 init_object(&dy_box, 831 dialog, 832 (width - DAY_WIDE) / 2, 833 1 + (height - (DAY_HIGH + BTN_HIGH + (5 * MARGIN))), 834 DAY_WIDE, 835 DAY_HIGH + 1, 836 draw_day, 837 week_start, 838 'D') < 0 || 839 ((dy_box.week_start = WeekStart()) < 0) || 840 DrawObject(&dy_box) < 0) { 841 return CleanupResult(DLG_EXIT_ERROR, dialog, prompt, &save_vars); 842 } 843 844 if (init_object(&mn_box, 845 dialog, 846 dy_box.x, 847 dy_box.y - (HDR_HIGH + 2 * MARGIN), 848 (DAY_WIDE / 2) - MARGIN, 849 HDR_HIGH, 850 draw_month, 851 0, 852 'M') < 0 853 || DrawObject(&mn_box) < 0) { 854 return CleanupResult(DLG_EXIT_ERROR, dialog, prompt, &save_vars); 855 } 856 857 if (init_object(&yr_box, 858 dialog, 859 dy_box.x + mn_box.width + 2, 860 mn_box.y, 861 mn_box.width, 862 mn_box.height, 863 draw_year, 864 0, 865 'Y') < 0 866 || DrawObject(&yr_box) < 0) { 867 return CleanupResult(DLG_EXIT_ERROR, dialog, prompt, &save_vars); 868 } 869 870 dlg_trace_win(dialog); 871 while (result == DLG_EXIT_UNKNOWN) { 872 int key2; 873 BOX *obj = (state == sDAY ? &dy_box 874 : (state == sMONTH ? &mn_box : 875 (state == sYEAR ? &yr_box : 0))); 876 877 button = (state < 0) ? 0 : state; 878 dlg_draw_buttons(dialog, height - 2, 0, buttons, button, FALSE, width); 879 if (obj != 0) 880 dlg_set_focus(dialog, obj->window); 881 882 key = dlg_mouse_wgetch(dialog, &fkey); 883 if (dlg_result_key(key, fkey, &result)) { 884 if (!dlg_button_key(result, &button, &key, &fkey)) 885 break; 886 } 887 888 #define Mouse2Key(key) (key - M_EVENT) 889 if (fkey && (key >= DLGK_MOUSE(KEY_MIN) && key <= DLGK_MOUSE(KEY_MAX))) { 890 key = dlg_lookup_key(dialog, Mouse2Key(key), &fkey); 891 } 892 893 if ((key2 = dlg_char_to_button(key, buttons)) >= 0) { 894 result = key2; 895 } else if (fkey) { 896 /* handle function-keys */ 897 switch (key) { 898 case DLGK_MOUSE('D'): 899 state = sDAY; 900 break; 901 case DLGK_MOUSE('M'): 902 state = sMONTH; 903 break; 904 case DLGK_MOUSE('Y'): 905 state = sYEAR; 906 break; 907 case DLGK_TOGGLE: 908 case DLGK_ENTER: 909 result = dlg_enter_buttoncode(button); 910 break; 911 case DLGK_FIELD_PREV: 912 state = dlg_prev_ok_buttonindex(state, sMONTH); 913 break; 914 case DLGK_FIELD_NEXT: 915 state = dlg_next_ok_buttonindex(state, sMONTH); 916 break; 917 #ifdef KEY_RESIZE 918 case KEY_RESIZE: 919 dlg_will_resize(dialog); 920 /* reset data */ 921 height = old_height; 922 width = old_width; 923 free(prompt); 924 _dlg_resize_cleanup(dialog); 925 /* repaint */ 926 goto retry; 927 #endif 928 default: 929 step = 0; 930 key2 = -1; 931 if (is_DLGK_MOUSE(key)) { 932 if ((key2 = dlg_ok_buttoncode(Mouse2Key(key))) >= 0) { 933 result = key2; 934 break; 935 } else if (key >= DLGK_MOUSE(KEY_MAX)) { 936 state = sDAY; 937 obj = &dy_box; 938 key2 = 1; 939 step = (key 940 - DLGK_MOUSE(KEY_MAX) 941 - day_cell_number(¤t)); 942 DLG_TRACE(("# mouseclick decoded %d\n", step)); 943 } 944 } 945 if (obj != 0) { 946 if (key2 < 0) { 947 step = next_or_previous(key, (obj == &dy_box)); 948 } 949 if (step != 0) { 950 struct tm old = current; 951 952 /* see comment regarding mktime -TD */ 953 if (obj == &dy_box) { 954 now_time += ONE_DAY * step; 955 } else if (obj == &mn_box) { 956 if (step > 0) 957 now_time += ONE_DAY * 958 days_in_month(¤t, 0); 959 else 960 now_time -= ONE_DAY * 961 days_in_month(¤t, -1); 962 } else if (obj == &yr_box) { 963 if (step > 0) 964 now_time += (ONE_DAY 965 * days_in_year(¤t, 0)); 966 else 967 now_time -= (ONE_DAY 968 * days_in_year(¤t, -1)); 969 } 970 971 current = *localtime(&now_time); 972 973 trace_date(¤t, &old); 974 if (obj != &dy_box 975 && (current.tm_mday != old.tm_mday 976 || current.tm_mon != old.tm_mon 977 || current.tm_year != old.tm_year)) 978 DrawObject(&dy_box); 979 if (obj != &mn_box && current.tm_mon != old.tm_mon) 980 DrawObject(&mn_box); 981 if (obj != &yr_box && current.tm_year != old.tm_year) 982 DrawObject(&yr_box); 983 (void) DrawObject(obj); 984 } 985 } else if (state >= 0) { 986 if (next_or_previous(key, FALSE) < 0) 987 state = dlg_prev_ok_buttonindex(state, sMONTH); 988 else if (next_or_previous(key, FALSE) > 0) 989 state = dlg_next_ok_buttonindex(state, sMONTH); 990 } 991 break; 992 } 993 } 994 } 995 996 #define DefaultFormat(dst, src) \ 997 sprintf(dst, "%02d/%02d/%0d", \ 998 src.tm_mday, src.tm_mon + 1, src.tm_year + 1900) 999 #ifdef HAVE_STRFTIME 1000 if (dialog_vars.date_format != 0) { 1001 size_t used = strftime(buffer, 1002 sizeof(buffer) - 1, 1003 dialog_vars.date_format, 1004 ¤t); 1005 if (used == 0 || *buffer == '\0') 1006 DefaultFormat(buffer, current); 1007 } else 1008 #endif 1009 DefaultFormat(buffer, current); 1010 1011 dlg_add_result(buffer); 1012 AddLastKey(); 1013 1014 return CleanupResult(result, dialog, prompt, &save_vars); 1015 } 1016