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