1 /*------------------------------------------------------------
2   statusline for the xdvi(k) previewer
3 
4   written by S. Ulrich (ulrich@cis.uni-muenchen.de)  2000/02/25
5 
6   Permission is hereby granted, free of charge, to any person obtaining a copy
7   of this software and associated documentation files (the "Software"), to
8   deal in the Software without restriction, including without limitation the
9   rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
10   sell copies of the Software, and to permit persons to whom the Software is
11   furnished to do so, subject to the following conditions:
12 
13   The above copyright notice and this permission notice shall be included in
14   all copies or substantial portions of the Software.
15 
16   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17   EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18   MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19   IN NO EVENT SHALL PAUL VOJTA OR ANY OTHER AUTHOR OF THIS SOFTWARE BE
20   LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21   OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22   WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23   ------------------------------------------------------------*/
24 
25 
26 #include "xdvi-config.h"
27 #include "xdvi.h"
28 #include "version.h"
29 #include "statusline.h"
30 #include "xm_menu.h"
31 #include "x_util.h"
32 #include "pagehist.h"
33 #include "util.h"
34 
35 #include <stdarg.h>
36 #include "my-vsnprintf.h"
37 
38 #include <ctype.h>
39 #include <X11/Xatom.h>
40 #include <X11/StringDefs.h>
41 
42 # ifdef MOTIF
43 #  include <Xm/Label.h>
44 #  include <Xm/Frame.h>
45 #  include <Xm/Text.h>
46 #  include <Xm/TextF.h>
47 # else
48 #  include <X11/Xaw/Viewport.h>
49 #  include <X11/Xaw/Label.h>
50 # endif
51 
52 
53 Widget statusline;
54 
55 static Boolean initialized = False;
56 
57 /*
58  * only print MAX_LEN characters to the statusline
59  * (it's only 1 line after all)
60  */
61 #define MAX_LEN 512
62 
63 /* for saving the statusline string if the statusline is
64  * destroyed and recreated
65  */
66 static char g_string_savebuf[MAX_LEN + 2];
67 
68 static int m_statusline_h = 20;
69 
70 /* access method */
get_statusline_height(void)71 int get_statusline_height(void)
72 {
73     return m_statusline_h;
74 }
75 
76 #if MOTIF
77 static void
handle_statusline_event(Widget w,XtPointer client_data,XEvent * ev,Boolean * cont)78 handle_statusline_event(Widget w, XtPointer client_data,
79 			XEvent *ev, Boolean *cont)
80 {
81     /*      const char *text = (const char *)client_data; */
82     UNUSED(w);
83     UNUSED(client_data);
84     UNUSED(cont);
85 
86     /*      fprintf(stderr, "text: |%s|; event: %p\n", text, ev); */
87     /* only used to do this if page history was already active, but it's probably
88        nicer to be able to get the history by clicking on the statusline ...
89     */
90     if (/* strncmp(text, "Page history:", sizeof "Page history:" - 1) == 0 && */ ev != NULL) {
91 	XmTextPosition pos = XmTextGetCursorPosition(statusline);
92 	char *ptr1, *ptr2;
93 	int diff = 0;
94 	/*  	fprintf(stderr, "pos: %d\n", pos); */
95 	if (pos == 0) { /* just display the page history */
96 	    page_history_move(0);
97 	    return;
98 	}
99 	ptr1 = g_string_savebuf + pos;
100 	ptr2 = strchr(g_string_savebuf, '[');
101 	if (ptr2 == NULL) { /* some other string, also display the page history */
102 	    page_history_move(0);
103 	    return;
104 	}
105 	/* 	fprintf(stderr, "ptr1: |%s|; ptr2: |%s|\n", ptr1, ptr2); */
106 	while (ptr1 < ptr2) {
107 	    if (*ptr1 == ' ' && *(ptr1 + 1) != '-') /* separator */
108 		diff--;
109 	    ptr1++;
110 	}
111 
112 	while (ptr1 > ptr2) {
113 	    if (*ptr1 == ' ' && *(ptr1 - 1) != '-') /* separator */
114 		diff++;
115 	    ptr1--;
116 	}
117 	/* 	fprintf(stderr, "diff: %d\n", diff); */
118 	page_history_move(diff);
119     }
120 }
121 #endif /* MOTIF */
122 
123 /*
124  * Create the statusline widget. To be called at the beginning
125  * of the program, and when expert mode is switched off.
126  *
127  *  Side effects:
128  *	sets <m_statusline_h> to the height of the statusline in pixels.
129  */
130 
131 Widget
create_statusline(Widget parent)132 create_statusline(
133 #ifdef MOTIF
134 		  Widget parent
135 #else
136 		  void
137 #endif
138 		  )
139 {
140 #ifndef MOTIF
141     Position vport_h;
142     Position clip_x;
143     Position clip_w;
144     static Position my_h = 0;
145 #endif
146 
147     /*
148      * FIXME: is there a better way to set the y position depending on
149      * the height of the widget?
150      * It doesn't work to change the value of XtNy *after* creating
151      * the widget!
152      */
153 
154     if (!initialized) {
155 #ifndef MOTIF
156 	/*
157 	 * determine height of statusline (depending on the font used).
158 	 * This is not changeable at runtime, so it's determined once and
159 	 * for all at program start.
160 	 */
161 	statusline = XtVaCreateWidget("statusline", labelWidgetClass, globals.widgets.vport_widget,
162 				      XtNlabel, (XtArgVal) "test",
163 				      NULL);
164 	XtVaGetValues(statusline, XtNheight, &my_h, NULL);
165 	m_statusline_h = my_h;
166 	XtDestroyWidget(statusline);
167 #endif
168 	initialized = True;
169 	/* initialize g_string_savebuf */
170 	sprintf(g_string_savebuf, "This is xdvik %s", XDVI_TERSE_VERSION_INFO);
171     }
172 #ifndef MOTIF
173     /* determine position and width of statusline */
174     XtVaGetValues(globals.widgets.clip_widget, XtNx, &clip_x, XtNwidth, &clip_w, NULL);
175     XtVaGetValues(globals.widgets.vport_widget, XtNheight, &vport_h, NULL);
176     if (vport_h - my_h <= 0) {
177 	XDVI_FATAL((stderr, "Window height too small for statusline (minimum value: %d).", my_h));
178 	return NULL;
179     }
180     statusline = XtVaCreateManagedWidget("statusline",
181 					 labelWidgetClass, globals.widgets.vport_widget,
182 					 XtNlabel, (XtArgVal) g_string_savebuf,
183 					 XtNwidth, clip_w,
184 					 XtNx, clip_x - 1,	/* so that left border becomes invisible */
185 					 XtNy, vport_h - my_h,
186 					 XtNjustify, XtJustifyLeft,
187 					 /* same as for the buttons line */
188 					 XtNborder, (XtArgVal) resource.fore_Pixel,
189 					 NULL);
190 #else
191     statusline = XtVaCreateManagedWidget("statusline",
192 					 xmTextFieldWidgetClass, parent,
193 					 XmNalignment, XmALIGNMENT_END,
194 					 XmNdepth, (XtArgVal) G_depth,
195 					 XmNbottomAttachment, XmATTACH_FORM,
196 					 XmNleftAttachment, XmATTACH_FORM,
197 					 XmNrightAttachment, XmATTACH_FORM,
198 					 XmNleftOffset, 1,
199 					 XmNrightOffset, 1,
200 					 XmNbottomOffset, 1,
201 					 XmNtopOffset, 0,
202 					 XmNcursorPositionVisible, False,
203 					 XmNautoShowCursorPosition, False,
204 					 XmNmarginWidth, 4,
205 					 XmNmarginHeight, 1,
206 					 XmNeditable, False,
207 					 XmNtraversalOn, False,
208 					 XmNvalue, g_string_savebuf,
209 					 NULL);
210 
211     /* Block processing of most interactive events on this widget, except
212      * for button events that should navigate the page history.
213      */
214     XtInsertEventHandler(statusline,
215 			 KeyPressMask | KeyReleaseMask |
216 			 PointerMotionMask| PointerMotionHintMask |
217 			 ButtonMotionMask |
218 #if !MOTIF
219 			 ButtonPressMask | ButtonReleaseMask |
220 #endif
221 			 FocusChangeMask,
222 			 /* ButtonPressMask | ButtonReleaseMask | */
223 			 /* 			 PointerMotionMask| PointerMotionHintMask | */
224 			 /* 			 ButtonMotionMask | */
225 			 True, block_event_callback,
226 			 (XtPointer)0, 0);
227 #if MOTIF
228     XtInsertEventHandler(statusline,
229 			 /* suboptimal, but needs to be release not press
230 			  * since we want to query the current cursor position,
231 			  * and that may not be set yet in the press event(?).
232 			  */
233 			 ButtonReleaseMask,
234 			 True, handle_statusline_event,
235 			 (XtPointer)g_string_savebuf, XtListTail);
236 
237 #endif /* MOTIF */
238 
239 #endif
240 
241     return statusline;
242 }
243 
244 
245 void
toggle_statusline(void)246 toggle_statusline(void)
247 {
248 #ifdef MOTIF
249     if ((resource.expert_mode & XPRT_SHOW_STATUSLINE) == 0)
250 	XtUnmanageChild(statusline);
251     else
252 	XtManageChild(statusline);
253 
254     set_menu(&resource.expert_mode, Act_set_expert_mode, check_resource_expert);
255 #else
256     static Boolean initialized = False;
257     static Boolean statusline_mapped = False;
258 
259     Boolean make_statusline_visible = False;
260     Boolean make_statusline_invisible = False;
261 
262     if (!initialized) {
263 	statusline_mapped = (resource.expert_mode & XPRT_SHOW_STATUSLINE) != 0;
264 	initialized = True;
265     }
266 
267     if ((resource.expert_mode & XPRT_SHOW_STATUSLINE) == 0) {
268 	if (statusline_mapped)
269 	    make_statusline_invisible = True;
270     }
271     else {
272 	if (!statusline_mapped)
273 	    make_statusline_visible = True;
274     }
275 
276     if (make_statusline_invisible) {
277 	XtDestroyWidget(statusline);
278 	statusline_mapped = False;
279     }
280     if (make_statusline_visible) {
281 	static Dimension window_w, window_h;
282 
283 	static Arg arg_wh[] = {
284 	    {XtNwidth, (XtArgVal) &window_w},
285 	    {XtNheight, (XtArgVal) &window_h},
286 	};
287 #ifdef MOTIF
288 	XtGetValues(globals.widgets.main_window, arg_wh, XtNumber(arg_wh));
289 #else
290 	XtGetValues(globals.widgets.vport_widget, arg_wh, XtNumber(arg_wh));
291 #endif
292 	XtVaSetValues(globals.widgets.vport_widget, XtNresizable, (XtArgVal)True, NULL);
293 	TRACE_GUI((stderr, "statusline: w %d, h %d", window_w, window_h));
294 	XtVaSetValues(globals.widgets.vport_widget, XtNwidth, (XtArgVal)window_w, XtNheight, (XtArgVal)window_h, NULL);
295 	TRACE_GUI((stderr, "after statusline"));
296 	create_statusline();
297 	statusline_mapped = True;
298     }
299 #endif /* MOTIF */
300 }
301 
302 
303 /*------------------------------------------------------------
304  *  handle_statusline_resize
305  *
306  *  Arguments:
307  *	void
308  *
309  *  Returns:
310  *	void
311  *
312  *  Purpose:
313  *	Resize the statusline when the total window size changes.
314  *
315  *------------------------------------------------------------*/
316 
317 void
handle_statusline_resize(void)318 handle_statusline_resize(void)
319 {
320 #ifndef MOTIF
321     if ((resource.expert_mode & XPRT_SHOW_STATUSLINE) == 0) {
322 	return;
323     }
324 
325     if (!statusline)
326 	return;
327 
328     /* apparently the x,y values of a widget can only be set at creation time, so
329      * the following won't work:
330      */
331 
332 #if 0
333     /*
334       BROKEN  Position vport_h, clip_x, clip_w;
335       BROKEN  static Position my_h = 0;
336       BROKEN
337       BROKEN  XtVaGetValues(globals.widgets.clip_widget,
338       BROKEN                XtNx, &clip_x,
339       BROKEN                XtNwidth, &clip_w,
340       BROKEN                NULL);
341       BROKEN  XtVaGetValues(globals.widgets.vport_widget,
342       BROKEN                XtNheight, &vport_h,
343       BROKEN                NULL);
344       BROKEN
345       BROKEN  XtUnmanageChild(statusline);
346       BROKEN  XtVaSetValues(statusline,
347       BROKEN                             XtNlabel, (XtArgVal) "",
348       BROKEN                XtNwidth, clip_w,
349       BROKEN                XtNx, clip_x - 1,
350       BROKEN                XtNy, vport_h - my_h,
351       BROKEN                XtNborderWidth, 1,
352       BROKEN                XtNjustify, XtJustifyLeft,
353       BROKEN                XtNborder, (XtArgVal) resource.fore_Pixel,
354       BROKEN                NULL);
355       BROKEN  XtManageChild(statusline);
356       BROKEN  XFlush(DISP);
357     */
358 #endif
359 
360     /* only this will: */
361     XtDestroyWidget(statusline);
362     create_statusline();
363 #endif
364 }
365 
366 
367 
368 
369 /*
370  * clear statusline by printing an empty message to it.
371  */
372 
373 
374 static void
clear_statusline(void)375 clear_statusline(void)
376 {
377     if ((resource.expert_mode & XPRT_SHOW_STATUSLINE) != 0) {
378 # ifdef MOTIF
379 	XmTextFieldSetString(statusline, " ");
380 # else
381 	XtVaSetValues(statusline, XtNlabel, " ", NULL);
382 # endif
383 	XFlush(DISP);
384     }
385     strcpy(g_string_savebuf, " ");
386 }
387 
388 
389 /* force a statusline update, no matter how busy the application is.
390    Use this with care (only for important messages).
391 */
392 void
force_statusline_update(void)393 force_statusline_update(void)
394 {
395 #ifdef MOTIF
396     XmUpdateDisplay(globals.widgets.top_level);
397 #else
398     XEvent event;
399     XSync(DISP, False);
400     while (XCheckMaskEvent(DISP, ExposureMask, &event))
401         XtDispatchEvent(&event);
402 #endif /* MOTIF */
403 }
404 
405 
406 /*
407  * timeout - if > 0, timeout in seconds after which the message will
408  *	     be deleted again. If < 0, message will remain (until
409  *	     another message overprints it)
410  * fmt     - message, a C format string
411  *
412  * If expert mode is off, print <fmt> to the statusline; else, print
413  * <fmt> to stdout, unless the `hushstdout' option is specified.
414  */
415 static XtIntervalId clear_timeout_id = 0;
416 
417 static void
clear_statusline_timer_proc(XtPointer client_data,XtIntervalId * id)418 clear_statusline_timer_proc(XtPointer client_data, XtIntervalId *id)
419 {
420     UNUSED(client_data);
421     UNUSED(id);
422 
423     if (clear_timeout_id) {
424 	clear_statusline();
425 	clear_timeout_id = 0;
426     }
427 }
428 
429 static void
internal_print_statusline(Boolean error,statusTimerT timeout,const char * old_content,const char * fmt,va_list argp)430 internal_print_statusline(Boolean error,
431 			  statusTimerT timeout,
432 			  const char *old_content,
433 			  const char *fmt,
434 			  va_list argp)
435 {
436     if (!XtIsRealized(globals.widgets.top_level)
437 	|| !initialized
438 	|| (resource.expert_mode & XPRT_SHOW_STATUSLINE) == 0) {
439 
440 	/* only print errors to stdout */
441 	if (!error)
442 	    return;
443 
444 	if (!resource.hush_stdout && strlen(fmt) > 0) { /* check for strlen since sometimes we clear the statusline
445 							   by printing "" to it, and we don't want that on stdout */
446 	    fprintf(stdout, "xdvi: ");
447 	    if (old_content != NULL)
448 		(void)fputs(old_content, stdout);
449 	    (void)vfprintf(stdout, fmt, argp);
450 	    fputc('\n', stdout);
451 	    fflush(stdout);
452 	}
453     }
454     else {
455 	char buf[MAX_LEN + 1];
456 	size_t offset = 0;
457 
458 	if (old_content != NULL && old_content[0] != '\0') {
459 	    offset += strlen(old_content);
460 	    strncpy(buf, old_content, MAX_LEN);
461 	    /* append separating space */
462 	    if (strlen(old_content) < MAX_LEN - 1) {
463 		strcat(buf, " ");
464 		offset++;
465 	    }
466 	}
467 	VSNPRINTF(buf + offset, MAX_LEN - offset, fmt, argp);	/* just throw away strings longer than MAX_LEN */
468 	buf[MAX_LEN] = '\0'; /* terminate buf */
469 	/*
470 	 * save current contents of statusline so that toggling the statusline
471 	 * on and off will display the same text again
472 	 */
473 	strcpy(g_string_savebuf, buf);
474 #ifdef MOTIF
475 	XmTextFieldSetString(statusline, buf);
476 #else
477 	XtVaSetValues(statusline, XtNlabel, buf, NULL);
478 #endif
479 	/* 	fprintf(stderr, "timeout: %d, id: %ld\n", timeout, clear_timeout_id); */
480 	if (timeout > 0) {
481 	    timeout *= 1000;	/* convert to miliseconds */
482 
483 	    if (clear_timeout_id) {
484 		/* 		fprintf(stderr, "clearing!\n"); */
485 		if (globals.debug & DBG_EVENT)
486 		    fprintf(stderr, "%s:%d: removing timeout %ld\n", __FILE__, __LINE__, clear_timeout_id);
487 		XtRemoveTimeOut(clear_timeout_id);
488 	    }
489 	    clear_timeout_id = XtAppAddTimeOut(globals.app, timeout,
490 					       clear_statusline_timer_proc, (XtPointer) NULL);
491 	}
492     }
493 }
494 
495 /*
496  * Append the varargs-string `fmt' to the currnent contents of the statusline
497  * erasing it after `timeout' seconds if timeout > 0, unless the current statusline
498  * contents matches pattern - in that case, overwrite the contents.
499  */
500 void
statusline_append(statusTimerT timeout,const char * pattern,const char * fmt,...)501 statusline_append(statusTimerT timeout, const char *pattern, const char *fmt, ...)
502 {
503     const char *buf = NULL;
504     va_list argp;
505 
506     if (XtIsRealized(globals.widgets.top_level) && initialized && (resource.expert_mode & XPRT_SHOW_STATUSLINE) != 0) {
507 	/* get current statusline contents */
508 #ifdef MOTIF
509 	XtVaGetValues(statusline, XmNvalue, &buf, NULL);
510 #else
511 	XtVaGetValues(statusline, XtNlabel, &buf, NULL);
512 #endif
513     }
514 
515     while (buf != NULL && isspace((int)*buf)) /* skip spaces inserted by statusline appending */
516 	buf++;
517     va_start(argp, fmt);
518 
519     if (buf != NULL && memcmp(buf, pattern, strlen(pattern)) == 0) {
520 	buf = NULL;
521     }
522     internal_print_statusline(false, timeout, buf, fmt, argp);
523     va_end(argp);
524 }
525 
526 /*
527  * Print the varargs-string `fmt' to the currnent contents of the statusline,
528  * erasing it after `timeout' seconds if timeout > 0.
529  */
530 
531 void
statusline_info(statusTimerT timeout,const char * fmt,...)532 statusline_info(statusTimerT timeout, const char *fmt, ...)
533 {
534     va_list argp;
535     va_start(argp, fmt);
536     /* for the time being, we don't differentiate between info/error
537      * wrt. printing to stdout/stderr. We could probably at some point
538      * remove the printing to stdout altogether (it's a bit un-GUIsh),
539      * but then there's already an option 'hushstdout' to suppress it ...
540      */
541     internal_print_statusline(True, timeout, NULL, fmt, argp);
542     va_end(argp);
543 }
544 
545 void
statusline_error(statusTimerT timeout,const char * fmt,...)546 statusline_error(statusTimerT timeout, const char *fmt, ...)
547 {
548     va_list argp;
549     va_start(argp, fmt);
550     internal_print_statusline(True, timeout, NULL, fmt, argp);
551     va_end(argp);
552 }
553 
554 void
statusline_clear(void)555 statusline_clear(void)
556 {
557     statusline_info(STATUS_SHORT, "");
558 }
559 
560 /*
561  * Erase the contents of the statusline if it starts with 'pattern'.
562  */
563 void
statusline_erase(const char * pattern)564 statusline_erase(const char *pattern)
565 {
566     const char *buf = NULL;
567     if (XtIsRealized(globals.widgets.top_level) && initialized && (resource.expert_mode & XPRT_SHOW_STATUSLINE) != 0) {
568 	/* get current statusline contents */
569 #ifdef MOTIF
570 	XtVaGetValues(statusline, XmNvalue, &buf, NULL);
571 #else
572 	XtVaGetValues(statusline, XtNlabel, &buf, NULL);
573 #endif
574 
575 	if (strncmp(buf, pattern, strlen(pattern)) == 0) {
576 	    statusline_clear();
577 	}
578     }
579 }
580 
581