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