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