6 * qsort filter to move completed tasks to bottom of task list
8 int task_completed_cmp(const void *vtask1, const void *vtask2) {
9 disp_cal *Task1 = (disp_cal *) GetSearchPayload(vtask1);
11 /* disp_cal * Task2 = (disp_cal *)GetSearchPayload(vtask2); */
13 icalproperty_status t1 = icalcomponent_get_status((Task1)->cal);
14 /* icalproperty_status t2 = icalcomponent_get_status(((struct disp_cal *)task2)->cal); */
16 if (t1 == ICAL_STATUS_COMPLETED)
23 * Helper function for do_tasks_view(). Returns the due date/time of a vtodo.
25 time_t get_task_due_date(icalcomponent * vtodo, int *is_date) {
33 * If we're looking at a fully encapsulated VCALENDAR
34 * rather than a VTODO component, recurse into the data
35 * structure until we get a VTODO.
37 if (icalcomponent_isa(vtodo) == ICAL_VCALENDAR_COMPONENT) {
38 return get_task_due_date(icalcomponent_get_first_component(vtodo, ICAL_VTODO_COMPONENT), is_date);
41 p = icalcomponent_get_first_property(vtodo, ICAL_DUE_PROPERTY);
43 struct icaltimetype t = icalproperty_get_due(p);
47 return (icaltime_as_timet(t));
55 * Compare the due dates of two tasks (this is for sorting)
57 int task_due_cmp(const void *vtask1, const void *vtask2) {
58 disp_cal *Task1 = (disp_cal *) GetSearchPayload(vtask1);
59 disp_cal *Task2 = (disp_cal *) GetSearchPayload(vtask2);
64 t1 = get_task_due_date(Task1->cal, NULL);
65 t2 = get_task_due_date(Task2->cal, NULL);
74 * do the whole task view stuff
76 int tasks_RenderView_or_Tail(SharedMessageStatus * Stat, void **ViewSpecific, long oper) {
87 wc_printf("<table class=\"calendar_view_background\"><tbody id=\"taskview\">\n<tr>\n<th>");
88 wc_printf(_("Completed?"));
89 wc_printf("</th><th>");
90 wc_printf(_("Name of task"));
91 wc_printf("</th><th>");
92 wc_printf(_("Date due"));
93 wc_printf("</th><th>");
94 wc_printf(_("Category"));
95 wc_printf(" (<select id=\"selectcategory\"><option value=\"showall\">%s</option></select>)</th></tr>\n", _("Show All"));
97 nItems = GetCount(WC->disp_cal_items);
99 /* Sort them if necessary
101 SortByPayload(WC->disp_cal_items, task_due_cmp);
103 * this shouldn't be neccessary, since we sort by the start time.
106 /* And then again, by completed */
108 SortByPayload(WC->disp_cal_items, task_completed_cmp);
111 Pos = GetNewHashPos(WC->disp_cal_items, 0);
112 while (GetNextHashPos(WC->disp_cal_items, Pos, &hklen, &HashKey, &vCal)) {
113 icalproperty_status todoStatus;
116 Cal = (disp_cal *) vCal;
117 wc_printf("<tr><td>");
118 todoStatus = icalcomponent_get_status(Cal->cal);
119 wc_printf("<input type=\"checkbox\" name=\"completed\" value=\"completed\" ");
120 if (todoStatus == ICAL_STATUS_COMPLETED) {
121 wc_printf("checked=\"checked\" ");
123 wc_printf("disabled=\"disabled\">\n</td><td>");
124 p = icalcomponent_get_first_property(Cal->cal, ICAL_SUMMARY_PROPERTY);
125 wc_printf("<a href=\"display_edit_task?msgnum=%ld?taskrm=", Cal->cal_msgnum);
126 urlescputs(ChrPtr(WC->CurRoom.name));
128 /* wc_printf("<img align=middle "
129 "src=\"static/taskmanag_16x.gif\" border=0> "); */
131 escputs((char *) icalproperty_get_comment(p));
134 wc_printf("</td>\n");
136 due = get_task_due_date(Cal->cal, &is_date);
137 wc_printf("<td><span");
139 webcit_fmt_date(buf, SIZ, due, is_date ? DATEFMT_RAWDATE : DATEFMT_FULL);
140 wc_printf(">%s", buf);
145 wc_printf("</span></td>");
147 p = icalcomponent_get_first_property(Cal->cal, ICAL_CATEGORIES_PROPERTY);
149 escputs((char *) icalproperty_get_categories(p));
155 wc_printf("</tbody></table>\n");
158 DeleteHash(&WC->disp_cal_items);
165 * Display a task by itself (for editing)
167 void display_edit_individual_task(icalcomponent * supplied_vtodo, long msgnum, char *from, int unread, calview * calv) {
168 icalcomponent *vtodo;
170 struct icaltimetype IcalTime;
171 int created_new_vtodo = 0;
172 icalproperty_status todoStatus;
174 if (supplied_vtodo != NULL) {
175 vtodo = supplied_vtodo;
178 * It's safe to convert to UTC here because there are no recurrences to worry about.
180 ical_dezonify(vtodo);
183 * If we're looking at a fully encapsulated VCALENDAR
184 * rather than a VTODO component, attempt to use the first
185 * relevant VTODO subcomponent. If there is none, the
186 * NULL returned by icalcomponent_get_first_component() will
187 * tell the next iteration of this function to create a
190 if (icalcomponent_isa(vtodo) == ICAL_VCALENDAR_COMPONENT) {
191 display_edit_individual_task(icalcomponent_get_first_component(vtodo, ICAL_VTODO_COMPONENT),
192 msgnum, from, unread, calv);
197 vtodo = icalcomponent_new(ICAL_VTODO_COMPONENT);
198 created_new_vtodo = 1;
201 /* TODO: Can we take all this and move it into a template? */
202 output_headers(1, 1, 1, 0, 0, 0);
203 wc_printf("<!-- start task edit form -->");
204 p = icalcomponent_get_first_property(vtodo, ICAL_SUMMARY_PROPERTY);
205 /* Get summary early for title */
206 wc_printf("<div class=\"box\">\n");
207 wc_printf("<div class=\"boxlabel\">");
208 wc_printf(_("Edit task"));
211 escputs((char *) icalproperty_get_comment(p));
215 wc_printf("<div class=\"boxcontent\">\n");
216 wc_printf("<FORM METHOD=\"POST\" action=\"save_task\">\n");
217 wc_printf("<div style=\"display: none;\">\n ");
219 wc_printf("<input type=\"hidden\" name=\"go\" value=\"");
220 StrEscAppend(WC->WBuf, WC->CurRoom.name, NULL, 0, 0);
223 wc_printf("<input type=\"hidden\" name=\"nonce\" value=\"%d\">\n", WC->nonce);
224 wc_printf("<INPUT TYPE=\"hidden\" NAME=\"msgnum\" VALUE=\"%ld\">\n", msgnum);
225 wc_printf("<INPUT TYPE=\"hidden\" NAME=\"return_to_summary\" VALUE=\"%d\">\n", ibstr("return_to_summary"));
227 wc_printf("<table class=\"calendar_background\"><tr><td>");
228 wc_printf("<TABLE STYLE=\"border: none;\">\n");
230 wc_printf("<TR><TD>");
231 wc_printf(_("Summary:"));
232 wc_printf("</TD><TD>" "<INPUT TYPE=\"text\" NAME=\"summary\" " "MAXLENGTH=\"64\" SIZE=\"64\" VALUE=\"");
233 p = icalcomponent_get_first_property(vtodo, ICAL_SUMMARY_PROPERTY);
235 escputs((char *) icalproperty_get_comment(p));
237 wc_printf("\"></TD></TR>\n");
239 wc_printf("<TR><TD>");
240 wc_printf(_("Start date:"));
241 wc_printf("</TD><TD>");
242 p = icalcomponent_get_first_property(vtodo, ICAL_DTSTART_PROPERTY);
243 wc_printf("<INPUT TYPE=\"CHECKBOX\" NAME=\"nodtstart\" ID=\"nodtstart\" VALUE=\"NODTSTART\" ");
245 wc_printf("CHECKED=\"CHECKED\"");
248 wc_printf(_("No date"));
251 wc_printf("<span ID=\"dtstart_date\">");
255 IcalTime = icalproperty_get_dtstart(p);
258 IcalTime = icaltime_current_time_with_zone(get_default_icaltimezone());
259 display_icaltimetype_as_webform(&IcalTime, "dtstart", 0);
261 wc_printf("<INPUT TYPE=\"CHECKBOX\" NAME=\"dtstart_time_assoc\" ID=\"dtstart_time_assoc\" VALUE=\"yes\"");
262 if (!IcalTime.is_date) {
263 wc_printf("CHECKED=\"CHECKED\"");
266 wc_printf(_("Time associated"));
267 wc_printf("</span></TD></TR>\n");
269 wc_printf("<TR><TD>");
270 wc_printf(_("Due date:"));
271 wc_printf("</TD><TD>");
272 p = icalcomponent_get_first_property(vtodo, ICAL_DUE_PROPERTY);
273 wc_printf("<INPUT TYPE=\"CHECKBOX\" NAME=\"nodue\" ID=\"nodue\" VALUE=\"NODUE\"");
275 wc_printf("CHECKED=\"CHECKED\"");
278 wc_printf(_("No date"));
280 wc_printf("<span ID=\"due_date\">\n");
284 IcalTime = icalproperty_get_due(p);
287 IcalTime = icaltime_current_time_with_zone(get_default_icaltimezone());
288 display_icaltimetype_as_webform(&IcalTime, "due", 0);
290 wc_printf("<INPUT TYPE=\"CHECKBOX\" NAME=\"due_time_assoc\" ID=\"due_time_assoc\" VALUE=\"yes\"");
291 if (!IcalTime.is_date) {
292 wc_printf("CHECKED=\"CHECKED\"");
295 wc_printf(_("Time associated"));
296 wc_printf("</span></TD></TR>\n");
297 todoStatus = icalcomponent_get_status(vtodo);
298 wc_printf("<TR><TD>\n");
299 wc_printf(_("Completed:"));
300 wc_printf("</TD><TD>");
301 wc_printf("<INPUT TYPE=\"CHECKBOX\" NAME=\"status\" VALUE=\"COMPLETED\"");
302 if (todoStatus == ICAL_STATUS_COMPLETED) {
303 wc_printf(" CHECKED=\"CHECKED\"");
306 wc_printf("</TD></TR>");
307 /* start category field */
308 p = icalcomponent_get_first_property(vtodo, ICAL_CATEGORIES_PROPERTY);
309 wc_printf("<TR><TD>");
310 wc_printf(_("Category:"));
311 wc_printf("</TD><TD>");
312 wc_printf("<INPUT TYPE=\"text\" NAME=\"category\" MAXLENGTH=\"32\" SIZE=\"32\" VALUE=\"");
314 escputs((char *) icalproperty_get_categories(p));
317 wc_printf("</TD></TR>\n ");
318 /* end category field */
319 wc_printf("<TR><TD>");
320 wc_printf(_("Description:"));
321 wc_printf("</TD><TD>");
322 wc_printf("<TEXTAREA NAME=\"description\" " "ROWS=\"10\" COLS=\"80\">\n");
323 p = icalcomponent_get_first_property(vtodo, ICAL_DESCRIPTION_PROPERTY);
325 escputs((char *) icalproperty_get_comment(p));
327 wc_printf("</TEXTAREA></TD></TR></TABLE>\n");
329 wc_printf("<SPAN STYLE=\"text-align: center;\">"
330 "<INPUT TYPE=\"submit\" NAME=\"save_button\" VALUE=\"%s\">"
332 "<INPUT TYPE=\"submit\" NAME=\"delete_button\" VALUE=\"%s\">\n"
334 "<INPUT TYPE=\"submit\" NAME=\"cancel_button\" VALUE=\"%s\">\n" "</SPAN>\n", _("Save"), _("Delete"), _("Cancel")
336 wc_printf("</td></tr></table>");
337 wc_printf("</FORM>\n");
338 wc_printf("</div></div></div>\n");
339 wc_printf("<!-- end task edit form -->");
342 if (created_new_vtodo) {
343 icalcomponent_free(vtodo);
348 * Save an edited task
350 * supplied_vtodo the task to save
351 * msgnum number of the mesage in our db
353 void save_individual_task(icalcomponent * supplied_vtodo, long msgnum, char *from, int unread, calview * calv) {
355 int delete_existing = 0;
357 icalcomponent *vtodo, *encaps;
358 int created_new_vtodo = 0;
361 struct icaltimetype t;
363 if (supplied_vtodo != NULL) {
364 vtodo = supplied_vtodo;
367 * If we're looking at a fully encapsulated VCALENDAR
368 * rather than a VTODO component, attempt to use the first
369 * relevant VTODO subcomponent. If there is none, the
370 * NULL returned by icalcomponent_get_first_component() will
371 * tell the next iteration of this function to create a
374 if (icalcomponent_isa(vtodo) == ICAL_VCALENDAR_COMPONENT) {
375 save_individual_task(icalcomponent_get_first_component(vtodo, ICAL_VTODO_COMPONENT),
376 msgnum, from, unread, calv);
381 vtodo = icalcomponent_new(ICAL_VTODO_COMPONENT);
382 created_new_vtodo = 1;
385 if (havebstr("save_button")) {
387 /** Replace values in the component with ones from the form */
389 while (prop = icalcomponent_get_first_property(vtodo, ICAL_SUMMARY_PROPERTY), prop != NULL) {
390 icalcomponent_remove_property(vtodo, prop);
391 icalproperty_free(prop);
393 if (havebstr("summary")) {
395 icalcomponent_add_property(vtodo, icalproperty_new_summary(bstr("summary")));
398 icalcomponent_add_property(vtodo, icalproperty_new_summary(_("Untitled Task")));
401 while (prop = icalcomponent_get_first_property(vtodo, ICAL_DESCRIPTION_PROPERTY), prop != NULL) {
402 icalcomponent_remove_property(vtodo, prop);
403 icalproperty_free(prop);
405 if (havebstr("description")) {
406 icalcomponent_add_property(vtodo, icalproperty_new_description(bstr("description")));
409 while (prop = icalcomponent_get_first_property(vtodo, ICAL_DTSTART_PROPERTY), prop != NULL) {
410 icalcomponent_remove_property(vtodo, prop);
411 icalproperty_free(prop);
413 if (IsEmptyStr(bstr("nodtstart"))) {
414 if (yesbstr("dtstart_time")) {
415 icaltime_from_webform(&t, "dtstart");
418 icaltime_from_webform_dateonly(&t, "dtstart");
420 icalcomponent_add_property(vtodo, icalproperty_new_dtstart(t)
423 while (prop = icalcomponent_get_first_property(vtodo, ICAL_STATUS_PROPERTY), prop != NULL) {
424 icalcomponent_remove_property(vtodo, prop);
425 icalproperty_free(prop);
427 while (prop = icalcomponent_get_first_property(vtodo, ICAL_PERCENTCOMPLETE_PROPERTY), prop != NULL) {
428 icalcomponent_remove_property(vtodo, prop);
429 icalproperty_free(prop);
432 if (havebstr("status")) {
433 icalproperty_status taskStatus = icalproperty_string_to_status(bstr("status"));
434 icalcomponent_set_status(vtodo, taskStatus);
435 icalcomponent_add_property(vtodo,
436 icalproperty_new_percentcomplete((strcasecmp(bstr("status"), "completed") ? 0 :
442 icalcomponent_add_property(vtodo, icalproperty_new_percentcomplete(0));
444 while (prop = icalcomponent_get_first_property(vtodo, ICAL_CATEGORIES_PROPERTY), prop != NULL) {
445 icalcomponent_remove_property(vtodo, prop);
446 icalproperty_free(prop);
448 if (!IsEmptyStr(bstr("category"))) {
449 prop = icalproperty_new_categories(bstr("category"));
450 icalcomponent_add_property(vtodo, prop);
452 while (prop = icalcomponent_get_first_property(vtodo, ICAL_DUE_PROPERTY), prop != NULL) {
453 icalcomponent_remove_property(vtodo, prop);
454 icalproperty_free(prop);
456 if (IsEmptyStr(bstr("nodue"))) {
457 if (yesbstr("due_time")) {
458 icaltime_from_webform(&t, "due");
461 icaltime_from_webform_dateonly(&t, "due");
463 icalcomponent_add_property(vtodo, icalproperty_new_due(t)
467 /** Give this task a UID if it doesn't have one. */
468 syslog(LOG_DEBUG, "Give this task a UID if it doesn't have one.\n");
469 if (icalcomponent_get_first_property(vtodo, ICAL_UID_PROPERTY) == NULL) {
471 icalcomponent_add_property(vtodo, icalproperty_new_uid(buf)
475 /* Increment the sequence ID */
476 syslog(LOG_DEBUG, "Increment the sequence ID\n");
477 while (prop = icalcomponent_get_first_property(vtodo, ICAL_SEQUENCE_PROPERTY), (prop != NULL)) {
478 i = icalproperty_get_sequence(prop);
479 syslog(LOG_DEBUG, "Sequence was %d\n", i);
482 icalcomponent_remove_property(vtodo, prop);
483 icalproperty_free(prop);
486 syslog(LOG_DEBUG, "New sequence is %d. Adding...\n", sequence);
487 icalcomponent_add_property(vtodo, icalproperty_new_sequence(sequence)
491 * Encapsulate event into full VCALENDAR component. Clone it first,
492 * for two reasons: one, it's easier to just free the whole thing
493 * when we're done instead of unbundling, but more importantly, we
494 * can't encapsulate something that may already be encapsulated
497 syslog(LOG_DEBUG, "Encapsulating into a full VCALENDAR component\n");
498 encaps = ical_encapsulate_subcomponent(icalcomponent_new_clone(vtodo));
500 /* Serialize it and save it to the message base */
501 serv_puts("ENT0 1|||4");
502 serv_getln(buf, sizeof buf);
504 serv_puts("Content-type: text/calendar");
506 serv_puts(icalcomponent_as_ical_string(encaps));
510 * Probably not necessary; the server will see the UID
511 * of the object and delete the old one anyway, but
516 icalcomponent_free(encaps);
520 * If the user clicked 'Delete' then explicitly delete the message.
522 if (havebstr("delete_button")) {
526 if ((delete_existing) && (msgnum > 0L)) {
527 serv_printf("DELE %ld", lbstr("msgnum"));
528 serv_getln(buf, sizeof buf);
531 if (created_new_vtodo) {
532 icalcomponent_free(vtodo);
535 /* Go back to wherever we came from */
536 if (ibstr("return_to_summary") == 1) {
537 display_summary_page();
540 readloop(readfwd, eUseDefault);
546 * free memory allocated using libical
548 void delete_task(void *vCal) {
549 disp_cal *Cal = (disp_cal *) vCal;
550 icalcomponent_free(Cal->cal);
557 * Load a Task into a hash table for later display.
559 void load_task(icalcomponent * event, long msgnum, char *from, int unread, calview * calv) {
560 icalproperty *ps = NULL;
561 struct icaltimetype dtstart, dtend;
564 icalcomponent *cptr = NULL;
566 dtstart = icaltime_null_time();
567 dtend = icaltime_null_time();
569 if (WC->disp_cal_items == NULL) {
570 WC->disp_cal_items = NewHash(0, Flathash);
573 Cal = (disp_cal *) malloc(sizeof(disp_cal));
574 memset(Cal, 0, sizeof(disp_cal));
575 Cal->cal = icalcomponent_new_clone(event);
577 /* Dezonify and decapsulate at the very last moment */
578 ical_dezonify(Cal->cal);
579 if (icalcomponent_isa(Cal->cal) != ICAL_VTODO_COMPONENT) {
580 cptr = icalcomponent_get_first_component(Cal->cal, ICAL_VTODO_COMPONENT);
582 cptr = icalcomponent_new_clone(cptr);
583 icalcomponent_free(Cal->cal);
588 Cal->unread = unread;
590 Cal->from = (char *) malloc(len + 1);
591 memcpy(Cal->from, from, len + 1);
592 Cal->cal_msgnum = msgnum;
594 /* Precalculate the starting date and time of this event, and store it in our top-level
595 * structure. Later, when we are rendering the calendar, we can just peek at these values
596 * without having to break apart every calendar item.
598 ps = icalcomponent_get_first_property(Cal->cal, ICAL_DTSTART_PROPERTY);
600 dtstart = icalproperty_get_dtstart(ps);
601 Cal->event_start = icaltime_as_timet(dtstart);
604 /* Do the same for the ending date and time. It makes the day view much easier to render. */
605 ps = icalcomponent_get_first_property(Cal->cal, ICAL_DTEND_PROPERTY);
607 dtend = icalproperty_get_dtend(ps);
608 Cal->event_end = icaltime_as_timet(dtend);
611 /* Store it in the hash list. */
612 /* syslog(LOG_DEBUG, "INITIAL: %s", ctime(&Cal->event_start)); */
613 Put(WC->disp_cal_items, (char *) &Cal->event_start, sizeof(Cal->event_start), Cal, delete_task);
621 int tasks_LoadMsgFromServer(SharedMessageStatus * Stat, void **ViewSpecific, message_summary * Msg, int is_new, int i) {
622 /* Not (yet?) needed here? calview *c = (calview *) *ViewSpecific; */
624 load_ical_object(Msg->msgnum, is_new, ICAL_VTODO_COMPONENT, load_task, NULL, 0);
629 * Display the editor component for a task
631 void display_edit_task(void) {
634 /* Force change the room if we have to */
635 if (havebstr("taskrm")) {
636 gotoroom(sbstr("taskrm"));
639 msgnum = lbstr("msgnum");
642 load_ical_object(msgnum, 0, ICAL_VTODO_COMPONENT, display_edit_individual_task, NULL, 0);
646 display_edit_individual_task(NULL, 0L, "", 0, NULL);
651 * save an edited task
653 void save_task(void) {
655 msgnum = lbstr("msgnum");
657 load_ical_object(msgnum, 0, ICAL_VTODO_COMPONENT, save_individual_task, NULL, 0);
660 save_individual_task(NULL, 0L, "", 0, NULL);
666 int tasks_GetParamsGetServerCall(SharedMessageStatus * Stat,
667 void **ViewSpecific, long oper, char *cmd, long len, char *filter, long flen) {
668 strcpy(cmd, "MSGS ALL");
669 Stat->maxmsgs = 32767;
674 int tasks_Cleanup(void **ViewSpecific) {
677 /* Tasks doesn't need the calview struct...
678 free (*ViewSpecific);
679 *ViewSpecific = NULL;
684 void InitModule_TASKS(void) {
685 RegisterReadLoopHandlerset(VIEW_TASKS,
686 tasks_GetParamsGetServerCall,
687 NULL, NULL, NULL, tasks_LoadMsgFromServer, tasks_RenderView_or_Tail, tasks_Cleanup, NULL);
688 WebcitAddUrlHandler(HKEY("save_task"), "", 0, save_task, 0);