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