summaryrefslogblamecommitdiffstats
path: root/src/utils.c
blob: a5adec1da9508279b90b5dc8ce613ae641dda41c (plain) (tree)
1
2
3
4
5
6
7
8
9

                                  
  
                                                              
                       
  


                                                                     
  



















                                                                        
  
                                                        
                                           


   
                 

                   
                   
                   
                  
                      
                  
 
                     
 

                                                                            
                                                

                          
 
                      
  

                           
                                 
               
                      

                           
                          
     
      
                        
  
                               
                          
                    

               
                      


                                                             
                                

                            
     
  
                

 















                                      

                                         
                             

                 
                                              
                                                          




                                


                  
                                      
                                                                           


                                           

                                                                
                                            
                         
                        
                  
                   

 



















                                                                            
                          

                         
                   

 



                                                                   

                                                
 
                           



                                                

 
                             

                                                                           
                                
 
           
 
                                         


                                           
                     


                          
        

                                                                              
 
                                                    

                     
                        


                                                      

                                                                            

                                              
                                              
                               


                                                                              
                                               
                            

                   



                                 
                                                                              
 

                            
 
                         
                    





                         


                                         

 




                                                                          

                                                                      
 
           
 

                               
                                                                               


                                               






























                                                                   

 
                                                         

                             
 
          
 


                              



                                                      
                                     
 
          
 




                              

 

                                                                  




                                                                           
   
           



















                                                          
                                    






















                                                                 


















                                                                  

























                                                                
                                            









                                                          
                 





                                                                        
         
                                                
                       



                                                      

 
                                        





                                                    


                                                             




                                                 
                                        

                                              
     
                    
                

 
                                               

                           
 




                                

 



                                                                                

                                                  




                         
                                                




                        
                                               

 
    
                                                       
 

                                      
 
                            
                         
                                  



                       
 



                                            

 
                                                                   
      


                                           
                                                              

               
                                          



                                            
 
                 

 
                                      























                                                                   
                    
                  
                                            



           
   


                                                                         

                                                         
 

                     
 




                         
                                                 
 
                    

 




                                                 
                               




















                                                                      

 

                          
 
                              









                                                                             


                         














                                                                    





                                                              

                                                      
                                                           
 







                                                              






                                                              









                                                                 
                                                                







                                                                    
                   
                           

                     

 
                                                                

                
 


                      
                  
 





                                     
 
                       
 

                                          

          
 
                           
 
 



                            
                    
 
                                                                     



             
    
                          
 

            
 


                                    
                                         
                                                                
                             
 
                

 
                                                          
    
                                                                              



                                 
                  



                                               
                       




                                              

                                   



                                                    
                   
 





                                                                               












                                                   
                                                          






                                                                            
         
                                                                            
                       

                
 
                                            
 


                                                                       
                                            
 
                        
 



                                    
                                                                   
                                 
                                          
     
                   
               
 
 


                                                                              






                                                                               

                                                                            
   
                                                                           
                                              
 



                                                  
 
                             
 



                                                         
     

                                           
     







                                                                      
                          


                            
            
                          


                
            
                          



                     

            
               














                                                               
             




                        
           
 
 
    

                     

                                    
 

    
                                     
 
                                                                       
 













                                                                              
/*
 * Calcurse - text-based organizer
 *
 * Copyright (c) 2004-2010 Frederic Culot <frederic@culot.org>
 * 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 <time.h>
#include <string.h>
#include <stdlib.h>
#include <limits.h>
#include <unistd.h>
#include <ctype.h>
#include <sys/types.h>
#include <errno.h>

#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 (ERASE_FORCE);
  apoint_llist_free ();
  apoint_free_bkp (ERASE_FORCE);
  recur_apoint_llist_free ();
  recur_event_llist_free ();
  recur_apoint_free_bkp (ERASE_FORCE);
  recur_event_free_bkp (ERASE_FORCE);
  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 == 0)
    return;
  
  (void)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);
  (void)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 == 0)
    return;
  
  (void)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);
  (void)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, " ");

  wnoutrefresh (win);
}

/* 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);
  (void)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, char *string)
{
  int len = strlen (string);
  int x, y;

  if (!win) win = stdscr;
  getyx (win, y, x);
  if (startx) x = startx;
  if (starty) y = starty;
  if (!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);
}

/* 
 * Draw the cursor at the correct position in string.
 * As echoing is not set, we need to know the string we are working on to 
 * handle display correctly.
 */
