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