/* * Calcurse - text-based organizer * * Copyright (c) 2004-2012 calcurse Development Team * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * - Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other * materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * Send your feedback or comments to : misc@calcurse.org * Calcurse home page : http://calcurse.org * */ #include #include #include #include #include #include #include #include #include #include #include "calcurse.h" #define ISLEAP(y) ((((y) % 4) == 0 && ((y) % 100) != 0) || ((y) % 400) == 0) /* General routine to exit calcurse properly. */ void exit_calcurse (int status) { int was_interactive; if (ui_mode == UI_CURSES) { notify_stop_main_thread (); clear (); wins_refresh (); endwin (); ui_mode = UI_CMDLINE; was_interactive = 1; } else was_interactive = 0; calendar_stop_date_thread (); io_stop_psave_thread (); free_user_data (); keys_free (); mem_stats (); if (was_interactive) { if (unlink (path_cpid) != 0) EXIT (_("Could not remove calcurse lock file: %s\n"), strerror (errno)); if (dmon.enable) dmon_start (status); } exit (status); } void free_user_data (void) { day_free_list (); event_llist_free (); event_free_bkp (); apoint_llist_free (); apoint_free_bkp (); recur_apoint_llist_free (); recur_event_llist_free (); recur_apoint_free_bkp (); recur_event_free_bkp (); todo_free_list (); notify_free_app (); } /* Function to exit on internal error. */ void fatalbox (const char *errmsg) { WINDOW *errwin; char *label = _("/!\\ INTERNAL ERROR /!\\"); char *reportmsg = _("Please report the following bug:"); const int WINROW = 10; const int WINCOL = col - 2; const int MSGLEN = WINCOL - 2; char msg[MSGLEN]; if (errmsg == NULL) return; strncpy (msg, errmsg, MSGLEN); errwin = newwin (WINROW, WINCOL, (row - WINROW) / 2, (col - WINCOL) / 2); custom_apply_attr (errwin, ATTR_HIGHEST); box (errwin, 0, 0); wins_show (errwin, label); mvwprintw (errwin, 3, 1, reportmsg); mvwprintw (errwin, 5, (WINCOL - strlen (msg)) / 2, "%s", msg); custom_remove_attr (errwin, ATTR_HIGHEST); wins_wrefresh (errwin); wgetch (errwin); delwin (errwin); wins_doupdate (); } void warnbox (const char *msg) { WINDOW *warnwin; char *label = "/!\\"; const int WINROW = 10; const int WINCOL = col - 2; const int MSGLEN = WINCOL - 2; char displmsg[MSGLEN]; if (msg == NULL) return; strncpy (displmsg, msg, MSGLEN); warnwin = newwin (WINROW, WINCOL, (row - WINROW) / 2, (col - WINCOL) / 2); custom_apply_attr (warnwin, ATTR_HIGHEST); box (warnwin, 0, 0); wins_show (warnwin, label); mvwprintw (warnwin, 5, (WINCOL - strlen (displmsg)) / 2, "%s", displmsg); custom_remove_attr (warnwin, ATTR_HIGHEST); wins_wrefresh (warnwin); wgetch (warnwin); delwin (warnwin); wins_doupdate (); } /* * Print a message in the status bar. * Message texts for first line and second line are to be provided. */ void status_mesg (char *mesg_line1, char *mesg_line2) { wins_erase_status_bar (); custom_apply_attr (win[STA].p, ATTR_HIGHEST); mvwprintw (win[STA].p, 0, 0, mesg_line1); mvwprintw (win[STA].p, 1, 0, mesg_line2); custom_remove_attr (win[STA].p, ATTR_HIGHEST); } /* Erase part of a window. */ void erase_window_part (WINDOW *win, int first_col, int first_row, int last_col, int last_row) { int c, r; for (r = first_row; r <= last_row; r++) { for (c = first_col; c <= last_col; c++) mvwprintw (win, r, c, " "); } } /* draws a popup window */ WINDOW * popup (int pop_row, int pop_col, int pop_y, int pop_x, char *title, char *msg, int hint) { char *any_key = _("Press any key to continue..."); char label[BUFSIZ]; WINDOW *popup_win; const int MSGXPOS = 5; popup_win = newwin (pop_row, pop_col, pop_y, pop_x); keypad (popup_win, TRUE); if (msg) mvwprintw (popup_win, MSGXPOS, (pop_col - strlen (msg)) / 2, "%s", msg); custom_apply_attr (popup_win, ATTR_HIGHEST); box (popup_win, 0, 0); snprintf (label, BUFSIZ, "%s", title); wins_show (popup_win, label); if (hint) mvwprintw (popup_win, pop_row - 2, pop_col - (strlen (any_key) + 1), "%s", any_key); custom_remove_attr (popup_win, ATTR_HIGHEST); wins_wrefresh (popup_win); return popup_win; } /* prints in middle of a panel */ void print_in_middle (WINDOW *win, int starty, int startx, int width, const char *string) { int len = strlen (string); int x, y; win = win ? win : stdscr; getyx (win, y, x); x = startx ? startx : x; y = starty ? starty : y; width = width ? width : 80; x += (width - len) / 2; custom_apply_attr (win, ATTR_HIGHEST); mvwprintw (win, y, x, "%s", string); custom_remove_attr (win, ATTR_HIGHEST); } /* checks if a string is only made of digits */ int is_all_digit (const char *string) { for (; *string; string++) { if (!isdigit ((int)*string)) return 0; } return 1; } /* Given an item date expressed in seconds, return its start time in seconds. */ long get_item_time (long date) { return (long)(get_item_hour(date) * HOURINSEC + get_item_min(date) * MININSEC); } int get_item_hour (long date) { return (localtime ((time_t *)&date))->tm_hour; } int get_item_min (long date) { return (localtime ((time_t *)&date))->tm_min; } long date2sec (struct date day, unsigned hour, unsigned min) { time_t t = now (); struct tm start = *(localtime (&t)); start.tm_mon = day.mm - 1; start.tm_mday = day.dd; start.tm_year = day.yyyy - 1900; start.tm_hour = hour; start.tm_min = min; start.tm_sec = 0; start.tm_isdst = -1; t = mktime (&start); EXIT_IF (t == -1, _("failure in mktime")); return t; } /* Return a string containing the date, given a date in seconds. */ char * date_sec2date_str (long sec, char *datefmt) { struct tm *lt; char *datestr = (char *) mem_calloc (BUFSIZ, sizeof (char)); if (sec == 0) strncpy (datestr, "0", BUFSIZ); else { lt = localtime ((time_t *)&sec); strftime (datestr, BUFSIZ, datefmt, lt); } return datestr; } /* Generic function to format date. */ void date_sec2date_fmt (long sec, const char *fmt, char *datef) { struct tm *lt = localtime ((time_t *)&sec); strftime (datef, BUFSIZ, fmt, lt); } /* * Used to change date by adding a certain amount of days or weeks. */ long date_sec_change (long date, int delta_month, int delta_day) { struct tm *lt; time_t t; t = date; lt = localtime (&t); lt->tm_mon += delta_month; lt->tm_mday += delta_day; lt->tm_isdst = -1; t = mktime (lt); EXIT_IF (t == -1, _("failure in mktime")); return t; } /* * Return a long containing the date which is updated taking into account * the new time and date entered by the user. */ long update_time_in_date (long date, unsigned hr, unsigned mn) { struct tm *lt; time_t t, new_date; t = date; lt = localtime (&t); lt->tm_hour = hr; lt->tm_min = mn; new_date = mktime (lt); EXIT_IF (new_date == -1, _("error in mktime")); return new_date; } /* * Returns the date in seconds from year 1900. * If no date is entered, current date is chosen. */ long get_sec_date (struct date date) { struct tm *ptrtime; time_t timer; long long_date; char current_day[] = "dd "; char current_month[] = "mm "; char current_year[] = "yyyy "; if (date.yyyy == 0 && date.mm == 0 && date.dd == 0) { timer = time (NULL); ptrtime = localtime (&timer); strftime (current_day, strlen (current_day), "%d", ptrtime); strftime (current_month, strlen (current_month), "%m", ptrtime); strftime (current_year, strlen (current_year), "%Y", ptrtime); date.mm = atoi (current_month); date.dd = atoi (current_day); date.yyyy = atoi (current_year); } long_date = date2sec (date, 0, 0); return long_date; } long min2sec (unsigned minutes) { return minutes * MININSEC; } /* * Display a scroll bar when there are so many items that they * can not be displayed inside the corresponding panel. */ void draw_scrollbar (WINDOW *win, int y, int x, int length, int bar_top, int bar_bottom, unsigned hilt) { mvwvline (win, bar_top, x, ACS_VLINE, bar_bottom - bar_top); if (hilt) custom_apply_attr (win, ATTR_HIGHEST); wattron (win, A_REVERSE); mvwvline (win, y, x, ' ', length); wattroff (win, A_REVERSE); if (hilt) custom_remove_attr (win, ATTR_HIGHEST); } /* * Print an item (either an appointment, event, or todo) in a * popup window. This is useful if an item description is too * long to fit in its corresponding panel window. */ void item_in_popup (char *saved_a_start, char *saved_a_end, char *msg, char *pop_title) { WINDOW *popup_win, *pad; const int margin_left = 4, margin_top = 4; const int winl = row - 5, winw = col - margin_left; const int padl = winl - 2, padw = winw - margin_left; pad = newpad (padl, padw); popup_win = popup (winl, winw, 1, 2, pop_title, NULL, 1); if (strcmp (pop_title, _("Appointment")) == 0) { mvwprintw (popup_win, margin_top, margin_left, "- %s -> %s", saved_a_start, saved_a_end); } mvwprintw (pad, 0, margin_left, "%s", msg); wmove (win[STA].p, 0, 0); pnoutrefresh (pad, 0, 0, margin_top + 2, margin_left, padl, winw); wins_doupdate (); wgetch (popup_win); delwin (pad); delwin (popup_win); } /* Returns the beginning of current day in seconds from 1900. */ long get_today (void) { struct tm *lt; time_t current_time; long current_day; struct date day; current_time = time (NULL); lt = localtime (¤t_time); day.mm = lt->tm_mon + 1; day.dd = lt->tm_mday; day.yyyy = lt->tm_year + 1900; current_day = date2sec (day, 0, 0); return current_day; } /* Returns the current time in seconds. */ long now (void) { return (long)time (NULL); } char * nowstr (void) { static char buf[BUFSIZ]; time_t t = now (); strftime (buf, sizeof buf, "%a %b %d %T %Y", localtime (&t)); return buf; } long mystrtol (const char *str) { char *ep; long lval; errno = 0; lval = strtol (str, &ep, 10); if (str[0] == '\0' || *ep != '\0') EXIT (_("could not convert string")); if (errno == ERANGE && (lval == LONG_MAX || lval == LONG_MIN)) EXIT (_("out of range")); return lval; } /* Print the given option value with appropriate color. */ void print_bool_option_incolor (WINDOW *win, unsigned option, int pos_y, int pos_x) { int color = 0; const char *option_value; if (option == 1) { color = ATTR_TRUE; option_value = _("yes"); } else if (option == 0) { color = ATTR_FALSE; option_value = _("no"); } else EXIT (_("option not defined")); custom_apply_attr (win, color); mvwprintw (win, pos_y, pos_x, "%s", option_value); custom_remove_attr (win, color); wnoutrefresh (win); wins_doupdate (); } /* * Get the name of the default directory for temporary files. */ const char * get_tempdir (void) { if (getenv ("TMPDIR")) return getenv ("TMPDIR"); #ifdef P_tmpdir else if (P_tmpdir) return P_tmpdir; #endif else return "/tmp"; } /* * Create a new unique file, and return a newly allocated string which contains * the random part of the file name. */ char * new_tempfile (const char *prefix, int trailing_len) { char fullname[BUFSIZ]; int prefix_len, fd; FILE *file; if (prefix == NULL) return NULL; prefix_len = strlen (prefix); if (prefix_len + trailing_len >= BUFSIZ) return NULL; memcpy (fullname, prefix, prefix_len); memset (fullname + prefix_len, 'X', trailing_len); fullname[prefix_len + trailing_len] = '\0'; if ((fd = mkstemp (fullname)) == -1 || (file = fdopen (fd, "w+")) == NULL) { if (fd != -1) { unlink (fullname); close (fd); } ERROR_MSG (_("temporary file \"%s\" could not be created"), fullname); return NULL; } fclose (file); return mem_strdup (fullname + prefix_len); } /* * Convert a string containing a date into three integers containing the year, * month and day. * * If a pointer to a date structure containing the current date is passed as * last parameter ("slctd_date"), the function will accept several short forms, * e.g. "26" for the 26th of the current month/year or "3/1" for Mar 01 (or Jan * 03, depending on the date format) of the current year. If a null pointer is * passed, short forms won't be accepted at all. * * Returns 1 if sucessfully converted or 0 if the string is an invalid date. */ int parse_date (const char *date_string, enum datefmt datefmt, int *year, int *month, int *day, struct date *slctd_date) { const char sep = (datefmt == DATEFMT_ISO) ? '-' : '/'; const char *p; int in[3] = {0, 0, 0}, n = 0; int d, m, y; if (!date_string) return 0; /* parse string into in[], read up to three integers */ for (p = date_string; *p; p++) { if (*p == sep) { if ((++n) > 2) return 0; } else if ((*p >= '0') && (*p <= '9')) in[n] = in[n] * 10 + (int)(*p - '0'); else return 0; } if ((!slctd_date && n < 2) || in[n] == 0) return 0; /* convert into day, month and year, depending on the date format */ switch (datefmt) { case DATEFMT_MMDDYYYY: m = (n >= 1) ? in[0] : 0; d = (n >= 1) ? in[1] : in[0]; y = in[2]; break; case DATEFMT_DDMMYYYY: d = in[0]; m = in[1]; y = in[2]; break; case DATEFMT_YYYYMMDD: case DATEFMT_ISO: y = (n >= 2) ? in[n - 2] : 0; m = (n >= 1) ? in[n - 1] : 0; d = in[n]; break; default: return 0; } if (slctd_date) { if (y > 0 && y < 100) { /* convert "YY" format into "YYYY" */ y += slctd_date->yyyy - slctd_date->yyyy % 100; } else if (n < 2) { /* set year and, optionally, month if short from is used */ y = slctd_date->yyyy; if (n < 1) m = slctd_date->mm; } } /* check if date is valid, take leap years into account */ if (y < 1902 || y > 2037 || m < 1 || m > 12 || d < 1 || d > days[m - 1] + (m == 2 && ISLEAP (y)) ? 1 : 0) return 0; if (year) *year = y; if (month) *month = m; if (day) *day = d; return 1; } /* * Converts a time string into hours and minutes. Short forms like "23:" * (23:00) or ":45" (0:45) are allowed. * * Returns 1 on success and 0 on failure. */ int parse_time (const char *string, unsigned *hour, unsigned *minute) { const char *p; unsigned in[2] = {0, 0}, n = 0; if (!string) return 0; /* parse string into in[], read up to two integers */ for (p = string; *p; p++) { if (*p == ':') { if ((++n) > 1) return 0; } else if ((*p >= '0') && (*p <= '9')) in[n] = in[n] * 10 + (int)(*p - '0'); else return 0; } if (n != 1 || in[0] >= DAYINHOURS || in[1] >= HOURINMIN) return 0; *hour = in[0]; *minute = in[1]; return 1; } /* * Converts a duration string into minutes. * * Allowed formats (noted as regular expressions): * * - \d*:\d* * - (\d*m|\d*h(|\d*m)|\d*d(|\d*m|\d*h(|\d*m))) * - \d+ * * "\d" is used as a placeholder for "(0|1|2|3|4|5|6|7|8|9)". * * Note that this function performs an additional range check on each token to * ensure we do not accept semantically invalid strings such as "42:23". * * Returns 1 on success and 0 on failure. */ int parse_duration (const char *string, unsigned *duration) { enum { STATE_INITIAL, STATE_HHMM_MM, STATE_DDHHMM_HH, STATE_DDHHMM_MM, STATE_DONE } state = STATE_INITIAL; const char *p; unsigned in = 0; unsigned dur = 0; if (!string || *string == '\0') return 0; /* parse string using a simple state machine */ for (p = string; *p; p++) { if ((*p >= '0') && (*p <= '9')) { if (state == STATE_DONE) return 0; else in = in * 10 + (int)(*p - '0'); } else { switch (state) { case STATE_INITIAL: if (*p == ':') { dur += in * HOURINMIN; state = STATE_HHMM_MM; } else if (*p == 'd') { dur += in * DAYINMIN; state = STATE_DDHHMM_HH; } else if (*p == 'h') { if (in >= DAYINHOURS) return 0; dur += in * HOURINMIN; state = STATE_DDHHMM_MM; } else if (*p == 'm') { if (in >= HOURINMIN) return 0; dur += in; state = STATE_DONE; } else return 0; break; case STATE_DDHHMM_HH: if (*p == 'h') { if (in >= DAYINHOURS) return 0; dur += in * HOURINMIN; state = STATE_DDHHMM_MM; } else if (*p == 'm') { if (in >= HOURINMIN) return 0; dur += in; state = STATE_DONE; } else return 0; break; case STATE_DDHHMM_MM: if (*p == 'm') { if (in >= HOURINMIN) return 0; dur += in; state = STATE_DONE; } else return 0; break; case STATE_HHMM_MM: case STATE_DONE: return 0; break; } in = 0; } } if ((state == STATE_HHMM_MM && in >= HOURINMIN) || ((state == STATE_DDHHMM_HH || state == STATE_DDHHMM_MM) && in > 0)) return 0; dur += in; *duration = dur; return 1; } void str_toupper (char *s) { if (!s) return; for (; *s; s++) *s = toupper (*s); } void file_close (FILE *f, const char *pos) { EXIT_IF ((fclose (f)) != 0, _("Error when closing file at %s"), pos); } /* * Sleep the given number of seconds, but make it more 'precise' than sleep(3) * (hence the 'p') in a way that even if a signal is caught during the sleep * process, this function will return to sleep afterwards. */ void psleep (unsigned secs) { unsigned unslept; for (unslept = sleep (secs); unslept; unslept = sleep (unslept)) ; } /* * Fork and execute an external process. * * If pfdin and/or pfdout point to a valid address, a pipe is created and the * appropriate file descriptors are written to pfdin/pfdout. */ int fork_exec (int *pfdin, int *pfdout, const char *path, char *const *arg) { int pin[2], pout[2]; int pid; if (pfdin && (pipe (pin) == -1)) return 0; if (pfdout && (pipe (pout) == -1)) return 0; if ((pid = fork ()) == 0) { if (pfdout) { if (dup2 (pout[0], STDIN_FILENO) < 0) _exit (127); close (pout[0]); close (pout[1]); } if (pfdin) { if (dup2 (pin[1], STDOUT_FILENO) < 0) _exit (127); close (pin[0]); close (pin[1]); } execvp (path, arg); _exit (127); } else { if (pfdin) close (pin[1]); if (pfdout) close (pout[0]); if (pid > 0) { if (pfdin) { fcntl (pin[0], F_SETFD, FD_CLOEXEC); *pfdin = pin[0]; } if (pfdout) { fcntl (pout[1], F_SETFD, FD_CLOEXEC); *pfdout = pout[1]; } } else { if (pfdin) close (pin[0]); if (pfdout) close (pout[1]); return 0; } } return pid; } /* Execute an external program in a shell. */ int shell_exec (int *pfdin, int *pfdout, char *cmd) { char *arg[] = { "/bin/sh", "-c", cmd, NULL }; return fork_exec (pfdin, pfdout, *arg, arg); } /* Wait for a child process to terminate. */ int child_wait (int *pfdin, int *pfdout, int pid) { int stat; if (pfdin) close (*pfdin); if (pfdout) close (*pfdout); waitpid (pid, &stat, 0); return stat; } /* Display "Press any key to continue..." and wait for a key press. */ void press_any_key (void) { fflush (stdout); fputs (_("Press any key to continue..."), stdout); fflush (stdout); fgetc (stdin); fflush (stdin); fputs ("\r\n", stdout); } /* * Display note contents if one is asociated with the currently displayed item * (to be used together with the '-a' or '-t' flag in non-interactive mode). * Each line begins with nbtab tabs. * Print "No note file found", if the notefile does not exists. * * (patch submitted by Erik Saule). */ static void print_notefile (FILE *out, char *filename, int nbtab) { char path_to_notefile[BUFSIZ]; FILE *notefile; char linestarter[BUFSIZ]; char buffer[BUFSIZ]; int i; int printlinestarter = 1; if (nbtab < BUFSIZ) { for (i = 0; i < nbtab; i++) linestarter[i] = '\t'; linestarter[nbtab] = '\0'; } else linestarter[0] = '\0'; snprintf (path_to_notefile, BUFSIZ, "%s/%s", path_notes, filename); notefile = fopen (path_to_notefile, "r"); if (notefile) { while (fgets (buffer, BUFSIZ, notefile) != 0) { if (printlinestarter) { fputs (linestarter, out); printlinestarter = 0; } fputs (buffer, out); if (buffer[strlen (buffer) - 1] == '\n') printlinestarter = 1; } fputs ("\n", out); file_close (notefile, __FILE_POS__); } else { fputs (linestarter, out); fputs (_("No note file found\n"), out); } } /* Print an escape sequence and return its length. */ static int print_escape (const char *s) { switch (*(s + 1)) { case 'a': putchar ('\a'); return 1; case 'b': putchar ('\b'); return 1; case 'f': putchar ('\f'); return 1; case 'n': putchar ('\n'); return 1; case 'r': putchar ('\r'); return 1; case 't': putchar ('\t'); return 1; case 'v': putchar ('\v'); return 1; case '0': putchar ('\0'); return 1; case '\'': putchar ('\''); return 1; case '"': putchar ('"'); return 1; case '\?': putchar ('?'); return 1; case '\\': putchar ('\\'); return 1; case '\0': return 0; default: return 1; } } /* Print a formatted appointment to stdout. */ void print_apoint (const char *format, long day, struct apoint *apt) { const char *p; char str_start[HRMIN_SIZE], str_end[HRMIN_SIZE]; apoint_sec2str (apt, day, str_start, str_end); for (p = format; *p; p++) { if (*p == '%') { p++; switch (*p) { case 's': printf ("%ld", apt->start); break; case 'S': printf ("%s", str_start); break; case 'd': printf ("%ld", apt->dur); break; case 'e': printf ("%ld", apt->start + apt->dur); break; case 'E': printf ("%s", str_end); break; case 'm': printf ("%s", apt->mesg); break; case 'n': printf ("%s", apt->note); break; case 'N': print_notefile (stdout, apt->note, 1); break; case '%': putchar ('%'); break; case '\0': return; break; default: putchar ('?'); break; } } else if (*p == '\\') p += print_escape (p); else putchar (*p); } } /* Print a formatted event to stdout. */ void print_event (const char *format, long day, struct event *ev) { const char *p; for (p = format; *p; p++) { if (*p == '%') { p++; switch (*p) { case 'm': printf ("%s", ev->mesg); break; case 'n': printf ("%s", ev->note); break; case 'N': print_notefile (stdout, ev->note, 1); break; case '%': putchar ('%'); break; case '\0': return; break; default: putchar ('?'); break; } } else if (*p == '\\') p += print_escape (p); else putchar (*p); } } /* Print a formatted recurrent appointment to stdout. */ void print_recur_apoint (const char *format, long day, unsigned occurrence, struct recur_apoint *rapt) { struct apoint apt; apt.start = occurrence; apt.dur = rapt->dur; apt.mesg = rapt->mesg; apt.note = rapt->note; print_apoint (format, day, &apt); } /* Print a formatted recurrent event to stdout. */ void print_recur_event (const char *format, long day, struct recur_event *rev) { struct event ev; ev.mesg = rev->mesg; ev.note = rev->note; print_event (format, day, &ev); } /* Print a formatted todo item to stdout. */ void print_todo (const char *format, struct todo *todo) { const char *p; for (p = format; *p; p++) { if (*p == '%') { p++; switch (*p) { case 'p': printf ("%d", abs (todo->id)); break; case 'm': printf ("%s", todo->mesg); break; case 'n': printf ("%s", todo->note); break; case 'N': print_notefile (stdout, todo->note, 1); break; case '%': putchar ('%'); break; case '\0': return; break; default: putchar ('?'); break; } } else if (*p == '\\') p += print_escape (p); else putchar (*p); } }