static void
showcursor (WINDOW *win, int y, int pos, char *str, int l, int offset)
{
  char *nc;

  nc = str + pos;
  wmove (win, y, pos - offset);
  (pos >= l) ? waddch (win, SPACE | A_REVERSE) : waddch (win, *nc | A_REVERSE);
}

/* Print the string at the desired position. */
static void
showstring (WINDOW *win, int y, int x, char *str, int len, int pos)
{
  const int rec = 30, border = 3;
  const int max_col = col - border, max_len = max_col - rec;
  int page, max_page, offset, c = 0;
  char *orig;

  orig = str;
  max_page = (len - rec) / max_len;
  page = (pos - rec) / max_len;
  offset = page * max_len;
  str += offset;
  mvwaddnstr (win, y, x, str, MIN (len, max_col));
  wclrtoeol (win);
  if (page > 0 && page < max_page)
    {
      c = '*';
    }
  else if (page > 0)
    {
      c = '<';
    }
  else if (page < max_page)
    {
      c = '>';
    }
  else
    c = 0;
  mvwprintw (win, y, col - 1, "%c", c);
  showcursor (win, y, pos, orig, len, offset);
}

/* Delete a character at the given position in string. */
static void
del_char (int pos, char *str)
{
  int len;

  str += pos;
  len = strlen (str) + 1;
  memmove (str, str + 1, len);
}

/* Add a character at the given position in string. */
static char *
add_char (int pos, int ch, char *str)
{
  int len;

  str += pos;
  len = strlen (str) + 1;
  memmove (str + 1, str, len);
  *str = ch;
  return (str += len);
}

/* 
 * Getstring allows to get user input and to print it on a window,
 * even if noecho() is on. This function is also used to modify an existing
 * text (the variable string can be non-NULL).
 * We need to do the echoing manually because of the multi-threading
 * environment, otherwise the cursor would move from place to place without
 * control.
 */
enum getstr
getstring (WINDOW *win, char *str, int l, int x, int y)
{
  int ch, newpos, len = 0;
  char *orig;

  orig = str;
  custom_apply_attr (win, ATTR_HIGHEST);
  for (; *str; ++str, ++len)
    ;
  newpos = x + len;
  showstring (win, y, x, orig, len, newpos);

  while ((ch = wgetch (win)) != '\n')
    {
      switch (ch)
	{
	case KEY_BACKSPACE:	/* delete one character */
	case 330:
	case 127:
	case CTRL ('H'):
	  if (len > 0 && newpos > x)
	    {
	      --newpos;
	      --len;
	      if (newpos >= x + len)
		--str;
	      else		/* to be deleted inside string */
		del_char (newpos, orig);
	    }
	  break;

	case CTRL ('D'):	/* delete next character */
	  if (newpos != (x + len))
	    {
	      --len;
	      if (newpos >= x + len)
		--str;
	      else
		del_char (newpos, orig);
	    }
	  else
	    printf ("\a");
	  break;

  case CTRL ('W'):	/* delete a word */
    while (len > 0 && newpos > x && *(orig + newpos - 1) == ' ') {
      --newpos;
      --len;
      if (newpos >= x + len)
        --str;
      else
        del_char (newpos, orig);
    }
    while (len > 0 && newpos > x && *(orig + newpos - 1) != ' ') {
      --newpos;
      --len;
      if (newpos >= x + len)
        --str;
      else
        del_char (newpos, orig);
    }
    break;

	case CTRL ('K'):	/* delete to end-of-line */
	  str = orig + newpos;
	  *str = 0;
	  len -= (len - newpos);
	  break;

	case CTRL ('A'):	/* go to begginning of string */
	  newpos = x;
	  break;

	case CTRL ('E'):	/* go to end of string */
	  newpos = x + len;
	  break;

	case KEY_LEFT:		/* move one char backward  */
	case CTRL ('B'):
	  if (newpos > x)
	    newpos--;
	  break;

	case KEY_RIGHT:	/* move one char forward */
	case CTRL ('F'):
	  if (newpos < len)
	    newpos++;
	  break;

	case ESCAPE:	/* cancel editing */
	  return (GETSTRING_ESC);
	  break;

	default:		/* insert one character */
	  if (len < l - 1)
	    {
	      if (newpos >= len)
		{
		  str = orig + newpos;
		  *str++ = ch;
		}
	      else		// char is to be inserted inside string 
		str = add_char (newpos, ch, orig);
	      ++len;
	      ++newpos;
	    }

	}
      showstring (win, y, x, orig, len, newpos);
      wins_doupdate ();
    }
  *str = 0;
  custom_remove_attr (win, ATTR_HIGHEST);
  return (len == 0 ? GETSTRING_RET : GETSTRING_VALID);
}

