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