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