clang-tidy: misc-redundant-expression
[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 "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 = nullptr;
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 static_cast<const gchar *>(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 static_cast<const gchar *>(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(static_cast<const gchar *>(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 = nullptr;
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 static_cast<const gchar *>(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 static_cast<const gchar *>(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(static_cast<const gchar *>(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 struct HistoryData
188 {
189         gchar *key;
190         GList *list;
191 };
192
193 static GList *history_list = nullptr;
194
195
196 static gchar *quoted_from_text(const gchar *text)
197 {
198         const gchar *ptr;
199         gint c = 0;
200         gint l = strlen(text);
201
202         if (l == 0) return nullptr;
203
204         while (c < l && text[c] !='"') c++;
205         if (text[c] == '"')
206                 {
207                 gint e;
208                 c++;
209                 ptr = text + c;
210                 e = c;
211                 while (e < l && text[e] !='"') e++;
212                 if (text[e] == '"')
213                         {
214                         if (e - c > 0)
215                                 {
216                                 return g_strndup(ptr, e - c);
217                                 }
218                         }
219                 }
220         return nullptr;
221 }
222
223 gboolean history_list_load(const gchar *path)
224 {
225         FILE *f;
226         gchar *key = nullptr;
227         gchar s_buf[1024];
228         gchar *pathl;
229
230         pathl = path_from_utf8(path);
231         f = fopen(pathl, "r");
232         g_free(pathl);
233         if (!f) return FALSE;
234
235         /* first line must start with History comment */
236         if (!fgets(s_buf, sizeof(s_buf), f) ||
237             strncmp(s_buf, "#History", 8) != 0)
238                 {
239                 fclose(f);
240                 return FALSE;
241                 }
242
243         while (fgets(s_buf, sizeof(s_buf), f))
244                 {
245                 if (s_buf[0]=='#') continue;
246                 if (s_buf[0]=='[')
247                         {
248                         gint c;
249                         gchar *ptr;
250
251                         ptr = s_buf + 1;
252                         c = 0;
253                         while (ptr[c] != ']' && ptr[c] != '\n' && ptr[c] != '\0') c++;
254
255                         g_free(key);
256                         key = g_strndup(ptr, c);
257                         }
258                 else
259                         {
260                         gchar *value;
261
262                         value = quoted_from_text(s_buf);
263                         if (value && key)
264                                 {
265                                 history_list_add_to_key(key, value, 0);
266                                 }
267                         g_free(value);
268                         }
269                 }
270
271         fclose(f);
272
273         g_free(key);
274
275         return TRUE;
276 }
277
278 gboolean history_list_save(const gchar *path)
279 {
280         SecureSaveInfo *ssi;
281         GList *list;
282         gchar *pathl;
283         gint list_count;
284
285         pathl = path_from_utf8(path);
286         ssi = secure_open(pathl);
287         g_free(pathl);
288         if (!ssi)
289                 {
290                 log_printf(_("Unable to write history lists to: %s\n"), path);
291                 return FALSE;
292                 }
293
294         secure_fprintf(ssi, "#History lists\n\n");
295
296         list = g_list_last(history_list);
297         while (list && secsave_errno == SS_ERR_NONE)
298                 {
299                 HistoryData *hd;
300                 GList *work;
301
302                 hd = static_cast<HistoryData *>(list->data);
303                 list = list->prev;
304
305                 secure_fprintf(ssi, "[%s]\n", hd->key);
306
307                 /* save them inverted (oldest to newest)
308                  * so that when reading they are added correctly
309                  */
310                 work = g_list_last(hd->list);
311                 list_count = g_list_position(hd->list, g_list_last(hd->list)) + 1;
312                 while (work && secsave_errno == SS_ERR_NONE)
313                         {
314                         if ((strcmp(hd->key, "path_list") != 0 || list_count <= options->open_recent_list_maxsize)
315                                         &&
316                                         (strcmp(hd->key, "recent") != 0 || !(!isfile(static_cast<const gchar *>(work->data))))
317                                         &&
318                                         (strcmp(hd->key, "image_list") != 0 || list_count <= options->recent_folder_image_list_maxsize))
319                                 {
320                                 secure_fprintf(ssi, "\"%s\"\n", static_cast<gchar *>(work->data));
321                                 }
322                         work = work->prev;
323                         list_count--;
324                         }
325                 secure_fputc(ssi, '\n');
326                 }
327
328         secure_fprintf(ssi, "#end\n");
329
330         return (secure_close(ssi) == 0);
331 }
332
333 static void history_list_free(HistoryData *hd)
334 {
335         GList *work;
336
337         if (!hd) return;
338
339         work = hd->list;
340         while (work)
341                 {
342                 g_free(work->data);
343                 work = work->next;
344                 }
345
346         g_free(hd->key);
347         g_free(hd);
348 }
349
350 static HistoryData *history_list_find_by_key(const gchar *key)
351 {
352         GList *work = history_list;
353
354         if (!key) return nullptr;
355
356         while (work)
357                 {
358                 auto hd = static_cast<HistoryData *>(work->data);
359                 if (strcmp(hd->key, key) == 0) return hd;
360                 work = work->next;
361                 }
362         return nullptr;
363 }
364
365 const gchar *history_list_find_last_path_by_key(const gchar *key)
366 {
367         HistoryData *hd;
368
369         hd = history_list_find_by_key(key);
370         if (!hd || !hd->list) return nullptr;
371
372         return static_cast<const gchar *>(hd->list->data);
373 }
374
375 void history_list_free_key(const gchar *key)
376 {
377         HistoryData *hd;
378         hd = history_list_find_by_key(key);
379         if (!hd) return;
380
381         history_list = g_list_remove(history_list, hd);
382         history_list_free(hd);
383 }
384
385 void history_list_add_to_key(const gchar *key, const gchar *path, gint max)
386 {
387         HistoryData *hd;
388         GList *work;
389
390         if (!key || !path) return;
391
392         hd = history_list_find_by_key(key);
393         if (!hd)
394                 {
395                 hd = g_new(HistoryData, 1);
396                 hd->key = g_strdup(key);
397                 hd->list = nullptr;
398                 history_list = g_list_prepend(history_list, hd);
399                 }
400
401         /* if already in the list, simply move it to the top */
402         work = hd->list;
403         while (work)
404                 {
405                 auto buf = static_cast<gchar *>(work->data);
406
407                 if (strcmp(buf, path) == 0)
408                         {
409                         /* if not first, move it */
410                         if (work != hd->list)
411                                 {
412                                 hd->list = g_list_remove(hd->list, buf);
413                                 hd->list = g_list_prepend(hd->list, buf);
414                                 }
415                         return;
416                         }
417                 work = work->next;
418                 }
419
420         hd->list = g_list_prepend(hd->list, g_strdup(path));
421
422         if (max == -1) max = options->open_recent_list_maxsize;
423         if (max > 0)
424                 {
425                 gint len = 0;
426                 GList *work = hd->list;
427                 GList *last = nullptr;
428
429                 while (work)
430                         {
431                         len++;
432                         last = work;
433                         work = work->next;
434                         }
435
436                 work = last;
437                 while (work && len > max)
438                         {
439                         GList *node = work;
440                         work = work->prev;
441
442                         g_free(node->data);
443                         hd->list = g_list_delete_link(hd->list, node);
444                         len--;
445                         }
446                 }
447 }
448
449 void history_list_item_change(const gchar *key, const gchar *oldpath, const gchar *newpath)
450 {
451         HistoryData *hd;
452         GList *work;
453
454         if (!oldpath) return;
455         hd = history_list_find_by_key(key);
456         if (!hd) return;
457
458         work = hd->list;
459         while (work)
460                 {
461                 auto buf = static_cast<gchar *>(work->data);
462
463                 if (!(g_str_has_prefix(buf, ".") && !newpath))
464                         {
465                         if (strcmp(buf, oldpath) == 0)
466                                 {
467                                 if (newpath)
468                                         {
469                                         work->data = g_strdup(newpath);
470                                         }
471                                 else
472                                         {
473                                         hd->list = g_list_remove(hd->list, buf);
474                                         }
475                                 g_free(buf);
476                                 return;
477                                 }
478                         }
479                 else
480                         {
481                         hd->list = g_list_remove(hd->list, buf);
482                         g_free(buf);
483                         return;
484                         }
485                 work = work->next;
486                 }
487 }
488
489 void history_list_item_move(const gchar *key, const gchar *path, gint direction)
490 {
491         HistoryData *hd;
492         GList *work;
493         gint p = 0;
494
495         if (!path) return;
496         hd = history_list_find_by_key(key);
497         if (!hd) return;
498
499         work = hd->list;
500         while (work)
501                 {
502                 auto buf = static_cast<gchar *>(work->data);
503                 if (strcmp(buf, path) == 0)
504                         {
505                         p += direction;
506                         if (p < 0) return;
507                         hd->list = g_list_remove(hd->list, buf);
508                         hd->list = g_list_insert(hd->list, buf, p);
509                         return;
510                         }
511                 work = work->next;
512                 p++;
513                 }
514 }
515
516 void history_list_item_remove(const gchar *key, const gchar *path)
517 {
518         history_list_item_change(key, path, nullptr);
519 }
520
521 GList *history_list_get_by_key(const gchar *key)
522 {
523         HistoryData *hd;
524
525         hd = history_list_find_by_key(key);
526         if (!hd) return nullptr;
527
528         return hd->list;
529 }
530
531 /**
532  * @brief Get image last viewed in a folder
533  * @param path Must be a folder
534  * @returns Last image viewed in folder or NULL
535  *
536  * Returned string should be freed
537  */
538 gchar *get_recent_viewed_folder_image(gchar *path)
539 {
540         HistoryData *hd;
541         GList *work;
542         gchar *dirname;
543         gchar *ret = nullptr;
544
545         if (options->recent_folder_image_list_maxsize == 0)
546                 {
547                 return nullptr;
548                 }
549
550         hd = history_list_find_by_key("image_list");
551
552         if (!hd)
553                 {
554                 hd = g_new(HistoryData, 1);
555                 hd->key = g_strdup("image_list");
556                 hd->list = nullptr;
557                 history_list = g_list_prepend(history_list, hd);
558                 }
559
560         work = hd->list;
561
562         while (work)
563                 {
564                 dirname = g_path_get_dirname(static_cast<const gchar *>(work->data));
565
566                 if (g_strcmp0(dirname, path) == 0)
567                         {
568                         if (isfile(static_cast<const gchar *>(work->data)))
569                                 {
570                                 ret = g_strdup(static_cast<const gchar *>(work->data));
571                                 }
572                         g_free(dirname);
573                         break;
574                         }
575
576                 g_free(dirname);
577                 work = work->next;
578                 }
579
580         return ret;
581 }
582
583 static void update_recent_viewed_folder_image_list(const gchar *path)
584 {
585         HistoryData *hd;
586         GList *work;
587         gchar *image_dir = nullptr;
588         gchar *list_dir = nullptr;
589         gboolean found = FALSE;
590
591         if (options->recent_folder_image_list_maxsize == 0)
592                 {
593                 return;
594                 }
595
596         image_dir = g_path_get_dirname(path);
597         hd = history_list_find_by_key("image_list");
598         if (!hd)
599                 {
600                 hd = g_new(HistoryData, 1);
601                 hd->key = g_strdup("image_list");
602                 hd->list = nullptr;
603                 history_list = g_list_prepend(history_list, hd);
604                 }
605
606         work = hd->list;
607
608         while (work)
609                 {
610                 list_dir = g_path_get_dirname(static_cast<const gchar *>(work->data));
611
612                 /* If folder already in list, update and move to start of list */
613                 if (g_strcmp0(list_dir, image_dir) == 0)
614                         {
615                         g_free(work->data);
616                         work->data = g_strdup(path);
617                         hd->list = g_list_remove_link(hd->list, work);
618                         hd->list = g_list_concat(work, hd->list);
619                         found = TRUE;
620                         g_free(list_dir);
621                         break;
622                         }
623                 g_free(list_dir);
624                 work = work->next;
625                 }
626
627         g_free(image_dir);
628
629         if (!found)
630                 {
631                 hd->list = g_list_prepend(hd->list, g_strdup(path));
632                 }
633 }
634 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */