1 /*
2  * Copyright (c) 2004 Stefan Ulrich
3  *
4  * Permission is hereby granted, free of charge, to any person obtaining a copy
5  * of this software and associated documentation files (the "Software"), to
6  * deal in the Software without restriction, including without limitation the
7  * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
8  * sell copies of the Software, and to permit persons to whom the Software is
9  * furnished to do so, subject to the following conditions:
10  *
11  * The above copyright notice and this permission notice shall be included in
12  * all copies or substantial portions of the Software.
13  *
14  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15  * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
17  * IN NO EVENT SHALL PAUL VOJTA OR ANY OTHER AUTHOR OF THIS SOFTWARE BE
18  * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19  * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20  * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21  */
22 
23 /*
24   Very simple page history (i.e. stack of visited pages) for xdvik
25 */
26 
27 #include "xdvi-config.h"
28 #include "xdvi.h"
29 #include "util.h"
30 #include "dl_list.h"
31 #include "string-utils.h"
32 #include "events.h"
33 #include "dvi-init.h"
34 #include "statusline.h"
35 #include "message-window.h"
36 #include "xm_toolbar.h"
37 #include "pagehist.h"
38 
39 /****************************************************************************
40  *
41  * File-scope globals
42  *
43  ****************************************************************************/
44 
45 /* maximum number of items to print before truncating (10 left and right) */
46 static const int HISTORY_MAX_CONTEXT = 21;
47 
48 /* list of pages in history */
49 static struct dl_list *m_page_history = NULL;
50 
51 /* pointer to the head of the list, which only gets changed when
52    truncating the head (when list has reached its max size). */
53 static struct dl_list *m_page_history_head = NULL;
54 
55 /* current size of the list */
56 static int m_page_history_length = 0;
57 
58 /* current position in the list */
59 static int m_page_history_currpos = 0;
60 
61 /* lookup table of filenames visited so far */
62 static char **m_filename_list = NULL;
63 static size_t m_filename_size = 0;
64 
65 /* item in above list */
66 struct page_history {
67     int pageno;
68     int file_idx; /* index in file_list */
69 };
70 
71 #define DEBUG 0
72 
73 /****************************************************************************
74  *
75  * Private functions
76  *
77  ****************************************************************************/
78 
79 static void
page_history_show(struct dl_list * head,const struct dl_list * curr)80 page_history_show(struct dl_list *head, const struct dl_list *curr)
81 {
82 #if DEBUG
83     int n;
84     for (n = 0; head != NULL; head = head->next, n++) {
85 	struct page_history *item = (struct page_history *)(head->item);
86 	if (head == curr) {
87 	    fprintf(stderr, "item %d: <%d>\n", n, item->pageno);
88 	}
89 	else {
90 	    fprintf(stderr, "item %d: %d\n", n, item->pageno);
91 	}
92     }
93 #else
94     UNUSED(head);
95     UNUSED(curr);
96 #endif /* DEBUG */
97 }
98 
99 /* like above, but output goes to the statusline, and it prints only up to HISTORY_MAX_CONTEXT
100    items around the current one.
101 */
102 static void
page_history_show_statusline(struct dl_list * head,const struct dl_list * curr,const char * msg)103 page_history_show_statusline(struct dl_list *head,
104 			     const struct dl_list *curr,
105 			     const char *msg)
106 {
107 #define HIST_LEN 1024 /* should be ample since statusline is limited to 512 MAX_LEN */
108 
109     int file_idx = 0;
110 
111     int n;
112     char history[HIST_LEN];
113     char *ptr = history;
114     int tot_len = m_page_history_length;
115     int curr_pos = m_page_history_currpos;
116     int initial_offset = 0;
117     int printed_len = 0;
118 
119 #if DEBUG
120     fprintf(stderr, "tot_len: %d, curr_pos: %d\n", tot_len, curr_pos);
121 #endif
122 
123     if (!(resource.expert_mode & XPRT_SHOW_STATUSLINE)) {
124 	/* too distracting for stdout */
125 	return;
126     }
127 
128     if (head == NULL){
129 	strcpy(ptr, "Page history empty.");
130 	ptr += strlen("Page history empty.");
131     }
132     else {
133 	strcpy(ptr, "Page history:");
134 	ptr += strlen("Page history:");
135     }
136 
137     /* check if we need to truncate at the beginning or end */
138     if (tot_len > HISTORY_MAX_CONTEXT) {
139 	/* need to truncate, try to make first and second chunk around current position of same length */
140 	int good_pos = HISTORY_MAX_CONTEXT / 2.0 + 0.5;
141 	while (curr_pos > good_pos /*  && */
142 	       /*  	       m_page_history_length - m_page_history_currpos > good_pos */) {
143 #if DEBUG
144 	    fprintf(stderr, "%d > %d; %d > %d\n", curr_pos, good_pos,
145 		    m_page_history_length - m_page_history_currpos, good_pos);
146 #endif
147 	    curr_pos--;
148 	    initial_offset++;
149 	}
150 #if DEBUG
151 	fprintf(stderr, "initial offset: %d\n", initial_offset);
152 #endif
153 	/* if we're more to the end, adjust good_pos and initial_offset */
154 	while (good_pos - 1 > m_page_history_length - m_page_history_currpos) {
155 #if DEBUG
156 	    fprintf(stderr, "%d > %d\n", m_page_history_length - m_page_history_currpos, good_pos);
157 #endif
158 	    initial_offset--;
159 	    good_pos--;
160 	}
161 #if DEBUG
162 	fprintf(stderr, "initial offset adjusted: %d\n", initial_offset);
163 #endif
164     }
165 
166     for (n = 0; head != NULL; head = head->next, n++) {
167 	struct page_history *item;
168 	/* skip initial offset, and insert truncation marker at beginning/end */
169 	if (initial_offset == 1 || printed_len >= HISTORY_MAX_CONTEXT) {
170 	    strcpy(ptr, " ...");
171 	    ptr += strlen(" ...");
172 	    if (printed_len >= HISTORY_MAX_CONTEXT)
173 		break;
174 	}
175 	if (initial_offset > 0) {
176 	    initial_offset--;
177 	    continue;
178 	}
179 
180 	printed_len++;
181 
182 	item = (struct page_history *)(head->item);
183 
184 	/* insert a marker if item is in different file ... */
185 	if (item->file_idx != file_idx) {
186 	    if (n > 0) { /* ... but only if we're not at the beginning of the list */
187 #if 1
188 		strcpy(ptr, " -");
189 		ptr += 2;
190 #else
191 		char *fname = m_filename_list[item->file_idx];
192 		char *tmp;
193 		if ((tmp = strrchr(m_filename_list[item->file_idx], '/')) != NULL)
194 		    fname = tmp + 1;
195 		strcpy(ptr, " [");
196 		ptr += 2;
197 		strcpy(ptr, fname);
198 		ptr += strlen(fname);
199 		strcpy(ptr, "]");
200 		ptr++;
201 #endif
202 	    }
203 	    file_idx = item->file_idx;
204 	}
205 
206 	if (head == curr) {
207 	    xdvi_assert(XDVI_VERSION_INFO, __FILE__, __LINE__,
208 			m_page_history_currpos == n + 1,
209 			"%d == %d + 1", m_page_history_currpos, n);
210 	    sprintf(ptr, " [%d]", item->pageno + 1);
211 	    ptr += 3 + length_of_int(item->pageno + 1);
212 	}
213 	else {
214 	    sprintf(ptr, " %d", item->pageno + 1);
215 	    ptr += 1 + length_of_int(item->pageno + 1);
216 	}
217     }
218 #if DEBUG
219     fprintf(stderr, "Statusline string: |%s|; printed len: %d\n", history, printed_len);
220 #endif
221     statusline_info(STATUS_MEDIUM, "%s %s", history, msg ? msg : "");
222 #undef HIST_LEN
223 }
224 
225 static void
goto_location(const char * filename)226 goto_location(const char *filename)
227 {
228 #if DEBUG
229     fprintf(stderr, "going to file %s\n", filename);
230 #endif
231     if (strcmp(globals.dvi_name, filename) != 0) { /* it's a different file */
232 	Boolean tried_dvi_ext = True;
233 	char *new_dvi_name;
234 #if DEBUG
235 	fprintf(stderr, "different file: |%s|\n", filename);
236 #endif
237 	if ((new_dvi_name = open_dvi_file_wrapper(filename, True, False,
238 						  &tried_dvi_ext, True)) == NULL) {
239 	    statusline_append(STATUS_MEDIUM,
240 			      "Re-opening file",
241 			      "Re-opening file \"%s\" failed!", filename);
242 #if DEBUG
243 	    fprintf(stderr, "Re-opening file \"%s\" failed!\n", filename);
244 #endif
245 	    page_history_delete(1);
246 	    return;
247 	}
248 	else {
249 	    dviErrFlagT errflag;
250 	    if (load_dvi_file(
251 #if !DELAYED_MKTEXPK
252 			      True,
253 #endif
254 			      &errflag)) {
255 		set_dvi_name(new_dvi_name);
256 
257 		globals.ev.flags |= EV_NEWDOC;
258 		globals.ev.flags |= EV_PAGEHIST_GOTO_PAGE;
259 #if DEBUG
260 		fprintf(stderr, "Back to file: \"%s\"\n", globals.dvi_name);
261 #endif
262 	    }
263 	    else { /* re-open old file */
264 		popup_message(globals.widgets.top_level,
265 			      MSG_ERR,
266 			      NULL,
267 			      "Could not open `%s': %s.\n"
268 			      /* "Removing this file from the history." */,
269 			      globals.dvi_name, get_dvi_error(errflag));
270 
271 		if (!internal_open_dvi(globals.dvi_name, &errflag, True
272 #if DELAYED_MKTEXPK
273 				       , True
274 #endif
275 				       )) {
276 		    popup_message(globals.widgets.top_level,
277 				  MSG_ERR,
278 				  NULL,
279 				  "Couldn't reopen `%s': %s.\n"
280 				  /* "Removing this file from the history." */,
281 				  globals.dvi_name, get_dvi_error(errflag));
282 		}
283 		else {
284 		    globals.ev.flags |= EV_NEWPAGE;
285 		}
286 		page_history_delete(1);
287 	    }
288 	}
289     }
290     else {
291 	globals.ev.flags |= EV_PAGEHIST_GOTO_PAGE;
292     }
293 }
294 
295 /****************************************************************************
296  *
297  * Exported functions
298  *
299  ****************************************************************************/
300 
301 /*
302   Move n elements in page history; n == 0 doesn't move,
303   n < 0 moves n items back, n > 0 moves n items forward.
304 */
page_history_move(int n)305 void page_history_move(int n)
306 {
307     struct dl_list *pos;
308     struct page_history *item;
309     const char *msg = NULL;
310 
311     page_history_show(m_page_history_head, m_page_history);
312 
313     if (resource.page_history_size == 0)
314 	return;
315 
316     if (m_page_history_head == NULL)
317 	m_page_history_head = m_page_history;
318 
319     if (n < 0) { /* move backwards */
320 	for (pos = NULL; n < 0; n++) {
321 	    if (m_page_history != NULL)
322 		pos = m_page_history->prev;
323 	    if (pos == NULL) {
324 		xdvi_bell();
325 		msg = " - at begin of page history.";
326 		break;
327 	    }
328 	    else {
329 		m_page_history = pos;
330 		m_page_history_currpos--;
331 	    }
332 	}
333     }
334     else { /* move forward */
335 	for (pos = NULL; n > 0; n--) {
336 	    if (m_page_history != NULL)
337 		pos = m_page_history->next;
338 	    if (pos == NULL) {
339 		xdvi_bell();
340 		msg = " - at end of page history.";
341 		break;
342 	    }
343 	    else {
344 		m_page_history = pos;
345 		m_page_history_currpos++;
346 	    }
347 	}
348     }
349     item = (struct page_history *)m_page_history->item;
350 #if DEBUG
351     fprintf(stderr, "going to page %d\n", item->pageno);
352 #endif
353     goto_location(m_filename_list[item->file_idx]);
354 
355 #if defined(MOTIF) && HAVE_XPM
356     tb_set_pagehist_back_sensitivity(m_page_history->prev != NULL);
357     tb_set_pagehist_forward_sensitivity(m_page_history->next != NULL);
358 #endif
359 
360     page_history_show(m_page_history_head, m_page_history);
361     page_history_show_statusline(m_page_history_head, m_page_history, msg);
362     page_history_update_toolbar_navigation();
363 }
364 
365 int
page_history_get_page(void)366 page_history_get_page(void)
367 {
368     return ((struct page_history *)m_page_history->item)->pageno;
369 }
370 
371 /* add page n to the page history */
page_history_insert(int n)372 void page_history_insert(int n)
373 {
374     struct page_history *item = NULL;
375     static char *current_filename = NULL;
376     static size_t filename_idx = 0; /* index of current filename */
377 
378 #if DEBUG
379     fprintf(stderr, "inserting into history: %d\n", n);
380 #endif
381     page_history_show(m_page_history_head, m_page_history);
382     /* do nothing if no history is used */
383     if (resource.page_history_size == 0)
384 	return;
385 
386     if (m_page_history_head == NULL)
387 	m_page_history_head = m_page_history;
388 
389     item = xmalloc(sizeof *item);
390     /* first call, or filename changed -> update file_list */
391     if (current_filename == NULL || strcmp(current_filename, globals.dvi_name) != 0) {
392 	size_t i;
393 	current_filename = xstrdup(globals.dvi_name);
394 
395 	for (i = 0; i < m_filename_size; i++) {
396 #if DEBUG
397 	    fprintf(stderr, "comparing %d: |%s|%s|\n", i, current_filename, m_filename_list[i]);
398 #endif
399 	    if (strcmp(current_filename, m_filename_list[i]) == 0) { /* found */
400 		filename_idx = i;
401 		break;
402 	    }
403 	}
404 
405 	if (i >= m_filename_size) { /* not found, insert into file list */
406 	    m_filename_list = xrealloc(m_filename_list, (m_filename_size + 1) * sizeof *m_filename_list);
407 	    m_filename_list[m_filename_size] = filename_append_dvi(current_filename);
408 	    filename_idx = m_filename_size++;
409 #if DEBUG
410 	    fprintf(stderr, "NEW file %d: %s\n", filename_idx, current_filename);
411 #endif
412 	}
413     }
414 
415 #if DEBUG
416     fprintf(stderr, "-------- %d >= %d?\n", m_page_history_length, resource.page_history_size - 1);
417 #endif
418     if (m_page_history_length >= resource.page_history_size - 1) { /* truncate history */
419 	free(m_page_history_head->item);
420 	m_page_history_head = dl_list_truncate_head(m_page_history_head);
421     }
422     else {
423 	m_page_history_length++;
424     }
425 
426     item->pageno = n;
427     item->file_idx = filename_idx;
428 
429 #if DEBUG
430     fprintf(stderr, "inserting %d\n", item->pageno);
431 #endif
432     m_page_history = dl_list_insert(m_page_history, item);
433     m_page_history_currpos++;
434 
435 #if DEBUG
436     fprintf(stderr, "head: %p, curr: %p\n", (void *)m_page_history_head, (void *)m_page_history);
437 #endif
438     page_history_show(m_page_history_head, m_page_history);
439     page_history_update_toolbar_navigation();
440 }
441 
442 void
page_history_update_toolbar_navigation(void)443 page_history_update_toolbar_navigation(void)
444 {
445 #if defined(MOTIF) && HAVE_XPM
446     tb_set_htex_back_sensitivity(m_page_history->prev != NULL);
447     tb_set_htex_forward_sensitivity(m_page_history->next != NULL);
448 #endif
449 }
450 
451 void
page_history_clear(void)452 page_history_clear(void)
453 {
454     struct dl_list *pos, *curr;
455 
456     if (resource.page_history_size == 0)
457 	return;
458 
459     m_page_history_head = m_page_history;
460 
461     for (curr = m_page_history; curr != NULL && curr->prev != NULL; curr = pos) {
462 	pos = curr->prev;
463 	free(curr->item);
464 	(void)dl_list_remove_item(&curr);
465 	m_page_history_length--;
466 	page_history_show(m_page_history_head, m_page_history);
467 	page_history_show_statusline(m_page_history_head, m_page_history, NULL);
468     }
469 
470     /*     for (curr = m_page_history; curr != NULL && curr->next != NULL; curr = pos) { */
471     /* 	pos = curr->next; */
472     /* 	free(curr->item); */
473     /* 	(void)dl_list_remove_item(&curr); */
474     /* 	m_page_history_length--; */
475     /* 	page_history_show(m_page_history_head, m_page_history); */
476     /*     } */
477 
478 
479 }
480 
481 /*
482   Delete n elements from the page history.
483   If n < 0, delete current and n-1 previous items and move to the item before them.
484   If n > 0, delete current and n-1 next items and move to the item after them.
485   E.g. (with current position marked by `<>'):
486 
487   a b <c> d e
488   -> page_history_delete(-2)
489   -> <a> d e
490 
491   a b <c> d e
492   -> page_history_delete(2)
493   -> a b <e>
494 
495   Prints an error to the statusline if number of deletions exceeds the limits
496   of the list.
497 */
page_history_delete(int n)498 void page_history_delete(int n)
499 {
500     struct dl_list *pos;
501     struct page_history *item;
502     const char *msg = NULL;
503 
504     if (resource.page_history_size == 0)
505 	return;
506 
507     if (m_page_history_head == NULL)
508 	m_page_history_head = m_page_history;
509 
510     /*      fprintf(stderr, "deleting items: |%d|\n", n); */
511 
512     if (n < 0) { /* delete backwards */
513 	for (pos = NULL; n < 0; n++) {
514 	    if (m_page_history != NULL)
515 		pos = m_page_history->prev;
516 	    if (pos == NULL) {
517 		xdvi_bell();
518 		msg = " - at begin of page history.";
519 		break;
520 	    }
521 	    else {
522 		/* remove item */
523 		free(m_page_history->item);
524 		(void)dl_list_remove_item(&m_page_history);
525 		m_page_history = pos;
526 		m_page_history_currpos--;
527 		m_page_history_length--;
528 	    }
529 	}
530     }
531     else { /* delete forward */
532 	for (pos = NULL; n > 0; n--) {
533 	    if (m_page_history != NULL)
534 		pos = m_page_history->next;
535 	    if (pos == NULL) {
536 		xdvi_bell();
537 		msg = " - at end of page history.";
538 		break;
539 	    }
540 	    else {
541 		/* remove item */
542 		free(m_page_history->item);
543 		if (m_page_history->prev == NULL) { /* at head */
544 		    m_page_history_head = m_page_history = dl_list_truncate_head(m_page_history);
545 		}
546 		else {
547 		    (void)dl_list_remove_item(&m_page_history);
548 		    m_page_history = pos;
549 		}
550 		/* Note: m_page_history_currpos remains unchanged here */
551 		m_page_history_length--;
552 	    }
553 	}
554     }
555     item = (struct page_history *)m_page_history->item;
556 #if DEBUG
557     fprintf(stderr, "going to page %d\n", item->pageno);
558 #endif
559     goto_location(m_filename_list[item->file_idx]);
560     page_history_show(m_page_history_head, m_page_history);
561     page_history_show_statusline(m_page_history_head, m_page_history, msg);
562 }
563 
564