/* Update an already existing string. */
int
updatestring (WINDOW *win, char **str, int x, int y)
{
  char *newstr;
  int escape, len = strlen (*str) + 1;

  EXIT_IF (len > BUFSIZ, _("Internal error: line too long"));
  
  newstr = mem_malloc (BUFSIZ);
  (void) memcpy (newstr, *str, len);
  escape = getstring (win, newstr, BUFSIZ, x, y);
  if (!escape)
    {
      len = strlen (newstr) + 1;
      *str = mem_realloc (*str, len, 1);
      EXIT_IF (*str == 0, _("out of memory"));
      (void) memcpy (*str, newstr, len);
    }
  mem_free (newstr);
  return escape;
}

/* checks if a string is only made of digits */
int
is_all_digit (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)
    (void)snprintf (datestr, BUFSIZ, "0");
  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;
  time_t t;

  t = sec;
  lt = localtime (&t);
  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);
}

/* 
 * Checks if a time has a good format. 
 * The format could be either HH:MM or H:MM or MM, and we should have:
 * 0 <= HH <= 24 and 0 <= MM < 999.
 * This function returns 1 if the entered time is correct and in 
 * [h:mm] or [hh:mm] format, and 2 if the entered time is correct and entered
 * in [mm] format.
 */
int
check_time (char *string)
{
  char *s = mem_strdup(string);
  char *hour = strtok(s, ":");
  char *min = strtok(NULL, ":");
  int h, m;
  int ret = 0;

  if (min) {
    h = atoi (hour);
    m = atoi (min);
    if (h >= 0 && h < 24 && m >= 0 && m < MININSEC) ret = 1;
  }
  else if (strlen(s) < 4 && is_all_digit(s) && atoi(s) > 0) ret = 2;

  xfree(s);
  return ret;
}

/*
 * 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, (char *)0, 1);
  if (strncmp (pop_title, _("Appointment"), 11) == 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 ();
  (void)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 (&current_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 ();

  (void)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;
  char option_value[BUFSIZ] = "";

  if (option == 1)
    {
      color = ATTR_TRUE;
      strncpy (option_value, _("yes"), BUFSIZ);
    }
  else if (option == 0)
    {
      color = ATTR_FALSE;
      strncpy (option_value, _("no"), BUFSIZ);
    }
  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 ();
}

/* 
 * 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);
  (void)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 (char *)0;
    }
  fclose (file);

  return mem_strdup (fullname + prefix_len);
}

/* Erase a note previously attached to a todo, event or appointment. */
void
erase_note (char **note, enum eraseflg flag)
{
  char fullname[BUFSIZ];

  if (*note == NULL)
    return;
  if (flag != ERASE_FORCE_KEEP_NOTE)
    {
      (void)snprintf (fullname, BUFSIZ, "%s%s", path_notes, *note);
      if (unlink (fullname) != 0)
        EXIT (_("could not remove note"));
    }
  mem_free (*note);
  *note = NULL;
}

/*
 * 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 (char *date_string, enum datefmt datefmt, int *year, int *month,
            int *day, struct date *slctd_date)
{
  char sep = (datefmt == DATEFMT_ISO) ? '-' : '/';
  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 = in[n > 0 ? 0 : 1];
      d = in[n > 0 ? 1 : 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 = in[0];
      m = in[n - 1];
      d = in[n];
      break;
    default:
      return 0;
  }

  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;
}

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))
    ;
}