Simplify vflist_get_formatted()
[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 #define HISTORY_DEFAULT_KEY_COUNT 16
183
184
185 typedef struct _HistoryData HistoryData;
186 struct _HistoryData
187 {
188         gchar *key;
189         GList *list;
190 };
191
192 static GList *history_list = NULL;
193
194
195 static gchar *quoted_from_text(const gchar *text)
196 {
197         const gchar *ptr;
198         gint c = 0;
199         gint l = strlen(text);
200
201         if (l == 0) return NULL;
202
203         while (c < l && text[c] !='"') c++;
204         if (text[c] == '"')
205                 {
206                 gint e;
207                 c++;
208                 ptr = text + c;
209                 e = c;
210                 while (e < l && text[e] !='"') e++;
211                 if (text[e] == '"')
212                         {
213                         if (e - c > 0)
214                                 {
215                                 return g_strndup(ptr, e - c);
216                                 }
217                         }
218                 }
219         return NULL;
220 }
221
222 gboolean history_list_load(const gchar *path)
223 {
224         FILE *f;
225         gchar *key = NULL;
226         gchar s_buf[1024];
227         gchar *pathl;
228
229         pathl = path_from_utf8(path);
230         f = fopen(pathl, "r");
231         g_free(pathl);
232         if (!f) return FALSE;
233
234         /* first line must start with History comment */
235         if (!fgets(s_buf, sizeof(s_buf), f) ||
236             strncmp(s_buf, "#History", 8) != 0)
237                 {
238                 fclose(f);
239                 return FALSE;
240                 }
241
242         while (fgets(s_buf, sizeof(s_buf), f))
243                 {
244                 if (s_buf[0]=='#') continue;
245                 if (s_buf[0]=='[')
246                         {
247                         gint c;
248                         gchar *ptr;
249
250                         ptr = s_buf + 1;
251                         c = 0;
252                         while (ptr[c] != ']' && ptr[c] != '\n' && ptr[c] != '\0') c++;
253
254                         g_free(key);
255                         key = g_strndup(ptr, c);
256                         }
257                 else
258                         {
259                         gchar *value;
260
261                         value = quoted_from_text(s_buf);
262                         if (value && key)
263                                 {
264                                 history_list_add_to_key(key, value, 0);
265                                 }
266                         g_free(value);
267                         }
268                 }
269
270         fclose(f);
271
272         g_free(key);
273
274         return TRUE;
275 }
276
277 gboolean history_list_save(const gchar *path)
278 {
279         SecureSaveInfo *ssi;
280         GList *list;
281         gchar *pathl;
282         gint list_count;
283
284         pathl = path_from_utf8(path);
285         ssi = secure_open(pathl);
286         g_free(pathl);
287         if (!ssi)
288                 {
289                 log_printf(_("Unable to write history lists to: %s\n"), path);
290                 return FALSE;
291                 }
292
293         secure_fprintf(ssi, "#History lists\n\n");
294
295         list = g_list_last(history_list);
296         while (list && secsave_errno == SS_ERR_NONE)
297                 {
298                 HistoryData *hd;
299                 GList *work;
300
301                 hd = list->data;
302                 list = list->prev;
303
304                 secure_fprintf(ssi, "[%s]\n", hd->key);
305
306                 /* save them inverted (oldest to newest)
307                  * so that when reading they are added correctly
308                  */
309                 work = g_list_last(hd->list);
310                 list_count = g_list_position(hd->list, g_list_last(hd->list)) + 1;
311                 while (work && secsave_errno == SS_ERR_NONE)
312                         {
313                         if ((!(strcmp(hd->key, "path_list") == 0 && list_count > options->open_recent_list_maxsize)) && (!(strcmp(hd->key, "recent") == 0 && (!isfile(work->data)))))
314                                 {
315                                 secure_fprintf(ssi, "\"%s\"\n", (gchar *)work->data);
316                                 }
317                         work = work->prev;
318                         list_count--;
319                         }
320                 secure_fputc(ssi, '\n');
321                 }
322
323         secure_fprintf(ssi, "#end\n");
324
325         return (secure_close(ssi) == 0);
326 }
327
328 static void history_list_free(HistoryData *hd)
329 {
330         GList *work;
331
332         if (!hd) return;
333
334         work = hd->list;
335         while (work)
336                 {
337                 g_free(work->data);
338                 work = work->next;
339                 }
340
341         g_free(hd->key);
342         g_free(hd);
343 }
344
345 static HistoryData *history_list_find_by_key(const gchar *key)
346 {
347         GList *work = history_list;
348
349         if (!key) return NULL;
350
351         while (work)
352                 {
353                 HistoryData *hd = work->data;
354                 if (strcmp(hd->key, key) == 0) return hd;
355                 work = work->next;
356                 }
357         return NULL;
358 }
359
360 const gchar *history_list_find_last_path_by_key(const gchar *key)
361 {
362         HistoryData *hd;
363
364         hd = history_list_find_by_key(key);
365         if (!hd || !hd->list) return NULL;
366
367         return hd->list->data;
368 }
369
370 void history_list_free_key(const gchar *key)
371 {
372         HistoryData *hd;
373         hd = history_list_find_by_key(key);
374         if (!hd) return;
375
376         history_list = g_list_remove(history_list, hd);
377         history_list_free(hd);
378 }
379
380 void history_list_add_to_key(const gchar *key, const gchar *path, gint max)
381 {
382         HistoryData *hd;
383         GList *work;
384
385         if (!key || !path) return;
386
387         hd = history_list_find_by_key(key);
388         if (!hd)
389                 {
390                 hd = g_new(HistoryData, 1);
391                 hd->key = g_strdup(key);
392                 hd->list = NULL;
393                 history_list = g_list_prepend(history_list, hd);
394                 }
395
396         /* if already in the list, simply move it to the top */
397         work = hd->list;
398         while (work)
399                 {
400                 gchar *buf = work->data;
401
402                 if (strcmp(buf, path) == 0)
403                         {
404                         /* if not first, move it */
405                         if (work != hd->list)
406                                 {
407                                 hd->list = g_list_remove(hd->list, buf);
408                                 hd->list = g_list_prepend(hd->list, buf);
409                                 }
410                         return;
411                         }
412                 work = work->next;
413                 }
414
415         hd->list = g_list_prepend(hd->list, g_strdup(path));
416
417         if (max == -1) max = HISTORY_DEFAULT_KEY_COUNT;
418         if (max > 0)
419                 {
420                 gint len = 0;
421                 GList *work = hd->list;
422                 GList *last = NULL;
423
424                 while (work)
425                         {
426                         len++;
427                         last = work;
428                         work = work->next;
429                         }
430
431                 work = last;
432                 while (work && len > max)
433                         {
434                         GList *node = work;
435                         work = work->prev;
436
437                         g_free(node->data);
438                         hd->list = g_list_delete_link(hd->list, node);
439                         len--;
440                         }
441                 }
442 }
443
444 void history_list_item_change(const gchar *key, const gchar *oldpath, const gchar *newpath)
445 {
446         HistoryData *hd;
447         GList *work;
448
449         if (!oldpath) return;
450         hd = history_list_find_by_key(key);
451         if (!hd) return;
452
453         work = hd->list;
454         while (work)
455                 {
456                 gchar *buf = work->data;
457                 if (strcmp(buf, oldpath) == 0)
458                         {
459                         if (newpath)
460                                 {
461                                 work->data = g_strdup(newpath);
462                                 }
463                         else
464                                 {
465                                 hd->list = g_list_remove(hd->list, buf);
466                                 }
467                         g_free(buf);
468                         return;
469                         }
470                 work = work->next;
471                 }
472 }
473
474 void history_list_item_move(const gchar *key, const gchar *path, gint direction)
475 {
476         HistoryData *hd;
477         GList *work;
478         gint p = 0;
479
480         if (!path) return;
481         hd = history_list_find_by_key(key);
482         if (!hd) return;
483
484         work = hd->list;
485         while (work)
486                 {
487                 gchar *buf = work->data;
488                 if (strcmp(buf, path) == 0)
489                         {
490                         p += direction;
491                         if (p < 0) return;
492                         hd->list = g_list_remove(hd->list, buf);
493                         hd->list = g_list_insert(hd->list, buf, p);
494                         return;
495                         }
496                 work = work->next;
497                 p++;
498                 }
499 }
500
501 void history_list_item_remove(const gchar *key, const gchar *path)
502 {
503         history_list_item_change(key, path, NULL);
504 }
505
506 GList *history_list_get_by_key(const gchar *key)
507 {
508         HistoryData *hd;
509
510         hd = history_list_find_by_key(key);
511         if (!hd) return NULL;
512
513         return hd->list;
514 }
515 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */