histogram caching
[geeqie.git] / src / main.c
1 /*
2  * Geeqie
3  * (C) 2006 John Ellis
4  * Copyright (C) 2008 - 2009 The Geeqie Team
5  *
6  * Author: John Ellis
7  *
8  * This software is released under the GNU General Public License (GNU GPL).
9  * Please read the included file COPYING for more information.
10  * This software comes with no warranty of any kind, use at your own risk!
11  */
12
13
14 #include "main.h"
15
16 #include "cache.h"
17 #include "collect.h"
18 #include "collect-io.h"
19 #include "filedata.h"
20 #include "filefilter.h"
21 #include "history_list.h"
22 #include "image-overlay.h"
23 #include "layout.h"
24 #include "layout_image.h"
25 #include "options.h"
26 #include "remote.h"
27 #include "secure_save.h"
28 #include "similar.h"
29 #include "ui_fileops.h"
30 #include "ui_utildlg.h"
31 #include "cache_maint.h"
32 #include "thumb.h"
33 #include "metadata.h"
34 #include "editors.h"
35 #include "exif.h"
36 #include "histogram.h"
37
38 #include <gdk/gdkkeysyms.h> /* for keyboard values */
39
40 #include <signal.h>
41 #include <sys/mman.h>
42
43 #include <math.h>
44 #ifdef G_OS_UNIX
45 #include <pwd.h>
46 #endif
47
48
49 static RemoteConnection *remote_connection = NULL;
50
51
52 /*
53  *-----------------------------------------------------------------------------
54  * keyboard functions
55  *-----------------------------------------------------------------------------
56  */
57
58 void keyboard_scroll_calc(gint *x, gint *y, GdkEventKey *event)
59 {
60         static gint delta = 0;
61         static guint32 time_old = 0;
62         static guint keyval_old = 0;
63
64         if (event->state & GDK_CONTROL_MASK)
65                 {
66                 if (*x < 0) *x = G_MININT / 2;
67                 if (*x > 0) *x = G_MAXINT / 2;
68                 if (*y < 0) *y = G_MININT / 2;
69                 if (*y > 0) *y = G_MAXINT / 2;
70
71                 return;
72                 }
73
74         if (options->progressive_key_scrolling)
75                 {
76                 guint32 time_diff;
77
78                 time_diff = event->time - time_old;
79
80                 /* key pressed within 125ms ? (1/8 second) */
81                 if (time_diff > 125 || event->keyval != keyval_old) delta = 0;
82
83                 time_old = event->time;
84                 keyval_old = event->keyval;
85
86                 delta += 2;
87                 }
88         else
89                 {
90                 delta = 8;
91                 }
92
93         *x = *x * delta;
94         *y = *y * delta;
95 }
96
97
98
99 /*
100  *-----------------------------------------------------------------------------
101  * command line parser (private) hehe, who needs popt anyway?
102  *-----------------------------------------------------------------------------
103  */
104
105 static gint startup_blank = FALSE;
106 static gint startup_full_screen = FALSE;
107 static gint startup_in_slideshow = FALSE;
108 static gint startup_command_line_collection = FALSE;
109
110
111 static void parse_command_line_add_file(const gchar *file_path, gchar **path, gchar **file,
112                                         GList **list, GList **collection_list)
113 {
114         gchar *path_parsed;
115
116         path_parsed = g_strdup(file_path);
117         parse_out_relatives(path_parsed);
118
119         if (file_extension_match(path_parsed, GQ_COLLECTION_EXT))
120                 {
121                 *collection_list = g_list_append(*collection_list, path_parsed);
122                 }
123         else
124                 {
125                 if (!*path) *path = remove_level_from_path(path_parsed);
126                 if (!*file) *file = g_strdup(path_parsed);
127                 *list = g_list_prepend(*list, file_data_new_simple(path_parsed));
128                 }
129 }
130
131 static void parse_command_line_add_dir(const gchar *dir, gchar **path, gchar **file,
132                                        GList **list)
133 {
134         GList *files;
135         gchar *path_parsed;
136         FileData *dir_fd;
137
138         path_parsed = g_strdup(dir);
139         parse_out_relatives(path_parsed);
140         dir_fd = file_data_new_simple(path_parsed);
141         
142
143         if (filelist_read(dir_fd, &files, NULL))
144                 {
145                 GList *work;
146
147                 files = filelist_filter(files, FALSE);
148                 files = filelist_sort_path(files);
149
150                 work = files;
151                 while (work)
152                         {
153                         FileData *fd = work->data;
154                         if (!*path) *path = remove_level_from_path(fd->path);
155                         if (!*file) *file = g_strdup(fd->path);
156                         *list = g_list_prepend(*list, fd);
157
158                         work = work->next;
159                         }
160
161                 g_list_free(files);
162                 }
163
164         g_free(path_parsed);
165         file_data_unref(dir_fd);
166 }
167
168 static void parse_command_line_process_dir(const gchar *dir, gchar **path, gchar **file,
169                                            GList **list, gchar **first_dir)
170 {
171
172         if (!*list && !*first_dir)
173                 {
174                 *first_dir = g_strdup(dir);
175                 }
176         else
177                 {
178                 if (*first_dir)
179                         {
180                         parse_command_line_add_dir(*first_dir, path, file, list);
181                         g_free(*first_dir);
182                         *first_dir = NULL;
183                         }
184                 parse_command_line_add_dir(dir, path, file, list);
185                 }
186 }
187
188 static void parse_command_line_process_file(const gchar *file_path, gchar **path, gchar **file,
189                                             GList **list, GList **collection_list, gchar **first_dir)
190 {
191
192         if (*first_dir)
193                 {
194                 parse_command_line_add_dir(*first_dir, path, file, list);
195                 g_free(*first_dir);
196                 *first_dir = NULL;
197                 }
198         parse_command_line_add_file(file_path, path, file, list, collection_list);
199 }
200
201 static void parse_command_line(gint argc, gchar *argv[], gchar **path, gchar **file,
202                                GList **cmd_list, GList **collection_list,
203                                gchar **geometry)
204 {
205         GList *list = NULL;
206         GList *remote_list = NULL;
207         GList *remote_errors = NULL;
208         gint remote_do = FALSE;
209         gchar *first_dir = NULL;
210
211         if (argc > 1)
212                 {
213                 gint i;
214                 gchar *base_dir = get_current_dir();
215                 i = 1;
216                 while (i < argc)
217                         {
218                         const gchar *cmd_line = argv[i];
219                         gchar *cmd_all = g_build_filename(base_dir, cmd_line, NULL);
220
221                         if (cmd_line[0] == G_DIR_SEPARATOR && isdir(cmd_line))
222                                 {
223                                 parse_command_line_process_dir(cmd_line, path, file, &list, &first_dir);
224                                 }
225                         else if (isdir(cmd_all))
226                                 {
227                                 parse_command_line_process_dir(cmd_all, path, file, &list, &first_dir);
228                                 }
229                         else if (cmd_line[0] == G_DIR_SEPARATOR && isfile(cmd_line))
230                                 {
231                                 parse_command_line_process_file(cmd_line, path, file,
232                                                                 &list, collection_list, &first_dir);
233                                 }
234                         else if (isfile(cmd_all))
235                                 {
236                                 parse_command_line_process_file(cmd_all, path, file,
237                                                                 &list, collection_list, &first_dir);
238                                 }
239                         else if (strncmp(cmd_line, "--debug", 7) == 0 && (cmd_line[7] == '\0' || cmd_line[7] == '='))
240                                 {
241                                 /* do nothing but do not produce warnings */
242                                 }
243                         else if (strcmp(cmd_line, "+t") == 0 ||
244                                  strcmp(cmd_line, "--with-tools") == 0)
245                                 {
246                                 options->layout.tools_float = FALSE;
247                                 options->layout.tools_hidden = FALSE;
248
249                                 remote_list = g_list_append(remote_list, "+t");
250                                 }
251                         else if (strcmp(cmd_line, "-t") == 0 ||
252                                  strcmp(cmd_line, "--without-tools") == 0)
253                                 {
254                                 options->layout.tools_hidden = TRUE;
255
256                                 remote_list = g_list_append(remote_list, "-t");
257                                 }
258                         else if (strcmp(cmd_line, "-f") == 0 ||
259                                  strcmp(cmd_line, "--fullscreen") == 0)
260                                 {
261                                 startup_full_screen = TRUE;
262                                 }
263                         else if (strcmp(cmd_line, "-s") == 0 ||
264                                  strcmp(cmd_line, "--slideshow") == 0)
265                                 {
266                                 startup_in_slideshow = TRUE;
267                                 }
268                         else if (strcmp(cmd_line, "-l") == 0 ||
269                                  strcmp(cmd_line, "--list") == 0)
270                                 {
271                                 startup_command_line_collection = TRUE;
272                                 }
273                         else if (strncmp(cmd_line, "--geometry=", 11) == 0)
274                                 {
275                                 if (!*geometry) *geometry = g_strdup(cmd_line + 11);
276                                 }
277                         else if (strcmp(cmd_line, "-r") == 0 ||
278                                  strcmp(cmd_line, "--remote") == 0)
279                                 {
280                                 if (!remote_do)
281                                         {
282                                         remote_do = TRUE;
283                                         remote_list = remote_build_list(remote_list, argc - i, &argv[i], &remote_errors);
284                                         }
285                                 }
286                         else if (strcmp(cmd_line, "-rh") == 0 ||
287                                  strcmp(cmd_line, "--remote-help") == 0)
288                                 {
289                                 remote_help();
290                                 exit(0);
291                                 }
292                         else if (strcmp(cmd_line, "--blank") == 0)
293                                 {
294                                 startup_blank = TRUE;
295                                 }
296                         else if (strcmp(cmd_line, "-v") == 0 ||
297                                  strcmp(cmd_line, "--version") == 0)
298                                 {
299                                 printf_term("%s %s\n", GQ_APPNAME, VERSION);
300                                 exit(0);
301                                 }
302                         else if (strcmp(cmd_line, "--alternate") == 0)
303                                 {
304                                 /* enable faster experimental algorithm */
305                                 log_printf("Alternate similarity algorithm enabled\n");
306                                 image_sim_alternate_set(TRUE);
307                                 }
308                         else if (strcmp(cmd_line, "-h") == 0 ||
309                                  strcmp(cmd_line, "--help") == 0)
310                                 {
311                                 printf_term("%s %s\n", GQ_APPNAME, VERSION);
312                                 printf_term(_("Usage: %s [options] [path]\n\n"), GQ_APPNAME_LC);
313                                 print_term(_("valid options are:\n"));
314                                 print_term(_("  +t, --with-tools           force show of tools\n"));
315                                 print_term(_("  -t, --without-tools        force hide of tools\n"));
316                                 print_term(_("  -f, --fullscreen           start in full screen mode\n"));
317                                 print_term(_("  -s, --slideshow            start in slideshow mode\n"));
318                                 print_term(_("  -l, --list                 open collection window for command line\n"));
319                                 print_term(_("      --geometry=GEOMETRY    set main window location\n"));
320                                 print_term(_("  -r, --remote               send following commands to open window\n"));
321                                 print_term(_("  -rh,--remote-help          print remote command list\n"));
322 #ifdef DEBUG
323                                 print_term(_("  --debug[=level]            turn on debug output\n"));
324 #endif
325                                 print_term(_("  -v, --version              print version info\n"));
326                                 print_term(_("  -h, --help                 show this message\n\n"));
327
328 #if 0
329                                 /* these options are not officially supported!
330                                  * only for testing new features, no need to translate them */
331                                 print_term(  "  --alternate                use alternate similarity algorithm\n");
332 #endif
333
334                                 exit(0);
335                                 }
336                         else if (!remote_do)
337                                 {
338                                 printf_term(_("invalid or ignored: %s\nUse --help for options\n"), cmd_line);
339                                 }
340
341                         g_free(cmd_all);
342                         i++;
343                         }
344                 g_free(base_dir);
345                 parse_out_relatives(*path);
346                 parse_out_relatives(*file);
347                 }
348
349         list = g_list_reverse(list);
350
351         if (!*path && first_dir)
352                 {
353                 *path = first_dir;
354                 first_dir = NULL;
355
356                 parse_out_relatives(*path);
357                 }
358         g_free(first_dir);
359
360         if (remote_do)
361                 {
362                 if (remote_errors)
363                         {
364                         GList *work = remote_errors;
365                         
366                         printf_term(_("Invalid or ignored remote options: "));
367                         while (work)
368                                 {
369                                 gchar *opt = work->data;
370                                                 
371                                 printf_term("%s%s", (work == remote_errors) ? "" : ", ", opt);
372                                 work = work->next;
373                                 }
374
375                         printf_term(_("\nUse --remote-help for valid remote options.\n"));
376                         }
377
378                 remote_control(argv[0], remote_list, *path, list, *collection_list);
379                 }
380         g_list_free(remote_list);
381
382         if (list && list->next)
383                 {
384                 *cmd_list = list;
385                 }
386         else
387                 {
388                 filelist_free(list);
389                 *cmd_list = NULL;
390                 }
391 }
392
393 static void parse_command_line_for_debug_option(gint argc, gchar *argv[])
394 {
395 #ifdef DEBUG
396         const gchar *debug_option = "--debug";
397         gint len = strlen(debug_option);
398
399         if (argc > 1)
400                 {
401                 gint i;
402
403                 for (i = 1; i < argc; i++)
404                         {
405                         const gchar *cmd_line = argv[i];
406                         if (strncmp(cmd_line, debug_option, len) == 0)
407                                 {
408                                 gint cmd_line_len = strlen(cmd_line);
409
410                                 /* we now increment the debug state for verbosity */
411                                 if (cmd_line_len == len)
412                                         debug_level_add(1);
413                                 else if (cmd_line[len] == '=' && g_ascii_isdigit(cmd_line[len+1]))
414                                         {
415                                         gint n = atoi(cmd_line + len + 1);
416                                         if (n < 0) n = 1;
417                                         debug_level_add(n);
418                                         }
419                                 }
420                         }
421                 }
422
423         DEBUG_1("debugging output enabled (level %d)", get_debug_level());
424 #endif
425 }
426
427 /*
428  *-----------------------------------------------------------------------------
429  * startup, init, and exit
430  *-----------------------------------------------------------------------------
431  */
432
433 #define RC_HISTORY_NAME "history"
434
435 static void keys_load(void)
436 {
437         gchar *path;
438
439         path = g_build_filename(get_rc_dir(), RC_HISTORY_NAME, NULL);
440         history_list_load(path);
441         g_free(path);
442 }
443
444 static void keys_save(void)
445 {
446         gchar *path;
447
448         path = g_build_filename(get_rc_dir(), RC_HISTORY_NAME, NULL);
449         history_list_save(path);
450         g_free(path);
451 }
452
453 static void mkdir_if_not_exists(const gchar *path)
454 {
455         if (isdir(path)) return;
456
457         log_printf(_("Creating %s dir:%s\n"), GQ_APPNAME, path);
458
459         if (!recursive_mkdir_if_not_exists(path, 0755))
460                 {
461                 log_printf(_("Could not create dir:%s\n"), path);
462                 }
463 }
464
465
466 /* We add to duplicate and modify  gtk_accel_map_print() and gtk_accel_map_save()
467  * to improve the reliability in special cases (especially when disk is full)
468  * These functions are now using secure saving stuff.
469  */
470 static void gq_accel_map_print(
471                     gpointer    data,
472                     const gchar *accel_path,
473                     guint       accel_key,
474                     GdkModifierType accel_mods,
475                     gboolean    changed)
476 {
477         GString *gstring = g_string_new(changed ? NULL : "; ");
478         SecureSaveInfo *ssi = data;
479         gchar *tmp, *name;
480
481         g_string_append(gstring, "(gtk_accel_path \"");
482
483         tmp = g_strescape(accel_path, NULL);
484         g_string_append(gstring, tmp);
485         g_free(tmp);
486
487         g_string_append(gstring, "\" \"");
488
489         name = gtk_accelerator_name(accel_key, accel_mods);
490         tmp = g_strescape(name, NULL);
491         g_free(name);
492         g_string_append(gstring, tmp);
493         g_free(tmp);
494
495         g_string_append(gstring, "\")\n");
496
497         secure_fwrite(gstring->str, sizeof(*gstring->str), gstring->len, ssi);
498
499         g_string_free(gstring, TRUE);
500 }
501
502 static gboolean gq_accel_map_save(const gchar *path)
503 {
504         gchar *pathl;
505         SecureSaveInfo *ssi;
506         GString *gstring;
507
508         pathl = path_from_utf8(path);
509         ssi = secure_open(pathl);
510         g_free(pathl);
511         if (!ssi)
512                 {
513                 log_printf(_("error saving file: %s\n"), path);
514                 return FALSE;
515                 }
516         
517         gstring = g_string_new("; ");
518         if (g_get_prgname())
519                 g_string_append(gstring, g_get_prgname());
520         g_string_append(gstring, " GtkAccelMap rc-file         -*- scheme -*-\n");
521         g_string_append(gstring, "; this file is an automated accelerator map dump\n");
522         g_string_append(gstring, ";\n");
523
524         secure_fwrite(gstring->str, sizeof(*gstring->str), gstring->len, ssi);
525
526         g_string_free(gstring, TRUE);
527
528         gtk_accel_map_foreach((gpointer) ssi, gq_accel_map_print);
529
530         if (secure_close(ssi))
531                 {
532                 log_printf(_("error saving file: %s\nerror: %s\n"), path,
533                            secsave_strerror(secsave_errno));
534                 return FALSE;
535                 }
536
537         return TRUE;
538 }
539
540 static gchar *accep_map_filename(void)
541 {
542         return g_build_filename(get_rc_dir(), "accels", NULL);
543 }
544
545 static void accel_map_save(void)
546 {
547         gchar *path;
548
549         path = accep_map_filename();
550         gq_accel_map_save(path);
551         g_free(path);
552 }
553
554 static void accel_map_load(void)
555 {
556         gchar *path;
557         gchar *pathl;
558
559         path = accep_map_filename();
560         pathl = path_from_utf8(path);
561         gtk_accel_map_load(pathl);
562         g_free(pathl);
563         g_free(path);
564 }
565
566 static void gtkrc_load(void)
567 {
568         gchar *path;
569         gchar *pathl;
570
571         /* If a gtkrc file exists in the rc directory, add it to the
572          * list of files to be parsed at the end of gtk_init() */
573         path = g_build_filename(get_rc_dir(), "gtkrc", NULL);
574         pathl = path_from_utf8(path);
575         if (access(pathl, R_OK) == 0)
576                 gtk_rc_add_default_file(pathl);
577         g_free(pathl);
578         g_free(path);
579 }
580
581 static void exit_program_final(void)
582 {
583         LayoutWindow *lw = NULL;
584
585         remote_close(remote_connection);
586
587         collect_manager_flush();
588
589         save_options(options);
590         keys_save();
591         accel_map_save();
592
593         if (layout_valid(&lw))
594                 {
595                 layout_free(lw);
596                 }
597
598         gtk_main_quit();
599 }
600
601 static GenericDialog *exit_dialog = NULL;
602
603 static void exit_confirm_cancel_cb(GenericDialog *gd, gpointer data)
604 {
605         exit_dialog = NULL;
606         generic_dialog_close(gd);
607 }
608
609 static void exit_confirm_exit_cb(GenericDialog *gd, gpointer data)
610 {
611         exit_dialog = NULL;
612         generic_dialog_close(gd);
613         exit_program_final();
614 }
615
616 static gint exit_confirm_dlg(void)
617 {
618         GtkWidget *parent;
619         LayoutWindow *lw;
620         gchar *msg;
621
622         if (exit_dialog)
623                 {
624                 gtk_window_present(GTK_WINDOW(exit_dialog->dialog));
625                 return TRUE;
626                 }
627
628         if (!collection_window_modified_exists()) return FALSE;
629
630         parent = NULL;
631         lw = NULL;
632         if (layout_valid(&lw))
633                 {
634                 parent = lw->window;
635                 }
636
637         msg = g_strdup_printf("%s - %s", GQ_APPNAME, _("exit"));
638         exit_dialog = generic_dialog_new(msg,
639                                 "exit", parent, FALSE,
640                                 exit_confirm_cancel_cb, NULL);
641         g_free(msg);
642         msg = g_strdup_printf(_("Quit %s"), GQ_APPNAME);
643         generic_dialog_add_message(exit_dialog, GTK_STOCK_DIALOG_QUESTION,
644                                    msg, _("Collections have been modified. Quit anyway?"));
645         g_free(msg);
646         generic_dialog_add_button(exit_dialog, GTK_STOCK_QUIT, NULL, exit_confirm_exit_cb, TRUE);
647
648         gtk_widget_show(exit_dialog->dialog);
649
650         return TRUE;
651 }
652
653 static void exit_program_write_metadata_cb(gint success, const gchar *dest_path, gpointer data)
654 {
655         if (success) exit_program();
656 }
657
658 void exit_program(void)
659 {
660         layout_image_full_screen_stop(NULL);
661
662         if (metadata_write_queue_confirm(exit_program_write_metadata_cb, NULL)) return;
663
664         if (exit_confirm_dlg()) return;
665
666         exit_program_final();
667 }
668
669
670
671 /* This code is supposed to handle situation when a file mmaped by image_loader 
672  * or by exif loader is truncated by some other process.
673  * This is probably not completely correct according to posix, because
674  * mmap is not in the list of calls that can be used safely in signal handler,
675  * but anyway, the handler is called in situation when the application would
676  * crash otherwise.
677  * Ideas for improvement are welcome ;)
678  */
679 /* FIXME: this probably needs some better ifdefs. Please report any compilation problems */
680
681 #ifdef SIGBUS
682 static void sigbus_handler_cb(int signum, siginfo_t *info, void *context)
683 {
684         unsigned long pagesize = sysconf(_SC_PAGE_SIZE);
685         DEBUG_1("SIGBUS %p", info->si_addr);
686         mmap((void *)(((unsigned long)info->si_addr / pagesize) * pagesize), pagesize, PROT_READ | PROT_WRITE, MAP_FIXED | MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
687 }
688 #endif
689
690 static void setup_sigbus_handler(void)
691 {
692 #ifdef SIGBUS
693         struct sigaction sigbus_action;
694         sigfillset(&sigbus_action.sa_mask);
695         sigbus_action.sa_sigaction = sigbus_handler_cb;
696         sigbus_action.sa_flags = SA_SIGINFO;
697
698         sigaction(SIGBUS, &sigbus_action, NULL);
699 #endif
700 }
701
702 gint main(gint argc, gchar *argv[])
703 {
704         LayoutWindow *lw;
705         gchar *path = NULL;
706         gchar *cmd_path = NULL;
707         gchar *cmd_file = NULL;
708         GList *cmd_list = NULL;
709         GList *collection_list = NULL;
710         CollectionData *first_collection = NULL;
711         gchar *geometry = NULL;
712         gchar *buf;
713         CollectionData *cd = NULL;
714
715 #ifdef HAVE_GTHREAD
716         g_thread_init (NULL);
717         gdk_threads_init();
718         gdk_threads_enter();
719 #endif
720         
721         /* init execution time counter (debug only) */
722         init_exec_time();
723
724         /* setup locale, i18n */
725         gtk_set_locale();
726
727 #ifdef ENABLE_NLS
728         bindtextdomain(PACKAGE, GQ_LOCALEDIR);
729         bind_textdomain_codeset(PACKAGE, "UTF-8");
730         textdomain(PACKAGE);
731 #endif
732
733         exif_init();
734         
735         /* setup random seed for random slideshow */
736         srand(time(NULL));
737
738 #if 1
739         log_printf("%s %s, This is an alpha release.\n", GQ_APPNAME, VERSION);
740 #endif
741
742         setup_sigbus_handler();
743
744         /* register global notify functions */
745         file_data_register_notify_func(cache_notify_cb, NULL, NOTIFY_PRIORITY_HIGH);
746         file_data_register_notify_func(thumb_notify_cb, NULL, NOTIFY_PRIORITY_HIGH);
747         file_data_register_notify_func(histogram_notify_cb, NULL, NOTIFY_PRIORITY_HIGH);
748         file_data_register_notify_func(collect_manager_notify_cb, NULL, NOTIFY_PRIORITY_LOW);
749
750         parse_command_line_for_debug_option(argc, argv);
751
752         options = init_options(NULL);
753         setup_default_options(options);
754         load_options(options);
755
756         gtkrc_load();
757         gtk_init(&argc, &argv);
758
759         parse_command_line(argc, argv, &cmd_path, &cmd_file, &cmd_list, &collection_list, &geometry);
760
761         if (gtk_major_version < GTK_MAJOR_VERSION ||
762             (gtk_major_version == GTK_MAJOR_VERSION && gtk_minor_version < GTK_MINOR_VERSION) )
763                 {
764                 log_printf("!!! This is a friendly warning.\n");
765                 log_printf("!!! The version of GTK+ in use now is older than when %s was compiled.\n", GQ_APPNAME);
766                 log_printf("!!!  compiled with GTK+-%d.%d\n", GTK_MAJOR_VERSION, GTK_MINOR_VERSION);
767                 log_printf("!!!   running with GTK+-%d.%d\n", gtk_major_version, gtk_minor_version);
768                 log_printf("!!! %s may quit unexpectedly with a relocation error.\n", GQ_APPNAME);
769                 }
770
771         mkdir_if_not_exists(get_rc_dir());
772         mkdir_if_not_exists(get_collections_dir());
773         mkdir_if_not_exists(get_thumbnails_cache_dir());
774         mkdir_if_not_exists(get_metadata_cache_dir());
775
776         keys_load();
777         filter_add_defaults();
778         filter_rebuild();
779
780         editor_load_descriptions();
781
782         accel_map_load();
783
784         if (startup_blank)
785                 {
786                 g_free(cmd_path);
787                 cmd_path = NULL;
788                 g_free(cmd_file);
789                 cmd_file = NULL;
790                 filelist_free(cmd_list);
791                 cmd_list = NULL;
792                 string_list_free(collection_list);
793                 collection_list = NULL;
794
795                 path = NULL;
796                 }
797         else if (cmd_path)
798                 {
799                 path = g_strdup(cmd_path);
800                 }
801         else if (options->startup.restore_path && options->startup.path && isdir(options->startup.path))
802                 {
803                 path = g_strdup(options->startup.path);
804                 }
805         else
806                 {
807                 path = get_current_dir();
808                 }
809
810         lw = layout_new_with_geometry(NULL, options->layout.tools_float, options->layout.tools_hidden, geometry);
811         layout_sort_set(lw, options->file_sort.method, options->file_sort.ascending);
812
813         if (collection_list && !startup_command_line_collection)
814                 {
815                 GList *work;
816
817                 work = collection_list;
818                 while (work)
819                         {
820                         CollectWindow *cw;
821                         const gchar *path;
822
823                         path = work->data;
824                         work = work->next;
825
826                         cw = collection_window_new(path);
827                         if (!first_collection && cw) first_collection = cw->cd;
828                         }
829                 }
830
831         if (cmd_list ||
832             (startup_command_line_collection && collection_list))
833                 {
834                 GList *work;
835
836                 if (startup_command_line_collection)
837                         {
838                         CollectWindow *cw;
839
840                         cw = collection_window_new("");
841                         cd = cw->cd;
842                         }
843                 else
844                         {
845                         cd = collection_new("");        /* if we pass NULL, untitled counter is falsely increm. */
846                         }
847
848                 g_free(cd->path);
849                 cd->path = NULL;
850                 g_free(cd->name);
851                 cd->name = g_strdup(_("Command line"));
852
853                 collection_path_changed(cd);
854
855                 work = cmd_list;
856                 while (work)
857                         {
858                         collection_add(cd, file_data_new_simple((gchar *)work->data), FALSE);
859                         work = work->next;
860                         }
861
862                 work = collection_list;
863                 while (work)
864                         {
865                         collection_load(cd, (gchar *)work->data, COLLECTION_LOAD_APPEND);
866                         work = work->next;
867                         }
868
869                 layout_set_path(lw, path);
870                 if (cd->list) layout_image_set_collection(lw, cd, cd->list->data);
871
872                 /* mem leak, we never unref this collection when !startup_command_line_collection
873                  * (the image view of the main window does not hold a ref to the collection)
874                  * this is sort of unavoidable, for if it did hold a ref, next/back
875                  * may not work as expected when closing collection windows.
876                  *
877                  * collection_unref(cd);
878                  */
879
880                 }
881         else if (cmd_file)
882                 {
883                 layout_set_path(lw, cmd_file);
884                 }
885         else
886                 {
887                 layout_set_path(lw, path);
888                 if (first_collection)
889                         {
890                         layout_image_set_collection(lw, first_collection,
891                                                     collection_get_first(first_collection));
892                         }
893                 }
894
895         image_osd_set(lw->image, options->image_overlay.common.state | (options->image_overlay.common.show_at_startup ? OSD_SHOW_INFO : OSD_SHOW_NOTHING));
896
897         g_free(geometry);
898         g_free(cmd_path);
899         g_free(cmd_file);
900         filelist_free(cmd_list);
901         string_list_free(collection_list);
902         g_free(path);
903
904         if (startup_full_screen) layout_image_full_screen_start(lw);
905         if (startup_in_slideshow) layout_image_slideshow_start(lw);
906
907         buf = g_build_filename(get_rc_dir(), ".command", NULL);
908         remote_connection = remote_server_init(buf, cd);
909         g_free(buf);
910         
911         gtk_main();
912 #ifdef HAVE_GTHREAD
913         gdk_threads_leave();
914 #endif
915         return 0;
916 }
917 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */