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