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   List of recently visited files, initialized from X resource
25   fileHistory and displayed in `File -> Open Recent' menu.
26 */
27 
28 #include <ctype.h>
29 
30 #include "xdvi-config.h"
31 #include "xdvi.h"
32 #include "util.h"
33 #include "dl_list.h"
34 #include "my-snprintf.h"
35 #include "string-utils.h"
36 #include "dvi-init.h"
37 #include "events.h"
38 #include "message-window.h"
39 #include "xm_menu.h"
40 #include "xaw_menu.h"
41 #include "filehist.h"
42 
43 /****************************************************************************
44  *
45  * File-scope globals
46  *
47  ****************************************************************************/
48 
49 /* list of files */
50 static struct dl_list *m_file_history = NULL;
51 
52 /* current size of the list */
53 static int m_file_history_length = 0;
54 
55 /* /\* current position in the list *\/ */
56 /* static int m_file_history_currpos = 0; */
57 
58 /* item in above list */
59 struct file_history {
60     char *filename;
61     int pageno;
62 };
63 
64 #define DEBUG 1
65 
66 /****************************************************************************
67  *
68  * Private functions
69  *
70  ****************************************************************************/
71 
72 static void
file_history_show(struct dl_list * list)73 file_history_show(struct dl_list *list)
74 {
75     if (globals.debug & DBG_FILES) {
76 	int n;
77 	fprintf(stderr, "======= File history:\n");
78 	for (n = 0; list != NULL; list = list->next, n++) {
79 	    struct file_history *item = (struct file_history *)(list->item);
80 	    if (item == NULL) {
81 		fprintf(stderr, "item %d is NULL!\n", n);
82 		continue;
83 	    }
84 	    fprintf(stderr, "item %d: %d:%s\n", n, item->pageno, item->filename);
85 	}
86     }
87 }
88 
89 
90 /****************************************************************************
91  *
92  * Exported functions
93  *
94  ****************************************************************************/
95 
96 void
file_history_init(void)97 file_history_init(void)
98 {
99     char **fileinfo;
100     size_t i;
101     struct dl_list *head = NULL;
102 
103     m_file_history_length = 0;
104 
105     if (resource.file_history == NULL)
106 	return;
107 
108     fileinfo = get_separated_list(resource.file_history, "\n", False);
109 
110     for (i = 0; fileinfo[i] != NULL && i < (size_t)resource.file_history_size; i++) {
111 	struct file_history *item = xmalloc(sizeof *item);
112 	char *ptr;
113 	int pageno = strtol(fileinfo[i], &ptr, 10);
114 	TRACE_FILES((stderr, "FILEINFO: %s", fileinfo[i]));
115 	if (ptr == fileinfo[i]) {
116 	    XDVI_WARNING((stderr, "Missing page number in resource line `%s'!\n",
117 			  fileinfo[i]));
118 	    pageno = 0;
119 	}
120 	else {
121 	    while (isspace((int)*ptr))
122 		ptr++;
123 	}
124 
125 	item->pageno = pageno;
126 	item->filename = xstrdup(ptr);
127 
128 	TRACE_FILES((stderr, "FILE: %d:%s", item->pageno, item->filename));
129 
130 	m_file_history = dl_list_insert(m_file_history, item);
131 	if (head == NULL) /* remember head position */
132 	    head = m_file_history;
133 
134 	TRACE_FILES((stderr, "NEW ELEM: %p", (void *)m_file_history));
135 
136 	m_file_history_length++;
137 
138 	free(fileinfo[i]);
139     }
140     free(fileinfo);
141 
142     m_file_history = head;
143     file_history_show(m_file_history);
144 }
145 
146 static Boolean
equals_filename(const void * it,const void * fname)147 equals_filename(const void *it, const void *fname)
148 {
149     const struct file_history *item = (const struct file_history *)it;
150     const char *filename = (const char *)fname;
151 
152     return strcmp(item->filename, filename) == 0;
153 }
154 
155 
156 /* put new elem for filename `filename' and page number `page' at the front
157    of the list. Return True if the addition caused the history to grow, else
158    False (in case the item was only moved to the front).
159 */
160 Boolean
file_history_push(const char * filename)161 file_history_push(const char *filename)
162 {
163     int i;
164     struct file_history *item = NULL;
165     void *removed_data = NULL;
166     struct file_history *removed_item = NULL;
167     int len_bak = m_file_history_length;
168     int count = 0;
169 
170     TRACE_FILES((stderr, "Pushing: |%s|", filename));
171 
172     /* if list already contains an item with same filename, remove it */
173     m_file_history = dl_list_remove(m_file_history, filename, &count, &removed_data, equals_filename);
174     if (count == 0)
175 	m_file_history_length++;
176 
177     removed_item = (struct file_history *)removed_data;
178 
179     file_history_show(m_file_history);
180 
181     TRACE_FILES((stderr, "current length: %d, max: %d", m_file_history_length, resource.file_history_size));
182 
183     /* truncate list if it has reached its max length */
184     if (m_file_history_length > resource.file_history_size) {
185 	struct dl_list *listpos = m_file_history;
186 	struct dl_list *last_pos = listpos;
187 	for (i = 0; listpos != NULL && i < m_file_history_length; i++) {
188 	    last_pos = listpos;
189 	    listpos = listpos->next;
190 	}
191 
192 	item = last_pos->item; /* reuse item */
193 	TRACE_FILES((stderr, "Re-using item: |%s| -> |%s|", item->filename, filename));
194 	item->filename = xrealloc(item->filename, strlen(filename) + 1);
195 	strcpy(item->filename, filename);
196 
197 	(void)dl_list_remove_item(&last_pos);
198 	m_file_history_length--;
199     }
200     else if (removed_item != NULL) {
201 	TRACE_FILES((stderr, "Re-using item: |%s|\n", removed_item->filename));
202 	item = removed_item; /* reuse item */
203     }
204     else {
205 	item = xmalloc(sizeof *item);
206 	TRACE_FILES((stderr, "NEW item: |%s|\n", filename));
207 	item->filename = xstrdup(filename);
208 	item->pageno = 0;
209     }
210 
211     /* add new element at front of list */
212     m_file_history = dl_list_push_front(m_file_history, item);
213 
214     file_history_show(m_file_history);
215 
216     TRACE_FILES((stderr, "returning: %d < %d", len_bak, m_file_history_length));
217     return len_bak < m_file_history_length;
218 }
219 
file_history_size(void)220 size_t file_history_size(void)
221 {
222     return m_file_history_length;
223 }
224 
file_history_set_page(int pageno)225 void file_history_set_page(int pageno)
226 {
227     struct dl_list *head;
228 
229     TRACE_FILES((stderr, "SETTING HEAD to %d", pageno));
230     file_history_show(m_file_history);
231     head = dl_list_head(m_file_history);
232     if (head != NULL) {
233 	struct file_history *item = (struct file_history *)head->item;
234 	TRACE_FILES((stderr, "Setting page of |%s| to %d", item->filename, pageno));
235 	item->pageno = pageno;
236     }
237 }
238 
file_history_set_page_at(int idx,int pageno)239 void file_history_set_page_at(int idx, int pageno)
240 {
241     int i;
242     struct dl_list *listpos = dl_list_head(m_file_history);
243     struct file_history *item;
244 
245     for (i = 0; listpos != NULL && i < idx; i++) {
246 	listpos = listpos->next;
247     }
248     if (listpos == NULL) {
249 	TRACE_FILES((stderr, "Asked for file at position %d, but only %d elements in list",
250 		     idx, i - 1));
251 	return;
252     }
253     item = (struct file_history *)listpos->item;
254     TRACE_FILES((stderr, "set_page_at index %d: Setting page of |%s| to %d", idx, item->filename, pageno));
255     item->pageno = pageno;
256 }
257 
file_history_get_page(void)258 int file_history_get_page(void)
259 {
260     struct dl_list *head = dl_list_head(m_file_history);
261     if (head != NULL) {
262 	struct file_history *item = (struct file_history *)head->item;
263 	TRACE_FILES((stderr, "Getting page of |%s|: %d", item->filename, item->pageno));
264 	return item->pageno;
265     }
266     return 0;
267 }
268 
269 /*
270  * Invoke `callback' for each elem of file history, passing
271  * the current index, filename, page number, and data passed to
272  * this function.
273  */
274 void
file_history_enumerate(filehistCallbackT callback,void * data)275 file_history_enumerate(filehistCallbackT callback, void *data)
276 {
277     int i;
278     struct dl_list *listpos = dl_list_head(m_file_history);
279 
280     for (i = 0; listpos != NULL; i++) {
281 	struct file_history *item = (struct file_history *)listpos->item;
282 	callback(i, item->filename, item->pageno, data);
283 	listpos = listpos->next;
284     }
285 }
286 
287 char *
file_history_get_elem(int idx,int * ret_page)288 file_history_get_elem(int idx, int *ret_page)
289 {
290     int i;
291     struct dl_list *listpos = dl_list_head(m_file_history);
292     struct file_history *item;
293 
294     for (i = 0; listpos != NULL && i < idx; i++) {
295 	listpos = listpos->next;
296     }
297     if (listpos == NULL) {
298 	XDVI_WARNING((stderr, "Asked for file at position %d, but only %d elements in list",
299 		      idx, i - 1));
300 	return NULL;
301     }
302     item = (struct file_history *)listpos->item;
303     *ret_page = item->pageno;
304     file_history_show(m_file_history);
305     return item->filename;
306 }
307 
308 char *
file_history_get_list(void)309 file_history_get_list(void)
310 {
311     char buf[LENGTH_OF_INT];
312     char *ret = xstrdup("");
313     struct dl_list *listpos;
314 
315     for (listpos = dl_list_head(m_file_history);
316 	 listpos != NULL;
317 	 listpos = listpos->next) {
318 
319 	struct file_history *item = (struct file_history *)listpos->item;
320 	SNPRINTF(buf, LENGTH_OF_INT, "%d ", item->pageno);
321 	ret = xstrcat(ret, buf);
322 	ret = xstrcat(ret, item->filename);
323 	ret = xstrcat(ret, "\n");
324     }
325 
326     ret[strlen(ret) - 1] = '\0'; /* chop off excess \n at end */
327     return ret;
328 }
329 
330 void
file_history_open(const char * fname)331 file_history_open(const char *fname)
332 {
333     Boolean tried_dvi_ext = True;
334     char *new_dvi_name = NULL;
335 
336     int dummy_cnt = 0;
337     void *dummy_data = NULL;
338 
339     file_history_set_page(current_page);
340     if ((new_dvi_name = open_dvi_file_wrapper(fname,
341 					      True, /* pretend file is from commandline, otherwise xdvi will
342 						       try to fork for non-existing files, leading to confusing
343 						       popup error messages */
344 					      False, &tried_dvi_ext,
345 					      True /* don't exit on error */)) == NULL) {
346 	/* remove this item from the file history */
347 	m_file_history = dl_list_remove(m_file_history, fname, &dummy_cnt, &dummy_data, equals_filename);
348 	m_file_history_length--;
349 	file_history_show(m_file_history);
350 
351 	filehist_menu_refresh();
352 	return;
353     }
354     else {
355 	dviErrFlagT errflag;
356 	if (load_dvi_file(
357 #if !DELAYED_MKTEXPK
358 			  True,
359 #endif
360 			  &errflag)) {
361 	    /* page_history_insert(pageno); */
362 	    set_dvi_name(new_dvi_name);
363 
364 	    globals.ev.flags |= EV_NEWDOC;
365 	    globals.ev.flags |= EV_FILEHIST_GOTO_PAGE;
366 	}
367 	else { /* re-open old file */
368 	    popup_message(globals.widgets.top_level,
369 			  MSG_ERR,
370 			  NULL,
371 			  "Could not open `%s': %s.\n"
372 			  /* "Removing this file from the history." */,
373 			  globals.dvi_name, get_dvi_error(errflag));
374 	    /* remove this item from the file history */
375 	    m_file_history = dl_list_remove(m_file_history, globals.dvi_name,
376 					    &dummy_cnt, &dummy_data,
377 					    equals_filename);
378 	    m_file_history_length--;
379 	    filehist_menu_refresh();
380 
381 	    if (!internal_open_dvi(globals.dvi_name, &errflag, True
382 #if DELAYED_MKTEXPK
383 				   , True
384 #endif
385 				   )) {
386 		popup_message(globals.widgets.top_level,
387 			      MSG_ERR,
388 			      NULL,
389 			      "Couldn't reopen `%s': %s.\n"
390 			      /* "Removing this file from the history." */,
391 			      globals.dvi_name, get_dvi_error(errflag));
392 		/* remove this item from the file history */
393 		m_file_history = dl_list_remove(m_file_history, globals.dvi_name, &dummy_cnt, &dummy_data, equals_filename);
394 		m_file_history_length--;
395 		filehist_menu_refresh();
396 	    }
397 	    else {
398 		globals.ev.flags |= EV_NEWPAGE;
399 	    }
400 	}
401     }
402 }
403