1 /* pal
2  *
3  * Copyright (C) 2004, Scott Kuhl
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18  *
19  */
20 
21 #include <stdarg.h>
22 #include <time.h>
23 #include <string.h>
24 #include <curses.h>
25 
26 /* for vsnprintf */
27 #include <stdarg.h>
28 
29 #include "main.h"
30 #include "colorize.h"
31 #include "event.h"
32 
33 
34 /* Interface between g_print and ncurses.  This is also the print
35  * handler that gets used if we aren't using curses.  The default
36  * handler apparently calls fflush() all of the time.  This slows down
37  * some of our code that calls fflush() often (such as the code we use
38  * to print out the calendar).  */
pal_output_handler(const gchar * instr)39 void pal_output_handler( const gchar *instr )
40 {
41     gsize strsize;
42     char *outstr = g_locale_from_utf8(instr, -1, NULL, &strsize, NULL);
43     /* if the previous fails, try the filename conversion, it gives reasonable results in most cases */
44     if(!outstr)
45         outstr = g_filename_from_utf8(instr, -1, NULL, &strsize, NULL);
46     /* If that doesn't work, just use the input string */
47     if(!outstr)
48         outstr = g_strdup(instr);
49 
50     if(settings->curses)
51 	waddnstr(pal_curwin, outstr, strsize);
52     else
53 	fputs(outstr, stdout);
54 
55     g_free(outstr);
56 }
57 
58 
59 /* set attribute w/o changing color */
pal_output_attr(gint attr,gchar * formatString,...)60 void pal_output_attr(gint attr, gchar *formatString, ...)
61 {
62     gchar buf[2048] = "";
63     va_list argptr;
64 
65     va_start(argptr, formatString);
66     if( attr == BRIGHT )
67       colorize_bright();
68 
69     /* glib 2.2 provides g_vfprintf */
70     vsnprintf(buf, 2048, formatString, argptr);
71     g_print("%s", buf);
72 
73     colorize_reset();
74     va_end(argptr);
75 }
76 
77 /* set foreground color and attribute */
pal_output_fg(gint attr,gint color,gchar * formatString,...)78 void pal_output_fg( gint attr, gint color, gchar *formatString, ...)
79 {
80     gchar buf[2048] = "";
81     va_list argptr;
82 
83     va_start(argptr, formatString);
84     colorize_fg(attr, color);
85 
86     /* glib 2.2 provides g_vfprintf */
87     vsnprintf(buf, 2048, formatString, argptr);
88     g_print("%s", buf);
89 
90     colorize_reset();
91     va_end(argptr);
92 }
93 
94 
pal_output_error(char * formatString,...)95 void pal_output_error(char *formatString, ... )
96 {
97     gchar buf[2048] = "";
98     va_list argptr;
99 
100     va_start( argptr, formatString );
101     colorize_error();
102 
103     /* glib 2.2 provides g_vfprintf */
104     vsnprintf(buf, 2048, formatString, argptr);
105     g_printerr("%s", buf); /* use g_print to convert from UTF-8 */
106 
107     colorize_reset();
108     va_end(argptr);
109 }
110 
111 
112 /* finishes with date on the sunday of the next week */
pal_output_text_week(GDate * date,gboolean force_month_label,const GDate * today)113 static void pal_output_text_week(GDate* date, gboolean force_month_label,
114 				 const GDate* today)
115 {
116     gint i=0;
117 
118     if(settings->week_start_monday)
119 	/* go to last day in week (sun) */
120 	while(g_date_get_weekday(date) != 7)
121 	    g_date_add_days(date,1);
122     else
123 	/* go to last day in week (sat) */
124 	while(g_date_get_weekday(date) != 6)
125 	    g_date_add_days(date,1);
126 
127 
128     for(i=0; i<7; i++)
129     {
130 	if(g_date_get_day(date) == 1)
131 	    force_month_label = TRUE;
132 
133 	g_date_subtract_days(date,1);
134     }
135 
136     g_date_add_days(date,1);
137     /* date is now at beginning of week */
138 
139     if(force_month_label)
140     {
141 	gchar buf[1024];
142 
143 	g_date_add_days(date,6);
144 	g_date_strftime(buf, 128, "%b", date);
145 	g_date_subtract_days(date,6);
146 
147 	/* make sure we're only showing 3 characters */
148 	if(g_utf8_strlen(buf, -1) != 3)
149 	{
150 	    /* append whitespace in case "buf" is too short */
151 	    gchar* s = g_strconcat(buf, "        ", NULL);
152 
153 	    /* just show first 3 characters of month */
154 	    g_utf8_strncpy(buf, s, 3);
155 	    g_free(s);
156 	}
157 
158 	pal_output_fg(BRIGHT, GREEN, "%s ", buf);
159 
160     }
161     else if( settings->show_weeknum )
162     {
163 	gint weeknum = settings->week_start_monday ? g_date_get_monday_week_of_year(date)
164 	    : g_date_get_sunday_week_of_year(date);
165 	pal_output_fg(BRIGHT, GREEN, " %2d ", weeknum);
166     }
167 
168     else
169 	g_print("    ");
170 
171 
172     for(i=0; i<7; i++)
173     {
174 	GList* events = NULL;
175 	gunichar start=' ', end=' ';
176 	gchar utf8_buf[8];
177 	gint color = settings->event_color;
178 	events = get_events(date);
179 
180 	if(g_date_compare(date,today) == 0)
181 	    start = end = '@';
182 
183 	else if(events != NULL)
184 	{
185 	    GList* item  = g_list_first(events);
186 	    PalEvent *event = (PalEvent*) item->data;
187 
188 	    gboolean same_char = TRUE;
189 	    gboolean same_color = TRUE;
190 
191 	    /* skip to a event that isn't hidden or to the end of the list */
192 	    while(g_list_length(item) > 1 && event->hide)
193 	    {
194 		item = g_list_next(item);
195 		event = (PalEvent*) item->data;
196 	    }
197 
198 	    /* save the markers for the event */
199 	    if(event->hide)
200 		start = ' ', end = ' ';
201 	    else
202 	    {
203 		start = event->start;
204 		end   = event->end;
205 		color = event->color;
206 	    }
207 
208 	    /* if multiple events left */
209 	    while(g_list_length(item) > 1)
210 	    {
211 		item = g_list_next(item);
212 		event = (PalEvent*) item->data;
213 
214 		/* find next non-hidden event */
215 		while(g_list_length(item) > 1 && event->hide)
216 		{
217 		    item = g_list_next(item);
218 		    event = (PalEvent*) item->data;
219 		}
220 
221 		/* if this event is hidden, there aren't any more non-hidden events left */
222 		/* if this event isn't hidden, then determine if it has different markers */
223 		if( ! event->hide )
224 		{
225 		    gunichar new_start = event->start;
226 		    gunichar new_end   = event->end;
227 		    gint     new_color = event->color;
228 
229 		    if(new_start != start || new_end != end)
230 			same_char = FALSE;
231 		    if(new_color != color)
232 			same_color = FALSE;
233 		}
234 		if(same_char == FALSE)
235 		    start = '*', end = '*';
236 		if(same_color == FALSE)
237 		    color = -1;
238 	    }
239 	}
240 
241 	utf8_buf[g_unichar_to_utf8(start, utf8_buf)] = '\0';
242 
243 	/* print color for marker if needed */
244 	if(start != ' ' && end != ' ')
245 	{
246 	    if(color == -1)
247 		pal_output_fg(BRIGHT, settings->event_color, utf8_buf);
248 	    else
249 		pal_output_fg(BRIGHT, color, utf8_buf);
250 	}
251 	else
252 	    g_print(utf8_buf);
253 
254 
255 	if(g_date_compare(date,today) == 0)	/* make today bright */
256 	    pal_output_attr(BRIGHT, "%02d", g_date_get_day(date));
257 	else
258 	    g_print("%02d", g_date_get_day(date));
259 
260 
261 	utf8_buf[g_unichar_to_utf8(end, utf8_buf)] = '\0';
262 
263 	/* print color for marker if needed */
264 	if(start != ' ' && end != ' ')
265 	{
266 	    if(color == -1)
267 		pal_output_fg(BRIGHT, settings->event_color, utf8_buf);
268 	    else
269 		pal_output_fg(BRIGHT, color, utf8_buf);
270 
271 	}
272 	else
273 	    g_print(utf8_buf);
274 
275 
276 	/* print extra space between days */
277 	if(i != 6)
278 	    g_print(" ");
279 
280 	g_date_add_days(date,1);
281 	g_list_free(events);
282     }
283 
284 }
285 
286 
287 
pal_output_week(GDate * date,gboolean force_month_label,const GDate * today)288 static void pal_output_week(GDate* date, gboolean force_month_label, const GDate* today)
289 {
290 
291     pal_output_text_week(date, force_month_label, today);
292 
293     if(!settings->no_columns && settings->term_cols >= 77)
294     {
295 	pal_output_fg(DIM,YELLOW,"%s","|");
296 
297        /* skip ahead to next column */
298 	g_date_subtract_days(date, 6);
299 	g_date_add_days(date, settings->cal_lines*7);
300 	pal_output_text_week(date, force_month_label, today);
301 
302 	/* skip back to where we were */
303 	g_date_subtract_days(date, settings->cal_lines*7);
304 
305     }
306 
307     if(settings->term_cols != 77)
308 	g_print("\n");
309 
310 }
311 
312 
313 
pal_output_cal(gint num_lines,const GDate * today)314 void pal_output_cal(gint num_lines, const GDate* today)
315 {
316     gint on_week = 0;
317     gchar* week_hdr = NULL;
318     GDate* date = NULL;
319 
320     if(num_lines <= 0)
321 	return;
322 
323     date = g_date_new();
324     memcpy(date, today, sizeof(GDate));
325 
326     if(settings->week_start_monday)
327 	week_hdr = g_strdup(_("Mo   Tu   We   Th   Fr   Sa   Su"));
328     else
329 	week_hdr = g_strdup(_("Su   Mo   Tu   We   Th   Fr   Sa"));
330 
331 
332     /* if showing enough lines, show previous week. */
333     if(num_lines > 3)
334 	g_date_subtract_days(date, 7);
335 
336     if(settings->no_columns || settings->term_cols < 77)
337 	pal_output_fg(BRIGHT,GREEN, "     %s\n", week_hdr);
338 
339     else
340     {
341 	pal_output_fg(BRIGHT,GREEN,"     %s ", week_hdr);
342 	pal_output_fg(DIM,YELLOW,"%s","|");
343 	pal_output_fg(BRIGHT,GREEN,"     %s\n", week_hdr);
344     }
345 
346     g_free(week_hdr);
347 
348     while(on_week < num_lines)
349     {
350 	if(on_week == 0)
351 	    pal_output_week(date, TRUE, today);
352 	else
353 	    pal_output_week(date, FALSE, today);
354 
355 	on_week++;
356 
357     }
358     g_date_free( date );
359 }
360 
361 /* replaces tabs with spaces */
pal_output_strip_tabs(gchar * string)362 static void pal_output_strip_tabs(gchar* string)
363 {
364     gchar *ptr = string;
365     while(*ptr != '\0')
366     {
367 	if(*ptr == '\t')
368 	    *ptr = ' ';
369 	ptr++;
370     }
371 }
372 
373 
374 /* returns the length of the next word */
pal_output_wordlen(gchar * string)375 static gint pal_output_wordlen(gchar* string)
376 {
377     gchar* p = string;
378     int i=0;
379     while(*p != ' ' && *p != '\0')
380     {
381 	p = g_utf8_next_char(p);
382 	i++;
383     }
384 
385     return i;
386 }
387 
388 
389 /* This function does not yet handle tabs and color codes.  Tabs
390  * should be stripped from 'string' before this is called.
391  * "chars_used" indicates the number of characters already used on the
392  * line that "string" will be printed out on.
393  * Returns the number of lines printed.
394  */
pal_output_wrap(gchar * string,gint chars_used,gint indent)395 int pal_output_wrap(gchar* string, gint chars_used, gint indent)
396 {
397     gint numlines = 0;
398     gchar* s = string;
399     gint width = settings->term_cols - 1;  /* -1 to avoid unexpected wrap */
400     if( width <= 0 )
401         width = 10000;
402 
403     while(*s != '\0')
404     {
405 	/* print out any leading whitespace on this line */
406 	while(*s == ' ' && chars_used < width)
407 	{
408 	    g_print(" ");
409 	    chars_used++;
410 	    s = g_utf8_next_char(s);
411 	}
412 
413 	/* if word doesn't fit on line, split it */
414 	if(pal_output_wordlen(s)+chars_used > width)
415 	{
416 	    gchar line[2048];
417 	    g_utf8_strncpy(line, s, width - chars_used);
418 	    g_print("%s\n", line); /* print as much as we can */
419 	    numlines++;
420 	    s = g_utf8_offset_to_pointer(s, width - chars_used);
421 	    chars_used = 0;
422 	}
423 	else /* if next word fits on line */
424 	{
425 
426 	    while(*s != '\0' &&
427 		  pal_output_wordlen(s)+chars_used <= width)
428 	    {
429 
430 		/* if the next word is not a blank, copy the word */
431 		if(*s != ' ')
432 		{
433 		    gint word_len = pal_output_wordlen(s);
434 		    gchar word[2048];
435 		    g_utf8_strncpy(word, s, word_len);
436 		    g_print("%s", word);
437 		    s = g_utf8_offset_to_pointer(s, word_len);
438 		    chars_used += word_len;
439 		}
440 
441 		/* print out any spaces that follow the word */
442 		while(*s == ' ' && chars_used < width)
443 		{
444 		    g_print(" ");
445 		    chars_used++;
446 		    s = g_utf8_next_char(s);
447 		}
448 
449 
450 		/* if we filled line up perfectly, and there is a
451 		 * space next in the string, ignore it---the newline
452 		 * will act as the space */
453 		if(chars_used == width && *s == ' ')
454 		{
455 		    s = g_utf8_next_char(s);
456 
457 		    /* if the next line is a space too, break out of
458 		     * this loop.  If we don't break, whitespace might
459 		     * not be preserved properly. */
460 		    if(*s == ' ')
461 			break;
462 		}
463 	    }
464 
465 	    numlines++;
466 	    g_print("\n");
467 
468 	    chars_used = width;
469 	}
470 
471 	/* if not done, print indents for next line */
472 	if(*s != '\0')
473 	{
474 	    gint i;
475 
476 	    /* now, chars_used == width, onto the next line! */
477 	    chars_used = indent;
478 
479 	    for(i=0; i<indent; i++)
480 		g_print(" ");
481 	}
482 
483     }
484 
485     return numlines;
486 
487 }
488 
489 
490 
491 
492 /* If event_number is -1, don't number the events.
493    Returns the number of lines printed.
494 */
pal_output_event(const PalEvent * event,const GDate * date,const gboolean selected)495 int pal_output_event(const PalEvent* event, const GDate* date, const gboolean selected)
496 {
497     gint numlines = 0;
498     gchar date_text[128];
499     const gint indent = 2;
500     gchar* event_text = NULL;
501     date_text[0] = '\0';
502 
503     if(selected)
504 	pal_output_fg(BRIGHT, GREEN, "%s ", ">");
505     else if(event->color == -1)
506 	pal_output_fg(BRIGHT, settings->event_color, "%s ", "*");
507     else
508 	pal_output_fg(BRIGHT, event->color, "%s ", "*");
509 
510     pal_output_strip_tabs(event->text);
511     pal_output_strip_tabs(event->type);
512 
513     event_text = pal_event_escape(event, date);
514 
515     if(settings->compact_list)
516     {
517 	gchar* s = NULL;
518 	g_date_strftime(date_text, 128,
519 			settings->compact_date_fmt, date);
520 	pal_output_attr(BRIGHT, "%s ", date_text);
521 
522 	if(settings->hide_event_type)
523 	    s = g_strconcat(event_text, NULL);
524 	else
525 	    s = g_strconcat(event->type, ": ", event_text, NULL);
526 
527 	numlines += pal_output_wrap(s, indent+g_utf8_strlen(date_text,-1)+1, indent);
528 	g_free(s);
529     }
530     else
531     {
532 	if(settings->hide_event_type)
533 	    numlines += pal_output_wrap(event_text, indent, indent);
534 	else
535 	{
536 	    gchar* s = g_strconcat(event->type, ": ", event_text, NULL);
537 	    numlines += pal_output_wrap(s, indent, indent);
538 	    g_free(s);
539 	}
540     }
541     g_free(event_text);
542 
543     return numlines;
544 }
545 
546 
pal_output_date_line(const GDate * date)547 void pal_output_date_line(const GDate* date)
548 {
549     gchar pretty_date[128];
550     gint diff = 0;
551 
552     GDate* today = g_date_new();
553     g_date_set_time_t(today, time(NULL));
554 
555     g_date_strftime(pretty_date, 128, settings->date_fmt, date);
556 
557     pal_output_attr(BRIGHT, "%s", pretty_date);
558     g_print(" - ");
559 
560     diff = g_date_days_between(today, date);
561     if(diff == 0)
562 	pal_output_fg(BRIGHT, RED, "%s", _("Today"));
563     else if(diff == 1)
564 	pal_output_fg(BRIGHT, YELLOW, "%s", _("Tomorrow"));
565     else if(diff == -1)
566 	g_print("%s", _("Yesterday"));
567     else if(diff > 1)
568 	g_print(_("%d days away"), diff);
569     else if(diff < -1)
570 	g_print(_("%d days ago"), -1*diff);
571 
572     g_print("\n");
573 
574     g_date_free(today);
575 }
576 
577 
578 /* outputs the events in the order of PalEvent->file_num.
579    Returns the number of lines printed. */
pal_output_date(GDate * date,gboolean show_empty_days,int selected_event)580 int pal_output_date(GDate* date, gboolean show_empty_days, int selected_event)
581 {
582     gint numlines = 0;
583     GList* events = get_events(date);
584     gint num_events = g_list_length(events);
585 
586     if(events != NULL || show_empty_days)
587     {
588 	GList* item = NULL;
589 	int i;
590 
591 	if(!settings->compact_list)
592 	{
593 	    pal_output_date_line(date);
594 	    numlines++;
595 	}
596 
597 	item = g_list_first(events);
598 
599 	for(i=0; i<num_events; i++)
600 	{
601 	    numlines += pal_output_event((PalEvent*) (item->data),
602 					 date, i==selected_event);
603 
604 	    item = g_list_next(item);
605 	}
606 
607 
608 	if(num_events == 0)
609 	{
610 	    if(settings->compact_list)
611 	    {
612 		gchar pretty_date[128];
613 
614 		g_date_strftime(pretty_date, 128,
615 				settings->compact_date_fmt, date);
616 		pal_output_attr(BRIGHT, "  %s ", pretty_date);
617 		g_print("%s\n", _("No events."));
618 
619 		numlines++;
620 	    }
621 	    else
622 	    {
623 		g_print("%s\n", _("No events."));
624 		numlines++;
625 	    }
626 	}
627 
628 	if(!settings->compact_list)
629 	{
630 	    g_print("\n");
631 	    numlines++;
632 	}
633     }
634 
635     return numlines;
636 }
637 
638 
639 
640 /* returns the PalEvent for the given event_number */
pal_output_event_num(const GDate * date,gint event_number)641 PalEvent* pal_output_event_num(const GDate* date, gint event_number)
642 {
643     GList* events = get_events(date);
644     gint num_events = g_list_length(events);
645 
646     if(events == NULL || event_number < 1 || event_number > num_events)
647 	return NULL;
648 
649     return (PalEvent*) g_list_nth_data(events, event_number-1);
650 }
651 
652 
653