1 /*
2 * draws the day view. PostScript printing is also here because it uses
3 * the same high-level routines, only the low-level output is diverted.
4 *
5 * locate_in_day_calendar() find out where we are in the calendar
6 * draw_day_calendar(region) If there is a day menu, resize, and
7 * redraw
8 * print_day_calendar(fp,l) print PostScript day to fp
9 */
10
11 #include <stdio.h>
12 #include <time.h>
13 #include <Xm/Xm.h>
14 #include "cal.h"
15
16 #define DBLTIME 1000 /* doubleclick time [ms] */
17
18 #define CUTOFF (config.day_gap-1) /* corner cut-off distance */
19 #define MINLEN 14 /* no bar is shorter than MINLEN */
20
21 #define TOD(t) ((t)%86400)
22 #define BOUND(t,a,b) ((t)<(a) ? (a) : (t)>(b) ? (b) : (t));
23 #define YPOS(t) (((t) - c->day_minhour*3600) * c->day_hourheight/3600)
24
25 static void gen_day (void);
26 static void draw_bar (struct weeknode *, int, int, int);
27 static void draw_octagon(int, BOOL, int, int, int, int);
28 static void draw_boxtext(int, int, int, int, char *, BOOL);
29
30 #ifdef JAPAN
31 extern int truncate_strpack();
32 extern int mixedstrlen_in_pixels();
33 extern void g_draw_strpack();
34 #endif
35
36 extern Display *display; /* everybody uses the same server */
37 extern XFontStruct *font[NFONTS]; /* fonts: FONT_* */
38 extern struct config config; /* global configuration data */
39 extern struct mainmenu mainmenu; /* all important main window widgets */
40 extern struct user *user; /* user list (from file_r.c) */
41 extern time_t curr_day; /* day being displayed, time in sec */
42 extern struct week day; /* info on day view */
43 extern struct plist *mainlist; /* list of all schedule entries */
44 extern struct holiday holiday[366]; /* info for each day, separate for */
45 extern struct holiday sm_holiday[366];/* full-line texts under, and small */
46
47
48 /*
49 * for a given position on the canvas, identify the entry there and return
50 * a pointer to it and which area it was hit in (one of the M_ constants).
51 * Locate an event if epp!=0, and return exactly where the event was hit.
52 * If none was found, return the time if possible for creating new events.
53 * If epp is 0, we are dragging an event right now; just return a new time.
54 *
55 * ret *epp *time *trig xp..ysp event
56 *
57 * M_ILLEGAL 0 0 - - no recognizable position
58 * M_OUTSIDE 0 time - - no event but in a valid day
59 * M_INSIDE entry time trigg - in event center, edit it
60 * M_LEFT entry time trigg pos/sz near top edge, move start date
61 * M_RIGHT entry time trigg pos/sz near bottom edge, move length
62 */
63
locate_in_day_calendar(struct entry ** epp,BOOL * warn,time_t * time,time_t * trigger,int * xp,int * yp,int * xsp,int * ysp,int xc,int yc)64 MOUSE locate_in_day_calendar(
65 struct entry **epp, /* returned entry or 0 */
66 BOOL *warn, /* is *epp an n-days-ahead w?*/
67 time_t *time, /* if *epp=0, clicked where */
68 time_t *trigger, /* if *epp=0, entry trigger */
69 int *xp, /* if M_INSIDE, rubber box */
70 int *yp,
71 int *xsp, /* position and size */
72 int *ysp,
73 int xc, /* pixel pos clicked */
74 int yc)
75 {
76 struct config *c = &config;
77 struct weeknode *vert, *horz; /* yov node scan pointers */
78 struct entry *ep; /* entry to test */
79 int d; /* day strip counter */
80 int x0, y0; /* XY start coord of day box*/
81 int x, y; /* upper left of day bk box */
82 int xs; /* width of bar col w/ gaps */
83 time_t bt; /* start time of bar */
84 int b, e; /* Y start end end pos of bar*/
85 int yend; /* bottom margin of chart */
86
87 x0 = c->day_margin + c->day_hourwidth + 2;
88 y0 = c->day_margin + c->day_title + 2 * c->day_headline + 2;
89 xs = c->day_barwidth + c->day_gap;
90 yend = y0 + c->day_hourheight * (c->day_maxhour - c->day_minhour);
91
92 if (yc > yend || yc < y0 || xc < x0)
93 return(M_ILLEGAL);
94 if (warn)
95 *warn = FALSE;
96 /* calc time in calendar */
97 *time = 0;
98 for (x=x0, d=0; d < c->day_ndays; d++) {
99 if (xc < x)
100 break;
101 x += day.nlines[d] * xs + c->day_margin;
102 if (d && config.moretimecols)
103 x += c->day_hourwidth + 2;
104 if (xc < x) {
105 *time = curr_day + d * 86400 + c->day_minhour * 3600 +
106 (yc - y0) * 3600 / c->day_hourheight;
107 break;
108 }
109 }
110 /* find entry under mouse */
111 if (epp)
112 for (d=0; d < c->day_ndays; d++) {
113 for (x=x0,y=y0,vert=day.tree[d]; vert; vert=vert->down,x+=xs) {
114 if (xc < x || xc >= x+xs)
115 continue;
116 if (yc < y)
117 return(M_OUTSIDE);
118 for (horz=vert; horz; horz=horz->next) {
119 ep = *epp = horz->entry;
120 bt = BOUND(TOD(ep->time), c->day_minhour*3600,
121 c->day_maxhour*3600);
122 e = BOUND(y + YPOS(bt), y0, yend);
123 if (config.weekwarn)
124 bt -= ep->early_warn > ep->late_warn ?
125 ep->early_warn : ep->late_warn;
126 b = BOUND(y + YPOS(bt), y0, yend);
127 if (ep->notime)
128 e = config.week_bignotime
129 ? y + YPOS(c->day_maxhour*3600)
130 : b + YPOS((c->day_minhour+1)*3600);
131 else {
132 int l = ep->length * c->day_hourheight/3600;
133 e += BOUND(l, MINLEN, 9999);
134 }
135 if (trigger)
136 *trigger = horz->trigger;
137 if (warn)
138 *warn = horz->days_warn;
139 if (xp) {
140 *xp = x + 1;
141 *yp = b + 1;
142 *xsp = c->day_barwidth & ~1;
143 *ysp = e - b - 1;
144 }
145 if (yc < b - 1)
146 continue;
147 if (yc < b + (e-b)/3)
148 return(M_LEFT);
149 if (yc < b + (e-b)*2/3)
150 return(M_INSIDE);
151 if (yc < e + 1)
152 return(M_RIGHT);
153 }
154 }
155 x0 += day.nlines[d] * xs + c->day_margin;
156 if (config.moretimecols)
157 x0 += c->day_hourwidth + 2;
158 }
159 if (epp)
160 *epp = 0;
161 return(*time ? M_OUTSIDE : M_ILLEGAL);
162 }
163
164
165 /*---------------------------------- drawing front-ends ---------------------*/
166 /*
167 * draw one day into the current window, in the specified region (all if null).
168 */
169
draw_day_calendar(Region region)170 void draw_day_calendar(
171 Region region)
172 {
173 if (!g_init_window(&day, region))
174 return;
175 g_setcolor(COL_WBACK);
176 g_fillrect(0, 0, day.canvas_xs, day.canvas_ys);
177 gen_day();
178 g_exit_window();
179 }
180
181
182 /*
183 * print PostScript day calendar to file fp.
184 */
185
print_day_calendar(FILE * fp,BOOL landscape)186 void print_day_calendar(
187 FILE *fp,
188 BOOL landscape)
189 {
190 time_t time = get_time();
191 struct tm *tm = time_to_tm(time);
192 time -= (tm->tm_wday + 6 + config.sunday_first)%7 * 86400;
193 if (!curr_day)
194 curr_day = time;
195 build_day(config.pr_omit_priv, config.pr_omit_appts);
196 g_init_ps(&day, fp, landscape);
197 gen_day();
198 g_exit_ps();
199 }
200
201
202 /*---------------------------------- high-level drawing ---------------------*/
203 /*
204 * draw the day, using low-level routines
205 */
206
gen_day(void)207 static void gen_day(void)
208 {
209 struct config *c = &config;
210 time_t today; /* today's date */
211 char buf[40], *p; /* text to print */
212 int d, h, l; /* day, hour, text length */
213 int x, xs; /* appt bar pos, col width */
214 int x0, y0; /* top left inside line box */
215 int dx, dy; /* size inside day line box */
216 int cy_d; /* character height */
217 int datecolor; /* color of date heading for day */
218 struct weeknode *vert, *horz; /* day node scan pointers */
219 struct holiday *hp, *shp, *holp;/* to check for holidays */
220 struct tm *tm; /* today's date as m/d/y */
221 #ifdef JAPAN
222 strpack partialstr[MAXPARTIALSTRING];
223 unsigned char strpool[MAXPARTIALCHAR];
224 #endif
225
226 x0 = c->day_margin;
227 y0 = c->day_margin + c->day_title + 3*c->day_headline + 2;
228 xs = c->day_barwidth + c->day_gap;
229 dy = c->day_hourheight * (c->day_maxhour - c->day_minhour);
230 cy_d = font[FONT_WDAY]->max_bounds.ascent;
231
232 strcpy(buf, mkdatestring(curr_day)); /* title */
233 strcat(buf, " - ");
234 strcat(buf, mkdatestring(curr_day + (c->day_ndays-1)*86400));
235 g_setcolor(COL_WTITLE);
236 g_setfont(FONT_WTITLE);
237 g_drawtext(c->day_margin + c->day_hourwidth + 2,
238 c->day_margin + c->day_title * 3/4,
239 buf, strlen(buf));
240 /* holidays */
241 tm = time_to_tm(curr_day);
242 if ((p = parse_holidays(tm->tm_year, FALSE)))
243 create_error_popup(mainmenu.cal, 0, p);
244 shp = &sm_holiday[tm->tm_yday];
245 hp = &holiday[tm->tm_yday];
246
247 for (d=0; d < c->day_ndays; d++, hp++, shp++) { /* day loop */
248
249 if (!d || config.moretimecols) { /* hour col */
250 g_setcolor(COL_WDAY);
251 g_setfont(FONT_WHOUR);
252 for (h=0; h <= c->day_maxhour-c->day_minhour; h++) {
253 p = mktimestring((h + c->day_minhour) * 3600,
254 FALSE);
255 l = strlen_in_pixels(p, FONT_WDAY);
256 g_drawtext(x0 + (c->day_hourwidth - l) / 2,
257 y0 + c->day_hourheight*h + cy_d/2-2,
258 p, strlen(p));
259 }
260 x0 += c->day_hourwidth + 2;
261 }
262 tm = time_to_tm(curr_day + d*86400);
263 if (tm->tm_yday == 0) { /* new year? */
264 if ((p = parse_holidays(tm->tm_year, FALSE)))
265 create_error_popup(mainmenu.cal, 0, p);
266 shp = &sm_holiday[0];
267 hp = &holiday[0];
268 }
269 if (!(dx = day.nlines[d] * xs))
270 continue;
271
272 today = get_time() - (curr_day + d*86400); /* green? */
273 if (today >= 0 && today < 86400) {
274 g_setcolor(COL_CALTODAY);
275 g_fillrect(x0-2, y0-7-2*c->day_headline,
276 dx+4, 2*c->day_headline+5);
277 }
278 /* day color */
279 datecolor = tm->tm_wday == 0 ||
280 tm->tm_wday == 6 ? COL_WEEKEND : COL_WDAY;
281
282 /* holiday */
283 if ((holp = hp->string ? hp : shp)->string) {
284 if (holp->daycolor)
285 datecolor = holp->daycolor;
286 g_setcolor(holp->stringcolor ? holp->stringcolor :
287 holp->daycolor ? holp->daycolor
288 : COL_WDAY);
289 #ifdef JAPAN
290 partialstr->strptr = strpool;
291 if (mixedstrlen_in_pixels(holp->string,
292 partialstr, FONT_WNOTE, FONT_JNOTE) > dx)
293 truncate_strpack(partialstr, dx, FONT_WNOTE);
294 g_draw_strpack(x0, y0-3, partialstr);
295 #else
296 strncpy(buf, holp->string, sizeof(buf)-1);
297 truncate_string(buf, dx, FONT_WDAY);
298 g_drawtext(x0, y0-3, buf, strlen(buf));
299 #endif
300 }
301 g_setfont(FONT_WHOUR); /* date */
302 g_setcolor(datecolor);
303 p = mkdatestring(curr_day + d * 86400);
304 truncate_string(p, dx, FONT_WHOUR);
305 g_drawtext(x0, y0-5-c->day_headline, p, strlen(p));
306
307 g_setcolor(COL_WBOXBACK); /* box bgnd */
308 g_fillrect(x0, y0, dx, dy);
309
310 g_setcolor(COL_WGRID); /* box lines */
311 g_fillrect(x0-2, y0-2, dx+4, 2);
312 g_fillrect(x0-2, y0+dy, dx+4, 2);
313 g_fillrect(x0-2, y0, 2, dy);
314 g_fillrect(x0+dx, y0, 2, dy);
315 for (h=1; h <= c->day_maxhour-c->day_minhour; h++)
316 g_fillrect(x0, y0 + c->day_hourheight * h, dx, 1);
317
318 x = x0 + c->day_gap/2; /* appt bars */
319 for (vert=day.tree[d]; vert; vert=vert->down) {
320 for (horz=vert; horz; horz=horz->next) {
321 int u = name_to_user(horz->entry->user);
322 draw_bar(horz, u>0 ? user[u].color : 0, x, y0);
323 }
324 x += xs;
325 }
326 x0 += dx + c->day_margin;
327 }
328 g_setcolor(COL_STD);
329 }
330
331
332 /*
333 * draw one entry bar.
334 */
335
draw_bar(struct weeknode * node,int color,int x,int y)336 static void draw_bar(
337 struct weeknode *node, /* entry node to print */
338 int color, /* color, 0..7 */
339 int x, /* top left pos in canvas */
340 int y)
341 {
342 struct entry *ep = node->entry;
343 struct config *c = &config;
344 time_t tod; /* clipped appt time of day */
345 int yend; /* right margin of chart */
346 int w[2], b, e; /* early/late, begin, end */
347 int i; /* warning counter, 0..1 */
348 char msg[256];
349
350 tod = BOUND(TOD(ep->time), c->day_minhour*3600, c->day_maxhour*3600);
351 yend = y + c->day_hourheight * (c->day_maxhour - c->day_minhour);
352 i = ep->early_warn > ep->late_warn;
353 w[!i] = tod - ep->early_warn;
354 w[ i] = tod - ep->late_warn;
355 b = tod;
356 e = tod + ep->length;
357 w[0] = y + YPOS(w[0]);
358 w[1] = y + YPOS(w[1]);
359 b = y + YPOS(b);
360 e = y + YPOS(e);
361 if (ep->notime) {
362 b = y + YPOS(c->day_minhour*3600);
363 e = config.week_bignotime ? y + YPOS(c->day_maxhour*3600)
364 : b + YPOS((c->day_minhour+1)*3600);
365 }
366 if (config.weekwarn && !ep->notime && !node->days_warn)
367 for (i=0; i < 2; i++)
368 draw_octagon(COL_WWARN, FALSE, x, w[i], e, yend);
369
370 draw_octagon(ep->suspended || node->days_warn ? COL_WWARN :
371 COL_WUSER_0 + (color & 7), color > 7, x, b, e, yend);
372 *msg = 0;
373 if (!ep->notime) {
374 strcpy(msg, mktimestring(node->trigger, FALSE));
375 if (ep->length)
376 sprintf(msg+strlen(msg), "-%s", mktimestring(
377 node->trigger + ep->length, FALSE));
378 if (node->days_warn)
379 strcat(msg, _(" (warn)"));
380 }
381 strcat(msg, " ");
382 strcat(msg, node->text);
383
384 draw_boxtext(x, b, c->day_barwidth, e-b < MINLEN ? MINLEN : e-b,
385 msg, FALSE);
386 }
387
388
389 /*
390 * draw one octagon (rectangle with the corners cut off, to make it easier
391 * to see where one box ends and the next begins). Allow a bottom limit to
392 * allow clipping, and to show flat bottoms where an end time isn't known.
393 * The 9th polygon vertex closes the polygon. It runs counter-clockwise
394 * from the topmost vertex at the left edge (10-o'clock position)
395 */
396
draw_octagon(int color,BOOL checker,int x,int y,int ye,int yend)397 static void draw_octagon(
398 int color, /* color, 0..7 */
399 BOOL checker, /* add checkerboard pattern */
400 int x, /* top left XY pos in canvas */
401 int y,
402 int ye, /* Y bottom in canvas */
403 int yend) /* bottom Y clip limit */
404 {
405 XPoint point[9]; /* polygon vertex list */
406
407 if (ye < y + MINLEN) { /* too narrow: draw flat bottom */
408 ye = y + 9999;
409 yend = y + MINLEN;
410 }
411 point[8].x =
412 point[0].x =
413 point[1].x = x;
414 point[7].x =
415 point[2].x = x + CUTOFF;
416 point[6].x =
417 point[3].x = x + config.day_barwidth - CUTOFF;
418 point[5].x =
419 point[4].x = x + config.day_barwidth;
420
421 point[7].y =
422 point[6].y = y;
423 point[8].y =
424 point[0].y =
425 point[5].y = y + CUTOFF;
426 point[1].y =
427 point[4].y = BOUND(ye - CUTOFF, y, yend);
428 point[2].y =
429 point[3].y = BOUND(ye, y, yend);
430
431 g_setcolor(color);
432 g_drawpoly(point, 8, checker);
433 g_setcolor(COL_WFRAME);
434 g_drawlines(point, 9);
435 }
436
437
438 /*
439 * draw a text into the given space, optionally splitting the text into
440 * multiple lines. Use the small note font.
441 */
442
443 #define BOXFONT FONT_NOTE
444 #define ISSPACE(c) ((c)==' ' || (c)=='\t')
445 #define MAXSPLIT 6
446
draw_boxtext(int x,int y,int xs,int ys,char * text,BOOL center)447 static void draw_boxtext(
448 int x, /* top left corner of free space */
449 int y,
450 int xs, /* size of free space */
451 int ys,
452 char *text, /* text to print */
453 BOOL center) /* center text? */
454 {
455 char *beg, *p, *p_s; /* soln, curr char, since last space */
456 int len, len_s; /* curr text length in pixels */
457 int cys, lys; /* character height, line height */
458 int i;
459 #ifdef JAPAN
460 int n, plen; /* pixel-length of partialstr */
461 strpack partialstr[MAXPARTIALSTRING], save, saveasc;
462 unsigned char strpool[MAXPARTIALCHAR];
463 #endif
464
465 g_setfont(BOXFONT);
466 cys = font[BOXFONT]->max_bounds.ascent;
467 #ifdef JAPAN
468 if (cys < font[FONT_JNOTE]->max_bounds.ascent)
469 cys = font[FONT_JNOTE]->max_bounds.ascent;
470 #endif
471 lys = cys * 5/4;
472 i = (ys-cys) / lys;
473 y += (ys - cys - i*lys) / 2 + cys;
474 x += 3;
475 xs -= 5;
476
477 #ifdef JAPAN
478 partialstr->strptr = strpool;
479 (void) mixedstrlen_in_pixels(text, partialstr, FONT_NOTE, FONT_JNOTE);
480 for (n = plen = 0; partialstr[n].strptr != NULL && n < MAXPARTIALSTRING; n++) {
481 if (partialstr[n].asciistr == False) { /* case of Japanese font */
482 if (n + 1 < MAXPARTIALSTRING)
483 saveasc = partialstr[n + 1];
484 while (plen + partialstr[n].pixlen > xs) {
485 save = partialstr[n];
486 (void) truncate_strpack(partialstr + n, xs - plen, FONT_NOTE);
487 if (partialstr[n].strptr == NULL) {
488 partialstr[n] = save;
489 } else {
490 i = center ? x + (xs - partialstr[n].length) / 2 : x + plen;
491 g_drawtext16(i, y, partialstr[n].strptr, partialstr[n].length / 2);
492 partialstr[n].strptr += partialstr[n].length;
493 partialstr[n].pixlen *= partialstr[n].length =
494 save.length - partialstr[n].length;
495 partialstr[n].pixlen /= save.length;
496 }
497 plen = 0;
498 y += lys;
499 if ((ys -= lys) < cys)
500 goto NO_MORE_SPACE;
501 }
502 i = center ? x + (xs - partialstr[n].pixlen) / 2 : x + plen;
503 g_drawtext16(i, y, partialstr[n].strptr, partialstr[n].length / 2);
504 if (center) {
505 plen = 0;
506 y += lys;
507 if ((ys -= lys) < cys)
508 break;
509 } else
510 plen += partialstr[n].pixlen;
511 if (n + 1 < MAXPARTIALSTRING)
512 partialstr[n + 1] = saveasc;
513 } else { /* case of ASCII font */
514 for (len_s = len = plen, beg = p_s = p = (char *) partialstr[n].strptr; *p; p++) {
515 if (*p == ' ' || *p == '\t') {
516 p_s = p;
517 len_s = len;
518 }
519 len += CHARWIDTH(FONT_NOTE, *p);
520 if (!p[1])
521 p++;
522 if (len > xs || !*p) {
523 if (len > xs && p_s != beg
524 && p_s >= p-MAXSPLIT && ys >= lys+cys) {
525 p = p_s;
526 len = len_s;
527 }
528 i = center ? x + (xs - len)/2 : x + plen;
529 g_drawtext(i, y, beg, p - beg);
530 if (*p) {
531 while (ISSPACE(*p)) p++;
532 beg = p_s = p--;
533 plen = len = len_s = 0;
534 y += lys;
535 if ((ys -= lys) < cys)
536 goto NO_MORE_SPACE;
537 } else {
538 if (center) {
539 plen = 0;
540 y += lys;
541 if ((ys -= lys) < cys)
542 goto NO_MORE_SPACE;
543 } else
544 plen = len;
545 break;
546 }
547 }
548 }
549 }
550 }
551 NO_MORE_SPACE:; /* It seems that some compilers need a SEMICOLON. */
552 #else
553 for (len_s=len=0, beg=p_s=p=text; *p; p++) {
554 if (*p == ' ' || *p == '\t') {
555 p_s = p;
556 len_s = len;
557 }
558 len += CHARWIDTH(BOXFONT, *p);
559 if (!p[1])
560 p++;
561 if (len > xs || !*p) {
562 if (len > xs && p_s != beg
563 && p_s >= p-MAXSPLIT && ys >= lys+cys) {
564 p = p_s;
565 len = len_s;
566 }
567 i = center ? x + (xs - len)/2 : x;
568 g_drawtext(i, y, beg, p-beg);
569 while (ISSPACE(*p)) p++;
570 beg = p_s = p--;
571 len = len_s = 0;
572 y += lys;
573 if ((ys -= lys) < cys)
574 break;
575 }
576 }
577 #endif
578 }
579