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