"Fossies" - the Fresh Open Source Software Archive

Member "calcurse-4.5.1/src/ui-day.c" (17 Oct 2019, 31301 Bytes) of package /linux/privat/calcurse-4.5.1.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "ui-day.c" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 4.5.0_vs_4.5.1.

    1 /*
    2  * Calcurse - text-based organizer
    3  *
    4  * Copyright (c) 2004-2017 calcurse Development Team <misc@calcurse.org>
    5  * All rights reserved.
    6  *
    7  * Redistribution and use in source and binary forms, with or without
    8  * modification, are permitted provided that the following conditions
    9  * are met:
   10  *
   11  *      - Redistributions of source code must retain the above
   12  *        copyright notice, this list of conditions and the
   13  *        following disclaimer.
   14  *
   15  *      - Redistributions in binary form must reproduce the above
   16  *        copyright notice, this list of conditions and the
   17  *        following disclaimer in the documentation and/or other
   18  *        materials provided with the distribution.
   19  *
   20  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
   21  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
   22  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
   23  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
   24  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
   25  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
   26  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
   27  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
   28  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
   29  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
   30  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   31  *
   32  * Send your feedback or comments to : misc@calcurse.org
   33  * Calcurse home page : http://calcurse.org
   34  *
   35  */
   36 
   37 #include "calcurse.h"
   38 
   39 struct day_item day_cut[38] = { {0, 0, 0, {NULL}} };
   40 
   41 /*
   42  * Set the selected day in the calendar from the selected item in the APP panel.
   43  */
   44 static void set_slctd_day(void)
   45 {
   46     ui_calendar_set_slctd_day(sec2date(ui_day_get_sel()->order));
   47 }
   48 
   49 /*
   50  * Return the selected APP item.
   51  * This is a pointer into the day vector and invalid after a day vector rebuild,
   52  * but the (order, item) data may be used to refind the object.
   53  */
   54 struct day_item *ui_day_get_sel(void)
   55 {
   56     if (day_item_count(0) <= 0)
   57         return &empty_day;
   58 
   59     return day_get_item(listbox_get_sel(&lb_apt));
   60 }
   61 
   62 /*
   63  * Set the selected item and day from the saved day_item.
   64  */
   65 void ui_day_find_sel(void)
   66 {
   67     int n;
   68 
   69     if ((n = day_sel_index()) != -1)
   70         listbox_set_sel(&lb_apt, n);
   71     set_slctd_day();
   72 }
   73 
   74 /*
   75  * Return the date (midnight) of the selected item in the APP panel.
   76  */
   77 time_t ui_day_sel_date(void)
   78 {
   79     return update_time_in_date(ui_day_get_sel()->order, 0, 0);
   80 }
   81 
   82 /*
   83  * If possible, move the selection to the beginning
   84  * of previous, current or next day.
   85  */
   86 static void daybegin(int dir)
   87 {
   88     dir = dir > 0 ? 1 : (dir < 0 ? -1 : 0);
   89     int sel = listbox_get_sel(&lb_apt);
   90 
   91     switch (dir) {
   92     case -1:
   93         while (day_get_item(sel)->type != DAY_HEADING)
   94             sel--;
   95         if (sel == 0)
   96             goto leave;
   97         sel--;
   98         while (day_get_item(sel)->type != DAY_HEADING)
   99             sel--;
  100         break;
  101     case 0:
  102         while (day_get_item(sel)->type != DAY_HEADING)
  103             sel--;
  104         break;
  105     case 1:
  106         while (day_get_item(sel)->type != END_SEPARATOR)
  107             sel++;
  108         if (sel == lb_apt.item_count - 1) {
  109             while (day_get_item(sel)->type != DAY_HEADING)
  110                 sel--;
  111             goto leave;
  112         } else
  113             sel++;
  114         break;
  115     }
  116   leave:
  117     listbox_set_sel(&lb_apt, sel);
  118     listbox_item_in_view(&lb_apt, sel);
  119     set_slctd_day();
  120 }
  121 
  122 /*
  123  * Request the user to enter a new start time.
  124  * Input: start time and duration in seconds.
  125  * Output: return value is new start time.
  126  * If move = 1, the new start time is for a move, and duration is passed on
  127  * for validation of the new end time.
  128  * If move = 0, the new end time is calculated by the caller.
  129  */
  130 static time_t day_edit_time(time_t start, long duration, int move)
  131 {
  132     const char *msg_time = _("Enter start date [%s] and/or time ([hh:mm] or [hhmm]):");
  133     const char *enter_str = _("Press [Enter] to continue");
  134     const char *fmt_msg = _("Invalid date or time.");
  135     char *input, *outstr;
  136     time_t ts;
  137     int ret;
  138 
  139     asprintf(&outstr, "%s %s", DATEFMT(conf.input_datefmt), "%H:%M");
  140     input = date_sec2date_str(start, outstr);
  141     mem_free(outstr);
  142     for (;;) {
  143         asprintf(&outstr, msg_time, DATEFMT_DESC(conf.input_datefmt));
  144         status_mesg(outstr, "");
  145         mem_free(outstr);
  146         if (updatestring(win[STA].p, &input, 0, 1) != GETSTRING_VALID) {
  147             ret = 0;
  148             break;
  149         }
  150         ts = start;
  151         if (parse_datetime(input, &ts, move ? duration : 0)) {
  152             ret = ts;
  153             break;
  154         }
  155         status_mesg(fmt_msg, enter_str);
  156         keys_wait_for_any_key(win[KEY].p);
  157     }
  158     mem_free(input);
  159     return ret;
  160 }
  161 
  162 /*
  163  * Change start time or move an item.
  164  * Input/output: start and dur.
  165  * If move = 0, end time is fixed, and the new duration is calculated
  166  * when the new start time is known.
  167  * If move = 1, duration is fixed, but passed on for validation of new end time.
  168  */
  169 static void update_start_time(time_t *start, long *dur, int move)
  170 {
  171     time_t newtime;
  172     const char *msg_wrong_time =
  173         _("Invalid time: start time must come before end time!");
  174     const char *msg_enter = _("Press [Enter] to continue");
  175 
  176     for (;;) {
  177         newtime = day_edit_time(*start, *dur, move);
  178         if (!newtime)
  179             break;
  180         if (move) {
  181             *start = newtime;
  182             break;
  183         } else {
  184             if (newtime <= *start + *dur) {
  185                 *dur -= (newtime - *start);
  186                 *start = newtime;
  187                 break;
  188             }
  189         }
  190         status_mesg(msg_wrong_time, msg_enter);
  191         keys_wgetch(win[KEY].p);
  192     }
  193     return;
  194 }
  195 
  196 /* Request the user to enter a new end time or duration. */
  197 static void update_duration(time_t *start, long *dur)
  198 {
  199     const char *msg_time =
  200         _("Enter end date (and/or time) or duration ('?' for input formats):");
  201     const char *msg_help_1 =
  202         _("Date: %s, year or month may be omitted.");
  203     const char *msg_help_2 =
  204         _("Time: hh:mm (hh: or :mm) or hhmm. Duration: +mm, +hh:mm, +??d??h??m.");
  205     const char *enter_str = _("Press [Enter] to continue");
  206     const char *fmt_msg_1 = _("Invalid time or duration.");
  207     const char *fmt_msg_2 = _("Invalid date: end time must come after start time.");
  208     time_t end;
  209     unsigned newdur;
  210     char *timestr, *outstr;
  211 
  212     end = *start + *dur;
  213     asprintf(&outstr, "%s %s", DATEFMT(conf.input_datefmt), "%H:%M");
  214     timestr = date_sec2date_str(end, outstr);
  215     mem_free(outstr);
  216     for (;;) {
  217         int ret, early = 0;
  218         status_mesg(msg_time, "");
  219         ret = updatestring(win[STA].p, &timestr, 0, 1);
  220         if (ret == GETSTRING_ESC) {
  221             mem_free(timestr);
  222             return;
  223         }
  224         if (*(timestr + strlen(timestr) - 1) == '?') {
  225             asprintf(&outstr, "%s %s", DATEFMT(conf.input_datefmt), "%H:%M");
  226             mem_free(timestr);
  227             timestr = date_sec2date_str(end, outstr);
  228             asprintf(&outstr, msg_help_1, DATEFMT_DESC(conf.input_datefmt));
  229             status_mesg(outstr, msg_help_2);
  230             mem_free(outstr);
  231             keys_wgetch(win[KEY].p);
  232             continue;
  233         }
  234         if (ret == GETSTRING_RET) {
  235             newdur = 0;
  236             break;
  237         }
  238         if (*timestr == '+') {
  239             if (parse_duration(timestr + 1, &newdur, *start)) {
  240                 newdur *= MININSEC;
  241                 break;
  242             }
  243         } else {
  244             int val = 1;
  245             ret = parse_datetime(timestr, &end, 0);
  246             /*
  247              * If same day and end time is earlier than start time,
  248              * assume that it belongs to the next day.
  249              */
  250             if (ret == PARSE_DATETIME_HAS_TIME && end < *start) {
  251                 end = date_sec_change(end, 0, 1);
  252                 /* Still valid? */
  253                 val = check_sec(&end);
  254             }
  255             if (ret && val && *start <= end) {
  256                 newdur = end - *start;
  257                 break;
  258             }
  259             /* Valid format, but too early? */
  260             early = ret && val && end < *start;
  261         }
  262         status_mesg(early ? fmt_msg_2 : fmt_msg_1 , enter_str);
  263         keys_wgetch(win[KEY].p);
  264     }
  265     mem_free(timestr);
  266     *dur = newdur;
  267 }
  268 
  269 static void update_desc(char **desc)
  270 {
  271     status_mesg(_("Enter the new item description:"), "");
  272     updatestring(win[STA].p, desc, 0, 1);
  273 }
  274 
  275 /* Edit the list of exception days for a recurrent item. */
  276 static int update_exc(llist_t *exc)
  277 {
  278     int updated = 0;
  279 
  280     if (!exc->head)
  281         return !updated;
  282     char *days;
  283     enum getstr ret;
  284 
  285     status_mesg(_("Exception days:"), "");
  286     days = recur_exc2str(exc);
  287     while (1) {
  288         ret = updatestring(win[STA].p, &days, 0, 1);
  289         if (ret == GETSTRING_VALID || ret == GETSTRING_RET) {
  290             if (recur_update_exc(exc, days)) {
  291                 updated = 1;
  292                 break;
  293             } else {
  294                 status_mesg(_("Invalid date format - try again:."), "");
  295                 mem_free(days);
  296                 days = recur_exc2str(exc);
  297             }
  298         } else if (ret == GETSTRING_ESC)
  299             break;
  300     }
  301     mem_free(days);
  302 
  303     return updated;
  304 }
  305 
  306 static void update_rept(struct rpt **rpt, const time_t start, llist_t *exc)
  307 {
  308     /* Pointers to dynamically allocated memory. */
  309     char *msg_rpt_current = NULL;
  310     char *msg_rpt_asktype = NULL;
  311     char *freqstr = NULL;
  312     char *timstr = NULL;
  313     char *outstr = NULL;
  314 
  315     /* Update repetition type. */
  316     int newtype;
  317     const char *msg_rpt_prefix = _("Enter the new repetition type:");
  318     const char *msg_rpt_daily = _("(d)aily");
  319     const char *msg_rpt_weekly = _("(w)eekly");
  320     const char *msg_rpt_monthly = _("(m)onthly");
  321     const char *msg_rpt_yearly = _("(y)early");
  322 
  323     /* Find the current repetition type. */
  324     const char *rpt_current;
  325     switch (recur_def2char((*rpt)->type)) {
  326     case 'D':
  327         rpt_current = msg_rpt_daily;
  328         break;
  329     case 'W':
  330         rpt_current = msg_rpt_weekly;
  331         break;
  332     case 'M':
  333         rpt_current = msg_rpt_monthly;
  334         break;
  335     case 'Y':
  336         rpt_current = msg_rpt_yearly;
  337         break;
  338     default:
  339         /* NOTREACHED, but makes the compiler happier. */
  340         rpt_current = msg_rpt_daily;
  341     }
  342     asprintf(&msg_rpt_current, _("(currently using %s)"), rpt_current);
  343     asprintf(&msg_rpt_asktype, "%s %s, %s, %s, %s? %s", msg_rpt_prefix,
  344          msg_rpt_daily, msg_rpt_weekly, msg_rpt_monthly,
  345          msg_rpt_yearly, msg_rpt_current);
  346     const char *msg_rpt_choice = _("[dwmy]");
  347     switch (status_ask_choice(msg_rpt_asktype, msg_rpt_choice, 4)) {
  348     case 1:
  349         newtype = 'D';
  350         break;
  351     case 2:
  352         newtype = 'W';
  353         break;
  354     case 3:
  355         newtype = 'M';
  356         break;
  357     case 4:
  358         newtype = 'Y';
  359         break;
  360     default:
  361         goto cleanup;
  362     }
  363 
  364     /* Update frequency. */
  365     int newfreq;
  366     const char *msg_wrong_freq = _("Invalid frequency.");
  367     const char *msg_enter = _("Press [Enter] to continue");
  368     do {
  369         status_mesg(_("Enter the repetition frequency:"), "");
  370         mem_free(freqstr);
  371         asprintf(&freqstr, "%d", (*rpt)->freq);
  372         if (updatestring(win[STA].p, &freqstr, 0, 1) !=
  373             GETSTRING_VALID) {
  374             goto cleanup;
  375         }
  376         newfreq = atoi(freqstr);
  377         if (newfreq == 0) {
  378             status_mesg(msg_wrong_freq, msg_enter);
  379             keys_wait_for_any_key(win[KEY].p);
  380         }
  381     }
  382     while (newfreq == 0);
  383 
  384     /* Update end date. */
  385     time_t newuntil;
  386     const char *msg_until_1 =
  387         _("Enter end date or duration ('?' for input formats):");
  388     const char *msg_help_1 =
  389         _("Date: %s (year or month may be omitted). Endless duration: 0.");
  390     const char *msg_help_2 =
  391         _("Duration in days: +dd. Duration in weeks and days: +??w??d.");
  392     const char *msg_wrong_time =
  393         _("Invalid date: end date must come after start date (%s).");
  394     const char *msg_wrong_date = _("Invalid date.");
  395 
  396     for (;;) {
  397         mem_free(timstr);
  398         if ((*rpt)->until)
  399             timstr = date_sec2date_str((*rpt)->until, DATEFMT(conf.input_datefmt));
  400         else
  401             timstr = mem_strdup("");
  402         status_mesg(msg_until_1, "");
  403         if (updatestring(win[STA].p, &timstr, 0, 1) == GETSTRING_ESC)
  404             goto cleanup;
  405         if (strcmp(timstr, "") == 0 || strcmp(timstr, "0") == 0) {
  406             newuntil = 0;
  407             break;
  408         }
  409         if (*(timstr + strlen(timstr) - 1) == '?') {
  410             mem_free(outstr);
  411             asprintf(&outstr, msg_help_1, DATEFMT_DESC(conf.input_datefmt));
  412             status_mesg(outstr, msg_help_2);
  413             keys_wgetch(win[KEY].p);
  414             continue;
  415         }
  416         if (*timstr == '+') {
  417             unsigned days;
  418             if (!parse_date_duration(timstr + 1, &days, start)) {
  419                 status_mesg(msg_wrong_date, msg_enter);
  420                 keys_wgetch(win[KEY].p);
  421                 continue;
  422             }
  423             /* Until is midnight of the day. */
  424             newuntil = date_sec_change(
  425                     update_time_in_date(start, 0, 0),
  426                     0, days
  427                    );
  428         } else {
  429             int year, month, day;
  430             if (!parse_date(timstr, conf.input_datefmt, &year,
  431                 &month, &day, ui_calendar_get_slctd_day())) {
  432                 status_mesg(msg_wrong_date, msg_enter);
  433                 keys_wgetch(win[KEY].p);
  434                 continue;
  435             }
  436             struct date d = { day, month, year };
  437             newuntil = date2sec(d, 0, 0);
  438         }
  439         /* Conmpare days (midnights) - until-day may equal start day. */
  440         if (newuntil >= update_time_in_date(start, 0, 0))
  441             break;
  442 
  443         mem_free(timstr);
  444         mem_free(outstr);
  445         timstr = date_sec2date_str(start, DATEFMT(conf.input_datefmt));
  446         asprintf(&outstr, msg_wrong_time, timstr);
  447         status_mesg(outstr, msg_enter);
  448         keys_wgetch(win[KEY].p);
  449     }
  450 
  451     /* Update exception list. */
  452     if (!update_exc(exc))
  453         goto cleanup;
  454 
  455     (*rpt)->type = recur_char2def(newtype);
  456     (*rpt)->freq = newfreq;
  457     (*rpt)->until = newuntil;
  458 
  459 cleanup:
  460     mem_free(msg_rpt_current);
  461     mem_free(msg_rpt_asktype);
  462     mem_free(freqstr);
  463     mem_free(timstr);
  464     mem_free(outstr);
  465 }
  466 
  467 /* Edit an already existing item. */
  468 void ui_day_item_edit(void)
  469 {
  470     struct recur_event *re;
  471     struct event *e;
  472     struct recur_apoint *ra;
  473     struct apoint *a;
  474     int need_check_notify = 0;
  475 
  476     if (day_item_count(0) <= 0)
  477         return;
  478 
  479     struct day_item *p = ui_day_get_sel();
  480 
  481     switch (p->type) {
  482     case RECUR_EVNT:
  483         re = p->item.rev;
  484         const char *choice_recur_evnt[2] = {
  485             _("Description"),
  486             _("Repetition")
  487         };
  488         switch (status_ask_simplechoice
  489             (_("Edit: "), choice_recur_evnt, 2)) {
  490         case 1:
  491             update_desc(&re->mesg);
  492             io_set_modified();
  493             break;
  494         case 2:
  495             update_rept(&re->rpt, re->day, &re->exc);
  496             io_set_modified();
  497             break;
  498         default:
  499             return;
  500         }
  501         break;
  502     case EVNT:
  503         e = p->item.ev;
  504         update_desc(&e->mesg);
  505         io_set_modified();
  506         break;
  507     case RECUR_APPT:
  508         ra = p->item.rapt;
  509         const char *choice_recur_appt[5] = {
  510             _("Start time"),
  511             _("End time"),
  512             _("Description"),
  513             _("Repetition"),
  514             _("Move"),
  515         };
  516         switch (status_ask_simplechoice
  517             (_("Edit: "), choice_recur_appt, 5)) {
  518         case 1:
  519             need_check_notify = 1;
  520             update_start_time(&ra->start, &ra->dur, ra->dur == 0);
  521             io_set_modified();
  522             break;
  523         case 2:
  524             update_duration(&ra->start, &ra->dur);
  525             io_set_modified();
  526             break;
  527         case 3:
  528             if (notify_bar())
  529                 need_check_notify =
  530                     notify_same_recur_item(ra);
  531             update_desc(&ra->mesg);
  532             io_set_modified();
  533             break;
  534         case 4:
  535             need_check_notify = 1;
  536             update_rept(&ra->rpt, ra->start, &ra->exc);
  537             io_set_modified();
  538             break;
  539         case 5:
  540             need_check_notify = 1;
  541             update_start_time(&ra->start, &ra->dur, 1);
  542             io_set_modified();
  543             break;
  544         default:
  545             return;
  546         }
  547         break;
  548     case APPT:
  549         a = p->item.apt;
  550         const char *choice_appt[4] = {
  551             _("Start time"),
  552             _("End time"),
  553             _("Description"),
  554             _("Move"),
  555         };
  556         switch (status_ask_simplechoice
  557             (_("Edit: "), choice_appt, 4)) {
  558         case 1:
  559             need_check_notify = 1;
  560             update_start_time(&a->start, &a->dur, a->dur == 0);
  561             io_set_modified();
  562             break;
  563         case 2:
  564             update_duration(&a->start, &a->dur);
  565             io_set_modified();
  566             break;
  567         case 3:
  568             if (notify_bar())
  569                 need_check_notify =
  570                     notify_same_item(a->start);
  571             update_desc(&a->mesg);
  572             io_set_modified();
  573             break;
  574         case 4:
  575             need_check_notify = 1;
  576             update_start_time(&a->start, &a->dur, 1);
  577             io_set_modified();
  578             break;
  579         default:
  580             return;
  581         }
  582         break;
  583     default:
  584         break;
  585     }
  586 
  587     ui_calendar_monthly_view_cache_set_invalid();
  588 
  589     if (need_check_notify)
  590         notify_check_next_app(1);
  591 }
  592 
  593 /* Pipe an appointment or event to an external program. */
  594 void ui_day_item_pipe(void)
  595 {
  596     char cmd[BUFSIZ] = "";
  597     char const *arg[] = { cmd, NULL };
  598     int pout;
  599     int pid;
  600     FILE *fpout;
  601 
  602     if (day_item_count(0) <= 0)
  603         return;
  604 
  605     struct day_item *p = ui_day_get_sel();
  606 
  607     status_mesg(_("Pipe item to external command:"), "");
  608     if (getstring(win[STA].p, cmd, BUFSIZ, 0, 1) != GETSTRING_VALID)
  609         return;
  610 
  611     wins_prepare_external();
  612     if ((pid = shell_exec(NULL, &pout, *arg, arg))) {
  613         fpout = fdopen(pout, "w");
  614 
  615         switch (p->type) {
  616         case RECUR_EVNT:
  617             recur_event_write(p->item.rev, fpout);
  618             break;
  619         case EVNT:
  620             event_write(p->item.ev, fpout);
  621             break;
  622         case RECUR_APPT:
  623             recur_apoint_write(p->item.rapt, fpout);
  624             break;
  625         case APPT:
  626             apoint_write(p->item.apt, fpout);
  627             break;
  628         default:
  629             break;
  630         }
  631 
  632         fclose(fpout);
  633         child_wait(NULL, &pout, pid);
  634         press_any_key();
  635     }
  636     wins_unprepare_external();
  637 }
  638 
  639 /*
  640  * Add an item in either the appointment or the event list,
  641  * depending if the start time is entered or not.
  642  */
  643 void ui_day_item_add(void)
  644 {
  645 #define LTIME 17
  646     const char *mesg_1 =
  647         _("Enter start time ([hh:mm] or [hhmm]), leave blank for an all-day event:");
  648     const char *mesg_2 =
  649         _("Enter end time as date (and/or time) or duration ('?' for input formats):");
  650     const char *mesg_3 = _("Enter description:");
  651     const char *mesg_help_1 =
  652         _("Date: %s (and/or time), year or month may be omitted.");
  653     const char *mesg_help_2 =
  654         _("Time: hh:mm (hh: or :mm) or hhmm. Duration: +mm, +hh:mm, +??d??h??m.");
  655     const char *format_message_1 = _("Invalid start time.");
  656     const char *format_message_2 = _("Invalid time or duration.");
  657     const char *format_message_3 = _("Invalid date: end time must come after start time.");
  658     const char *enter_str = _("Press [Enter] to continue");
  659     char item_time[LTIME] = "";
  660     char item_mesg[BUFSIZ] = "";
  661     time_t start = ui_day_sel_date(), end, saved = start;
  662     unsigned dur;
  663     int is_appointment = 1;
  664     union aptev_ptr item;
  665 
  666     /* Get the starting time */
  667     for (;;) {
  668         status_mesg(mesg_1, "");
  669         if (getstring(win[STA].p, item_time, LTIME, 0, 1) ==
  670             GETSTRING_ESC)
  671             return;
  672         if (strlen(item_time) == 0) {
  673             is_appointment = 0;
  674             break;
  675         }
  676         start = saved;
  677         /* Only time, no date, allowed. */
  678         if (parse_datetime(item_time, &start, 0) ==
  679             PARSE_DATETIME_HAS_TIME) {
  680             break;
  681         }
  682         status_mesg(format_message_1, enter_str);
  683         keys_wait_for_any_key(win[KEY].p);
  684     }
  685 
  686     /*
  687      * Check if an event or appointment is entered,
  688      * depending on the starting time, and record the
  689      * corresponding item.
  690      */
  691     if (is_appointment) {   /* Get the appointment duration */
  692         item_time[0] = '\0';
  693         for (;;) {
  694             int early = 0;
  695             status_mesg(mesg_2, "");
  696             if (getstring(win[STA].p, item_time, LTIME, 0, 1) ==
  697                 GETSTRING_ESC)
  698                 return;
  699             if (*item_time == '?') {
  700                 char *outstr;
  701                 item_time[0] = '\0';
  702                 asprintf(&outstr, mesg_help_1, DATEFMT_DESC(conf.input_datefmt));
  703                 status_mesg(outstr, mesg_help_2);
  704                 mem_free(outstr);
  705                 wgetch(win[KEY].p);
  706                 continue;
  707             }
  708             if (strlen(item_time) == 0) {
  709                 dur = 0;
  710                 break;
  711             }
  712             if (*item_time == '+') {
  713                 if (parse_duration(item_time + 1, &dur, start)) {
  714                     dur *= MININSEC;
  715                     break;
  716                 }
  717             } else {
  718                 int ret, val = 1;
  719                 /* Same day? */
  720                 end = start;
  721                 ret = parse_datetime(item_time, &end, 0);
  722                 /*
  723                  * If same day and end time is earlier than start time,
  724                  * assume that it belongs to the next day.
  725                  */
  726                 if (ret == PARSE_DATETIME_HAS_TIME && end < start) {
  727                     end = date_sec_change(end, 0, 1);
  728                     /* Still valid? */
  729                     val = check_sec(&end);
  730                 }
  731                 if (ret && val && start <= end) {
  732                     dur = end - start;
  733                     break;
  734                 }
  735                 /* Valid format, but too early? */
  736                 early = ret && val && end < start;
  737                     
  738             }
  739             status_mesg(early ? format_message_3 : format_message_2 , enter_str);
  740             keys_wgetch(win[KEY].p);
  741         }
  742     }
  743 
  744     status_mesg(mesg_3, "");
  745     if (getstring(win[STA].p, item_mesg, BUFSIZ, 0, 1) == GETSTRING_VALID) {
  746         if (is_appointment) {
  747             item.apt = apoint_new(item_mesg, 0L, start, dur, 0L);
  748             if (notify_bar())
  749                 notify_check_added(item_mesg, start, 0L);
  750         } else {
  751             item.ev = event_new(item_mesg, 0L, start, 1);
  752         }
  753         io_set_modified();
  754         /* Set the selected APP item. */
  755         struct day_item d = empty_day;
  756         d.order = start;
  757         d.item = item;
  758         day_set_sel_data(&d);
  759     }
  760 
  761     ui_calendar_monthly_view_cache_set_invalid();
  762 
  763     wins_erase_status_bar();
  764 }
  765 
  766 /* Delete an item from the appointment list. */
  767 void ui_day_item_delete(unsigned reg)
  768 {
  769     const char *del_app_str =
  770         _("Do you really want to delete this item?");
  771 
  772     const char *erase_warning =
  773         _("This item is recurrent. "
  774           "Delete (a)ll occurences or just this (o)ne?");
  775     const char *erase_choices = _("[ao]");
  776     const int nb_erase_choices = 2;
  777 
  778     const char *note_warning =
  779         _("This item has a note attached to it. "
  780           "Delete (i)tem or just its (n)ote?");
  781     const char *note_choices = _("[in]");
  782     const int nb_note_choices = 2;
  783     time_t occurrence;
  784 
  785     if (day_item_count(0) <= 0)
  786         return;
  787 
  788     struct day_item *p = ui_day_get_sel();
  789 
  790     if (conf.confirm_delete) {
  791         if (status_ask_bool(del_app_str) != 1) {
  792             wins_erase_status_bar();
  793             return;
  794         }
  795     }
  796 
  797     if (day_item_get_note(p)) {
  798         switch (status_ask_choice
  799             (note_warning, note_choices, nb_note_choices)) {
  800         case 1:
  801             break;
  802         case 2:
  803             day_item_erase_note(p);
  804             io_set_modified();
  805             return;
  806         default:    /* User escaped */
  807             return;
  808         }
  809     }
  810 
  811     if (p->type == RECUR_EVNT || p->type == RECUR_APPT) {
  812         switch (status_ask_choice
  813             (erase_warning, erase_choices, nb_erase_choices)) {
  814         case 1:
  815             break;
  816         case 2:
  817             if (p->type == RECUR_EVNT) {
  818                 day_item_add_exc(p, ui_day_sel_date());
  819             } else {
  820                 recur_apoint_find_occurrence(p->item.rapt,
  821                                  ui_day_sel_date(),
  822                                  &occurrence);
  823                 day_item_add_exc(p, occurrence);
  824             }
  825 
  826             io_set_modified();
  827             ui_calendar_monthly_view_cache_set_invalid();
  828             /* Keep the selection on the same day. */
  829             day_set_sel_data(
  830                 day_get_item(listbox_get_sel(&lb_apt) - 1)
  831             );
  832             return;
  833         default:
  834             return;
  835         }
  836     }
  837 
  838     ui_day_item_cut_free(reg);
  839     p = day_cut_item(listbox_get_sel(&lb_apt));
  840     day_cut[reg].type = p->type;
  841     day_cut[reg].item = p->item;
  842     /* Keep the selection on the same day. */
  843     day_set_sel_data(day_get_item(listbox_get_sel(&lb_apt) - 1));
  844     io_set_modified();
  845     ui_calendar_monthly_view_cache_set_invalid();
  846 }
  847 
  848 /*
  849  * Ask user for repetition characteristics:
  850  *  o repetition type: daily, weekly, monthly, yearly
  851  *  o repetition frequency: every X days, weeks, ...
  852  *  o repetition end date
  853  * and then delete the selected item to recreate it as a recurrent one
  854  */
  855 void ui_day_item_repeat(void)
  856 {
  857     char user_input[BUFSIZ] = "";
  858     const char *msg_rpt_prefix = _("Enter the repetition type:");
  859     const char *msg_rpt_daily = _("(d)aily");
  860     const char *msg_rpt_weekly = _("(w)eekly");
  861     const char *msg_rpt_monthly = _("(m)onthly");
  862     const char *msg_rpt_yearly = _("(y)early");
  863     const char *msg_type_choice = _("[dwmy]");
  864     const char *mesg_freq_1 = _("Enter the repetition frequency:");
  865     const char *mesg_wrong_freq = _("Invalid frequency.");
  866     const char *mesg_until_1 = _("Enter end date or duration ('?' for input formats):");
  867     const char *mesg_help_1 = _("Date: %s (year or month may be omitted). Endless duration: '0'.");
  868     const char *mesg_help_2 = _("Duration in days: +dd. Duration in weeks and days: +??w??d.");
  869     const char *mesg_wrong_1 = _("Invalid date.");
  870     const char *mesg_wrong_2 = _("Press [ENTER] to continue.");
  871     const char *wrong_type_1 = _("This item is already a repeated one.");
  872     const char *wrong_type_2 = _("Press [ENTER] to continue.");
  873     const char *mesg_older = _("Invalid date: end date must come after start date (%s).");
  874 
  875     char *msg_asktype;
  876     asprintf(&msg_asktype, "%s %s, %s, %s, %s", msg_rpt_prefix,
  877          msg_rpt_daily, msg_rpt_weekly, msg_rpt_monthly,
  878          msg_rpt_yearly);
  879 
  880     int type = 0, freq = 0;
  881     int item_nb;
  882     struct day_item *p;
  883     struct recur_apoint *ra;
  884     time_t until;
  885     unsigned days;
  886 
  887     if (day_item_count(0) <= 0)
  888         goto cleanup;
  889 
  890     item_nb = listbox_get_sel(&lb_apt);
  891     p = day_get_item(item_nb);
  892     if (p->type != APPT && p->type != EVNT) {
  893         status_mesg(wrong_type_1, wrong_type_2);
  894         keys_wait_for_any_key(win[KEY].p);
  895         goto cleanup;
  896     }
  897 
  898     switch (status_ask_choice(msg_asktype, msg_type_choice, 4)) {
  899     case 1:
  900         type = RECUR_DAILY;
  901         break;
  902     case 2:
  903         type = RECUR_WEEKLY;
  904         break;
  905     case 3:
  906         type = RECUR_MONTHLY;
  907         break;
  908     case 4:
  909         type = RECUR_YEARLY;
  910         break;
  911     default:
  912         goto cleanup;
  913     }
  914 
  915     while (freq == 0) {
  916         status_mesg(mesg_freq_1, "");
  917         if (getstring(win[STA].p, user_input, BUFSIZ, 0, 1) !=
  918             GETSTRING_VALID)
  919             goto cleanup;
  920         freq = atoi(user_input);
  921         if (freq == 0) {
  922             status_mesg(mesg_wrong_freq, wrong_type_2);
  923             keys_wait_for_any_key(win[KEY].p);
  924         }
  925         user_input[0] = '\0';
  926     }
  927 
  928     char *outstr, *datestr;
  929     for (;;) {
  930         status_mesg(mesg_until_1, "");
  931         if (getstring(win[STA].p, user_input, BUFSIZ, 0, 1) == GETSTRING_ESC)
  932             goto cleanup;
  933         if (strcmp(user_input, "") == 0 || strcmp(user_input, "0") == 0) {
  934             until = 0;
  935             break;
  936         }
  937         if (*user_input == '?') {
  938             user_input[0] = '\0';
  939             asprintf(&outstr, mesg_help_1, DATEFMT_DESC(conf.input_datefmt));
  940             status_mesg(outstr, mesg_help_2);
  941             mem_free(outstr);
  942             wgetch(win[KEY].p);
  943             continue;
  944         }
  945         if (*user_input == '+') {
  946             if (!parse_date_duration(user_input + 1, &days, p->start)) {
  947                 status_mesg(mesg_wrong_1, mesg_wrong_2);
  948                 keys_wgetch(win[KEY].p);
  949                 continue;
  950             }
  951             /* Until is midnight of the day. */
  952             until = date_sec_change(
  953                     update_time_in_date(p->start, 0, 0),
  954                     0, days
  955                 );
  956         } else {
  957             int year, month, day;
  958             if (!parse_date(user_input, conf.input_datefmt,
  959                 &year, &month, &day, ui_calendar_get_slctd_day())) {
  960                 status_mesg(mesg_wrong_1, mesg_wrong_2);
  961                 keys_wgetch(win[KEY].p);
  962                 continue;
  963             }
  964             struct date d = { day, month, year };
  965             until = date2sec(d, 0, 0);
  966         }
  967         /* Compare days (midnights) - until-day may equal start day. */
  968         if (until >= get_slctd_day())
  969             break;
  970 
  971         datestr = date_sec2date_str(p->start, DATEFMT(conf.input_datefmt));
  972         asprintf(&outstr, mesg_older, datestr);
  973         status_mesg(outstr, wrong_type_2);
  974         mem_free(datestr);
  975         mem_free(outstr);
  976         keys_wgetch(win[KEY].p);
  977     }
  978 
  979     /* Set the selected APP item. */
  980     struct day_item d = empty_day;
  981     if (p->type == EVNT) {
  982         struct event *ev = p->item.ev;
  983         d.item.rev = recur_event_new(ev->mesg, ev->note, ev->day,
  984                          ev->id, type, freq, until, NULL);
  985     } else if (p->type == APPT) {
  986         struct apoint *apt = p->item.apt;
  987         d.item.rapt = ra = recur_apoint_new(apt->mesg, apt->note,
  988                             apt->start, apt->dur,
  989                             apt->state, type, freq,
  990                             until, NULL);
  991         if (notify_bar())
  992             notify_check_repeated(ra);
  993     } else {
  994         EXIT(_("wrong item type"));
  995         /* NOTREACHED */
  996     }
  997     day_set_sel_data(&d);
  998 
  999     ui_day_item_cut_free(REG_BLACK_HOLE);
 1000     p = day_cut_item(item_nb);
 1001     day_cut[REG_BLACK_HOLE].type = p->type;
 1002     day_cut[REG_BLACK_HOLE].item = p->item;
 1003     io_set_modified();
 1004 
 1005     ui_calendar_monthly_view_cache_set_invalid();
 1006 
 1007 cleanup:
 1008     mem_free(msg_asktype);
 1009 }
 1010 
 1011 /* Free the current cut item, if any. */
 1012 void ui_day_item_cut_free(unsigned reg)
 1013 {
 1014     if (!day_cut[reg].type) {
 1015         /* No previously cut item, don't free anything. */
 1016         return;
 1017     }
 1018 
 1019     switch (day_cut[reg].type) {
 1020     case APPT:
 1021         apoint_free(day_cut[reg].item.apt);
 1022         break;
 1023     case EVNT:
 1024         event_free(day_cut[reg].item.ev);
 1025         break;
 1026     case RECUR_APPT:
 1027         recur_apoint_free(day_cut[reg].item.rapt);
 1028         break;
 1029     case RECUR_EVNT:
 1030         recur_event_free(day_cut[reg].item.rev);
 1031         break;
 1032     default:
 1033         break;
 1034     }
 1035 }
 1036 
 1037 /* Copy an item, so that it can be pasted somewhere else later. */
 1038 void ui_day_item_copy(unsigned reg)
 1039 {
 1040     if (day_item_count(0) <= 0 || reg == REG_BLACK_HOLE)
 1041         return;
 1042 
 1043     struct day_item *item = ui_day_get_sel();
 1044     ui_day_item_cut_free(reg);
 1045     day_item_fork(item, &day_cut[reg]);
 1046 }
 1047 
 1048 /* Paste a previously cut item. */
 1049 void ui_day_item_paste(unsigned reg)
 1050 {
 1051     struct day_item day = empty_day;
 1052 
 1053     if (reg == REG_BLACK_HOLE || !day_cut[reg].type)
 1054         return;
 1055 
 1056     day_item_fork(&day_cut[reg], &day);
 1057     day_paste_item(&day, ui_day_sel_date());
 1058     day_set_sel_data(&day);
 1059     io_set_modified();
 1060     ui_calendar_monthly_view_cache_set_invalid();
 1061 }
 1062 
 1063 void ui_day_load_items(void)
 1064 {
 1065     listbox_load_items(&lb_apt, day_item_count(1));
 1066 }
 1067 
 1068 void ui_day_sel_reset(void)
 1069 {
 1070     listbox_set_sel(&lb_apt, 0);
 1071     /* Make the day visible. */
 1072     if (lb_apt.item_sel)
 1073         listbox_item_in_view(&lb_apt, lb_apt.item_sel - 1);
 1074     set_slctd_day();
 1075 }
 1076 
 1077 int ui_day_sel_move(int delta)
 1078 {
 1079     int ret;
 1080 
 1081     ret = listbox_sel_move(&lb_apt, delta);
 1082     /* When moving up, make the line above visible. */
 1083     if (delta < 0 && ret && lb_apt.item_sel)
 1084         listbox_item_in_view(&lb_apt, lb_apt.item_sel - 1);
 1085     set_slctd_day();
 1086     return ret;
 1087 }
 1088 
 1089 /*
 1090  * Move the selection to the beginning of the current day or
 1091  * the day n days before or after.
 1092  */
 1093 void ui_day_sel_daybegin(int n)
 1094 {
 1095     if (n == 0) {
 1096         daybegin(0);
 1097         return;
 1098     }
 1099     int dir = n > 0 ? 1 : -1;
 1100     n = dir * n;
 1101     for (int i = 0; i < n; i++)
 1102         daybegin(dir);
 1103 }
 1104 
 1105 /*
 1106  * Move the selection to the end of the current day.
 1107  */
 1108 void ui_day_sel_dayend(void)
 1109 {
 1110     int sel = listbox_get_sel(&lb_apt);
 1111 
 1112     while (day_get_item(sel)->type != END_SEPARATOR)
 1113         sel++;
 1114     while (lb_apt.type[sel] != LISTBOX_ROW_TEXT)
 1115         sel--;
 1116     listbox_set_sel(&lb_apt, sel);
 1117     listbox_item_in_view(&lb_apt, sel);
 1118 }
 1119 
 1120 static char *fmt_day_heading(time_t date)
 1121 {
 1122     struct tm tm;
 1123     struct string s;
 1124 
 1125     localtime_r(&date, &tm);
 1126     string_init(&s);
 1127     string_catftime(&s, conf.day_heading, &tm);
 1128     return string_buf(&s);
 1129 }
 1130 
 1131 /* Display appointments in the corresponding panel. */
 1132 void ui_day_draw(int n, WINDOW *win, int y, int hilt, void *cb_data)
 1133 {
 1134     struct day_item *item = day_get_item(n);
 1135     /* The item order always indicates the date. */
 1136     time_t date = update_time_in_date(item->order, 0, 0);
 1137     int width = lb_apt.sw.w - 2, is_slctd;
 1138 
 1139     hilt = hilt && (wins_slctd() == APP);
 1140     if (item->type == EVNT || item->type == RECUR_EVNT) {
 1141         day_display_item(item, win, !hilt, width - 1, y, 1);
 1142     } else if (item->type == APPT || item->type == RECUR_APPT) {
 1143         day_display_item_date(item, win, !hilt, date, y, 1);
 1144         day_display_item(item, win, !hilt, width - 1, y + 1, 1);
 1145     } else if (item->type == DAY_HEADING) {
 1146         is_slctd = conf.multiple_days && (date == get_slctd_day());
 1147         if (conf.header_line && n) {
 1148             wmove(win, y, 0);
 1149             whline(win, ACS_HLINE, width);
 1150         }
 1151         char *buf = fmt_day_heading(date);
 1152         utf8_chop(buf, width);
 1153         custom_apply_attr(win, is_slctd ? ATTR_MIDDLE : ATTR_HIGHEST);
 1154         mvwprintw(win, y + (conf.header_line && n),
 1155             conf.heading_pos == RIGHT ? width - utf8_strwidth(buf) - 1 :
 1156             conf.heading_pos == LEFT ? 1 :
 1157             (width - utf8_strwidth(buf)) / 2, "%s", buf);
 1158         custom_remove_attr(win, is_slctd ? ATTR_MIDDLE : ATTR_HIGHEST);
 1159         mem_free(buf);
 1160     }
 1161 }
 1162 
 1163 enum listbox_row_type ui_day_row_type(int n, void *cb_data)
 1164 {
 1165     struct day_item *item = day_get_item(n);
 1166 
 1167     if (item->type == DAY_HEADING ||
 1168         item->type == EVNT_SEPARATOR ||
 1169         item->type == EMPTY_SEPARATOR ||
 1170         item->type == END_SEPARATOR)
 1171         return LISTBOX_ROW_CAPTION;
 1172     else
 1173         return LISTBOX_ROW_TEXT;
 1174 }
 1175 
 1176 int ui_day_height(int n, void *cb_data)
 1177 {
 1178     struct day_item *item = day_get_item(n);
 1179 
 1180     if (item->type == DAY_HEADING)
 1181         return 1 + (conf.header_line && n);
 1182     else if (item->type == APPT || item->type == RECUR_APPT)
 1183         return conf.empty_appt_line ? 3 : 2;
 1184     else if (item->type == EVNT_SEPARATOR)
 1185         return conf.event_separator;
 1186     else if (item->type == END_SEPARATOR)
 1187         return conf.day_separator;
 1188     else
 1189         return 1;
 1190 }
 1191 
 1192 /* Updates the Appointment panel */
 1193 void ui_day_update_panel(int hilt)
 1194 {
 1195     listbox_display(&lb_apt, hilt);
 1196 }
 1197 
 1198 void ui_day_popup_item(void)
 1199 {
 1200     if (day_item_count(0) <= 0)
 1201         return;
 1202 
 1203     struct day_item *item = ui_day_get_sel();
 1204     day_popup_item(item);
 1205 }
 1206 
 1207 void ui_day_flag(void)
 1208 {
 1209     if (day_item_count(0) <= 0)
 1210         return;
 1211 
 1212     struct day_item *item = ui_day_get_sel();
 1213     day_item_switch_notify(item);
 1214     io_set_modified();
 1215 }
 1216 
 1217 void ui_day_view_note(void)
 1218 {
 1219     if (day_item_count(0) <= 0)
 1220         return;
 1221 
 1222     struct day_item *item = ui_day_get_sel();
 1223     day_view_note(item, conf.pager);
 1224 }
 1225 
 1226 void ui_day_edit_note(void)
 1227 {
 1228     if (day_item_count(0) <= 0)
 1229         return;
 1230 
 1231     struct day_item *item = ui_day_get_sel();
 1232     day_edit_note(item, conf.editor);
 1233     io_set_modified();
 1234 }