1 /*
2  * Page selector for xdvi
3  *
4  * Copyright (c) 2001-2004 xdvik development team
5  *
6  * This code is derived from the page selector in xdvik-j, and
7  * parts of it are Copyright (c) 1993, 1995
8  *      MATSUURA Syun           syun@fuka.info.waseda.ac.jp
9  *      HIRAHARA Atsushi        hirahara@fuka.info.waseda.ac.jp
10  *      ONO Kouichi             onono@fuka.info.waseda.ac.jp
11  * All rights reserved.
12  *
13  *
14  * (SU: I was unsure how to interpret the `All rights reserved' in the
15  * previous line, so emailed Ono Kouichi about this.  Here's a
16  * verbatim quote of the relevant part of his answer (which was CC'ed
17  * to Hirahara Atsushi - all three of them had left Waseda university
18  * around '95):
19  *
20  *    You can modify, embed, copy and distribute a part of or the
21  *    entire of our source code when you specify our copyright in your
22  *    xdvik version.
23  *
24  * IANAL, but I think this is compatible with the X consortium
25  * license, which follows.)
26  *
27  * Permission is hereby granted, free of charge, to any person obtaining a copy
28  * of this software and associated documentation files (the "Software"), to
29  * deal in the Software without restriction, including without limitation the
30  * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
31  * sell copies of the Software, and to permit persons to whom the Software is
32  * furnished to do so, subject to the following conditions:
33  *
34  * The above copyright notice and this permission notice shall be included in
35  * all copies or substantial portions of the Software.
36  *
37  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
38  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
39  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
40  * PAUL VOJTA OR ANY OTHER AUTHOR OF THIS SOFTWARE BE LIABLE FOR ANY CLAIM,
41  * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
42  * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
43  * OTHER DEALINGS IN THE SOFTWARE.
44  *
45  */
46 
47 /*
48   BUGS:
49 
50   - with Xaw, the highlighting for the selected page (XawListHighlight)
51   vanishes when mouse is dragged outside the widget and then released
52   (but the respective page is selected, which is IMHO the desired behaviour).
53 
54   - with Xaw, scrolling the list with PgUp/PgDown until the current page
55   gets `out of focus' should un-highlight the current page number
56 
57   - The ASCII-based marks are *ugly*. Pixmaps (for the marked state)
58   would be better. The viewer gv has one (but it's Xaw only). Some
59   file directory widgets like
60   e.g. http://ftp.xfree86.org/pub/X.Org/contrib/widgets/ListTree-3.0b3.tar.gz
61   also have facilities for that, but most suffer from other
62   inadequacies (e.g. no such ting as browseSelection) and all kinds
63   of bitrot ...  Another alternative would be using XmContainer
64   (see e.g. the `filemanager' example in demos/programs/filemanagers
65   in the openmotif distribution), but that's available for Motif >= 2.1 only.
66 
67 */
68 
69 #include "xdvi-config.h"
70 #include "xdvi.h"
71 
72 #include <stdio.h>
73 #include <stdarg.h>
74 #include <stdlib.h>
75 #include <X11/Intrinsic.h>
76 #include <X11/StringDefs.h>
77 #include <X11/Shell.h>
78 
79 #include "xm_toolbar.h"
80 #include "xm_menu.h"
81 #include "xaw_menu.h"
82 
83 #include "x_util.h"
84 
85 #ifdef MOTIF
86 # include <Xm/Xm.h>
87 # include <Xm/List.h>
88 # include <Xm/ScrollBar.h> /* for XmScrollBarGetValues */
89 #else /* MOTIF */
90 # include <X11/Xaw/Dialog.h>
91 # include <X11/Xaw/Cardinals.h>
92 # include <X11/Xaw/Command.h>
93 # include <X11/Xaw/List.h>
94 # include <X11/Xaw/Viewport.h>
95 #endif /* MOTIF */
96 
97 #include "message-window.h"
98 #include "pagesel.h"
99 #include "util.h"
100 #include "string-utils.h"
101 #include "dvi-init.h"
102 #include "statusline.h"
103 #include "events.h"
104 #include "print-dialog.h"
105 #include "search-internal.h"
106 #include "pagehist.h"
107 
108 #define	PAGENUMLEN 128
109 #define SCROLL_LIST_SCROLLBAR 0
110 
111 #ifndef	MAX_PAGE
112 # define MAX_PAGE    (1024)
113 #endif /* MAX_PAGE */
114 
115 #define	LONGESTPAGENUM  55
116 
117 
118 /* for saving the GC of the pagelist widget
119    (when un-highlighting items in highlight_page_callback, and
120    drawing the `current' marker)
121 */
122 static struct page_gc {
123     GC fore;
124     GC back;
125 } m_page_gc;
126 
127 #define MOTIF_IDX_OFFSET 1 /* motif index starts at 1, not 0 */
128 #if !defined(LESSTIF_VERSION)
129 static Boolean my_list_pos_to_bounds(Widget widget, int idx, Position *x, Position *y, Dimension *w, Dimension *h);
130 static void refresh_highlight_marker(Widget widget, GC gc, Position x, Position y, Dimension w, Dimension h);
131 #endif /* !defined(LESSTIF_VERSION) */
132 
133 
134 
135 #ifdef MOTIF
136 
137 /* make button2 mark instead of drag&drop */
138 static void xm_list_set_mark(Widget widget, XEvent *event, String *params, Cardinal *num_params);
139 static void xm_list_drag_mark(Widget widget, XEvent *event, String *params, Cardinal *num_params);
140 static XtActionsRec CustomListActions[] = {
141     { "ListSetMark",		xm_list_set_mark	},
142     { "ListDragMark",		xm_list_drag_mark	},
143 };
144 static char *motif_custom_translations =
145 "#override \n"
146 "s <Btn2Down>:                   ListDragMark(ListButtonMotion)\n"
147 "<Btn2Motion>:                   ListSetMark(ListButtonMotion)\n"
148 "<Btn2Up>:                       ListSetMark(ListButtonMotion)\n"
149 "<Btn2Down>:                     ListSetMark(ListButtonMotion)\n"
150 "<Btn4Down>,<Btn4Up>:            scroll-list-up()\n"
151 "<Btn5Down>,<Btn5Up>:            scroll-list-down()\n"
152 /* /\*     "s ~m ~a <Btn2Down>:             ListMyProcessBtn2(ListBeginExtend)\n" *\/ */
153 /* /\*     "s ~m ~a <Btn2Up>:               ListMyProcessBtn2(ListEndExtend)\n" *\/ */
154 /* /\*     "~c ~s ~m ~a <Btn2Down>:         ListMyProcessBtn2(ListBeginSelect)\n" *\/ */
155 /* /\*     "~c ~s ~m ~a <Btn2Up>:           ListMyProcessBtn2(ListEndSelect)\n" *\/ */
156 /* /\*     "c ~s ~m ~a <Btn2Down>:          ListMyProcessBtn2(ListBeginToggle)\n" *\/ */
157 /* /\*     "c ~s ~m ~a <Btn2Up>:            ListMyProcessBtn2(ListEndToggle)\n" *\/ */
158 /* /\*     "c ~s ~m a <Btn2Down>:           ListProcessDrag()\n" *\/ */
159 /* /\*     "~c s ~m a <Btn2Down>:           ListProcessDrag()\n" *\/ */
160 ;
161 
162 #define LIST_WIDGET page_list
163 /* motif pagenumber is a string */
164 static const char* const pageno_format = "%c %s  ";
165 
166 #else /* MOTIF */
167 
168 static int view_y;
169 extern Widget panel_widget;
170 static Widget list_widget = NULL;
171 static Widget viewport = NULL;
172 #define LIST_WIDGET list_widget
173 /* Xaw pagenumber is an integer, and we need to left-pad it */
174 static const char* const pageno_format = "%c %*d  ";
175 
176 static int xaw_maybe_scroll_pagelist(int new_page, Boolean force_recenter, int old);
177 
178 #define REDRAW_CURRENT_MARKER_HACK 1
179 
180 
181 /*
182   The following hack tries to address the following 2 bugs with the
183   self-made page highlighting marker:
184 
185   - the marker overlaps with the ordinary XawListHighlight marker;
186   when un-highlighting a page, 1 pixel (vertically) at the edge of
187   the ordinary marker is overdrawn.
188 
189   - When the XawListHighlight crosses the self-drawn rectangle,
190   the vertical bars remain visible, but the horizontal bars
191   are erased.
192 
193   The hack just redraws the appropriate items whenever one of
194   the above can happen, i.e. when the two markers are 2 or less
195   pages apart from each other.
196 */
197 #if REDRAW_CURRENT_MARKER_HACK
198 /* Store index of currently marked (with our own marker) list
199    item, or -1 if none is marked. */
200 static int g_current_highlighted = -1;
201 
202 /* redraw the default Xaw list highlight (XawListHighlight()) */
203 static void
xaw_maybe_redraw_highlight(int idx)204 xaw_maybe_redraw_highlight(int idx)
205 {
206     XawListReturnStruct *ret;
207     int high;
208 
209     if (LIST_WIDGET == NULL)
210 	return;
211 
212     ret = XawListShowCurrent(LIST_WIDGET);
213     high = ret->list_index;
214     if (high != XAW_LIST_NONE && abs(idx - (high + MOTIF_IDX_OFFSET)) <= 2) {
215 	/* re-highlight it */
216 	XawListHighlight(LIST_WIDGET, high);
217     }
218     g_current_highlighted = -1;
219 }
220 
221 /* redraw our own rectangle highlight marker: */
222 static void
xaw_maybe_redraw_current_marker(int idx)223 xaw_maybe_redraw_current_marker(int idx)
224 {
225     Position x, y;
226     Dimension w, h;
227 
228     /*     fprintf(stderr, "idx: %d, high: %d; diff: %d\n", idx + MOTIF_IDX_OFFSET, g_current_highlighted, */
229     /* 	    abs(idx + MOTIF_IDX_OFFSET - g_current_highlighted)); */
230     if (abs((idx + MOTIF_IDX_OFFSET) - g_current_highlighted) <= 2
231 	&& my_list_pos_to_bounds(LIST_WIDGET, g_current_highlighted, &x, &y, &w, &h)) {
232 	refresh_highlight_marker(LIST_WIDGET, m_page_gc.fore, x, y, w, h);
233     }
234 }
235 #endif /* REDRAW_CURRENT_MARKER_HACK */
236 
237 #endif /* MOTIF */
238 
239 /*
240  * Table of page offsets in DVI file, indexed by page number - 1,
241  * marked pages, and page sizes.
242  * Initialized in prepare_pages().
243  */
244 struct page_index {
245     long offset;
246     int number;
247     Boolean marked;
248     unsigned int pw, ph; /* page size */
249     unsigned int ww, wh; /* window size */
250 };
251 
252 struct page_index_info {
253     struct page_index *index; /* above struct */
254     size_t index_size;	 /* size of currently allocated index */
255     char **page_labels;  /* label strings */
256 };
257 
258 static struct page_index_info page_info;
259 
260 /* access functions used by dvi-draw.c and dvi-init.c */
261 long
pageinfo_get_offset(int page)262 pageinfo_get_offset(int page)
263 {
264     ASSERT(page >= 0 && page < (int)page_info.index_size, "Page number out of range");
265     /*      fprintf(stderr, "offset for page %d is %ld\n", page, page_info.index[page].offset); */
266     return page_info.index[page].offset;
267 }
268 
269 /* access functions used by dvi-draw.c and dvi-init.c */
270 unsigned int
pageinfo_get_page_width(int page)271 pageinfo_get_page_width(int page)
272 {
273     ASSERT(page >= 0 && page < (int)page_info.index_size, "Page number out of range");
274     return page_info.index[page].pw;
275 }
276 
277 unsigned int
pageinfo_get_page_height(int page)278 pageinfo_get_page_height(int page)
279 {
280     ASSERT(page >= 0 && page < (int)page_info.index_size, "Page number out of range");
281     return page_info.index[page].ph;
282 }
283 
284 unsigned int
pageinfo_get_window_width(int page)285 pageinfo_get_window_width(int page)
286 {
287     ASSERT(page >= 0 && page < (int)page_info.index_size, "Page number out of range");
288     return page_info.index[page].ww;
289 }
290 
291 unsigned int
pageinfo_get_window_height(int page)292 pageinfo_get_window_height(int page)
293 {
294     ASSERT(page >= 0 && page < (int)page_info.index_size, "Page number out of range");
295     return page_info.index[page].wh;
296 }
297 
298 void
pageinfo_set_page_width(int page,unsigned int width)299 pageinfo_set_page_width(int page, unsigned int width)
300 {
301     ASSERT(page >= 0 && page < (int)page_info.index_size, "Page number out of range");
302     page_info.index[page].pw = width;
303 }
304 
305 void
pageinfo_set_page_height(int page,unsigned int height)306 pageinfo_set_page_height(int page, unsigned int height)
307 {
308     ASSERT(page >= 0 && page < (int)page_info.index_size, "Page number out of range");
309     page_info.index[page].ph = height;
310 }
311 
312 void
pageinfo_set_window_width(int page,unsigned int width)313 pageinfo_set_window_width(int page, unsigned int width)
314 {
315     ASSERT(page >= 0 && page < (int)page_info.index_size, "Page number out of range");
316     page_info.index[page].ww = width;
317 }
318 
319 void
pageinfo_set_window_height(int page,unsigned int height)320 pageinfo_set_window_height(int page, unsigned int height)
321 {
322     ASSERT(page >= 0 && page < (int)page_info.index_size, "Page number out of range");
323     page_info.index[page].wh = height;
324 }
325 
326 int
pageinfo_get_number(int page)327 pageinfo_get_number(int page)
328 {
329     ASSERT(page >= 0 && page < (int)page_info.index_size, "Page number out of range");
330     return page_info.index[page].number;
331 }
332 
333 /* search for page with TeX number `number', and return its index, or -1 if it's not found. */
334 int
pageinfo_get_index_of_number(int number)335 pageinfo_get_index_of_number(int number)
336 {
337     size_t i;
338     for (i = 0; i < page_info.index_size - 1; i++) {
339 	if (number == page_info.index[i].number)
340 	    return i;
341     }
342     return -1;
343 }
344 
345 void
pageinfo_set_offset(int index,long offset)346 pageinfo_set_offset(int index, long offset)
347 {
348     ASSERT(index >= 0 && index < (int)page_info.index_size, "");
349     page_info.index[index].offset = offset;
350 }
351 
352 void
pageinfo_set_number(int index,int number)353 pageinfo_set_number(int index, int number)
354 {
355     ASSERT(index >= 0 && index < (int)page_info.index_size, "");
356     page_info.index[index].number = number;
357 }
358 
359 void
pageinfo_allocate(int total_pages)360 pageinfo_allocate(int total_pages)
361 {
362     int i;
363     page_info.index = xmalloc(total_pages * sizeof *(page_info.index));
364     for (i = 0; i < total_pages; i++) {
365 	page_info.index[i].marked = False;
366     }
367     /* following initializations are handled by the respective Motif/Xaw functions */
368     page_info.page_labels = NULL;
369     page_info.index_size = total_pages;
370 }
371 
372 /*
373   Deallocate page_info. NOTE: We mustn't free the page_labels here,
374   since the page list might survive quite some time (e.g. while fonts
375   for the new file are being generated) and needs the labels.
376 */
377 void
pageinfo_deallocate(void)378 pageinfo_deallocate(void)
379 {
380     free(page_info.index);
381     page_info.index_size = 0;
382     page_info.index = NULL;
383 }
384 
385 #ifdef MOTIF
386 void
toggle_pagelist(void)387 toggle_pagelist(void)
388 {
389     Dimension curr_w, curr_x;
390     XtVaGetValues(globals.widgets.main_window,
391 		  XmNwidth, &curr_w,
392 		  XmNx, &curr_x,
393 		  NULL);
394 
395     if ((resource.expert_mode & XPRT_SHOW_PAGELIST) != 0) {
396 	XtManageChild(XtParent(page_list));
397 	XtManageChild(page_list);
398 
399 	curr_x += resource.pagelist_width;
400 	curr_w -= resource.pagelist_width;
401 	XtVaSetValues(globals.widgets.main_window,
402 		      XtNwidth, curr_w,
403 		      XtNx, curr_x,
404 		      XmNleftAttachment, XmATTACH_WIDGET,
405 		      XmNleftWidget, XtParent(page_list),
406 		      NULL);
407     }
408     else {
409 	XtUnmanageChild(XtParent(page_list));
410 	XtUnmanageChild(page_list);
411 
412 	curr_x -= resource.pagelist_width;
413 	curr_w += resource.pagelist_width;
414 	XtVaSetValues(globals.widgets.main_window,
415 		      XmNwidth, curr_w,
416 		      XmNx, curr_x,
417 		      XmNleftAttachment, XmATTACH_FORM,
418 		      NULL);
419     }
420 
421     set_menu(&resource.expert_mode, Act_set_expert_mode, check_resource_expert);
422 }
423 #endif
424 
425 
426 Boolean
pageinfo_have_marked_pages(void)427 pageinfo_have_marked_pages(void)
428 {
429     int i;
430     for (i = 0; i < total_pages; i++) {
431 	if (page_info.index[i].marked) {
432 	    return True;
433 	}
434     }
435     return False;
436 }
437 
438 
439 /* return True if page i is marked, False else */
440 Boolean
pageinfo_is_marked(int i)441 pageinfo_is_marked(int i)
442 {
443     ASSERT(i <= (int)page_info.index_size, "");
444     return page_info.index[i].marked;
445 }
446 
447 
448 typedef enum { SCROLL_UP, SCROLL_DOWN, CLICK } saveCmdT;
449 
450 
451 static void internal_process_button2(Widget widget, XEvent *event);
452 static int get_item_index(Widget w, int mouse_y);
453 
454 static int
get_page_size(void)455 get_page_size(void)
456 {
457     int offset = 0;
458     int min_page = 0;
459     int max_page = 0;
460     int min_pageno_len = 0;
461     int max_pageno_len = 0;
462     int i;
463 
464     if (globals.dvi_file.bak_fp == NULL)
465 	return 0;
466 
467     for (i = 0; i < total_pages; i++) {
468 	max_page = MAX(page_info.index[i].number, max_page);
469 	min_page = MIN(page_info.index[i].number, min_page);
470     }
471 
472     if (min_page >= 0) {
473 	offset = 0;	/* plus symbol is hidden */
474     } else {
475 	offset = 1;	/* offset for minus symbol */
476 	min_page = -min_page;
477     }
478     for (min_pageno_len = offset; min_page > 0;
479 	 min_page /= 10, min_pageno_len++);
480 
481     if (max_page >= 0) {
482 	offset = 0;	/* plus symbol is hidden */
483     } else {
484 	offset = 1;	/* offset for minus symbol */
485 	max_page = -max_page;
486     }
487     for (max_pageno_len = offset; max_page > 0;
488 	 max_page /= 10, max_pageno_len++);
489 
490     return MAX(min_pageno_len, max_pageno_len);
491     /* Plus 1 for minus symbol */
492 }
493 
494 #if !defined(LESSTIF_VERSION)
495 /* draw or erase highlight marker at position x, y with widht w an height h,
496    using suitable offsets (for sake of consistency of the latter, and because
497    of the differences Xaw/Motif, this is a separate function).
498 */
499 static void
refresh_highlight_marker(Widget widget,GC gc,Position x,Position y,Dimension w,Dimension h)500 refresh_highlight_marker(Widget widget, GC gc,
501 			 Position x, Position y, Dimension w, Dimension h)
502 {
503     XDrawRectangle(XtDisplay(widget), XtWindow(widget), gc,
504 #ifdef MOTIF
505 		   x + 1, y + 1, w - 3, h - 3
506 #else
507 		   x + 2, y, w - 1, h - 1
508 #endif
509 		   );
510 }
511 #endif /* !defined(LESSTIF_VERSION) */
512 
513 #ifndef MOTIF
514 
515 /* update (redisplay) the list, saving the currently highlighted item.
516  * This is invoked whenever marking the page, and the re-construction of the
517  * entire list causes considerable flicker; but I guess that's unavoidable
518  * with the current simplistic labelling scheme (with changing the list items
519  * themselves). Gv does this considerably better (using custom widgets).
520  */
521 static void
xaw_update_list(void)522 xaw_update_list(void)
523 {
524     static int pagelist_width = -1;
525     static int total_pages_bak = -1;
526 
527     XawListReturnStruct *ret;
528     int idx, button_width;
529 
530     if (pagelist_width == -1 || total_pages != total_pages_bak) {
531 	pagelist_width = xaw_get_pagelist_size();
532 	total_pages_bak = total_pages;
533     }
534 
535     /* save selected item */
536     ret = XawListShowCurrent(LIST_WIDGET);
537     idx = ret->list_index;
538     button_width = get_panel_width() - 2 * (resource.btn_side_spacing + resource.btn_border_width);
539     /* delete and re-create list */
540     ASSERT(total_pages <= (int)page_info.index_size, "");
541     XawListChange(LIST_WIDGET, page_info.page_labels, 0,
542 		  MAX(button_width, pagelist_width), False);
543     /* restore selected item */
544     if (idx != XAW_LIST_NONE) {
545 	XawListHighlight(LIST_WIDGET, idx);
546     }
547 }
548 
549 /*
550   return height of a row in the list widget, and the initial offset of XtNinternalHeight
551   in parameters row_height and internal_h
552 */
553 static void
xaw_get_row_height(Widget w,Dimension * row_height,Dimension * internal_h)554 xaw_get_row_height(Widget w, Dimension *row_height, Dimension *internal_h)
555 {
556     Dimension row_space;
557     XFontStruct *font;
558     Arg arglist[5];
559     int i = 0;
560 
561     if (w == NULL || !XtIsRealized(w))
562 	return;
563 
564     XtSetArg(arglist[i], XtNfont, &font); ++i;
565     XtSetArg(arglist[i], XtNrowSpacing, &row_space); ++i;
566     XtSetArg(arglist[i], XtNinternalHeight, internal_h); i++;
567     XtGetValues(w, arglist, i);
568     *row_height = font->max_bounds.ascent + font->max_bounds.descent + row_space;
569 }
570 
571 /*
572  * Get pagelist width.
573  */
574 int
xaw_get_pagelist_size(void)575 xaw_get_pagelist_size(void)
576 {
577     Widget w;
578     XFontStruct *font;
579 
580     w = XtVaCreateWidget("list", listWidgetClass, globals.widgets.top_level, NULL);
581     XtVaGetValues(w, XtNfont, &font, NULL);
582     XtDestroyWidget(w);
583 
584     /* have space for max. pageno + space + current-marker,
585        plus a few pixels for right margin */
586     return (get_page_size() + 2) * get_avg_font_width(font) + 6;
587 }
588 
589 /* auto-scroll pagelist when mouse-1 is down and moved above top or below bottom of
590    page list.
591 */
592 static void
xaw_drag_page_callback(Widget widget,XtPointer data,XEvent * event,Boolean * cont)593 xaw_drag_page_callback(Widget widget, XtPointer data, XEvent *event, Boolean *cont)
594 {
595     int y, idx, actual_idx = 0;
596 
597     UNUSED(data);
598     UNUSED(cont);
599 
600     if (event->xany.type == ButtonPress || ((event->xbutton.state & Button1Mask) == 0))
601 	return;
602     if (event->xany.type == MotionNotify)
603 	y = (int)event->xmotion.y;
604     else
605 	y = (int)event->xbutton.y;
606 
607     idx = get_item_index(widget, y);
608 
609     if (idx <= 0) {
610 	idx = 1;
611     }
612     else if (idx > total_pages) {
613 	idx = total_pages;
614     }
615 
616     actual_idx = xaw_maybe_scroll_pagelist(idx, False, actual_idx);
617     XawListHighlight(LIST_WIDGET, idx - MOTIF_IDX_OFFSET);
618 
619     /*      if (event->xany.type == ButtonRelease) { */
620     /*  	fprintf(stderr, "1\n"); */
621     /*  	page_history_insert(idx - MOTIF_IDX_OFFSET); */
622     /*  	goto_page(idx - MOTIF_IDX_OFFSET, resource.keep_flag ? NULL : home); */
623     /*  	search_signal_page_changed(); */
624     /*      } */
625 }
626 
627 static void
xaw_SendReportProc(Widget w,XtPointer closure,XtPointer call_data)628 xaw_SendReportProc(Widget w, XtPointer closure, XtPointer call_data)
629 {
630     XawPannerReport *rep = (XawPannerReport *) call_data;
631 
632     UNUSED(w);
633     UNUSED(closure);
634 
635     view_y = rep->slider_y;
636 }
637 
638 #endif /* not MOTIF */
639 
640 
641 
642 /* returns the index of the current item in `Motif'-style, i.e. first item has index 1, not 0 */
643 static int
get_item_index(Widget w,int mouse_y)644 get_item_index(Widget w, int mouse_y)
645 {
646 #ifdef MOTIF
647     return XmListYToPos(w, mouse_y);
648 #else
649     Dimension row_height, internal_height;
650 
651     xaw_get_row_height(w, &row_height, &internal_height);
652     return (mouse_y - internal_height) / row_height + MOTIF_IDX_OFFSET;
653 #endif
654 }
655 
656 #if !defined(LESSTIF_VERSION)
657 /* idx is Motif-style index, i.e. 1 for 1st item, not 0 */
658 static Boolean
my_list_pos_to_bounds(Widget widget,int idx,Position * x,Position * y,Dimension * w,Dimension * h)659 my_list_pos_to_bounds(Widget widget, int idx, Position *x, Position *y, Dimension *w, Dimension *h)
660 {
661 #ifdef MOTIF
662     Position x1, y1;
663     Dimension w1, h1;
664     if (XmListPosToBounds(widget, idx, &x1, &y1, &w1, &h1)) {
665 	*x = x1;
666 	*y = y1;
667 	*w = w1;
668 	*h = h1;
669 	return True;
670     }
671     return False;
672 #else
673     Dimension row_height, internal_height;
674     /* FIXME: Remove this hard-coded offset! */
675     const int X_OFFSET = 9;
676     const int RULE_OFFSET = 2;
677 
678     if (idx <= 0 || idx > total_pages) {
679 	return False;
680     }
681 
682     if (viewport != NULL && XtIsRealized(viewport))
683 	XtVaGetValues(viewport, XtNx, x, NULL);
684     xaw_get_row_height(widget, &row_height, &internal_height);
685     XtVaGetValues(widget, XtNwidth, w, NULL);
686 
687     *x -= X_OFFSET;
688     *y = row_height * idx + internal_height - row_height - 1;
689     *w -= RULE_OFFSET;
690     *h = row_height + RULE_OFFSET;
691 
692     return True;
693 #endif
694 }
695 
696 
697 /* draw a hightlight rectangle around the page the mouse is currently over, to
698    make e.g. marking easier.
699 */
700 static void
highlight_page_callback(Widget widget,XtPointer data,XEvent * event,Boolean * cont)701 highlight_page_callback(Widget widget, XtPointer data, XEvent *event, Boolean *cont)
702 {
703     int curr_idx = get_item_index(widget, event->xmotion.y);
704     Position x, y;
705     Dimension w, h;
706     static int idx_bak = -1;
707 
708     UNUSED(data);
709     UNUSED(cont);
710 
711     switch(event->xany.type) {
712     case ButtonPress:
713     case ButtonRelease:
714     case MotionNotify:
715 	/* might need to un-highlight previous one */
716 	if (idx_bak >= 0 && idx_bak != curr_idx
717 	    && my_list_pos_to_bounds(widget, idx_bak, &x, &y, &w, &h)) {
718 	    /* 	    fprintf(stderr, "index: %d, %d, h: %d, w: %d\n", x, y, h, w); */
719 	    refresh_highlight_marker(widget, m_page_gc.back, x, y, w, h);
720 #if REDRAW_CURRENT_MARKER_HACK
721 	    xaw_maybe_redraw_highlight(curr_idx);
722 #endif
723 	}
724 	idx_bak = curr_idx;
725 	/* redraw unless out of bounds (when pagelist is shorter than view area) */
726 	if (my_list_pos_to_bounds(widget, curr_idx, &x, &y, &w, &h)) {
727 	    refresh_highlight_marker(widget, m_page_gc.fore, x, y, w, h);
728 #if REDRAW_CURRENT_MARKER_HACK
729 	    g_current_highlighted = curr_idx;
730 #endif
731 	}
732 	break;
733     case LeaveNotify:
734 	/* this might look overly complicated, but is neccessary to cover all
735 	   cases of 1-pixel movement up/down before leaving the list, or no
736 	   movement at all before leaving it. */
737 	if ((idx_bak >= 0 && idx_bak != curr_idx && my_list_pos_to_bounds(widget, idx_bak, &x, &y, &w, &h))
738 	    || my_list_pos_to_bounds(widget, curr_idx, &x, &y, &w, &h)) {
739 	    refresh_highlight_marker(widget, m_page_gc.back, x, y, w, h);
740 #if REDRAW_CURRENT_MARKER_HACK
741 	    xaw_maybe_redraw_highlight(curr_idx);
742 #endif
743 	}
744 	break;
745     default:
746 	break;
747     }
748 }
749 #endif /* !defined(LESSTIF_VERSION) */
750 
751 /*
752  * invoked on Button-1 Down.
753  */
754 static void
select_page_callback(Widget w,XtPointer closure,XtPointer call_data)755 select_page_callback(Widget w, XtPointer closure, XtPointer call_data)
756 {
757 #ifdef MOTIF
758     XmListCallbackStruct *cbs = (XmListCallbackStruct *) call_data;
759     int new = cbs->item_position;
760 
761     UNUSED(w);
762     UNUSED(closure);
763 
764     maybe_scroll_pagelist(new - MOTIF_IDX_OFFSET, False);
765     page_history_insert(new - MOTIF_IDX_OFFSET);
766     goto_page(new - MOTIF_IDX_OFFSET, resource.keep_flag ? NULL : home, False);
767 #else
768     XawListReturnStruct *item = (XawListReturnStruct *) call_data;
769     int new = item->list_index;
770 
771     UNUSED(w);
772     UNUSED(closure);
773 
774     if (globals.debug & DBG_EVENT)
775 	fprintf(stderr, "got: button-1 for `%d'\n", new);
776 
777 #if 0
778     fprintf(stderr, "select page: %d\n", new);
779 #endif
780     maybe_scroll_pagelist(new, False);
781     page_history_insert(new);
782     goto_page(new, resource.keep_flag ? NULL : home, False);
783     statusline_erase("Page history:");
784 #endif
785     search_signal_page_changed();
786 }
787 
788 
789 static void
init_pagelabels(int start,int end)790 init_pagelabels(int start, int end)
791 {
792     int i;
793     char s[PAGENUMLEN];
794 #if 0
795     fprintf(stderr, "===== init_pagelabels from %d to %d\n", start, end);
796 #endif
797     ASSERT(end < (int)page_info.index_size, "");
798 
799     page_info.page_labels = xrealloc(page_info.page_labels, sizeof *(page_info.page_labels) * (end + 2));
800     for (i = start; i < end; i++) {
801 	if (page_info.index[i].marked)
802 	    sprintf(s, "* %*d  ", get_page_size(),
803 		    resource.use_tex_pages ? page_info.index[i].number : i + 1);
804 	else
805 	    sprintf(s, "  %*d  ", get_page_size(),
806 		    resource.use_tex_pages ? page_info.index[i].number : i + 1);
807 
808 	page_info.page_labels[i] = xstrdup(s);
809     }
810     page_info.page_labels[i] = NULL; /* terminate - important for creating the widget. */
811 }
812 
813 
814 #ifdef MOTIF
815 
816 
817 static int
xm_get_top_visible(int start)818 xm_get_top_visible(int start)
819 {
820     int top = start;
821     while (top < total_pages && !XmListPosToBounds(LIST_WIDGET, top, NULL, NULL, NULL, NULL))
822         top++;
823     return top;
824 }
825 
826 static int
xm_get_bottom_visible(int start)827 xm_get_bottom_visible(int start)
828 {
829     int bot = start;
830     while (bot < total_pages && XmListPosToBounds(LIST_WIDGET, bot, NULL, NULL, NULL, NULL))
831         bot++;
832     bot--;
833     return bot;
834 }
835 
836 /* Scroll pagelist down or up if needed, and update top_visible and
837    bot_visible.  List is always scrolled so that 1 element is still
838    visible below or above pointer, to make it possible to flip through
839    document by repeatedly clicking on first/last.
840 */
841 static void
xm_maybe_scroll_pagelist(int current,saveCmdT curr_cmd,int * top_visible,int * bot_visible)842 xm_maybe_scroll_pagelist(int current, saveCmdT curr_cmd, int *top_visible, int *bot_visible)
843 {
844 #if 0
845     fprintf(stderr, "topmost visible: %d, bottom: %d, current: %d, total: %d\n",
846 	    top_visible, bottom_visible, current, total_pages);
847 #endif
848 
849     if (current < *top_visible && curr_cmd != SCROLL_DOWN) {
850 	XmListSetPos(LIST_WIDGET,
851 		     current < 1
852 		     ? 1
853 		     : current);
854 	(*top_visible)--;
855 	(*bot_visible)--;
856     }
857     else if (current + MOTIF_IDX_OFFSET >= *bot_visible && curr_cmd != SCROLL_UP) {
858 	XmListSetBottomPos(LIST_WIDGET,
859 			   current + MOTIF_IDX_OFFSET >= total_pages
860 			   ? current + MOTIF_IDX_OFFSET
861 			   : current + MOTIF_IDX_OFFSET + 1);
862 	(*top_visible)++;
863 	(*bot_visible)++;
864     }
865 }
866 
867 static void
xm_set_page_labels(void)868 xm_set_page_labels(void)
869 {
870     int i;
871     char buf[PAGENUMLEN];
872 
873     XmString *motif_page_labels = xmalloc((total_pages + 2) * sizeof *motif_page_labels);
874 
875     for (i = 0; i < total_pages; ++i) {
876 	sprintf(buf, pageno_format, ' ', page_info.page_labels[i]);
877 	motif_page_labels[i] = XmStringCreateLocalized(buf);
878     }
879     XmListDeleteAllItems(LIST_WIDGET);
880     XmListAddItems(LIST_WIDGET, motif_page_labels, total_pages, 0);
881 
882     XmListSelectPos(LIST_WIDGET, current_page + MOTIF_IDX_OFFSET, False);
883     for (i = 0; i < total_pages; ++i) {
884 	XmStringFree(motif_page_labels[i]);
885     }
886     free(motif_page_labels);
887 }
888 
889 static void
xm_toggle_label(Widget widget,int idx,Boolean update)890 xm_toggle_label(Widget widget, int idx, Boolean update)
891 {
892     /* TODO: use `update' to update all labels at once when toggling multiple */
893     XmString str;
894     char *mark_font, buf[128];
895 
896     if (widget == NULL)
897 	return;
898 
899     UNUSED(update);
900 
901     /*     ensure_labelinfo_size(idx); */
902     ASSERT(idx < (int)page_info.index_size, "");
903     if (!page_info.index[idx].marked) {
904 	sprintf(buf, pageno_format, '*', page_info.page_labels[idx]);
905 	mark_font = "MARKED";
906 	page_info.index[idx].marked = True;
907     }
908     else {
909 	sprintf(buf, pageno_format, ' ', page_info.page_labels[idx]);
910 	mark_font = "UNMARKED";
911 	page_info.index[idx].marked = False;
912     }
913     /*     str = XmStringCreateLocalized(buf); */
914     str = XmStringCreateLtoR(buf, mark_font);
915     XmListReplaceItemsPos(widget, &str, 1, idx + MOTIF_IDX_OFFSET);
916     XmStringFree(str);
917 }
918 
919 
920 #else /* MOTIF */
921 
922 static void
mark_page_callback(Widget w,XtPointer data,XEvent * event,Boolean * cont)923 mark_page_callback(Widget w, XtPointer data, XEvent *event, Boolean *cont)
924 {
925     UNUSED(data);
926     UNUSED(cont);
927 
928     /* moving button2 generates MotionNotify events for button0 */
929     if (event->type == MotionNotify || event->xbutton.button == Button2)
930 	internal_process_button2(w, event);
931 
932     if (event->type != ButtonPress)
933 	notify_print_dialog_have_marked();
934 }
935 
936 void
xaw_create_pagelist_widgets(Dimension height,Dimension width,Position y,Widget parent)937 xaw_create_pagelist_widgets(Dimension height, Dimension width, Position y, Widget parent)
938 {
939     viewport = XtVaCreateWidget("viewport",
940 				viewportWidgetClass, parent,
941 				XtNallowVert, True,
942 				/* this is not related to the scroll bar: */
943 				/* XtNforceBars, True, */
944 				XtNx, resource.btn_side_spacing,
945 				XtNy, y,
946 				XtNheight, height,
947 				XtNwidth, width,
948 				NULL);
949     LIST_WIDGET = XtVaCreateWidget("list",
950 				   listWidgetClass, viewport,
951 				   XtNlist, page_info.page_labels,
952 				   XtNdefaultColumns, 1,
953 				   XtNforceColumns, True,
954 				   XtNx, 10,
955 				   XtNy, 10,
956 				   XtNheight, height,
957 				   XtNwidth, width - 10,
958 				   XtNlongest, LONGESTPAGENUM,
959 				   XtNverticalList, True,
960 				   NULL);
961     XtManageChild(LIST_WIDGET);
962     XtManageChild(viewport);
963     XtAddCallback(LIST_WIDGET, XtNcallback, select_page_callback,
964 		  (XtPointer) NULL);
965     /* for scrolling the list */
966     XtAddCallback(viewport, XtNreportCallback, xaw_SendReportProc,
967 		  (XtPointer) NULL);
968     XtAddEventHandler(LIST_WIDGET,
969 		      ButtonPressMask | ButtonReleaseMask | Button2MotionMask,
970 		      False, mark_page_callback, (XtPointer)NULL);
971 
972     if (resource.pagelist_highlight_current)
973 	XtAddEventHandler(LIST_WIDGET,
974 			  ButtonPressMask | ButtonReleaseMask | PointerMotionMask | LeaveWindowMask,
975 			  False, highlight_page_callback, (XtPointer)NULL);
976 
977 
978     {
979 	Widget y_bar;
980 	XtTranslations xlats = XtParseTranslationTable(
981 	  "<Btn4Down>,<Btn4Up>: scroll-list-up()\n"
982 	  "<Btn5Down>,<Btn5Up>: scroll-list-down()\n");
983 
984 	XtOverrideTranslations(LIST_WIDGET, xlats);
985 
986 	y_bar = XtNameToWidget(viewport, "vertical");
987 	if (y_bar != NULL)
988 	    XtOverrideTranslations(y_bar, xlats);
989     }
990 
991     XtAddEventHandler(LIST_WIDGET,
992 		      /* FIXME: We should add PointerMotionMask here, but handling PointerMotionMask
993 			 currently doesn't work with the Xaw list widget: the auto-scrolling code doesn't
994 			 realize when the mouse direction of the pointer movement changes, and continues
995 			 to scroll into the same direction. This will be rather annoying for users, so
996 			 we disabled PointerMotionMask for the time being.
997 		      */
998 		      ButtonReleaseMask /* | PointerMotionMask */ | Button1MotionMask,
999 		      False, xaw_drag_page_callback, (XtPointer)NULL);
1000 }
1001 
1002 static void
xaw_toggle_label(Widget w,int idx,Boolean update)1003 xaw_toggle_label(Widget w, int idx, Boolean update)
1004 {
1005     if (w == NULL)
1006 	return;
1007 
1008     /*     ensure_labelinfo_size(idx); */
1009     ASSERT(idx < (int)page_info.index_size, "");
1010     if (!page_info.index[idx].marked) {
1011 	/* 	sprintf(toc[idx], "* %*d  ", get_page_size(), page_index[idx].number);  */
1012 	sprintf(page_info.page_labels[idx], pageno_format, '*', get_page_size(),
1013 		resource.use_tex_pages ? page_info.index[idx].number : idx + 1);
1014 	page_info.index[idx].marked = True;
1015     }
1016     else {
1017 	sprintf(page_info.page_labels[idx], pageno_format, ' ', get_page_size(),
1018 		resource.use_tex_pages ? page_info.index[idx].number : idx + 1);
1019 	/* 	sprintf(toc[idx], "  %*d  ", get_page_size(), page_index[idx].number);  */
1020 	page_info.index[idx].marked = False;
1021     }
1022 
1023     if (update)
1024 	xaw_update_list();
1025 }
1026 
1027 static int
xaw_maybe_scroll_pagelist(int new_page,Boolean force_recenter,int idx_bak)1028 xaw_maybe_scroll_pagelist(int new_page, Boolean force_recenter, int idx_bak)
1029 {
1030     Position x;
1031     Position y, new_y, bot_y;
1032     Dimension view_height, row_height, internal_height;
1033     /* Dimension clip_height; */
1034     /* static Widget list_clip = 0; */
1035 
1036     if (LIST_WIDGET == NULL || (resource.expert_mode & XPRT_SHOW_BUTTONS) == 0)
1037 	return idx_bak;
1038 
1039     /*     if (list_clip == 0) { */
1040     /* 	list_clip = XtNameToWidget(viewport, "clip"); */
1041     /*     } */
1042     /*     if (XtIsRealized(list_clip)) */
1043     /* 	XtVaGetValues(list_clip, XtNheight, &clip_height, XtNx, &cx, XtNy, &y1, NULL); */
1044     if (viewport != NULL && XtIsRealized(viewport))
1045 	XtVaGetValues(viewport, XtNheight, &view_height, XtNx, &x, NULL);
1046     /*     fprintf(stderr, "diff: %d, %d, %d, %d, %d\n", cx - x, clip_height, view_height, y1, (int)y2); */
1047     xaw_get_row_height(LIST_WIDGET, &row_height, &internal_height);
1048     y = row_height * new_page;
1049 
1050 #if DEBUG
1051     fprintf(stderr, "###### xaw_maybe_scroll_pagelist: y %d, view_y %d, view_height %d, row_height %d, internal_height %d; actual %d\n",
1052 	    y, view_y, view_height, row_height, internal_height, idx_bak);
1053 #endif
1054     /*FIXME: when page list is destroyed, view_y will be 0 until user scrolls page list */
1055     bot_y = view_y + view_height;
1056     if (force_recenter || ((y >= bot_y - row_height))) {
1057 #if DEBUG
1058 	fprintf(stderr, "scrolled below bottom; incrementing %d to %d\n", view_y, view_y + row_height);
1059 #endif
1060 	y += row_height;
1061 	XawViewportSetCoordinates(viewport, x, y - view_height > 0 ? y - view_height : 0);
1062 	return new_page + 1;
1063     }
1064     else if (force_recenter || ((y <= view_y + row_height + internal_height))) {
1065 #if DEBUG
1066 	fprintf(stderr, "scrolled over top; new_y: %d\n", y - row_height);
1067 #endif
1068 	new_y = y - 2 * row_height;
1069 	XawViewportSetCoordinates(viewport, x, new_y);
1070 	return new_page - 1;
1071     }
1072     /* not scrolled */
1073     return -2;
1074 }
1075 
1076 #endif /* MOTIF */
1077 
1078 
1079 /* idx is C-style index (0-based), not Motif one (1-based) */
1080 static void
toggle_label(Widget widget,int idx,Boolean update)1081 toggle_label(Widget widget, int idx, Boolean update)
1082 {
1083     if (idx >= total_pages)
1084 	return;
1085     ASSERT(idx < total_pages, "");
1086     ASSERT(idx >= 0, "");
1087 #ifdef MOTIF
1088     xm_toggle_label(widget, idx, update);
1089 #else
1090     xaw_toggle_label(widget, idx, update);
1091 #endif
1092 }
1093 
1094 
1095 void
list_toggle_marks(int arg)1096 list_toggle_marks(int arg)
1097 {
1098     int i;
1099 
1100     if (arg < 0) { /* mark all */
1101 	for (i = 0; i < total_pages; i++) {
1102 	    ASSERT(i < (int)page_info.index_size, "");
1103 	    /* 	    ensure_labelinfo_size(i); */
1104 	    if (!page_info.index[i].marked) {
1105 		toggle_label(LIST_WIDGET, i, False);
1106 	    }
1107 	}
1108     }
1109     else if (arg == 0) { /* unmark all */
1110 	for (i = 0; i < total_pages; i++) {
1111 	    ASSERT(i < (int)page_info.index_size, "");
1112 	    /* 	    ensure_labelinfo_size(i); */
1113 	    if (page_info.index[i].marked) {
1114 		toggle_label(LIST_WIDGET, i, False);
1115 	    }
1116 	}
1117     }
1118     else { /* toggle odd/even */
1119 	if (arg == 2) /* toggle even */
1120 	    arg = 0;
1121 	for (i = 0; i < total_pages; i++) {
1122 	    if ((i + 1) % 2 == arg) {
1123 		toggle_label(LIST_WIDGET, i, False);
1124 	    }
1125 	}
1126     }
1127     /* TODO: update widget once for Motif as well! */
1128 #ifndef MOTIF
1129     xaw_update_list();
1130 #endif
1131     notify_print_dialog_have_marked();
1132 }
1133 
1134 static Boolean PagelistInitialized = False;
1135 
1136 #ifndef MOTIF
1137 void
handle_pagelist_resize(void)1138 handle_pagelist_resize(void)
1139 {
1140     /* TODO: the following will mess up the geometry of the list
1141        (doesn't increase height, and incrementally decreases width):
1142        if (list_widget) {
1143        Dimension height;
1144        --- without the (un)manage, I get an X Error:
1145        XtMakeGeometryRequest - parent has no geometry manager
1146        ---
1147        XtUnmanageChild(viewport);
1148        XtUnmanageChild(LIST_WIDGET);
1149        XtVaGetValues(globals.widgets.clip_widget, XtNheight, &height, NULL);
1150        height -= resource.btn_top_spacing + resource.btn_border_width + global_y_pos;
1151        XtVaSetValues(viewport, XtNheight, height, NULL);
1152        XtManageChild(LIST_WIDGET);
1153        XtManageChild(viewport);
1154        }
1155        ... so we use brute force instead: */
1156     handle_destroy_pagelist(LIST_WIDGET, NULL, NULL);
1157     create_pagelist();
1158 }
1159 
1160 void
handle_destroy_pagelist(Widget w,XtPointer client_data,XtPointer call_data)1161 handle_destroy_pagelist(Widget w, XtPointer client_data, XtPointer call_data)
1162 {
1163     UNUSED(w);
1164     UNUSED(client_data);
1165     UNUSED(call_data);
1166 
1167     if (viewport != NULL) {
1168 	XtDestroyWidget(viewport);
1169 	viewport = NULL;
1170 	LIST_WIDGET = NULL;
1171     }
1172     PagelistInitialized = False;
1173 }
1174 #endif /* MOTIF */
1175 
1176 void
create_pagelist(void)1177 create_pagelist(void)
1178 {
1179     Pixel background, foreground;
1180 #ifdef MOTIF
1181 
1182     /*     items = xrealloc(items, sizeof *items * (total_pages + 2)); */
1183     init_pagelabels(0, total_pages);
1184     xm_set_page_labels();
1185     if (!PagelistInitialized) {
1186 	XtAppContext app;
1187 
1188 	XtVaGetValues(LIST_WIDGET, XmNforeground, &foreground, XmNbackground, &background, NULL);
1189 	m_page_gc.back = set_or_make_gc(NULL, GXcopy, background, foreground);
1190 	m_page_gc.fore = set_or_make_gc(NULL, GXcopy, foreground, background);
1191 	XtManageChild(LIST_WIDGET);
1192 
1193 	XtAddCallback(LIST_WIDGET, XmNbrowseSelectionCallback, select_page_callback, NULL);
1194 #if !defined(LESSTIF_VERSION)
1195 	/*
1196 	  Don't use the highlighting hack with LessTif, since its XmListPosToBounds()
1197 	  is too broken to be usable (as of 0.93.36):
1198 	  - it returns generally too low values, apparently it doesn't take
1199 	  XmNlistSpacing into account;
1200 	  - it doesn't take scrollbar position into account.
1201 	*/
1202 	if (resource.pagelist_highlight_current)
1203 	    XtAddEventHandler(LIST_WIDGET,
1204 			      ButtonPressMask | ButtonReleaseMask | PointerMotionMask | LeaveWindowMask,
1205 			      False, highlight_page_callback, (XtPointer)NULL);
1206 #endif /* !defined(LESSTIF_VERSION) */
1207 	app = XtWidgetToApplicationContext(globals.widgets.top_level);
1208 	XtAppAddActions(app, CustomListActions, XtNumber(CustomListActions));
1209 	XtOverrideTranslations(LIST_WIDGET, XtParseTranslationTable(motif_custom_translations));
1210 	PagelistInitialized = True;
1211     }
1212 #else /* MOTIF */
1213     if ((resource.expert_mode & XPRT_SHOW_BUTTONS) == 0) {
1214 	PagelistInitialized = False; /* might need to re-create widgets in this case */
1215 	return;
1216     }
1217 
1218     if (globals.debug & DBG_GUI)
1219 	fprintf(stderr, "allocating list with %d pages\n", total_pages);
1220 
1221     init_pagelabels(0, total_pages);
1222     if (!PagelistInitialized) {
1223 	xaw_create_pagelist();
1224 	XtVaGetValues(LIST_WIDGET, XtNforeground, &foreground, XtNbackground, &background, NULL);
1225 	m_page_gc.back = set_or_make_gc(NULL, GXcopy, background, foreground);
1226 	m_page_gc.fore = set_or_make_gc(NULL, GXcopy, foreground, background);
1227 	PagelistInitialized = True;
1228     }
1229 #endif /* MOTIF */
1230     /* scroll to the current page if needed */
1231     maybe_scroll_pagelist(current_page, False);
1232 }
1233 
1234 #ifndef MOTIF
1235 static void
free_pagelabels(void)1236 free_pagelabels(void)
1237 {
1238     int i;
1239     for (i = 0; page_info.page_labels != NULL && page_info.page_labels[i] != NULL; i++) {
1240 	free(page_info.page_labels[i]);
1241     }
1242     free(page_info.page_labels);
1243     page_info.page_labels = NULL;
1244 }
1245 #endif /* not MOTIF */
1246 
1247 
1248 void
refresh_pagelist(int newsize,int newpage)1249 refresh_pagelist(int newsize, int newpage)
1250 {
1251     if (
1252 #ifndef MOTIF
1253 	(resource.expert_mode & XPRT_SHOW_BUTTONS) == 0 ||
1254 #endif
1255 	!XtIsRealized(globals.widgets.top_level))
1256 	return;
1257 
1258 #ifdef DEBUG
1259     fprintf(stderr, "=== refresh_pagelist: newsize %d, newpage %d\n", newsize, newpage);
1260 #endif
1261 #ifdef MOTIF
1262     /*     items = xrealloc(items, sizeof *items * (newsize + 2)); */
1263     init_pagelabels(0, newsize);
1264     xm_set_page_labels();
1265 #else /* MOTIF */
1266     if ((resource.expert_mode & XPRT_SHOW_BUTTONS) == 0)
1267 	return;
1268 
1269     /* FIXME - is this really neccessary?? The alternative:
1270        XawListChange(LIST_WIDGET, page_info.page_labels, newsize, 0, True);
1271        has problems when freeing the page labels afterwards.
1272     */
1273     handle_destroy_pagelist(LIST_WIDGET, NULL, NULL);
1274 
1275     free_pagelabels();
1276     init_pagelabels(0, newsize);
1277 
1278     xaw_create_pagelist();
1279 #endif /* MOTIF */
1280     /* `True' since the pagelist is newly created */
1281     maybe_scroll_pagelist(newpage, True);
1282 }
1283 
1284 void
maybe_scroll_pagelist(int newpage,Boolean force_recenter)1285 maybe_scroll_pagelist(int newpage, Boolean force_recenter)
1286 {
1287 #ifdef MOTIF
1288     int top_visible, bot_visible;
1289     UNUSED(force_recenter);
1290 #endif
1291 
1292     if (
1293 #ifndef MOTIF
1294 	(resource.expert_mode & XPRT_SHOW_BUTTONS) == 0 ||
1295 #endif
1296 	!XtIsRealized(globals.widgets.top_level))
1297 	return;
1298 
1299 #ifdef MOTIF
1300     XmListSelectPos(LIST_WIDGET, newpage + MOTIF_IDX_OFFSET, False);
1301 
1302     top_visible = xm_get_top_visible(1);
1303     bot_visible = xm_get_bottom_visible(top_visible);
1304 
1305     xm_maybe_scroll_pagelist(newpage, CLICK, &top_visible, &bot_visible);
1306 #if HAVE_XPM
1307     tb_check_navigation_sensitivity(current_page);
1308 #endif
1309 #else
1310 
1311     if (LIST_WIDGET == NULL)
1312 	return;
1313 
1314     (void)xaw_maybe_scroll_pagelist(newpage + 1, force_recenter, 0);
1315 
1316     XawListHighlight(LIST_WIDGET, newpage);
1317 #if REDRAW_CURRENT_MARKER_HACK
1318     /* if the XawListHighlight happens adjacent to the page that was
1319        last highlighted with our home-made `current selected'
1320        rectangle, it might overdraw that rectangle. In this case,
1321        restore it:
1322     */
1323     xaw_maybe_redraw_current_marker(newpage);
1324 #endif
1325 #endif
1326 }
1327 
1328 #ifdef MOTIF
1329 static void
set_all_marks(int from,int to)1330 set_all_marks(int from, int to)
1331 {
1332     int i;
1333     for (i = from; i < to; i++) {
1334 	/* 	ensure_labelinfo_size(i); */
1335 	page_info.index[i].marked = True;
1336 	toggle_label(LIST_WIDGET, i, False);
1337     }
1338 }
1339 
1340 static void
internal_process_button2_drag(Widget widget,XEvent * event)1341 internal_process_button2_drag(Widget widget, XEvent *event)
1342 {
1343     int i, idx, min = total_pages, max = 0;
1344     idx = get_item_index(widget, event->xbutton.y);
1345 
1346     for (i = 0; i < total_pages; i++) {
1347 	/* 	ensure_labelinfo_size(i); */
1348 	if (page_info.index[i].marked && i > max)
1349 	    max = i;
1350     }
1351     for (i = total_pages; i > 0; i--) {
1352 	/* 	ensure_labelinfo_size(i); */
1353 	if (page_info.index[i].marked && i < min)
1354 	    min = i;
1355     }
1356 
1357     if (min == total_pages)
1358 	min = 0;
1359     if (max == 0)
1360 	max = total_pages;
1361 
1362     if (idx < min) {
1363 	set_all_marks(idx, min);
1364     }
1365     else if (idx > max) {
1366 	set_all_marks(max + 1, idx);
1367     }
1368     else {
1369 	set_all_marks(0, idx);
1370     }
1371 }
1372 #endif /* MOTIF */
1373 
1374 static void
internal_process_button2(Widget widget,XEvent * event)1375 internal_process_button2(Widget widget, XEvent *event)
1376 {
1377     int curr_idx;
1378     static int prev_idx = 0;
1379 #ifndef MOTIF
1380     static int actual_idx = -2;
1381 #endif
1382     static int top_visible = 0, bot_visible = 0;
1383     static int prev_y = 0, curr_y = 0;
1384     static saveCmdT prev_cmd = 0;	/* previous command (CLICK/SCROLL_UP/SCROLL_DOWN) */
1385     static saveCmdT curr_cmd = 0;	/* current command (CLICK/SCROLL_UP/SCROLL_DOWN) */
1386     static saveCmdT bak_cmd = 0;	/* last command that started inside the pagelist (CLICK/SCROLL_UP/SCROLL_DOWN) */
1387     static Boolean change_scroll_direction = False;
1388 
1389     switch(event->xany.type) {
1390     case ButtonPress:
1391 	prev_y = event->xbutton.y;
1392 	prev_idx = curr_idx = get_item_index(widget, prev_y);
1393 #ifdef MOTIF
1394 	top_visible = xm_get_top_visible(1);
1395 	bot_visible = xm_get_bottom_visible(top_visible);
1396 #endif
1397 	toggle_label(widget, curr_idx - 1, True);
1398 	prev_cmd = CLICK;
1399 #ifdef MOTIF
1400 	xm_maybe_scroll_pagelist(curr_idx - 1, CLICK, &top_visible, &bot_visible);
1401 #else
1402 	actual_idx = xaw_maybe_scroll_pagelist(curr_idx, False, actual_idx);
1403 #endif
1404 	break;
1405     case ButtonRelease:
1406 	prev_cmd = CLICK;
1407 	break;
1408     case MotionNotify:
1409 	curr_y = (int)event->xmotion.y;
1410 	curr_idx = get_item_index(widget, event->xmotion.y);
1411 #ifndef MOTIF
1412 	if (actual_idx > 0) {
1413 	    curr_idx = actual_idx;
1414 	}
1415 #endif
1416 
1417 	if (curr_y < prev_y)
1418 	    curr_cmd = SCROLL_UP;
1419 	else if (curr_y > prev_y)
1420 	    curr_cmd = SCROLL_DOWN;
1421 	prev_y = curr_y;
1422 
1423 	if (prev_cmd != CLICK && curr_cmd != prev_cmd)
1424 	    change_scroll_direction = True;
1425 
1426 	if ((curr_idx != prev_idx && !change_scroll_direction)
1427 	    || (change_scroll_direction
1428 		/* last or first visible are always spared, unless they are really
1429 		   the first or last page; this way, always 1 more page is visible
1430 		   than is currently marked/selected
1431 		*/
1432 		&& !(curr_idx == top_visible) && !(curr_idx == bot_visible))) {
1433 	    if (curr_idx <= 0) {
1434 		/* When user has scrolled off, mark this by setting curr_idx to 1 more or less
1435 		   than the pagelist has so that the last/first page don't oscillate between
1436 		   marked/unmarked state when user continues to scroll.
1437 		   Also, we continue scrolling as long as user drags in the same direction as
1438 		   the last `real' scrolling event (saved as bak_cmd).
1439 		*/
1440 		if (curr_cmd == SCROLL_DOWN && bak_cmd == SCROLL_DOWN && prev_idx <= total_pages) {
1441 		    curr_idx = prev_idx + 1;
1442 		}
1443 		else if (curr_cmd == SCROLL_UP && bak_cmd == SCROLL_UP && prev_idx > 0) {
1444 		    curr_idx = prev_idx - 1;
1445 		}
1446 	    }
1447 
1448 
1449 	    if (curr_idx > 0 && curr_idx <= total_pages) {
1450 		toggle_label(widget, curr_idx - 1, True);
1451 #ifdef MOTIF
1452 		xm_maybe_scroll_pagelist(curr_idx - 1, curr_cmd, &top_visible, &bot_visible);
1453 #else
1454 		actual_idx = xaw_maybe_scroll_pagelist(curr_idx, False, actual_idx);
1455 #endif
1456 		prev_idx = curr_idx;
1457 		bak_cmd = curr_cmd;
1458 		change_scroll_direction = False;
1459 	    }
1460 	    else {
1461 #ifndef MOTIF
1462 		if (curr_idx > total_pages)
1463 		    actual_idx = -2;
1464 		else
1465 		    actual_idx = xaw_maybe_scroll_pagelist(curr_idx, False, actual_idx);
1466 #endif
1467 	    }
1468 	    prev_cmd = curr_cmd;
1469 	}
1470 	break;
1471     default:
1472 	break;
1473     }
1474 }
1475 
1476 void
list_toggle_current(int arg)1477 list_toggle_current(int arg)
1478 {
1479     toggle_label(LIST_WIDGET, arg, True);
1480 
1481     notify_print_dialog_have_marked();
1482 }
1483 
1484 #ifdef MOTIF
1485 
1486 static void
xm_list_set_mark(Widget widget,XEvent * event,String * params,Cardinal * num_params)1487 xm_list_set_mark(Widget widget,
1488 		 XEvent *event,
1489 		 String *params,
1490 		 Cardinal *num_params)
1491 {
1492     UNUSED(params);
1493 
1494     if ((*num_params != 1) || !XmIsList(widget))
1495 	return;
1496 
1497     internal_process_button2(widget, event);
1498 
1499     if (event->type != ButtonPress)
1500 	notify_print_dialog_have_marked();
1501 }
1502 
1503 static void
xm_list_drag_mark(Widget widget,XEvent * event,String * params,Cardinal * num_params)1504 xm_list_drag_mark(Widget widget,
1505 		  XEvent *event,
1506 		  String *params,
1507 		  Cardinal *num_params)
1508 {
1509     UNUSED(params);
1510 
1511     if ((*num_params != 1) || !XmIsList(widget))
1512 	return;
1513 
1514     internal_process_button2_drag(widget, event);
1515 
1516     if (event->type != ButtonPress)
1517 	notify_print_dialog_have_marked();
1518 }
1519 
1520 #endif /* MOTIF */
1521