split filelist.c to filefilter.c and filedata.c
[geeqie.git] / src / main.c
1 /*
2  * Geeqie
3  * (C) 2006 John Ellis
4  * Copyright (C) 2008 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 "debug.h"
20 #include "dnd.h"
21 #include "editors.h"
22 #include "filedata.h"
23 #include "filefilter.h"
24 #include "fullscreen.h"
25 #include "image-overlay.h"
26 #include "img-view.h"
27 #include "layout.h"
28 #include "layout_image.h"
29 #include "menu.h"
30 #include "pixbuf_util.h"
31 #include "preferences.h"
32 #include "rcfile.h"
33 #include "remote.h"
34 #include "similar.h"
35 #include "slideshow.h"
36 #include "utilops.h"
37 #include "ui_bookmark.h"
38 #include "ui_help.h"
39 #include "ui_fileops.h"
40 #include "ui_tabcomp.h"
41 #include "ui_utildlg.h"
42
43 #include <gdk/gdkkeysyms.h> /* for keyboard values */
44
45
46 #include <math.h>
47
48
49 static RemoteConnection *remote_connection = NULL;
50 static CollectionData *command_collection = NULL;
51
52
53 /*
54  *-----------------------------------------------------------------------------
55  * misc (public)
56  *-----------------------------------------------------------------------------
57  */
58
59 GtkWidget *window_new(GtkWindowType type, const gchar *name, const gchar *icon,
60                       const gchar *icon_file, const gchar *subtitle)
61 {
62         gchar *title;
63         GtkWidget *window;
64
65         window = gtk_window_new(type);
66         if (!window) return NULL;
67
68         if (subtitle)
69                 {
70                 title = g_strdup_printf("%s - %s", subtitle, GQ_APPNAME);
71                 }
72         else
73                 {
74                 title = g_strdup_printf("%s", GQ_APPNAME);
75                 }
76
77         gtk_window_set_title(GTK_WINDOW(window), title);
78         g_free(title);
79
80         window_set_icon(window, icon, icon_file);
81         gtk_window_set_role(GTK_WINDOW(window), name);
82         gtk_window_set_wmclass(GTK_WINDOW(window), name, GQ_WMCLASS);
83
84         return window;
85 }
86
87 void window_set_icon(GtkWidget *window, const gchar *icon, const gchar *file)
88 {
89         if (!icon && !file) icon = PIXBUF_INLINE_ICON;
90
91         if (icon)
92                 {
93                 GdkPixbuf *pixbuf;
94
95                 pixbuf = pixbuf_inline(icon);
96                 if (pixbuf)
97                         {
98                         gtk_window_set_icon(GTK_WINDOW(window), pixbuf);
99                         g_object_unref(pixbuf);
100                         }
101                 }
102         else
103                 {
104                 gtk_window_set_icon_from_file(GTK_WINDOW(window), file, NULL);
105                 }
106 }
107
108 gint window_maximized(GtkWidget *window)
109 {
110         GdkWindowState state;
111
112         if (!window || !window->window) return FALSE;
113
114         state = gdk_window_get_state(window->window);
115         return (state & GDK_WINDOW_STATE_MAXIMIZED);
116 }
117
118 gdouble get_zoom_increment(void)
119 {
120         return ((options->image.zoom_increment != 0) ? (gdouble)options->image.zoom_increment / 10.0 : 1.0);
121 }
122
123
124 /*
125  *-----------------------------------------------------------------------------
126  * Open  browser with the help Documentation
127  *-----------------------------------------------------------------------------
128  */
129
130 static gchar *command_result(const gchar *binary, const gchar *command)
131 {
132         gchar *result = NULL;
133         FILE *f;
134         char buf[2048];
135         int l;
136
137         if (!binary) return NULL;
138         if (!file_in_path(binary)) return NULL;
139
140         if (!command) return g_strdup(binary);
141         if (command[0] == '!') return g_strdup(command + 1);
142
143         f = popen(command, "r");
144         if (!f) return NULL;
145
146         while ((l = fread(buf, sizeof(char), sizeof(buf), f)) > 0)
147                 {
148                 if (!result)
149                         {
150                         int n = 0;
151
152                         while (n < l && buf[n] != '\n' && buf[n] != '\r') n++;
153                         if (n > 0) result = g_strndup(buf, n);
154                         }
155                 }
156
157         pclose(f);
158
159         return result;
160 }
161
162 static void help_browser_command(const gchar *command, const gchar *path)
163 {
164         gchar *result;
165         gchar *buf;
166         gchar *begin;
167         gchar *end;
168
169         if (!command || !path) return;
170
171         DEBUG_1("Help command pre \"%s\", \"%s\"", command, path);
172
173         buf = g_strdup(command);
174         begin = strstr(buf, "%s");
175         if (begin)
176                 {
177                 *begin = '\0';
178                 end = begin + 2;
179                 begin = buf;
180
181                 result = g_strdup_printf("%s%s%s &", begin, path, end);
182                 }
183         else
184                 {
185                 result = g_strdup_printf("%s \"%s\" &", command, path);
186                 }
187         g_free(buf);
188
189         DEBUG_1("Help command post [%s]", result);
190
191         system(result);
192
193         g_free(result);
194 }
195
196 /*
197  * each set of 2 strings is one browser:
198  *   the 1st is the binary to look for in the path
199  *   the 2nd has 3 capabilities:
200  *        NULL     exec binary with html file path as command line
201  *        string   exec string and use results for command line
202  *        !string  use text following ! as command line, replacing optional %s with html file path
203 */
204 static gchar *html_browsers[] =
205 {
206         /* Redhat has a nifty htmlview script to start the user's preferred browser */
207         "htmlview",     NULL,
208         /* GNOME 2 */
209         "gconftool-2",  "gconftool-2 -g /desktop/gnome/url-handlers/http/command",
210         /* KDE */
211         "kfmclient",    "!kfmclient exec \"%s\"",
212         /* use fallbacks */
213         "firefox",      NULL,
214         "mozilla",      NULL,
215         "konqueror",    NULL,
216         "netscape",     NULL,
217         NULL,           NULL
218 };
219
220 static void help_browser_run(void)
221 {
222         gchar *result = NULL;
223         gint i;
224
225         i = 0;
226         while (!result && html_browsers[i])
227                 {
228                 result = command_result(html_browsers[i], html_browsers[i+1]);
229                 i += 2;
230                 }
231
232         if (!result)
233                 {
234                 printf("Unable to detect an installed browser.\n");
235                 return;
236                 }
237
238         help_browser_command(result, GQ_HTMLDIR "/index.html");
239
240         g_free(result);
241 }
242
243 /*
244  *-----------------------------------------------------------------------------
245  * help window
246  *-----------------------------------------------------------------------------
247  */
248
249 static GtkWidget *help_window = NULL;
250
251 static void help_window_destroy_cb(GtkWidget *window, gpointer data)
252 {
253         help_window = NULL;
254 }
255
256 void help_window_show(const gchar *key)
257 {
258         if (key && strcmp(key, "html_contents") == 0)
259                 {
260                 help_browser_run();
261                 return;
262                 }
263
264         if (help_window)
265                 {
266                 gtk_window_present(GTK_WINDOW(help_window));
267                 if (key) help_window_set_key(help_window, key);
268                 return;
269                 }
270
271         {
272         gchar *title = g_strdup_printf("%s - %s", _("Help"), GQ_APPNAME);
273         help_window = help_window_new(title, GQ_WMCLASS, "help",
274                                       GQ_HELPDIR "/README", key);
275         g_free(title);
276         }
277         g_signal_connect(G_OBJECT(help_window), "destroy",
278                          G_CALLBACK(help_window_destroy_cb), NULL);
279 }
280
281
282 /*
283  *-----------------------------------------------------------------------------
284  * keyboard functions
285  *-----------------------------------------------------------------------------
286  */
287
288 void keyboard_scroll_calc(gint *x, gint *y, GdkEventKey *event)
289 {
290         static gint delta = 0;
291         static guint32 time_old = 0;
292         static guint keyval_old = 0;
293
294         if (event->state & GDK_CONTROL_MASK)
295                 {
296                 if (*x < 0) *x = G_MININT / 2;
297                 if (*x > 0) *x = G_MAXINT / 2;
298                 if (*y < 0) *y = G_MININT / 2;
299                 if (*y > 0) *y = G_MAXINT / 2;
300
301                 return;
302                 }
303
304         if (options->progressive_key_scrolling)
305                 {
306                 guint32 time_diff;
307
308                 time_diff = event->time - time_old;
309
310                 /* key pressed within 125ms ? (1/8 second) */
311                 if (time_diff > 125 || event->keyval != keyval_old) delta = 0;
312
313                 time_old = event->time;
314                 keyval_old = event->keyval;
315
316                 delta += 2;
317                 }
318         else
319                 {
320                 delta = 8;
321                 }
322
323         *x = *x * delta;
324         *y = *y * delta;
325 }
326
327
328 /*
329  *-----------------------------------------------------------------------------
330  * remote functions
331  *-----------------------------------------------------------------------------
332  */
333
334 static void gr_image_next(const gchar *text, gpointer data)
335 {
336         layout_image_next(NULL);
337 }
338
339 static void gr_image_prev(const gchar *text, gpointer data)
340 {
341         layout_image_prev(NULL);
342 }
343
344 static void gr_image_first(const gchar *text, gpointer data)
345 {
346         layout_image_first(NULL);
347 }
348
349 static void gr_image_last(const gchar *text, gpointer data)
350 {
351         layout_image_last(NULL);
352 }
353
354 static void gr_fullscreen_toggle(const gchar *text, gpointer data)
355 {
356         layout_image_full_screen_toggle(NULL);
357 }
358
359 static void gr_fullscreen_start(const gchar *text, gpointer data)
360 {
361         layout_image_full_screen_start(NULL);
362 }
363
364 static void gr_fullscreen_stop(const gchar *text, gpointer data)
365 {
366         layout_image_full_screen_stop(NULL);
367 }
368
369 static void gr_slideshow_start_rec(const gchar *text, gpointer data)
370 {
371         GList *list;
372
373         list = filelist_recursive(text);
374         if (!list) return;
375 //printf("length: %d\n", g_list_length(list));
376         layout_image_slideshow_stop(NULL);
377         layout_image_slideshow_start_from_list(NULL, list);
378 }
379
380 static void gr_slideshow_toggle(const gchar *text, gpointer data)
381 {
382         layout_image_slideshow_toggle(NULL);
383 }
384
385 static void gr_slideshow_start(const gchar *text, gpointer data)
386 {
387         layout_image_slideshow_start(NULL);
388 }
389
390 static void gr_slideshow_stop(const gchar *text, gpointer data)
391 {
392         layout_image_slideshow_stop(NULL);
393 }
394
395 static void gr_slideshow_delay(const gchar *text, gpointer data)
396 {
397         gdouble n;
398
399         n = strtod(text, NULL);
400         if (n < SLIDESHOW_MIN_SECONDS || n > SLIDESHOW_MAX_SECONDS)
401                 {
402                 printf_term("Remote slideshow delay out of range (%.1f to %.1f)\n",
403                             SLIDESHOW_MIN_SECONDS, SLIDESHOW_MAX_SECONDS);
404                 return;
405                 }
406         options->slideshow.delay = (gint)(n * 10.0 + 0.01);
407 }
408
409 static void gr_tools_show(const gchar *text, gpointer data)
410 {
411         gint popped;
412         gint hidden;
413
414         if (layout_tools_float_get(NULL, &popped, &hidden) && hidden)
415                 {
416                 layout_tools_float_set(NULL, popped, FALSE);
417                 }
418 }
419
420 static void gr_tools_hide(const gchar *text, gpointer data)
421 {
422         gint popped;
423         gint hidden;
424
425         if (layout_tools_float_get(NULL, &popped, &hidden) && !hidden)
426                 {
427                 layout_tools_float_set(NULL, popped, TRUE);
428                 }
429 }
430
431 static gint gr_quit_idle_cb(gpointer data)
432 {
433         exit_program();
434
435         return FALSE;
436 }
437
438 static void gr_quit(const gchar *text, gpointer data)
439 {
440         /* schedule exit when idle, if done from within a
441          * remote handler remote_close will crash
442          */
443         g_idle_add(gr_quit_idle_cb, NULL);
444 }
445
446 static void gr_file_load(const gchar *text, gpointer data)
447 {
448         if (isfile(text))
449                 {
450                 if (file_extension_match(text, ".gqv"))
451                         {
452                         collection_window_new(text);
453                         }
454                 else
455                         {
456                         layout_set_path(NULL, text);
457                         }
458                 }
459         else if (isdir(text))
460                 {
461                 layout_set_path(NULL, text);
462                 }
463         else
464                 {
465                 printf("remote sent filename that does not exist:\"%s\"\n", text);
466                 }
467 }
468
469 static void gr_file_view(const gchar *text, gpointer data)
470 {
471         view_window_new(file_data_new_simple(text));
472 }
473
474 static void gr_list_clear(const gchar *text, gpointer data)
475 {
476         if (command_collection) collection_unref(command_collection);
477         command_collection = NULL;
478 }
479
480 static void gr_list_add(const gchar *text, gpointer data)
481 {
482         gint new = TRUE;
483
484         if (!command_collection)
485                 {
486                 CollectionData *cd;
487
488                 cd = collection_new("");
489
490                 g_free(cd->path);
491                 cd->path = NULL;
492                 g_free(cd->name);
493                 cd->name = g_strdup(_("Command line"));
494
495                 command_collection = cd;
496                 }
497         else
498                 {
499                 new = (!collection_get_first(command_collection));
500                 }
501
502         if (collection_add(command_collection, file_data_new_simple(text), FALSE) && new)
503                 {
504                 layout_image_set_collection(NULL, command_collection,
505                                             collection_get_first(command_collection));
506                 }
507 }
508
509 static void gr_raise(const gchar *text, gpointer data)
510 {
511         LayoutWindow *lw = NULL;
512
513         if (layout_valid(&lw))
514                 {
515                 gtk_window_present(GTK_WINDOW(lw->window));
516                 }
517 }
518
519 typedef struct _RemoteCommandEntry RemoteCommandEntry;
520 struct _RemoteCommandEntry {
521         gchar *opt_s;
522         gchar *opt_l;
523         void (*func)(const gchar *text, gpointer data);
524         gint needs_extra;
525         gint prefer_command_line;
526         gchar *description;
527 };
528
529 static RemoteCommandEntry remote_commands[] = {
530         /* short, long                  callback,               extra, prefer,description */
531         { "-n", "--next",               gr_image_next,          FALSE, FALSE, N_("next image") },
532         { "-b", "--back",               gr_image_prev,          FALSE, FALSE, N_("previous image") },
533         { NULL, "--first",              gr_image_first,         FALSE, FALSE, N_("first image") },
534         { NULL, "--last",               gr_image_last,          FALSE, FALSE, N_("last image") },
535         { "-f", "--fullscreen",         gr_fullscreen_toggle,   FALSE, TRUE,  N_("toggle full screen") },
536         { "-fs","--fullscreen-start",   gr_fullscreen_start,    FALSE, FALSE, N_("start full screen") },
537         { "-fS","--fullscreen-stop",    gr_fullscreen_stop,     FALSE, FALSE, N_("stop full screen") },
538         { "-s", "--slideshow",          gr_slideshow_toggle,    FALSE, TRUE,  N_("toggle slide show") },
539         { "-ss","--slideshow-start",    gr_slideshow_start,     FALSE, FALSE, N_("start slide show") },
540         { "-sS","--slideshow-stop",     gr_slideshow_stop,      FALSE, FALSE, N_("stop slide show") },
541         { "-sr","--slideshow-recurse",  gr_slideshow_start_rec, TRUE,  FALSE, N_("start recursive slide show") },
542         { "-d", "--delay=",             gr_slideshow_delay,     TRUE,  FALSE, N_("set slide show delay in seconds") },
543         { "+t", "--tools-show",         gr_tools_show,          FALSE, TRUE,  N_("show tools") },
544         { "-t", "--tools-hide",         gr_tools_hide,          FALSE, TRUE,  N_("hide tools") },
545         { "-q", "--quit",               gr_quit,                FALSE, FALSE, N_("quit") },
546         { NULL, "file:",                gr_file_load,           TRUE,  FALSE, N_("open file") },
547         { NULL, "view:",                gr_file_view,           TRUE,  FALSE, N_("open file in new window") },
548         { NULL, "--list-clear",         gr_list_clear,          FALSE, FALSE, NULL },
549         { NULL, "--list-add:",          gr_list_add,            TRUE,  FALSE, NULL },
550         { NULL, "raise",                gr_raise,               FALSE, FALSE, NULL },
551         { NULL, NULL, NULL, FALSE, FALSE, NULL }
552 };
553
554 static RemoteCommandEntry *remote_command_find(const gchar *text, const gchar **offset)
555 {
556         gint match = FALSE;
557         gint i;
558
559         i = 0;
560         while (!match && remote_commands[i].func != NULL)
561                 {
562                 if (remote_commands[i].needs_extra)
563                         {
564                         if (remote_commands[i].opt_s &&
565                             strncmp(remote_commands[i].opt_s, text, strlen(remote_commands[i].opt_s)) == 0)
566                                 {
567                                 if (offset) *offset = text + strlen(remote_commands[i].opt_s);
568                                 return &remote_commands[i];
569                                 }
570                         else if (remote_commands[i].opt_l &&
571                                  strncmp(remote_commands[i].opt_l, text, strlen(remote_commands[i].opt_l)) == 0)
572                                 {
573                                 if (offset) *offset = text + strlen(remote_commands[i].opt_l);
574                                 return &remote_commands[i];
575                                 }
576                         }
577                 else
578                         {
579                         if ((remote_commands[i].opt_s && strcmp(remote_commands[i].opt_s, text) == 0) ||
580                             (remote_commands[i].opt_l && strcmp(remote_commands[i].opt_l, text) == 0))
581                                 {
582                                 if (offset) *offset = text;
583                                 return &remote_commands[i];
584                                 }
585                         }
586
587                 i++;
588                 }
589
590         return NULL;
591 }
592
593 static void remote_cb(RemoteConnection *rc, const gchar *text, gpointer data)
594 {
595         RemoteCommandEntry *entry;
596         const gchar *offset;
597
598         entry = remote_command_find(text, &offset);
599         if (entry && entry->func)
600                 {
601                 entry->func(offset, data);
602                 }
603         else
604                 {
605                 printf("unknown remote command:%s\n", text);
606                 }
607 }
608
609 static void remote_help(void)
610 {
611         gint i;
612
613         print_term(_("Remote command list:\n"));
614
615         i = 0;
616         while (remote_commands[i].func != NULL)
617                 {
618                 if (remote_commands[i].description)
619                         {
620                         printf_term("  %-3s%s %-20s %s\n",
621                                     (remote_commands[i].opt_s) ? remote_commands[i].opt_s : "",
622                                     (remote_commands[i].opt_s && remote_commands[i].opt_l) ? "," : " ",
623                                     (remote_commands[i].opt_l) ? remote_commands[i].opt_l : "",
624                                     _(remote_commands[i].description));
625                         }
626                 i++;
627                 }
628 }
629
630 static GList *remote_build_list(GList *list, int argc, char *argv[])
631 {
632         gint i;
633
634         i = 1;
635         while (i < argc)
636                 {
637                 RemoteCommandEntry *entry;
638
639                 entry = remote_command_find(argv[i], NULL);
640                 if (entry)
641                         {
642                         list = g_list_append(list, argv[i]);
643                         }
644                 i++;
645                 }
646
647         return list;
648 }
649
650 static void remote_control(const gchar *arg_exec, GList *remote_list, const gchar *path,
651                                   GList *cmd_list, GList *collection_list)
652 {
653         RemoteConnection *rc;
654         gint started = FALSE;
655         gchar *buf;
656
657         buf = g_strconcat(homedir(), "/", GQ_RC_DIR, "/.command", NULL);
658         rc = remote_client_open(buf);
659         if (!rc)
660                 {
661                 GString *command;
662                 GList *work;
663                 gint retry_count = 12;
664                 gint blank = FALSE;
665
666                 printf_term(_("Remote %s not running, starting..."), GQ_APPNAME);
667
668                 command = g_string_new(arg_exec);
669
670                 work = remote_list;
671                 while (work)
672                         {
673                         gchar *text;
674                         RemoteCommandEntry *entry;
675
676                         text = work->data;
677                         work = work->next;
678
679                         entry = remote_command_find(text, NULL);
680                         if (entry)
681                                 {
682                                 if (entry->prefer_command_line)
683                                         {
684                                         remote_list = g_list_remove(remote_list, text);
685                                         g_string_append(command, " ");
686                                         g_string_append(command, text);
687                                         }
688                                 if (entry->opt_l && strcmp(entry->opt_l, "file:") == 0)
689                                         {
690                                         blank = TRUE;
691                                         }
692                                 }
693                         }
694
695                 if (blank || cmd_list || path) g_string_append(command, " --blank");
696                 if (get_debug_level()) g_string_append(command, " --debug");
697
698                 g_string_append(command, " &");
699                 system(command->str);
700                 g_string_free(command, TRUE);
701
702                 while (!rc && retry_count > 0)
703                         {
704                         usleep((retry_count > 10) ? 500000 : 1000000);
705                         rc = remote_client_open(buf);
706                         if (!rc) print_term(".");
707                         retry_count--;
708                         }
709
710                 print_term("\n");
711
712                 started = TRUE;
713                 }
714         g_free(buf);
715
716         if (rc)
717                 {
718                 GList *work;
719                 const gchar *prefix;
720                 gint use_path = TRUE;
721                 gint sent = FALSE;
722
723                 work = remote_list;
724                 while (work)
725                         {
726                         gchar *text;
727                         RemoteCommandEntry *entry;
728
729                         text = work->data;
730                         work = work->next;
731
732                         entry = remote_command_find(text, NULL);
733                         if (entry &&
734                             entry->opt_l &&
735                             strcmp(entry->opt_l, "file:") == 0) use_path = FALSE;
736
737                         remote_client_send(rc, text);
738
739                         sent = TRUE;
740                         }
741
742                 if (cmd_list && cmd_list->next)
743                         {
744                         prefix = "--list-add:";
745                         remote_client_send(rc, "--list-clear");
746                         }
747                 else
748                         {
749                         prefix = "file:";
750                         }
751
752                 work = cmd_list;
753                 while (work)
754                         {
755                         FileData *fd;
756                         gchar *text;
757
758                         fd = work->data;
759                         work = work->next;
760
761                         text = g_strconcat(prefix, fd->path, NULL);
762                         remote_client_send(rc, text);
763                         g_free(text);
764
765                         sent = TRUE;
766                         }
767
768                 if (path && !cmd_list && use_path)
769                         {
770                         gchar *text;
771
772                         text = g_strdup_printf("file:%s", path);
773                         remote_client_send(rc, text);
774                         g_free(text);
775
776                         sent = TRUE;
777                         }
778
779                 work = collection_list;
780                 while (work)
781                         {
782                         const gchar *name;
783                         gchar *text;
784
785                         name = work->data;
786                         work = work->next;
787
788                         text = g_strdup_printf("file:%s", name);
789                         remote_client_send(rc, text);
790                         g_free(text);
791
792                         sent = TRUE;
793                         }
794
795                 if (!started && !sent)
796                         {
797                         remote_client_send(rc, "raise");
798                         }
799                 }
800         else
801                 {
802                 print_term(_("Remote not available\n"));
803                 }
804
805         _exit(0);
806 }
807
808 /*
809  *-----------------------------------------------------------------------------
810  * command line parser (private) hehe, who needs popt anyway?
811  *-----------------------------------------------------------------------------
812  */
813
814 static gint startup_blank = FALSE;
815 static gint startup_full_screen = FALSE;
816 static gint startup_in_slideshow = FALSE;
817 static gint startup_command_line_collection = FALSE;
818
819
820 static void parse_command_line_add_file(const gchar *file_path, gchar **path, gchar **file,
821                                         GList **list, GList **collection_list)
822 {
823         gchar *path_parsed;
824
825         path_parsed = g_strdup(file_path);
826         parse_out_relatives(path_parsed);
827
828         if (file_extension_match(path_parsed, ".gqv"))
829                 {
830                 *collection_list = g_list_append(*collection_list, path_parsed);
831                 }
832         else
833                 {
834                 if (!*path) *path = remove_level_from_path(path_parsed);
835                 if (!*file) *file = g_strdup(path_parsed);
836                 *list = g_list_prepend(*list, file_data_new_simple(path_parsed));
837                 }
838 }
839
840 static void parse_command_line_add_dir(const gchar *dir, gchar **path, gchar **file,
841                                        GList **list)
842 {
843         GList *files = NULL;
844         gchar *path_parsed;
845
846         path_parsed = g_strdup(dir);
847         parse_out_relatives(path_parsed);
848
849         if (filelist_read(path_parsed, &files, NULL))
850                 {
851                 GList *work;
852
853                 files = filelist_filter(files, FALSE);
854                 files = filelist_sort_path(files);
855
856                 work = files;
857                 while (work)
858                         {
859                         FileData *fd = work->data;
860                         if (!*path) *path = remove_level_from_path(fd->path);
861                         if (!*file) *file = g_strdup(fd->path);
862                         *list = g_list_prepend(*list, fd);
863
864                         work = work->next;
865                         }
866
867                 g_list_free(files);
868                 }
869
870         g_free(path_parsed);
871 }
872
873 static void parse_command_line_process_dir(const gchar *dir, gchar **path, gchar **file,
874                                            GList **list, gchar **first_dir)
875 {
876
877         if (!*list && !*first_dir)
878                 {
879                 *first_dir = g_strdup(dir);
880                 }
881         else
882                 {
883                 if (*first_dir)
884                         {
885                         parse_command_line_add_dir(*first_dir, path, file, list);
886                         g_free(*first_dir);
887                         *first_dir = NULL;
888                         }
889                 parse_command_line_add_dir(dir, path, file, list);
890                 }
891 }
892
893 static void parse_command_line_process_file(const gchar *file_path, gchar **path, gchar **file,
894                                             GList **list, GList **collection_list, gchar **first_dir)
895 {
896
897         if (*first_dir)
898                 {
899                 parse_command_line_add_dir(*first_dir, path, file, list);
900                 g_free(*first_dir);
901                 *first_dir = NULL;
902                 }
903         parse_command_line_add_file(file_path, path, file, list, collection_list);
904 }
905
906 static void parse_command_line(int argc, char *argv[], gchar **path, gchar **file,
907                                GList **cmd_list, GList **collection_list,
908                                gchar **geometry)
909 {
910         GList *list = NULL;
911         GList *remote_list = NULL;
912         gint remote_do = FALSE;
913         gchar *first_dir = NULL;
914
915         if (argc > 1)
916                 {
917                 gint i;
918                 gchar *base_dir = get_current_dir();
919                 i = 1;
920                 while (i < argc)
921                         {
922                         const gchar *cmd_line = argv[i];
923                         gchar *cmd_all = concat_dir_and_file(base_dir, cmd_line);
924
925                         if (cmd_line[0] == '/' && isdir(cmd_line))
926                                 {
927                                 parse_command_line_process_dir(cmd_line, path, file, &list, &first_dir);
928                                 }
929                         else if (isdir(cmd_all))
930                                 {
931                                 parse_command_line_process_dir(cmd_all, path, file, &list, &first_dir);
932                                 }
933                         else if (cmd_line[0] == '/' && isfile(cmd_line))
934                                 {
935                                 parse_command_line_process_file(cmd_line, path, file,
936                                                                 &list, collection_list, &first_dir);
937                                 }
938                         else if (isfile(cmd_all))
939                                 {
940                                 parse_command_line_process_file(cmd_all, path, file,
941                                                                 &list, collection_list, &first_dir);
942                                 }
943                         else if (strncmp(cmd_line, "--debug", 7) == 0 && (cmd_line[7] == '\0' || cmd_line[7] == '='))
944                                 {
945                                 /* do nothing but do not produce warnings */
946                                 }
947                         else if (strcmp(cmd_line, "+t") == 0 ||
948                                  strcmp(cmd_line, "--with-tools") == 0)
949                                 {
950                                 options->layout.tools_float = FALSE;
951                                 options->layout.tools_hidden = FALSE;
952
953                                 remote_list = g_list_append(remote_list, "+t");
954                                 }
955                         else if (strcmp(cmd_line, "-t") == 0 ||
956                                  strcmp(cmd_line, "--without-tools") == 0)
957                                 {
958                                 options->layout.tools_hidden = TRUE;
959
960                                 remote_list = g_list_append(remote_list, "-t");
961                                 }
962                         else if (strcmp(cmd_line, "-f") == 0 ||
963                                  strcmp(cmd_line, "--fullscreen") == 0)
964                                 {
965                                 startup_full_screen = TRUE;
966                                 }
967                         else if (strcmp(cmd_line, "-s") == 0 ||
968                                  strcmp(cmd_line, "--slideshow") == 0)
969                                 {
970                                 startup_in_slideshow = TRUE;
971                                 }
972                         else if (strcmp(cmd_line, "-l") == 0 ||
973                                  strcmp(cmd_line, "--list") == 0)
974                                 {
975                                 startup_command_line_collection = TRUE;
976                                 }
977                         else if (strncmp(cmd_line, "--geometry=", 11) == 0)
978                                 {
979                                 if (!*geometry) *geometry = g_strdup(cmd_line + 11);
980                                 }
981                         else if (strcmp(cmd_line, "-r") == 0 ||
982                                  strcmp(cmd_line, "--remote") == 0)
983                                 {
984                                 if (!remote_do)
985                                         {
986                                         remote_do = TRUE;
987                                         remote_list = remote_build_list(remote_list, argc, argv);
988                                         }
989                                 }
990                         else if (strcmp(cmd_line, "-rh") == 0 ||
991                                  strcmp(cmd_line, "--remote-help") == 0)
992                                 {
993                                 remote_help();
994                                 exit(0);
995                                 }
996                         else if (strcmp(cmd_line, "--blank") == 0)
997                                 {
998                                 startup_blank = TRUE;
999                                 }
1000                         else if (strcmp(cmd_line, "-v") == 0 ||
1001                                  strcmp(cmd_line, "--version") == 0)
1002                                 {
1003                                 printf("%s %s\n", GQ_APPNAME, VERSION);
1004                                 exit(0);
1005                                 }
1006                         else if (strcmp(cmd_line, "--alternate") == 0)
1007                                 {
1008                                 /* enable faster experimental algorithm */
1009                                 printf("Alternate similarity algorithm enabled\n");
1010                                 image_sim_alternate_set(TRUE);
1011                                 }
1012                         else if (strcmp(cmd_line, "-h") == 0 ||
1013                                  strcmp(cmd_line, "--help") == 0)
1014                                 {
1015                                 printf("%s %s\n", GQ_APPNAME, VERSION);
1016                                 printf_term(_("Usage: %s [options] [path]\n\n"), GQ_APPNAME_LC);
1017                                 print_term(_("valid options are:\n"));
1018                                 print_term(_("  +t, --with-tools           force show of tools\n"));
1019                                 print_term(_("  -t, --without-tools        force hide of tools\n"));
1020                                 print_term(_("  -f, --fullscreen           start in full screen mode\n"));
1021                                 print_term(_("  -s, --slideshow            start in slideshow mode\n"));
1022                                 print_term(_("  -l, --list                 open collection window for command line\n"));
1023                                 print_term(_("      --geometry=GEOMETRY    set main window location\n"));
1024                                 print_term(_("  -r, --remote               send following commands to open window\n"));
1025                                 print_term(_("  -rh,--remote-help          print remote command list\n"));
1026 #ifdef DEBUG
1027                                 print_term(_("  --debug[=level]            turn on debug output\n"));
1028 #endif
1029                                 print_term(_("  -v, --version              print version info\n"));
1030                                 print_term(_("  -h, --help                 show this message\n\n"));
1031
1032 #if 0
1033                                 /* these options are not officially supported!
1034                                  * only for testing new features, no need to translate them */
1035                                 print_term(  "  --alternate                use alternate similarity algorithm\n");
1036 #endif
1037
1038                                 exit(0);
1039                                 }
1040                         else if (!remote_do)
1041                                 {
1042                                 printf_term(_("invalid or ignored: %s\nUse --help for options\n"), cmd_line);
1043                                 }
1044
1045                         g_free(cmd_all);
1046                         i++;
1047                         }
1048                 g_free(base_dir);
1049                 parse_out_relatives(*path);
1050                 parse_out_relatives(*file);
1051                 }
1052
1053         list = g_list_reverse(list);
1054
1055         if (!*path && first_dir)
1056                 {
1057                 *path = first_dir;
1058                 first_dir = NULL;
1059
1060                 parse_out_relatives(*path);
1061                 }
1062         g_free(first_dir);
1063
1064         if (remote_do)
1065                 {
1066                 remote_control(argv[0], remote_list, *path, list, *collection_list);
1067                 }
1068         g_list_free(remote_list);
1069
1070         if (list && list->next)
1071                 {
1072                 *cmd_list = list;
1073                 }
1074         else
1075                 {
1076                 filelist_free(list);
1077                 *cmd_list = NULL;
1078                 }
1079 }
1080
1081 static void parse_command_line_for_debug_option(int argc, char *argv[])
1082 {
1083 #ifdef DEBUG
1084         const gchar *debug_option = "--debug";
1085         gint len = strlen(debug_option);
1086
1087         if (argc > 1)
1088                 {
1089                 gint i;
1090
1091                 for (i = 1; i < argc; i++)
1092                         {
1093                         const gchar *cmd_line = argv[i];
1094                         if (strncmp(cmd_line, debug_option, len) == 0)
1095                                 {
1096                                 gint cmd_line_len = strlen(cmd_line);
1097
1098                                 /* we now increment the debug state for verbosity */
1099                                 if (cmd_line_len == len)
1100                                         debug_level_add(1);
1101                                 else if (cmd_line[len] == '=' && g_ascii_isdigit(cmd_line[len+1]))
1102                                         {
1103                                         gint n = atoi(cmd_line + len + 1);
1104                                         if (n < 0) n = 1;
1105                                         debug_level_add(n);
1106                                         }
1107                                 }
1108                         }
1109                 }
1110
1111         DEBUG_1("debugging output enabled (level %d)", get_debug_level());
1112 #endif
1113 }
1114
1115 /*
1116  *-----------------------------------------------------------------------------
1117  * startup, init, and exit
1118  *-----------------------------------------------------------------------------
1119  */
1120
1121 #define RC_HISTORY_NAME "history"
1122
1123 static void keys_load(void)
1124 {
1125         gchar *path;
1126
1127         path = g_strconcat(homedir(), "/", GQ_RC_DIR, "/", RC_HISTORY_NAME, NULL);
1128         history_list_load(path);
1129         g_free(path);
1130 }
1131
1132 static void keys_save(void)
1133 {
1134         gchar *path;
1135
1136         path = g_strconcat(homedir(), "/", GQ_RC_DIR, "/", RC_HISTORY_NAME, NULL);
1137         history_list_save(path);
1138         g_free(path);
1139 }
1140
1141 static void check_for_home_path(gchar *path)
1142 {
1143         gchar *buf;
1144
1145         buf = g_strconcat(homedir(), "/", path, NULL);
1146         if (!isdir(buf))
1147                 {
1148                 printf_term(_("Creating %s dir:%s\n"), GQ_APPNAME, buf);
1149
1150                 if (!mkdir_utf8(buf, 0755))
1151                         {
1152                         printf_term(_("Could not create dir:%s\n"), buf);
1153                         }
1154                 }
1155         g_free(buf);
1156 }
1157
1158 static void setup_default_options(void)
1159 {
1160         gchar *path;
1161         gint i;
1162
1163         for (i = 0; i < GQ_EDITOR_SLOTS; i++)
1164                 {
1165                 options->editor_name[i] = NULL;
1166                 options->editor_command[i] = NULL;
1167                 }
1168
1169         editor_reset_defaults();
1170
1171         bookmark_add_default(_("Home"), homedir());
1172         path = concat_dir_and_file(homedir(), "Desktop");
1173         bookmark_add_default(_("Desktop"), path);
1174         g_free(path);
1175         path = concat_dir_and_file(homedir(), GQ_RC_DIR_COLLECTIONS);
1176         bookmark_add_default(_("Collections"), path);
1177         g_free(path);
1178
1179         g_free(options->file_ops.safe_delete_path);
1180         options->file_ops.safe_delete_path = concat_dir_and_file(homedir(), GQ_RC_DIR_TRASH);
1181
1182         for (i = 0; i < COLOR_PROFILE_INPUTS; i++)
1183                 {
1184                 options->color_profile.input_file[i] = NULL;
1185                 options->color_profile.input_name[i] = NULL;
1186                 }
1187
1188         set_default_image_overlay_template_string(options);
1189         sidecar_ext_add_defaults();
1190         options->layout.order = g_strdup("123");
1191 }
1192
1193 static void exit_program_final(void)
1194 {
1195         gchar *path;
1196         gchar *pathl;
1197         LayoutWindow *lw = NULL;
1198
1199         remote_close(remote_connection);
1200
1201         collect_manager_flush();
1202
1203         if (layout_valid(&lw))
1204                 {
1205                 options->layout.main_window.maximized =  window_maximized(lw->window);
1206                 if (!options->layout.main_window.maximized)
1207                         {
1208                         layout_geometry_get(NULL, &options->layout.main_window.x, &options->layout.main_window.y,
1209                                             &options->layout.main_window.w, &options->layout.main_window.h);
1210                         }
1211                 options->image_overlay.common.enabled = image_osd_get(lw->image, NULL, NULL);
1212                 }
1213
1214         layout_geometry_get_dividers(NULL, &options->layout.main_window.hdivider_pos, &options->layout.main_window.vdivider_pos);
1215
1216         layout_views_get(NULL, &options->layout.dir_view_type, &options->layout.file_view_type);
1217
1218         options->layout.show_thumbnails = layout_thumb_get(NULL);
1219         options->layout.show_marks = layout_marks_get(NULL);
1220
1221         layout_sort_get(NULL, &options->file_sort.method, &options->file_sort.ascending);
1222
1223         layout_geometry_get_tools(NULL, &options->layout.float_window.x, &options->layout.float_window.y,
1224                                   &options->layout.float_window.w, &options->layout.float_window.h, &options->layout.float_window.vdivider_pos);
1225         layout_tools_float_get(NULL, &options->layout.tools_float, &options->layout.tools_hidden);
1226         options->layout.toolbar_hidden = layout_toolbar_hidden(NULL);
1227
1228         options->color_profile.enabled = layout_image_color_profile_get_use(NULL);
1229         layout_image_color_profile_get(NULL,
1230                                        &options->color_profile.input_type,
1231                                        &options->color_profile.screen_type,
1232                                        &options->color_profile.use_image);
1233
1234         save_options();
1235         keys_save();
1236
1237         path = g_strconcat(homedir(), "/", GQ_RC_DIR, "/accels", NULL);
1238         pathl = path_from_utf8(path);
1239         gtk_accel_map_save(pathl);
1240         g_free(pathl);
1241         g_free(path);
1242
1243         gtk_main_quit();
1244 }
1245
1246 static GenericDialog *exit_dialog = NULL;
1247
1248 static void exit_confirm_cancel_cb(GenericDialog *gd, gpointer data)
1249 {
1250         exit_dialog = NULL;
1251         generic_dialog_close(gd);
1252 }
1253
1254 static void exit_confirm_exit_cb(GenericDialog *gd, gpointer data)
1255 {
1256         exit_dialog = NULL;
1257         generic_dialog_close(gd);
1258         exit_program_final();
1259 }
1260
1261 static gint exit_confirm_dlg(void)
1262 {
1263         GtkWidget *parent;
1264         LayoutWindow *lw;
1265         gchar *msg;
1266
1267         if (exit_dialog)
1268                 {
1269                 gtk_window_present(GTK_WINDOW(exit_dialog->dialog));
1270                 return TRUE;
1271                 }
1272
1273         if (!collection_window_modified_exists()) return FALSE;
1274
1275         parent = NULL;
1276         lw = NULL;
1277         if (layout_valid(&lw))
1278                 {
1279                 parent = lw->window;
1280                 }
1281
1282         msg = g_strdup_printf("%s - %s", GQ_APPNAME, _("exit"));
1283         exit_dialog = generic_dialog_new(msg,
1284                                 GQ_WMCLASS, "exit", parent, FALSE,
1285                                 exit_confirm_cancel_cb, NULL);
1286         g_free(msg);
1287         msg = g_strdup_printf(_("Quit %s"), GQ_APPNAME);
1288         generic_dialog_add_message(exit_dialog, GTK_STOCK_DIALOG_QUESTION,
1289                                    msg, _("Collections have been modified. Quit anyway?"));
1290         g_free(msg);
1291         generic_dialog_add_button(exit_dialog, GTK_STOCK_QUIT, NULL, exit_confirm_exit_cb, TRUE);
1292
1293         gtk_widget_show(exit_dialog->dialog);
1294
1295         return TRUE;
1296 }
1297
1298 void exit_program(void)
1299 {
1300         layout_image_full_screen_stop(NULL);
1301
1302         if (exit_confirm_dlg()) return;
1303
1304         exit_program_final();
1305 }
1306
1307 int main (int argc, char *argv[])
1308 {
1309         LayoutWindow *lw;
1310         gchar *path = NULL;
1311         gchar *cmd_path = NULL;
1312         gchar *cmd_file = NULL;
1313         GList *cmd_list = NULL;
1314         GList *collection_list = NULL;
1315         CollectionData *first_collection = NULL;
1316         gchar *geometry = NULL;
1317         gchar *buf;
1318         gchar *bufl;
1319
1320         /* init execution time counter (debug only) */
1321         init_exec_time();
1322
1323         /* setup locale, i18n */
1324         gtk_set_locale();
1325         bindtextdomain(PACKAGE, GQ_LOCALEDIR);
1326         bind_textdomain_codeset(PACKAGE, "UTF-8");
1327         textdomain(PACKAGE);
1328
1329         /* setup random seed for random slideshow */
1330         srand(time(NULL));
1331
1332 #if 1
1333         printf("%s %s, This is an alpha release.\n", GQ_APPNAME, VERSION);
1334 #endif
1335         parse_command_line_for_debug_option(argc, argv);
1336
1337         options = init_options(NULL);
1338         setup_default_options();
1339         load_options();
1340
1341         parse_command_line(argc, argv, &cmd_path, &cmd_file, &cmd_list, &collection_list, &geometry);
1342
1343         gtk_init(&argc, &argv);
1344
1345         if (gtk_major_version < GTK_MAJOR_VERSION ||
1346             (gtk_major_version == GTK_MAJOR_VERSION && gtk_minor_version < GTK_MINOR_VERSION) )
1347                 {
1348                 printf_term("!!! This is a friendly warning.\n");
1349                 printf_term("!!! The version of GTK+ in use now is older than when %s was compiled.\n", GQ_APPNAME);
1350                 printf_term("!!!  compiled with GTK+-%d.%d\n", GTK_MAJOR_VERSION, GTK_MINOR_VERSION);
1351                 printf_term("!!!   running with GTK+-%d.%d\n", gtk_major_version, gtk_minor_version);
1352                 printf_term("!!! %s may quit unexpectedly with a relocation error.\n", GQ_APPNAME);
1353                 }
1354
1355         check_for_home_path(GQ_RC_DIR);
1356         check_for_home_path(GQ_RC_DIR_COLLECTIONS);
1357         check_for_home_path(GQ_CACHE_RC_THUMB);
1358         check_for_home_path(GQ_CACHE_RC_METADATA);
1359
1360         keys_load();
1361         filter_add_defaults();
1362         filter_rebuild();
1363
1364         buf = g_strconcat(homedir(), "/", GQ_RC_DIR, "/accels", NULL);
1365         bufl = path_from_utf8(buf);
1366         gtk_accel_map_load(bufl);
1367         g_free(bufl);
1368         g_free(buf);
1369
1370         if (startup_blank)
1371                 {
1372                 g_free(cmd_path);
1373                 cmd_path = NULL;
1374                 g_free(cmd_file);
1375                 cmd_file = NULL;
1376                 filelist_free(cmd_list);
1377                 cmd_list = NULL;
1378                 string_list_free(collection_list);
1379                 collection_list = NULL;
1380
1381                 path = NULL;
1382                 }
1383         else if (cmd_path)
1384                 {
1385                 path = g_strdup(cmd_path);
1386                 }
1387         else if (options->startup_path_enable && options->startup_path && isdir(options->startup_path))
1388                 {
1389                 path = g_strdup(options->startup_path);
1390                 }
1391         else
1392                 {
1393                 path = get_current_dir();
1394                 }
1395
1396         lw = layout_new_with_geometry(NULL, options->layout.tools_float, options->layout.tools_hidden, geometry);
1397         layout_sort_set(lw, options->file_sort.method, options->file_sort.ascending);
1398
1399         if (collection_list && !startup_command_line_collection)
1400                 {
1401                 GList *work;
1402
1403                 work = collection_list;
1404                 while (work)
1405                         {
1406                         CollectWindow *cw;
1407                         const gchar *path;
1408
1409                         path = work->data;
1410                         work = work->next;
1411
1412                         cw = collection_window_new(path);
1413                         if (!first_collection && cw) first_collection = cw->cd;
1414                         }
1415                 }
1416
1417         if (cmd_list ||
1418             (startup_command_line_collection && collection_list))
1419                 {
1420                 CollectionData *cd;
1421                 GList *work;
1422
1423                 if (startup_command_line_collection)
1424                         {
1425                         CollectWindow *cw;
1426
1427                         cw = collection_window_new("");
1428                         cd = cw->cd;
1429                         }
1430                 else
1431                         {
1432                         cd = collection_new("");        /* if we pass NULL, untitled counter is falsely increm. */
1433                         command_collection = cd;
1434                         }
1435
1436                 g_free(cd->path);
1437                 cd->path = NULL;
1438                 g_free(cd->name);
1439                 cd->name = g_strdup(_("Command line"));
1440
1441                 collection_path_changed(cd);
1442
1443                 work = cmd_list;
1444                 while (work)
1445                         {
1446                         collection_add(cd, file_data_new_simple((gchar *)work->data), FALSE);
1447                         work = work->next;
1448                         }
1449
1450                 work = collection_list;
1451                 while (work)
1452                         {
1453                         collection_load(cd, (gchar *)work->data, COLLECTION_LOAD_APPEND);
1454                         work = work->next;
1455                         }
1456
1457                 layout_set_path(lw, path);
1458                 if (cd->list) layout_image_set_collection(lw, cd, cd->list->data);
1459
1460                 /* mem leak, we never unref this collection when !startup_command_line_collection
1461                  * (the image view of the main window does not hold a ref to the collection)
1462                  * this is sort of unavoidable, for if it did hold a ref, next/back
1463                  * may not work as expected when closing collection windows.
1464                  *
1465                  * collection_unref(cd);
1466                  */
1467
1468                 }
1469         else if (cmd_file)
1470                 {
1471                 layout_set_path(lw, cmd_file);
1472                 }
1473         else
1474                 {
1475                 layout_set_path(lw, path);
1476                 if (first_collection)
1477                         {
1478                         layout_image_set_collection(lw, first_collection,
1479                                                     collection_get_first(first_collection));
1480                         }
1481                 }
1482         image_osd_set(lw->image, FALSE, (options->image_overlay.common.show_at_startup || options->image_overlay.common.enabled));
1483
1484         g_free(geometry);
1485         g_free(cmd_path);
1486         g_free(cmd_file);
1487         filelist_free(cmd_list);
1488         string_list_free(collection_list);
1489         g_free(path);
1490
1491         if (startup_full_screen) layout_image_full_screen_start(lw);
1492         if (startup_in_slideshow) layout_image_slideshow_start(lw);
1493
1494         buf = g_strconcat(homedir(), "/", GQ_RC_DIR, "/.command", NULL);
1495         remote_connection = remote_server_open(buf);
1496         remote_server_subscribe(remote_connection, remote_cb, NULL);
1497         g_free(buf);
1498
1499         gtk_main();
1500         return 0;
1501 }