ecf73cfd5ab828d818d0cd854e5f8398f3410e75
[geeqie.git] / src / history_list.c
1 /*
2  * Copyright (C) 2008 - 2016 The Geeqie Team
3  *
4  * Authors: John Ellis, Vladimir Nadvornik, Laurent Monin
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License along
17  * with this program; if not, write to the Free Software Foundation, Inc.,
18  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19  */
20
21 #include "main.h"
22 #include "history_list.h"
23
24 #include "secure_save.h"
25 #include "ui_fileops.h"
26
27
28 /*
29  *-----------------------------------------------------------------------------
30  * Implements a history chain. Used by the Back and Forward toolbar buttons.
31  * Selecting any folder appends the path to the end of the chain.
32  * Pressing the Back and Forward buttons moves along the chain, but does
33  * not make additions to the chain.
34  * The chain always increases and is deleted at the end of the session
35  *-----------------------------------------------------------------------------
36  */
37
38 static GList *history_chain = NULL;
39 static guint chain_index = G_MAXUINT;
40 static gboolean nav_button = FALSE; /** Used to prevent the nav buttons making entries to the chain **/
41
42 const gchar *history_chain_back()
43 {
44         nav_button = TRUE;
45
46         chain_index = chain_index > 0 ? chain_index - 1 : 0;
47
48         return g_list_nth_data(history_chain, chain_index);
49 }
50
51 const gchar *history_chain_forward()
52 {
53         nav_button= TRUE;
54         guint last = g_list_length(history_chain) - 1;
55
56         chain_index = chain_index < last ? chain_index + 1 : last;
57
58         return g_list_nth_data(history_chain, chain_index);
59 }
60
61 /**
62  * @brief Appends a path to the history chain
63  * @param path Path selected
64  * 
65  * Each time the user selects a new path it is appended to the chain
66  * except when it is identical to the current last entry
67  * The pointer is always moved to the end of the chain
68  */
69 void history_chain_append_end(const gchar *path)
70 {
71         GList *work;
72
73         if (!nav_button)
74                 {
75                 if(chain_index == G_MAXUINT)
76                         {
77                         history_chain = g_list_append (history_chain, g_strdup(path));
78                         chain_index = 0;
79                         }
80                 else
81                         {
82                         work = g_list_last(history_chain);
83                         if (g_strcmp0(work->data , path) != 0)
84                                 {
85                                 history_chain = g_list_append (history_chain, g_strdup(path));
86                                 chain_index = g_list_length(history_chain) - 1;
87                                 DEBUG_3("%d %s", chain_index, path);
88                                 }
89                         else
90                                 {
91                                 chain_index = g_list_length(history_chain) - 1;
92                                 }
93                         }
94                 }
95         else
96                 {
97                 nav_button = FALSE;
98                 }
99 }
100
101 /*
102  *-----------------------------------------------------------------------------
103  * Implements an image history chain. Whenever an image is displayed it is
104  * appended to a chain.
105  * Pressing the Image Back and Image Forward buttons moves along the chain,
106  * but does not make additions to the chain.
107  * The chain always increases and is deleted at the end of the session
108  *-----------------------------------------------------------------------------
109  */
110 static GList *image_chain = NULL;
111 static guint image_chain_index = G_MAXUINT;
112 static gboolean image_nav_button = FALSE; /** Used to prevent the nav buttons making entries to the chain **/
113 const gchar *image_chain_back()
114 {
115         image_nav_button = TRUE;
116
117         image_chain_index = image_chain_index > 0 ? image_chain_index - 1 : 0;
118
119         return g_list_nth_data(image_chain, image_chain_index);
120 }
121
122 const gchar *image_chain_forward()
123 {
124         image_nav_button= TRUE;
125         guint last = g_list_length(image_chain) - 1;
126
127         image_chain_index = image_chain_index < last ? image_chain_index + 1 : last;
128
129         return g_list_nth_data(image_chain, image_chain_index);
130 }
131
132 /**
133  * @brief Appends a path to the image history chain
134  * @param path Image path selected
135  * 
136  * Each time the user selects a new image it is appended to the chain
137  * except when it is identical to the current last entry
138  * The pointer is always moved to the end of the chain
139  */
140 void image_chain_append_end(const gchar *path)
141 {
142         GList *work;
143
144         if (!image_nav_button)
145                 {
146                 if(image_chain_index == G_MAXUINT)
147                         {
148                         image_chain = g_list_append(image_chain, g_strdup(path));
149                         image_chain_index = 0;
150                         }
151                 else
152                         {
153                         work = g_list_last(image_chain);
154                         gchar *tmp;
155                         tmp = work->data;
156                         if (g_strcmp0(work->data , path) != 0)
157                                 {
158                                 image_chain = g_list_append(image_chain, g_strdup(path));
159                                 image_chain_index = g_list_length(image_chain) - 1;
160                                 DEBUG_3("%d %s", image_chain_index, path);
161                                 }
162                         else
163                                 {
164                                 image_chain_index = g_list_length(image_chain) - 1;
165                                 }
166                         }
167                 }
168         else
169                 {
170                 image_nav_button = FALSE;
171                 }
172 }
173
174 /*
175  *-----------------------------------------------------------------------------
176  * history lists
177  *-----------------------------------------------------------------------------
178  */
179
180 #define HISTORY_DEFAULT_KEY_COUNT 16
181
182
183 typedef struct _HistoryData HistoryData;
184 struct _HistoryData
185 {
186         gchar *key;
187         GList *list;
188 };
189
190 static GList *history_list = NULL;
191
192
193 static gchar *quoted_from_text(const gchar *text)
194 {
195         const gchar *ptr;
196         gint c = 0;
197         gint l = strlen(text);
198
199         if (l == 0) return NULL;
200
201         while (c < l && text[c] !='"') c++;
202         if (text[c] == '"')
203                 {
204                 gint e;
205                 c++;
206                 ptr = text + c;
207                 e = c;
208                 while (e < l && text[e] !='"') e++;
209                 if (text[e] == '"')
210                         {
211                         if (e - c > 0)
212                                 {
213                                 return g_strndup(ptr, e - c);
214                                 }
215                         }
216                 }
217         return NULL;
218 }
219
220 gboolean history_list_load(const gchar *path)
221 {
222         FILE *f;
223         gchar *key = NULL;
224         gchar s_buf[1024];
225         gchar *pathl;
226
227         pathl = path_from_utf8(path);
228         f = fopen(pathl, "r");
229         g_free(pathl);
230         if (!f) return FALSE;
231
232         /* first line must start with History comment */
233         if (!fgets(s_buf, sizeof(s_buf), f) ||
234             strncmp(s_buf, "#History", 8) != 0)
235                 {
236                 fclose(f);
237                 return FALSE;
238                 }
239
240         while (fgets(s_buf, sizeof(s_buf), f))
241                 {
242                 if (s_buf[0]=='#') continue;
243                 if (s_buf[0]=='[')
244                         {
245                         gint c;
246                         gchar *ptr;
247
248                         ptr = s_buf + 1;
249                         c = 0;
250                         while (ptr[c] != ']' && ptr[c] != '\n' && ptr[c] != '\0') c++;
251
252                         g_free(key);
253                         key = g_strndup(ptr, c);
254                         }
255                 else
256                         {
257                         gchar *value;
258
259                         value = quoted_from_text(s_buf);
260                         if (value && key)
261                                 {
262                                 history_list_add_to_key(key, value, 0);
263                                 }
264                         g_free(value);
265                         }
266                 }
267
268         fclose(f);
269
270         g_free(key);
271
272         return TRUE;
273 }
274
275 gboolean history_list_save(const gchar *path)
276 {
277         SecureSaveInfo *ssi;
278         GList *list;
279         gchar *pathl;
280         gint list_count;
281
282         pathl = path_from_utf8(path);
283         ssi = secure_open(pathl);
284         g_free(pathl);
285         if (!ssi)
286                 {
287                 log_printf(_("Unable to write history lists to: %s\n"), path);
288                 return FALSE;
289                 }
290
291         secure_fprintf(ssi, "#History lists\n\n");
292
293         list = g_list_last(history_list);
294         while (list && secsave_errno == SS_ERR_NONE)
295                 {
296                 HistoryData *hd;
297                 GList *work;
298
299                 hd = list->data;
300                 list = list->prev;
301
302                 secure_fprintf(ssi, "[%s]\n", hd->key);
303
304                 /* save them inverted (oldest to newest)
305                  * so that when reading they are added correctly
306                  */
307                 work = g_list_last(hd->list);
308                 list_count = g_list_position(hd->list, g_list_last(hd->list)) + 1;
309                 while (work && secsave_errno == SS_ERR_NONE)
310                         {
311                         if ((!(strcmp(hd->key, "path_list") == 0 && list_count > options->open_recent_list_maxsize)) && (!(strcmp(hd->key, "recent") == 0 && (!isfile(work->data)))))
312                                 {
313                                 secure_fprintf(ssi, "\"%s\"\n", (gchar *)work->data);
314                                 }
315                         work = work->prev;
316                         list_count--;
317                         }
318                 secure_fputc(ssi, '\n');
319                 }
320
321         secure_fprintf(ssi, "#end\n");
322
323         return (secure_close(ssi) == 0);
324 }
325
326 static void history_list_free(HistoryData *hd)
327 {
328         GList *work;
329
330         if (!hd) return;
331
332         work = hd->list;
333         while (work)
334                 {
335                 g_free(work->data);
336                 work = work->next;
337                 }
338
339         g_free(hd->key);
340         g_free(hd);
341 }
342
343 static HistoryData *history_list_find_by_key(const gchar *key)
344 {
345         GList *work = history_list;
346
347         if (!key) return NULL;
348
349         while (work)
350                 {
351                 HistoryData *hd = work->data;
352                 if (strcmp(hd->key, key) == 0) return hd;
353                 work = work->next;
354                 }
355         return NULL;
356 }
357
358 const gchar *history_list_find_last_path_by_key(const gchar *key)
359 {
360         HistoryData *hd;
361
362         hd = history_list_find_by_key(key);
363         if (!hd || !hd->list) return NULL;
364
365         return hd->list->data;
366 }
367
368 void history_list_free_key(const gchar *key)
369 {
370         HistoryData *hd;
371         hd = history_list_find_by_key(key);
372         if (!hd) return;
373
374         history_list = g_list_remove(history_list, hd);
375         history_list_free(hd);
376 }
377
378 void history_list_add_to_key(const gchar *key, const gchar *path, gint max)
379 {
380         HistoryData *hd;
381         GList *work;
382
383         if (!key || !path) return;
384
385         hd = history_list_find_by_key(key);
386         if (!hd)
387                 {
388                 hd = g_new(HistoryData, 1);
389                 hd->key = g_strdup(key);
390                 hd->list = NULL;
391                 history_list = g_list_prepend(history_list, hd);
392                 }
393
394         /* if already in the list, simply move it to the top */
395         work = hd->list;
396         while (work)
397                 {
398                 gchar *buf = work->data;
399
400                 if (strcmp(buf, path) == 0)
401                         {
402                         /* if not first, move it */
403                         if (work != hd->list)
404                                 {
405                                 hd->list = g_list_remove(hd->list, buf);
406                                 hd->list = g_list_prepend(hd->list, buf);
407                                 }
408                         return;
409                         }
410                 work = work->next;
411                 }
412
413         hd->list = g_list_prepend(hd->list, g_strdup(path));
414
415         if (max == -1) max = HISTORY_DEFAULT_KEY_COUNT;
416         if (max > 0)
417                 {
418                 gint len = 0;
419                 GList *work = hd->list;
420                 GList *last = NULL;
421
422                 while (work)
423                         {
424                         len++;
425                         last = work;
426                         work = work->next;
427                         }
428
429                 work = last;
430                 while (work && len > max)
431                         {
432                         GList *node = work;
433                         work = work->prev;
434
435                         g_free(node->data);
436                         hd->list = g_list_delete_link(hd->list, node);
437                         len--;
438                         }
439                 }
440 }
441
442 void history_list_item_change(const gchar *key, const gchar *oldpath, const gchar *newpath)
443 {
444         HistoryData *hd;
445         GList *work;
446
447         if (!oldpath) return;
448         hd = history_list_find_by_key(key);
449         if (!hd) return;
450
451         work = hd->list;
452         while (work)
453                 {
454                 gchar *buf = work->data;
455                 if (strcmp(buf, oldpath) == 0)
456                         {
457                         if (newpath)
458                                 {
459                                 work->data = g_strdup(newpath);
460                                 }
461                         else
462                                 {
463                                 hd->list = g_list_remove(hd->list, buf);
464                                 }
465                         g_free(buf);
466                         return;
467                         }
468                 work = work->next;
469                 }
470 }
471
472 void history_list_item_move(const gchar *key, const gchar *path, gint direction)
473 {
474         HistoryData *hd;
475         GList *work;
476         gint p = 0;
477
478         if (!path) return;
479         hd = history_list_find_by_key(key);
480         if (!hd) return;
481
482         work = hd->list;
483         while (work)
484                 {
485                 gchar *buf = work->data;
486                 if (strcmp(buf, path) == 0)
487                         {
488                         p += direction;
489                         if (p < 0) return;
490                         hd->list = g_list_remove(hd->list, buf);
491                         hd->list = g_list_insert(hd->list, buf, p);
492                         return;
493                         }
494                 work = work->next;
495                 p++;
496                 }
497 }
498
499 void history_list_item_remove(const gchar *key, const gchar *path)
500 {
501         history_list_item_change(key, path, NULL);
502 }
503
504 GList *history_list_get_by_key(const gchar *key)
505 {
506         HistoryData *hd;
507
508         hd = history_list_find_by_key(key);
509         if (!hd) return NULL;
510
511         return hd->list;
512 }
513 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */